From d2b7ff5f43e306d9f188478b1e0cd6ea26c5b020 Mon Sep 17 00:00:00 2001 From: Zero <128979226+lux-zero-161@users.noreply.github.com> Date: Wed, 10 Jun 2026 16:12:51 +0200 Subject: [PATCH] Add RmlUi --- CMakeLists.txt | 4 +- CMakePresets.json | 3 +- .../Assets/RmlUiAssets/HelpOptions.rcss | 55 + .../Assets/RmlUiAssets/HelpOptions.rml | 17 + .../Assets/RmlUiAssets/PauseMenu.rcss | 158 + .../Assets/RmlUiAssets/PauseMenu.rml | 32 + .../Assets/RmlUiAssets/SettingsMenu.rcss | 104 + .../Assets/RmlUiAssets/SettingsMenu.rml | 25 + .../Assets/RmlUiAssets/fonts/MinecraftTen.ttf | Bin 0 -> 218588 bytes .../Assets/RmlUiAssets/fonts/Mojangles.ttf | Bin 0 -> 235228 bytes .../Assets/RmlUiAssets/fonts/chars.txt | Bin 0 -> 564 bytes .../RmlUiAssets/images/AdventureIcon.png | Bin 0 -> 751 bytes .../RmlUiAssets/images/Button_Background.png | Bin 0 -> 1699 bytes .../RmlUiAssets/images/Button_Background2.png | Bin 0 -> 1578 bytes .../RmlUiAssets/images/Button_Square.png | Bin 0 -> 16088 bytes .../images/Button_Square_Highlighted.png | Bin 0 -> 16487 bytes .../RmlUiAssets/images/Change_Skin_Icon.png | Bin 0 -> 29706 bytes .../RmlUiAssets/images/CreateWorldIcon.png | Bin 0 -> 834 bytes .../RmlUiAssets/images/CreativeIcon.png | Bin 0 -> 14781 bytes .../Assets/RmlUiAssets/images/ModIcon.png | Bin 0 -> 1014 bytes .../RmlUiAssets/images/OriginalLogo.png | Bin 0 -> 541893 bytes .../Assets/RmlUiAssets/images/SaveChest.png | Bin 0 -> 2694 bytes .../images/SliderHandlerBackground.png | Bin 0 -> 53759 bytes .../RmlUiAssets/images/Slider_Handle.png | Bin 0 -> 78267 bytes .../RmlUiAssets/images/SurvivalIcon.png | Bin 0 -> 444 bytes .../RmlUiAssets/images/Toggle_Switch_Off.png | Bin 0 -> 10306 bytes .../RmlUiAssets/images/Toggle_Switch_On.png | Bin 0 -> 10320 bytes .../RmlUiAssets/images/Trash_Bin_Icon.png | Bin 0 -> 70075 bytes .../RmlUiAssets/images/button_highlighted.png | Bin 0 -> 3770 bytes .../Assets/RmlUiAssets/images/down_icon.png | Bin 0 -> 491 bytes .../RmlUiAssets/images/frame_background.png | Bin 0 -> 6599 bytes .../Assets/RmlUiAssets/images/lceheadwer.png | Bin 0 -> 438 bytes .../Assets/RmlUiAssets/images/up_icon.png | Bin 0 -> 596 bytes .../RmlUiAssets/panorama/Background_Day.png | Bin 0 -> 147533 bytes .../RmlUiAssets/panorama/Background_Night.png | Bin 0 -> 145173 bytes Minecraft.Client/CMakeLists.txt | 105 +- Minecraft.Client/Common/Consoles_App.h | 6 + .../Common/UI/IUIScene_PauseMenu.cpp | 276 +- .../Common/UI/IUIScene_PauseMenu.h | 4 - .../Common/UI/IUIScene_StartGame.cpp | 3 +- .../Common/UI/UIControl_EnchantmentButton.cpp | 1 + Minecraft.Client/Common/UI/UIEnums.h | 1 + Minecraft.Client/Common/UI/UIScene.h | 2 +- .../Common/UI/UIScene_HelpAndOptionsMenu.cpp | 407 +- .../Common/UI/UIScene_HelpAndOptionsMenu.h | 77 +- .../Common/UI/UIScene_LoadCreateJoinMenu.cpp | 7 + .../Common/UI/UIScene_PauseMenu.cpp | 1000 +- .../Common/UI/UIScene_PauseMenu.h | 133 +- .../Common/UI/UIScene_SettingsMenu.cpp | 378 +- .../Common/UI/UIScene_SettingsMenu.h | 80 +- Minecraft.Client/Common/UI/UIStructs.h | 25 +- Minecraft.Client/GameRenderer.cpp | 2 +- Minecraft.Client/MinecraftServer.h | 5 + Minecraft.Client/RmlManager.cpp | 156 + Minecraft.Client/RmlManager.h | 52 + Minecraft.Client/Tesselator.h | 2 + Minecraft.Client/TileRenderer.cpp | 2 +- .../Render/vendor/DirectXMath/DirectXColors.h | 311 + .../Render/vendor/DirectXMath/DirectXMath.h | 2385 ++ .../vendor/DirectXMath/DirectXMathConvert.inl | 2219 ++ .../vendor/DirectXMath/DirectXMathMatrix.inl | 3554 +++ .../vendor/DirectXMath/DirectXMathMisc.inl | 2493 ++ .../vendor/DirectXMath/DirectXMathVector.inl | 14883 ++++++++++++ .../vendor/DirectXMath/DirectXPackedVector.h | 1252 + .../DirectXMath/DirectXPackedVector.inl | 4628 ++++ .../Windows64/Libs/4JLibs/inc/4J_Render.h | 14 +- .../Windows64/Libs/Iggy/inc/rrCore.h | 6 +- .../Windows64/Libs/RmlUi/.appveyor.yml | 27 + .../Windows64/Libs/RmlUi/.clang-format | 69 + .../Windows64/Libs/RmlUi/.editorconfig | 24 + .../Windows64/Libs/RmlUi/.gitignore | 42 + .../Libs/RmlUi/Backends/CMakeLists.txt | 253 + .../Libs/RmlUi/Backends/RmlUi_Backend.h | 41 + .../Backends/RmlUi_Backend_GLFW_DX12.cpp | 280 + .../RmlUi/Backends/RmlUi_Backend_GLFW_GL2.cpp | 227 + .../RmlUi/Backends/RmlUi_Backend_GLFW_GL3.cpp | 235 + .../RmlUi/Backends/RmlUi_Backend_GLFW_VK.cpp | 268 + .../RmlUi/Backends/RmlUi_Backend_SDL_DX12.cpp | 394 + .../RmlUi/Backends/RmlUi_Backend_SDL_GL2.cpp | 353 + .../RmlUi/Backends/RmlUi_Backend_SDL_GL3.cpp | 388 + .../RmlUi/Backends/RmlUi_Backend_SDL_GPU.cpp | 246 + .../RmlUi_Backend_SDL_SDLrenderer.cpp | 242 + .../RmlUi/Backends/RmlUi_Backend_SDL_VK.cpp | 319 + .../RmlUi/Backends/RmlUi_Backend_SFML_GL2.cpp | 296 + .../Backends/RmlUi_Backend_Win32_DX12.cpp | 393 + .../Backends/RmlUi_Backend_Win32_GL2.cpp | 444 + .../RmlUi/Backends/RmlUi_Backend_Win32_VK.cpp | 395 + .../RmlUi/Backends/RmlUi_Backend_X11_GL2.cpp | 292 + ...Ui_Backend_BackwardCompatible_GLFW_GL2.cpp | 226 + ...Ui_Backend_BackwardCompatible_GLFW_GL3.cpp | 240 + .../RmlUi_Renderer_BackwardCompatible_GL2.cpp | 300 + .../RmlUi_Renderer_BackwardCompatible_GL2.h | 46 + .../RmlUi_Renderer_BackwardCompatible_GL3.cpp | 765 + .../RmlUi_Renderer_BackwardCompatible_GL3.h | 117 + .../Backends/RmlUi_DirectX/.clang-format | 2 + .../Backends/RmlUi_DirectX/D3D12MemAlloc.cpp | 10247 ++++++++ .../Backends/RmlUi_DirectX/D3D12MemAlloc.h | 2533 ++ .../RmlUi/Backends/RmlUi_DirectX/LICENSE.txt | 74 + .../RmlUi_DirectX/offsetAllocator.cpp | 513 + .../RmlUi_DirectX/offsetAllocator.hpp | 97 + .../RmlUi/Backends/RmlUi_Include_DirectX_12.h | 284 + .../Libs/RmlUi/Backends/RmlUi_Include_GL3.h | 3582 +++ .../RmlUi/Backends/RmlUi_Include_Vulkan.h | 32 + .../RmlUi/Backends/RmlUi_Include_Windows.h | 20 + .../Libs/RmlUi/Backends/RmlUi_Include_Xlib.h | 18 + .../RmlUi/Backends/RmlUi_Platform_GLFW.cpp | 330 + .../Libs/RmlUi/Backends/RmlUi_Platform_GLFW.h | 56 + .../RmlUi/Backends/RmlUi_Platform_SDL.cpp | 569 + .../Libs/RmlUi/Backends/RmlUi_Platform_SDL.h | 78 + .../RmlUi/Backends/RmlUi_Platform_SFML.cpp | 287 + .../Libs/RmlUi/Backends/RmlUi_Platform_SFML.h | 60 + .../RmlUi/Backends/RmlUi_Platform_Win32.cpp | 730 + .../RmlUi/Backends/RmlUi_Platform_Win32.h | 119 + .../RmlUi/Backends/RmlUi_Platform_X11.cpp | 610 + .../Libs/RmlUi/Backends/RmlUi_Platform_X11.h | 75 + .../RmlUi/Backends/RmlUi_Renderer_DX12.cpp | 9995 ++++++++ .../Libs/RmlUi/Backends/RmlUi_Renderer_DX12.h | 696 + .../RmlUi/Backends/RmlUi_Renderer_GL2.cpp | 325 + .../Libs/RmlUi/Backends/RmlUi_Renderer_GL2.h | 49 + .../RmlUi/Backends/RmlUi_Renderer_GL3.cpp | 2171 ++ .../Libs/RmlUi/Backends/RmlUi_Renderer_GL3.h | 210 + .../RmlUi/Backends/RmlUi_Renderer_SDL.cpp | 212 + .../Libs/RmlUi/Backends/RmlUi_Renderer_SDL.h | 46 + .../RmlUi/Backends/RmlUi_Renderer_SDL_GPU.cpp | 630 + .../RmlUi/Backends/RmlUi_Renderer_SDL_GPU.h | 122 + .../Libs/RmlUi/Backends/RmlUi_Renderer_VK.cpp | 3017 +++ .../Libs/RmlUi/Backends/RmlUi_Renderer_VK.h | 563 + .../RmlUi_SDL_GPU/SDL_shadercross/LICENSE.txt | 28 + .../RmlUi_SDL_GPU/ShadersCompiledSPV.h | 817 + .../Backends/RmlUi_SDL_GPU/compile_shaders.py | 58 + .../RmlUi_SDL_GPU/shader_frag_color.frag | 3 + .../RmlUi_SDL_GPU/shader_frag_texture.frag | 8 + .../Backends/RmlUi_SDL_GPU/shader_vert.vert | 28 + .../RmlUi/Backends/RmlUi_Vulkan/.clang-format | 2 + .../RmlUi/Backends/RmlUi_Vulkan/LICENSE.txt | 29 + .../RmlUi_Vulkan/ShadersCompiledSPV.h | 180 + .../Backends/RmlUi_Vulkan/compile_shaders.py | 38 + .../RmlUi_Vulkan/shader_frag_color.frag | 12 + .../RmlUi_Vulkan/shader_frag_texture.frag | 15 + .../Backends/RmlUi_Vulkan/shader_vert.vert | 25 + .../Backends/RmlUi_Vulkan/vk_mem_alloc.h | 19558 ++++++++++++++++ .../Libs/RmlUi/Backends/RmlUi_Vulkan/vulkan.h | 4486 ++++ .../RmlUi/CMake/BackendAutoSelection.cmake | 33 + .../Libs/RmlUi/CMake/Dependencies.cmake | 107 + .../RmlUi/CMake/DependenciesForBackends.cmake | 213 + .../RmlUi/CMake/DependenciesForShell.cmake | 37 + .../RmlUi/CMake/DependenciesForTests.cmake | 24 + .../Modules/Emscripten/FindFreetype.cmake | 63 + .../CMake/Modules/Emscripten/FindOpenGL.cmake | 84 + .../CMake/Modules/Emscripten/FindSDL2.cmake | 66 + .../Modules/Emscripten/FindSDL2_image.cmake | 82 + .../CMake/Modules/Emscripten/Findglfw3.cmake | 66 + .../RmlUi/CMake/Modules/FindHarfBuzz.cmake | 40 + .../Libs/RmlUi/CMake/Modules/FindLuaJIT.cmake | 16 + .../RmlUi/CMake/Modules/FindSDL2_image.cmake | 55 + .../RmlUi/CMake/Modules/Findlunasvg.cmake | 83 + .../Libs/RmlUi/CMake/OptionsLists.cmake | 36 + .../Libs/RmlUi/CMake/PackageUtilities.cmake | 85 + .../Libs/RmlUi/CMake/RmlUiConfig.cmake.in | 36 + .../Libs/RmlUi/CMake/RuntimeUtilities.cmake | 53 + .../Libs/RmlUi/CMake/Utilities.cmake | 233 + .../Windows64/Libs/RmlUi/CMakeLists.txt | 210 + .../Windows64/Libs/RmlUi/CMakePresets.json | 58 + .../Libs/RmlUi/Dependencies/.gitignore | 4 + .../Libs/RmlUi/Include/RmlUi/Config/Config.h | 187 + .../Windows64/Libs/RmlUi/Include/RmlUi/Core.h | 82 + .../Libs/RmlUi/Include/RmlUi/Core/Animation.h | 65 + .../RmlUi/Include/RmlUi/Core/BaseXMLParser.h | 108 + .../Libs/RmlUi/Include/RmlUi/Core/Box.h | 83 + .../Include/RmlUi/Core/CallbackTexture.h | 96 + .../Libs/RmlUi/Include/RmlUi/Core/Colour.h | 126 + .../Libs/RmlUi/Include/RmlUi/Core/Colour.inl | 69 + .../Include/RmlUi/Core/CompiledFilterShader.h | 44 + .../RmlUi/Include/RmlUi/Core/ComputedValues.h | 438 + .../Include/RmlUi/Core/Containers/LICENSE.txt | 58 + .../RmlUi/Core/Containers/itlib/flat_map.hpp | 387 + .../RmlUi/Core/Containers/itlib/flat_set.hpp | 325 + .../RmlUi/Core/Containers/robin_hood.h | 2401 ++ .../Libs/RmlUi/Include/RmlUi/Core/Context.h | 447 + .../Include/RmlUi/Core/ContextInstancer.h | 34 + .../Include/RmlUi/Core/ConvolutionFilter.h | 61 + .../Libs/RmlUi/Include/RmlUi/Core/Core.h | 173 + .../Include/RmlUi/Core/DataModelHandle.h | 197 + .../Include/RmlUi/Core/DataStructHandle.h | 183 + .../Include/RmlUi/Core/DataTypeRegister.h | 116 + .../Libs/RmlUi/Include/RmlUi/Core/DataTypes.h | 82 + .../RmlUi/Include/RmlUi/Core/DataVariable.h | 286 + .../Libs/RmlUi/Include/RmlUi/Core/Debug.h | 98 + .../Include/RmlUi/Core/DecorationTypes.h | 38 + .../Libs/RmlUi/Include/RmlUi/Core/Decorator.h | 104 + .../RmlUi/Include/RmlUi/Core/Dictionary.h | 32 + .../Include/RmlUi/Core/EffectSpecification.h | 43 + .../Libs/RmlUi/Include/RmlUi/Core/Element.h | 809 + .../Libs/RmlUi/Include/RmlUi/Core/Element.inl | 31 + .../Include/RmlUi/Core/ElementDocument.h | 179 + .../Include/RmlUi/Core/ElementInstancer.h | 94 + .../RmlUi/Include/RmlUi/Core/ElementScroll.h | 76 + .../RmlUi/Include/RmlUi/Core/ElementText.h | 110 + .../Include/RmlUi/Core/ElementUtilities.h | 112 + .../Include/RmlUi/Core/Elements/ElementForm.h | 27 + .../RmlUi/Core/Elements/ElementFormControl.h | 53 + .../Core/Elements/ElementFormControlInput.h | 92 + .../Core/Elements/ElementFormControlSelect.h | 103 + .../Elements/ElementFormControlTextArea.h | 110 + .../RmlUi/Core/Elements/ElementProgress.h | 92 + .../RmlUi/Core/Elements/ElementTabSet.h | 66 + .../Libs/RmlUi/Include/RmlUi/Core/Event.h | 121 + .../RmlUi/Include/RmlUi/Core/EventInstancer.h | 33 + .../RmlUi/Include/RmlUi/Core/EventListener.h | 30 + .../RmlUi/Core/EventListenerInstancer.h | 31 + .../Libs/RmlUi/Include/RmlUi/Core/Factory.h | 191 + .../RmlUi/Include/RmlUi/Core/FileInterface.h | 61 + .../Libs/RmlUi/Include/RmlUi/Core/Filter.h | 49 + .../RmlUi/Include/RmlUi/Core/FontEffect.h | 63 + .../Include/RmlUi/Core/FontEffectInstancer.h | 58 + .../Include/RmlUi/Core/FontEngineInterface.h | 116 + .../Libs/RmlUi/Include/RmlUi/Core/FontGlyph.h | 42 + .../RmlUi/Include/RmlUi/Core/FontMetrics.h | 22 + .../Libs/RmlUi/Include/RmlUi/Core/Geometry.h | 35 + .../Libs/RmlUi/Include/RmlUi/Core/Header.h | 55 + .../Libs/RmlUi/Include/RmlUi/Core/ID.h | 252 + .../Libs/RmlUi/Include/RmlUi/Core/Input.h | 244 + .../Libs/RmlUi/Include/RmlUi/Core/Log.h | 36 + .../Libs/RmlUi/Include/RmlUi/Core/Math.h | 219 + .../Libs/RmlUi/Include/RmlUi/Core/Matrix4.h | 467 + .../Libs/RmlUi/Include/RmlUi/Core/Matrix4.inl | 629 + .../Libs/RmlUi/Include/RmlUi/Core/Mesh.h | 25 + .../RmlUi/Include/RmlUi/Core/MeshUtilities.h | 60 + .../RmlUi/Include/RmlUi/Core/NumericValue.h | 25 + .../RmlUi/Include/RmlUi/Core/ObserverPtr.h | 167 + .../Libs/RmlUi/Include/RmlUi/Core/Platform.h | 50 + .../Libs/RmlUi/Include/RmlUi/Core/Plugin.h | 64 + .../Libs/RmlUi/Include/RmlUi/Core/Profiling.h | 49 + .../RmlUi/Core/PropertiesIteratorView.h | 45 + .../Libs/RmlUi/Include/RmlUi/Core/Property.h | 60 + .../Include/RmlUi/Core/PropertyDefinition.h | 68 + .../Include/RmlUi/Core/PropertyDictionary.h | 56 + .../RmlUi/Include/RmlUi/Core/PropertyIdSet.h | 133 + .../RmlUi/Include/RmlUi/Core/PropertyParser.h | 26 + .../RmlUi/Core/PropertySpecification.h | 115 + .../Libs/RmlUi/Include/RmlUi/Core/Rectangle.h | 85 + .../Libs/RmlUi/Include/RmlUi/Core/RenderBox.h | 59 + .../Include/RmlUi/Core/RenderInterface.h | 143 + .../RmlUi/Core/RenderInterfaceCompatibility.h | 91 + .../RmlUi/Include/RmlUi/Core/RenderManager.h | 128 + .../Include/RmlUi/Core/ScriptInterface.h | 22 + .../RmlUi/Include/RmlUi/Core/ScrollTypes.h | 38 + .../Libs/RmlUi/Include/RmlUi/Core/Span.h | 41 + .../RmlUi/Include/RmlUi/Core/Spritesheet.h | 57 + .../RmlUi/Include/RmlUi/Core/StableVector.h | 95 + .../Libs/RmlUi/Include/RmlUi/Core/Stream.h | 100 + .../RmlUi/Include/RmlUi/Core/StreamMemory.h | 83 + .../Include/RmlUi/Core/StringUtilities.h | 204 + .../RmlUi/Include/RmlUi/Core/StyleSheet.h | 97 + .../Include/RmlUi/Core/StyleSheetContainer.h | 47 + .../RmlUi/Core/StyleSheetSpecification.h | 103 + .../Include/RmlUi/Core/StyleSheetTypes.h | 96 + .../RmlUi/Include/RmlUi/Core/StyleTypes.h | 145 + .../Include/RmlUi/Core/SystemInterface.h | 65 + .../Include/RmlUi/Core/TextInputContext.h | 62 + .../Include/RmlUi/Core/TextInputHandler.h | 35 + .../Include/RmlUi/Core/TextShapingContext.h | 18 + .../Libs/RmlUi/Include/RmlUi/Core/Texture.h | 57 + .../Libs/RmlUi/Include/RmlUi/Core/Traits.h | 160 + .../Libs/RmlUi/Include/RmlUi/Core/Transform.h | 53 + .../Include/RmlUi/Core/TransformPrimitive.h | 233 + .../Libs/RmlUi/Include/RmlUi/Core/Tween.h | 40 + .../RmlUi/Include/RmlUi/Core/TypeConverter.h | 152 + .../Include/RmlUi/Core/TypeConverter.inl | 442 + .../Libs/RmlUi/Include/RmlUi/Core/Types.h | 170 + .../Libs/RmlUi/Include/RmlUi/Core/URL.h | 112 + .../Include/RmlUi/Core/UniqueRenderResource.h | 56 + .../Libs/RmlUi/Include/RmlUi/Core/Unit.h | 85 + .../Libs/RmlUi/Include/RmlUi/Core/Utilities.h | 20 + .../Libs/RmlUi/Include/RmlUi/Core/Variant.h | 145 + .../Libs/RmlUi/Include/RmlUi/Core/Variant.inl | 92 + .../Libs/RmlUi/Include/RmlUi/Core/Vector2.h | 136 + .../Libs/RmlUi/Include/RmlUi/Core/Vector2.inl | 206 + .../Libs/RmlUi/Include/RmlUi/Core/Vector3.h | 122 + .../Libs/RmlUi/Include/RmlUi/Core/Vector3.inl | 171 + .../Libs/RmlUi/Include/RmlUi/Core/Vector4.h | 127 + .../Libs/RmlUi/Include/RmlUi/Core/Vector4.inl | 185 + .../Libs/RmlUi/Include/RmlUi/Core/Vertex.h | 27 + .../RmlUi/Include/RmlUi/Core/XMLNodeHandler.h | 39 + .../Libs/RmlUi/Include/RmlUi/Core/XMLParser.h | 89 + .../Libs/RmlUi/Include/RmlUi/Debugger.h | 3 + .../RmlUi/Include/RmlUi/Debugger/Debugger.h | 35 + .../RmlUi/Include/RmlUi/Debugger/Header.h | 17 + .../Include/RmlUi/Lottie/ElementLottie.h | 79 + .../Windows64/Libs/RmlUi/Include/RmlUi/Lua.h | 8 + .../Libs/RmlUi/Include/RmlUi/Lua/Header.h | 21 + .../Libs/RmlUi/Include/RmlUi/Lua/IncludeLua.h | 14 + .../RmlUi/Include/RmlUi/Lua/Interpreter.h | 53 + .../Libs/RmlUi/Include/RmlUi/Lua/Lua.h | 22 + .../Libs/RmlUi/Include/RmlUi/Lua/LuaType.h | 172 + .../Libs/RmlUi/Include/RmlUi/Lua/LuaType.inl | 247 + .../Libs/RmlUi/Include/RmlUi/Lua/Utilities.h | 69 + .../Libs/RmlUi/Include/RmlUi/SVG/ElementSVG.h | 56 + .../Windows64/Libs/RmlUi/LICENSE.txt | 22 + .../Windows64/Libs/RmlUi/Samples/.gitignore | 1 + .../Libs/RmlUi/Samples/CMakeLists.txt | 43 + .../Libs/RmlUi/Samples/assets/LICENSE.txt | 316 + .../RmlUi/Samples/assets/LatoLatin-Bold.ttf | Bin 0 -> 146156 bytes .../Samples/assets/LatoLatin-BoldItalic.ttf | Bin 0 -> 149756 bytes .../RmlUi/Samples/assets/LatoLatin-Italic.ttf | Bin 0 -> 153688 bytes .../Samples/assets/LatoLatin-Regular.ttf | Bin 0 -> 148540 bytes .../Samples/assets/NotoEmoji-Regular.ttf | Bin 0 -> 418804 bytes .../RmlUi/Samples/assets/RobotoMono-Bold.ttf | Bin 0 -> 87696 bytes .../Samples/assets/RobotoMono-BoldItalic.ttf | Bin 0 -> 95100 bytes .../Samples/assets/RobotoMono-Italic.ttf | Bin 0 -> 94836 bytes .../Samples/assets/RobotoMono-Regular.ttf | Bin 0 -> 87540 bytes .../Libs/RmlUi/Samples/assets/alien_small.tga | Bin 0 -> 4140 bytes .../Libs/RmlUi/Samples/assets/demo.rml | 28 + .../Libs/RmlUi/Samples/assets/hello_world.png | Bin 0 -> 10463 bytes .../Samples/assets/high_scores_alien_1.tga | Bin 0 -> 16402 bytes .../Samples/assets/high_scores_alien_2.tga | Bin 0 -> 16402 bytes .../Samples/assets/high_scores_alien_3.tga | Bin 0 -> 16402 bytes .../Samples/assets/high_scores_defender.tga | Bin 0 -> 32786 bytes .../Libs/RmlUi/Samples/assets/invader.rcss | 769 + .../Libs/RmlUi/Samples/assets/invader.tga | Bin 0 -> 1049115 bytes .../Libs/RmlUi/Samples/assets/present.tga | Bin 0 -> 40539 bytes .../Libs/RmlUi/Samples/assets/rml.rcss | 61 + .../Libs/RmlUi/Samples/assets/window.rml | 19 + .../Libs/RmlUi/Samples/basic/CMakeLists.txt | 41 + .../Samples/basic/animation/CMakeLists.txt | 12 + .../basic/animation/data/animation.rml | 273 + .../Samples/basic/animation/src/main.cpp | 338 + .../Samples/basic/benchmark/CMakeLists.txt | 12 + .../basic/benchmark/data/benchmark.rml | 36 + .../Samples/basic/benchmark/src/main.cpp | 250 + .../Samples/basic/bitmap_font/CMakeLists.txt | 16 + .../bitmap_font/data/Comfortaa_Regular_22.fnt | 758 + .../bitmap_font/data/Comfortaa_Regular_22.tga | Bin 0 -> 131116 bytes .../basic/bitmap_font/data/LICENSE.txt | 49 + .../basic/bitmap_font/data/bitmap_font.rml | 42 + .../bitmap_font/src/FontEngineBitmap.cpp | 264 + .../basic/bitmap_font/src/FontEngineBitmap.h | 90 + .../src/FontEngineInterfaceBitmap.cpp | 73 + .../src/FontEngineInterfaceBitmap.h | 67 + .../Samples/basic/bitmap_font/src/main.cpp | 97 + .../Samples/basic/custom_log/CMakeLists.txt | 14 + .../basic/custom_log/src/SystemInterface.cpp | 53 + .../basic/custom_log/src/SystemInterface.h | 22 + .../Samples/basic/custom_log/src/main.cpp | 86 + .../Samples/basic/data_binding/CMakeLists.txt | 12 + .../basic/data_binding/data/data_binding.rml | 404 + .../Samples/basic/data_binding/src/main.cpp | 503 + .../RmlUi/Samples/basic/demo/CMakeLists.txt | 21 + .../RmlUi/Samples/basic/demo/data/demo.rml | 937 + .../basic/demo/src/DemoEventListener.cpp | 179 + .../basic/demo/src/DemoEventListener.h | 31 + .../Samples/basic/demo/src/DemoWindow.cpp | 220 + .../RmlUi/Samples/basic/demo/src/DemoWindow.h | 42 + .../RmlUi/Samples/basic/demo/src/main.cpp | 89 + .../RmlUi/Samples/basic/drag/CMakeLists.txt | 16 + .../RmlUi/Samples/basic/drag/data/icon.rcss | 22 + .../Samples/basic/drag/data/inventory.rml | 22 + .../Samples/basic/drag/src/DragListener.cpp | 55 + .../Samples/basic/drag/src/DragListener.h | 13 + .../Samples/basic/drag/src/Inventory.cpp | 40 + .../RmlUi/Samples/basic/drag/src/Inventory.h | 23 + .../RmlUi/Samples/basic/drag/src/main.cpp | 80 + .../Samples/basic/effects/CMakeLists.txt | 17 + .../Samples/basic/effects/data/effects.rml | 194 + .../basic/effects/data/effects_style.rcss | 199 + .../RmlUi/Samples/basic/effects/src/main.cpp | 130 + .../Samples/basic/harfbuzz/CMakeLists.txt | 43 + .../basic/harfbuzz/data/Cairo-Regular.ttf | Bin 0 -> 94484 bytes .../Samples/basic/harfbuzz/data/LICENSE.txt | 209 + .../basic/harfbuzz/data/Poppins-Regular.ttf | Bin 0 -> 158240 bytes .../Samples/basic/harfbuzz/data/harfbuzz.rml | 66 + .../src/FontEngineInterfaceHarfBuzz.cpp | 75 + .../src/FontEngineInterfaceHarfBuzz.h | 64 + .../Samples/basic/harfbuzz/src/FontFace.cpp | 59 + .../Samples/basic/harfbuzz/src/FontFace.h | 42 + .../harfbuzz/src/FontFaceHandleHarfBuzz.cpp | 803 + .../harfbuzz/src/FontFaceHandleHarfBuzz.h | 179 + .../basic/harfbuzz/src/FontFaceLayer.cpp | 366 + .../basic/harfbuzz/src/FontFaceLayer.h | 136 + .../Samples/basic/harfbuzz/src/FontFamily.cpp | 62 + .../Samples/basic/harfbuzz/src/FontFamily.h | 54 + .../Samples/basic/harfbuzz/src/FontGlyph.h | 40 + .../basic/harfbuzz/src/FontProvider.cpp | 231 + .../Samples/basic/harfbuzz/src/FontProvider.h | 74 + .../basic/harfbuzz/src/FreeTypeInterface.cpp | 408 + .../basic/harfbuzz/src/FreeTypeInterface.h | 20 + .../Samples/basic/harfbuzz/src/LanguageData.h | 15 + .../basic/harfbuzz/src/TextureLayout.cpp | 64 + .../basic/harfbuzz/src/TextureLayout.h | 55 + .../harfbuzz/src/TextureLayoutRectangle.cpp | 64 + .../harfbuzz/src/TextureLayoutRectangle.h | 63 + .../basic/harfbuzz/src/TextureLayoutRow.cpp | 70 + .../basic/harfbuzz/src/TextureLayoutRow.h | 46 + .../harfbuzz/src/TextureLayoutTexture.cpp | 119 + .../basic/harfbuzz/src/TextureLayoutTexture.h | 44 + .../RmlUi/Samples/basic/harfbuzz/src/main.cpp | 156 + .../RmlUi/Samples/basic/ime/CMakeLists.txt | 40 + .../Libs/RmlUi/Samples/basic/ime/data/ime.rml | 119 + .../Samples/basic/ime/src/SystemFontWin32.cpp | 55 + .../Samples/basic/ime/src/SystemFontWin32.h | 5 + .../Libs/RmlUi/Samples/basic/ime/src/main.cpp | 117 + .../basic/load_document/CMakeLists.txt | 12 + .../Samples/basic/load_document/src/main.cpp | 78 + .../RmlUi/Samples/basic/lottie/CMakeLists.txt | 12 + .../Samples/basic/lottie/data/LICENSE.txt | 12 + .../Samples/basic/lottie/data/a_mountain.json | 1 + .../Samples/basic/lottie/data/lottie.rml | 33 + .../RmlUi/Samples/basic/lottie/src/main.cpp | 73 + .../RmlUi/Samples/basic/svg/CMakeLists.txt | 12 + .../RmlUi/Samples/basic/svg/data/LICENSE.txt | 669 + .../Samples/basic/svg/data/svg_decorator.rml | 27 + .../Samples/basic/svg/data/svg_element.rml | 25 + .../Samples/basic/svg/data/svg_inline.rml | 32 + .../RmlUi/Samples/basic/svg/data/tiger.svg | 725 + .../Libs/RmlUi/Samples/basic/svg/src/main.cpp | 121 + .../Samples/basic/transform/CMakeLists.txt | 12 + .../basic/transform/data/transform.rml | 225 + .../Samples/basic/transform/src/main.cpp | 165 + .../Samples/basic/tree_view/CMakeLists.txt | 14 + .../basic/tree_view/data/tree_view.rml | 54 + .../basic/tree_view/src/FileBrowser.cpp | 98 + .../Samples/basic/tree_view/src/FileBrowser.h | 9 + .../Samples/basic/tree_view/src/main.cpp | 79 + .../RmlUi/Samples/invaders/CMakeLists.txt | 48 + .../Samples/invaders/data/background.rml | 41 + .../Samples/invaders/data/background.tga | Bin 0 -> 4194322 bytes .../Libs/RmlUi/Samples/invaders/data/game.rml | 112 + .../Libs/RmlUi/Samples/invaders/data/help.rml | 54 + .../Samples/invaders/data/help_defender.tga | Bin 0 -> 32786 bytes .../invaders/data/help_invader_mothership.tga | Bin 0 -> 32786 bytes .../invaders/data/help_invader_rank1.tga | Bin 0 -> 16402 bytes .../invaders/data/help_invader_rank2.tga | Bin 0 -> 16402 bytes .../invaders/data/help_invader_rank3.tga | Bin 0 -> 16402 bytes .../Samples/invaders/data/high_score.rml | 82 + .../invaders/data/high_scores_defender.tga | Bin 0 -> 4114 bytes .../RmlUi/Samples/invaders/data/invaders.tga | Bin 0 -> 65554 bytes .../Libs/RmlUi/Samples/invaders/data/logo.rml | 23 + .../Libs/RmlUi/Samples/invaders/data/logo.tga | Bin 0 -> 524306 bytes .../RmlUi/Samples/invaders/data/main_menu.rml | 27 + .../RmlUi/Samples/invaders/data/options.rml | 47 + .../RmlUi/Samples/invaders/data/pause.rml | 26 + .../Samples/invaders/data/start_game.rml | 58 + .../invaders/src/DecoratorDefender.cpp | 87 + .../Samples/invaders/src/DecoratorDefender.h | 34 + .../invaders/src/DecoratorStarfield.cpp | 155 + .../Samples/invaders/src/DecoratorStarfield.h | 58 + .../RmlUi/Samples/invaders/src/Defender.cpp | 154 + .../RmlUi/Samples/invaders/src/Defender.h | 51 + .../Samples/invaders/src/ElementGame.cpp | 86 + .../RmlUi/Samples/invaders/src/ElementGame.h | 30 + .../Samples/invaders/src/EventHandler.cpp | 3 + .../RmlUi/Samples/invaders/src/EventHandler.h | 9 + .../invaders/src/EventHandlerHighScore.cpp | 44 + .../invaders/src/EventHandlerHighScore.h | 11 + .../invaders/src/EventHandlerOptions.cpp | 131 + .../invaders/src/EventHandlerOptions.h | 11 + .../invaders/src/EventHandlerStartGame.cpp | 52 + .../invaders/src/EventHandlerStartGame.h | 11 + .../Samples/invaders/src/EventListener.cpp | 18 + .../Samples/invaders/src/EventListener.h | 18 + .../invaders/src/EventListenerInstancer.cpp | 12 + .../invaders/src/EventListenerInstancer.h | 12 + .../Samples/invaders/src/EventManager.cpp | 123 + .../RmlUi/Samples/invaders/src/EventManager.h | 30 + .../Libs/RmlUi/Samples/invaders/src/Game.cpp | 396 + .../Libs/RmlUi/Samples/invaders/src/Game.h | 91 + .../Samples/invaders/src/GameDetails.cpp | 100 + .../RmlUi/Samples/invaders/src/GameDetails.h | 72 + .../RmlUi/Samples/invaders/src/HighScores.cpp | 157 + .../RmlUi/Samples/invaders/src/HighScores.h | 44 + .../RmlUi/Samples/invaders/src/Invader.cpp | 222 + .../Libs/RmlUi/Samples/invaders/src/Invader.h | 81 + .../RmlUi/Samples/invaders/src/Mothership.cpp | 54 + .../RmlUi/Samples/invaders/src/Mothership.h | 19 + .../RmlUi/Samples/invaders/src/Shield.cpp | 154 + .../Libs/RmlUi/Samples/invaders/src/Shield.h | 57 + .../RmlUi/Samples/invaders/src/Sprite.cpp | 48 + .../Libs/RmlUi/Samples/invaders/src/Sprite.h | 24 + .../Libs/RmlUi/Samples/invaders/src/main.cpp | 108 + .../RmlUi/Samples/lua_invaders/CMakeLists.txt | 42 + .../Samples/lua_invaders/data/background.rml | 41 + .../Samples/lua_invaders/data/background.tga | Bin 0 -> 4194322 bytes .../RmlUi/Samples/lua_invaders/data/game.rml | 111 + .../RmlUi/Samples/lua_invaders/data/help.rml | 54 + .../lua_invaders/data/help_defender.tga | Bin 0 -> 32786 bytes .../data/help_invader_mothership.tga | Bin 0 -> 32786 bytes .../lua_invaders/data/help_invader_rank1.tga | Bin 0 -> 16402 bytes .../lua_invaders/data/help_invader_rank2.tga | Bin 0 -> 16402 bytes .../lua_invaders/data/help_invader_rank3.tga | Bin 0 -> 16402 bytes .../Samples/lua_invaders/data/high_score.rml | 91 + .../data/high_scores_defender.tga | Bin 0 -> 4114 bytes .../Samples/lua_invaders/data/invaders.tga | Bin 0 -> 65554 bytes .../RmlUi/Samples/lua_invaders/data/logo.rml | 23 + .../RmlUi/Samples/lua_invaders/data/logo.tga | Bin 0 -> 524306 bytes .../Samples/lua_invaders/data/main_menu.rml | 37 + .../Samples/lua_invaders/data/options.rml | 126 + .../RmlUi/Samples/lua_invaders/data/pause.rml | 26 + .../Samples/lua_invaders/data/start_game.rml | 78 + .../Samples/lua_invaders/data/window.rml | 35 + .../RmlUi/Samples/lua_invaders/lua/start.lua | 7 + .../lua_invaders/src/DecoratorDefender.cpp | 87 + .../lua_invaders/src/DecoratorDefender.h | 34 + .../lua_invaders/src/DecoratorStarfield.cpp | 155 + .../lua_invaders/src/DecoratorStarfield.h | 58 + .../Samples/lua_invaders/src/Defender.cpp | 154 + .../RmlUi/Samples/lua_invaders/src/Defender.h | 51 + .../Samples/lua_invaders/src/ElementGame.cpp | 89 + .../Samples/lua_invaders/src/ElementGame.h | 30 + .../lua_invaders/src/ElementGameInstancer.cpp | 14 + .../lua_invaders/src/ElementGameInstancer.h | 17 + .../RmlUi/Samples/lua_invaders/src/Game.cpp | 399 + .../RmlUi/Samples/lua_invaders/src/Game.h | 94 + .../Samples/lua_invaders/src/GameDetails.cpp | 100 + .../Samples/lua_invaders/src/GameDetails.h | 72 + .../Samples/lua_invaders/src/HighScores.cpp | 157 + .../Samples/lua_invaders/src/HighScores.h | 44 + .../Samples/lua_invaders/src/Invader.cpp | 222 + .../RmlUi/Samples/lua_invaders/src/Invader.h | 81 + .../Samples/lua_invaders/src/LuaInterface.cpp | 122 + .../Samples/lua_invaders/src/LuaInterface.h | 23 + .../Samples/lua_invaders/src/Mothership.cpp | 54 + .../Samples/lua_invaders/src/Mothership.h | 19 + .../RmlUi/Samples/lua_invaders/src/Shield.cpp | 154 + .../RmlUi/Samples/lua_invaders/src/Shield.h | 57 + .../RmlUi/Samples/lua_invaders/src/Sprite.cpp | 48 + .../RmlUi/Samples/lua_invaders/src/Sprite.h | 24 + .../RmlUi/Samples/lua_invaders/src/main.cpp | 96 + .../Windows64/Libs/RmlUi/Samples/readme.md | 47 + .../Libs/RmlUi/Samples/shell/CMakeLists.txt | 39 + .../shell/include/PlatformExtensions.h | 12 + .../shell/include/RendererExtensions.h | 16 + .../Libs/RmlUi/Samples/shell/include/Shell.h | 24 + .../shell/include/ShellFileInterface.h | 32 + .../Samples/shell/src/PlatformExtensions.cpp | 262 + .../Samples/shell/src/RendererExtensions.cpp | 99 + .../Libs/RmlUi/Samples/shell/src/Shell.cpp | 117 + .../Samples/shell/src/ShellFileInterface.cpp | 38 + .../RmlUi/Samples/tutorial/CMakeLists.txt | 4 + .../Samples/tutorial/drag/CMakeLists.txt | 14 + .../Samples/tutorial/drag/data/inventory.rml | 16 + .../Samples/tutorial/drag/data/tutorial.rcss | 24 + .../Samples/tutorial/drag/src/Inventory.cpp | 35 + .../Samples/tutorial/drag/src/Inventory.h | 23 + .../RmlUi/Samples/tutorial/drag/src/main.cpp | 91 + .../Samples/tutorial/template/CMakeLists.txt | 12 + .../tutorial/template/data/tutorial.rcss | 37 + .../tutorial/template/data/tutorial.rml | 17 + .../Samples/tutorial/template/src/main.cpp | 81 + .../Libs/RmlUi/Source/CMakeLists.txt | 14 + .../Libs/RmlUi/Source/Core/BaseXMLParser.cpp | 519 + .../Windows64/Libs/RmlUi/Source/Core/Box.cpp | 107 + .../Libs/RmlUi/Source/Core/BoxShadowCache.cpp | 97 + .../Libs/RmlUi/Source/Core/BoxShadowCache.h | 35 + .../Libs/RmlUi/Source/Core/BoxShadowHash.h | 143 + .../Libs/RmlUi/Source/Core/CMakeLists.txt | 480 + .../RmlUi/Source/Core/CallbackTexture.cpp | 96 + .../Libs/RmlUi/Source/Core/Clock.cpp | 16 + .../Windows64/Libs/RmlUi/Source/Core/Clock.h | 17 + .../Source/Core/CompiledFilterShader.cpp | 32 + .../RmlUi/Source/Core/ComputeProperty.cpp | 265 + .../Libs/RmlUi/Source/Core/ComputeProperty.h | 46 + .../Libs/RmlUi/Source/Core/ComputedValues.cpp | 71 + .../Libs/RmlUi/Source/Core/Context.cpp | 1637 ++ .../RmlUi/Source/Core/ContextInstancer.cpp | 7 + .../Source/Core/ContextInstancerDefault.cpp | 25 + .../Source/Core/ContextInstancerDefault.h | 26 + .../Source/Core/ControlledLifetimeResource.h | 62 + .../RmlUi/Source/Core/ConvolutionFilter.cpp | 90 + .../Windows64/Libs/RmlUi/Source/Core/Core.cpp | 465 + .../Libs/RmlUi/Source/Core/DataController.cpp | 41 + .../Libs/RmlUi/Source/Core/DataController.h | 83 + .../Source/Core/DataControllerDefault.cpp | 120 + .../RmlUi/Source/Core/DataControllerDefault.h | 52 + .../Libs/RmlUi/Source/Core/DataExpression.cpp | 1218 + .../Libs/RmlUi/Source/Core/DataExpression.h | 51 + .../Libs/RmlUi/Source/Core/DataModel.cpp | 383 + .../Libs/RmlUi/Source/Core/DataModel.h | 77 + .../RmlUi/Source/Core/DataModelHandle.cpp | 56 + .../RmlUi/Source/Core/DataTypeRegister.cpp | 103 + .../Libs/RmlUi/Source/Core/DataVariable.cpp | 171 + .../Libs/RmlUi/Source/Core/DataView.cpp | 138 + .../Libs/RmlUi/Source/Core/DataView.h | 100 + .../RmlUi/Source/Core/DataViewDefault.cpp | 602 + .../Libs/RmlUi/Source/Core/DataViewDefault.h | 161 + .../Libs/RmlUi/Source/Core/Decorator.cpp | 77 + .../RmlUi/Source/Core/DecoratorGradient.cpp | 697 + .../RmlUi/Source/Core/DecoratorGradient.h | 202 + .../RmlUi/Source/Core/DecoratorNinePatch.cpp | 227 + .../RmlUi/Source/Core/DecoratorNinePatch.h | 41 + .../RmlUi/Source/Core/DecoratorShader.cpp | 91 + .../Libs/RmlUi/Source/Core/DecoratorShader.h | 49 + .../Libs/RmlUi/Source/Core/DecoratorText.cpp | 141 + .../Libs/RmlUi/Source/Core/DecoratorText.h | 56 + .../Libs/RmlUi/Source/Core/DecoratorTiled.cpp | 373 + .../Libs/RmlUi/Source/Core/DecoratorTiled.h | 126 + .../RmlUi/Source/Core/DecoratorTiledBox.cpp | 269 + .../RmlUi/Source/Core/DecoratorTiledBox.h | 53 + .../Source/Core/DecoratorTiledHorizontal.cpp | 145 + .../Source/Core/DecoratorTiledHorizontal.h | 42 + .../RmlUi/Source/Core/DecoratorTiledImage.cpp | 76 + .../RmlUi/Source/Core/DecoratorTiledImage.h | 40 + .../Source/Core/DecoratorTiledVertical.cpp | 147 + .../Source/Core/DecoratorTiledVertical.h | 42 + .../RmlUi/Source/Core/DecoratorUtilities.cpp | 31 + .../RmlUi/Source/Core/DecoratorUtilities.h | 13 + .../Libs/RmlUi/Source/Core/DocumentHeader.cpp | 41 + .../Libs/RmlUi/Source/Core/DocumentHeader.h | 48 + .../RmlUi/Source/Core/EffectSpecification.cpp | 25 + .../Libs/RmlUi/Source/Core/Element.cpp | 3018 +++ .../RmlUi/Source/Core/ElementAnimation.cpp | 785 + .../Libs/RmlUi/Source/Core/ElementAnimation.h | 62 + .../Source/Core/ElementBackgroundBorder.cpp | 140 + .../Source/Core/ElementBackgroundBorder.h | 41 + .../RmlUi/Source/Core/ElementDefinition.cpp | 27 + .../RmlUi/Source/Core/ElementDefinition.h | 35 + .../RmlUi/Source/Core/ElementDocument.cpp | 819 + .../Libs/RmlUi/Source/Core/ElementEffects.cpp | 327 + .../Libs/RmlUi/Source/Core/ElementEffects.h | 65 + .../Libs/RmlUi/Source/Core/ElementHandle.cpp | 346 + .../Libs/RmlUi/Source/Core/ElementHandle.h | 51 + .../RmlUi/Source/Core/ElementInstancer.cpp | 68 + .../Libs/RmlUi/Source/Core/ElementMeta.cpp | 30 + .../Libs/RmlUi/Source/Core/ElementMeta.h | 37 + .../Libs/RmlUi/Source/Core/ElementScroll.cpp | 241 + .../Libs/RmlUi/Source/Core/ElementStyle.cpp | 910 + .../Libs/RmlUi/Source/Core/ElementStyle.h | 142 + .../Libs/RmlUi/Source/Core/ElementText.cpp | 722 + .../RmlUi/Source/Core/ElementUtilities.cpp | 490 + .../RmlUi/Source/Core/Elements/CMakeLists.txt | 50 + .../Source/Core/Elements/ElementForm.cpp | 57 + .../Core/Elements/ElementFormControl.cpp | 62 + .../Core/Elements/ElementFormControlInput.cpp | 170 + .../Elements/ElementFormControlSelect.cpp | 166 + .../Elements/ElementFormControlTextArea.cpp | 177 + .../Source/Core/Elements/ElementImage.cpp | 297 + .../RmlUi/Source/Core/Elements/ElementImage.h | 101 + .../Source/Core/Elements/ElementLabel.cpp | 96 + .../RmlUi/Source/Core/Elements/ElementLabel.h | 32 + .../Source/Core/Elements/ElementProgress.cpp | 363 + .../Source/Core/Elements/ElementTabSet.cpp | 185 + .../Core/Elements/ElementTextSelection.cpp | 32 + .../Core/Elements/ElementTextSelection.h | 33 + .../RmlUi/Source/Core/Elements/InputType.cpp | 47 + .../RmlUi/Source/Core/Elements/InputType.h | 73 + .../Source/Core/Elements/InputTypeButton.cpp | 42 + .../Source/Core/Elements/InputTypeButton.h | 41 + .../Core/Elements/InputTypeCheckbox.cpp | 60 + .../Source/Core/Elements/InputTypeCheckbox.h | 38 + .../Source/Core/Elements/InputTypeRadio.cpp | 104 + .../Source/Core/Elements/InputTypeRadio.h | 45 + .../Source/Core/Elements/InputTypeRange.cpp | 74 + .../Source/Core/Elements/InputTypeRange.h | 45 + .../Source/Core/Elements/InputTypeSubmit.cpp | 60 + .../Source/Core/Elements/InputTypeSubmit.h | 38 + .../Source/Core/Elements/InputTypeText.cpp | 112 + .../Source/Core/Elements/InputTypeText.h | 64 + .../Source/Core/Elements/WidgetDropDown.cpp | 647 + .../Source/Core/Elements/WidgetDropDown.h | 106 + .../Source/Core/Elements/WidgetSlider.cpp | 616 + .../RmlUi/Source/Core/Elements/WidgetSlider.h | 126 + .../Source/Core/Elements/WidgetTextInput.cpp | 1640 ++ .../Source/Core/Elements/WidgetTextInput.h | 300 + .../Elements/WidgetTextInputMultiLine.cpp | 19 + .../Core/Elements/WidgetTextInputMultiLine.h | 23 + .../Elements/WidgetTextInputSingleLine.cpp | 25 + .../Core/Elements/WidgetTextInputSingleLine.h | 22 + .../WidgetTextInputSingleLinePassword.cpp | 26 + .../WidgetTextInputSingleLinePassword.h | 19 + .../Core/Elements/XMLNodeHandlerSelect.cpp | 57 + .../Core/Elements/XMLNodeHandlerSelect.h | 20 + .../Core/Elements/XMLNodeHandlerTabSet.cpp | 103 + .../Core/Elements/XMLNodeHandlerTabSet.h | 24 + .../Core/Elements/XMLNodeHandlerTextArea.cpp | 51 + .../Core/Elements/XMLNodeHandlerTextArea.h | 24 + .../Libs/RmlUi/Source/Core/Event.cpp | 157 + .../RmlUi/Source/Core/EventDispatcher.cpp | 257 + .../Libs/RmlUi/Source/Core/EventDispatcher.h | 79 + .../Libs/RmlUi/Source/Core/EventInstancer.cpp | 7 + .../Source/Core/EventInstancerDefault.cpp | 25 + .../RmlUi/Source/Core/EventInstancerDefault.h | 33 + .../Source/Core/EventListenerInstancer.cpp | 7 + .../RmlUi/Source/Core/EventSpecification.cpp | 166 + .../RmlUi/Source/Core/EventSpecification.h | 39 + .../Libs/RmlUi/Source/Core/Factory.cpp | 608 + .../Libs/RmlUi/Source/Core/FileInterface.cpp | 41 + .../Source/Core/FileInterfaceDefault.cpp | 35 + .../RmlUi/Source/Core/FileInterfaceDefault.h | 45 + .../Libs/RmlUi/Source/Core/Filter.cpp | 16 + .../Libs/RmlUi/Source/Core/FilterBasic.cpp | 52 + .../Libs/RmlUi/Source/Core/FilterBasic.h | 34 + .../Libs/RmlUi/Source/Core/FilterBlur.cpp | 48 + .../Libs/RmlUi/Source/Core/FilterBlur.h | 34 + .../RmlUi/Source/Core/FilterDropShadow.cpp | 72 + .../Libs/RmlUi/Source/Core/FilterDropShadow.h | 35 + .../Libs/RmlUi/Source/Core/FontEffect.cpp | 67 + .../Libs/RmlUi/Source/Core/FontEffectBlur.cpp | 113 + .../Libs/RmlUi/Source/Core/FontEffectBlur.h | 46 + .../Libs/RmlUi/Source/Core/FontEffectGlow.cpp | 154 + .../Libs/RmlUi/Source/Core/FontEffectGlow.h | 50 + .../RmlUi/Source/Core/FontEffectInstancer.cpp | 29 + .../RmlUi/Source/Core/FontEffectOutline.cpp | 96 + .../RmlUi/Source/Core/FontEffectOutline.h | 46 + .../RmlUi/Source/Core/FontEffectShadow.cpp | 62 + .../Libs/RmlUi/Source/Core/FontEffectShadow.h | 42 + .../Core/FontEngineDefault/CMakeLists.txt | 22 + .../FontEngineInterfaceDefault.cpp | 80 + .../FontEngineInterfaceDefault.h | 52 + .../Core/FontEngineDefault/FontFace.cpp | 65 + .../Source/Core/FontEngineDefault/FontFace.h | 38 + .../FontFaceHandleDefault.cpp | 466 + .../FontEngineDefault/FontFaceHandleDefault.h | 127 + .../Core/FontEngineDefault/FontFaceLayer.cpp | 247 + .../Core/FontEngineDefault/FontFaceLayer.h | 97 + .../Core/FontEngineDefault/FontFamily.cpp | 77 + .../Core/FontEngineDefault/FontFamily.h | 52 + .../Core/FontEngineDefault/FontProvider.cpp | 250 + .../Core/FontEngineDefault/FontProvider.h | 79 + .../Source/Core/FontEngineDefault/FontTypes.h | 24 + .../FontEngineDefault/FreeTypeInterface.cpp | 578 + .../FontEngineDefault/FreeTypeInterface.h | 41 + .../RmlUi/Source/Core/FontEngineInterface.cpp | 68 + .../Libs/RmlUi/Source/Core/Geometry.cpp | 39 + .../Source/Core/GeometryBackgroundBorder.cpp | 330 + .../Source/Core/GeometryBackgroundBorder.h | 109 + .../RmlUi/Source/Core/GeometryBoxShadow.cpp | 244 + .../RmlUi/Source/Core/GeometryBoxShadow.h | 57 + .../Libs/RmlUi/Source/Core/IdNameMap.h | 85 + .../Source/Core/Layout/BlockContainer.cpp | 535 + .../RmlUi/Source/Core/Layout/BlockContainer.h | 165 + .../Core/Layout/BlockFormattingContext.cpp | 263 + .../Core/Layout/BlockFormattingContext.h | 41 + .../RmlUi/Source/Core/Layout/CMakeLists.txt | 40 + .../RmlUi/Source/Core/Layout/ContainerBox.cpp | 298 + .../RmlUi/Source/Core/Layout/ContainerBox.h | 139 + .../Core/Layout/FlexFormattingContext.cpp | 972 + .../Core/Layout/FlexFormattingContext.h | 42 + .../Source/Core/Layout/FloatedBoxSpace.cpp | 235 + .../Source/Core/Layout/FloatedBoxSpace.h | 97 + .../Source/Core/Layout/FormattingContext.cpp | 52 + .../Source/Core/Layout/FormattingContext.h | 37 + .../RmlUi/Source/Core/Layout/InlineBox.cpp | 130 + .../Libs/RmlUi/Source/Core/Layout/InlineBox.h | 69 + .../Source/Core/Layout/InlineContainer.cpp | 288 + .../Source/Core/Layout/InlineContainer.h | 105 + .../Source/Core/Layout/InlineLevelBox.cpp | 204 + .../RmlUi/Source/Core/Layout/InlineLevelBox.h | 115 + .../RmlUi/Source/Core/Layout/InlineTypes.h | 39 + .../RmlUi/Source/Core/Layout/LayoutBox.cpp | 31 + .../Libs/RmlUi/Source/Core/Layout/LayoutBox.h | 48 + .../Source/Core/Layout/LayoutDetails.cpp | 559 + .../RmlUi/Source/Core/Layout/LayoutDetails.h | 126 + .../RmlUi/Source/Core/Layout/LayoutEngine.cpp | 33 + .../RmlUi/Source/Core/Layout/LayoutEngine.h | 19 + .../RmlUi/Source/Core/Layout/LayoutPools.cpp | 76 + .../RmlUi/Source/Core/Layout/LayoutPools.h | 17 + .../Libs/RmlUi/Source/Core/Layout/LineBox.cpp | 415 + .../Libs/RmlUi/Source/Core/Layout/LineBox.h | 185 + .../Core/Layout/ReplacedFormattingContext.cpp | 51 + .../Core/Layout/ReplacedFormattingContext.h | 35 + .../Core/Layout/TableFormattingContext.cpp | 466 + .../Core/Layout/TableFormattingContext.h | 65 + .../Core/Layout/TableFormattingDetails.cpp | 643 + .../Core/Layout/TableFormattingDetails.h | 149 + .../Windows64/Libs/RmlUi/Source/Core/Log.cpp | 68 + .../Libs/RmlUi/Source/Core/LogDefault.cpp | 43 + .../Libs/RmlUi/Source/Core/LogDefault.h | 17 + .../Windows64/Libs/RmlUi/Source/Core/Math.cpp | 269 + .../Libs/RmlUi/Source/Core/Memory.cpp | 69 + .../Windows64/Libs/RmlUi/Source/Core/Memory.h | 107 + .../Libs/RmlUi/Source/Core/MeshUtilities.cpp | 140 + .../Libs/RmlUi/Source/Core/ObserverPtr.cpp | 56 + .../Libs/RmlUi/Source/Core/Plugin.cpp | 34 + .../Libs/RmlUi/Source/Core/PluginRegistry.cpp | 137 + .../Libs/RmlUi/Source/Core/PluginRegistry.h | 48 + .../Windows64/Libs/RmlUi/Source/Core/Pool.h | 113 + .../Windows64/Libs/RmlUi/Source/Core/Pool.inl | 232 + .../Libs/RmlUi/Source/Core/Profiling.cpp | 21 + .../RmlUi/Source/Core/PropertiesIterator.h | 78 + .../Source/Core/PropertiesIteratorView.cpp | 45 + .../Libs/RmlUi/Source/Core/Property.cpp | 33 + .../RmlUi/Source/Core/PropertyDefinition.cpp | 168 + .../RmlUi/Source/Core/PropertyDictionary.cpp | 75 + .../Source/Core/PropertyParserAnimation.cpp | 352 + .../Source/Core/PropertyParserAnimation.h | 36 + .../Source/Core/PropertyParserBoxShadow.cpp | 90 + .../Source/Core/PropertyParserBoxShadow.h | 27 + .../Core/PropertyParserColorStopList.cpp | 72 + .../Source/Core/PropertyParserColorStopList.h | 25 + .../Source/Core/PropertyParserColour.cpp | 548 + .../RmlUi/Source/Core/PropertyParserColour.h | 54 + .../Source/Core/PropertyParserDecorator.cpp | 146 + .../Source/Core/PropertyParserDecorator.h | 29 + .../Source/Core/PropertyParserFilter.cpp | 91 + .../RmlUi/Source/Core/PropertyParserFilter.h | 20 + .../Source/Core/PropertyParserFontEffect.cpp | 119 + .../Source/Core/PropertyParserFontEffect.h | 20 + .../Source/Core/PropertyParserKeyword.cpp | 21 + .../RmlUi/Source/Core/PropertyParserKeyword.h | 24 + .../Source/Core/PropertyParserNumber.cpp | 99 + .../RmlUi/Source/Core/PropertyParserNumber.h | 37 + .../RmlUi/Source/Core/PropertyParserRatio.cpp | 39 + .../RmlUi/Source/Core/PropertyParserRatio.h | 24 + .../Source/Core/PropertyParserString.cpp | 17 + .../RmlUi/Source/Core/PropertyParserString.h | 24 + .../Source/Core/PropertyParserTransform.cpp | 263 + .../Source/Core/PropertyParserTransform.h | 37 + .../Source/Core/PropertyShorthandDefinition.h | 45 + .../Source/Core/PropertySpecification.cpp | 602 + .../RmlUi/Source/Core/RenderInterface.cpp | 65 + .../Core/RenderInterfaceCompatibility.cpp | 189 + .../Libs/RmlUi/Source/Core/RenderManager.cpp | 372 + .../RmlUi/Source/Core/RenderManagerAccess.cpp | 48 + .../RmlUi/Source/Core/RenderManagerAccess.h | 48 + .../RmlUi/Source/Core/ScrollController.cpp | 332 + .../Libs/RmlUi/Source/Core/ScrollController.h | 81 + .../Libs/RmlUi/Source/Core/Spritesheet.cpp | 78 + .../Libs/RmlUi/Source/Core/Stream.cpp | 119 + .../Libs/RmlUi/Source/Core/StreamFile.cpp | 100 + .../Libs/RmlUi/Source/Core/StreamFile.h | 50 + .../Libs/RmlUi/Source/Core/StreamMemory.cpp | 196 + .../RmlUi/Source/Core/StringUtilities.cpp | 637 + .../Libs/RmlUi/Source/Core/StyleSheet.cpp | 239 + .../RmlUi/Source/Core/StyleSheetContainer.cpp | 215 + .../RmlUi/Source/Core/StyleSheetFactory.cpp | 200 + .../RmlUi/Source/Core/StyleSheetFactory.h | 52 + .../Libs/RmlUi/Source/Core/StyleSheetNode.cpp | 392 + .../Libs/RmlUi/Source/Core/StyleSheetNode.h | 76 + .../RmlUi/Source/Core/StyleSheetParser.cpp | 1315 ++ .../Libs/RmlUi/Source/Core/StyleSheetParser.h | 105 + .../RmlUi/Source/Core/StyleSheetSelector.cpp | 367 + .../RmlUi/Source/Core/StyleSheetSelector.h | 129 + .../Source/Core/StyleSheetSpecification.cpp | 440 + .../RmlUi/Source/Core/SystemInterface.cpp | 90 + .../Libs/RmlUi/Source/Core/Template.cpp | 117 + .../Libs/RmlUi/Source/Core/Template.h | 40 + .../Libs/RmlUi/Source/Core/TemplateCache.cpp | 96 + .../Libs/RmlUi/Source/Core/TemplateCache.h | 36 + .../Libs/RmlUi/Source/Core/Texture.cpp | 49 + .../RmlUi/Source/Core/TextureDatabase.cpp | 180 + .../Libs/RmlUi/Source/Core/TextureDatabase.h | 74 + .../Libs/RmlUi/Source/Core/TextureLayout.cpp | 70 + .../Libs/RmlUi/Source/Core/TextureLayout.h | 53 + .../Source/Core/TextureLayoutRectangle.cpp | 68 + .../Source/Core/TextureLayoutRectangle.h | 62 + .../RmlUi/Source/Core/TextureLayoutRow.cpp | 74 + .../Libs/RmlUi/Source/Core/TextureLayoutRow.h | 44 + .../Source/Core/TextureLayoutTexture.cpp | 123 + .../RmlUi/Source/Core/TextureLayoutTexture.h | 43 + .../Libs/RmlUi/Source/Core/Traits.cpp | 11 + .../Libs/RmlUi/Source/Core/Transform.cpp | 39 + .../RmlUi/Source/Core/TransformPrimitive.cpp | 148 + .../Libs/RmlUi/Source/Core/TransformState.cpp | 66 + .../Libs/RmlUi/Source/Core/TransformState.h | 38 + .../RmlUi/Source/Core/TransformUtilities.cpp | 774 + .../RmlUi/Source/Core/TransformUtilities.h | 40 + .../Libs/RmlUi/Source/Core/Tween.cpp | 212 + .../Libs/RmlUi/Source/Core/TypeConverter.cpp | 280 + .../Windows64/Libs/RmlUi/Source/Core/URL.cpp | 619 + .../Libs/RmlUi/Source/Core/Variant.cpp | 533 + .../Libs/RmlUi/Source/Core/WidgetScroll.cpp | 504 + .../Libs/RmlUi/Source/Core/WidgetScroll.h | 99 + .../Libs/RmlUi/Source/Core/XMLNodeHandler.cpp | 7 + .../RmlUi/Source/Core/XMLNodeHandlerBody.cpp | 58 + .../RmlUi/Source/Core/XMLNodeHandlerBody.h | 24 + .../Source/Core/XMLNodeHandlerDefault.cpp | 61 + .../RmlUi/Source/Core/XMLNodeHandlerDefault.h | 25 + .../RmlUi/Source/Core/XMLNodeHandlerHead.cpp | 141 + .../RmlUi/Source/Core/XMLNodeHandlerHead.h | 24 + .../Source/Core/XMLNodeHandlerTemplate.cpp | 48 + .../Source/Core/XMLNodeHandlerTemplate.h | 24 + .../Libs/RmlUi/Source/Core/XMLParseTools.cpp | 165 + .../Libs/RmlUi/Source/Core/XMLParseTools.h | 40 + .../Libs/RmlUi/Source/Core/XMLParser.cpp | 191 + .../Libs/RmlUi/Source/Core/precompiled.h | 3 + .../Libs/RmlUi/Source/Debugger/.clang-format | 2 + .../Libs/RmlUi/Source/Debugger/BeaconSource.h | 27 + .../Libs/RmlUi/Source/Debugger/CMakeLists.txt | 71 + .../Libs/RmlUi/Source/Debugger/CommonSource.h | 169 + .../RmlUi/Source/Debugger/DataModelsSource.h | 46 + .../Libs/RmlUi/Source/Debugger/Debugger.cpp | 71 + .../RmlUi/Source/Debugger/DebuggerPlugin.cpp | 451 + .../RmlUi/Source/Debugger/DebuggerPlugin.h | 104 + .../Debugger/DebuggerSystemInterface.cpp | 66 + .../Source/Debugger/DebuggerSystemInterface.h | 68 + .../Source/Debugger/ElementContextHook.cpp | 31 + .../Source/Debugger/ElementContextHook.h | 31 + .../Source/Debugger/ElementDataModels.cpp | 201 + .../RmlUi/Source/Debugger/ElementDataModels.h | 37 + .../Source/Debugger/ElementDebugDocument.cpp | 12 + .../Source/Debugger/ElementDebugDocument.h | 16 + .../RmlUi/Source/Debugger/ElementInfo.cpp | 723 + .../Libs/RmlUi/Source/Debugger/ElementInfo.h | 70 + .../Libs/RmlUi/Source/Debugger/ElementLog.cpp | 290 + .../Libs/RmlUi/Source/Debugger/ElementLog.h | 58 + .../Libs/RmlUi/Source/Debugger/FontSource.h | 5164 ++++ .../Libs/RmlUi/Source/Debugger/Geometry.cpp | 85 + .../Libs/RmlUi/Source/Debugger/Geometry.h | 32 + .../Libs/RmlUi/Source/Debugger/InfoSource.h | 146 + .../Libs/RmlUi/Source/Debugger/LICENSE.txt | 99 + .../Libs/RmlUi/Source/Debugger/LogSource.h | 82 + .../Libs/RmlUi/Source/Debugger/MenuSource.h | 66 + .../Libs/RmlUi/Source/Lottie/CMakeLists.txt | 10 + .../RmlUi/Source/Lottie/ElementLottie.cpp | 234 + .../Libs/RmlUi/Source/Lottie/LottiePlugin.cpp | 36 + .../Libs/RmlUi/Source/Lottie/LottiePlugin.h | 9 + .../Libs/RmlUi/Source/Lua/.clang-format | 2 + .../Libs/RmlUi/Source/Lua/CMakeLists.txt | 118 + .../Libs/RmlUi/Source/Lua/Colourb.cpp | 201 + .../Windows64/Libs/RmlUi/Source/Lua/Colourb.h | 36 + .../Libs/RmlUi/Source/Lua/Colourf.cpp | 173 + .../Windows64/Libs/RmlUi/Source/Lua/Colourf.h | 35 + .../Libs/RmlUi/Source/Lua/Context.cpp | 319 + .../Windows64/Libs/RmlUi/Source/Lua/Context.h | 40 + .../Source/Lua/ContextDocumentsProxy.cpp | 105 + .../RmlUi/Source/Lua/ContextDocumentsProxy.h | 25 + .../Libs/RmlUi/Source/Lua/Document.cpp | 163 + .../Libs/RmlUi/Source/Lua/Document.h | 36 + .../Libs/RmlUi/Source/Lua/Element.cpp | 652 + .../Windows64/Libs/RmlUi/Source/Lua/Element.h | 89 + .../Source/Lua/ElementAttributesProxy.cpp | 54 + .../RmlUi/Source/Lua/ElementAttributesProxy.h | 25 + .../Source/Lua/ElementChildNodesProxy.cpp | 62 + .../RmlUi/Source/Lua/ElementChildNodesProxy.h | 26 + .../RmlUi/Source/Lua/ElementInstancer.cpp | 53 + .../Libs/RmlUi/Source/Lua/ElementInstancer.h | 22 + .../RmlUi/Source/Lua/ElementStyleProxy.cpp | 137 + .../Libs/RmlUi/Source/Lua/ElementStyleProxy.h | 26 + .../Libs/RmlUi/Source/Lua/ElementText.cpp | 49 + .../Libs/RmlUi/Source/Lua/ElementText.h | 22 + .../Libs/RmlUi/Source/Lua/Elements/As.h | 40 + .../RmlUi/Source/Lua/Elements/CMakeLists.txt | 20 + .../RmlUi/Source/Lua/Elements/ElementForm.cpp | 48 + .../RmlUi/Source/Lua/Elements/ElementForm.h | 22 + .../Lua/Elements/ElementFormControl.cpp | 89 + .../Source/Lua/Elements/ElementFormControl.h | 28 + .../Lua/Elements/ElementFormControlInput.cpp | 178 + .../Lua/Elements/ElementFormControlInput.h | 40 + .../Lua/Elements/ElementFormControlSelect.cpp | 99 + .../Lua/Elements/ElementFormControlSelect.h | 30 + .../Elements/ElementFormControlTextArea.cpp | 138 + .../Lua/Elements/ElementFormControlTextArea.h | 36 + .../Source/Lua/Elements/ElementTabSet.cpp | 86 + .../RmlUi/Source/Lua/Elements/ElementTabSet.h | 30 + .../Lua/Elements/SelectOptionsProxy.cpp | 58 + .../Source/Lua/Elements/SelectOptionsProxy.h | 25 + .../Windows64/Libs/RmlUi/Source/Lua/Event.cpp | 87 + .../Windows64/Libs/RmlUi/Source/Lua/Event.h | 26 + .../RmlUi/Source/Lua/EventParametersProxy.cpp | 62 + .../RmlUi/Source/Lua/EventParametersProxy.h | 25 + .../RmlUi/Source/Lua/GlobalLuaFunctions.cpp | 52 + .../RmlUi/Source/Lua/GlobalLuaFunctions.h | 11 + .../Libs/RmlUi/Source/Lua/Interpreter.cpp | 121 + .../Windows64/Libs/RmlUi/Source/Lua/Log.cpp | 66 + .../Windows64/Libs/RmlUi/Source/Lua/Log.h | 20 + .../Windows64/Libs/RmlUi/Source/Lua/Lua.cpp | 19 + .../Libs/RmlUi/Source/Lua/LuaDataModel.cpp | 812 + .../Libs/RmlUi/Source/Lua/LuaDataModel.h | 18 + .../Libs/RmlUi/Source/Lua/LuaDocument.cpp | 29 + .../Libs/RmlUi/Source/Lua/LuaDocument.h | 18 + .../Lua/LuaDocumentElementInstancer.cpp | 23 + .../Source/Lua/LuaDocumentElementInstancer.h | 20 + .../RmlUi/Source/Lua/LuaElementInstancer.cpp | 63 + .../RmlUi/Source/Lua/LuaElementInstancer.h | 29 + .../RmlUi/Source/Lua/LuaEventListener.cpp | 113 + .../Libs/RmlUi/Source/Lua/LuaEventListener.h | 42 + .../Source/Lua/LuaEventListenerInstancer.cpp | 16 + .../Source/Lua/LuaEventListenerInstancer.h | 16 + .../Libs/RmlUi/Source/Lua/LuaPlugin.cpp | 144 + .../Libs/RmlUi/Source/Lua/LuaPlugin.h | 37 + .../Libs/RmlUi/Source/Lua/LuaType.cpp | 90 + .../Windows64/Libs/RmlUi/Source/Lua/Pairs.h | 110 + .../Windows64/Libs/RmlUi/Source/Lua/RmlUi.cpp | 307 + .../Windows64/Libs/RmlUi/Source/Lua/RmlUi.h | 38 + .../RmlUi/Source/Lua/RmlUiContextsProxy.cpp | 59 + .../RmlUi/Source/Lua/RmlUiContextsProxy.h | 24 + .../Libs/RmlUi/Source/Lua/Utilities.cpp | 104 + .../Libs/RmlUi/Source/Lua/Vector2f.cpp | 209 + .../Libs/RmlUi/Source/Lua/Vector2f.h | 36 + .../Libs/RmlUi/Source/Lua/Vector2i.cpp | 173 + .../Libs/RmlUi/Source/Lua/Vector2i.h | 33 + .../Libs/RmlUi/Source/SVG/CMakeLists.txt | 16 + .../Libs/RmlUi/Source/SVG/DecoratorSVG.cpp | 67 + .../Libs/RmlUi/Source/SVG/DecoratorSVG.h | 43 + .../Libs/RmlUi/Source/SVG/ElementSVG.cpp | 143 + .../Libs/RmlUi/Source/SVG/SVGCache.cpp | 390 + .../Libs/RmlUi/Source/SVG/SVGCache.h | 49 + .../Libs/RmlUi/Source/SVG/SVGPlugin.cpp | 51 + .../Libs/RmlUi/Source/SVG/SVGPlugin.h | 9 + .../RmlUi/Source/SVG/XMLNodeHandlerSVG.cpp | 14 + .../Libs/RmlUi/Source/SVG/XMLNodeHandlerSVG.h | 19 + .../Windows64/Libs/RmlUi/Tests/CMakeLists.txt | 13 + .../Data/UnitTests/Specificity_Basic.rcss | 16 + .../UnitTests/Specificity_MediaQuery.rcss | 6 + .../RmlUi/Tests/Data/UnitTests/data-title.rml | 11 + .../Tests/Data/UnitTests/template_basic.rml | 7 + .../RmlUi/Tests/Data/VisualTests/LICENSE.txt | 42 + .../RmlUi/Tests/Data/VisualTests/acid1.rml | 175 + .../Tests/Data/VisualTests/border_radius.rml | 55 + .../Tests/Data/VisualTests/box_sizing.rml | 67 + .../Tests/Data/VisualTests/clip_mask.rml | 72 + .../Data/VisualTests/color_interpolation.rml | 74 + .../Tests/Data/VisualTests/css1_clear.rml | 50 + .../display_block_inside_inline.rml | 51 + .../Data/VisualTests/document_position.rml | 72 + .../RmlUi/Tests/Data/VisualTests/drag.rml | 57 + .../Tests/Data/VisualTests/element_label.rml | 92 + .../filter_backdrop_filter_box_shadow.rml | 44 + .../Data/VisualTests/filter_blur_area.rml | 42 + .../Data/VisualTests/filter_blur_orb.rml | 44 + .../Data/VisualTests/filter_drop_shadow.rml | 29 + .../Data/VisualTests/filter_overflow.rml | 35 + .../Data/VisualTests/filter_with_backdrop.rml | 113 + .../RmlUi/Tests/Data/VisualTests/flex_01.rml | 71 + .../RmlUi/Tests/Data/VisualTests/flex_02.rml | 73 + .../Tests/Data/VisualTests/flex_03_scroll.rml | 73 + .../RmlUi/Tests/Data/VisualTests/flex_04.rml | 113 + .../RmlUi/Tests/Data/VisualTests/flex_05.rml | 97 + .../RmlUi/Tests/Data/VisualTests/flex_06.rml | 217 + .../flex_absolutely_positioned.rml | 50 + .../Tests/Data/VisualTests/flex_direction.rml | 71 + .../Tests/Data/VisualTests/flex_inline.rml | 71 + .../Tests/Data/VisualTests/flex_nested.rml | 46 + .../VisualTests/flex_shrink_to_fit_01.rml | 88 + .../VisualTests/flex_shrink_to_fit_02.rml | 72 + .../VisualTests/flex_wrap_column_reverse.rml | 63 + .../Tests/Data/VisualTests/float_basic.rml | 51 + .../float_excluded_by_flow_root.rml | 58 + .../Tests/Data/VisualTests/float_overflow.rml | 48 + .../Data/VisualTests/float_placement.rml | 59 + .../Data/VisualTests/float_text_wrap.rml | 42 + .../VisualTests/fractional-dimensions-01.rml | 65 + .../VisualTests/fractional-dimensions-02.rml | 67 + .../VisualTests/fractional-dimensions-03.rml | 69 + .../RmlUi/Tests/Data/VisualTests/gap-001.rml | 35 + .../RmlUi/Tests/Data/VisualTests/gap-002.rml | 35 + .../RmlUi/Tests/Data/VisualTests/gap-003.rml | 37 + .../RmlUi/Tests/Data/VisualTests/gap-004.rml | 35 + .../RmlUi/Tests/Data/VisualTests/gap-005.rml | 35 + .../RmlUi/Tests/Data/VisualTests/gap-006.rml | 50 + .../RmlUi/Tests/Data/VisualTests/gap-007.rml | 38 + .../RmlUi/Tests/Data/VisualTests/gap-008.rml | 40 + .../RmlUi/Tests/Data/VisualTests/gap-009.rml | 39 + .../Data/VisualTests/handle_target_anchor.rml | 91 + .../Tests/Data/VisualTests/image_ratio.rml | 40 + .../VisualTests/ink_overflow_bounding_box.rml | 47 + .../Tests/Data/VisualTests/inline_block.rml | 46 + .../Data/VisualTests/inline_formatting_01.rml | 55 + .../Data/VisualTests/inline_formatting_02.rml | 27 + .../Data/VisualTests/inline_formatting_03.rml | 31 + .../Data/VisualTests/inline_formatting_04.rml | 66 + .../Data/VisualTests/inline_formatting_05.rml | 58 + .../Data/VisualTests/inline_formatting_06.rml | 73 + .../Data/VisualTests/inline_formatting_07.rml | 55 + .../inline_formatting_aligned_subtree.rml | 114 + ...verflow_in_absolutely_positioned_input.rml | 32 + .../issue_452_scrollbar_resets.rml | 119 + .../Tests/Data/VisualTests/letter_spacing.rml | 44 + .../Tests/Data/VisualTests/mask_image.rml | 68 + .../Tests/Data/VisualTests/media_query_01.rml | 100 + .../Tests/Data/VisualTests/media_query_02.rml | 58 + .../Tests/Data/VisualTests/navigation_01.rml | 163 + .../Tests/Data/VisualTests/navigation_02.rml | 111 + .../RmlUi/Tests/Data/VisualTests/opacity.rml | 59 + .../VisualTests/overflow_clip_transform.rml | 192 + .../Data/VisualTests/overflow_hidden.rml | 97 + .../Data/VisualTests/overflow_nested.rml | 62 + .../VisualTests/position_01_normal_flow.rml | 30 + .../position_02_relative_positioning.rml | 30 + .../position_03_floating_a_box.rml | 30 + .../position_04_floating_a_box_sibling.rml | 31 + .../position_05_floating_a_box_clear.rml | 31 + .../position_06_absolute_positioning.rml | 35 + ...ition_07_absolute_positioning_relative.rml | 38 + ...on_08_absolute_positioning_no_relative.rml | 35 + ...on_09_absolute_positioning_change_bars.rml | 27 + .../position_absolute_static_position.rml | 59 + .../position_absolute_transform.rml | 63 + .../Data/VisualTests/position_relative.rml | 75 + .../VisualTests/reference/flex_05-ref.rml | 72 + .../VisualTests/reference/flex_06-ref.rml | 214 + .../reference/flex_direction-ref.rml | 80 + .../VisualTests/reference/flex_nested-ref.rml | 47 + .../flex_wrap_column_reverse-ref.rml | 71 + .../fractional-dimensions-02-ref.rml | 18 + .../VisualTests/reference/gap-001-ref.rml | 34 + .../VisualTests/reference/gap-002-ref.rml | 36 + .../VisualTests/reference/gap-003-ref.rml | 42 + .../VisualTests/reference/gap-004-ref.rml | 34 + .../VisualTests/reference/gap-005-ref.rml | 34 + .../VisualTests/reference/gap-006-ref.rml | 50 + .../VisualTests/reference/gap-007-ref.rml | 42 + .../VisualTests/reference/gap-008-ref.rml | 44 + .../VisualTests/reference/gap-009-ref.rml | 47 + .../reference/overflow_hidden-ref.rml | 83 + .../position_absolute_static_position-ref.rml | 55 + .../VisualTests/reference/table_01-ref.rml | 103 + .../renderer_alpha_blending_01.rml | 101 + .../renderer_alpha_blending_02.rml | 68 + .../renderer_alpha_blending_03.rml | 68 + .../Data/VisualTests/replaced_display.rml | 118 + .../Data/VisualTests/replaced_elements.rml | 78 + .../VisualTests/shader_conic_gradient.rml | 133 + .../VisualTests/shader_linear_gradient.rml | 118 + .../VisualTests/shader_radial_gradient.rml | 166 + .../Data/VisualTests/shrink_to_fit_01.rml | 58 + .../Data/VisualTests/shrink_to_fit_02.rml | 97 + .../Data/VisualTests/shrink_to_fit_03.rml | 99 + .../Data/VisualTests/shrink_to_fit_04.rml | 67 + .../Data/VisualTests/stacking_context_01.rml | 65 + .../Data/VisualTests/stacking_context_02.rml | 65 + .../RmlUi/Tests/Data/VisualTests/table_01.rml | 97 + .../RmlUi/Tests/Data/VisualTests/table_02.rml | 135 + .../RmlUi/Tests/Data/VisualTests/table_03.rml | 82 + .../RmlUi/Tests/Data/VisualTests/table_04.rml | 52 + .../RmlUi/Tests/Data/VisualTests/table_05.rml | 40 + .../Tests/Data/VisualTests/table_inline.rml | 74 + .../Tests/Data/VisualTests/text_alignment.rml | 47 + .../Tests/Data/VisualTests/text_decorator.rml | 107 + .../Data/VisualTests/text_input_overflow.rml | 62 + .../Data/VisualTests/text_overflow_01.rml | 96 + .../Data/VisualTests/text_overflow_02.rml | 80 + .../Tests/Data/VisualTests/white_space.rml | 54 + .../Tests/Data/VisualTests/word_break.rml | 53 + .../Libs/RmlUi/Tests/Data/description.rml | 91 + .../Libs/RmlUi/Tests/Data/style.rcss | 154 + .../Libs/RmlUi/Tests/Data/view_source.rml | 31 + .../RmlUi/Tests/Data/visual_tests_help.rml | 79 + .../Libs/RmlUi/Tests/Dependencies/LICENSE.txt | 105 + .../doctest/cmake/Config.cmake.in | 6 + .../cmake/assemble_single_header.cmake | 13 + .../Dependencies/doctest/cmake/common.cmake | 212 + .../Dependencies/doctest/cmake/doctest.cmake | 189 + .../doctest/cmake/doctestAddTests.cmake | 120 + .../doctest/cmake/exec_test.cmake | 70 + .../Tests/Dependencies/doctest/doctest.h | 7106 ++++++ .../Tests/Dependencies/lodepng/lodepng.cpp | 6410 +++++ .../Tests/Dependencies/lodepng/lodepng.h | 1945 ++ .../Tests/Dependencies/nanobench/nanobench.h | 3356 +++ .../trompeloeil/doctest/trompeloeil.hpp | 58 + .../Dependencies/trompeloeil/trompeloeil.hpp | 42 + .../trompeloeil/trompeloeil/coro.hpp | 399 + .../trompeloeil/cpp11_shenanigans.hpp | 370 + .../trompeloeil/trompeloeil/lifetime.hpp | 225 + .../trompeloeil/trompeloeil/matcher.hpp | 221 + .../trompeloeil/trompeloeil/matcher/any.hpp | 98 + .../trompeloeil/matcher/compare.hpp | 163 + .../trompeloeil/trompeloeil/matcher/deref.hpp | 83 + .../trompeloeil/trompeloeil/matcher/not.hpp | 82 + .../trompeloeil/trompeloeil/matcher/re.hpp | 148 + .../trompeloeil/trompeloeil/mock.hpp | 4267 ++++ .../trompeloeil/trompeloeil/sequence.hpp | 397 + .../trompeloeil/trompeloeil/stream_tracer.hpp | 44 + .../Libs/RmlUi/Tests/Output/.gitignore | 2 + .../Source/Benchmarks/BackgroundBorder.cpp | 258 + .../Tests/Source/Benchmarks/CMakeLists.txt | 38 + .../Tests/Source/Benchmarks/DataBinding.cpp | 219 + .../Source/Benchmarks/DataExpression.cpp | 69 + .../RmlUi/Tests/Source/Benchmarks/Element.cpp | 289 + .../Source/Benchmarks/ElementDocument.cpp | 154 + .../RmlUi/Tests/Source/Benchmarks/Flexbox.cpp | 642 + .../Tests/Source/Benchmarks/FontEffect.cpp | 59 + .../Tests/Source/Benchmarks/Selectors.cpp | 267 + .../RmlUi/Tests/Source/Benchmarks/Table.cpp | 289 + .../Source/Benchmarks/WidgetTextInput.cpp | 134 + .../RmlUi/Tests/Source/Benchmarks/main.cpp | 26 + .../Libs/RmlUi/Tests/Source/CMakeLists.txt | 5 + .../RmlUi/Tests/Source/Common/CMakeLists.txt | 19 + .../Libs/RmlUi/Tests/Source/Common/Mocks.h | 37 + .../Tests/Source/Common/TestsInterface.cpp | 221 + .../Tests/Source/Common/TestsInterface.h | 94 + .../RmlUi/Tests/Source/Common/TestsShell.cpp | 220 + .../RmlUi/Tests/Source/Common/TestsShell.h | 37 + .../RmlUi/Tests/Source/Common/TypesToString.h | 76 + .../Tests/Source/UnitTests/Animation.cpp | 992 + .../Tests/Source/UnitTests/CMakeLists.txt | 53 + .../RmlUi/Tests/Source/UnitTests/Core.cpp | 432 + .../Tests/Source/UnitTests/DataBinding.cpp | 990 + .../Tests/Source/UnitTests/DataExpression.cpp | 175 + .../Tests/Source/UnitTests/DataModel.cpp | 91 + .../RmlUi/Tests/Source/UnitTests/Debugger.cpp | 131 + .../Tests/Source/UnitTests/Decorator.cpp | 708 + .../RmlUi/Tests/Source/UnitTests/Element.cpp | 446 + .../UnitTests/ElementBackgroundBorder.cpp | 187 + .../Source/UnitTests/ElementDocument.cpp | 431 + .../UnitTests/ElementFormControlSelect.cpp | 545 + .../Tests/Source/UnitTests/ElementHandle.cpp | 393 + .../Tests/Source/UnitTests/ElementImage.cpp | 164 + .../Tests/Source/UnitTests/ElementStyle.cpp | 58 + .../Tests/Source/UnitTests/EventListener.cpp | 136 + .../RmlUi/Tests/Source/UnitTests/Filter.cpp | 155 + .../Tests/Source/UnitTests/FlexFormatting.cpp | 177 + .../RmlUi/Tests/Source/UnitTests/Layout.cpp | 112 + .../Tests/Source/UnitTests/Localization.cpp | 113 + .../RmlUi/Tests/Source/UnitTests/Math.cpp | 156 + .../Tests/Source/UnitTests/MediaQuery.cpp | 545 + .../Tests/Source/UnitTests/Navigation.cpp | 560 + .../Source/UnitTests/PluginDataModel.cpp | 105 + .../Tests/Source/UnitTests/Properties.cpp | 262 + .../UnitTests/PropertySpecification.cpp | 378 + .../Tests/Source/UnitTests/Selectors.cpp | 590 + .../Source/UnitTests/Specificity_Basic.cpp | 270 + .../UnitTests/Specificity_MediaQuery.cpp | 299 + .../Tests/Source/UnitTests/StableVector.cpp | 48 + .../Source/UnitTests/StringUtilities.cpp | 171 + .../Source/UnitTests/StyleSheetParser.cpp | 258 + .../RmlUi/Tests/Source/UnitTests/Template.cpp | 309 + .../Libs/RmlUi/Tests/Source/UnitTests/URL.cpp | 198 + .../RmlUi/Tests/Source/UnitTests/Variant.cpp | 85 + .../Tests/Source/UnitTests/XMLParser.cpp | 196 + .../RmlUi/Tests/Source/UnitTests/main.cpp | 6 + .../Tests/Source/VisualTests/CMakeLists.txt | 25 + .../Source/VisualTests/CaptureScreen.cpp | 213 + .../Tests/Source/VisualTests/CaptureScreen.h | 30 + .../Tests/Source/VisualTests/TestConfig.cpp | 42 + .../Tests/Source/VisualTests/TestConfig.h | 7 + .../Source/VisualTests/TestNavigator.cpp | 630 + .../Tests/Source/VisualTests/TestNavigator.h | 68 + .../Tests/Source/VisualTests/TestSuite.h | 110 + .../Tests/Source/VisualTests/TestViewer.cpp | 333 + .../Tests/Source/VisualTests/TestViewer.h | 44 + .../Source/VisualTests/XmlNodeHandlers.cpp | 74 + .../Source/VisualTests/XmlNodeHandlers.h | 56 + .../RmlUi/Tests/Source/VisualTests/main.cpp | 126 + .../Tools/convert_css_test_suite_to_rml.py | 385 + .../Windows64/Libs/RmlUi/Tests/readme.md | 48 + .../Libs/RmlUi/Utilities/natvis/RmlUi.natvis | 61 + .../Windows64/Libs/RmlUi/changelog.md | 2408 ++ .../Windows64/Libs/RmlUi/contributing.md | 42 + .../Windows64/Libs/RmlUi/readme.md | 573 + .../Windows64/RmlUi_FileInterface.cpp | 90 + .../Windows64/RmlUi_FileInterface.h | 36 + .../Windows64/RmlUi_Renderer_D3D11.cpp | 803 + .../Windows64/RmlUi_Renderer_D3D11.h | 120 + .../Windows64/RmlUi_SystemInterface_Win64.cpp | 114 + .../Windows64/RmlUi_SystemInterface_Win64.h | 43 + .../Windows64/Windows64_Minecraft.cpp | 2 + .../Windows64/Windows64_UIController.cpp | 8 + Minecraft.Client/cmake/sources/Common.cmake | 1 - Minecraft.Client/cmake/sources/Windows.cmake | 8 + Minecraft.Client/compat_shims.cpp | 76 +- Minecraft.Server/CMakeLists.txt | 46 +- Minecraft.Server/RmlManagerStub.cpp | 18 + Minecraft.Server/cmake/sources/Common.cmake | 3 +- Minecraft.World/BasicTypeContainers.cpp | 1 + Minecraft.World/BiomeSource.cpp | 12 +- Minecraft.World/BiomeSource.h | 6 +- Minecraft.World/Entity.cpp | 5 +- Minecraft.World/Hasher.cpp | 3 +- Minecraft.World/LivingEntity.cpp | 12 +- Minecraft.World/Recipes.cpp | 6 +- Minecraft.World/StaticDataStubs.cpp | 93 + Minecraft.World/StructureFeature.cpp | 1 + Minecraft.World/ThreadName.cpp | 41 + Minecraft.World/cmake/sources/Common.cmake | 1 + Minecraft.World/stdafx.h | 1 + 1255 files changed, 270119 insertions(+), 1362 deletions(-) create mode 100644 Minecraft.Client/Assets/RmlUiAssets/HelpOptions.rcss create mode 100644 Minecraft.Client/Assets/RmlUiAssets/HelpOptions.rml create mode 100644 Minecraft.Client/Assets/RmlUiAssets/PauseMenu.rcss create mode 100644 Minecraft.Client/Assets/RmlUiAssets/PauseMenu.rml create mode 100644 Minecraft.Client/Assets/RmlUiAssets/SettingsMenu.rcss create mode 100644 Minecraft.Client/Assets/RmlUiAssets/SettingsMenu.rml create mode 100644 Minecraft.Client/Assets/RmlUiAssets/fonts/MinecraftTen.ttf create mode 100644 Minecraft.Client/Assets/RmlUiAssets/fonts/Mojangles.ttf create mode 100644 Minecraft.Client/Assets/RmlUiAssets/fonts/chars.txt create mode 100644 Minecraft.Client/Assets/RmlUiAssets/images/AdventureIcon.png create mode 100644 Minecraft.Client/Assets/RmlUiAssets/images/Button_Background.png create mode 100644 Minecraft.Client/Assets/RmlUiAssets/images/Button_Background2.png create mode 100644 Minecraft.Client/Assets/RmlUiAssets/images/Button_Square.png create mode 100644 Minecraft.Client/Assets/RmlUiAssets/images/Button_Square_Highlighted.png create mode 100644 Minecraft.Client/Assets/RmlUiAssets/images/Change_Skin_Icon.png create mode 100644 Minecraft.Client/Assets/RmlUiAssets/images/CreateWorldIcon.png create mode 100644 Minecraft.Client/Assets/RmlUiAssets/images/CreativeIcon.png create mode 100644 Minecraft.Client/Assets/RmlUiAssets/images/ModIcon.png create mode 100644 Minecraft.Client/Assets/RmlUiAssets/images/OriginalLogo.png create mode 100644 Minecraft.Client/Assets/RmlUiAssets/images/SaveChest.png create mode 100644 Minecraft.Client/Assets/RmlUiAssets/images/SliderHandlerBackground.png create mode 100644 Minecraft.Client/Assets/RmlUiAssets/images/Slider_Handle.png create mode 100644 Minecraft.Client/Assets/RmlUiAssets/images/SurvivalIcon.png create mode 100644 Minecraft.Client/Assets/RmlUiAssets/images/Toggle_Switch_Off.png create mode 100644 Minecraft.Client/Assets/RmlUiAssets/images/Toggle_Switch_On.png create mode 100644 Minecraft.Client/Assets/RmlUiAssets/images/Trash_Bin_Icon.png create mode 100644 Minecraft.Client/Assets/RmlUiAssets/images/button_highlighted.png create mode 100644 Minecraft.Client/Assets/RmlUiAssets/images/down_icon.png create mode 100644 Minecraft.Client/Assets/RmlUiAssets/images/frame_background.png create mode 100644 Minecraft.Client/Assets/RmlUiAssets/images/lceheadwer.png create mode 100644 Minecraft.Client/Assets/RmlUiAssets/images/up_icon.png create mode 100644 Minecraft.Client/Assets/RmlUiAssets/panorama/Background_Day.png create mode 100644 Minecraft.Client/Assets/RmlUiAssets/panorama/Background_Night.png create mode 100644 Minecraft.Client/RmlManager.cpp create mode 100644 Minecraft.Client/RmlManager.h create mode 100644 Minecraft.Client/Windows64/Libs/4JLibs/impls/Windows_Libs/Render/vendor/DirectXMath/DirectXColors.h create mode 100644 Minecraft.Client/Windows64/Libs/4JLibs/impls/Windows_Libs/Render/vendor/DirectXMath/DirectXMath.h create mode 100644 Minecraft.Client/Windows64/Libs/4JLibs/impls/Windows_Libs/Render/vendor/DirectXMath/DirectXMathConvert.inl create mode 100644 Minecraft.Client/Windows64/Libs/4JLibs/impls/Windows_Libs/Render/vendor/DirectXMath/DirectXMathMatrix.inl create mode 100644 Minecraft.Client/Windows64/Libs/4JLibs/impls/Windows_Libs/Render/vendor/DirectXMath/DirectXMathMisc.inl create mode 100644 Minecraft.Client/Windows64/Libs/4JLibs/impls/Windows_Libs/Render/vendor/DirectXMath/DirectXMathVector.inl create mode 100644 Minecraft.Client/Windows64/Libs/4JLibs/impls/Windows_Libs/Render/vendor/DirectXMath/DirectXPackedVector.h create mode 100644 Minecraft.Client/Windows64/Libs/4JLibs/impls/Windows_Libs/Render/vendor/DirectXMath/DirectXPackedVector.inl create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/.appveyor.yml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/.clang-format create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/.editorconfig create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/.gitignore create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/CMakeLists.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_Backend.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_Backend_GLFW_DX12.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_Backend_GLFW_GL2.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_Backend_GLFW_GL3.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_Backend_GLFW_VK.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_Backend_SDL_DX12.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_Backend_SDL_GL2.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_Backend_SDL_GL3.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_Backend_SDL_GPU.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_Backend_SDL_SDLrenderer.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_Backend_SDL_VK.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_Backend_SFML_GL2.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_Backend_Win32_DX12.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_Backend_Win32_GL2.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_Backend_Win32_VK.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_Backend_X11_GL2.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_BackwardCompatible/RmlUi_Backend_BackwardCompatible_GLFW_GL2.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_BackwardCompatible/RmlUi_Backend_BackwardCompatible_GLFW_GL3.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_BackwardCompatible/RmlUi_Renderer_BackwardCompatible_GL2.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_BackwardCompatible/RmlUi_Renderer_BackwardCompatible_GL2.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_BackwardCompatible/RmlUi_Renderer_BackwardCompatible_GL3.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_BackwardCompatible/RmlUi_Renderer_BackwardCompatible_GL3.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_DirectX/.clang-format create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_DirectX/D3D12MemAlloc.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_DirectX/D3D12MemAlloc.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_DirectX/LICENSE.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_DirectX/offsetAllocator.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_DirectX/offsetAllocator.hpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_Include_DirectX_12.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_Include_GL3.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_Include_Vulkan.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_Include_Windows.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_Include_Xlib.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_Platform_GLFW.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_Platform_GLFW.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_Platform_SDL.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_Platform_SDL.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_Platform_SFML.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_Platform_SFML.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_Platform_Win32.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_Platform_Win32.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_Platform_X11.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_Platform_X11.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_Renderer_DX12.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_Renderer_DX12.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_Renderer_GL2.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_Renderer_GL2.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_Renderer_GL3.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_Renderer_GL3.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_Renderer_SDL.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_Renderer_SDL.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_Renderer_SDL_GPU.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_Renderer_SDL_GPU.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_Renderer_VK.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_Renderer_VK.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_SDL_GPU/SDL_shadercross/LICENSE.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_SDL_GPU/ShadersCompiledSPV.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_SDL_GPU/compile_shaders.py create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_SDL_GPU/shader_frag_color.frag create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_SDL_GPU/shader_frag_texture.frag create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_SDL_GPU/shader_vert.vert create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_Vulkan/.clang-format create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_Vulkan/LICENSE.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_Vulkan/ShadersCompiledSPV.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_Vulkan/compile_shaders.py create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_Vulkan/shader_frag_color.frag create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_Vulkan/shader_frag_texture.frag create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_Vulkan/shader_vert.vert create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_Vulkan/vk_mem_alloc.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Backends/RmlUi_Vulkan/vulkan.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/CMake/BackendAutoSelection.cmake create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/CMake/Dependencies.cmake create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/CMake/DependenciesForBackends.cmake create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/CMake/DependenciesForShell.cmake create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/CMake/DependenciesForTests.cmake create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/CMake/Modules/Emscripten/FindFreetype.cmake create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/CMake/Modules/Emscripten/FindOpenGL.cmake create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/CMake/Modules/Emscripten/FindSDL2.cmake create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/CMake/Modules/Emscripten/FindSDL2_image.cmake create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/CMake/Modules/Emscripten/Findglfw3.cmake create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/CMake/Modules/FindHarfBuzz.cmake create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/CMake/Modules/FindLuaJIT.cmake create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/CMake/Modules/FindSDL2_image.cmake create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/CMake/Modules/Findlunasvg.cmake create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/CMake/OptionsLists.cmake create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/CMake/PackageUtilities.cmake create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/CMake/RmlUiConfig.cmake.in create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/CMake/RuntimeUtilities.cmake create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/CMake/Utilities.cmake create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/CMakeLists.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/CMakePresets.json create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Dependencies/.gitignore create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Config/Config.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Animation.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/BaseXMLParser.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Box.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/CallbackTexture.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Colour.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Colour.inl create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/CompiledFilterShader.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/ComputedValues.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Containers/LICENSE.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Containers/itlib/flat_map.hpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Containers/itlib/flat_set.hpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Containers/robin_hood.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Context.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/ContextInstancer.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/ConvolutionFilter.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Core.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/DataModelHandle.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/DataStructHandle.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/DataTypeRegister.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/DataTypes.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/DataVariable.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Debug.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/DecorationTypes.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Decorator.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Dictionary.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/EffectSpecification.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Element.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Element.inl create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/ElementDocument.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/ElementInstancer.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/ElementScroll.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/ElementText.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/ElementUtilities.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Elements/ElementForm.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Elements/ElementFormControl.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Elements/ElementFormControlInput.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Elements/ElementFormControlSelect.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Elements/ElementFormControlTextArea.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Elements/ElementProgress.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Elements/ElementTabSet.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Event.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/EventInstancer.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/EventListener.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/EventListenerInstancer.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Factory.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/FileInterface.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Filter.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/FontEffect.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/FontEffectInstancer.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/FontEngineInterface.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/FontGlyph.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/FontMetrics.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Geometry.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Header.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/ID.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Input.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Log.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Math.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Matrix4.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Matrix4.inl create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Mesh.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/MeshUtilities.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/NumericValue.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/ObserverPtr.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Platform.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Plugin.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Profiling.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/PropertiesIteratorView.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Property.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/PropertyDefinition.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/PropertyDictionary.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/PropertyIdSet.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/PropertyParser.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/PropertySpecification.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Rectangle.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/RenderBox.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/RenderInterface.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/RenderInterfaceCompatibility.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/RenderManager.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/ScriptInterface.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/ScrollTypes.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Span.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Spritesheet.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/StableVector.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Stream.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/StreamMemory.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/StringUtilities.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/StyleSheet.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/StyleSheetContainer.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/StyleSheetSpecification.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/StyleSheetTypes.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/StyleTypes.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/SystemInterface.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/TextInputContext.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/TextInputHandler.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/TextShapingContext.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Texture.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Traits.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Transform.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/TransformPrimitive.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Tween.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/TypeConverter.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/TypeConverter.inl create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Types.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/URL.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/UniqueRenderResource.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Unit.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Utilities.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Variant.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Variant.inl create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Vector2.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Vector2.inl create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Vector3.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Vector3.inl create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Vector4.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Vector4.inl create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Vertex.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/XMLNodeHandler.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/XMLParser.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Debugger.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Debugger/Debugger.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Debugger/Header.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Lottie/ElementLottie.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Lua.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Lua/Header.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Lua/IncludeLua.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Lua/Interpreter.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Lua/Lua.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Lua/LuaType.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Lua/LuaType.inl create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Lua/Utilities.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/SVG/ElementSVG.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/LICENSE.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/.gitignore create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/CMakeLists.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/assets/LICENSE.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/assets/LatoLatin-Bold.ttf create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/assets/LatoLatin-BoldItalic.ttf create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/assets/LatoLatin-Italic.ttf create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/assets/LatoLatin-Regular.ttf create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/assets/NotoEmoji-Regular.ttf create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/assets/RobotoMono-Bold.ttf create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/assets/RobotoMono-BoldItalic.ttf create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/assets/RobotoMono-Italic.ttf create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/assets/RobotoMono-Regular.ttf create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/assets/alien_small.tga create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/assets/demo.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/assets/hello_world.png create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/assets/high_scores_alien_1.tga create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/assets/high_scores_alien_2.tga create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/assets/high_scores_alien_3.tga create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/assets/high_scores_defender.tga create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/assets/invader.rcss create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/assets/invader.tga create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/assets/present.tga create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/assets/rml.rcss create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/assets/window.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/CMakeLists.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/animation/CMakeLists.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/animation/data/animation.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/animation/src/main.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/benchmark/CMakeLists.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/benchmark/data/benchmark.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/benchmark/src/main.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/bitmap_font/CMakeLists.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/bitmap_font/data/Comfortaa_Regular_22.fnt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/bitmap_font/data/Comfortaa_Regular_22.tga create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/bitmap_font/data/LICENSE.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/bitmap_font/data/bitmap_font.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/bitmap_font/src/FontEngineBitmap.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/bitmap_font/src/FontEngineBitmap.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/bitmap_font/src/FontEngineInterfaceBitmap.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/bitmap_font/src/FontEngineInterfaceBitmap.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/bitmap_font/src/main.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/custom_log/CMakeLists.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/custom_log/src/SystemInterface.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/custom_log/src/SystemInterface.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/custom_log/src/main.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/data_binding/CMakeLists.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/data_binding/data/data_binding.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/data_binding/src/main.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/demo/CMakeLists.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/demo/data/demo.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/demo/src/DemoEventListener.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/demo/src/DemoEventListener.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/demo/src/DemoWindow.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/demo/src/DemoWindow.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/demo/src/main.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/drag/CMakeLists.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/drag/data/icon.rcss create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/drag/data/inventory.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/drag/src/DragListener.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/drag/src/DragListener.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/drag/src/Inventory.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/drag/src/Inventory.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/drag/src/main.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/effects/CMakeLists.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/effects/data/effects.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/effects/data/effects_style.rcss create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/effects/src/main.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/CMakeLists.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/data/Cairo-Regular.ttf create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/data/LICENSE.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/data/Poppins-Regular.ttf create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/data/harfbuzz.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FontEngineInterfaceHarfBuzz.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FontEngineInterfaceHarfBuzz.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FontFace.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FontFace.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FontFaceHandleHarfBuzz.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FontFaceHandleHarfBuzz.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FontFaceLayer.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FontFaceLayer.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FontFamily.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FontFamily.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FontGlyph.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FontProvider.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FontProvider.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FreeTypeInterface.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FreeTypeInterface.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/LanguageData.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/TextureLayout.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/TextureLayout.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/TextureLayoutRectangle.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/TextureLayoutRectangle.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/TextureLayoutRow.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/TextureLayoutRow.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/TextureLayoutTexture.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/TextureLayoutTexture.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/main.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/ime/CMakeLists.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/ime/data/ime.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/ime/src/SystemFontWin32.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/ime/src/SystemFontWin32.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/ime/src/main.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/load_document/CMakeLists.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/load_document/src/main.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/lottie/CMakeLists.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/lottie/data/LICENSE.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/lottie/data/a_mountain.json create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/lottie/data/lottie.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/lottie/src/main.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/svg/CMakeLists.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/svg/data/LICENSE.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/svg/data/svg_decorator.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/svg/data/svg_element.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/svg/data/svg_inline.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/svg/data/tiger.svg create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/svg/src/main.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/transform/CMakeLists.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/transform/data/transform.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/transform/src/main.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/tree_view/CMakeLists.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/tree_view/data/tree_view.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/tree_view/src/FileBrowser.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/tree_view/src/FileBrowser.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/tree_view/src/main.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/CMakeLists.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/background.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/background.tga create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/game.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/help.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/help_defender.tga create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/help_invader_mothership.tga create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/help_invader_rank1.tga create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/help_invader_rank2.tga create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/help_invader_rank3.tga create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/high_score.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/high_scores_defender.tga create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/invaders.tga create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/logo.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/logo.tga create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/main_menu.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/options.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/pause.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/start_game.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/DecoratorDefender.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/DecoratorDefender.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/DecoratorStarfield.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/DecoratorStarfield.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/Defender.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/Defender.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/ElementGame.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/ElementGame.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventHandler.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventHandler.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventHandlerHighScore.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventHandlerHighScore.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventHandlerOptions.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventHandlerOptions.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventHandlerStartGame.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventHandlerStartGame.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventListener.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventListener.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventListenerInstancer.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventListenerInstancer.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventManager.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventManager.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/Game.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/Game.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/GameDetails.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/GameDetails.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/HighScores.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/HighScores.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/Invader.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/Invader.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/Mothership.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/Mothership.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/Shield.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/Shield.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/Sprite.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/Sprite.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/main.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/CMakeLists.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/background.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/background.tga create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/game.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/help.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/help_defender.tga create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/help_invader_mothership.tga create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/help_invader_rank1.tga create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/help_invader_rank2.tga create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/help_invader_rank3.tga create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/high_score.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/high_scores_defender.tga create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/invaders.tga create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/logo.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/logo.tga create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/main_menu.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/options.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/pause.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/start_game.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/window.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/lua/start.lua create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/DecoratorDefender.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/DecoratorDefender.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/DecoratorStarfield.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/DecoratorStarfield.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/Defender.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/Defender.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/ElementGame.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/ElementGame.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/ElementGameInstancer.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/ElementGameInstancer.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/Game.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/Game.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/GameDetails.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/GameDetails.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/HighScores.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/HighScores.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/Invader.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/Invader.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/LuaInterface.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/LuaInterface.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/Mothership.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/Mothership.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/Shield.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/Shield.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/Sprite.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/Sprite.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/main.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/readme.md create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/shell/CMakeLists.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/shell/include/PlatformExtensions.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/shell/include/RendererExtensions.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/shell/include/Shell.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/shell/include/ShellFileInterface.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/shell/src/PlatformExtensions.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/shell/src/RendererExtensions.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/shell/src/Shell.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/shell/src/ShellFileInterface.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/tutorial/CMakeLists.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/tutorial/drag/CMakeLists.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/tutorial/drag/data/inventory.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/tutorial/drag/data/tutorial.rcss create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/tutorial/drag/src/Inventory.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/tutorial/drag/src/Inventory.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/tutorial/drag/src/main.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/tutorial/template/CMakeLists.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/tutorial/template/data/tutorial.rcss create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/tutorial/template/data/tutorial.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Samples/tutorial/template/src/main.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/CMakeLists.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/BaseXMLParser.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Box.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/BoxShadowCache.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/BoxShadowCache.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/BoxShadowHash.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/CMakeLists.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/CallbackTexture.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Clock.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Clock.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/CompiledFilterShader.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ComputeProperty.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ComputeProperty.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ComputedValues.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Context.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ContextInstancer.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ContextInstancerDefault.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ContextInstancerDefault.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ControlledLifetimeResource.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ConvolutionFilter.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Core.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataController.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataController.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataControllerDefault.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataControllerDefault.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataExpression.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataExpression.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataModel.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataModel.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataModelHandle.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataTypeRegister.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataVariable.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataView.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataView.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataViewDefault.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataViewDefault.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Decorator.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorGradient.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorGradient.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorNinePatch.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorNinePatch.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorShader.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorShader.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorText.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorText.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorTiled.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorTiled.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorTiledBox.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorTiledBox.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorTiledHorizontal.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorTiledHorizontal.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorTiledImage.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorTiledImage.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorTiledVertical.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorTiledVertical.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorUtilities.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorUtilities.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DocumentHeader.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DocumentHeader.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/EffectSpecification.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Element.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementAnimation.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementAnimation.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementBackgroundBorder.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementBackgroundBorder.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementDefinition.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementDefinition.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementDocument.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementEffects.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementEffects.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementHandle.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementHandle.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementInstancer.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementMeta.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementMeta.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementScroll.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementStyle.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementStyle.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementText.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementUtilities.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/CMakeLists.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/ElementForm.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/ElementFormControl.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/ElementFormControlInput.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/ElementFormControlSelect.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/ElementFormControlTextArea.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/ElementImage.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/ElementImage.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/ElementLabel.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/ElementLabel.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/ElementProgress.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/ElementTabSet.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/ElementTextSelection.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/ElementTextSelection.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputType.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputType.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputTypeButton.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputTypeButton.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputTypeCheckbox.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputTypeCheckbox.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputTypeRadio.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputTypeRadio.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputTypeRange.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputTypeRange.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputTypeSubmit.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputTypeSubmit.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputTypeText.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputTypeText.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/WidgetDropDown.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/WidgetDropDown.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/WidgetSlider.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/WidgetSlider.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/WidgetTextInput.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/WidgetTextInput.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/WidgetTextInputMultiLine.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/WidgetTextInputMultiLine.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/WidgetTextInputSingleLine.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/WidgetTextInputSingleLine.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/WidgetTextInputSingleLinePassword.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/WidgetTextInputSingleLinePassword.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/XMLNodeHandlerSelect.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/XMLNodeHandlerSelect.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/XMLNodeHandlerTabSet.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/XMLNodeHandlerTabSet.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/XMLNodeHandlerTextArea.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/XMLNodeHandlerTextArea.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Event.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/EventDispatcher.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/EventDispatcher.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/EventInstancer.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/EventInstancerDefault.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/EventInstancerDefault.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/EventListenerInstancer.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/EventSpecification.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/EventSpecification.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Factory.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FileInterface.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FileInterfaceDefault.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FileInterfaceDefault.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Filter.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FilterBasic.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FilterBasic.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FilterBlur.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FilterBlur.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FilterDropShadow.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FilterDropShadow.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEffect.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEffectBlur.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEffectBlur.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEffectGlow.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEffectGlow.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEffectInstancer.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEffectOutline.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEffectOutline.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEffectShadow.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEffectShadow.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/CMakeLists.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FontEngineInterfaceDefault.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FontEngineInterfaceDefault.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FontFace.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FontFace.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FontFaceHandleDefault.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FontFaceHandleDefault.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FontFaceLayer.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FontFaceLayer.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FontFamily.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FontFamily.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FontProvider.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FontProvider.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FontTypes.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FreeTypeInterface.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FreeTypeInterface.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineInterface.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Geometry.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/GeometryBackgroundBorder.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/GeometryBackgroundBorder.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/GeometryBoxShadow.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/GeometryBoxShadow.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/IdNameMap.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/BlockContainer.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/BlockContainer.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/BlockFormattingContext.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/BlockFormattingContext.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/CMakeLists.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/ContainerBox.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/ContainerBox.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/FlexFormattingContext.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/FlexFormattingContext.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/FloatedBoxSpace.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/FloatedBoxSpace.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/FormattingContext.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/FormattingContext.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/InlineBox.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/InlineBox.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/InlineContainer.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/InlineContainer.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/InlineLevelBox.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/InlineLevelBox.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/InlineTypes.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/LayoutBox.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/LayoutBox.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/LayoutDetails.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/LayoutDetails.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/LayoutEngine.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/LayoutEngine.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/LayoutPools.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/LayoutPools.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/LineBox.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/LineBox.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/ReplacedFormattingContext.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/ReplacedFormattingContext.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/TableFormattingContext.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/TableFormattingContext.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/TableFormattingDetails.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/TableFormattingDetails.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Log.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/LogDefault.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/LogDefault.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Math.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Memory.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Memory.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/MeshUtilities.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ObserverPtr.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Plugin.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PluginRegistry.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PluginRegistry.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Pool.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Pool.inl create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Profiling.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertiesIterator.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertiesIteratorView.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Property.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyDefinition.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyDictionary.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserAnimation.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserAnimation.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserBoxShadow.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserBoxShadow.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserColorStopList.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserColorStopList.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserColour.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserColour.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserDecorator.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserDecorator.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserFilter.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserFilter.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserFontEffect.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserFontEffect.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserKeyword.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserKeyword.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserNumber.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserNumber.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserRatio.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserRatio.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserString.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserString.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserTransform.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserTransform.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyShorthandDefinition.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertySpecification.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/RenderInterface.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/RenderInterfaceCompatibility.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/RenderManager.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/RenderManagerAccess.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/RenderManagerAccess.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ScrollController.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ScrollController.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Spritesheet.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Stream.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StreamFile.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StreamFile.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StreamMemory.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StringUtilities.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StyleSheet.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StyleSheetContainer.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StyleSheetFactory.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StyleSheetFactory.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StyleSheetNode.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StyleSheetNode.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StyleSheetParser.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StyleSheetParser.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StyleSheetSelector.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StyleSheetSelector.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StyleSheetSpecification.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/SystemInterface.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Template.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Template.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TemplateCache.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TemplateCache.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Texture.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TextureDatabase.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TextureDatabase.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TextureLayout.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TextureLayout.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TextureLayoutRectangle.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TextureLayoutRectangle.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TextureLayoutRow.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TextureLayoutRow.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TextureLayoutTexture.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TextureLayoutTexture.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Traits.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Transform.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TransformPrimitive.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TransformState.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TransformState.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TransformUtilities.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TransformUtilities.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Tween.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TypeConverter.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/URL.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Variant.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/WidgetScroll.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/WidgetScroll.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/XMLNodeHandler.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/XMLNodeHandlerBody.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/XMLNodeHandlerBody.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/XMLNodeHandlerDefault.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/XMLNodeHandlerDefault.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/XMLNodeHandlerHead.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/XMLNodeHandlerHead.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/XMLNodeHandlerTemplate.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/XMLNodeHandlerTemplate.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/XMLParseTools.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/XMLParseTools.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/XMLParser.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/precompiled.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/.clang-format create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/BeaconSource.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/CMakeLists.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/CommonSource.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/DataModelsSource.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/Debugger.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/DebuggerPlugin.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/DebuggerPlugin.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/DebuggerSystemInterface.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/DebuggerSystemInterface.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/ElementContextHook.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/ElementContextHook.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/ElementDataModels.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/ElementDataModels.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/ElementDebugDocument.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/ElementDebugDocument.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/ElementInfo.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/ElementInfo.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/ElementLog.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/ElementLog.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/FontSource.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/Geometry.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/Geometry.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/InfoSource.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/LICENSE.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/LogSource.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/MenuSource.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lottie/CMakeLists.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lottie/ElementLottie.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lottie/LottiePlugin.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lottie/LottiePlugin.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/.clang-format create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/CMakeLists.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Colourb.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Colourb.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Colourf.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Colourf.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Context.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Context.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/ContextDocumentsProxy.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/ContextDocumentsProxy.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Document.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Document.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Element.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Element.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/ElementAttributesProxy.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/ElementAttributesProxy.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/ElementChildNodesProxy.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/ElementChildNodesProxy.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/ElementInstancer.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/ElementInstancer.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/ElementStyleProxy.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/ElementStyleProxy.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/ElementText.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/ElementText.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/As.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/CMakeLists.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/ElementForm.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/ElementForm.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/ElementFormControl.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/ElementFormControl.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/ElementFormControlInput.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/ElementFormControlInput.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/ElementFormControlSelect.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/ElementFormControlSelect.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/ElementFormControlTextArea.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/ElementFormControlTextArea.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/ElementTabSet.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/ElementTabSet.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/SelectOptionsProxy.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/SelectOptionsProxy.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Event.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Event.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/EventParametersProxy.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/EventParametersProxy.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/GlobalLuaFunctions.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/GlobalLuaFunctions.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Interpreter.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Log.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Log.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Lua.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaDataModel.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaDataModel.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaDocument.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaDocument.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaDocumentElementInstancer.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaDocumentElementInstancer.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaElementInstancer.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaElementInstancer.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaEventListener.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaEventListener.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaEventListenerInstancer.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaEventListenerInstancer.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaPlugin.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaPlugin.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaType.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Pairs.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/RmlUi.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/RmlUi.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/RmlUiContextsProxy.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/RmlUiContextsProxy.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Utilities.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Vector2f.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Vector2f.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Vector2i.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Vector2i.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/SVG/CMakeLists.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/SVG/DecoratorSVG.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/SVG/DecoratorSVG.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/SVG/ElementSVG.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/SVG/SVGCache.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/SVG/SVGCache.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/SVG/SVGPlugin.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/SVG/SVGPlugin.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/SVG/XMLNodeHandlerSVG.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Source/SVG/XMLNodeHandlerSVG.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/CMakeLists.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/UnitTests/Specificity_Basic.rcss create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/UnitTests/Specificity_MediaQuery.rcss create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/UnitTests/data-title.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/UnitTests/template_basic.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/LICENSE.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/acid1.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/border_radius.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/box_sizing.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/clip_mask.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/color_interpolation.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/css1_clear.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/display_block_inside_inline.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/document_position.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/drag.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/element_label.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/filter_backdrop_filter_box_shadow.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/filter_blur_area.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/filter_blur_orb.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/filter_drop_shadow.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/filter_overflow.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/filter_with_backdrop.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/flex_01.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/flex_02.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/flex_03_scroll.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/flex_04.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/flex_05.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/flex_06.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/flex_absolutely_positioned.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/flex_direction.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/flex_inline.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/flex_nested.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/flex_shrink_to_fit_01.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/flex_shrink_to_fit_02.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/flex_wrap_column_reverse.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/float_basic.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/float_excluded_by_flow_root.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/float_overflow.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/float_placement.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/float_text_wrap.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/fractional-dimensions-01.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/fractional-dimensions-02.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/fractional-dimensions-03.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/gap-001.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/gap-002.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/gap-003.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/gap-004.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/gap-005.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/gap-006.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/gap-007.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/gap-008.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/gap-009.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/handle_target_anchor.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/image_ratio.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/ink_overflow_bounding_box.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/inline_block.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/inline_formatting_01.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/inline_formatting_02.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/inline_formatting_03.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/inline_formatting_04.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/inline_formatting_05.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/inline_formatting_06.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/inline_formatting_07.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/inline_formatting_aligned_subtree.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/issue_143_overflow_in_absolutely_positioned_input.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/issue_452_scrollbar_resets.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/letter_spacing.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/mask_image.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/media_query_01.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/media_query_02.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/navigation_01.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/navigation_02.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/opacity.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/overflow_clip_transform.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/overflow_hidden.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/overflow_nested.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/position_01_normal_flow.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/position_02_relative_positioning.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/position_03_floating_a_box.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/position_04_floating_a_box_sibling.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/position_05_floating_a_box_clear.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/position_06_absolute_positioning.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/position_07_absolute_positioning_relative.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/position_08_absolute_positioning_no_relative.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/position_09_absolute_positioning_change_bars.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/position_absolute_static_position.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/position_absolute_transform.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/position_relative.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/reference/flex_05-ref.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/reference/flex_06-ref.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/reference/flex_direction-ref.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/reference/flex_nested-ref.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/reference/flex_wrap_column_reverse-ref.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/reference/fractional-dimensions-02-ref.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/reference/gap-001-ref.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/reference/gap-002-ref.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/reference/gap-003-ref.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/reference/gap-004-ref.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/reference/gap-005-ref.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/reference/gap-006-ref.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/reference/gap-007-ref.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/reference/gap-008-ref.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/reference/gap-009-ref.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/reference/overflow_hidden-ref.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/reference/position_absolute_static_position-ref.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/reference/table_01-ref.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/renderer_alpha_blending_01.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/renderer_alpha_blending_02.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/renderer_alpha_blending_03.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/replaced_display.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/replaced_elements.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/shader_conic_gradient.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/shader_linear_gradient.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/shader_radial_gradient.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/shrink_to_fit_01.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/shrink_to_fit_02.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/shrink_to_fit_03.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/shrink_to_fit_04.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/stacking_context_01.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/stacking_context_02.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/table_01.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/table_02.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/table_03.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/table_04.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/table_05.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/table_inline.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/text_alignment.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/text_decorator.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/text_input_overflow.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/text_overflow_01.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/text_overflow_02.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/white_space.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/word_break.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/description.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/style.rcss create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/view_source.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/visual_tests_help.rml create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Dependencies/LICENSE.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Dependencies/doctest/cmake/Config.cmake.in create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Dependencies/doctest/cmake/assemble_single_header.cmake create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Dependencies/doctest/cmake/common.cmake create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Dependencies/doctest/cmake/doctest.cmake create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Dependencies/doctest/cmake/doctestAddTests.cmake create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Dependencies/doctest/cmake/exec_test.cmake create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Dependencies/doctest/doctest.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Dependencies/lodepng/lodepng.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Dependencies/lodepng/lodepng.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Dependencies/nanobench/nanobench.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Dependencies/trompeloeil/doctest/trompeloeil.hpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Dependencies/trompeloeil/trompeloeil.hpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Dependencies/trompeloeil/trompeloeil/coro.hpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Dependencies/trompeloeil/trompeloeil/cpp11_shenanigans.hpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Dependencies/trompeloeil/trompeloeil/lifetime.hpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Dependencies/trompeloeil/trompeloeil/matcher.hpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Dependencies/trompeloeil/trompeloeil/matcher/any.hpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Dependencies/trompeloeil/trompeloeil/matcher/compare.hpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Dependencies/trompeloeil/trompeloeil/matcher/deref.hpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Dependencies/trompeloeil/trompeloeil/matcher/not.hpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Dependencies/trompeloeil/trompeloeil/matcher/re.hpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Dependencies/trompeloeil/trompeloeil/mock.hpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Dependencies/trompeloeil/trompeloeil/sequence.hpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Dependencies/trompeloeil/trompeloeil/stream_tracer.hpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Output/.gitignore create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/Benchmarks/BackgroundBorder.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/Benchmarks/CMakeLists.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/Benchmarks/DataBinding.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/Benchmarks/DataExpression.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/Benchmarks/Element.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/Benchmarks/ElementDocument.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/Benchmarks/Flexbox.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/Benchmarks/FontEffect.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/Benchmarks/Selectors.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/Benchmarks/Table.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/Benchmarks/WidgetTextInput.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/Benchmarks/main.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/CMakeLists.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/Common/CMakeLists.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/Common/Mocks.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/Common/TestsInterface.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/Common/TestsInterface.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/Common/TestsShell.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/Common/TestsShell.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/Common/TypesToString.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/UnitTests/Animation.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/UnitTests/CMakeLists.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/UnitTests/Core.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/UnitTests/DataBinding.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/UnitTests/DataExpression.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/UnitTests/DataModel.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/UnitTests/Debugger.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/UnitTests/Decorator.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/UnitTests/Element.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/UnitTests/ElementBackgroundBorder.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/UnitTests/ElementDocument.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/UnitTests/ElementFormControlSelect.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/UnitTests/ElementHandle.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/UnitTests/ElementImage.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/UnitTests/ElementStyle.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/UnitTests/EventListener.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/UnitTests/Filter.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/UnitTests/FlexFormatting.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/UnitTests/Layout.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/UnitTests/Localization.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/UnitTests/Math.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/UnitTests/MediaQuery.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/UnitTests/Navigation.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/UnitTests/PluginDataModel.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/UnitTests/Properties.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/UnitTests/PropertySpecification.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/UnitTests/Selectors.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/UnitTests/Specificity_Basic.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/UnitTests/Specificity_MediaQuery.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/UnitTests/StableVector.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/UnitTests/StringUtilities.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/UnitTests/StyleSheetParser.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/UnitTests/Template.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/UnitTests/URL.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/UnitTests/Variant.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/UnitTests/XMLParser.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/UnitTests/main.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/VisualTests/CMakeLists.txt create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/VisualTests/CaptureScreen.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/VisualTests/CaptureScreen.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/VisualTests/TestConfig.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/VisualTests/TestConfig.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/VisualTests/TestNavigator.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/VisualTests/TestNavigator.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/VisualTests/TestSuite.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/VisualTests/TestViewer.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/VisualTests/TestViewer.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/VisualTests/XmlNodeHandlers.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/VisualTests/XmlNodeHandlers.h create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Source/VisualTests/main.cpp create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/Tools/convert_css_test_suite_to_rml.py create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Tests/readme.md create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/Utilities/natvis/RmlUi.natvis create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/changelog.md create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/contributing.md create mode 100644 Minecraft.Client/Windows64/Libs/RmlUi/readme.md create mode 100644 Minecraft.Client/Windows64/RmlUi_FileInterface.cpp create mode 100644 Minecraft.Client/Windows64/RmlUi_FileInterface.h create mode 100644 Minecraft.Client/Windows64/RmlUi_Renderer_D3D11.cpp create mode 100644 Minecraft.Client/Windows64/RmlUi_Renderer_D3D11.h create mode 100644 Minecraft.Client/Windows64/RmlUi_SystemInterface_Win64.cpp create mode 100644 Minecraft.Client/Windows64/RmlUi_SystemInterface_Win64.h create mode 100644 Minecraft.Server/RmlManagerStub.cpp create mode 100644 Minecraft.World/StaticDataStubs.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 7e2367df..31b2ef36 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,9 @@ set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) -set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) +if(NOT MINGW) + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) +endif() if(NOT WIN32 AND NOT CMAKE_CROSSCOMPILING) message(FATAL_ERROR "This CMake build currently supports Windows only. For cross-compilation from Linux, use the clang-cl toolchain.") diff --git a/CMakePresets.json b/CMakePresets.json index cb08b959..f1c4167c 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -14,7 +14,8 @@ "cacheVariables": { "PLATFORM_DEFINES": "_WINDOWS64", "PLATFORM_NAME": "Windows64", - "IGGY_LIBS": "iggy_w64.lib;iggyperfmon_w64.lib;iggyexpruntime_w64.lib" + "IGGY_LIBS": "iggy_w64.lib;iggyperfmon_w64.lib;iggyexpruntime_w64.lib", + "CMAKE_ASM_MASM_COMPILER": "C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/MSVC/14.16.27023/bin/HostX64/x64/ml64.exe" } } ], diff --git a/Minecraft.Client/Assets/RmlUiAssets/HelpOptions.rcss b/Minecraft.Client/Assets/RmlUiAssets/HelpOptions.rcss new file mode 100644 index 00000000..1a8e7ae6 --- /dev/null +++ b/Minecraft.Client/Assets/RmlUiAssets/HelpOptions.rcss @@ -0,0 +1,55 @@ +body +{ + width: 100%; + height: 100%; + font-family: Minecraft Default; + font-size: 14px; + color: #ffffff; + background: transparent; + display: flex; + align-items: center; + justify-content: center; +} + +div#help_options_menu +{ + width: 624px; + height: auto; + margin: auto; + background: rgba(0, 0, 0, 0.65); + border-top: 2px solid #555555; + border-bottom: 2px solid #555555; + border-left: 2px solid #555555; + border-right: 2px solid #555555; + border-radius: 8px; + padding: 40px 48px; +} + +div#buttons +{ + width: 100%; + display: flex; + flex-direction: column; + align-items: center; +} + +button +{ + display: block; + width: 524px; + height: 64px; + margin-bottom: 11px; + color: #ffffff; + font-size: 25px; + text-align: center; + line-height: 64px; + cursor: pointer; + border: none; + background: transparent; + decorator: image(images/Button_Background.png); +} + +button:hover +{ + decorator: image(images/button_highlighted.png); +} diff --git a/Minecraft.Client/Assets/RmlUiAssets/HelpOptions.rml b/Minecraft.Client/Assets/RmlUiAssets/HelpOptions.rml new file mode 100644 index 00000000..6156124d --- /dev/null +++ b/Minecraft.Client/Assets/RmlUiAssets/HelpOptions.rml @@ -0,0 +1,17 @@ + + + Help & Options + + + +
+
+ + + + + +
+
+ +
diff --git a/Minecraft.Client/Assets/RmlUiAssets/PauseMenu.rcss b/Minecraft.Client/Assets/RmlUiAssets/PauseMenu.rcss new file mode 100644 index 00000000..8d539ef6 --- /dev/null +++ b/Minecraft.Client/Assets/RmlUiAssets/PauseMenu.rcss @@ -0,0 +1,158 @@ +body +{ + width: 100%; + height: 100%; + font-family: Minecraft Default; + font-size: 14px; + color: #ffffff; + background: transparent; + display: flex; + align-items: center; + justify-content: center; +} + +div#pause_menu +{ + width: 624px; + height: auto; + margin: auto; + background: rgba(0, 0, 0, 0.65); + border-top: 2px solid #555555; + border-bottom: 2px solid #555555; + border-left: 2px solid #555555; + border-right: 2px solid #555555; + border-radius: 8px; + padding: 40px 48px; +} + +div#buttons +{ + width: 100%; + display: flex; + flex-direction: column; + align-items: center; +} + +button +{ + display: block; + width: 524px; + height: 64px; + margin-bottom: 11px; + color: #ffffff; + font-size: 25px; + text-align: center; + line-height: 64px; + cursor: pointer; + border: none; + background: transparent; + decorator: image(images/Button_Background.png); +} + +button:hover +{ + decorator: image(images/button_highlighted.png); +} + +div#exit_dialog +{ + display: none; + width: 100%; + margin-top: 16px; + text-align: center; +} + +p#exit_text +{ + font-size: 31px; + color: #ffffff; + margin-bottom: 23px; +} + +div#exit_buttons +{ + width: 100%; + display: flex; + justify-content: center; + gap: 10px; + flex-wrap: wrap; +} + +button#exit_save, +button#exit_nosave, +button#exit_cancel +{ + display: inline-block; + width: auto; + min-width: 233px; + height: 58px; + margin: 0; + padding-left: 29px; + padding-right: 29px; + color: #ffffff; + font-size: 22px; + text-align: center; + line-height: 58px; + cursor: pointer; + border: none; + background: transparent; + decorator: image(images/Button_Background2.png); +} + +button#exit_save:hover, +button#exit_nosave:hover, +button#exit_cancel:hover +{ + decorator: image(images/button_highlighted.png); +} + +div#save_dialog +{ + display: none; + width: 100%; + margin-top: 16px; + text-align: center; +} + +p#save_text +{ + font-size: 31px; + color: #ffffff; + margin-bottom: 23px; +} + +div#save_buttons +{ + width: 100%; + display: flex; + justify-content: center; + gap: 10px; +} + +button#save_cancel, +button#save_confirm +{ + display: inline-block; + width: auto; + min-width: 204px; + height: 58px; + margin: 0; + padding-left: 29px; + padding-right: 29px; + color: #ffffff; + font-size: 22px; + text-align: center; + line-height: 58px; + cursor: pointer; + border: none; + background: transparent; + decorator: image(images/Button_Background2.png); +} + +button#save_cancel:hover, +button#save_confirm:hover +{ + decorator: image(images/button_highlighted.png); +} + + diff --git a/Minecraft.Client/Assets/RmlUiAssets/PauseMenu.rml b/Minecraft.Client/Assets/RmlUiAssets/PauseMenu.rml new file mode 100644 index 00000000..97610aa2 --- /dev/null +++ b/Minecraft.Client/Assets/RmlUiAssets/PauseMenu.rml @@ -0,0 +1,32 @@ + + + Pause Menu + + + +
+
+ + + + + +
+
+

Exit Game?

+
+ + + +
+
+
+

Overwrite existing save?

+
+ + +
+
+
+ +
diff --git a/Minecraft.Client/Assets/RmlUiAssets/SettingsMenu.rcss b/Minecraft.Client/Assets/RmlUiAssets/SettingsMenu.rcss new file mode 100644 index 00000000..911340ea --- /dev/null +++ b/Minecraft.Client/Assets/RmlUiAssets/SettingsMenu.rcss @@ -0,0 +1,104 @@ +body +{ + width: 100%; + height: 100%; + font-family: Minecraft Default; + font-size: 14px; + color: #ffffff; + background: transparent; + display: flex; + align-items: center; + justify-content: center; +} + +div#settings_menu +{ + width: 624px; + height: auto; + margin: auto; + background: rgba(0, 0, 0, 0.65); + border-top: 2px solid #555555; + border-bottom: 2px solid #555555; + border-left: 2px solid #555555; + border-right: 2px solid #555555; + border-radius: 8px; + padding: 40px 48px; +} + +div#buttons +{ + width: 100%; + display: flex; + flex-direction: column; + align-items: center; +} + +button +{ + display: block; + width: 524px; + height: 64px; + margin-bottom: 11px; + color: #ffffff; + font-size: 25px; + text-align: center; + line-height: 64px; + cursor: pointer; + border: none; + background: transparent; + decorator: image(images/Button_Background.png); +} + +button:hover +{ + decorator: image(images/button_highlighted.png); +} + +div#confirm_dialog +{ + display: none; + width: 100%; + margin-top: 16px; + text-align: center; +} + +p#confirm_text +{ + font-size: 31px; + color: #ffffff; + margin-bottom: 23px; +} + +div#confirm_buttons +{ + width: 100%; + display: flex; + justify-content: center; + gap: 10px; +} + +button#confirm_no, +button#confirm_yes +{ + display: inline-block; + width: auto; + min-width: 204px; + height: 58px; + margin: 0; + padding-left: 29px; + padding-right: 29px; + color: #ffffff; + font-size: 22px; + text-align: center; + line-height: 58px; + cursor: pointer; + border: none; + background: transparent; + decorator: image(images/Button_Background2.png); +} + +button#confirm_no:hover, +button#confirm_yes:hover +{ + decorator: image(images/button_highlighted.png); +} diff --git a/Minecraft.Client/Assets/RmlUiAssets/SettingsMenu.rml b/Minecraft.Client/Assets/RmlUiAssets/SettingsMenu.rml new file mode 100644 index 00000000..37b86491 --- /dev/null +++ b/Minecraft.Client/Assets/RmlUiAssets/SettingsMenu.rml @@ -0,0 +1,25 @@ + + + Settings + + + +
+
+ + + + + + +
+
+

Reset all settings to defaults?

+
+ + +
+
+
+ +
diff --git a/Minecraft.Client/Assets/RmlUiAssets/fonts/MinecraftTen.ttf b/Minecraft.Client/Assets/RmlUiAssets/fonts/MinecraftTen.ttf new file mode 100644 index 0000000000000000000000000000000000000000..4cd602f8cc1136a1d8c34784d47b6b891736de74 GIT binary patch literal 218588 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

y`H^dX>GZUK6jG*TQS%wei|{jlHH`bFZb>+H30# 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 E2h6n!wEzGB literal 0 HcmV?d00001 diff --git a/Minecraft.Client/Assets/RmlUiAssets/fonts/Mojangles.ttf b/Minecraft.Client/Assets/RmlUiAssets/fonts/Mojangles.ttf new file mode 100644 index 0000000000000000000000000000000000000000..7f3d0dcf6ea279c0f5b6d6e7bcfd6428fdd0d2a2 GIT binary patch literal 235228 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

`KH>^m2cEhT|XQC!==4q=XUwRoQ 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�nz;?1Ch%s%^wlFuYCRw6~S9q4(W4DTaEePuVlmQMuajEJt~tcbMVNJvq& z5ymQ21;2|HY5 z7ziLDy%o|F5QK1Iey*OTBSvIAF0GH?egSGRAQk12T%j8lvPi3s7`vF);M7>VDq!@I zz8_>O7wy;Rrx1C`HI@Q`;{D}%PuamPT%eVhYhbFahrZ$fHHZPNWFGfYa#*;*f;v4j z4eksDEJqh{py38FfWR6yVB>Y#thutQMF|?PhH!_FMd38h2`1+8I2KI087gE0A%sCs z)B}Ndi$oC&2{D+bCxeT)nQ=GHNo!G@;B1eiaJDjoQb{9_;l*!Y)Yzjz5H^0{a190_ z0`h}%rtE6#IC>3kMUL({%b`p2#j>{XO^I-gSjX~-gHSxkH-tSMFB~QH14eSoOQuYii00XFo@_Ply~e7C!<0l(6|sq1 zB5114)xb26GN%e8UBpI?T>7tK2-}ZvaDQ159Slsc;$dpp?T<{z0Ru%ZJO~D?c49P) z6_}ZT)v<|*Q#EiH*H%{GOMWjS`(g&RLnRV7+Bf@Qc*)*Ix25CS39rRqGpJG+Y8(A> zQLBJ(53&%_h}{g6nTTD#(h(NvLMGAG2Y9kSpa%%{CtN(+Ghd2`tVR0j0TNo}BIq%8 zV0M(ZSdMA-^v>NR02q*;r)(G*g-??q)8>Wj3EOG&_IwBSwVNWyR31 z2OACe*3u-$-#>j@O0Tnf1BQ@d`pa3@| z-X&EbA%Bcj0SCtFq%!PFZKu_k$vlMu7kiylXr$6`UEWUOi$i?tDChV`;MruYhI|se z_JMAZ&wRZE_n_XUCL~ z0S~txUzFD(U_h3-B&eE^)aSb>sb9>l$1GLX!EvM&;kvA>q3O_s@yG(A%i)`)1`{u) z+ohxp2^UtFiLfPh4%nzLBpbOZToX3t@J%G?qt?`>L%0P5C?UpKgdqZ`xgb4jSQuO( zDhve5Otd`;Hk%H^}~ERv9n`odQ>oIJ5wi?+|-cJ z1%()cftV*DrR?5-L0X$gSz^n?a@70z`viq(lzn-#zx(D?3n87=KEOLO%|mSPL?k30 z-Z~c51g-Ka$Y$!oVCZd3fIi>YohI3Z*!)V1rUuzI60*iY#O$i7Jvjw2=y7!_j&-oL zmg557C7@A;?&!?qg@7wW7Mr{s2F-w6p z!|Fq=esK*nsFUKl5Z?L3c^2zZW^0td?@Js0N?BnxYnHn;^BFCXFWBiVU%CQy#O_k8)| zw9Qf)%+(=Wk+tVUyHg9V2C$PX)yv8G3z>@1)GOjFXs0$EYjmD^-Js8Ja1F)FprrEjtKqcs#aI;{y z>1ZuOky98Fr+icFBeECCKk_x#h3yh%;*i(OWM*(0Eo37S@CT9L!DlZ4zA%FAYSkhE z=M4b_Wjl(fN@K>xVRlan`j0dM1x*v$u0~AFbE#o?JezPG!JQz0mA177_j8P(WqKW{ zTpgeTJ&eDrkg&H;!Wstzz=S;+OMHm!rf>qLFyq(x`{8{SwpkeEN*t_R{}vQRD7pOe zXe~_JqX>=I#*?;Ytf)3k=AVf(1-x!i8rs0I3Q32^5eQxauqCl`WAL z>9936d17zibW)>%3S|k4+=jQPS86AvKC_kG64#KgTp_r^t3#QTz2@3!mLBDbQw~OL z!cM&pX>qN?hanK^cG!kv&;~_{B=KPXk%aifvo)bNrkkuFjp$BN4VdeR4)A6$g-ptY0?@b zvB`ZRV}Bb)N9ZJ}KB_}vv{4DLiJ}Nkq@$8hOL$;Pv5#*MTq4^9i>28n4xUL60*3Zk zoxypQZG40oOs3J13X4!Ud52@0uGg7xZLtJ_4afT@sKT>BUiEFVM)oE>VK_yy!YZTd99_Cl{ zh#M$-pn@KOQyD&%iBu3(G`O?`DA?kJt)>zCI5avEC4Inr8vsrR>j`vhLqp)+qXB*l z{S7@pBH$el2X7qAxRH6;-^7^VRy{jg5y!1I1g*y{Arm^Q;8s(j*9E(G79|yKh#06+ zzl1I;G|yI#`3v+x74lgZ#VG?>8L#}D&5*1`KaC&dV){u~A5hc&_s)n}T`q%H$ZlDVp06Z4Sw2vN{p zX7*)N_M1ou#~Vo%053Y_V#$G{PSavvNVuNC z*|y;*w!*?@0(i5n*mx$vL8pItBk zK-p-UbvzS0!WGI^z;xH1{-k@rMpY3C&?ZE8;GE@n<=wM^^;hzvfY%4Sp%@4 ze*Tp(y~dj-`IHus-B0oKQ%rtLcrmF7MNYd3jxO~gyhoE`EaXcRZWkCs*RlwfVf3=X zK_2`k8AO~mwe`_RIq@@1nnt(iiKO9H8GR$IO|nyz@tMeyIf)B(CFS-_f}k=Wo#O)# zWXL&52x_Aaet-wHz>Xq~5rhz)_KLiQebWqg7t`K@I1Rzj)H$>e0ht?Wk_6@Estw8V z$JD@AFb^_C9*_%ZNljBYuh#MtB*uRnO3*n20E2)qsmVk^AFiMeAW(3-d@imlg@L`Y z6hXm%3y|H^;7iDZymmg}N3K)=hT_9w8@$MkKXGSrub2ekm(3BpejH@jj*A$PH~ESg zmRTZyGoxt;>Ugm8jA}|p5XXOf8qBB(IAdXWXGW)Yz(6U1369LxgC2ukUXYAJd;q-A z3N6F*G(`3}K^h3h3-dA>en)zfFT;dC&e}ac8LrM!0e{i}6&W2ek_uI^GL0}fZ%jj- zicrkcn_)g5eaS1J16n2)v|X-YuQ_X$K` z1G`8hZzXE+!B;#iVCWG;ga3}dn3K|&w7cPk{$ONP`e_^Bf!dI;e8Q;riUPtjc*EBW z0@^ffeWlW!Fs-Bte1k9vn)-M{GFAiVH9#&YR3T-^0338L5KR4TiHRNjBx9{P6ktjv z3V{(aa9WB7>mdqw#{|x90BZEr$o9wKB6!SN@&nOl{(y0;Qmn$CtV+;cG&St4{g8A3 z=+n=zy_X|#g{G!9e&e4LwA;RajVrbEkZ-(fL!8i|dAtktSs_XT$-Iw}i4f3iYsH)N z$x&(a5;Ehj4C!Qw>2QP?prC1&X=1Ych#=@Nj#G+IL@B>u*<0y@eF=yB!mJGcuY#;x zxrQLHmE{#%P1pK{_Z#ud7ZBnMAUz=4lguh46cq_#k}1eCh)Nxcg*Q7mbSqzra`)+A z^4H-A#Zj8 z8#gP;Ct2E+O8P_HtbNJQ!9+hMKJaz(3Zgq44+8O|3Yr!9L3V&ai3$28H7SBIoQsP$ zs$IBzha^|C5{wA`9&lPtP}CDCArqb?nC(gOy;p z-B{X@s49+Mb~kn$owERT0~cAzriWRs9TPACFv>SQ!Yj>

hx-!ZsXOpGs^JRK06C$U{vh>01i8d_YCsf~S0~{Fz);6~Ys^D& zFscATnp&uUtWqFaCD7J-RT`QaaJuv&wc|)pFhDpG@0L4~8$*=W)V8DrJ??%%0-(={ z@|gR%O%Ebk?o1<@WDHjQ*&PLMISJ2)=xyxS^I=2_0UW1e1t#KVQ z8a2)-sSO21P=m6{0ib1Dm4d3gh$rla@Nr5&;ZR8&4aHnVOC-pGK|M`p#OtP5m8HdG ze#9azM}pRT#FQv{@dS{;rSAeorq{U0G@xidLd@uYeLxY8m}?YpP>DL>+Czj1)G zePQQ4q~r*SlXF8UB_`cLt>dQMkVJcGFdV7bX=^F9k%WO>FQKXy=tudKQkBa&Y(fq* zXL@^vuXCrmVH2~0Jclv;mNpw&h{bP(x}bbW4G|FRsWG#yt`xX{dZ27DVLnLcv!d`w zC3halfC6aH{mP;UMj5ux9pOq3KBpi6q|>SPbw*lP{0sBdpae;5>IzVr>v)KRf5bQE zC!C%fPc+L@K1of3E9Y5a^N~Ez$s|F&geQ6lnym(2!z?d6;(j*@G`y?Kp}r?dZR}OT zRnl>>rf5+vqXdJw=^$FDAOMgySq{XK<>rcpqn%p)zatTFKB7;MTmLz9B8F?KwITY5+SlT3Muk- z=r9$d&>lur<|xiDEymX)-Fi%hlhx^I%Ou1#V&0SRY9&u%JS@IjVA9sZN4aRfhf>09 zL(Fi>oWO}FcExOt&G!$Hy?#{ftwPasYPMt;BOadNU_5|2^DbWko)v8SNJG~MYekGz zXR*ANbd-P;xFxTBV9wXmI|fCoEj14WnXjoBC(QUZw8ukTLnSbOCZ=S@MuiC|LC*(I za_SGyphgaIhOmJ;$V3F)g2`z4N zfxP%f41xomArPgEk8;r^^(!anDpW{dOzXD$WRlS88A6mvlr{6vBh*#<&}jeh2Po6<-tti0l9L-!shiCC|daI9e;W}D5U<~}vj zjPlWiyGbq4*t!X##n%q;)-(=zQJx-2NrF06Kw=puz}$UK7{=6y zmcRl@W4GuW#s?D{^ez|isS+H=Z%9ILH+MX^n43v0IGar^=?($}eW(Y>P$~fl!P=~j*xEb%2BqWz{0s6=M?ER*%26Q74it`MJn9IR16PzHHzp)L&D?` z>wiSvx*R7zl0F2?jE6Z!ph${c2|MuY27DA?67iQ10oVYOg$W|&P)@`H8bWZa!Cd*^ z#AX6EW$v}SSX2&@Ox47hThAwV{WLj1kC*i33|fc{&04VMo}mo7bdxCWfHD{k zMtY@-$tB;OJjex+skZfhDm!#ZlwNt%_{q5*{c?_tl+6_D82KfB+zBKC(_)<-lUB^c z!6a60&D)Fc_IllzjVZ(xfEt7{Elt!&6^4=00X|#MfEcUGWF`pHGT|X`7)0pHnJ(I_ zN~i0=aW_x%gdhM4FfZ3gJFx^%@K0YCWfiRp9sC*%33En98HWvt$iVm@1_C5$2v!VO zgv${ojHm^FlT|_uu>_$A4AB5BZ@4uJz~cooP0seJ*=&ORg_7KBMI*XK7|-!x4tISp zmxGUmS1h<_pffYRh<&QOL|<5n^9jQle}n;zgk#G9iNPe!c(9d$$t-8eRPK7nN@s(7 zbTXXDs^mOOd1&B1^tDkl7X*`ls|PS5ASnSKc;kl0FLWy?#dWJhDxhqJ7Tl_QE<$=5 zk#hZf&=B;4Mx-`|cP|sAiuy&VEHw&Tw)_NO!RVl~U>L=fi9nW{pd<@4BbgB1thccF z5EGt+6il8gke&?`G?C^!@v4J;_Dgu;HApS)6`1sr9T$!QYh2OaKNw-p;)MSKIzFM@z2ocZeoyTN0mX*%P9X22={EoiWI0y6imrt~W=P z&kfOvCp%_f1jRnp*U>7ra#p-q2ulk1f!iv8#RA_%_Dtqooz-b}W&NJf*>D(;p`r%x zI8n&oLxvrcH`{tL+QA{TC{adxX41{l^dYE_m^P_AO|EfNNOmwem+ZrW zRk;ZMHWd@b#CRnvVN%f1dfISsO`?iZ7|Dbpqxl{&byg2Og6s!lY|xP^V&x)op>O@y zXpzGcGpKAU>*}Nk>V*Ha#`tsyl_obZ;OJeRozj#H`(-RrrK#EL1pq^woAgNN z=!fBLw2m4DeyY-XhJ>>te*-StPA$Z-eRHeBT@VG&dNsQ${n6UO?t_v>*Ez z=qXL@@VL`QlL|B;45U?B9BjijctP2MIL6U#d!o4Hy8b4NSWZLyPh&{2{WDdmEai=d>5V}uhD@0IiZRVJpdQkRAb~5b z@L=$TosbhmDK>x*fxfXLmGXHk&0!F-gI4tL3GRffWM=XPMjES&4IGNj#U@3m=U}AH z%EAm4rDDZ%k%H@u{^}(3A(-=_Y2iLsNq%GoH+I4CE?{(ia&SDHf|x#G zJ*$kc+bWt8Z5c(E{UB;^03_u{%yxQXU5gCKlLR?uLNn?aukbnF6QRJVXlu}6bU}!; zM4l%n*ze)lc$lXIr8sg2F_K0C!>CCGD4uqqt*fNoFUS~fIY$c!nkWFHTS%G&SC|jP zYkEWt5p67&cqn;>8mXzOB>?&V%8L#_C`all9CIE66_>2Rt;)CCvC^b2Z?UxJPlO-2&mb6w=0v zk`0FG^ranRX3-(@2kF5y=6IUI<}#2mWgHj?n(E=1Uv{36o#-9Oj#{Cu16O`{PR8^! zs;N!wmW#5R?IY?(#BeWxZNqcAjj&RG?Q<~zj*=t@atxwJ;t7?_IA=m%^C6hAb^&KN z6UBz*oLfz{MU(LkoWAmjD9sP>U~Vf9b&K;@@V(7cYxP~Uu!DCIv}|81lQL+9YqbjT zGAXxitzT)yFNjWZ<)Vlh5AvhGW`$%?0pWF1Lh`B!n;|2cV3NYKGUHfAs!fRd%Nz-9 zih2=)oY(>GrJ z0%Hwa9rYk1J5vkZ*(D5tMtZCZwjb*u7Koz>MZj%JkLbot&|?l(CiUj{uoEI>b^Zel=Cb9+}of6@hW%gt_T>f$!xZBFr!^pO`YNEqc(^ zLcVKb-g5Bw%FzFVxPlU*J`RIGp5*9|5Dr2ZnWa%%Wk+d?&k$cmm-0iWpvz=z*vNmb zE<=QwGHbA`PtOD}o1qjVFqtzQD<+=F?M@#MoYh z>a(^)VYgdX*);S#(MT&1%w02UMNN}$ey0G@ri4R+zVe;Iq^pr%dj%fq4elKQZH(BQ&aoC(3F;wTlo zkEP9%n^&=zp*fCfmz8Zu_lu>7nS>=^kiWn^{kgJ>?|>FV~Ba0MZdeB=HrXf&%+r1HlZ0dXd^(VS!j)*8{|(t7Mml}Yi@(YF_|kD6OXI5 zNcQUaV$6*EHr*%9_|4Rd$%#~FTBXEsy?UWpGW7((wQkU~-iCk*e8`}0ne##RmnQ;f(+v4B>*F*II+bPp{$%e}0ne3?OT3uyJJ=G>@Kh;%DYM(SClmN~C#cr8lceQEi zE2XNPg8ktGZ~TM$^P4Q!exjWmDqmXtK|$wL2edlTkI7^Dkgx!sWJkOHM*(TvH?ZKLlfcS)BsAp$B?a0 zUmtz%0j9g{!l5`gO#gVNqw>djHM!1aZ!+e7CXsLsH!1`t}5+Yq%W)S&5Y$1H7DomF|-`% zFO1~&BYP8a?wMlp4@3wi8fPU=YLsj`L#L(IjtV73wo)fq|uuf zi|6=#R@h+P*f7j9uStvazzOsrEJd16dN?#gdq=1b%t~j5USTY<4T%O)!w$1jCngp+ z>N!$}24GW#4l}iz7*DP-q#nm(FzY^v>iYH|Ka7u8YUh|u374o0rsL~^=La1%(kSFV zqZqS|jR3Diwwa>WXPc-U=goZ{+q4s?8O{n5Y(n2aK`9ScK{dL=vSP@7aGM|9qf2pB zE-K2j<4;GYxi@kCfIGotlW{h{p5abwa8c89ot;d~KC;Sm z4oxsYDeXARb`mjU&>*Ad(4l;ZkCj7*@=c*-Am6}pK-)m3LB;qDMu0h^RC@j-(r;$3#AJaOz##5fw!wzUAG-dLca~B6?Dc5;E2Y z5~|HC`(cnGX2I&P=&3_6^h88DkwD5LaoF?++eV2w)`>lzaqwye>bR+zOT7xE+07_H z-L|-_|HUS_kZG%T7nX~mVl1UX9LFTaC$0Vg4X(Tr0xc8vP>G&80>S`-cwIt?!E{^N z3q`xH2P=`~WPuqkxauy7z_H!gWg|_39^hv2FQYZ$XQ(_15NLC=Wu&&ReLF{Rt zkHj9?d5Me)E~J+0j1xj&}93YSR*4y=S719P~)@!E_q# zwFr552kVZ&0rY1xF+Rax=44~yd^VLK=&FOmTxF7wC$ySM$uYYHt>}CQ7d&z0^nwlx z{1=mRuUBM)H!Wy14b1_fK z$m>39MFd8!vRrQ|qzrGyJ!@)9&KBDJ8S5;)8@1*p8uq|3@B8Q^Ia@}zQ1KegW2I?t zw^Y_iDzn;3f}zHWz^HS``W#JgddlV-`qe?$uJ7xWktr0$tF$3w*&eZzRho#j#`g~# zP@`-!6kR$o>Fp&{=-eXB7RAiKO_A;+9?{Pbhb6%k_)wRVz#s`T<7(()V%f&`(wPc) zHUn#`(}-il;fP~j0w*|Tm@hDn;c(qB&YO7S`afeCBFL0HN=n+!h}hc%meJBSiKFe& z>^~4~ix@}pF4)+%M+B`6c{kCOV)D$U)W+qa%{*&MyBj&fS(u_lBO|-wVH{8^F`y?4DO{TYd|gZ7QqCv_{a|Y%xqyE=V^+XF$fr& zC)MT(73hV|Ar;DUlEi~i^_mKWG`|LoJxm4nBnDcLmT;F|5t6-?7@T;}ZNW~+wM*D4 z6xmcP(SvzJX+kbsIcaIRH{<6z~@mt20j za*``*WDw;Ky3Nc1aHg^XM6rPiZH9_}pPx4ZOVsS#ib5lNAmMoOp=%#C(MgQkj((u;OZ#wl}kSK}S3zL+~zq`dtTF z#Ne$wq41Hc6LdnVf`12ebWpGIm{PF1t&2~fJe9^|N@{f?43W&`Io*i_!t~B#sU8XC zg!Cc-gNpp5SJR40nEFkOwSyWXPP}pz4L8idqu&-1&IWcDu~+hNV`t*yV~QxdPmqdf z#x_DiB092Pf=l|!q)1E&l?2l!NyVxOsk~F_6=X!hG9fVkZ_6co7@!B%Vh7+?1lv@( z7LC>t_F_=d@>BL2I&oRCHr0tULJ9V0S0?r?%jgV9IRXto@I^=l+tU$pt7Q)_iY_XU zX0d5A>b`F^#Aa+GrZ7hy0}oSZeKw0e3O=z_Ifx$i=j<>RT@?GR%DwCem1)tVv)pOc z!uPq0P0alzvT9PLhq@M$MvxfHw%{Jrc{YM%JP@}ViQ(c4gb zm!IqFASB?H%P0^w*-!z&1g+B~rp+`i`s9w}B8YU2a8DhIK&7vFe9K?4TGn#5lJfAFTUyX!Hkcs zhCpt(WkTA8L3&6HN=}v-ybX;~Q?=NzOi6C3U;>k#3UEf9`UQ}ml-x6cpN%lFlP(St zCN!el{UgH*hv{v~L_ngG2F;cT(qtc~lHm0ggQrydxi3Yc%Y!9-9`l zm=lwD<7A7o{u=T2dq@VvVamki_s@nP=YYaj|9hh2q~HX5<3lVwX232$CRa0u4aU(I z#||c7xoEGBk)W$a9kwNoS63hAC?gStu|Y_%qh~0Ky3y3hj4>_PYMl<8qV5yaLsAVR zm2qA$g681Opl>6h=_&O|Eo5dl#gr=ilfE-fM4sf3h=VHADhz~Qq(TX$5T`Neg#IaX zu49^#)Z|HxPP%+-67ATVjR8yw?#SgbnwkKZfWuhFN(fV=7rQ}OQYu|MnzBcV1?#lX zQ;ADicq(y4>VZCNK3aicw}wwOlpZt-X^FB?0`sFXBVRe-m05H?5vYnZs~fbSSvq>BZUzu)^-LkcMHmvD^VGKCYXT z8;8l@namC0EVOKb&Q2Puv-w~idkK)1D z9E1Rs;)B4Q34HSw6In{|XE)fi;S)$ix4CgH_yvvgBI@OcvL zYey)~bJ}3^j&K|)N0UlpDmdY~(DRNol=J!$F<(O&&oyL7;vXKCz!vp>(tEvz1Pq`V zlFeH7zO?SNzPPm>>AO}M+Jl*=W6zlUV3-}mZKPrFp){@W+q~RNepYtgZeF7#tIWo80eo@d47xg2BK}6J)9(DS z(`U@mCIM7VJv%C^w21x3E4PSP&;Y=8;`Oxh=72eFj*9b*O;3VF@bks~XJ5aZc6L;r zDbi=k-hPY)FA1P2D2o?3QM2ia4UaCGMtz=zb*)`8W^rC+T|ZmU zV|QAe=()|+W~nlmYU6Z)4ZYLL(z>8$^?gw5yin2l5r$C@#R7hVXKHs4S$KL4eC_w7 zc;FAPFrJx&cwT_*UrLgXMT5|=RCS!uQY@MHCUhlM86L4lj(r#AOMKR0p2nd{?l4DV z$v}jwh4zFPRbE_1}swVPaBQ3?ST-0yNXdUY8F06Ka!dE>L>78O3 zU|~)bHiKBL;sy-&zKfIQK72H{6f6HRQOP9A`Pvq!T{yfJG$uS@f(aZ96Ff%vGl&;8 zU28Y&Aem_;QY9c}Dm99#uHqL4^`lIULczuIcs463O^pRp4#7l2Hr@@-FA6EE(0(r$ zs&l&sPUFnUORyL3Hw3*jGz2Se$1FOhw6i{xH_~>$GbNaLI1!=EthI`}lDVl4_URKZ48Mw|!??!}K}P|bgezf6 z686%5DOICoMk3^BX+lHrFgNmYtgxvq%9}{R<_Hvfz|fcz~(!=apC$v$2^Ti1XmIJr$gdmEL_u@GTM!X6J@-kNm5fO49tv~e3t9MOeA!QoX* z8Kdg)> z#l%zRTnfP+E`fkdpcIBrfG9&7kK~?bF#zR4I0vu2L~@SpIOsX+)GT7en~Choz= z7E)MIkY0QrHDq!IO}L!!!~33U`YzZl{VZXFuq1~2tm?%HB$12!pA{0$0^nh_Jk71{ z+C=g+QO^=Ia!e3T&C@J@w})j~U87bzgVf%_1W?mU8>TmiGbh5T0UHWvvDbh#6FPR& zJsFd(HA>p_iMNhTRUhUIUTTVxkf`0@g!ldg@}o4YJ#}@HH05SR$svr@NKmk2yo*bFu0-_Y z79a=~Gr94X^H|8xx^olJmdV+y7Q*Xv8u0+#ekkQ($uNYMi&6-7urh)#8QpRbvCKJ6WSm0KB9;#g;6|JyDled|qos3}iXy$rzxj=ud1H z;wk`mogl0x6USeEiXjRHwHv@0OLobe>1V{-lmo^IN{<}5a+VRat>JSesEB@GN@RMb z`&AG^m-!DdWA<{@P{s*EK{#j%uVTZX!`dvF;agCW10OFSlo0kn$H#8~gm#*MzG0hK z#0Twe@P_xx{T2p8LMk#mGU<0wF^ORbK8Ss?g;)$)S~z4c)8G4f^l@RQ2IR3Gj2fqp z5Cl>&+%ADDkD%ms^|He>4b-dX1PYYRP#{jeOe@KFkpV0k*fcxb;0pI1BG?JI5iDZ zX-gp~t>rTZa^lKZ^1y+b&?^|qIQnnmhq8tX>7&7P3n}NhZpyL}55qpt_${Elrl3o~?V>Qbl&*0g!*OP+Jnmj?u}mXv55 z+^>p;!VrrI!@~wr>ZAw;h{qC5Z=);+t`vvGboQ(Pl{c`rTjWM%BF0rqjTP(=S|KDe zFHG!e)e7-m8}zPx&7j7A_iu<7qeXluz1uT#RiI^hXnY=M88y*Ta+O><6A6PcHbFE6 z&}_EApdb%{Y(aFLo>sZ}Ll2JcJ26gduc4SgJrO%erpI~>3_8WLfqfnN>@ z#J(4tG!kf`6jO@cl(CQN!wyDudPWN>(F_JGlwUq#dd+~dkfNaqMB<;J2o50|M$z1F z1aojq47Fm2Ft36V#87O~!!&C8kazkIq!AcJR_0&}PzEU*P&dAFvuq!csS&0i2N$*nP3f zL0LACvRKm0i6?pXo0aDDUJC(?@?N-tBLdSqFc{vi0m-hHm|{qZ$Cgl5Y1T7<#V|ysu^77? zC|tfG$e4V03y*hx7a_)PnoDg8z|$fxSOrQ9$py~~a)9h@5g=Kh3Oz9+jVDwdFUKp0 z!*vK;8kQ?ll#!wYoLI&^I#7~Hj#ya0*XfNAhXDYe3NiS0NJK&h+{brHmBbH zi*n1r0JtP7x-6K069up%SR_Vd(o|S7NJtq0gJ*iu9y0tu?iR|C3wM_lyTOq=uCe=DqL4 zp7E^hG`+jwLXCwz49w}HDQELPp99cAjRr{!u5|)pUAJ<=>f!kq#M`Zd`WAq$R#Odd z^?2p?!r;`uE63CIa67>$$K#mz;P6DT$7!ID(Y@)x5jf5q4}ElWJP;}vCa_r$Oz4$q(^_0Rp10jDCzWq5RJ-{(ZTa*L>eX>{0NMvGwA+`HICTK~AXJj89 z&ZyxlI|mCgJv#}8esBz9nK#C~5aQyPgh`Z+3Nym&AIaJSN5n4CkP``W%AB`=Vu9D> zv!y6>$?gobyH1sAPyx>dB;MOj67fVb4ra4#FH5i{C_dz6By7MXV)57zP56r9K*L1k zt_2K>!Yq}NXPlx^I{_iWu}YAfjSaw2HDVMqd%?DYRRjdJuOJYt;0yb0LKTv~7()!5 zF=u34RW8C7)c_6v7YfZ9fJigbBH}nA3J0eutFsgd-Rua5gzBy7B3e8NST-Z9+sxYd z5gO?iKB8qwz-&aX^vqNne8_3cC@O#m-N?)U$zH9`GB(cx7Klo)BTJbU+|+c60F*P; z(h)c%w2V)vSfZvp&_L(+t9@gNkk74N(M*)ZjsT+|3iVdaQtwOc734rA{-JkD9!xOt zS3kZ7AyN~LFjJ+e3sw@2Rwt@1Kv`LpUpExMTAZE=2EtL+F3>p0-d;BFU2|gM@a2jV zkCE=`l++5l?>x%s0%(n-vWhRZR)9m2bpl@_7#++&W~?Ku5X5x+(sx_+;E?Cd95h+# zdD#Ls>2FR4Ls&2&_)Sc36F(+88z=>&T|#r}kt90*K6FlYGP zrSRaj2n^L%R#isu$ffkw-VbCaXhQwztB=(l|2mGFbOjwrq$Lps$w#?=SXTNu9!5NK zUgBEEuV6vr&)8v9rH~>Hk7*8HHjnW@b0r~@EszS?VQhO z0=61n^#_^*TyIPi;Lug8ZNiu7hzF}pqcH?}Pm_Q@=9<`;BNeI7FpRih8a(oTRFm%Y znM8;zt<7$|x~cMPL}wmKbr^Y}a}*Coocxfu96V+|>j^E-;%b~AD0p2b&hpwwlrtRM z*=e`gTA%Hc?VsJ!RHRC4@xxbaXVvSb{tnwRnX^Rjb+djIFnYa`cTAGlpu=;c)4*|FJ!vy-#ava_;BWRI2}41w@t(1tE%S^$+`wI61e_`?a? za2O}nuk8Co_&INZKt5PB4F(Cy$mEJG+Jn$}?*!Bi2l zBlDOCXbqC8>G&#cO$f!2lPCsya*bv%5l&0cpwLx40#e8YNqi$s;V_duNRB;e7ctZD zYY?Zl`4I}lk-J34NDP!ElG-q9*})1yr|*NT$pjUGJe-IyVZsQ@4nTLuRXO~_At)o* zm`0)5%J@hOoIn`ggQT*GoC6_bI((NUCpZ$8_VmmNWV0TOOqlQrgOX5$R@1|*S7c^A ziT&Nc!rErvAWrlh=YjiQ@9HB*8f0y`1_KW|5kNLZo_``CVUp5HF?>xm72NQ3_>-2U z)gv4wroqvS9z=0Ql13a2{r&fCs6$7Ya_n2k)k4m^uW$1ThQa&DbwV!=K^i93p%Txv z;rFA_?E8=hqY8|dIl(|kvTu|;y!UTl5OAaUJK$0bboUJmf-r;5PMpg$U;+IT773oo zqz1cyAAI@p6*LpL0nyaRftFY>9Ich){i7_IFOiwfRqxhGP_I*so0{3^i03I1fSZ*2K5Xma?Yc8E{VR4C(&; zT`fh>X7&#|Xf3_zd;Z;kq(0t-B775bTtSE=Ocn2D*bu5jGs0D_M?AZ*{G!}$u=j5L zLXFhu=$t7}u}0Q%Eyd&>en<&JQ$T%B>LWv_J5)+iXqZxNGHKRZ&5i{T%Rj;s4hN2+ zNJ-8jAO3`-j=W-!A|~BgN_I$Lmh9kGCOR01$R zF=QpR=r9tOP~dp$SZpLlu)3VqCB zW0s%1&;`|6kb(>3*$-J+6ZjJcPk65fDI!n?qedEth6($8cr2A82~24LM-a>Q70{Z9 zLliup>k5NsD{PxVJ@fF)%lY~?z+ z5b>aYt}AGBM9O1R#CrC?_*7;FAFZFS;5r>~nO!aWtVIUiUl%`$5tLCM@ zFS9-KmHxiU_RbIR_jPui{4{^xWV`3j@b{~-Tjd`<=;Tw*KK1xxk2`(MPDk#%X74@s z+;7bxC!cV{Nyn}^;J`zUKk4WrPd(z8)7NY``j{imIPvsDk3RN{6OTAGe(oRdKK?v> ze~tg)u%k~s?f8>VTC>;gd+xdC_-Oym5hoqB=Jex^UUSAt$DfI>AHC+ZBaT06&F$Bm zc=DM?uQ~hVGqQs)zC0y68|FD4qs`;8(_x;SF!J0PuX|^E;@^IFcL=_9LUu%U627wr z!>a@F$?^E@XngZje143M+koGX!Ea||C*t=*@!PR_e+0fW`~Bg!cVG7VYqH@HYeq*L z27aevtb9CVJqc3omF+J1_gt3uP*Q4n6plX~);JokXW;kavooDKKRFFL91Lk ze*EhiSn*_h8xQ3;wjB7<#AAz7@L`^nd6B&)`z(<5sl3XbmAyLqbN1})4|$#aHhWd} z$Gpkgyvw)9uaa+>ugJH`ubOY2UoBsmUp-%yUn5_gUo+n(zgB+j{5tuz`E~Q{^6TZ> z=WFt{`StU4`3>?N@*Czm<~!v(=QqlCL5_H7zFWR~zDK@izE{3?zCOQkzL4J}-zVQU z-!H#uzJGqR{O0*B@>}M&$`8m7%n!;pm& zKQzBve)s$y`C<7z^TYFd<@e6-lixSLUw;4m0r>;-Bk~93N9IT6N9V`n$L7c7$LA;H z56(}_ACjMxpPZkPKQuozKP^8!KO;XgKP!J&es=!w{1N#%`6Kg3<&VxElf5f@clN&Q z{n>l6_hz5T-kd)+e_a0fd@+AQ{>1!A`IGafGVm3-TA{FUnt>za)QYes2D<{N?#8@>k}s%3qzoCVy@Iy8QL|8}jq=H|B53-<-cC zdwTxX{B8N$^LOO$%-@y2JAY69-u!*}`|}UvAIv|Le>gus|49DP{A2mY^H1cT%s-WX zI{!@m+5B_)1^MUmFXUg$zm$JD|4RPV{A>Bw^Kaze%rDHpm47>1%s!I6AbVQ&)a-@X zle52Pmt_CQzmtDA`*ilW?33A(^6%x}&wr5rF#l2h-}8Uu|IGiD|2zLr{@?u4 z{IdM={E8wg@}elpqAKd5DcYhdwkWPrY+0-*wkob#Y+YQfSXo@XSXEr3SY2GR*rvEv zaqZ$d#kR$Di|vZ*726kUinYb{i*>~fiXDm@7CRO@6+0Ic9ufyF_^hT_)6ZHn6#w<~U6+@ZK* zad2@+ai`+W#a)WK7KavhEAC$0qd2U%CkoTOihCFLDeha`ueg8lfZ~D05ygXwBa5So zql;sTV~gX8Pc@Yw@?@lH%{hKZ<`A z|0@1n{HOSDacOZ`ad~kCN>*MLWm#5bT{dM~cI6i3Rmv^P73EgtRm-i*tCcIutCy?F zYm}?YYnI!T*D9}FUZ>o)yl%N&dA)LbT#tTjdHw9-a$R|Ya)3zXmiv`AE%z^PR^Gh4MS08eR^#|y}U4<#FZl=KB0VK`K0p6`4L_m%H2KTv+K{80Jf^8E564l}?eaV2 zcgydU-!Feq{;>Q}`Q!2@+<6AH|1~3-<7{F|4{z1{8Rbo z@-O9I%fFSElz%V(QU0_1SNZSqKjnYROUuj3%gZaOtjepRDyyohtEOtJuG*ryO0{LR zqS~suYPEHBwQ6N`^=eghjcRpu&1#$KTGh3y>r~rT*R8gzu2*eet*O>l*RR%9H>h@~ zZdmPD?Nset-Kg57+O^uP+P&JN+OyiL+Phj`-MCt)Zc^=2?OW|v-L%@jx>K4^4 zt6NnER0n1+t`4d;RJX2fQ{A?@U3L5F4%Hp2gR>V^hg5f}?p)oax@&c4b+_v7)jg`i zs(V(4SNE#!UEQa;Z*{-w{?!Aj2UbT^52}u=j;fBXj;W5Vj;oHZPRP!!9$cMRJ)}CR zI=MQfdT4cOby{_Lbw+h&byoGT>g?*_)g!8Nsz+9jsvccErh07kxa#rMV)caTiPe*; zCs$9Yo?1PvdV2MY>Y3HEs*TmNtLIeDt)5pszj{IS!ssGZFRjk4URJ%ldPVih z>Q&XNtJhSotzK8XzIsD-UiHT6P1T#Lw^VPf-d4T6dPnun>Rr{ltM^pzt=?C?zxqJ+ z!RkZRhpY3ek5nJ6K309a`b71~>QmLHtIt%Qtv**>P<_7oLiNS!OVyXFuT)>HzE*v` z`bPE5>cZ+<)wiqfRNt+>SADntdtza`u(% zH)zVfnSCw$diHiSWZ%v%%)W)z=67i5j>q!CL(ssSl)X}|%jsxj&dMH^JshpfBeF+k z=U~y{QQ2d%$7a9JK2-g!x}^Gh^^fYG)xWaKvdgP~SO2O0TV0x6k!RIq)#cR{Ia-%G zuZy~@tGce6x~;o~#e)!Wx=>b3Rt>vi=F>K*DE);rca)jMZD%YI(psNSXCwcf4Xz22kV zv)-%TyIx=4xL&AlQty+!BYS7PZ@piA(|Z5)X}0ukTRbu|Bvyq`p&q=lU-7UF$>ZyVZBE?@=FC-!uDgeRzGZ`rh?@ zvR`GtuJ2plufBi%fck;;5%q)WBkQB;qw8bpW9#GUPOd)sUKTEu6}&ISU;hDV*RB0$@Np}r`Aua zpI$$serElwdSm_U`Z@J;>*v+auU}BVuzpef;`$}^OY3v%m(?$?Us1oZepUVI`Ze`y z>(|w<&mM}#9Sv{&hWfnvjrE)AH`i~e-&((|etZ3n`knQ=>UY=gsoz__uYQ01f%=2> zhw2a4=hq*pKU#mR{&@X~`jhpi>QC37sXtqPuD+oDeEo&`i}jc4FV|nGzgmB-{(Ak5 z`kVEI^|$J8*WanXTYsVMb&ssCGFT3=RQUSH8!d%@)m7nk}0Z%~s7-o2{FxH7lE|H>;X!G^?9yHrq7UYOdW}r`fi-ZnIr;y=MDn zO|!PSezUH*L9;`1!)C{3r)KBoM$InGuFY=E?#&*}p3Pp(-p%^v#?3-=lV+c0-)6t& zrp^A%&6=Axw`gwJ+^RXCIj}jX+0fj&xlME1=622Pn>#djYz}S?Y3|hAxw%Vo*XGdX zZq41Bdo+hN_iPSt?$zA8xleQ7=6=onn+G%xY>sFi)EwCy)g0X%(;V9z*BsxR&^)*~ zv3W>yQgd>1O7qa>)aJD2^yZA_%;v1-Va?gi!<$Dm=QNLO9@RX$c}(-z=5fvAo5kh{ z%@dm^HBWAy(mb_!TJ!Yg8O<}BXEhs}XE)Djp4&XHd4BVP=7r6Rnin@OX#}+I+0}c=L(olg+1^PdA@wKHGe*xuE%c^M&S%&6k=lH(zPK z+I+3~dh?Cuo6UvIx0-J^-)X+ve6RU_^MmGx&5xQNH$Q1EYJS@MtoeEKi{_WjubN*s z7dO9Ye%t)6`F-<;=8w&vnm;#xY5v;$t+}N6d-IRxpUuCTe>eYW{@YyIT-IFPT+wE2 z-WF}yR&CujZQFM37VTBqE!!3CR_#^Wt=p@$E8DBLtJ-U{tJ`a~+qBneuiaj!-L}1M zyIp&|cKdcsySBZ4yRN-KyF+`!cE@(7cIWm+?Jn)E?QZSv?H=u(?OyHP?fUk{?LvE# zcAs|NcE9$f?f&h}+MBnxXm8oxsy(1Rusx{V(B8VeO?%t+cJ1xkJG6If4{i@>@6_J8 zy-R!7_R#ij?cLjZw1>6#Y!7em)!w_kPkZ0?e(n9+2ec1tk7ys%9@!q%9^D?(9@`$* z9^anOKDa%xeMoy!dvbe9`_T5(_O$l&_Kfz-_N?|{?b+?a+eftLw2y2b)jqm?O#9gO zaqZ*V#r6s96Wb@XPi~*mKDB*X`}Fo1?K9hFwHw=Kx6f&x+di*-e*1#5I zU)rAAzN~$D`-=9J?W@{Xx36hm+rF-Sefx&?y!MUlo7y+GZ)xA!zO8+G`;PXV?Yr7{ zx9@4++rF=TfBS* zm)bA4UunPEey#m_`;GRS?S<{P+HbetX}{Zkul;`egZ78*kJ=x%KWQ&&f7<@6{dxO~ z_LuFi+F!R9x4&tB+y1Woefx*@kL{n@KevBr|Jweoy`=qn`;YdY?Z4W8xBqGX+g{pU z)?VIT(PdrU6(-<{AsxI3|XNOw|qa(7Di(C*akwC?oojPA_ttnOjm+19k^zIqmGrMPX8@p$B&*`4qJ+FIy_k!+) z-HW;xcQ5H)+MV0Itb2L)itd%&tGZWrujyXfy{>zG_lEAg?v34>x;J-k>E7DCt$Ta- zj_#e^ySjIG@9Ey#y{~(J_kr$%-G{mlcjtE>=|0+htowNPiSCo#r@BvfpXol^eXhHp z`+WC>?u*@*x-WNM>Au>1t^0cSjqaP>h26KhZ+GA6zT17T`+oO>?uXrvx*vBx=`QMi z+WoBidH0L%m))r+WoD&uERR#oNWMIRZTj}+F40!S&~;8YgdldUBIwcBzw%Du+Hfh*#2mLayZAoz<E(7D7+Lt6+RulI($v|+VFMZGvVvQH-v8t-xR(%d`tM&@NMDS z!*_)54Br*LJA6<0-tc|l`@;`}9}GVfemML{_|foV;m5;Igr5vCq>#fRbfFJJSca2u z8h$GLboiO@v*G8$&xcO z5`IYV*^lVgjz1=p@+b6T(kL)0>x7}x{e(f6Jx&Cw{gkez{EY4@{G2YR{6d4&UJ=0c zc<*z%1oGP6>wBNqRggCXUA?IbA#d;fiomXS_TJrlviGaH9P-7zFYUdrD-U;u9C*fDad*O@WOX2b4n zuCKaQ?^dh*m(QP_jSqgY+>HCLju#hWd$Bm}4j(_e-kfhQAH1(u_aE!^yJsprczvtl zFOAQhjR$Xx7prN!_sh5U-n_l{#m(Vc7n}9f_VV7_oBLll+wQ-!d3rJ4e|Nk+eB!R| zzkRm3|BC*5`+8$GSpYHhYPv!h(JS=ek%8vW)rLa?R-|V=5(72yB?(S!` zzIS) zZohIpMQDQ3Pg+n%*@=D={iY@F=|9nLn)~&nb*W8S>{6k9HO=tN@Xhee@Xhee@Xhee z@Xheej4Q)G!#~46!#~46!+!z)1^gHAU%-C>e_iI;Exv96{{{RP@L#}p0pA6D7w}!c zcLCoHz8!o!_;&E^;M>8sgKr1l4!#}Z?%?0Szk`1V{|^2g{CoQE>A$D{o_>4!?OVS& zc4HW?uC^bYmwI#9O1ICB8*K*@emQMFy4hl&Ap%J0kKEK8xgM{ zMzI)qMfud5}WogmDAQqj;raxB)f-)P=5?_O<>R&N z^|U%~Ydm^uYil&gM45SSN!6KgZ#~u9h;r=Ph(!6WH-}_XnzK!A9%9pksM`;b8H`?m zgH2T)ka$>32en*`=jFlfPm^&N`~B-@<5V83&u>@zXJx#)Ih;1*#XRg`IDL3=d%anY z=R0M6>V@(=&SCJ19R_P^80dTOc!m??JVDMAk83z-Gn9uD!qU7F(}~BQ(0?+&ap@g5F0GH)d;Z3yEkT;>8uM~Gd9=B_*&Uy*Z*<`5 z?QT1t6Lvp)urXt&6Wb&AO<8HOxrupbN=;M5P}-cxd^8~?OjEMjweMoKFfpHao8%L3 z!+hfHmp!jB=5_`@E5*y9g-{Gk5zT-up`k00#ugFSvwmlbH&Q=o(A+ZcoX*n`oaPB17^{iP2Zs}Z#LD@7jCJ?UG%uE-fk=Q zxVIkn*4sVlIsEOORENLalj`uddr}?#tfzhn|9Nq0IlNTP1Xw#agO~j%!etA)C!WPxeWl`Iz(DNpj zs7>0Uwpmf#E^ATEe^G5R7uEf*6s6rY)`R2JG>mQjMB8_w?K{z?Dbdzm)W&xho2FE6ni6gDh&D}$Hn~KbrbL^j zMB7Y=wwXwSjkIW!TNM5d?^K7sG%C%i4&N1g9S-U_d>xpo4&N1gSMc?UOM}BqQTVRm zyJp-DnDw4(bzGPL%OGj8h%GJB(AE@jIMT9sPU9sSf|b_zV2K z1N9#M-hpYTthdVeEBq_t_YPG%#_t`fI^(Y=;re2u>}OmTx$h6}D&9=q9$T$R?kHK zl}|klQ*6saVSv>=MV>pR8kgi>*=An-TV0dWdBrgF?<^!ur0v=Ho$AOjXEV5^jGM}s z>x?_ixY6ubP~lD+pT^zzO3pUzsv|d9O2%E~yYQyzO<_%24*Jge&bU)qIrQWH5?NnD z@5$RSCT~y4(Uyg%r+=E|!M!Ocr$6}N-ZO4oL9gGRwE2@aM-oqcW z8!@{Pvl}tH5wjaHyAiV+F}D#j8Zny@vl%g)5wjUFn-Q}aF`E&y88Mp?vl%g)5wjUF zn-Q}aF`E&y88Mp?vl%g)5wjUFn-Q}aF`E&y88Mp?vl%g)5wjUFn-Q}aF`E&y88Mp? zvl%g)5wjUFn-Q}aF`E&y86DW9=)gwQb}2frQQh_=I!sWV`K!$?)s@4Ax-y)ndV4S0 z>{nem&8VwHdk1uS-}a^6w{@j@}xyaQg!@+uvTwoXzIr@~4 zhujxGqV~w+`BEJ@9EhuqJoYn^yARvTQg(Ns&mP^J3HfvR`eD^gWXg}O%ZKHX%8}aN zoNcbAaP!f&6|Q$CLY~1Q4X7GeKo;Gq9&8d+_fQvheR;tU2EzyH$09GC`Q-taVz4Vv z-BUW)HOmuuiUzxE!8W{iX01B>0iK4%(xB=}(!bP@Rv|JCY;EI6>-uPZx@+QxwGSHP z?r!#Y{;+(!xxOivH=EIjl_02NLt+D; ziIN9>=NE2$(jT91pYC8I;je+>_R249oO8Bu7WHByyUGMBSxAf9kKIe+qC`bV?(XV3 zPrdZAU64Muew2&_9iv>8y1nuYWB{PdObLB<59`GvH9IdKGo-oVAsxJyg}!={d(hq9 zen$zFMegl4R&&$hSMZnCh5i|8G$h^ciI z6A_NG$DI-ds<)jOGXPEkcTV8W(&4V1P_@>tsM{w;5HWfBM4RY}OdJ6vaP2$^cCnpeY+@lKz@rMccSi!Q&M?UcuuPdpt$pO+TWiM?3w;#Ujpy zG#Bz*Sj>fPF7$I@mh`W_h|o8{?ddAeDi zZkDH;<>_X5x>=si^3+9ko7!i8UKjYY1O}YI(3A6poSaXH+8-vvbgFxG2+VD>u6VhP zq6*A)L6D0JGqpFS_|RGJD&ZprER)w>RlpWYRBtzeC>G;56_;LAH232uO6J~&%x_AoAaq0Y#KRyo$+iK&+8t~#tM8L$u}tQ^Ur&H z_~qwiOMy8|i=}Qftapc7=Z)~zX=+-m58m+j&l`F0h6eNCTz?MU^jDWJdOUN`dao#!Kec>TQWaRP&+VR7)TKfYkx_z;B_=K6dWv1SHF4x14UMPR*h4LrQ%Lng!gkNdo z`2D*DcKnsQ;^E7W+uOsjSPySqpO4pPE^nWgn;x>Ad&~~MMeTeQAsx4?GB?}H?e!xr z$F$1PwwSvvo?XH9Q$*1{jt{=CX zGH%E1m{vKG&1;3Q>#vnQ!%dg@Q-Aa#yHEeW7LT|%$q#wftPg-_H})wSw&p~fpU!0f!B24H68d-XMYqsjx{=Po(>$R1MlfL*628! z?Ccd%agrk1thQ*INYS>TQ*quT+WebLRx?JTw0E+R3OHWzaa-byG!<_;(KhZ>jBAND zuOSM*VRi7{)2mU|{Uek2+R8ilsFCpAJW1iBxpeR)H~gfLhnM5C?e&ckjI(m^wCzdn zSi4Xuo&i|l8UW#^6(>OmpM0W|WWIObi=qhd?*edB07i;|k({`%q=?^q=W7<6+vPbT zCrnHLwF#g$0qAnEML=PbPt;*(GH|3i;jQIU9m{L^bhh6XPMQo93Sey$CfX)Uv}HM> zt$)$hzi5+Nl-CsedesLo|t|Dvs5or`#)hgHA-*4g$- zKRalDpR~WXyT8^mz+M5^D*%TD;III^6@a$_U{(OU3V>GukSYLD1%R*s5EcN)0svV6 z@CpE40eC9_X9eJ_0BjY2tpdfW-o!R{-P+z+nM6ES$#_ z&SMJaErs)z0x(zr@(RFQ0hlWQZw2730Gt(ovjT8in4bbLS^!4n1eh201wd)xe5!De zPyjRwKx6@kEC7iGAh7`S6@b11uvY-~3fF*=uO+1d5L^I)3&3Xq_$-`!4gfm?Ir-pA z8>s;N7J%OZkXrz93&3mvm@NRW1wga_hzbX?9snAPfyUUKdLMQ^K+*Lzr>PwW4ISxd zXZd5|M3!?&WpT~ED0_4m-GSzGpgA3=PY3GLIsH_7htnM>Q0IdHJ$E?W*`TW4?1E^U zM^T6G9hYl5F4uHE$WZp`FuwCais}yIJHq#l@V)aoP<9GGuWQxe2PV@2aCF|cdf)7Y zvRTFp@X>kQ>pA>D$2!l8o-hSlzR=wG0 zQTW@wMWwRM-(>DdWE{M1HMQr-dXS%Ja>QhLMC#WBRq~>X0-1K-DcWm`R+MRQP@y_%cJQEj<1b484kA=% z{O(_M#_!-oc*op7v>7V@~a*Cp4Y)_MvSBB zJNV6VAXl+*R6p?dEdbRSw@>R-XaCqa$Y^@ICp!n#+d30%{fK&BB|8VzJ)gN$dT)8NlY)_(z@q8k zS<&ef>hj^T4vU-kqKMC(#e@$|+*M56NlaWyNnFxMs6BBvHgT9M`+*v~3=)UK>F#jY z!%4)RiP$r7c_lIIG^>F-mWkmd$MiZXY0F2H<;{;HGQYJ+&Xsi3Vsnz5)9AHW_|-=Hnbshw80A(KcSuwv9wvzoL#lh}j}Bo2ZuyF`MqaTaM1T zjbd}l*?F0&J@4739Y;aLsJcgQvr3Gr_FgU>+m@J4&#kx4v1=rD)pNdgj2DUNM3J9) z?HtqTIr2EBQyqRYR)PENzz90FQRi)>@iKlegN`Vpb1tR#HbdQr1y^g#=+}Zv_*5G?zhKH^>&APZhiMeLp^SRSXUR-887RfSYPvm-dJZnVNMU4 zNDiRiF}PiiyXrX->gPUC6%P(Q3w?<;*juS4_&wnNUP_HEup z`JT%l-OLwMws$V9b!j`&*^Fu)+Ike9nKrYn`m8V8gWlndp8NaWQM!It;&7yM)UW>R z_B!lY=iE*6YP;BBmwJb-`riGM(@76**R#)ij$(U9|7wr?nK`Cbwu?hoWtri8lE~n_Q`aWmUql3N|Fruet9EMkE7Hz3EYxd0@qqdM|)rJxeNRKlZ57*h$8Dp-(iZ6P1y1pAU> z=Q;W!ORQw0m29yR_ExgON_JYk&IR$#-y<*CXr1Sgth9plRj|HFxLOHUE9qh7ys&~z zSHj3j+E~4R1Sz9G!20TRLeG)Mc3XAEPdZb<#4G7!B~7i~r)tmq6INEjze;#m2?MLI za45S%kG{&GI{dk^Pzfh1R~IT_Wwl>Wd*+49#g(wWg2`9H`wBK+!A>h!Y6at~V3ifD zvVs{`(trwfTuBEi>8Nf6cpZSLR}MKU>8l=1no62mNpC9YZsoA1lJ-_#RS{^x`XSw} zPMh_d`6KPEq}`SDw~~HWu;!>-*c8x!P+Zl8u}>$?-@V8d8wSqRL*QFXDXGmo66ZrN_Zw33W zoJmy2?Yg$t{EY7Gw|ytt{DEkj2VEVrKTKfG>-o2c-amOgUzTNEPsFWD+ktV3y_xVc z6aHl)9$I35Cj82TKbi0&6aHhue@*zUiFA6xPfhrz$?}d%?9YT>n(#*x_H)8cPILM^ z%Q?$~9h_!Z1N%E+cPH#^;WBXYx)qwx;sl|AGp&c`H#*aw%6|KMIDOFQ(L0|@N&i#c zs6srYpr5h&iNIV6a~s{sJ7QJcaoy z-E8!e#?fEatBkbK}Jd2e%Y%N@AI2ycg5AsZky^NV>6#Rfp)m58(E=l^vk?ywf*@2 E0D3>oNB{r; literal 0 HcmV?d00001 diff --git a/Minecraft.Client/Assets/RmlUiAssets/fonts/chars.txt b/Minecraft.Client/Assets/RmlUiAssets/fonts/chars.txt new file mode 100644 index 0000000000000000000000000000000000000000..f7ba12aeaa7304129e96010066b47fccc824f92a GIT binary patch literal 564 zcmV~$2Y`rW007YU9~ouDkvF8j(X(V)o4%E~BZ6iT7u z$X;bMjO_RB-HpUZBdrIdlU@cHJ?J5sWR^u%56dRISUKeMh+G~OC$~KE$|t`9f_MoY zQ&1s^3M-vRZ&$n)zwf_Ew$D0jJoQ1R(%aL z)W~xhYoe(n$(ngyb1!J2r5Cl*+DqDKtDW{b=;&pgyrQ#Lb@7_lb=6IGJ@nK|Z+-OD zPk#dp^oBv+G}sVB4Kv&bBaJfJ7-Nky-do-_!9rki1=S!SE#J@1?A z1M|%Hp^tp*6ALW#sn0C3*yom5YMJG}u)<2KthUCN)>>!14L179CYx=s)z`kU&9}a@ z-3~kL^1UDIw#QyS`pG^&`^A0-9Q3Q-{O*v$QXO&BAC5Whgp*D=?ToX|Iq!l${pF%d zF1zAy|M=H`uDa&B8~%6GEw|lq*F8y5a?~zr9}SF#L_?#LXnRa*Ol-&z;zI6_H{=WX ULkRI9AtZ(}p<<{MmV|`(`?sTuLjV8( literal 0 HcmV?d00001 diff --git a/Minecraft.Client/Assets/RmlUiAssets/images/AdventureIcon.png b/Minecraft.Client/Assets/RmlUiAssets/images/AdventureIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..60d306fe23be3e5394678e3162c22ec374e3ae42 GIT binary patch literal 751 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 literal 0 HcmV?d00001 diff --git a/Minecraft.Client/Assets/RmlUiAssets/images/Button_Square.png b/Minecraft.Client/Assets/RmlUiAssets/images/Button_Square.png new file mode 100644 index 0000000000000000000000000000000000000000..08433912b2196c67356ab5756b84883b04978531 GIT binary patch literal 16088 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 literal 0 HcmV?d00001 diff --git a/Minecraft.Client/Assets/RmlUiAssets/images/Change_Skin_Icon.png b/Minecraft.Client/Assets/RmlUiAssets/images/Change_Skin_Icon.png new file mode 100644 index 0000000000000000000000000000000000000000..62d5012fa03080363e8f452d2cfd73a37705de18 GIT binary patch literal 29706 zcmeHQdu-E27&jWP(v_ki0i!(J0ArvgwnLMpUR;(YjCLceD%mIuAsqXXcya91c1Q{p zLp(bA2P-OQLfa&yX@4Lz5Cy~^d!bP%tAvq}o*)TC{3mNLUpYszlq|>XK3342wA= zkinCggskXnhO=3WSzI=6Q#R5fG2)!fXSPT-2J1<`sv)w`w zPTDz#qTQ6!Wp~mn&9E*PIq+x0?G<@7F~T+kULI%;*En0uFp?}qrPFCgdX7WYq7+kA zRYlQG%IUOY3cKE{7<|UA=+i77IVJ(53!0QPBvm0zrhHgU8JrEvo9jp-S>UYb1KPrR zqcVJwVjQ&DF^f(J=B~;R0Go0lqR5gE3uf<%-$qfzlL~2)k3N&=ZC94FEZ(L zIp&bhFP0QhP3yA;J%9qdfjAo;x=K6E*lACQ_OLEDTgfcVQ3X{|iZtJa%H_;cVRI3A zga0QwQD7sgmf&#@r34>ER8ooB$b#NjzbdO5Zq5|PYC9NY>lEGK6#>-+I2-PyLy|<6 zuH+fFn|9lg+wHPP_)3r6QzmnG7&0+ts{>*j2NIjjhGD0f?WV1?wKPsb2u zdgtNJQRr&KNb?%ao?lXTLGJ7k*I zNo?FmGaZZM4blv_I?h73j~G=lD#(k73^x~uqLL9yh0Wol;wep4P+V1-D;1Jg!~)I0 zFjKeac97}a-uv4$oO>AOORQ_`Px)kxYx%5tuj>t9fE1CNnVvbf`s**8zYs*6tPA); zndQHK|5d81eoWU(XF9k3_2P-i!&a0Y30>Z?bHvFU!(Tt-Ld7RqQU2_mvf-b>`z089 z1bhj=8qZb%8UQo^XaJLE>+Kdm1Aqnq4PebJw~qg>p#i)UhdlwXB?5MYz=oKIupfx& zFW>*O{@VxAp}tIeQ&VZGrT!q^h*a^De1Y73f$KiXeRsuo2Jb@Z+_Hb;`U9=m3FEW% z$3~!HysxR|#JJI?CY}?P{otQGb(1Wi$H>WqCv#; zo^{#I;hs8Y+xl31?bhjAJ(~_f6|@1y@C5k6>gr=(_D$Hdd9Ju`Lw1EQ^~h1LTymKi z9v1NLDKe$*YrbX2yLySK0_iq?ivc8{7cd5J5{L{C4k1lIZhQ;?!L?<5>wgSeXr--f z^-idO4!{mFrJw`QfjNT%J9yMO*v`D&Lyv>=2;evdIE?}hP&9@y1Q=i+o?U zvCAtTpN5ZtIW-Xn*yhvMxn8=o>}oAOE#^S$#+We0y{6rDysO*Wfg9b;m!IAK&gSz% z$<@6~yLn6uumRnG-GDPdpnxC>nE|rqV}dA__W`^C%m#QCKsrodU~&gDR+!DhN(U_8 zz|t11*TGsQtn>~ i4ZscV;s)}cBaWOLT6S*f^%nF0Yu3$M7}!&@V%^{Qn55AF literal 0 HcmV?d00001 diff --git a/Minecraft.Client/Assets/RmlUiAssets/images/CreateWorldIcon.png b/Minecraft.Client/Assets/RmlUiAssets/images/CreateWorldIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..7debd644e255754d20fe6b77ad9b0fe90d570623 GIT binary patch literal 834 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

I0Ps?8u4@oCXDVt#@N+`W;g;0Q#RC$9u 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

S-0KpniHX*>^?PViBc1B?2~K4gWK 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�m+A83bn1AMRHWDp1KA{?}} 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%0D!vPD1X6DOFjuDh{8BIcQ6MTOQ!AiTq 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

5!6c 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�B@^v6(Q*PxWx>Rn?hzz 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%0D1;Uh_JoIc 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 Uvh%e0fA>(3Rgo!wWfJ^<0eEBni~s-t literal 0 HcmV?d00001 diff --git a/Minecraft.Client/Assets/RmlUiAssets/images/SaveChest.png b/Minecraft.Client/Assets/RmlUiAssets/images/SaveChest.png new file mode 100644 index 0000000000000000000000000000000000000000..04e46ee96dca817dbf8ab82f0250e01cb165003c GIT binary patch literal 2694 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 literal 0 HcmV?d00001 diff --git a/Minecraft.Client/Assets/RmlUiAssets/images/Slider_Handle.png b/Minecraft.Client/Assets/RmlUiAssets/images/Slider_Handle.png new file mode 100644 index 0000000000000000000000000000000000000000..acfd1fe864b737d2a5f151a53e75a600cb8f146b GIT binary patch literal 78267 zcmeHQeQXow89zuF1qM~ew6TdPCwCIvRDYbEKyr$mn3zE{Orq2wp`D=T^Vzw@zB9fv z`6vps?4N4gvVXdgZX^6tioyg;?H_AL8|WC>){J!OXu7o%m8z1J&oQ?PE+pA|@7*~L zAM45%rE7ndqS)ux@B7|!&+~ge?s=YjtYO32#S6c>kYSj`b?ZWn3^RWx!+hcBoh9Um zdetyg5e;;{Gw;kHDMn4{OyMIku~Ihe}BKKs>x)swY9ae zu~AW!wzjsWrl$S-_ovh8R4OIQawHPjv}x16efu_U+<4%?f%^J-UDsP$TDET8s%cs_ zn{94xKL6|PzmVw`q}OfI7-rE@<8Kc0)K8Z&%$=*{NQ>SQUeAka$|c0qsO0KMrO9Z9 zsjBWs3u2q3vr#D_D}KkNUkx}|Ip%jXmxn#!bgk4Xuj|T4_jhfGh+S=BWz10>SiG>R zgC{ejBwb)TQb|SQJNynhF;CtbhusdA4$<5Ej-at3+Y)YIYt@XzR=PN+=<#suD)O#u zrMIH8@;=t<@%r4Jm2Pjj)9c|q9A93}S}zA#Z$VWi7Uvs7-?Ek?pZtziT~G6FcYAxg ztKH{PGYL0WSy}1!c->yFlZqolCB>U-BMCYNs6wKv0Pznx>eOxtyR6n z)P=97EkFny4j0zA>0VN)!V;RkW*a#TizYW6twlQ1lDkpT)NDqS)@&o&yvI13G+&#M z1YONU2qW5;tHItf>n(Tr*n690C8oA(-zfTl6cTjF?;w}1%<18ro{9)p&X-s6T*U+C zsIVH7?`Gbw?bp_~#Dk~^&=9BhGZe65;PGh{hqJ9O=E zn6FbbT~I`+F64KRlXS^)jQ5qT;<#u9=PX+p6`kIdWo6Dv59f2PD(6J5g5!j8A7}qQ zq>5SNQrf?deazq2XJjH#Lh`ofG5CJLf-1@f{5n}9SE$oGW^(fV(pKxE`8g@GlnZ!4 zG`QRE5RJ zTdRK5|0%;jQeyrU?JKGSu6T1B!;CR?p_gOy9 zSN8KbM6W9-0iJ*;KokIvHK%}uz(Qalun<@XEQGHB7YWWH+-BpJAnd}~whJfn+>(~R zs-?NBgV!!|lOH0WW8R2wM@hB5Xz6V}rARo2TVB&eQj;@g)#OZbrp@$29J1?^KnYM{D$)Syup@t<1SmlgwE>TSN5CVk zWQHg}6d(!^g=q!d00n>oKmnjIGoX;`>$$wkD7JcO;3PXTp64!paD3T*e*Vu%6fi%* zmoz2zo_*`wW2BgFNec;!<;JHK7#}7BMz5Zu5TzYjaiZo_*uKLI$6;fj7qc!I|5Aqo%$hyp|b`q3&Qgrz`YQP2v|3eXDB z3eXBOj)ljJ^c@>=(FPHepy^n4ZOJVw`7nywC=eJ33`7BQ!@a>+`qmr9?N+m}4!EiTJ?V@RU9+Is5W z;oeuM;;kLbDh_}i zhGc*pjPApb21o;>0boE|kpm0>1^@$q0l)x;5!@!zfrgv#$#xSGuB)UPU=q~;sDCgY z#Dj_t)VQFk2X#zEIXH8Wl#ir*B;_M1A4&OBH$ILcDIZDsvyhZeP-wRF@x|eut%uJ| zjB%6s-KPU5Xvw14x8CmGfJ2~!oiqVTfD%)s&A9jO!c;73j|!uC#-w9xWboQJ*Il!Z8jPfG9u|APO*$ zU@GDf?X+A4q5x5VC_ogtqA&|)4+|$g+USDtL6A#k|+AIqAa^Eyf`7dvepJ_6?9lEa!4IMCVZf*X1Z*Q}i$9d#!pYKox z6O%k9kOEu*0r}uKx%CIE0Bk~oK$(&!|G=*JjG0)ucf=!azxI#8!H$U+`l`*wZ=<{R ze2MTv{v7cFOdE5)-$_EHxsk#Rw7a?*?y}vd$@?2>=EKu^#{c literal 0 HcmV?d00001 diff --git a/Minecraft.Client/Assets/RmlUiAssets/images/Toggle_Switch_Off.png b/Minecraft.Client/Assets/RmlUiAssets/images/Toggle_Switch_Off.png new file mode 100644 index 0000000000000000000000000000000000000000..f47fc6f348fff29a459a03cddd3861b2c3560c51 GIT binary patch literal 10306 zcmeHNe@q)y9DgN62Mg0kB1Un!bSC$Y?NJa$yS7*;LX8E(FwVKKUGE;{Z11k^T|+_n z;r=mX88|VKXa)Z;(LWpsaWWSTkYHqq3R^UotQr_7m6?qh7B`20@7*>-}m#r-_Q4Z-}im(c)qG4EA!Dz06>-YP|nPO7OW1^wf> z^Z1d^>2U%u(6#$-Ivst_2v)u%0kCHje{^Hw$1DKpbzJQs`H<&1D2uH44(c^gk!DsbbX&;$6QasC~qz5D6fFhe^C>Ve|6l94qo6Hz9%UESj z4nP&XGa%m?`T&Q2Zw=6b&u)ksSy)d;07iLj zc$-p#e4N}6@nX_Z{zzC9pkEZ4o)QRJU{hm~WTZAQV4Z_(Uj1esD0fgKLhhA{TZlo# z|Gn0NCKOPlY(ycktEaaF00ZxKIcuYJSK|XGAN%Iv@{2PMJe&4*Hp7*f&tI|qVCcS* zIO@;yc<01V;NDMT`9;_G(-9K@w|WH>w81p9vj2W1EKjfYDJ=lbG_|A8P}>KeP}`^9 z)COP;XnNQNe2`!Ezc2mr?&6IP!E54J=-ZAHt;@Fy$-3DhOlQZ}I5R<)#$inlSOYsN zru{s5IFWm9&SsxKkUM53frE<=T0$3x79$2!Z&R$+^>l&LiYoT4+Q7i7QRTk8!r|Ch{1J=M+CQWBQo3vE) zTdJ#X??L5~jP~kFgM8JIGYd_hs>c)WtxzRR!Qh=HmvAnnd^o?Xp z%9mWWG8+J2pH5z(;?a4cr=&^*AoZI3i@70u$p8>v!&V%Y4wvmS(E_KZ7{LMce$Int z17NoLJrrFJCBgw+EN{`y4E~@cSjM6~lv|cn<|%}AY)P{h9%!~#(9QL4?GEP%@(caJubY{`iNs`Azl8udx`oY-3=E~Cm>h$S$;n~f%r)d?I}Hv^XnTeZ@9yz=-OAn!O+q*1ATNoeL2r;TGqg!! zB{x*j+uYK#p?94ZA4jNDJ}3KVG~6tnMwK9+-$zuLE9pZ z6B857pvuOP{0 z$&!`(rb-cd3k6QrvWg*U(ygdtMS|rh7bF@fk#IpCdMODq1SJu&dyB}wH|mN^Nh+fl zNI06plU%G+=X1!SBkO%$frs@1-?WD(D4q$=35k(hr+^3t*?9LGb)bx(NQB%g9Ji2y zi2r-61x?7KNZE)&;_2@5Rsb|DrA4+1f6a8jaD2kKb1(JJiOg%AmJjz79{l(oH~M+} zfbU}E_U#7^`_7Ga?)>(}^mpDLT>Ro#aMt{=x_8HGCtIpfNX{GY-UdKxob^_W5~3IW zO;cJxl&I|ogn9wA$EgDeuiyzX1DJALrN)PwDpm8Drp_-Y@Ggn^fpIPn!19vX> zi4S4}{kJ;K98qnC#f*878Q2=ZO{(sC*u5)g9=432pnPg+E*La6oEV?27`}I9MvNV^ zPhB4ANOlL_(AvGBv}02_C4Q19-i9BTYS7uh!)>mri~gu*?#a_Ut$ucKd8!9xC-*JZ z)=$&Y^HTT!{MGbwz7{bEuCz7W7bj4PwXm^5qKydW9m%b$t0pa2 z$hy+@_k|^LWhp?4?Jorv|G0^y-r(=8;eMYt#&|CJjs~yC_j6~{wBM_%;1^8HgUkRf zIH)?nJYXJV2BJ`3`YiBG=8X(vaB{lAeq?NJ{;n|wTpetAZy{T&_hg*!JrtO4vxDE* zqjynO&IKl}m+G-CqGStPR#l`G&8PpP{>+<(;*qA6<$0uMp|&=NO4u`xFIiV{ItkQ#$U9y zc1`#;mx`3V(n+IccE^Asdl1b$2!F{}IL0s5s??qX507d)ercKd>(|m#@{*-*+l%@N Hj>!vI1u literal 0 HcmV?d00001 diff --git a/Minecraft.Client/Assets/RmlUiAssets/images/Trash_Bin_Icon.png b/Minecraft.Client/Assets/RmlUiAssets/images/Trash_Bin_Icon.png new file mode 100644 index 0000000000000000000000000000000000000000..543d891748f5961aa15d66cefd7c9a72398b71ce GIT binary patch literal 70075 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 VKffJXZJLMg51{u~e;a=FzX04%Y5f2I literal 0 HcmV?d00001 diff --git a/Minecraft.Client/Assets/RmlUiAssets/images/button_highlighted.png b/Minecraft.Client/Assets/RmlUiAssets/images/button_highlighted.png new file mode 100644 index 0000000000000000000000000000000000000000..af20c131447f86e9506032874b1f466b5917bcd4 GIT binary patch literal 3770 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 literal 0 HcmV?d00001 diff --git a/Minecraft.Client/Assets/RmlUiAssets/images/frame_background.png b/Minecraft.Client/Assets/RmlUiAssets/images/frame_background.png new file mode 100644 index 0000000000000000000000000000000000000000..d6dae3f3c4eedf6f8021cff277976d63154c34d2 GIT binary patch literal 6599 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 MBi*6U{=oPD0eKF?g8%>k literal 0 HcmV?d00001 diff --git a/Minecraft.Client/Assets/RmlUiAssets/images/lceheadwer.png b/Minecraft.Client/Assets/RmlUiAssets/images/lceheadwer.png new file mode 100644 index 0000000000000000000000000000000000000000..abf38ba010ceb4605a44874d518c822a8452ea2e GIT binary patch literal 438 zcmV;n0ZIOeP)_=c(n#zV2(^yPN?`381PfF^Yk~ zAa;+tczZjGHK|0H)WqRm%Y2 zU4v)c0LB6!Usf4z42TT+3XZS20El`33>$RF%c=#9iNL!IAZdV*UdN!d8E`o0a3&@w z2*v=q0sg)l3&6M6HGr1AuI>27YP=+lNQ41&UO*@afa&!XF`4!{$NS};J3w^(+i#~> g()!*2#u5YY1B^rYT_#82^Z)<=07*qoM6N<$f>^({KmY&$ literal 0 HcmV?d00001 diff --git a/Minecraft.Client/Assets/RmlUiAssets/images/up_icon.png b/Minecraft.Client/Assets/RmlUiAssets/images/up_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..b119bcd235a4a527aeb90ed2bd6d37d3c2b400dc GIT binary patch literal 596 zcmeAS@N?(olHy`uVBq!ia0vp^0YI$9!3HE%R@?zeFct^7J2BoosZ$T+u%tWsIx;Y9 z?C1WI$O_~uBzpw;GB8xBF)%c=FfjZA3N^f7U???UV0e|lz+g3lfkC`r&aOZkpaf@u zM`SSr1K(i~W;~w1A_XYOUgGKN%KnO#gVWqNEb@FA(7sikE{-7?_ukIh?|s-o=IHwn z0iGYs3+o*mH98bh^f`4OT~l;ExN&mu#4;fjRo8;o7fxy}j&9RaittpZeygCDB=)?? zqQhyaU*7wcDbrNmRzH6I_4aP}{rCCZm0V9fu{g)_b8VP+mq?awuJotc_!Lj=rlba* z35{h9%!ZR_V8drgXW#uwtbQ3H?~?bYqFhSnEqBP zeDzg@+`x(cIcjwJi(yJHskB= z4XKe6+QilQ3;sU5_uHqg{KqEVzo%yleX%`1TXSx@+rx_)Cf65)1YHe(+YZyD(0cl* z(URQvT=so;BK~_mNtl0s^@a6wW?y*!{de$1OS9JFj|*2d1$l0jzxwayp*f8_1)59e zG`=m9KJ@72N3+Z9=ib&FwOXHhyGV;mx{Zyxv~6`M80zAs$=`qz_qF73?O8^d#-yQN=F;Vxz~a$xgVsS%?g@_;>2!bn!J eMQgtOU#72DQ}tF)Z9E8!NCr<=KbLh*2~7YiE%%WC literal 0 HcmV?d00001 diff --git a/Minecraft.Client/Assets/RmlUiAssets/panorama/Background_Day.png b/Minecraft.Client/Assets/RmlUiAssets/panorama/Background_Day.png new file mode 100644 index 0000000000000000000000000000000000000000..c680c09994227de4f4d8ce6e6da25a0c2c799035 GIT binary patch literal 147533 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

l@snb8$8CYw?u)< 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

nAjO9?>_j^!wN6+k2QP#mlm;%d7SE&BYUFpQ8jK z`6%#kh62i~Qh33iJWjh`ZV8xp{XGGXTxeW02Kjy4{j5KpRam|Yv_s*{ekE+A;nicB zxcL#u#knF$g41AzQ0We^Sho4TrN$WeKT9C`gpNk+b$3#ywqU7!kw-`woNB8_`B9Hs z_~h{TLkb#B)8pHNiQOsYhA__paiJ;+kLgAU6crwi!PB3GGQY*Ra+!$5UgYF%M- z_ic=AjQ26OQ#lf@scA=CGg{d-oP`8NrvKrovWf0hnSVp(fm5^B4pkrM1MA zk1m?iajU!SHm9+3S2{Az{s_y0cQkvgGf;X?JU#C!6j_|*7eDde+|uq5i6%0gG8g#H zAFq5sNX_JFkENU0^ZEQ7S0Xy)v0i!)C_n3>vaH)2bIz~tZ^Pyf>q`gt1tsw^p}4=k zJ_ILsw=?XJ9tB}xl}BG%Yh&0BatGX7ZO-}j_P%Xf0{*~pln%}K9^r-`t%MdHMwdL^mc5RDluW4n%w-fqMb`x=Eu*)C6 z4ptI_o_gjq1ZA~t6T5iteYgHmO!j&PVg4%sh_Ero9Pia8O$kz~)$8?nsc49wKZJA4m0% z@?=|suAY?4>jR&vRL1%f5oA8ap{3cyR32}lX}gVSW3ZdBc5N@~W7EdvJ;!dRgpe}3 z@U8eY_7X)MJxD6M6{y~vtMbGo;m`QUY6~*S(n$F8lETfW$2j0WdqCNM_z|278zoUQ zpk^#r+J$HchdJz{N{X{!w5!uInINN(Cm_$A<}~*Sd{|b)+SvyulYvw4Y-i$#1i-(y zk~Xq%KpM})3qA#&1Gs+BeVQ{MsQBh3$a%tL2SpORce;zlor9c+irN^C7r2Det-y6a zzpMusn%H?n`Rm_)`G4QP{=Dx`>|~gV%j=idKfSzs;rd}wwNsDp7AvO; z;a8UkS61X13}(H;ph*;}y-GEiH;AUj)%AehpU>^xx3?eu^hmvBfq%5T1@69U20aPBnbx>=~!?L^WXKwJ(D?l8=pwtdSnOHiviA2V+ z+s?DKK`O(hxl=ayT3(oZjQ#n1QW(4*JV#bRoXcZfmj`?SC^LJ?Fcdc10RduiEO)eV!wzw2en!*fBLoaJ*Mm%L6o4)DuwS#m${Y;|?SpdjWox+oHOV zF2U@#%@uAE4*3FDAvYrpEu3Pmi*soOaHs(y$$^GNCrSeLZG&I|EvVnCp&AB@^ypNl5 zMiJAVV{5Iw@6Tyte{NMm2~(9NLJ&_4?H5dKf`Y0jbTHhC$hRJ2x@Qzh<0>HT#Hla~ zlR@kgeo1G{H3!S9k7@}v0gkXQOmGDOT7L`)x>_k}ru+)u!hutRA(>Rp*~x(i|G0iX}QEf#Ds;WSR+#=!_tOBNHz zA@Y6SiG1l@F`HzQV*;#Ely_o*ix!>eH2m#B!nVaJsX%EBOf1cHT~81BS=;bbL-9%8 zO4i^c30B3nJ&GI9e?o5X<#h6_2cE|!_5x7~T#p_=wy=ycXS=o_dw>7+*RMZ+{rdMY zcSv!9oA!0vwjah%U3A)voIBwF{cdVu#;Qa!DYC1o=SPP}!NJ)wr@)B2kh8BMr2~bz z?WjBbZsYyAztf!Szx{XXmoGp5&;N_O{y6{s_4&T{IbzN;J>^3ovGP)sp5(dsCltPl ziAV^aH8Mn^TEC4aOgcH0fazt+GSfD9;g+_Ip+uz5fsC_AoM#~GrwL<}w4mX2s9MQn zlI_93#k_SsiV?zN%E79nZoIPz)>`{i3@lV+l4nxnW06IvNd(zTZxX|z(3>oRBU@@S zx!$aj%L!^w1YJP4JAL+_or)=k&_Pbpm@8!(TPh70uOy*@9D&QX?_fQ`f(6!->helQ zxtBC6N=D)CXiLm-D^XTe1{$}6IqJk zj!iAxkSjMuzmT_w*L9|C+Lk}j)z6b!dk9s&5YdY7JjJ8|6o z@#Tj<{p+9p`M-aCeQiAvaH&e3c~SrQGqYnlNI1j%O@>OSQ|nC&={v*6Of+MT#vS^0 zY9yyjIj3X$_V$(~c;*}i9WA_q%MfY)8|P<__xJZXXZbBo-wR0Mm=sUt>`Z)4JVP`U z#f90PsglHc;-cgONJ~l4qQp``$3crdR%E#jzV`NbEUgKysGhv$v2hmoGXDpq6Gi0v zm@kO#6cRu#ZTlQ!H0jv;89TT9$v^0XOQ0qPow(F1XIn}q6KNFH@&CG-yn>1On;VX4 zJWb7&UBy@7#doqd_SvH?5e_@vxV&D4y2Z;%ahJq1xWb$UmV%e?MM5;tJDF1&cf~~p zvwT#}@G5yw&Gbc?aH@+o2_kbBPQA*V9r##~;^z_~h&5#>*^w8Ju`6rA$qUWFgUIkJ z6XprSG^)8xfD4z=kQXTdvINImTMc8OlQ;r8Ep-)C^F)E$L|-7f52>H)ziGk*Zwp24 z6Yp+LK{vvLmL(y7RHlK?Aw}M#4GhLE??27aRZ~HU1 zae`i_F&qZZ_vf##%a`6)T)BQ;qh7C#!craSU0+|;SM&ooJtc)MMry*ec|HOsFrNb(4+3x?{|O> zLCtyz;jDHs-7;MAsjXqPNi*W~B@$in^kdx9MC#Fb_<+7{(_Fl3Zygx`O9x6Qd`}i? zF7Sk~PeYEdATcvo3>t3EcGfpG_rw4o94b&M3Knbbe60WPTxEChAj)TQ)Dd%^&Mrxr zTy<5vLuwHL2+KgHc#sMSY#VA=)sia0)psib;?9J#B&C-krs_cuW)yrnNSWe@a@vqe z=@85}2(K~}61ph*C+i$dbP+OU(FTuW9Af4I)sWn7pHuq@--b@VCm11aNiL>M=-#{w z@PWD8zK@s&l(t9L-n&cAI50w7id~e(K(j*S0)fzBFkVB{6sD%j=K-{7?V#KmYmP{>R7b7lykh1%?Mc7uhgNN_KCgj_GlyqQ`66@fvBjC7k@bU(jqhNbn%hf0h znEk9!8R3ifbfT6#E00o`Q`SXPFeh@rQ}L7V%v&ZVsSF}Je3%tb&ud7)(gPco=|V;c zH<#|bkSf>=0O6@hd@9a$o667G^qvODl~U;(MvnEl@B2M}vGi3BP$j_Kk5-EYW?~i2 zIF++wErx&!X&8KJLg`IcC)E5ljEs+%1~P{!b6>`M`&~o zU)dLO%3!B^V-vQf_8@;ytPvz_=Q&M~aimLE6?OmEO}57C4A_VRHgPvA^Wt-Dg;X*6 z>ONimwni$Vo!bf0z0nO~Qw*a}4+{nZ|$n%#%WS8`k zTqWC#!DC@oI`+dpxrW?vI|VXL+eP&Rw;80dL5m%`y_;`#s7*oV-h)F-a=q&3!lV*) zlFP!p50EX&Jd>3z*bt)vj$qHSSahZ)H>GrGsr5h(I+kZWTb8ncT`KuvuN`}+0g zzx?H)t-ZWNS7RcTCY*!m&>B9YM&ctm3ZdC%%l$zD;*)K ze(VVT&SM&w)Xh#w8kR7!iN> zF?|+tzUn3^yZZ?b$sbJfeV=p7F4*GG#Sr9bp$-wzDfmQK&X~|4z?pAUzFW{e7=41? zdTUF6fo6sb8EtJ@UtWJ)A3yYUA(lIE^N*_@mWo^?Tm|M7{!=!m0?6{I862qqr$6j- zhUpzKa+C)v%8W93WABGcq%8tiQIDX zP^jd}4L9#@25loc$D`3}M~cqeeMXO%tfqYro!pRJ9WN9O3}uP=gAjO9G@1{djb}`2 z5H_++bBo7gizkE+C%BC32BNQS)yls;i`JQE1q&3R-h zciH;vtg$SE8@t=?+oTa~vubaDdC`|2_R#Iuw|M#G z2UVOp%08EmED!X%MSyN8JZ5CfDZN|uez;IVkU3RMobg_Pw6B`QIKMDe_g!#Z;pwhZj;7MU2c{7; zlk{Ejj2Qvk2Odmflu7Nev&UduHtcFPk%g&6*iFeOXXpVzg34NDW#Dk-4;xl;wG#mB zSX6jlLII66v>>!#()-jDlwex0%xNAbARM`fMAAW$yS0L47)cx;VYbMVO$f^vWTX3( z)ZUwNTbR`y__oHx=3bFXpWmo;&4ocCW{@s$tS&A6jxi@UmVFYtO9QW6v^PR#Z7KbX z7f`hQQ~&HDh3_m5JL-hLI269~%jgK;+#2;Imx#0A_AN*zOezstyEc_nfR%*r2(&?MV2!GI=>$ z2Q<;9043S<3CYzouj(9xGt;iM_L;{b$sexPpoE;MR&-gl7fwu1&}Psd5R|Y_uOUvt zNZA>w`qMCUk+@}cj2nYWY0!4RHX{1i19awa-tlKh3M^|jxwP{v7=bVR$c(ow^kW9dp z;Bh%bFfWlBGr)W8R?W!6uMC$PhKcAqKhTXw;7)-rfxSFR5z^MEwI$_fqtLlM!l^Z^ zwWWeT z90?i{+DVnz$Rfw;I&2W=D$RJ(HF4!fQ7>Qj_uWVtbjm`>f>skS6e1&IZCVDdXZuLu z7>T=eo94?Ht+m|%r_`8pZgU81x<~I_5$_9{vy59!gmPO*o7?YojIHF0oTQv0VNeD8 zE@Qnm+MPKxOf7x~?TQgexb0$z^sXqc}QgWA2z!%Nr~|Ly_Abc zN7mh1s23!x4WR&4nwEw7+zPekGq^Fv9pH)`@QK|6cFIW>#wH;Mnai;V#2sSJ!}mF9 zO3={*n#%)|ompVqROU5OjbH;i2h>B$Ee4}mv}@VMU1eFC2+u@*P^=;aWxh33=-)DV zU%!0$lFHe2<44V@F-oncP9idbQf8M^jWZM|jvxNR&H&f`y57f&1|JjVH}g>pricfq z8hGkAdORL?P5M-_Q`I=GUjVY%7{Oc^-cERkmXOshCwS(l8q>xdxJh4IUrz)jSG`S1yuz&^M#Fst3BobGQ8F)3I^~6B zhF?ou$jYtZon5*z{1q_ty@r@&gm{DsE6EX%T#3+!B5)13Ah5l#8L$;wF8AH$Dk-kn ztJ7!UJP2AdrzeezRJFm_y%o&#gMr@X$epLr)AeZq-Y+v`iYR^*W4EIgu4hR4Ty3X# zV>!i>XX8+6$WX@*t%*k=Nm-m5{5WC2CYP9)HU3x_m<~3sJc!aof^V`ESiiV)?R)(G zV@JO@x@@@_I6iTW14z;2ip|3IF}`l&*BEcHKf{Nxw#Sz*Km1F7{Lmh+lcL!m9|U_H zj10G+3S6Q6ZX;n(IJ?l*wVgS%NnfQensNnsS1m6ptX)i*E0~z|);VppN-sEt3PBOQ zIwD*;YZx%sGo*_IF1QXzXwPbm8a6avH8C}nc2*3f5{U)vZZ2V*9m!&*X8;P(_s|Le zHbn74Q@!^w$s}-9hXg? z#32e?L?jaYOruuS4@Kw6)0NMqqE-4ZbX&ipV1pFv01ks0tlIExh}XG$1PvoIEcn1K zxIjxlP8@}AaHcp+L5f74f2vVTCChQ!Oq9>?W3lb#dE?H?jXBexRHaJ9b>+6U$I_ii z#vEpaa=}e`Ehi?>DYn0hF zAkJbbVPMw>!{J%XsRFFpNcc>IN&#^wTBYIRpn+Jg5xa#|vQsD}hbh7+o?TyAv^A$% z%(aqwJ_LP8x7S@Je}L zCVK?MO8!tDsm|Ew)Luwln}+jH9+2=sJitn+Nq_Xl;sURzXw@fFJv-4RT8>ou@({H8 zO#hF2WC@va`&Wmf$Y5-Pf{>!F^rx{2`_iSW<{~$fFn6c|(32FhR0BymCu}fMz~I~U#m_)7Cu17KMq=znWQi9%grY)O zs_jTYRmY3VAYc&KU9v7tSn!FLCO$NDSbG9Er&137p*4QYCy#(GWAk|JS$F3j^p1>( zKXRT0C}*(XcPzE8%bBWd9JGIZ|M~g$x4Hf5<6Sva+n@gY=igwq%ga9nHl&RwGk206 z(cFhwyS(+kAJDwB*PL4vZyW9|O|@l>r}#&=Q*I=%!UKNp&V+kmlG?y@(zVI(xiU$J z;;xnIN;2=-q0cnv*A8Lb5J8o~U^z7+taux9L+ep!auix&&|DR9))S91%DUWg%>@4# zfKqup1$$JXr3shYMG}zWT=d!dSVznLOD>IEgN>k(2Ep``z>G2+-Ui(<4ydL3N9F^% zvanlkO%wsadmaq8&aBg7z*<_?m#&Lw`_8wQjbhy;!bDiStE_-e2mD-}o4Ol~>1+cj zcXL|62+gjnXL#{!ND2qJOYrWQdnno#;*Ps0&JM@}5vyuys&$(cLF{db7qvxOp8&(= zeYItEH^%p;$S8Sx6i%1NRNoSa2j(YgAEdM+qP|t zarXu#Z2Stj1Ur6~0lx2BGJu4I!K{PKw+V^mZq>F}gq>n?rp{Pf)J$mBgr+W7EnXOD zl`>D>ju)vCm7#@rVV=5*EZ3U3mZ&;eHDk2dxB)cL$RjTwm5p`n{POycm3K#C!-V3d zg~^U6h(X@9fj(%5phDsv#$M2L54dOZQmTq!wR!l*RA!eZHsJdncg%`Z!L=`QU+0`* z=E(_gXa=S9BrmNp4)yLVyP9BjZ1z;gfra8wt?9-cnuwed3NK0>K@Bh=lC)-LjQ5lq9znCW_4AN|pnh-d~@hU;{zW|T%jQDNDTsziQ`bC-l@NT;USNu-oT zC0V$2mo|;Y7!x{Q$4Dn~e;d1*F^=;`lISX2f6(Vzs<|&}B2)$RNjm&KvZ(2@a_a!{ z=y!fskC>PUY3u6`4dvq&cz0^-KjsFvBO|i8&lLZjvZZTdrYX_`c!tCSx`DRsed2(? zVso1Qj)_?ocW$AJMGNXC#A`HZf$xMfB%1;ul4{4Us!h5`>+5SL0w3`(i^9uvmJG{I zD0!*t1dNGxaU!gMP>IG7i9%}#rufmDR9Lb~`phx7F|x|QFh^O=nGuoc6E!UnbZ_Cx zQvQPs!2vyYl43|RrR(Nl9$>Y4ONad6a1u!|dm=g&_4$ek@kh*xo=N+c6Arh9$t2wL z;g3l>lTXxhCZdN4FgBRtkh6cijlx44zG(o<)wvjI9>`%-e@b5pk+eR`QmLJvf&WVp z6orH^OoGA34M-4KjJf?fzWx@<{Ofaj_i24ufBL7N{`p`3`J~wkE7GQ8(niHz4L}Tiip2>2w&j$s?J;}ULBb>%rYEMt!bwI~~iqY1t zt>sEcoe(4MVPgzrB6n^NUi!&+&IXHNle0D$Qd0sd@5NFQoUWv;P+~{!*yXbXk$kWGsb&&mBmQW^u$t5OMw3LA#~0Pk zSxWwhu-t-fm2*l>zy+*!$ce0itBGT(cAJ(Gh{NI3^d_-%WocEN7CHwxnK)SL3I-zK z=^bo2X@cp?HDZ9@l=75*jfJ5Bh?%ER-g;+gl&~vhI<|TC-DetS@iy}bK7|Ks?$v|Y zka8+Gck$pU-uWc(wPfi>m~5nmeF?BIHxkB>;mzUJJ$*M9iAW-GYx}n@K1mwMPL|#K zX8tBP#X0GsUBX7&WDCD|1yQ;J=3&*8Vgrd_5ZEH^f)uM}E-lj`J|st62!C{f-_gC1 z`RR6&@*=ySmX9tP>O$0-bSLT37uD7RG1%Nk0{V=#zf>7ix-s)@ClZss3`ZwP>4A7@ zIiJX>Na*)TN=>`MR8_dowW%OH9B+AkP;pf z-<-r&b6L88-V9O6ewP7eUE--^P1Yn4b{E0q8yK0Urob-crr@sJp>=tPwwD!iI%XId zWXuUYm^_hiE~SQ4PB8eM5-EiOeK73=g7N%U@`-q5)M?2`|7C8_l zkp^LHlg}Q6vt+@XRiZWyWbOU?pBrl)%2{hv-zzA^SrEZMiCWNQVGwy(*w`+20nY}ACpJ|PB(2#LgmXDq(!I8R*_Yhz>il_LQC5xMi>;_Su+Q#KGVDvB44b7_V0t*@C?F|E z9s}iccoC;h;pR`eKNnrFm22YEgNa)tQ_;qvbvxuXpX2$<_Vw>`e07^<#@Zi0{PPcg z{vTd{`nP3$9U&4st8`jlB^G*Y5&O?y+5D&VhZm6;qw2+cB#HBSYKsn^G{a+Bm@?n7 zO;Sg=MogIN^T6ljRuQwR--r=X5mHi4-;t*`l?4+(oGsq6wU`_`c{-EG_rN$7EjB@? zf1o_=hu1)1D)zR#EZXkgtgvb5jAvn8RF{V=ok=ejPBv?_!C6~$PL2R@K##u>KExB_ zqFHuhP~1KIHUn#^Y)WW&bfh3rLjT!4jQx-+!njW7EV*$nyc7}l3I!KTR;#b8RO!w} z5+}=Js2_9ea|~dUOLk@=!;#rxw^E!Q(%_V3l6Ja4P$OWDx!Jzcq}FCQtN7ZRX>>M( zf#IDB6=4kNH!}7#Z3VI%XsU|&DYp=4Ke!NLw~177=zPwh!F=*t^w2zgQ4<%|^czvm zHlA~Q!mk|sduNf)p_v@jk8rVb85d2rLOLIm%P&Xlj7#JgUO-#SpY;AYtA+{Sn6`?J z;Elv`jYZWtpNKDle}aG1yo6l=ysC0|vWvo`Ir#bV=n%0^AcdaGGvZAgG+RTO|x@>FXia=_MgeLNn^vRs?%4u2f(KHs+Y zr#;)qd=Md7mi{{CbK2WJ-d2AV#F6kr`ma;5^b>G`y|K2{`q*YV0o9j|YMzqNfSWV?N{v>P&|vX9N*dcs z+|1od>@^nPgtKY~u~LwPm1=_7t;mwg95C)9&48lYNh>AW>P`EgEViXBi)eSIMDS~C ztb$%q&;9+Ew_pC>x3~YWv5QE5{McW9{L{bvFJJ!YUuAtAEFNI7&^f=z8tlJ5-+z7k z+I9Ky^-C8X^wxACQL=f{%&;P0(8F^95z7;B;E6TU=5#%;Ux{x{XfMoO0si3TP0k7P zA`7`>E3C#2eIUKHmB~UXx+pIrnrAe#JfVdgF}oLT5E4#u=-vV|l{2ZW@THmV(1JV5 z5|CFB#*SmK>J*kiOwu~F&azOH^eV%=YJp`{CT2?9Yb1gbt0 zAW4B%i3rZYSM>xk^8)JL!vP#5^dZ&XVB9wm&5V5^DGfCZw{6-`Tt4ip8LAxK5C}_= z?=x%+!%m|n)zMM|W7aFpc<$!L$$s{2-=2e`wH7Q<6-h{#I;0pDx5V70?*~AtV3gw2 zdoY9X(i$zj)9PX2bIv`&6SM-U*2r6MMM)g0*|*2hq`dIjt&6;1=J7RHMf|ZJ+C6dX zj_j<>g>lRJLDG_~32p@s6+%Ac>0+tDw}C+LmFRcxP2@>4z%WepKvID;3)i9!GxW2q zdhStRtNJAP8p%^SAdIW-tSZTo+M)>NU$+KbuKsLI`lBt&;t;Vcp{l?~Mv_ge*(m;| z60NItF&nIK?U#4XF{CueVps-Jr8kw8>nk2nK|IlLfwq<1b(h%HRL}^*?@O)-PZFEPY`T`u|5BHs|yC8+)5$%L3L-x^m;#lPx<3 zL`%*7y0ikP6?mb>v(2HZLToXeIg9cVMS^tGH+E0;kU%IQao00j0w6d|jO;N2UVvN@ zF6hY>VWyZ*Y=WarrZesMxj4&FI0G4$mo84jgI)W=3nUQc%C9=ED&?n@7=&3_sI4p- zB<|(ZRM5RcpiuHlIyT(6``|0+#UXNq?BS1?sFko?av!Yra>3F+D$RZPQ%M+EuesBX z&uos}_ov5R-UZiCZhCU}JxObKA-H@DW1ML_FMPVZyGi zXH40~Tka0MbRY_DHJ#mEqckwHG50Ne2bu!-RLefe=?OJfb#DZ=V#QCPMF6lv%W`VR zN0@9n2#nH`1T%_gZ-WZw6H=lviMCAK+6I0&4eyJ2#Pm}=y;oW~t z(9JZs49&c}yT>aJ#h~OP459nw(O#DR^7>HaeOJ`il^|JFciZ>5eI=(-yaQTAkV8Mi zt$vr{CRYVyCbD~mDI$#wGRzbm47%1{MqnkNA&00U?x>npX?eDz)?Y&;;+TkO!MWZ7 z$2#r)k3Fee@HL7-?#4qzDubTFXZYN1a6BczKiZBb#9okTt+V-_k_AjMUr1w65fW_1f6^Kh}_+*uBdD~FhgQV zRBYpd6Q~3{01kjuMvjPuH6-WKP`zDAvYMg>OH{Qi*F!1%fTB|z2S5;9wW^4R1O!CF z%thpP@foFQ!e<`HWu_ZdTNpsy6;wzL^NXW_{vhM&s0Fyc&;4zJYU#FW9O^-KPF|tux_Js}UR4I|fHZeL4%+Lk0&*g}oONyN@Bn&DkYlL=_e=?V)>rd;jI% z|NZ}r;OX?=i~JyeW?c(B^Iszlo7?l}zyIa$|MB0pufNQ>C+_~L<;Ot<=96>clBq^q z4M@CY}%3I?pPy0t4l0ICfMSSMMn`I;RgLbH*AVO-o&@j8DlB|U8_>9AdFN%O!R zyQ2Yh%R2@qP!0;q38UZ-v>O&pDpNE>&{}88a?kfmU%hj%Wch za2RW&-is#^6qdQo@l|3nq)V2eP!3ndkM#23QEt=(sKt5Pb>8f>N0CXz5N;9vO?{-m zn7ScIl!Hq^Qn;95cQYlK!-uEdMd-CQNS)o|a?gk}rIpDX(%XWxr9_rsh@pT2Q8L`a zrhyLgcwZF#K%_L46D_N_o2<(>=agW$*;@#kLp)0-X?g-ESfG?fh7?C4UK1xxPUcM1 z7ya>d`SWj~xG#?d@X~kTK6KHhy2NYSt$%%rsQ>!(y^1%AJCQOB9_-BoVm8M$W=yoY zuVCUFnD-HC?%`Oa408OzDlo`0-MU544mU^R5O&pEEO>?lK6OoIqRhC`FyC@BM&+Xf z&@#&DNiC{SPK<4P`@7I2ZA^LwUd_kc-}kq_Kfiu`KHuhyw!HM$fBDm&|A+O*e{Suy zwTCF18N5tBJplO*z)W7++Leq|Aaf?si3unfcA7`Hk-!a=IdsDrcx|?_-5jqvO8}Tw z3_GlP5tc#*H#t^FDaUZ*8wm+Pkg+sgx-KS*VPB+qljRxZR!r52+^V&(2KJ3G@`00? z5$xu!tePk#VUf$`Gcc5;H9B@dQi4Il3B^12F^6D>7Os>xrtFSol_m#|0W>}_T+qEv zN<&yb?vZN@V}+8%m3EpA_i$WkfYQ6R71VoYVB4S1r@McrkVST$cPA0Ek%EPMgl#rA zCTAR)e7X$|M%@{|LUIw}i9l#>D&gE3R569sXg-H;8_^CcDTEyNh*<&~L4*?LBqPeO zLQ1PL|1d!)Kg=6B6{yZ}x0IC`o<85*24Z)X?pm?o92U%y{Y57*5f~k3cUV8iXdevC z&tg8GlZOTN{TSc5tnlS#4I3tu#J#lt!&|mck8{0a%`9~j3v#Wq@bLg za1T@LYRtK_pqDsC9a3iux5vS!o{pQ>v4 z)TOq|AzZ3bV1RBNHtYE9${?r=5l`2;R{cPVaK*8#it5@TWYDhx)-WJDM}!G$1inav z!G@Yh@+N{TSZ?U`8y`uH?KFd~??_Zd29_WQ80Sz5qoHJgo)yro$yuGjBs}V{kxGp~ z7f|JcYM?Lp!gVDe_!yRTU6v&;X5Y5c#b`6k=RHtZDp~?dn8p}pw_ses;XQv|sg4ke zB?mOYsE(%#kXp;-M>g&;4yccu9kL003vqzxBL(@~-`;+X`TRwyvAw1=f$}{9g@wCn>+AZ%pMU)6pO?pL zIRnO141q=8$&K6`g+MU`iVz|u5x$U$T~`0#dvaL>owpe|HL5AlYroaPKCL$bhX<9G ziVb$=b6|f40w0m!euEj0+&_`wBS4>t>H$vVO~{V|$#<4b-B=n8ldu3v9) zb{4*f#KbYDr^C2`vY6)sq^jAlZ`(Gk#-fw!m33R=kNL_g4A%ztOJlb*;DWgpsUYet z$%SkZV}8mAMol$XddB#@M=943R~!T9WIYy7*~UH* zE1Jq&@n;X_iFv2ICec!Om`hhkMWR(b>N3z%!TpRlK0`T>h3tr9DyXi`eS#K{BZ~rc zxrX~CtC;AV*?c}717}nGd`A=L&8}3T09|t8?s?Fqrq9^LLo&1_(kX@qe1%ZH>y981 zQs9B?8x^Wdy0nFvTa5sywjdP7GzFF9afK-guAX+*t;%#IA)cd0pp>7;^pfDGk-08i zR*|Z~d14}KaEcGW`px4+tysVe34210?l1eceRX$%BqDA9_Ak1uKL{`MpOKK8&HcBg z#y|h%=fC~8-!!way?tw!x4*sb?{g00=~=C#>T0Oo+@E*MGzaCHUzTNzg$#__tzZpZhd82%i?g z$1hFQ$R(WJ;D7B{+(kF9oBY1}AJkMxB^4v_+)KoE%h7Ej`C4%hztRmTSZ;_>7NsD{ zqFg&W({&w}zE$Q~z*%O;P;tEN9nMet2E}^`|fY^y6b~=xaJ{n{&gmB_WzlKHtZ4+sCkCH|CLijnc6a z7u2`@7x@4L1{__5R?i|1=TyouZUqgs5jA%Z*IL~%(p_^bxICSbvx|yHv<{Xe6UK#G zmQTV@Zsn349Hh|k;RYNc1#fW^`B8z@*#v1{{k1=S=#QV;qA|9Ku<5zq;N>*zWip>G z;<_*^20wi6-x%5U9zKOLh2ujf#}pnB%3<6f7!*z~@Lb$LF0BumOy6~gRis>Weov=*jYmUNu67+iE}7#>$MoIJ?6HL2*d zYJht&3Y{4@mJ^79(!fcuoL`|jCGMwd1 zE$p^0zSw3!IC3Ky9#rFn?}T8`%snnZZA6abaH5;}3cP@1w*a6`!3-7ct!SI!=xY~g-aGA!W()eXLp^@yta90~8)iPb5;#1#UtcmZ$=8gL$RX#^b6#7ow8V|8rNtp%TPp^Oa>G8TS^0~Hs_`cD;yO9#pm}89h zVf&620mx@%L{tYGTcL9y+DVTgjGGcn(8o*^ro5;}wOCI4Y=B_!Xl4JUBHs8REj?tc z_am8qeS3r)+SfJ#8{Rhvx$3E_^A3Z_t9caK_A<-j8*%^lm;n*xXSsDoH`NDP-KSc17GC;Xf{wx*1wcrF zKq%C1+;=E=2v^`fXNnZx{p;F_afN?JF?ED zBWTIO3S#EY<*?tuS+44`BxaWCbs_W-8z`n)2Ed$W9=oPlx zzS~YVc{-1n?_;;IJbGVSTYBJExBfyTbafI^Py^*L`G%v-P$^fRX5au++p?&1d|5H^;%;+${_^*~Fr}ku z)zg(P3(4ertzk|K?nw z3ZXe3foY61UM;JjtElP(%Z(7mGVLmDBJezB0EUh%UGA+bW4BN2!Ctb2oY(x89EkdRe%;3(NcA7Mj)^~$G&|XV}#|G^tyLY;Bj}l zxWi|r>Q073R0&^J5i&Jk=x_wwu%;>tl9Z^z#Cat!_Sm~9j5dg3$RuE9U;&*FBBqYd zUe^AnA720bTVmnbm{1#Vvwg&vJSMqtbB>^WCy!>1${(XLD=7h2QQ_-ruIvA}#FzwC zZjEHx;Kc~Ml={Rw*V3 zj7$b|!7!scb}%Y|c?`2^yqq#D$ocF++xxNt*OwZJ4)Ed&{p5~>0SA`Mwz;D0v z-Bt(JBT+f^v+4(4v2rfMj!Vv4B{Gu|g@Y%1kTO7qewI1#BUmfM8K*uR&Y>KWEAs0e zj3a$u792_f*jHMck{(GCZQPU;@48^6xt|FiC$Lu-RF#N2IKFLj-{$izYyc5hR@NCM zPuR3dZ|l0OFYwzwBlhx91?^l2yA#O~AKWLzWpvL};qTc`BC&D)yAj_%6uZ<~M+N7Kk?;|f$S8h=!GRdf{H8`)8wVBdG#`e~=RlA1;3U9q1fslz9S=QfaH8i{BL z3(Mt`V5JfbKE=6_=GXvqR;|)DFpk;Z<{Gb2r-~Z3;?e8|;x98XQ_pkeS&bgxz9~Z6*kFZJE}~sj-BpDf~>Nrl#~fXfTxE> z9j8K13N3#Eed)3;jm#ZVvjsQep7Rr4!``&Q-!Wlb@l3u7u9H`(hpiMx*lO*IgDh&D ze5WUTh*U}(FJJ+8>1M;+D%|9VeJQR+;b}tM+7Gl3@rp2T`p#V>PtG*VD;w1De_M-OQLo0YW*|ASL`$}f zLyLGiyfo>i1BZeKrTp8VqX;s62-66GhgOiwRpunmn0FXcOJ3g4PS>5b5&PIFA_cxI z6490P%j^12KRv!Ylw$1Pyw&f&zP-&sA*)Ci;OabpbIsJ0eRC^S_psa8-0$v$vq>55 z;#4J@kXkSqP=^qoc*=m3N{CqirVQhM(PinKO913dIw6E4Is)1OcQb6(ttZ3&TCB_yP z#d0wC>wHoW(i5NWW4@D{sOS+syXOF60rX{kX^$_Bg|t8EtM5_H-{q`q4mM^cjX>Uj>j=UZun zfg&uML6u1sjmSy^b}$T7x{eej%CJ~0!Szs7>?#9^W2tJ>MWi{!e9R7AZ5}`;^j1iT zuexznVXn1r9^U2U_3`NXfE&G(vlN*3s_~~c&U5;bX$WL(@qvNg<=n8zHR;t|6%^q1=~_{!kX?BDAQk4Y@f2{`6=sYXjPFCkkZ^bp19t%djZW zZ*uo@=bjql0|r+>0Wwg4ZnI2EK)iq_8@chM=u$l$O=0O7(#B8iA9{QaqFYSz+4k*8 z9J*}M6unZ^gr#+X?d)*0!rueHHi!?K38w0f9Hfk4Pmv`#3j^>8=y9ikLn-7UZ8Qkcqqal^>K)R4s8k#VsS{iZV{%FgW zu?^(a8iU;kGp344uPyzF=a8v{e3f`AhmP_k&_CF?fW43BC*5zY!^fq+j@RICVms{H#|Z;n)e ztRjzG?ctE(;HHP_L#e${G~ok8m8w`%$&CM)?zS15a4Ut+EhrMPjLUEu<}$-JCf8OA zrPlk6q%G^?OMm&+w3MYudyq#KN>z!@grN_Jatsp*S7X%DfKwL2?n(uULo18U%--I9 z+d5GC6=n()D*&u9Rp#Oai zj>+1X0}5Ap0y8D6K~1%QW=7E{B&zgZTA@yH&{_Y9!>^kvlMF-LQi=3{&b^CJfl~`L z88JurqP?%{^0M}QA8OpYvO|ha^5;ZQS^$!;vw{@62dc{&`-)L5Ll8vp0EUL7&Ox5G z_C3sMnUB+$og}JEjB4ufqOrJ72|4te3(UH7eSKMfc)UKAmnN%(sI+B)#5p666+Du1 zFVM>a*Yj9RJPoIPIWm%WUZ;u~Z>t?oZ?_+FDyGU9Kh1V>Ef%|j&_19nj_Wu6M zoX;$jJ;pZXmdT-hRn_c%*K%k1r32jZLv(O zr8I{`(+W=ww@dH{$Fj(r_XS;@sy=eKU8U+ol!t!qhXV>EDe@%`4aTNN7KG- zHs`J)F{jUs-IN=XD@wl-eh@j`AWGs$26u&il-4d^UY6HKM-p9N+-kSn10Q2I6&~wc zmgR@nmzUSBTzOBbnqI%EBqaKHJSe0!?T@AP6&k$`<$aE~vF*0>`EHZC7LRZ(j1iY* z6y*;+J9r`%``Wm7nBDDPAMS`5C#b?=Q!Xz#-vKz zb$L8qgw!W*a(XU{5@lbzrMM)J?1uSN7VTMP&!w@pV8rz12E+tlzfB)6eO=cX{`U4Z zx1Gd%jx0+i+_?9ZN#ud8vV{%hV31-j=nXR8Rh?395!~I6Wo=_@W8c%y;%-e8S`+!O zk-g7=Bohd15&UXd-2Gd=;f}tXN}q`(#47?KNmc5&@_!2hq*UCqsQMO>+a2grjM#?I z#3E;baX?OU?o5}Wqb$@>-+13_>`dv0u)F>)aj&n&ti3NUy1wfA()tUD3itV%VdcqT zO{oc6r6N`Z`LmuXVZ8II(sGrQGXZmg(BWfu1ce5Fs+R-EQ!7Ni{) zaK(~T8gbxxVNru-Xp@gbOm_A(iJ_md@etbK`#+#UWtWPIN?$}flQ`o#TmtbGpXneM zAbkmpL5W}wXH(#eLymBnO1Z|cs#yZ|2emY1MP*^>lpNbRk|(Mh+IPmjoF+mMQV`}a z^J!z-$JhX`F#($;Sp=~CdkFw8Mc9RHkq`|PFe}l^DFULTOyf{#TJJ%+Qv#j{l&&Au z6M;YsI-?dTLBxHZlfcbdE2Mo>KJh0a?a`OKc{F+tvx zL%2UyVzQty2UWrVR4P?;qsgBPB@Nunb!`Zor74uNk1^pC`a+dcWn@a)SbEp~Sif1( zYg;1}PTt;@^n>+5gNs!AUz zN0%eMs#>=} z4nh;cmy<&hV{_wN9vIUC#t3m5#4J*{5{Cq$8AcM&VJJMXXj#u3h!1iZo6C*P;3o5# z-yp3$L?l76<((1X!=s#|%Aus-atD0b!!O-8T!msfh2fu}DE!&XS+Ej_BvB1foSK*m z5@tlt8E-S5)13HqVdwVRSg@F&Em7I-u(|}bA&5kgHiHb>E42Z~j0V_EHj_Wpf5(fK zfbpPCeP~3Kbg8jM+(f!IZp(1Bpts$=?sJAOLSL|!NU2eYw0i4 zi9Im%XIDh;{IU?y(!RN{mvw1yQD*kqsD7R1Oxmi7H)FnnX(G3bmPP^nNSpNsxeIP0 zU}KvayJH|YQs_fq6xIi3N^9AfyDhGZ8LXs%^p5%M!7W&_-7iW*a_s47q`+@A=P^CE zX3o&oL`ty$vLb|A?|o^#@v^C@Zl@VqA^~QWfGEfd>yWOa)Z7xaAZG!g6guwFHMDW- zv8=p4M5hmRc(I|D(3o?$4B_d64Q1lzKC-1`IA*_6RACxOor@yyLR55VZEZc~kYF)p z@g}lJBjRnEPo@c6l60~|n2Ka^S~CmS=1qwdBd;gCw+i-3<4iic=~!{}aif+|kn(ad zc~rwAuv$#xhn^GU(`R7oMEHOksbE_w#LlVgxrjyQ2AOY1MJ3ocHU^>seJ72hxmAy|13u#9weP}D92J2bV9f(=C^pQthzWCVY5WN3!V2k2mZ87~ zsbX=g#`cX+@=z_Z_t4>TaAHwO9?i3gPgeF48Jw|-Uhl9?S{TJ`j;06hi?iD%Qv(J{ zY&Uxvq(`(z(?zAJ)E;3Ic8@zg{b+m{#14qyPDbEY3sG~f?*<6*0+*Pg0Wsjh61VJ! z3}oQXXiZs3GHMfh8rqOKb9U$`_3_78XU8}>d;tciN@s;(hX$ITHEA!)x-30iGpgLWbkS7KR54hgng5u!R#Y-Y zV3a1YZ%DW7_!nx0+`E_y6(tzx=nc zy<6(38=z)3BVA=eWrqQt^rx^Fa!K3;W?|5JVrk} zV8d5*(BRotx&iH-j3{BLEx9W!YGl5Ym=Lm1cyUm-oC>NhMF_Q}g49)khPrf0cRH*x zl6!ADm`oOlOe2=oIfhd}pkm!g$cJb+Qy!;WtqFC+q(yS{)XW~eWdPLt>Y+z!Xz}hI z3^q2sTBf!oA!0e(;B^5vc-?fkfHq8eERRfWxKcygR>Hd^LJ=G3PzP zcQUux=m9x|$QO7EQ;{d@=3s@}-dX^9JcnQgnoDVDT}9$MEOV!}rldYw2VFS<+HcSY>(yThM95IR@6w7~34?3Ak~Kq`MM1 zri&yCo72m?+qyz5ie(dnaqqGRbO_dpBxXg#xmpyW!TcJ1ObW9xX2hHljT<*10j^r0 za?P+xQzHOvjZ>un9ys-2V`Fk+&0I2A`l3CxKsmFSLb>SPyzB(V?dENac0eBup`9RC z?N>7ya#3%OzII+3sqR=aQ`=aDEt+rExkYmlcH!VLp_iN6*yr5l7~wOdsW_dKqO6IW zO@(W`NV(v%yl`K1ai-lVYzj5pk*45rGbXZqJc%r(lK~6V04pxdT#2b8lc;m67BwzH zwL&D=aq9HMr&`K>B6GRggPUeB{VW8fBw5Ko3T2->MRju6xle;NDcp!$IW>4(O@XLx z0O7z|ZM`k8tuN5bTHq*z#v!cT9NiHhk1FNlMt4VP>Xm25#(ERv?Cw|_xK5y*lH-_C z#N6HXupQ`yjJU87FRTo1e`~-+On5E`V4M4#U*~+EwgodUuP(6)Mc#E|abiAE&s9*m zY{jx_SjgP4*DUZOeCB3UCTELmNF!HPjUwERN}b8jij*s2mi8skKw_Kw+t`0af&SEpB`uuyo4obU{BHVRDxB6aS&JUf&cWDf`ft&Hdc z0QGP`3R>L4-zHGIC&YA1)uA`(tum{HCiza~v2bCV9*lsa@X^JNGaqWBsQAFLHmN)y z7b$eVS%lJnHpxLvI%{vxlcdY~Q0?l#rJA&0{f- zrHm;xU=cJGjD~oa9y0@JTXFd_E|V8*7Kg3Q1$ZhBnf2~Xsu)pi0}f6UDyxLkz#jZu zedc@)FCC`%3Fuz%uQip{5#x}sVH5-QQrP?+ojHfwARFW(Vo)}Ka+*#JIVT$6MCI_r z-Q=1a=FhNSC%>7zg}Q1aR%Cki_;`?M&>2G3Aj6OmB1{y*E+sJ9zGn}Jl;Ki9{wJ(spcLvw{4Hj;!m9)8ZA%dbnE*W;7o|$=^9}JE(P&a zqU_Qrp=aV+b2e;*^ISJ|iqS%pJm300=1)RDqDfjsZbokArn^)vz_ zm_d?|h7>cZL2!aWn^#dt^pH5*MW!6JsRY?oqHZ*aXNb=OMJq~Vg=WnI0Wa(#x#^79 zYX6)`%Pvhil^Et+c@;+h9bc#5M5x%tLXA6XB3d~nc8Fy4e^P2++WMexgDSBytr|@R z#T-2x$ZG36MVl?M2uT(mo8i#pcb#)=&q0+iL>2EFcJCQ*uE|7g!}n0_gTsh8`xiar ztPdB?1}1O5#~yQ#m}p=um6c8czNN~>!xjxL*BU0qNm6oy;WxQd4Z(Hb zOlWI@@~xbu65-#z< zMn87iRtZ8s?QPhb`B#^Xc&|nl7GCHjwSy5mv60w0>CjyD)PyEE)@Zs@z(`7ZWy=lB zN+Q!LT=@aF7a)ihP+-ne-4C{R_pc+Kr0*;t+$hTeYIcXxPaHBhD#*FG>|rCER`3t@ zI0{S}%YzAbM+6~foYAF|L@e2F8g_Ad_#9;t?u-9$4@6QQ$FXRmJAZ5Du)}Ul#!v(ixv49YTnkZe@mxnG2^Ls?Z zzR%XedrWg?Fph6Hwr>FmRo(l1wA`67Xj%>hS0$z>o6wjsFLDLAS&<^HysT|~bY(d) z#`luT6l!+Xd>PCvlO+vkCn=%`u3+#StduJrtzM_MJ%Vj~OZ|Gj|ML3!lM=Hm>@Xql z2)DQQZ(g&%{>T5by?-@KnS-Y7S)DF?GEuHHqe+qT?kZRT2*sq!+P#uF?zP#bkE>x< z2mvN1#JKRJYPTN-2|yvrm|r?68My6p*gRE}MGFeGNDD!*tEc}?;WU_?bC4=5o}JT# zW&o>}P&X%q!VZECK&fFw)m1#45u^zatW8>QYa?1%NEDKUCn=XMiaR=KLRKibJK&BP z3!<`QK+Y8A8XCcs*<=x9YQ94eDdZ!0j+hcGnHWB^-Q{$rVLUC2P<=RM%%S()YOFDt z(c3nDHGW2S?XHc8Cv(^y7HY=bA4I~J=Jh=XGe z%WO>*u%a3qu#gJuWl)Irbfpx!1;iXzxLvCKG=j=Le21|JkIvH3)17TnurG}pc)z*r zJNkTKK+JrMu@5VcS}Sl4>?(wt+FB_jmeUC2oKfta`Y!^FOx&3w>yaTZh9OS5DcWs| zxP*w7at3m~)fq;KJ(o}x3o){|>D;2QG&LK9C!(+_YDn^nD!&1v#aJ&_1SksOQ4M$# zr3orq$*K^$3oxhlZ3B#Zas`i+bcPmi5peD6!q>DIZ*I{$q8vck7gk8Gd z5F1UEjrCsiQd(bvN&51-KHBog@@Ag=5Do|v*4CECYc5FeX(?mu>djg+t1PwgK?)KO zktEv8!A4j}U>z~$jAD`xcOPzstW!qKi0_JSLm-t(gtx+W+rPyEtZk%Im6VAMa&R(| z=2i>EcXRP)X%)v!*p<;1{L#V|bx3Io&x_4auv17tRDlR~MSx1(1l^rgVd49*ujKE+ zyR22h&^_GPc5JM+Mo^}U(m7U~gcKtZHs2?T@hu&W)lA*zjkwFPkKqnhN zIkU;YX<6E8F3BEW0BT_W7@X{Utw>dB%Nod^k9{?w#WLq%A&2w>++CSvlwH0 z-`=10KC(uN%B?u1=H{)iyOh9|@TQ6~TU}XGZVNwHdISqBH;-(CJFjU4mpC*7?b56* z0G-s_HGp{2i)mv+ex8i6|MKhK|N6K8SRX&NpH^mbRyo#-+1U5z+qcC1Z=C>ey2=P9 z4^DZZe7gO(K6L^zrE>=IrL>>?DDHvs6k$!c12*Gk*|U6JQuXu$33B5p1s?{gw)A0H zsX9CSWU}b;Sk^BuUtS-Nr2(p^Buz85MWBE~8q$@n;WNe-wuNuV0O%x3rs#4t`wxRALkYp9ZxxC61_*`)sXIYXa-I zS`xD|X%KLbURhZfmQX^<{@h~DuxdeM!P97xl4=uB3e5uO9qnZt6)CrA^frc+R0 zu}UVGG@&N0?kw#dxSWaSMG(t~GUDnKR}16>S^1SDGI0$GzrNx=?!onYG-QVF7Qb%u zEt)mSuO1$Loe1%8a^wh!Ylm>pHpiyW@={=!h!^VLmC{G+Siy2s*oWL;g^+F#CYZg`H69_EWg;+wV?AN8IiJ(N z(V(%%h|1k8&YoU6Kn55JDpM(NsM_jC@&F33kr9l2#@4XOMnbv>M)T}6OgBPs1(=KQ zvH5r#^d4kgLzM+QoyHS*01Nto`K1hNV#i1d=JASgoHZQ5ZwcJ(Qq=H843)*1m}YiG zckc@fPAta3C6DIYoWGiHBD;!<;HJ_Vi}$(3#1}<7$60tB6z=##TU!9%US>>o5(~kB z$PA0sJ*bPUtuMj>m;5Df;?`}2MHkU(A;7I`AUWH;6Oo?vs~dcsWx-5VNe!o5Rl zeJ27qjyi8)*`eXU4j@WAE$Bu;xSTpVs_ULfA+4>*LD9DKtmh(q@rXIL*u%w$$$NWg zRfZl#f0j;*(xP1(d*Xybp6r}`1xn~hDViL>3xEi#HOR;l?I}xpRapjSDffqm3Mudk zjaYNi?;1Qa`xwaYTOW@f+FWvyOWWTVMaQNN0jM z0`hR!ORH#uo6Ql>+zbU%TGK_>1^md-VQ_ey)&EoTURYIoMqO2|;wBfgfb8Y*!w+Bf z>GA&l6$qOhJEC@a=19ScL#Rfsg`KGMwrCmpOG{JMW+*RTzWn&--~N03v7PP)BG#6* zoWLxHkns^cK@{R?_LV0*$B$P5DteBM$%wFw-P%6(XKcHLs|9@OkQz7zRwPhMm`y+8 zla3e)W5V?;6+%T3QfgU6r>o3Of*CZGp2XD|eE9_lc42K`G@KYPAwMeoqvfFOVOw?M zu-Hdt_(;T|QBe_7QF-buvJn>^5*%;^Q>5XUt2()h`xNMMDxQL{H$h>(u0lMv zv5)7zO$%eOP^(r)ytTe`U>QB6M;9H~|GM@@FVB5@?lZH8;f^sD zMkWxjZ;3`)7k1k@7Ni8LnuearF&wmQhDYO2j~?l>feWO>624qb?olDjW*Rgh;GUvD zT)Ky|t-J^`G{VdfxF$te?wN+|L`2y%b{aE6#e7}6F0w3*bI$}62e^(VoCU$p&I)x;)8I!XmN^Ul9PIF8Tlz?&6 zzK0_%@oLh@C#7s~r;JbCy9IV&ATG@l%;L=Vf*ymA}U2n zP6m28&xGz}^X9a%13$HWc@gkp!nr@)%!qm|i$^QTH2Y>*4==#L#9sNT}uxLcF(?#u)=BRcX& zn)5_uIy7lbbAnZ(MGk>@qVFJX13ZyyRw;l`ax6&+6ib8CN+hqrB%-(eg}aWBq1YQR zwV^6S#ezW$as;N?*Z%ro{sEvmoPO@&){&EL;m;A9PaD%>>z?hn=03;MY>b_XjvN|Q zU0I-W@$%9aWicOSBS@%qUOLM98V8A+Se#Z6X7NL| zCX*}={o$Y0U%%+rU*_}eo7>?1&Bi8(=XVl@T^In@aN}S&>LwvR(2&XKs+Cbg5wyFy zGHLJYvOI9!a5w~ll?Aq65a7H?ESMmT)=e0kEY=t9YfPI>M5Zc5qeh|c+xAtrXPC27 zdv5bN0u@hy{FpT6B$8U&iB6LkHlMriGgy|~#memlsVWj;L-Kx8#2+e2liUy*1^Iz? z(AIH3QQatzFq9D+8}GKi`+Pb?Sm|Lm;-*+(&iu$QV-b>vS zfxz5~5`gTG4$vBCz;*iiXiF#NfS8m8=1eBV5OTCh*U)|dqvJ?#c06u5BXny4NnD}g zPv6tEo{OqC+ZIMAHeV?4_SzPgzr2j zD!W7)Oh_NF84`K_0lMle#L?K3X!*RvIZp5v35GCV2ng@zW)>R=nL*5x4JDqyn3)IH zDPhp=6s9w13u?q~GP-y&#LdDr^>#DgE7o8>rwa$PGB2Wse=hI9c*y6OvoW zs=GLB(_L_5X=lvd8#DAxafi?cnW55CfpZxI^_RA zF6XMcv<9GolN$vhbrJip_igOP)48&b=Y)DunAzNLN+4nO09Ta6kV zg3oEwYydKbt0852=>qKwSf$pa^|kj!+nhd~-AKq_Qn>}#FQSBtCPWva9*+p5`7vWdqpe$QAZHNeCkQ+;s z@X@8M$*H22u`3~1ArIk|b#1RdX?u+6oBIe_gj@55yu;>v*)d9YdXxdjhy7Cz>-ZST|GD17b9+LuRP zk>X2gY>1||*Oz$tVGoY6?b|!;PjQ#*2@i^+ldMEI)XiwZebtvgEkFFrtG&nj+y3|e z7{C4;a|oqn?32RDTp02Fl=YdGlnWEjlEvIZ(!nU=Mu%+%2q~jH<5drAk{9qArn&<0 z_d-lD2Yd4^96ZJn?HtIEMHN&H6BI3o7CeGERv^X<==#j95-603!x_GBHm39R{n${# zFcPl5_|P0x)!S+jaTw|zVKKuP-hKQs833zfd~;4>M+b>TMCJ~iZ0xAeZSTXMyKk0x zD-7t`Xm(L(-rYAUpED$ih=l|dhWBuNDWsu%?-GlXQzQV+q_m}*)>6;^fZXd!%Hc1O zWO!!bK)o>mk`mMIxR!F^k-0Lz3Sdy2y&2QwWT_)?Y=;}_}wmsj! zPPX+o`trisQ=8SM0C!k4HPT7Da)mlrtgj0R$4^{QkYo+n zPO*=eCd9pWF;a14_RqX2CbwX0vZ+|(F;#bm{52v!G zKz})WpBoett@ODBnF$}Hpi?;`JTZJ)(Ag^1328r>t0_E%RJ}4HX*0hggdG(yD7ZO1(;*c$~7uqdY5v!nnA!21D4trcnjb- zSh`BMuy<`v@diTGY_Ik^8Q^DJIUodPITTS%vV$ncqa)y|8qQ|6?@vPg>I;uN_c4wN zDa-`QbR`2}uVoJhY1*7`h8bm#HE&CQ{6=f%ImhmE+dOEvZ~J`T#s;Bf41QRJyNco< zV)o=bcl!b4r!jeV>N^LBjwi1a?;y~J)_ZaLA?Wy%apqLdKla-N%QuocwyT_o?#XxE zEJAKtS$uSfdCvoZ;-usCa4Ym_1lPJ^+AS^PMAT>3pem7o%*QGe;Evc%GKwZb6IYfJ zbZ9iFHHMVK>8RK+w}BLf#5i}L2_CABg7W6G(L>kQri+p7PxGM8t_>(g6{y|D)F73$ zamXs%MU<*rgYn$Mm|tJMEd+|)e2g7Pk2#*x<^&{m?tMHT?fK2vC9ye2BH2L-JmUvgnH{Q@mOAdh-IB$e%#-G z^?ma(joiYA56rlyOs>UF1? z=gAR!|D4Oy^nsIDK69W-I=xTc9 z-6%wGe4oaX#6?9>=vtRWQGbL$F3bu>aVAVK>kZ|QFPM*FY=?4{kw6pV%nsvH)47WWXRDi*Na?{qVoVyKU*yvYi6|;DxL!Q1pDid6!%8&?U zUaAtqHE=+jnJ_1zDuuy!r*I8T_>{PI=B`R4V~)h1^tCry8!b&%>q4QR=8URrWaTsO zu&IISTzhN%F-!oP0*7@gOv& z#G4IvMO|{r81e!wdTEO+tIC>nt)LNdF-nUupB_80MffBZr`)2Jk{+{@6>M8;YhP5^ z8q?o5dk&i}L7EsTa|&p$9aAz;o9_X&6sk7H{_PPsJ)1>BRLczo1fKicJ({!5T$!w3h7<9^;V~UaiRNxk zr%?FvGZll(eFyi(y4ro`WR?WqEYA=VFgKFO;H4|7oxwAxy72Pg8$C!f{* z_kGiSSMBE4Dsm}-4CT%MJ61A}4V6~R7wz7%X`Z^%zE9?A8(2ms z);;3Iz7l&xXJ1&_<4Xv-vTpCT53{k`<;(JA z(U)aexv57Ww1;N>Gzi4xom~Z4erD48(pR$~Zt8Q9;MTHTN&^nsrrYk`mDW{TEaY>V zbCbR(g3B6PQx1~%Z^|x3aBD4)H%C0bNPO`OJlHU_@B(3S%sq~3QR%cY<*BqlvSjpD z8|oXUL~u7lM+Y~{XlLrL>LC=&P?@{^KkXmpZ5R>r*WjfzR zY+(eyeDY!Sv1A4?#(OcgrCPvu*o+75FGpi!hhxOl6Bjb|NYaxeW<|W@5yO z`a55=_b!}}z;#{c1RG<3(oY1O2M#5xP_WJC*!QQk3f}3uJ|IfhtX@n_Svj00 zTF^vu*tky3*JahV3O7<%;MfJ)*T%xxF%ANeS)%vlhacKwMFhHH69_~$C*-+{0w+Ww zlA93ypHm%qVX`q}+sCva5vr)h#=QI7rkOhnchTx*2Ur{&Nx*!rnxs^2{h$BSY~caxCAB6_Utg`|rts<3J!Yo}4yAW;F+T$aUzvf|g$7HNyo19KpmeI?a!Nj($@ z%YDZGSJ#_xH1j?= zStFONCt!JW9HTY3YfLZMX;(N($@lvwng+(vTZeFvuQS#(gu;L}J3)HF&gV+3Y`)C> zoc1*xn%*e55aV+>V+UHot=6>MRxg`LQ0~Y zsY-;~0vCig{$0BG^-bi(%=sI97XNRB^*#8%$!`NZ+BmvoNG$SEe+%hb1#oErj)T_dyOu z6XgrzSON1G$Kf{D({q8Sxb(J0??8V@TUNb0m3E^pO$wAVrze46T)SJkNuqZ&JcOS zxDS!$M3tXD?Rna*sa8Z%aJa~n@Pnl(kUz~SxRJZ9x-(s6TkFW(IEPDQ5B7{L=8!I# zM$s6c;#7ACqc5|$EIhXYaJZT3J1uvNPi}heKSsZE;x^hscz;bVq6*)X68)PEaw%M= zbBF+HdYiLR-pssygi+aXEnsV8(Ci9{U5sq%>0m3d32J)5Iqq70;Yag-LP%^nCBmn{@uSOU=ets8 zj{GL1RIYJfBaFyp8`>H2=|>jxjbRMLH#74M*di-ZmG$lNw|f zV+Ktn!6y+b_7zEC7Y)3JYoi@>yx_@!nNyrKZsg$50R9o1R4GiSDISdWjsVhZjsi2` zTIi@&ny~5ClgXtMo#C;ZND3>}M=PF~hVPFihsp)~mqXhaH|aO+x?ECTvM3n-1aj zjAgcnPSiw%TNOxE;QhvE60QJ&^*&Ov2%n@ev9ZKf@t?NlGs|Vh)=DXh>1n=b26WY9 zJCB@gP&-WM>1!q7r)rg+CIilZd$5?KwjiFlmP|PS&^)sB?8MYYq?8&t>R;PQ4$op= zF4-b2ugDe!G_}%O@!1ojnnaDw*Voep06{~-EWXZ2-a-+&A9pfu;KN+0tzDQf=GfCe zcNT%#O_-YTLK_2fQ4_&o=R?}9$q}OBVa)ROnQ&_}1IMf?r{KY{r3a$fzwtuXKQyZr zxkWC>vH2iMQtlN42h(QoVkA(y%8xFM;*qxSnMtXT{``=3SG_B1Fg|2M9+Oc%Gt1vR zGZEQU0Vnft&n7|HDHr))h%tD(hFeyhu<6}mcIrv2+WNq>PbuiUYn5zl%{faz3F<|I zDz^3MzWkhPdDBDtXKx>ovK-)E(xwAipzY|{yN>(urKg|W*4<2+tTlapH3}w^$RaNa@Yl85N6&2i5L@~Dwafx52tWik)#f(W`4mPS ze;Kp~gb8cO`gI(4bgwfLTh`PNFpjP1=8;2>{_!8jkN^4j@jrOnA~nt6Ak)jLx*q*# zA3xgdhdlq){6uxp&-47!pPjYqV3-GqP$%vjfn3u(e^D4YlKc?)me!3)+Boh^OflDb zeyuqj>PP+d(T-b^+x*J&OTx(E4%^Cg<}@;=>gZrDL}3X~MzIrv1%X@~rD*zG84N1Z=FrqiI*SPVAojHa_6AsaOf^iv#^nxTWdaJ zosov<%r|bq0}QfxPM?-D*v%52Y7k`bzgiQOhc0q(49aTcWw8U zheYRs$wkGHZt-22D^qZtyq(a?^l!XAu$R(mX1#c|&XOQ{CB3QuIBqgfkvAqmS7EpO z@@4q>r|O61jafL$__{RCmpT3<7WVI?(hfx}ah#s$#VvhjA7qfngB4 zl9CGH%$xQxMy_)jML1FqV60Fc+=*lBj?r$N>Lg7Yyo*rcKH4b28Iy3S2up8m^h4Tk zZiGVQu3cV65YL4J#9cT>>rYOor1joNKn2QJCUKoC%G!~>9whB0aI z!Rw?3-NDWWh*(=rM@-t;K6Lsf|r_s!4eoHN7M`Sf)%B}T2hJIH|^GK*<3S74rt zC+VeoRr8kUoyYo4m+178-f~UE9Uq@PA#jfl>t(TKL`rMp zIJTfum_>rgWoREPok?AUHz7q6A$5vIlgs7qgK%YzD!H$TmDjeEzIN7D4X2(E$%C8} zhAwIQx;_4iep@zeK8d}7+Kn;0O3a_I9)?J+Xg$}p{&}8IW9wadSL&sU zn(zYQs;@NQ>D|bwARFMrRg0A%C}#)^Z_xs@?a%qN=Ob)oRR$YMkSGGEmW*0pJ5>_F zj!ux@XvD#-i~F+k`Fx(|nUZa28+v@)toPY`nI5B68&>aJ>*5qVnI!~IP-l4 z$pSx_;-~4*j0q3nCRn>b#WkrnhnWSk%t>xGzsO_GbIw`h!}C1N=bHutfeU$@D=l&* zJ7$ZQhX{*rq;AcF;!>w@O2s|_!lX^)Q_vqUJIhDs$c+zzpN1OPrC!cj`MB?j2gFoA z_?;2^ErY!sC&nq@e{px9B3?_(@BIW@fG?O_L~czxaTn{v%e{#X$r}@5%3|}tM^$I9 zQ07b0i`|LQp?jhAGV2U2Ud;59Nt|=$L3DJ<*n*28iUo1SjAUN+NPB2MDyoRo#~JR_ zx}eBHO09_mKt;n0A93G#xF_@_KYsE#IpoznMt<>4n*dPW~5+jy|>myvCyOAF+}uP5rVcMeX#ij zMB_?}-MjF|LBWCwOA0`>2>4fcnnMV`w|1!ZszJLW1bJCgzi1(sc0AYyCSXk5oA9;?w#%N0Q)nhk=Bgj4XOtisODSL@P83(BHn|@ z8w`gQ$;z1;u}NoaC=a(MR!@$8gqyZQj}PsGSRpP5c~9!*)xE=RAu3zOKW-?TV$IL< z=-Rx|l8JGD8-LvI_so@^C!g9NWZpk0RzGI=lbas5KaS&WX}^B`HJ`tNY(V8uU_&C&6ZoH%w9w8yL+@>!&gTMmhz4I*nSmw@d=hvJY$=i5V$zW23-i*ahNyt1lL%MO`|Z|VobNzH3!O{&t7Uf$xeF^l{me%I z795q^8}1I=QqkVlKCWC6n;;&K6lI{x5!%((0u5hjifG5}{95x-XYjryC)j?}ucY zmcOQdp?Q;=z;VoG(el&?8jTY(;H)O}s9n@f^cT_}>=oQd5Ptea7-m;tAS8^r#u)kO<&W^ zCO`ne(cEdVbZR$kO?p8q!0ZIXqnn-cr4z}$b7LZQ_jo=l%yM5DUWH0&in~8)%}8s) zavRUn*ILV*k$@o>6X#A%X@mz_lN#Y9w22V>q6C4Olt?h>NJjR9Wynr`NlM7&2XP$` zh9(7Vd6b|TW+`LOY0D9%s{p`zwBEJf85TCbGC5R?)R*I%n9jz^w7Llop zj-R-M;fe5T6$(oWk{2}d=wsYQ%YzazG1*HcOm=-<@UOJo7Ezp1S=bI%{Fd_lI9l}# zgjSzV@Dd`_SR$5rWed&IT}KQ7^Kny;FiW^7S)Hz%jRM(nZmqW)%boHoCi#4vZYP;h zI&Rvn^~N_6y*JA{b4qV_=qtA$l6cONJV3;nvQN=b4t1z3k zcrPc4eaU3uwWL;K&=BhF=pq4-AApF6a+c4vs69vgqp_qM=W{)u6RNa0arjiczsr&1rzQbzx7W7gYeQN+g4n| zE#Y?(Rrbrx3_T!?nlx$cMrx(@OF`;@pLqgG#+qStfy=iVsC6p z6_a8u1C)f{t(_Q`m)zH@UkyRnDzI6_GBEgw`2JFOcNTv2b$S$yp-1daG{M{U$7Kl% zkhp~hqH@%zDKqmWanxV{QUUyxPOuZ zu87>}_O(-HvO!+OOF#ekY+dRZ`byS=Wu}0dQXw#fr7W1A&W&ROVmMW+^dIJH`z0h@ zF22DeTTf8oIb5q`;!XFzCPby5E!H8dtV`*f%aa5TrBFYxD_4raKnlemI5tI`G)5oV zRZ+0wuKhFGjrwqI{#+3+zaPQii;n*-J59wC#kiOZs-U%{W;;BB+R#2^9Ie8P$W)E2 za8S7p!5mRw%0_Ag9y0M{E;Yl#afq~{1jisJ2Uo?$NF`Ik+yaM!TP0vDja%p4b2`mS z<)`k72qD^J5-n)O_U>S+dh~Wf_D&(<5JBeCB9=dzIZ@GLY7`I>Y^_c4$aF9dCqsqlHpZ~Durt;pGMemM zFFE--N8a7x4zE)vpZ-Ky^PVLK{5YS_xw~jQ&=Ab6K@aDWeJn?_mDbFH+7{ZT^k6upkGv$AmM%WyqKo&ToSz z>)E>1!xCJkJ%9ep{|#f)e^4I6+3qW8WljpK@sm_dSeRsUJtf{B1 z{qe^iz3b1PKY#uDbuPmY^eAQ_82tC`CFIt=pju(;zH1xDXwD}zQv3x|lGbYwcF~~9 z{N*?{!R&dSX|CzvQj7f<<2WkWqp}&gOVaw5ZX3-kA)*3RC{f))FlwWJQ~^j3i|rPI zWz)~fV`0oSY}jyp9XKL09mjr}jK8W(&0FxDq!Kuo>T~*h-iO>+D-mEB#2sl2QjV*3 zx1lY_MM2D}M7FJy86jxN58R3YXe7f z%x8)#2IZY4i#flD2ukcjp_gjwkzvKLY%r+YeD0Y>v~_Rfpa2`%IF4rLW4cFW*?S)! zqz%igawp9{KIGPK%-LJ(I?$Sw0LiYHOI6(hAaN$v+tGz`t-0oD9+{dtGV`qujRye4 zZ8FF*1Q?<<%@IRP_#0iy&x4ocrmC{3y|J(3fN*BA`aVWKj*mNp-nF7NM|2MFn;r7f zOtq$L{Ll-Cv7+cH0=+Slr8$LOKQ5{T$zm)5V;n+};H#%oYBs?`2);Pt`C9w{2i|0h zfKg-Bf**N(MBr3`C**Lsc4^`)<0w|)5*2dJ$ckRlS-J@K-fy>CX~=ZZ0z5L+QuczE zyvC1YA|^}M)>#|sePc+U6l*@nLEW^qH6g;!%9n_Z*@Mzam0IJ-Ip+g{BwFkzLY~%+fSl96}#;xG_SZwpkKh-G)AZ~;JQqAl_h1!e3&CqaP!znzuY3m1qmbK)sxxFw4d-c3mWc<_#$9BPRVlEG@MsQuVto|;TMby_5>ARnd~ z)cQEEL-Sh8*-N07SR%KpP`vv-UO+60)=}MqBJioV-O_umoFnh z1#<`Lv{4gLoSmXrG{`$zWP$>Iusxph@j%%S+7ISZaFmXS(-9P5_1xUlqW!qzutw_) z5A8$x(B3?WaZ$ubk~P72NQZ5t^`-?#{F6U!V>D@>A3u)ckls-T@c?I_$0@!`!{yRK z4@q{_d#6Vtnz}j3AsB;#dEgh^9^S~Z6&5b^mg`p>g)Z~o8_|Qgt_mlW_Dm@X zG}e4$b>EdTg*z*&lkhl>(GIZe?XJqwxsTYFrgi2ZO`QCU#bB`$@*(-5y`}W6=_`#c z-KP!r&0x6wE1fxNXhxtnnpz}sbzYE6M>OezBn(JpKeKn{jx`$D#0ZUO8}`AKumCCt zPY>Z^DEjx#CU84$+V7(`mJ+@Qc(YI}cLOCs@7!8xeZ&KW5!wTXIQ zV3Y*`Rz#`0jTT!0w*pBeovV->smxM@RYyPWAOCIr__)>tS5I!cr;RLZo^=~{=^Qhq zynf(ma8t5~mP2_%<>5qKx+EC?A(vt}B}#>FTt6|+Jc|MV1b}b4+K_zFX|PbdL{+;o zfq1O}@hl5?0p$DgnKotZtcJ!Em7PQbVWWx8jRI z)wN5%3mSrwqv>F8Y15M!CX|Ff7dS^SK;WUe0XI*BccnUa7Bz0Yz7;r1^E_;6YnpIW?tA@P{(vArxqURs$nfH;M7uRS^6)58;?e-! zk22~!HgR)pt&1ED$fHwPx&?3~f+;5y8&DTqmbfKvKFIHsX4ohcaW`#Lox2#NZ<;ii zBdeaEg_Q(CE>S{*RpMGPQqgHLF=w=qC`fC{bJ8FkP3hJpdgGtZQ`XA3x_-Wys(R8M zYV>#ZAZZ=qri1%IDaZ+jyz~=|_vTHP=f{DhF%=!>ij(XFar4OAN25>ez~yfJ>1&FK~2WmRk+qEaD!xPL$r}*l;Wcf zQBk=PTGQv85fYNw3U19Er7$YyG?BtdAFZSPMtN5$#VW}xYC#sO^!e|%L3z)gYc8Aq zU}-vfivi3pu+{>~G(Z)da+xpp5O$GdVd<;~_f}8HgTMso!Ge2Vh43wcn#G5jv;g1_ zBtTGXkTld0gocExtrW&RVI@55X^dRAYG3So6!>a$~ zHBQ$2VlxxMEMU<_F-V&Lteshz?w!3@%YnthG*{eJf!~o_!k9?3B7x|r0mk%Yb3M-I z*PPGHwALflf<@TNhlp?(iOQ9cEv051w~QWZiK5GJr;mib8N&jL*;`mfc5T3EZ%ZoF z!T0cS+&(}5==U3v0ewrfg^`DV2*MIHm!~mvAf1`aQByl`&V<>~wC!AdmltfSR6m0i zTaLx_=|SbzYvpySJo`=76AZ?%EB<5=+zigTs_o?ztT!7-s`11YX6F?XmepENha!g> zh$4|dXK!Q3u@6W&wAq?ApWtyVA`gOTLIt(<;)-N`YZQg;UYtHia~${MI7B3N>ENY| zqK@j^=Ikxo5VqxK5|j>nN!o;y1j?q?0iYj&WNEE+xK&&c+*$^DyA_v*RGt~7BZI!& z&hpRDlTib+XS5n`@q*cCKkheST@h}qAyK%jJ((Zj~h!P9MnZpY6}}oYEdwL zAv9Md+0O7)rpk&10@L(2+gUE$FU8onbaWDW<4#zhc4nvYY|QLCOCLSEKK|wB zm)iuGB@hQ1R&OZXp`rPz^JXN0ZuZv4KrPDw^%2E?EpW50M)JRZy$lz=t(4xINN-4E zX@d^I`sF~K5PAs%Ql?RFu6%gN9izbR#6NHO#6xVPSz??jXaSU#>=%Ankjh$ktGmWMsbjun;| zYT9ooUbzRH;>%uzj|`)5w`a^}x|OVbTs$j!NoQ2A?t8VB-tHf91UBZJa|Y&I;^ADl zX_M-dEF%pqQ0vi^u+&M!(@i|6bjBDr8^iYnYj*q}WhJDRO+y>^JLy5(P?(tME7Ost zSjOSHnsgp*@Mjyng|(P1XoxTu+r6sK=R9defF;yexpfwQxEB^-18EOpeC7aYwpOn9ay2tLpCTg6wW$$ z7k%@^!YWtt^^o?_kI$h;YfS;4V=!G)gP2{Sz+^zb!>|`dX5}q{K!>e9=PZPD8wVK| zkyyni0yoOmvkBooCz0CY0q1OlwoX4%I=3F7?u$qm*1J{?mz?6=E{&tw>F0Akf056% z9t7+U`)c7Ba31cQEjc%{SLCcd0Bz{lP*Gap0lA(8Su^oTqErhdKBbH=tKMq7lvNxM z7*T?fmyZV0-ur!w+c*wtBeA1I9TVD85zndy+qo!8_D#z}*)S-4f&8i_w_pV$%BXEV z!YT*>yl0^hj>BR^Fm-(6 z+-7K0GWHez%$Tue;${oxHB;?Tp}4{O%#V*T1`ywIp7Xh-0&wq%g|8(p;GqNqL)zv7=AY4X1%!6*i@BwxZr{m3gJndOz|b z&vQ+4S?jx7lVMDIX+k9gQ(P3YHRM)3nZU7dMnS@j=M+_~gY5Fc<6r*#E9@kx>7NCaut|TvdP?pJ-J~yPXf_(zX_hOXYD!H^x ziQ*Ms3Wn)-(Yh=POa*c!Ad;AngW`-OOpT>J6-UO3fCv0KauRLf+E)}yMH+9JUqn=; z7OrS4Q<#Q|sf2>*1u$Hkx$GMa+uz`$FGdX`*b7qjcsxq_<90uac^_PtjKLbMwTz~f z*Q0n(*Ce-RFwwJVueiT7>30#czGv9Ztj=eyXV`l@o?{xEzmo@M*!j>zaCwgVo=M@SM`*}I#Tb_ zkL1oc;nHL@EGBK}XdnH!>+x}O^0nysxIJB;QzWT%0u>c0C?jlH2(_E`4^{=jaY<&$ zB2l)>U%y(|5+%5_pi*M&GkA5TG?H}b**mpU4qoJ<6etokZHH(ZLwVdnm+C1((Eb8J zSUZlJss?a!%RDtRv2%P|fJNl!TET`YgP@v5Z?~qmK2)fSrjFpIq`5nLgH<=TA?&-$ z5?F{5k~*D9vV-3GLu@$g0%BqRRdl+5|E{u#Tta-U&rCf&1c1@1r_>nRs&GNDS_(Pp zNu59K>zB`;<;t z9f^GpH0x`fq>7+6$@kvxhxU$=MOs4HpEo)oia@~Us{N^RO^Pu)W|(1;c<;tVcEZP?zph>P!6wN}#Ps%j!f?*PlRev@?J?yt~mJp*p8 zTyZ~FyMb1qm`rU?w2UgC5tV|1y_<8w(kGKQ2_9k=LOKyLxa8|Lejj7kEALH3xr(Jb zck+DBh`8VBxNB=d)O*~j1~e_kT&kmsdhbb##T!d%GVPZ3THO(d`NZ0zH|}?CBE=tT zaB1)iIAK(l1@1c89%Sg@4SUDo{66igKmUi^ z{)dl0LK+H}X~dqfvf_0pJglGj`0Muk89856f8`op=?U4dGquvUj5>|K8Q7fVvo#Ur#;*0GJXe45WW zr2>OCqFb&VRc!?{93GAxJH+ovRH9*L7VL??#0tXV3i$_01 z2IR{G58)>TMPHumb^=gt$iux-W%zhpZSLr|{uS@yU{3MXHHX62|ElGkghaSS(juc>`x5 zBShbkWlAl!JCex2HM}j*0FQF6lTxJfCAcLZ6v(wo=ur6Z(@uLLh~{G0;*6Tbh=5Fb zB>946zLoNzgk1xtRMO83lf0Z=%(rbIWmQnjyF05$ZyiF0NWM&aKAE943uA6g#Br&d zZ|>Imt4L$1iGU!w_0>99bJM0v3vNsmZC<^W+#7Xar~-gTz75h5I=GLJPL~~bA2(@7CdV=s*7=n4Dd&uC(T3fH`-glC>KP%fgYJiFQcj^$iO`Sq$<*ZH z5vWzeVlC&>4RWGKNEg?fX8sg*lbDBVPnQ+6B9U;2F07*smZQt9Q@h`ebsXp8`Fwt@ zl-3SDKBcu>rXgaqcpwM;5JW-l`W-1wo-qgb}&l|qVNcALfjBK;Zcj*8|_4+t73l#Wv zx0(QYU$4yPdi=8IKP6Tzu{*RV^jkniNjB*tAkS-^)03s&`nZpNwARd*uQ{KO^t{~; zRmK#2t5TNZRkHWaByy#AFkb8X3Xq87h9>h{=ONO#R6U+)PR&GnL8p3q946_0SVT)P(305+My_nz%r>o zv4s{b)T8odl?;4*m#c?~p!^T3JR-^7)riz8SBDgRtgkTHXP-ea!Cz3gwJc9bIg~Ha zjMi{z!oQo~)mr)p7EW{8;{jx0Yqwiy8&xgpP#rR}=ku$NY)wWEmmv4z=_sfg(M!7~ zBb{^kdXOG{+_@7aAyK#xV82VZ^$|Ozv}VppX`3~ga<_E@ZOBmEUvv6gnR!F&W+m9t z+~;yimKI1HT%qjNy5mTBxg(J%u8KlcwIRr@l*CStfl4RJ^@iiVkxQAc{V`?VxLVsU zhwMUEGfbEOVL+b0;#H&4>vxp3k!Nd?IzGmqasQKU|B)YmoZKo@FL~%70RY(vkDi`C z&wu?N&-v?*1C$dm99km5cUTguK;9L=Dao(?i`d%-ZSC}zU2X1p9ZvbOq}@J6T(^$% zvUmR}K!uSTfZ+L^vAG7O(pB!Kef!HhHy4E$4wS`?R2O!_)m7VpPR($X@bs0n*jn0q z-iK7Q1H(Aj=T>}8nf>j>iY6a)F@3FY=*LGfqTR+#xSLtJK^0$H)2h|(6vK&jDEGh3 zrW*6RY%N&LVCktWsaLlU$ZHa-x=_^>voULN`ei18^LyIj@E5T@f3wVZQI%XtdKCf+ zPzc|10aqyW`ZMsFYFSmt}R8YvkEZc4c*SD^t&udagVtdo)+tA8qjRe^ zMplx}?ci~+jASv7HQl0cP&Z`g~VJDi^pGoewhBz*jkTPJVk0At*WZ}IbVNE$$N5dg#dr!RU0`EZa zs>PSIp0IR0azk<=g}gW6&>gGQm&zA|e91)evbqGN4TC9x{EBu9WFaZ8a9t?bCkbvt zxT5a{Y|W!G$JOZ{f#`wj~+OfEy5EF9kw zMTL^Kh^s&G5ccGvbeC|7>kx4wdXhY4?7)1gOMl}0PoSUe6!)sqK zO!n5=`vi&z10@+z_=UjGl!Q_d=#;Y?J3Er~wFX--W#sqmCh_)w75N)gC?3gsEWW)H z*!xV9UNbk!staK-S&JYqLucMsO(Ua0D~;Vm7VYCixm&%gI3i=(Gl;kzg-Z}Wo(1SY?dV1)?A1G*B(35Nt_P&O`1*~UJ;ob228eP>* ztgyPG9c~e`l)aHxdr-k}qiO_%*XRXJl!EP`6qy^f!71XNk{;sA}z9ldwHB zl^Qc7$O~eeVOeaBdJ5O*$G!rbS8-mx$NARU9g=tH2#EG*c*sg^t#& zx%+QVV(-t1$v7HwwBc%~y^MBz0xBD9D-(lEyTBMwa(cP`l_&ik)98S{S1VBQ?zq7< zC538J?X1dL!l8Tmz1-{XyU2I%`Q>H4>l)iwNhv6sG(A*}-M&@?sGXzbXg)rleESv0>C`lN zq@rj-Cuqxd2B${r9>q$xgkJoDZX06AJ5{mwW7_;qIHqCm z@0l9-CE9I)T}Y@3nR*~=1VL?cVQo74_~>l_&x-?rT6Zsz)o}-Oc125nlMHXXW$guj zSfzF#3zsllC1y7g{&KmQ(It?*sgA+`&m!~GU2w4sf6*_;%OMFIdk^@e#Oo}V6O193 zD`zH7RMMHEHBA+Y3s}Qim-$vM30w|tv|N$+yN1a15^Y`QWD11nSwr51oK=+HFp>g; zReI435AM~x_AZdIN2J&*w^1O}h}g%Gmb7EkASZI+l1<1Ypq=+cBXnfKRWsepys*53eF^O-@KSwBdq8@;GDg%-i)t- zrt&Tj=B<`eO%y1|L`#LFSLk0ZP|>Py?tooBQJ}9XH}p=Zygh(x$4C^gUl?k_yI9G6 zlMW(6C@R4y)^vG~;j)nAy;sWRGzbYSc|Wyx`W8ki$^)r3Aqz}(BTZK*;3qFi$H~`w zcc)q4=AjTl+$&8|$%59$X!pn^?aW~5cj@;wj@J6$yh(Vc+XsHV{(IGxgfV-9F2Q*4 z%J#@DMxEEItf&;?${>cuMHG_FU$NZ(5K z(cB12d$QuHw{*eF0eaV4Suqj3Fn1dOl+M+S(Fap!Q7vg^N}9{^x_l}2iUF52SZ+`Rac)JDb_{JQzNyPNwzvl_ zVYh}8)3s)a?j`A38fv>6jYS5DGD(1K+dYLsZ5OH6)g0RzqwNdY+L(-F!ygt%_S}6aJPk>YxEBl{pL?7utGY`$tRQ9=}@M{8XL23m+V+Bm3n1l-nUpyRWo0KKV2n>nhv$eeGm1J*DSZaOY1B>lh<1L{4#z5 zYTS!BRGZKeCE5fr%$_0NmNSJhUQ8HgTp1?Pi(G*5&pNpmH_06vOCNUUfaF!}~oBK(L69dCp5aa|{8ti+E9GYpW+o2p*Z^GI9 z^*Hci2z@^XA^~rO*>F^GJj^W&P=t+kqsny&(IuekM7gPuaX-r;*&VF1vLdVUu+xjV z@KU@0!zG6*pSCO+JmRy*d=Q5;BQZQZ{w8L)>bYsxkAJjNQg#hTSQRkdLn7@6VtD!@8T-+V1DD-1?q@|s>1~QBD`Lwy*SM7C$4688~ z+l!=(0)bNEYq*oAhmjMp3zq%P@;7zbT8J|9`QA_EWtIvTkS^IZ;amthoSpEYxG4Zc zwH=dq`Xmw#<8@|4m4{MM(qKY5sL?igfs1?@C67){32;Oy2HhgI&KUV31mhYkz$0X< zrQ123;aSuqLPmw%!KvxKVNe3Df~KuY`=9^(e4g_$eL^?43RoV-gvstqc(FBkrR<`L zz38)p9|bSpLJ@f*7^~<+F&*f&b+FQJ?TuHG)61WQPAY1z4*&cOW8x?`v1hUY72rw5 z8Gw(FYI&+7C*GsA z$qde4DFN`NNpZ^6)S=)x(|Q|++>aWRA~?y|na>UXkEo32_x!C393BhuB|@x zRjbY>a9b(7wB*Q6r4_NH0at@fYnMbLa7#8owyOVzI7CB1W2G;h$9gOk+-(}6a9n&4 za&P?@nW?>rcF`uHsL(ZbYZ4#Lk9&N}Kktj;1UGhvyhguezljbxExvpw!L*EA&@%igU3zA9eUD|Ss)N4%{n-7Kye=+$c&ABt^@Mk ziu?+}F+-CC4%cD0!7!0v53xcr?5zo_H556)54O<2GbuA*wJ z$ZAHaj&dek$v*yNlvuL6qG;Wr%wt%!-x(!V@IGUpC%Nq4YjZd3uDMoIQi(NjUlI#n z7gF#xu~Z~YW-~ILs|GewS(Q$a_s(6BOKo$sje)kqj1J|h99gD4i7tE+%;`e0{0(zc z-W+j8P$WC#-477YF9`pimsT77phOIPuQ5NFkGRy9r-83D`S{D|J2o{dGmm zqSD(q(CpSAYT0Q`+#5ujV2cpGyfdyMTWu#wf6X`}6XR#~#2J-W$%2|m9#eA&w@VPMV zZp=3kBHffaMY?GozMdW{VSrrFXnhRgjJ3ita?>7`Fc?Y;76ILwaO7ZAlZFA^`haz1%w-~lQ^oszm3Bg5O6msoI?tG=D9*8*Yxw@3ku-3d;5^l z57PJWrZ)k#dWt0nDi0DBIqOcugyVNCV+mPrf~OfGBpw_dwj?oP@z8~&6=6}q9D!vJ z;nDSeJO2EZxwGq}-L?q-z zbqPQ%tb*y9v@cm}w*nkQ4YGuJM(K8CRtdxt3HIed=!u(di97^J5p{QM+Q+Pu zSoRSjUD~c#@6?p!{b>LB=lI9xjhX-Y>mUF7|Nejd^RJ(_qBf@@>}(h*E5nE}y;Yl% z=ok%RZBscuMvG2R-~P%KW89_prVSbjJK3ADA_6UOi&=Iwd;wicI0g{upNDeGKJ8b? z2y8p}{a~Nffrq1-ugNrwsHUXJUZL+ zYt2cfn$fO|X~(-d_u2_%95hDuTS`x9<=ITS*u0gkp0$Ay|stTuV@?JPCL_NFW%97L&sjU8UO}NDp67dN%fXiMMM=j*e|f zQt_a#4SNH^cJ>Z;f2WPn&R^nbz4z*4ayMZblGSZqR)^xvzo=aG^mnNu8jHcB5X?y? zKeXMBaqA8232H80y*%=WA1h>0Y7>)6?jC_Hs3qsTGP6O7e$(P?uTnzIx3u8hEBpQTuYh+;+9 zyq}R0)R5@lp?N!Q%Kqy~{L9Z-WMtH8Nl>mTon6PT>ftLf8ELU5g?BB`+9VSw?Pzaz zcqA8+8dm9JF1*M*dB}^AKZR?RW-b4~p7XT%pj=Q97>x}3Xrpi;Z{_mtUOu#?`OKK< z6Zp_xWV^bHhAynXV{qTD;q}VSqvDws5BQk{QWqipx5v2vl==GU?)H`p0l&Y43uY|u zAZTW&9$!RRRpF^Cwy+5!rnJuAz#V$0f_^ z&vTt?k@|hzN%colmEURqb`N`NvKE!wGuLA@?Fu=^MzQjIKFCj%=(=f2Se0SedR&3L zNg}U=6KhlH(z;{-v(d-D8T7}}ng*Ku@X>gh!UEZ0I^;*!Kkn_v{r1P_T~(C%dCrJ;8YYShur$L)4MMxk?>_;J^!9hDPXxrDS~HaLmf6kbieiE~oxF@%!8mnC4H&+|MV zzbJq8BiRMFba?hcp+!&v7+3fSM%*#F-}_XS6$Y|^PSkuKv)JUaq+_LWsbrK&>1H#a zcQ(f8n!*Xx%OsN~y(vfLa|Yy$xZheI|2EbhL~G?*fz6hL6trqZGt>#NN&!!V&1a}l zq~Vjfppl4ap?yBeha2Ufdjg;2=tZrziR0fkT2Lh+P zlSg>W=@bp)SsJjtkp-q!RkP*i6CbN6lf1ajFTzZroe{A!?SU0`Nb5m2Zi=FVF)LrW zUXG**9K(9j{31X!Lq`7~X+20;6oeers^u2b*ymMeX7{SVz z=zIagm3VMPhzS&@a+7Uu6mtY=M&YmHQs@hkx`ff_IQoD7asRx@TDSl6pYmV-{PkQx znN8)@_ux)Kn{CJ`UQ~!Ah=`sKq4fb})}TZ_CVL+`y7U1uRd(|^ZO-Rft9tyE2kzb9 z4XKoLkH_QJ&&Pkw^{eSRs3ni^Tep)1OnJ7HXv+jeII2J((VbA6C8nXRy17HC7*Kp; z%r}l`d%ccL;!TVfD1O`Wb*4|WOH}GrN56l5R=4SMPRo3QtpjEm@WIA7O1QixlV4mF z%Kb)o=Fl^|hb;y15Gt)%&zUe@BrK8EXAvAmDdgK@1x;1czyL|&%3L5U^d$#HKriDp zM5X>RR5G0y=Tso)?Wt_Iy|yCcb1c|zY1b-_p-mZjD)m~c+&m6~;`HsKVHa_WkPiG3 z&@x?n`(WN(M0!k;A$pK}&1nRCp9U4SQ^GPE2@iolnR!_33{Th+pO6uQ+mMZ53G?f+ zFR&Wy^$H#g7*}TeIwMW)==w+SumPRgunsi%7?c+L^`ytla~a7>(QzNgjZ_@BZL=<< z3w!URKl;%Rhj3Hx(pwxu4isfV)*;EswTGJ189n82)?j~ipD4tZUMLO)5kN#spr%HX z&!KH@NWcTtuoAy2FAtk};z3 z4DnSq$#N2~TyUo-UvPEaQlQ)@5Tq9m91bF{z*@LQNwTAi8eWJQiOgM>V1Xl-ntMI2 zu%LDQ@j3qZF?wfrbEkr^Y)=1VL9%+&SJFznOG?#_a=={?O1GuT$I#FFAwm{(E?bM8 zO<1AQ-;OqNBy$JGPlI9_O2u_CBri7S)0XCpak0HGMp?cGiJ7BrW_y*_96dj7{9~Xe z{FKOb!De6V09o9*qjB##v?-GlJox0^l(Mxhx86W7ev+M>OE9>9eLbE}OQUT0q5?-K z8FQ}3M))$Df+;?oy zhWh45$*n{Xt*?Ox()0L3Zns7xlC1$^cQkHbYTFQ^SC1RjUnL(RN9p_a^*E+|rh+v1 z*0T>eq9-Hk%dm#?ELSpNH8x6W(1TyE_kO5IthuWE(rz8ifeA6kbw1YlOkbAn%bBYk z-#BK97Y*LDQ2qBBVj%IXbpW&e)@xXHckCNm=1!+8K}SS%CBtVbpWkGEmMVQ0NO$9;wF^0 zWBiXlfBdii^B+I%gERm7b-F*FD?JjReo|G_CMfq;-%Pnyo7@%22ZN^BE8`Aux@XX| zmH17;-k*%?!{`_7vW-BnRzyDU?+eIZ-xuET%9ICk1fP#>{x<{7*VkkI zBzef;G&(sWG@Y6a=drotZB|(f^ar^t4RQ(GSyH1HNdMwSevya0I9F-g1#<}xdxou& zrz;$1H9Z!O`xv+0zmmlPfw_<;OdgsSxS1(g_pp3ph$SyQ<%or(Iyda<2c?Nm53*ndFe7W2)^amo$u#z2m_)N-^SG%oCbHfbvdlrG2j(p zYjZ1z70LJR2j0vLLamLWRSS~{OE<4d-+`b9_I@J_z9)V6%vkt!P$nsr+$BqT>qjrO z75+w^hnl-koHT<@3(M5^r~Jm|UlK$voAZMPy*>oC9yAe7%y#o#Fkyj|X{ zi#vW2+q!r=Z?>f)lgi4$|6Uwfi{_m6Z+B$=`g$%mZKDsB-W=`pqWw5-kY(3RlGH~V zM;nKhuzx(wVy$%?y(@X-Jk8cga`t14-Umtz6q#VJI@gNnz=-FCDy)rtWHfF7%=(IW;^;*p$_e zf#P7bfNi3|QvA)@vCvdfq9DEX0+lSE_e=&&usR(^fHCUPZbUTDMfm-ZqdyA2Ufr@u zLymT|f<)-^>%*-lYj1~VcS4^`SEK|ygLm+)rQ}HTZtad*J7I;L?>dMixSCE5>B@c7 z6c=4hT9C}gKi!H3h9m{&$AO~M1)Ig*?4{C^B{FDcU9z>~^YiwPA0^qD#JwFqSPv(e zbFQ_R5?ny7%66FTWT4=l;hVfhUdXRxpFK~FC#RY;^Rf73)`GIdOK^oER}k4?yXRNr z3X5gOgYVg&$GE*Z)d|LAO!H`ce6)M}aQ6TNv8p$>Tf2`g-15lPTKFi+aD==5Je^%a zdji-FZJ$Mu7jA;SPo&iq>~2E+L~wrZ`Z4rqd~|j9G35TyS4#ehWnvjxBRPYl`Xw6> zv64gvR66f2*2=dM>c+J9FC^epbwii5$EF-_DxY?ekupkL1<%+^Ts}_@FM`kIX<9Mp5EPte6VkH@>W# zQYNC)PqK_NL7Li+Ls}OdteqH5?yvf+gYlnuQz_}*%PnKAb8_UmNR^t_aFnm0M;n;v12y6>F1_Q8w<{Sb&X#abR#1K0V{r(arc#ngt3E+3>+@ zorP=SBnw~j3|kbdX^znxyj@im+x#qrv|`+rjNO66I&SwC$4h=NDSB@oAGdZK3awut z&-OjxAn8?Jh2vBOaAa^CbEl1-&U6*aPxJf1UXvC`S)}W6Ikw_zn!Pi|<$BXYH zyflvYBe_Drzqv>Gitma4#-7Z4{Sy_OiWCH&CB!JPN7gI#Mp%**u;6+xe&qr_)BY*D z``7b90h~R@n*KP?=Q2;e-Hw0!<4+mK*Yh#Y7-JlFY(J3>%S;(JRg{S-r&~PCOz%rY z@pT3(RK)3na(GC{YP9ec z?B}|)Nzz=4&qXuUxhJW-N-{9!6u_0-?6inR3epXg!`W@w^XqhX4320@LcG>Gr=8Db zkz1>j6?1F>+bBh>yva2a-c!WZ4r+&I1(KR6Kw-x`ClMW-JRg*77Au@d3wzqTV&3j8 z=bf^(boUoqy}GJMd7MY$`szdrltQdU2J8q)*WpU4Gm@u&I}q z+@aDV=z58!bpLS(VxH+piAkUrWU$^m)$IQ9^oV3_G9ccFxD+ zAqrVK{HQIn=Xrw3jW@Q2xu!&QpC`<*t`jUP9Ebi(m7q3ghS3krMdfFeNW;MBbopV) z)hNy-%gz|nTWQ=Az;djxM)jTomTqB z@?TauhLzwsm&dstkMrx-qr`BVwyGed@W;o;4+XkB(=p?uX&b%i(AIlXKYwN}RLt`@ z`sgiLriabPjOj@#eB7IUNQM;zoIFl5TjXX!=}F}Fx&Z=HR!d&)Jz{AbLpnkBL&9Mt z{PSL|QH%>byVeG?z)`UBpjmi7f+$%#Xej{kf{z4r74NP&t9=y2B}mH8NHa{^y}ZLw zu4p4##QW(*oabW^7pYV8MljaS+B(|qrY)Oi6T(P7MkRj;vXX8f2T3 zDfy}rgc(BA)}pKQTZ&f7{RNJ^Fj*-eIe$EUo$Kiznpz{-;h@YU<&@UDz&~(X)c}xL zIq4}VO6^17$LN7bX}oagh)eA)`uHdx>{=Caf0s<@@V<8=>J_0DagitTO$bRRJS zh1O}K%@??=f>GsdqxW%eQf*1c(eFS0=-#{?0AK8v*XodfG*7x4vobA8{hI{SvxkzZ8*}9C@RRz2@{`fi{j}mOu z^>I`?^Rieai$a51wl?~YKmYjiKSt9sL+i=&`StkMPxEy@ZrbwKzy7&=PIrFJdKT?p zzrM8HoO*&*1gSXpY77X>q?77Dz)I&j@mbLuQR>LAgY7uYI533XWzSGlGD zl*{AsoEUY{yAsJcSH|v5`S|!a4i(un0dBG8G=normsDcxwl1mR0icZAA!d!NiK29M zvyh+#RK0^>Zr6t4W{YOdYQXQRt?+pg?$5{5=A1%# zbpE($SDv_s=ebS`&o0vXxH(8s`Y{@1gOLhHp6BE7>uaua^l`r*9_O#WV$H9|BmUJi zh{qTz-7Tww`YL;c0C<#CCY12>UYzg<%J~&xa#jMql3mxz2G6M#Z=}jLGUOwr)J^u$ z0m?++cWoZ^jS%dmlv*FeNzwB_X*cd&wb``$ar^xEaeREfT5&5HD7F;NsD`Hqe>_h!KG(X9AtKKikLNj8 zdh)pS`)x=Q3ZSmR39v$z$8rzsl{iD+g{0nZw~t8Zv3&VSDF=1!+-|5cgMA-VK$52_ zifpkWW7$crU53(NR=P~C{E%p1jE4tW+v?HvE};L&F-SV`hzOMI_0k1j%sRs!kH^Of&a~*wP^cZr8e4u4f?nqn(RL;z!p ziom;NIG-cx7CxC-JW&Pu_>iWk zjds%J$&y86qmdd+UJKXl;D7ufqqii!-NyZfj%L~k#$ve0nuNa68WhQgE(FYIA~~n6 zXhI0GbRoz=30}C#U{wa2=W#oZen-7%D?P4dsd|KleSF?}BRikJekufet-|y6d`#Bh zb`Kd$ztwT8=vLo<=aRjgN~X8Pn+&OtjP<>;l)T?IuIz=VR%n>%Bq1sIEPi(=WqEr{FBRGY|crSj7AVlUY=!X3L|?xCqF^sf6mpG_e0wl zQm*UcVNl+!XJc!Xf6`p>z|w>8Bc2nFmb0X9s#>+xV09Cz5LNfMKM#IU- z)`uzEGWS&|;5wAsObdo(Kj?NBRaA(nYI(_2)cpX{aRh0ji@?{;*a@hLsWVpdg{i(c zQpW-48-|w8HJ^kY__V|}wMXOce06bcHoU0@vBad$ST3dMoFe1i-8*fo72hKrfYFUp z4#G4MmB!kR))XevD%5`d+rE&@Cz~sM*Ox`4zNu~FHah1NKT^_RX7M~h8YB0R@g3)0 zhN&wlJ@vlQl@?1EJ%oos)V*ti^w&T=H+ve*jIghD{<3(+h<0z|7~}oW6Kq8VikMF) z3Gd93O{6Z2n9sF-e$DedmGXA<^>K_bBH~ zUhH2<_4eHETA7Mo?i_sw$85rSd?DV)#_hTU`;-Y=|4VeOlour9;!clm3XJb_rpsRE zyeaGUkFZLe8Mf0{^Ry}pG?ClwgZt1P{dWJyKmPOfaewu&1MlMJTCujDmMp8*xRn;z za74~?J)Y`p0ct@LY>HpKd{(F!v7jO}VvBafeR^BHx8972~SE zz8?Sl=U@Mww?J}{93KifqpL{*CL-_+SevG_J)#InParXq*eevm4(CaaxsHPp7A5Q> zRM0>!a<`IOC|)!ok5$ROthyvHXVdMOxpK!Fv@rx(-;`ytw0_(^y0fUKpLEd)2}g%# zM3zC-`nq01m~PPg(2ja{OQ`qZ#4Iqtrg`FyC5yDqVZpL#;ZOsSCdsmeI~kWoidWtY z=OANJK@nn4lO{#5&~;EKRK7d4M%qQSp%NJ^D{`4HJTB?&*gTGc^?ZgG!)PQ5oJ}-i zX9;hut==e|Map3f%o3(bu&NR{u1J1>|Ap zPNRott?OET=953pK~=WGBJ21mHSw{rhBZG?!d}mHta$h z#u(Nh%AgBj_i8KMbAa(PBBdleFii6;MaE9d8{3{Do1Rp;Ak_rM+7jQOEM;6gY(!63 z9R-e{MmiFBfnmSbAO8ItW-Wg69gSR)066jvS_u}L1G*NEFf*JHZuAz0I@jZ;#2G8X z)^nW+it9*zssDoDe)*gR^EI`BhfLmd=Z;DN`+M21jXj)2xLS0wP>b_n9IYRtHU9jJ zxGSUhLn6D625t&`O2NKiDW1YHn$$v~wMf@eRqcj~1;)%{M!G2zf+!R0#0X9^_F`$%u)12sxj)vTo#!vJ8H zu$k)_zQWOvOL{QVMZXfc%)S!D)~pB6-nZT_#x==n5V2n!qy)TkYeFF_Bi6%WxlvN< zec(4D^R2s3m5wY)Hr=_Vk3aqx$6eZ?Z8ZF7bGp(apHIyHI!?~HJa{dC;Zan31I_gF zJeiBVB9|Ev%Ew78lSSOaQGze?B(7_gp8YdfPb9XZeO5||NdoF zIiECSeDvd939XDvA*YHWnMP!UOthp@zT<)Ak8`fIkkAVeHjEcDH$#A*uDR3a#ieqC zz_2KwFw>H1llb=!#?g{Gvu3bUp3g=3JasYa@;p7mdnW(plKw8R@#dWg;%n((DvL^P z^4oSbATzEbyxTNuePejN49hH0kwqK6pVfc4AxAJw-nf{pIAYadvE1!5BG^mS8)@PE z9a#Nth{Ie?a{u*+lY#>|55J2Y#4VHXM+`BH#YN$j z(&+|Zb#}$$7T#NJn1m?@MD$G1NIk}fMcoZr%ObLvZV(nS_HxYN#6)EAW&D`#%PC6` zQ&=E8r=Z39K4hX0t(Jyj98!TQ#cKgYKzP^S=(m&z2Jn_KDl`=2+U=fw5DOB8km7-0 zVrwP2W{fW6$W}r)&uCxS9_H&{Ble9nW4LGXN|6Zq*>l{W*!rwd1AybqneNydz1fmL zO{ZG%s@n$Be2}zA5p0c|#S0R(M@Qn8B^ns3{<^W}l)D(O4h}QRC`+btd^b{4ra#l{Bcsla^aC?=YLymG{#faR}f<*~ih`!5X?-AA}q zD}4AqCI0^qofPKSXBAg3>nk$ONWsV>x&-^k)0WLI&2G!VP{wPmIgNRW>~hq_Gv@OVwn{Q?lHqfuwoqpUn4!NHv}=$>IYqD+7483Lc8Y&960RT<{KK$v&g#&w z=Tg(=Mfvz!ml>=mTT4xFV?Rpf0fxc@mA$0Y{+2C!!-4}b+v{>*E0#}_n4AR};@m^U zY)w*0ldl;;>fDlUk*r9^o|oh0w$g0V`^^O+#0K1p7pp{xvxnuw*C}3(Ur@S%jZw30 z!5?C>Tr1ntYULq5`cVDzW8H$QX-<<$1ROD~$6U5u&C;EvHsC(uz7|0~!t0onu8muh zQgax`(Z=BkBq$&#Idi!kw;MXI57OqgDt;TS4{aHtMelOI*QDd79p$a?wVr2h?J5Du zZscK<7i=+U_I~uP7r8-cUg2sOH_FYmb79~*vJwi{jU*UraAnHv9Xv#uO~d4Sa{Jl( zuXdd6Hf7jvPnqeJXh2OR8A^YuVO&2hG(*@)o}{4PQnjGs*@?=wu&5|W1Ct9~g1OGx z#hqao^HNpgU}@T_x^4z`<`?mv&)8fb0tE$V;W6EALq^xzXt#Uq+M~%wKaS%*+QxHR z_(WfBLCR`B#m+mQ>x_s&*`n3P7_bxlyJQyOShA+@;A*3(xSzJD`o#$iCEm9w!GiH6 zBLY-!^UN@xCzu9Z@iccBCdJv16&;mZgxUVK`prSDBs0j(&Zomiy)Zv-@nW!nEMcWI zC=Oe>K(AnaGnGe`{5GCudp#I2nn8T2gI#ZAY#UeX2e+yUn)nIEhUk7*DFc$wl~U4| z0`mpZ-&$I%Ne)Mf<;v>+j$+;_!l+pDeg(I-e)ioD;|l;sTlRMoYpr=c*7=odamKAHwZ1Qfa1|HFwBW*-`BJB3_e;H6>q)0n zEj!$hVUnicr5nZOdNo~?1(oeXRN8xT4b2tl*d`6BQRM`G&CZ#;lm!=3aUL2vS_*OH z$y@0n=x$@Qt@8k)0VMNnoxj{`vHtk@7^5*WmT@o+jBy*Ck{i==O?OCeanuNd!tx#- zfNrO+N0D47O9n9v<0Wy;ob@%6knW;b7$R95+?&NMXRXd6A*o%ChFS<`ahunMwBZSZ zBg9o4t*8&k*$$5t;b{hf5li$-2YGgt=wzUjHnzPWdZER)-pV_G0wJ;?DEoA_Mmeu zHY-sSi`wME_r~u)>bV4dBO`N>9>j&3>R8!ZVN5a*+F6QnY}+AHa--I`kAtX{OARmH z&*xg_dVYmZAy<*)D(1by>)>FBv#gC2;ZRYGC5dn^0@H;;GMTJMp2}KYCXuvjIeR?k zzqilxDa~W~B0ir_^O>*}Zpo7FcvncW7InL1(Ep!tFzgB}_g~kB#4mNDdX*LHGq!+Q zLX^nia$wa~_z)=b)zoEEOk&4$t)m1&R+M|eTK|e^8HQJI-{IJ{++ikB23)A$9ksW8 zD_lpe6=brj1zB-Vq`h?dMOtRXqY?9TGYZhPN-kjg1-we;{_2S# zQ3~Jwr7&r0M54WQ>Gkn8&HWiZ#*I~kRT{brdM{_XJa|iE;=IiFPV~+if=HHtB`_)x z1zRGK(K6U`Dg<|EXBO4N7TS*%u@U#)hw2tMhi>|mnQRB0Dt#A_zX&R8*#U^!K6?SNl~)Ng{Lpc8&sIkW>5 z$GLVZOuRJ@NZWpXj2u$R_Y%Nveb5ye56uUT5f)_8-=63)++>W4|B*XczN>~EoGWJsjqZ0qbZ^R4m; zGfLTGmGI?S72Lk%q={ zuN~?%noQcj2sY>#O?z(DZmt6)O6mw(1N-SJ9Uczfxw`Fl)+(i`!RboU6r8$vELT^~ zRwjQ*xTpwRAKc8XRuv3nL545jXKO{i-G+$l;u@!t5y-Ggdie1SV-n2UY|Xg|S@ARF za+{*J^g}OHxPd`(IzA{J_FgVa(aYCzPA#mz%*PFanF2OB=N*By9Ptz_hGn* z6H*O}DH+#Zi$z#+c-><`f0#XgEuJJARa2(i+w;zdm}HRef+N)oa{JyK!<)aG8;1We zWQ=}XG8GTdvP=)O$4-%EW{wQye|u(+$0O3}1C?`86fhkdp4Q&*owV83h7AMMSUzlS zzL``!qre&z87kJKnQ-UcJllrEC6)1&0s3vqQwnK9xTo+HoXY_Qab%j!CS^cU1D~oT zU@EvW7oVHvb4qLIo z(@+_(^BM7E_ol2z-$J*f8M{-g*2$L?pIi(q?rFJKA>`SB0!auV&#&Sj0R`Y8t#fkC zk=Tj=bWw)Jw>^kBvi4Il0Aiz%M#hQ$cc_yX#h}&JVjU$nFAd0HqhMNA#VJy5tc-0% z%X=u7dffEitziH718_87xCBLX+PFBh6JGYxMw~QlaqrJ*)u=SI5p<2)=p=hv)i-oQHgg4G-(`tS1QHAqf0I7@$2tCZw2#%=zB`u?jHK_`O2K;-5)PyEqUWW{ zoRVv4(CS-JBL`MT;hkvXJq5FJz7(RUa8x6js%Wcb%$A|ED$e%V$`7fvlRMEEE9@Ed`BR@1Q^I(qJNjS^tH+XE5{7`auhP(hj`)&}dB9CIZe= zS~*+klB}4dTc#*kg<8U@WMBM~VQ1K4+&2`9!Jsc4imgY#9W3qfc-XJU`Fyq}+Tfn4 zzIH&0c(bEz*e7*qsgO$dNvW1UnY>A=&_))wk_PEVkH+Ees$kM(El4sPT=K6)(m9=W zmqNohrtjV6aU}}(avi*NxHmJ>YwhDA+IUgJ2?i(rf-_fJ=KcVE0gAbBFOiOFLEVKH zg)F}L4cIH0ybrn0P4Yrm#P+(r(F)hZ$?aIE6k4K+d6&kjvNWzEXbSFWxTQT}{bYM~ znyjJ1_@k??HvnZon!ny-aXZwWS?B??a`}@0ylmrk|5V1)UFV$7c|JBxgH5Lsb)rGX zt&fjNJqE1`ld&@`UlHrfuqv7oZ0l0D0StDBrp%;x2=QptJA=@dFM@#YC~-ExecxM4 zmel5AL1EP$Y~UDJx`k4qgX5~!(&@BRF2%dX>>Nw@=sm_kr%6_kqlZD8%VeoMNxw*+ zo}l{5F5EeqEmC8V8(~N&JY?0{+umvu8*2{=f1Zo@Z;l22dF35Tboq7J@U-@h z=@)Z93*dB5^Y~gRAsQl&;}1d}W13S?tsXK*o@0<#(Z z7AB;xBy&GWdqyuNEyU1BvV~t^*B)wbKZYGt2Zpr2Bhp)D=C%YAIY7EtsRd}2ty)s0 z)x5ii+^U@jCa@BSEajV2_Av8@c+!%|A z`wV`nJkthCL#af!)5`@N)`C0}j41`@meV{IU?G8^>0KaXalyrqSr=$L$yg8jP+;Nsgoc z$a_15GI8qq()S}6OKXQ}@r{NBoUNo&36?9jJXzQA0p6^4K-)OsF+P1g(-%u)N#!BE zp&9AWeiW?+V&)U5)Lu-VM2ge+9cEC7_N~6YD{f6<2$la$p1DMBP36W&n;o56(2xzW z(ZYl;MOH9Yn)`cOKK zzK+}Nw$=#-gpz40hCr%mUpdF?Uw}Zx3}0A?OYNsYF%jJLeJJp=9wyK-?3}$(8AQ|a zdsy=#zaZK&u9>;8;1S>}Zt2VBa?vAv)f?e=dt)CGjXl=GClZLiJKRyS7%Y zd2b{vN8=&s9A}}}32C|CO^N-gyS)~o{YYqAty^W^mn^Dn5ca*BxPCb`SfM_r!n_0M zyyOat7^ULRYDK3~N)Nvt7FY0iIi_?iI2gBeM}m+rqU&t(m5O+sGSxdSBiQ$l_1grO zpxQIAsbg_HI@oxXY8P?ybSOa2pSczzSXBnt86HERCjJry|9(S6q#V3LOnN}CT+AuE^!su9Z)>V@^<83>vt&Nqrma|^#J6^g)>Sq)p0WfC2z(>oFkFn;QI&^% zw}IR0odgs0rpIjz6ap+g3ATl>5=7M)JAl3_oMi?<$$T!85C< zx{ubCiuW;S^RB*!x~~OTX>YAJ=@HTTuyvSCAu2_7qEOUu2-{!~JLQWP^KueeA4A8` z*5H=iMfXBlxHns_()bW6N=y$04YbF~(u z_{gUsS#~C!kY%m#%WOpidti;)mM~(HU38=B!g}>%IDTAcD3yEe+QF}bT z6bB?!MoDm~G!9bf3JDAC#6b&5oTue4Qn*B{832pXi1jB2OEx~$Fg}|IOYz+>cc2c1 zWdt04>W#B{Ri9O1|u;R(~MbKWr;E@;Q%>^u5JJ_mA6nU}{rI_Jp{|%Lp@}A09xWooR_${cc2!gW`mUN>@7hL?I5J62 zSzK<|9iJQ-zS0bk6ep4Ds8Si)kK_KK{V0H+{rtIIIwn~GqB?%B2V1M)O~UM0b9+}2 zmGE3aLd%zg(~3~yqYLLwn{|OoS2WI<{C?s)T^$s{n8uEdq*4vyx7SrrNF`$2p-VBL zY!{8P{8tiY{}yMamJ^)25?cK4Td-Yt+tqTU4E(r8kQO|vyd}aTHHK;9@Hd>tXc#Gn7Y*B09TSn)*v55Nt_?cKi^yd*Wt2(Db-Q*#rZsNS#?Bt zYnM^J)>7g0AehHe%LM8u@S1YP!gZgO;`<{I z$4WNDQ8AyHA#y!#R%y2qX(iE`(-DhaUC0%OGvBnk#4d&?$15yI*fk!4qo@`d%NXOw z$7j>J&srn7AO{(Y>&;zw+714myxq0PJftPwrSR>(wX3D5BCE8KE?6Lj`+|rSc?Lug z&QfqXTK^pVN7Fx>*#@2Srq_7ai)i^={5*asKA%Tuy7a}b4i?xZ=xY6K2NcCUL;$V*-~c9 zL%7s^Lbj||)LWLtb4{qQqf|hzvKsu~F{E`1-txOj4a@f?FFj5BbKLF?@K~^Q_ST2p z?l#YWlAEV4kJA=iK^o*;2lpJU6KLGE9TgyY0DTLbRv>NGM|H1gOg94`t8;R1oq3si zTnNOSSj7W+ArpgEU^J!>h8KnM3u_phOUW9A7J*k%P2(3YREfD(b_r{rmP}xfh(#c^ zvxq%4HwIA<5a?Z;ZrXEc`NuC!f{91I*70Bcdyq;cJ$ejo#_d*O@Cc;(PHV`5! zL94j&GFk>!bWtwvwYRixHUZ{ah3rIoy@9@)Q^pMTcwst~@QtmL+$lYgC~Koj8?AM1 zqaXJ+4yJ1Ndlskh(R$(6%nXb_8#Y^uugG@+J4MX2U+J1%np5ctSjM7U)d{B(+(-kJ z{p}Oiat|1A!|GoPNFcr=hXY0l<15uD#P6OO+7`b9{{<BLVO#?{lD$FwO%=rk}YqwCH;*7)C)kq ztmvLm!_wOrRXI%$_iu3sUc#wWjbaDHTaQ(W0uv*F;gL{km>Tu45WQxKF#jf#=r&iF zqeL$*cYC%_LVH;yV*;%FaO>q7msUN94U}!`W|FcX02)8owFcbLQm~yKU{kYlgVtMv^bOh-X~vbtl{L$X$co0 z)=tCjiVrOb1RThgRey98=)OE$ieh&U6wa`b%#|4q1m0do0e&60yQK$6wZ344KG!v_&;6-`3)uXArr5A*@-X;ZD^bpw+EsO>EFEQYF* zJQwLRga=b>nBJyC%_2iY5O-yg5OK&*GIl0bC0Nyx+HMIoO5cJ$K_Z=lH%HNX zmg4)%07LfOO3YIGa*e$#1S{+q#|L+?xi3&*Ov#{&Z&R7y$~lQTIYMpGbqxRWPx|-} zCi!~UKmX;AFZIws{BX*1J?hs$5LH_C#A}EFEf7+ zR)y*1G9u3eS3xrQ`FvV#{Ia<>kw7iPV=CP*b>nEl5gYJk$c@=%(uW|=ZJ_=&&)7SY zHU*GkkSKtWQYyF=-L3hwbv7Nn_c3mT%kKR%+XrQ6PU%m6uI0vUP*H6ygZqvHH6~}G zAwvqBuG(3p0$@jcn{ENa!|$yP;f7?xwpX1F-V*sS=k7tXFkjav1t@W!hE*FGal8;?e|KclF1`P7xF!Yn?V?nXHc{OrCFHfj4G{3S;Fa>C zZo}`lV;ue&5%&D$VU8Qf?jo~Qmaese-OK2u+Fj{n19;I4Yvjj$G#zcL+N|fQa_X*b zQ3i}i+E7+4agMk0B-QQJBuW@49jH!*aQWp42eP6RsWJu!R(Nk%6)o{~hDCTCCO8nG zPcw{|0-dC|UtaSSIlsCisShNm+QQCQyj6t5;9+0xUF9L*tCgA!r$p}3f+q4Bw;D`9&@2JDv87NJrE!HG_d;L$~mv#;kFwpy2a>#51j$=m|vDY35dhP!D47OBgQ zNl=W_A?gkAMb4C_qaSsz5&tb!Wh5ksG})&4d7f)I!$f)r<_0PH1&wUJB9da>V@|5Z zb}%55bgw=MjzDtdYr2SaBuvU`U?n?U{a3qvz4r}cBI9>WpjaBbi62Ue4+6weuXdb$ z%qtyI)K{V{1wx~9u5+3hCpF?X3&8i2F=Jzn&Rqp-O_bsZUkGORYp%nJzJb|C=i3M0 zZuxOjK`nAkrIfkkT;jAzWWVhbiBUqNYYiAAZF5QB3U+Nl+j+eomx^ks1I?CvR z*kqc&qD*AhEJYPn21;>HYC+-338dx40Q_PuWS$-mC`toTCVSy4E>Mwbx7>Fv7R4eC z+V)!ogiuudp0`#K&o|n?a$?^TV1cIJCF1y|XAX{JL5=I}zAQ&foH-Xi-)%1hy z$6zKqgV*F0T)zU9^qK;OgI|~MBC_4~{-Dyrn~@9dZA)pK8K$(e*Sl~j#IW!E1G$O8 zzcIaO#SgS~K4?i>=|v9ph!1M*>mp*PP!;*x63j~OM^Fcnp zz_&MT+}u;jk;Cv)9V!Jvoy{x|11t}~E?1q2n}QGat{PY2w~!<&9ftBKv=-#}Z@b^6 zx4$*!)x5z%d4=zIMsC zG!E>$-boApnrFF=(vC64=l$Mf-y-2G=ce%Cn16Pa$gix4MDP70`gB&H_0PxC+g_XM zIB%Ndy{a2gv^IunV~xr6G~4&Ud=0W1J`THVyTrdDb~67iP2uhyA9?&6sGh{o8AvzLj||M;zi4 z2=tQ+B$Ll+%sJFB-5q#dJBBvt&|O^tBYuT&WEO455S)dpy&^C!czxk7Ni?-vAC4_Y zGdfqCp-=**{LNx-Y~VkkU{AK%O^LACSFzx_B;@St#yy{pu*qCn`BjAU>jqK2?Va5n zt+_S@F5By6?ZU|`6=|{Gf=iUmij3M`bB2Vn3N2p823-#~Q32{QeUfyple9SJ>e*FD zL=*WKr#HqPAl)Oq?G*(4s3O)zwsGL5hLS}1 zs-a!WdJQj-T7_`%X7zj{tZ{->{*gLzEokq;d92ymaXR|_93Ti zP7D)yb819HwFP(8+tKcKx{n50KUD9N?uV+#<5Z%s6?8S#zRC@OyMptSZ&Z*5Bq+Hv zsa7zFIYM=Y&!?T8W+QO9ZDTlZ%GKm5nY=vA*~Lk+)|yMXHkaiTJN_uKDw6XeN><}1vXJ9CsC+GGXItv2rjZ^Gt1yH1~C&HiL1}mnEC2* zbC2@Obhc(=Jh^lFZA}Uv;7O5}hyAwwihF3|gi%It3hW{Wp`+2g#nJL>#mo-ssS>S7 zGb&Y*fbH|&65lVsFNiK?cHt-Gsr}hT65YbPo!u8I-72L@Qo|ocuXF{cnNJacFMHAc z$#;S@WtVi}LSs-l3J4YwWh=Eb9MUlEAk@pb3$y%U`Bcx1-xD3$nzqh-czm(U^vd@t zp-c=q@1>|lx56j&o3?ZoPnzfRG1qyXxDesNLmD?phR8+j*#-Z)lDbGphxXRSZ2)*G z^4=-0B)tI`j&p5-HbyNUkqcJFsoFM`CM+Vpi}Tcv6!zxIK~=O7-=x)}8wlP&Auqe{ z3cp9HuUuDCeZ!zr(0D(8KEN4~mK@ypvo3dn}OMrW-jM=K4(ybWE*>+zWDflBdQmO(O(4;@FH$r$4}j;W;u5d5)fbA49$Pji z-ZD5?g1tE1UME)wQ03H;TCa5tSlY_z?JvT*PZ=4`*rng!^g3Y(2eqqQg^U0-`@~T^ zarAkv6h=!0ZCrDDBy}iIyf{QC(Lr1*F6ufJmw$IID{qXh)mZTT2iF1LF<<_kRT5@5 z3*Ui#z%{EO$Wnq84&SluJp(Jlg)~UYut@dbCd|4CJfZj#nINl2gOC6gN*V6op|k?8 zzi}u#PV-%FCX(DHnfyL&j}$LrHuG|(kgk3`o-3ch@^_6w&n#*I<}H*#q(S~2paU$m z^xi*ikO%c08qAy6nl@X;-fcL z7gj}Wl(n;$xUd!GFstnRX3Q^%Lx4Qo^#yb0hD3ssPu2v9dkUs1GH2#g1$Olk8YrW* znkl}w*l*d`Z>$r$i}TVY6Is2l%VP#n&J~S=Cy!Fi+lN2{pCi zCmawjcLyhi^wwOf3K%eg{3_@ATR;0g&v({2z_OaNjXB0S?kTD^`5C1TEAr}1W(2oe z-JLpFHZ%MxDt#dk7+s< zQc+gL9tOrQN&9X7NV{v5d(GHp#_wBBI-_>hJq4m!P38$xJiQU zO0S3)a?Lx6E(&pmws9rlm5FT4$Itoo)8^N7o6DCox6#^A?-_9-U=-L6@+&O|^Y<-b z5SU{2V#DdZXQ8h441QZ)w$rpEIH@F2<>LL=9Vh&N95vFR$yjZ<98>!^vve<9XBb& zg4hV;UNiFa5RYyf7l|RdNdXapXQ|B#5NI1h>cpj{0VvWgv8Qnoe7)11N-N3nBR{7% zf2$qHo&%9EN)%oLrxwgw<4G^A1%>0u6)1Dqvgh-8Vq!NH;`UOxDL$c!Cb0}&^t^aX z=}{kF^=-oIRL(tKEVc+^eyagGPWiGFHD8Zq*kr>pge3^+g|#-s`up#v3#xKl-7jTw zRl#cmqU)iU9$hKxo-QTLMp)dw@D{9c<=8VJ2;`XzoO>L~iLGR=LG(>p?B|^(d4N!r3OM zQjtg&*wLIo`IxLhd(ghK5y{{&5x$}%Gf1AVDD)EH44v4W=5m`03ekfpF!W1J1&9(t9d$doQXW+n&G*2nO@$FP_HO++d!O8x#M-*$U~=&b=e1oW%m6OESu`$g zAo;S`*c3*$8mlalzoLCTSu!kXc{p6&$_~u)tb(d?WEf{1F=~N=e_wO?8lu`lo3Wm% z(#KOs010eTdfR{x6UrXj-h@FF8?122w@xnHHEW5^!6a0L8mka|ixLSyc0us48v3d= ztHlDIR(Tb)C6Q}I%uCz;vNzErTqbQ=;0^D~PG8}%#GGiUS{G8@TK=!V!)4dDC^MF? zH6#6%WkkV@qP&5~LKHPIACJeJvvOvhem=fzezo+~sKb1I_T!$%C->pDeb^Us zq!3i1i_WvDNV%XpMe0_#k?73ZoB7r^AW0=%_;*L`TgF#acObERh*BHO*-3KR3`!Wo za!X#~?fCRMpt-Y0PZgqCxlVaz_k?Y}G4gK$L>hB9OUuG^$U8H>A;AAp{G)eg)(AiFekrR_v zrKnzt%>S1L2!&u7LVMUEUw)dJqOT|2P`V1)zYo^oSLjbkmjwx5G@rG{2!@heX&6F` z7iKjK|KH5l8L>6-z@_;zM}e^Fj+Tp4gU(29wx-YF*|OOGEd#R{zb_(@%QFObS(F8q zQFP(2{`^4W%)9rrg1^i`AQ-X?3EixB3tRST5HF+*VWYsN#05F`pv?Y zjj`cJfJHhnYACKRbW{YtoGJ-&R9nrwBW`@55Q#it7x|Ys$)2chx8{`@z4)I*v!-q- zLZS55iN5`E2s#Z_jMBf6p>v%Rc7NK-Wtf05a;tYKqz+7!AW^jZ|mymz&^ep1;1P^)Z@oqHJf_@YFNb^5=8a(!ay6GQt;}@Ujp= zI>&RajO15v<(Q$njEFR6z0Ex(0YNWdEWmXUmM~9(Uz*FZDRtc_7I#OiFIlpgc(9nF z^s;(;WLP+It{R}&`8uDO`ElGvW`7}^JU={NTmNE^IklR_$L}o(5 zT_yA4*nb-yibSNX6=sQPUyMyELy-UWy`3xG%LI#B+LHZ3&PG96ufa0ip659ou|8f2 zxMkDhG{@a9kZi4G-Xx5puD4p^-;x8opDDpGdQs^~d9i{ey{&1FCVt$qk43UlB&lFw z9qhlkBI|=N_ca+2n&VMr29giOQ17PdxmC$|VV#vXy)!rnn94aj&jsig+@2Q?8JK1w zz9-U9BoytvcWn0bl8_`}Zal-&5Z;hB~qgg79(d&HlT3!Tc!#tN|QWMrzfw1oB z3}e`J@>%Qr20@04aKYAz*fGR#VYt!tEx*y(l{N#byXH&^OS7Pf+3V&DTpn|!i{G4O zxh+qJ01m}W}3O5K9`%NB`nZu&$$o=hZ0d2?t@vvaz4L&KGi2d9aJFhkd*6uCbPDq zD#*|0GnQ#T`Y}Y_L%kpYZ`)&uJxGJyGQ}z4!cuQ(n9n=(2Fo#jaS6kEcSXMV;1fxXVjyL`p)5W0M{x5=8)uu;7|8N*rx#QIuLjSxM0d z7T9*oz)HIn46Epy?+xI}Q3bR6Ji|PQ%?nuMczrt`^*3E@V%%H!geoOCRs44qB0m1MAB z-i>H!$AKC+Oq9bf#Hws+UwC6R2jAS(6^y17+fe!jC~Tm#O@Ay0vi{|QJH;y1)Fy0= z3kX5^_Qv4WB6Kc&W`PTW0V{hq<9HPiS>P^=0B>lpu;<7;m^+gaU#m0KQuDng5QPL& z$(=vj6IRU@^J{J0p@v6Uox)naV*^cSqSf($>6LmUmuHOD*-H5|%1$&gjoDc&#Z|(U zUnCkRR0#cM_|dDYl)sOJ2ET*3Mo<-m3Bzier?2UrGFt2PUI7g;hR{YvZVS~KZ&@)} zRIi0x!|z?!Q%6(z<8eM8r>g45O?2SmHLi?iVfNupRW4zpDif4>W>Mk_D)i2|GnHNL z5v6aQi^^beq|LR?2l>*pR$_QDNm@9aPv?4%>Li+;zdWAQvuSVTxI|+C9e*_Cq0+@T zQbFW+SM9`=$KmkA#qZQ19K~1mMKKAyCMjiJ9gDVU&y`_s5R2e)_BxKi{#F$$#qm^S zD!4}**V2J%^Cn{1Bs(RM1l4Jch+Xa3@+iSDx-7zQwzw(0QzOt@R%8qEDvkxCCqzLV z?99JB|K<5`p23j~bvMC!;)KmyLW2|crI~bVHxcp)j2_8AfYLDRfm2zw*uvY0K8~b3 z|J&%i-CWuhg{%AG*P2y^`PVn%FayIs1V~+EcYANeTP2L28u6?9zIE-Rsp^%j1obAh zJ(%G=Soq`l%m}(2O)Er{tv13Ql97xI2lnT!%7sFvmoW5FZiQ_EkniHh3MLp398$aj z&9i>;dzVmhdD(S5qoQWt#0Nm>ySX(jjQ!ThOxU;nyUlu4l6+e7u|O91KghK)m+&c&_S@CO7%zXazUPhSCG&i&crH(hxaFWN)ca-{fesu0AJyH zV-UnJ$tvq;L`*Ab3Xh{nL$WY}U&xFUF{9^sj|o);xKW%Y;mM-td+bDE%Aw>)5^R*-MVpBtmS+=*SoF;m>c8Xvm%X|dtKXs~$2&VBm@mDFqaQ^$uGZ-lIc-H$njFv5 zmaXiX%u$5p`_rmQ2} z8Bnq7w*(<3_}|oM96f^&DnPRokN|5wVm`=c;}og8!;92O96GVw7c))flj0}o7dL8m zK0ZY|0rDu=wi?vknr)q_58^*KKba3EUu(|ivM_1f^cC5lp5Y>?#8=x>WdSgcugut{ zF8@X_CMnP*UCjBr_NzAOtg>G;Plm$`^Q+YW^g4&&ehJssAV362)Ec(A6{4C=Az4bk zJM*H<)9_mvBnU7mD=TA*Y#pql_1kDU+LPP=P4QpSrvMJ+OX3&hsxql?lT@}d$QNmF zKS_TDF%Rk_M>Os8`K!^_u%+pO zDoRS@qYW8aIVaZ826ZN(2r06jsYuc_MFj_2jnyO^d zWV6fC?reHjWK%9SZmCTpx9D4ddM=CELt66nmT)g13Eqp-Po#sTyQN zSz99c{mRy;l0>9*JE;1 zwAm{2XgV~&u|@_s*Bi&?Z!G7PqAx&j!}rNW+kc5ESN>$D(MU>+?#a-ZehLcOu8(BS_Wur<6m%t&!uq%&=%tS<9$fPFKcPQx=N#PrAQGnuQc9P-|V)wIq(V9aWsa zC)TdG(&MGWY-w?V@U4}a-Ex~A%|hzLGA~jk*;^295*&IGp)ncPXPrTc!q(i z!yh;En3e%#=7y6cEGjV}vPsQ%qu34%(6mdZdDC&z(LO{rnPvV>to_YNp@vVl$i}gQ zB+eL3<#xB)vVtsYytxNmy$DUTRI}dEJJ$hV<=viZKAsOsFSN=$zs|>BtNBYW38n!QnXNcPK+_2myBx+;f9V?6Z+-x<#`fEZgwAR?*gqA4Ps|} zAO%4wT-W^6@uRmOb!z{l{(ouvXK#>j3AIOeYnXOQ9Ecx8G~og+rbW6^S?=0@$oQk3 zzmE1_?bm_>Y+&@hMUR?b zqC{3&r+|~==*Mvk=;s97;!H0_t%i?1ng3$_ryggI=2or*+9WAq1*k>ogc)F!p?bfM zyGqHk-nQs2l8&#v_A=f^RgD7G%padBJzNnnQssYwwiVffrU+j#O7QQHvfNGD1=zlL zG4cYURT4>vJ&iK>P2d?DHjb(wA_wTBUEQRZSqvQsySk9}M<)#HR~V3@@Okzb1XpWf zw9>su7b@=-CQMu{qg^F6T1!sJLV6jxLCJQXIt5)B1+j2d`Qx}-mq;e_=g%ie)fzIo ziYm2{0iFxurR^8D2kWyjclCBi(%l`jaiP1W;I^IumcwK8q4>nBSoiLkZ^$Z!Lo3C4jKK0 zVj04pz27?vR~yD}ZKswwjb<_Q4uPoe?vWTh&SuXWjq(Q+ZGnFiey9I zBxL-8??hZ@0M7-Hbi4;xdWmO6xjkXZ^1^KFK}i`HKo?99**Hc-NshypK$E=42nzG; ziPi$~E&aC6m4*^EDIUrTf`hwK9MKm#j?f|TT^K5A754D!_(8HO@PTlahxP`h{;hX~ z0z9JC+U9e6b!f$Zt~i|t;M_*4kdVo@`Ad=MI1V!_((KJ}0X4i@>$dl{v;tI)+~@;$ zuNCPc;HVpZVPk?3lMq_MU!(N;g;;hbkUHob4y8V&J@uI55C8a|e01GEJn*3@D}hEpdknc<}=A;~J4Ay0vCLEN9x7PbH4 z_Aeg4+TlVLtLLPs9Y`IrIuc5t3m|7Fer6bHN|Sz9mfk;dd~{Gj_%9!BgWyqxFPrm` zs(0z5U2`WR)_J-wi0%kkG9i*i5Rph}yD9|=%J9taCq)YW5S4Ec^>N^%(u* zhUTnL(m`}j?m_}LIH+u+%^YGu=DZsn7`V^lj;tO}1Gce`DaRyr8pPU@b*BJbNyX>Q z^go9D&(i)=bHudAb3SJHGd+MKb3Ayha%R}#^b}7GNc~`(V)*0*(%ddw=GL#U%=JuP zp5Tqj6jb$ndw|X{hx&2oM;oWmpO!1TjT>pN7FAMdXda|6&WA=P1j5{$`v=8LX+cdc zkuOgVUzV;SJ}~tt-Ywj|yKP6$<3^vYcjsk8E^ji-`CJ^kAp`r9FE^ImM()`C3CQOFQjf(}!QJ_$4@@UN`>&n1$rVqqNqJR(Xz!yl@n=HP1@x9x!yAv9mj6 z85|UGrU&yslTK+X&Q2i`TqB1IKKC8Nt`}O1Wpui!A#!gly@OVng09e5ISYFJlInZa zv&_>xE6<2TrvM})Z(6u*Xk&C+Ldze)8_*f>%<@t_N1<@r_Rx~|+7=;#U#p|1i`Tk^ z7X!s0Y_HW5!+<$^6_Z~yW-!$J_xaU7gEvEa6y_)-*r~V)n6Up&Ph6P1uIn8p)e#ac z@Ic?BY#Dp0s)1iR?4DE!#lUIHkfAqD0^Kla5HF|adfmP4{lQ+GYAf?e`lU+aW_qx5 z27lCex-hs%g<3;0z83%I^H~op1gJIa8ar*Ed}TP^U{>bce6XK*dLav{>y5%Ar}^ql zU(kiBScT7=Q#bfH0f*Ar|M+7eq4|O zeUkhaQbMLQl2(s@F8WtC@1J@5BpEfX0P5TVo|_RTQWBeBu1~U)Jkv%j3;S2*KO>(T zg+|It=*444P6CC;o^%ijZg-9!!kWA|PmeRnNG)4ZpL7HbqzPpMq8Y;=y@&jVd&6ZU z)4xdnZyEoW9#8F&-x@Ob3WS)!IN;46;uDNCVx(Uw9BqX>TR&wODP1+kYaP~7w?nb7 z@Upx^N@T7Yn#2?A@y8z&?s}g0c`AiU?ltE$X*5{0jfhz1jI@|fN(i4P5`BP@B9$a0 z+_dyyqHu?GK|tM65n)J)atmBC)txg+&-DZs{=?2^QY7db!k5c>dOkhASp7J7kj094 z#&n(4Qr=?RLqB49^1s+lN7N+_%5tU#=H-!qT)SoRs{$JdHq!+Un@mLet<>BPrBI%! zN>$!ZidW^E%HCccUyFPFc{@_yLT7$`uS9!W#*SG|lzQIT5CiX|r)G8GwqoKpx3C78*F>d~ASv37Gm)umSbf z^I{mOdzn!f>bDplGrud(v5YH);%OO1Ug=6} z2i^am*1(Ycmqeiwx%l_Ev2fEMMKWBIXfrYLH!2{BOe>In?Vm6fWl=}8;?kxb4<27B zCarQCS}o<-iQ#{b;LM=pD;Vsj&i>m^?_`WH2G*fY&mJSAt^Zktf9d6y&{ z8`@Wcw)$tc6d!3fvp$k`#L)z*D?Mgrxhdv18E|=N%uP$7GoxSy z0mzVD}49ZJ#ZF_f!HSHF@LRJTBH{4imv1PMj6LLvs1T#Nbc`Z-*s}iA$G$V>ZZ% z3I~kG5Z0Mmm$moWC4v zMpLKeq!`Wq8}oxi`FJnY=?k1rl^HGg2WpKs9VX*5`q6ouajL;L3p&k|RQ(oVbLLtn zQ%D$Rl#rm!iHwV}HqOQYNIkd04JbF!lwI|wOCdaCGG8u2LV3(oqO>#clgLwy4{J16}ndqOxN;frD$UpW{?No5=ME(HE3;Xw?f=_<5T zgkt*XICN9mfRso{oAjdeF}!4<6Z%{tuVobA?;-w?VBul!#pf?UsqkMbAYI`XZe~ z4Z=_}2>dH5w4BxTp2r{Uc4Nxkgx4<$lY_Z8ZdL1yM)1rkB8qh85|-EGU@lAp0(wE; zh-2q0IX`5XNLFiKB>y5k0liaDd(?d@0r0!#e=7fHlMf+1$X^XX&?E)9dE)wnfLn=6uF$%+IoT;ql-CX$qn{82%O z%x0P!)=(6{T4+zL=F*i^noE6>{-w8v9*e|L(ld&?*%3Bmb+QIf8<~-uD+QQ3R$GpISBcdiAhJ$h57s|4_roD9OZ;to8`e_8&d_B=jChPpdDsfngM#*Xkkm6pP>Q0H>bm&432dUVb- zD+j9O6)L`fz&L~7nLlBGWwVol@+U4TGc0o8RAy)%T*4Z8MV?4n_ue~eXKCDpd4^2# zl4J-U79rJ$g>jT7u#eHax258$(=C9SH47&;>2&+Zm2hxWlbx@y1Q{6+y2k>Y$R*R`&aVYLN`(vG{p6%0=EQxN!M z16gRY*+S`;5?!&?t@%73xt?7}x;j(g#wU?cP83^`rIt|TtX$o0K465K7cwEC&W2>@ z9u)6JD{5ci&tNuCR9Skb%VmBi3!JGc+8l|kb_dv-+%LVQFF%9$A;*LJQ$I+!<(au6 zNb&n2>ZB&a^fvW|eq$zEWZ9ipvVnTqNR|%k_osf&c1zMEdG&X5~G>j9d;$+~d zkir9zp3U$XVKoz-+yI+D-TC?S$SJ*X|Iw%kO)7nQCN34dtyIv)?RFbC?>t-Bqme6T z3%_xGjDGLkm#3GH+l02}?n*13b-SjypbXsST~53%Bzalk;))3Jy1CU_td~5Yzr^mewh$Gn z>Z@C_e`65H`uHtB&KjH`N9@`pIY_O8${|+@YH(&I&W=ZzD%$nvs!gkqd?Q&+K?oGo zIQWP@h>ug~JgcPA&?Bd9*k(!V(iI1VxRiBbUpQ7WwSb?~U3}7ZT~iQ%KQm#j=%s^?n@pmY@AI zXU<&ISO%KgVGQR?NM3NMghghMF3)rNIejHJ&?~c4Op%nWq^DED1kQuQ8ETxnM)#%7Ro~wM7 zR+(+bN-&WL^w@2KtTH}s$B;#lmh+LmaO$-p7t913BY6D9f))wWTa{lbp)M)c=-yxJ zuVS*PQe(ZTQUVFQn0v8Nyfx8z^R90{Oi?NjmbTk8UTjs!^#ONTaef&_Xdz^`4|g?I z_trXR=aRV2Vvz^VM5D$Jf~;clh!lVED`+{=JVv%TR)l} zZ5%4BRc@>Jd$$lJe>26c=2xZ0SK3TF(~T=}V!VhgBqgBo`qLwYw5VQ|12rT$ii0SW!bN(*w3A;EAz*bcON5W|P*Du=3qLbm*eTdD3 zh4Ez+2In$>_CEdmYRi<__1MvNSa4Er$?|lN^CBZ8F4DBi80{v_n$Gaaxp-4d%$QcX zImo4P);tnc-Yv|M#NNCuyKQ{fI#BufAmJeCMY#&?i!NUbsak}KzT0G<+5j@k?RBIUI0 zIgm99S@uXGMnsBteW#Is(@J9GmYY^}n%cA}X2v7T@sSA2utKXcDFg+f!V67ja$4%GU#}y+I?6TzB!qU1LpKkFTHS`A9c@ zMlJ*kkOyULu{Yy>`}~9a)|JaAPxH*3E|n}n(JG-=RM&RcqzN^EyR?xTmryqR%I!1P>W@AHBoOZ8%A#J zN>N`y2uZv=l{hwAM+mnzI(^=0+*F$!KSFqm)TKUVOGY#6@H_YRkuy?yC$~b+6&E+5oMxnD*s9lJQt6ZNh zfCwf>}WEY*y!lsV-kp}A3X>u z=#`6+2uV`PC^@UHottyWbmlC8pUcINPMlXcR9SJ`w}=rUno_XtLX4J##ADc-aa4-V zy~?jUeM>^S1l=QzBHlEr88F;?CUL|$1w=!18aVc@_(V}EzNu|6ghfS@HXYErjed7c zY%P!%S-S|XK?%wN@B$f`ra;SxC%2eM(OSvpvquwjAgUY#DytD}DhbB#m@LYmxk1_ME3hDB2j8Ev#95qA4nzvUBuq@K^o( z0+xJxXtz|R-Us*2qaXL1WE94r3eb&RIXDPfmMLNBLPeVAoVm=1TtY_YcGF~C zPdz84Nm|7Y)gJL8LC#B!YkNm|OW;L4f6mAM{c}Fo z0ur{R9z$=0q+(?eR6CH>GuX+edvJB+Dn|e%$<$RbBsESlLSELYoi(~_hD&f=C-d@H z5HlDhx6aDxg_vZT%A@>U7TW$L0de7{n~Cs?f7OXhP*ZFY8Otr;WOS^$w;SI-fPfcd zn8`#VxN@v{W537qGg#fsPvuAf2-%HXJh+IUpoZZysj)B&ehPWy)BI^`lj>hAqa5VE zp6fZ2pVa=Mcn+~Zyblisr%Q#QZm~4ajCtA&U(e^$=d$%QGDJNP20A`en-KKBZg(1^ zGi@^bB==2WzrpQkZ)QLK&Yv!gP-Bw5R|@lKXV^sKl)#nUPHm!JZ5)y`_~;QrO|;bj zhoU-}w1@dWK`V1JQ=4H6c=`U4fX~gn`S8=F_vg`$Xd*AnB$X8((SeZ`K_>NMP!NAI z1$Ph7x>7mvlqr zRcmG{U_YvQDRz{6?V`+LT5yyAD6L8W0jQy2)FR_&X=ijEI{pxSvKd;iJBP@3e7!O& z62!+Lw~wh}J;}OeA7^Gywi+KD!^ z3Bzim$DZ-ss0maI5Wt+qP1^0ykEyptZGANr+RFD9Mnk*b$VW>xmt4b_pYzKW$5N~TQb|Gy zKp+5pNGC6$>LzVS>sy7nA6me&vJ8a6o*rOuIWtT$X@^)nxfdY8h~4aI&!?>wi%5zk zH6&RZ#wddZv$3?0VXlGJmLw6SpcVAe#!aNqz+4b3L(4M~Qatad$4&Sv!-(oFi37y{ z2gf9glP>WX{;uR(k2avL;k303os%2s&_fVxUhrT!;1#y}8>{{FGRPJRO)F2>|4DXiFsPUB(a=o?0%NDEq{o>%O@Qcr;0A ziFC{*sHM`xl>xg3_DgixnqiB`I2O*4(Gt*^4r$xlc9INHBp^{P!HK{{UuXRKzcL1O zZW2l~xd%w3CPOJ#L>bPq{%?kjDvuVvvZ)JGp(xf+`d;jk(??VUX1 zVe2$EA-PM_-un>nEWcDuC()VobUHm|PA_+pIjCe!+0WC~uc!U`8S@qnhsLTRltl+n zOlyhjyY#lTslZti!3jrqcfyf3GA^j>cNa|riyAOJn=0AnbInJ&jNE$z3E1=b)aBk| z$qck;-nG&EG0e;GAfc|P0v3SYua`@qgt%(1IVv_AbS>E9`c)Gn=6t@RblX7c7A zdg-YNz#-IF2>^~RcF6kl{Mr1O34b&~qYM8S<9_s!)M@FI__gdjEl!~jyqkhn9ToSc zg=0Tih?rWjlrTOdQwvTZiewBj<%Mcwbe^VJCA$le`&N zFa5iOr56$IyF-n$HbZ>5 z)EAOoR{mL1?3O@!{l7ue*Q?*9+aU?1Q7XM@7frV#bN$B7CoUGVV}CD$@jJhgGiOtFZI-=v&dpFE#ffMiP) z$wjatTfv$yIC<$t^G<%f+eL-$5m{fFJEN(g^`Tl6u@pJo%)Iqu+~uShOnDr1?>SmD$;Nt9X#g!* zQ;(<3_WU|~?*|IB5xT;yL;}Ms!#C}#6j4gGeF^?hOxDijI*1^7*obOCf;9>wcV(>k z6jsswldacwRYG5>($`STGA`*~#Ybr6zGU;JX^?XU23H~6rskbDTS7Ee2aLzV&*>3O zlp3vZd@y5bIf%$n!)ZkALrAiC0s4K6AG`PZC6a~<(UXw zwaYjp;~sHE)#=FRSFT8$*+_Wh%(SB0+~G$^(&Dc7ANSVazddUHM=i}Fg~7AH^Uy;F z*b)?k0nbN}xmkL;1ebg}ns>)B+QFj*%j&IMWQCaZVU4_tSagmiE^&5kqpM*rvK5ml zgoX9q4`uRoPII%Rs_hte(qNh%XM{ItN5eAP29MD?=Ua0#k|#)eo@=H+_G&L}RpxeC z=lS(7J%4GOE1h{~)I=133(HuUp0FsjHr!YtWI{`;^s)j>7D?tC@WHk2u=@mxypm^} zu>FXm=sv}R080r@S@QVmhYh!n9!)M?ITPq3D*CItS+_G6hJz;2k>A>D8XiPZ1Wvc- z^!5Ffj0+E@oOn zVM46><3D}`wtrUA(Sf01Bx!m`#O_c8!lOt_7{9T*wl(R!5WO}n`F}a|-_f-Ai(2E2 zvB`|xjs8to8b&I3K=GX@72zjl@{UXS`|&BVOmLSGn7wtTMYKFi2pV|t_9v&5Pv`;q zAUH2P;E}N&IX?3C=OT}SZOn7c(-`ipHg>w*K5hXS)`O8-V^6dTCHPFi_MVv!@utty z?F&iQ2y*sQm+0xLOtx&DUX=ui5Ki2DLCoyQ;gve1UJXyhrQs$XB-D7wF27je=QykP zkKp>HC=v_QP|EDo4ok7f)9qpY75QYf`v)M!n?sdYQyaCBa!V1!I~>x1tQQX58)FE=(O~;_uy-kqi1v$HIxTD1 zNO2K<9}I7OOn*6cyWpFp6GfZ8ks|Qc-4p`bX*v%4e2f0D>>90Kl~XM)^z#sKqbh-Nx{8Zw0sr2IW=hpdGPU4isz z8Vd5}78EFm-uQ4){pfP<+&D!sIqM91>c z?)`=UsH;etM0TMAob|m4!gyAa)1oQ2kRh_U+=wd3We^v~1t>^#gZ|q%OL{n#r@I7e zfFvx%gU?`-LAQ^aZ~=NM$QE;E1M*T4SBm!0+6WXM?L0}8Lwk`WiICL9@xsQ^%IA|L zT;}r_rKU1IN{{p$6fj;F-B0yD}G;-d!*RfRP{6EtIe-lI1NAcQi|a^ znb~}u?nk6&pyS0wuC^ehkQAkMv>SigTC`%4e;Ml%lyEM98zOY#7Fd`-9ld7&ffu(y z45aaD@GzFnXV25wS*XRW+lT(rZbO90k|y1g?MN~^r@IpaE~O08dGu)}2xfD`Uj(Rz zHqfgNJqF%JtP#t}pA@TfxRMoNX#j;wDLeJ;76D1xO4Y+3HgeCl$b^G)LBO7)$PC$^ zw1iX2v>mUIX zPB#BX9c;UE^)*Q3r4bnf!wXqGE>--)#~?2#lEUKY5TnlOHVeIM#9Fq`Ri;HlT5IqO9I==C1Atbv+d~ZXZMT=x>hV)r+Xf<-f_CU4ua~qH! zZ7WjDI8XRT-bH)wJw-2F=!jVB>`OV9yT>{+7IDVu)Q3_*<3a)jV<_JjUqcgY8)z?T z8#c#@hO$ssjw?Wc}bhaoe zN6icRO1_8&;&6n!JkTzhyXP}ZOqIE}Y;DzW=XB}h#e1Ex1%D;4?rQ?ls9~)E3fhSg z=Itc}4;90OV=AQ+2}iuG@rHO|{@fVq+6-i{Ax_mn=7SaD~+ zr&1`<@l=jw#XDM14|Hw^MH47mNK0~~45>-YgAF~rJ5|jII%hE{JuPl6wT1b5d_C4V zb7k8R1F3+qT5qFuZAI#DQ3X_xUmnuhL0*h#sDDYpdzxaqU3_(U*oSg4yRS6&r#F?c>A*oD; zrS;a2;r*bnB!@FFldX^5npR_{3J{8|Rds~CDnNu8C8VL$_%qjY#nY!CUv(Tg`Xian zsn6&*Me~UANQ(#L>&a`OnkJNa8;x$mlPsc=F%l`^l?hptszQglbThNlkqvP0I4w8I z*3O68i^BhmO)ixA)3lcovK7*OX!_TGE^?~1!dIAc81Tapwl;4`?uFu=&W9&dYZ>8M zX#WCbG4p>$(=Qp{$+S09L&l^CS7I=i|0kkl13H zBbUTv0FMANZjCDQs;x;!A{6Vx##c3*m?mS?v6;ncE?R*Zru9|D=4eeFzLI51?tm`M zsamnt?(Sl;hOI1lgX=`rP3HdcIOx%BSb+syQ(G>+9G#KtaZ``LM*Ipq@D*V9VA_$Q4Bzy{GByJ+f&Fp`AM48fz)BZJ|TjEv6F zRgQ~;Gy*?zv{xjPTc_w^z?r8;wjj>R0&J>|J?Kk06C^q%Dym#aRAOYrY#Cs686L}) zO?N|wW9x?Ese5qs%a|t#C?!$alUt0jNTc6Y`}98ixYO-M+CC`mti6E3l*}gd-tbe^ zxVele?a}yG|FoM+n#a23Y5|gaXGDucCyU{9$(_81B+&GoKQ-q&OdRP+ofU}M2IOEi> z(<_<$5Co*)(cZLUNvklB9FDO_5f;1N>bQ1#tw%+ek;vj#e{MEGV9GATWu()x^XGg% zo*v{!>-X@HDQT^8P1u~fs0wvaloq|xY&!Z))0yNZ<0g7!3Yx>yvuZ(&`Jf(btXkql z2G`Lu>x`vyzco=vPkKl=b=6U?h-d-KrSVt2>U1A6v3*a{Jm5%~J@WmG`(MBKIviIlDrV6tZ(dp(rXj0VIl9>TK?y)5VN>sFB7JKZU#m!75Y) zkfc_CfC%#?f)Xn+B~l0`myZ?sjc@b2L`%YWTS@!UrC);Dt8xhu0#sY=IJ0shLLVd| z=-p0Vi(>c+Hg{;sri5$(3FMzBPWQEPu`FhH85bG%=>5lsg;@Rmj}N}LNp4T0HG70I znq^f9jMO>Z&r{u%tH%OPNs~9#C4!{z+V<>Bhty#w1sK6BW~60L))a@l@@c+8qup;~ zv|}}&9Elvcy4zkiS`%uqiF9#GSWVgj%j+rqsr`$`gK&5d(Wf@ zyIkSKFq$Q@gy^ugv3ow9Cf*iV-MsS@QjDFGb5fklct56ZAUcnDdXo5=hOAo@WDNMT z+#1t_x1n2QuSh#uFypzhnqJexPnm+4K^SL&xs>i#Ew%2SNHmo!9DF1dtCGeRU^PnI z6^sgeXiM6Nx2UyDx@$8Y&IjuUGMX(_zSm)i_2q;W(xU(?R8Y?17iXzESXvr^%4LvF zX*2WDZ->ur=ZD)Z&Ko4g$OHb$ZZZuHGatduZIV2=|E2oR+xRK>`SBkzPq#0cf8EwE zweui{2r2S;*{`*|;L;aeWw;2J6I?NEgHc@gOZRipPGFJ{6z|NI+g$FRNDs&f^&__o zM&E1789j)GLT^r65V;O-$I>Im-TN(c*tku-x%Ao8TXXi0v_Fz=)DI?>2=!>)D;237 z95!RNaY{dRJn@(`-TaKSVbA6dS&w5qS}sZx0Ij2hb*A?KzU{Qf*H;A5=r$tg=k#D!Jt%YMBk@=jNl%Fc5AF9-^^T!@J5(tb6`9KpNh5Mm zEXs3B)I;Mcgq5%}=Ax8r5FWn35Cmg9xIRW#E)4!!%W! z&*hV%jgczJ;eIyup?QY_6GBHIi;=8Y(|lQY;o9N2ucSFma&hAEn6F4DGfQ*9_|L$e zFN&Hwt(g-!R^bE6aoot163PDo_7^S5X}D*tk2@b9V+?6GBRL=E)AeDrf<=c=@jDS_ zzF}(aoR6pya%o+gYA09>t<7vS(Z+|$t;tPfbok?#&h=1U?2gK$)upA4q?LY@ehgB0 zX0Y^(XWB20XSD3c7$O46xO1)bd>X|Rj^$gX)mef!dRkeyYEro(qOtPTNuXG0nbjTX zW9F+h7{ry>kIG&HatEx5?_uw)k+O!Xc1YRQwynZaI@!rwG({*ynP;tm?hI)D!jTP; z3&6WsgsaTu9Q$#dgL^)o=Op829^Uj$Z8Q=L#UcIko~p^&Zk<)m=i)heIVWSdq=$L1 zEweevywRQ_b9uI4kiTN@!G420UK+4RNs-X&hp;55QK^vz%Fr1+sZqNRIhx#9y7n%5 zFj&MO{BO_alQ~B_7(Ywx%VH%tx086b@z8N(#7X4L8v+vIm~CgGkPz|Y-1=-xMPlVqFi2DZv&iPD z;)!k0`J0IWO)3x%n(UY0l2;f5hNMC~vz82^m;|I3goXQ&!3-6h%csadvaxOf z0A=*ac9#}%xOC!MqD7+=_2boVk>!QP?vbU!JtpSpy9u_M$u7QJsdP`U8bxD0r5`$0 zoXeW|BbN}wrj-@ywc^l8fi*WXBrfZ_HOtRp~DlhOL+0c7fhA;R1hXH zs&xnk#kpoi+Ok~67F0t%ansLpSw!`_X){*i+iYSgCT(e-+-4D)aQ2vCD_Zw{xBg-M zmhG1P0I6Vi7Dr4rIiK8a+vlK)<0qPMZtx zId%yIWFls^&XWPZL#m>C5^K{wwwxbV+mNPSEU@6O+tOYMBM)0^{q&fgUUP4A9CT?F zsq?$NoJ)EyJlr;bsF1n{Rk2kSFPDliq69B^qg>j}Rc5915)y00zJ_9!n?oNBpPm$= zNnmK~pB2kdl!~=)d}Pcf$keuEF2Ah2r&5dxV&{NH9(s7CQ4raj^XoAmbNZyFdbB=R zG#dHAdovf6r&B{(Y$xpZGR-%b%9+sNYy`r_jtW;GYO853` zH))4xQ_e0!WrO)VR+fB;M9^_a@5hITP#Yn#f*W@be7X3dAK zUzRgdsW*7bWCl%2bMh@5pJ1gsv>j)%&6Ol%Qz_ft8OYr$sjn?XbO1`L6t)spAVzR6VG0aiKVpaKEwkTk-IbTVcyOZEogTfW-oV2#ZQp zh1`gQe_>nvo$(^*bDqt83 zp4oHFF5Goc?YNYS^~pT-_K^M@_lLAo`^g*BQ7gmSB9j&XO8Zg1>WMX17{VnlWrfR0 zYNVd2!IT~^-nHEhsVGKg+p;#W0_gyabqS<~?4rSb(*9lefk~;TuvLkxiU7Q^Eaw$P zBABdbrm2`u**T5KE{`P%zegY0|sTQ1&%|AD^643z+ z{9dnzI)?48Ti5NEy2}M;P)>@1>r0(ZaB4DmJyc#DoUd{xDD_>7o`)%0d0pViXhl+r zctK1_IYa7Nc7xVVK{8#$@oDO&xAKfakIVejFU^WBuD;c1F{4W|3&>*H6|R#;)M7y za*x?My?}K=YH6HO`ft^ysKjBOY);t%!A~2S$a^@3iN~DJb#~6>l%VZhrGJqQ-Xagf znZbmVV3$N6YL#1D2pIa!zhf=c&i) zf5iB)`px={+YK$vIbH4QI~kF?vjDp$tU!@Mc`3EMZ&9t-TSi;nWk)=ALWF=xdWoZp@FfJ$ zo!u)j%ne{3EthDkM3;xbCA;xf`*}f!FLt5eq9xlAd%F2`6=-MruDHEbwkt8oMh#4% zhbMWSXRHQ2oYllBRgcJ8W>1cFi+PY=X;Ia%o1aITa46+9s^W6V;?1Q3^F*F>8#@pa zmB3yWYnjQXe+c)lKrg0An#nC6_eBFswDhrop%P%)c1 zPW_7-CMCwR%0D%!_|Y0 z&4yJ4jCf68)69aVg;~5A7wN?ClUBMhju34vipcOOX49&zWN~*HDXkJ|TlN$dOagh& zJ=UYfxeL{0pqH`Q3>i^^-Xd`OGYbrxHY3j!XPO2ff~g7iyJ2t&kkq-{mZy$e9*&Qz zMHbM7@+;C}J=|;)wQET4xhCIsbwg^z?H#}iZssJuK@ggF7-2EfRxTz(KUb7C*K7CC zTCE!wb3kVdzu-iI^Z9(bWnMZz5!juv)LO>#Ip;KGxu-TBl&S7&K=MT>RmDz%B-()1 z-p8pQtKV03*JcFK&PJ@%3@c<4^WA z{cP(?&wus#^JWvQ%LZ;}Y&RJO#SIo(;L3A%GlP7s_TIf~49zj^7;C7M<5EremDH|5 z3?!y|aN5?aE2%ZoNG;91q~J!% zL4bd8c}|^3K!So~?KZ~lwgV}qNSk&(IJt1YT{1>vrPi;WzvSH%T1tH>5t&sJSV5pY zT$u!A%dh@^v5b%8z*t1ewPjw4u>4k|W*8y5s9hOn zN~&6~Bq0)hZL(6xMvR)BaV4tG%A)(DdL8E};^Ef?3oaHb>!xcKDe%JJ@ z`}{&A0~-?z$ua*^`}_w4Je+F=8ewOmP>51Dsse3V}uNj7|+Le{8zW1 zpX|N0J)^@rStl1@l*-|Ix%wIT>QF-TST9Cm=p-2iR=lC8nF4X^>3%Ms7R@B5sC5uq zM(^E?c#llbm<6cN@BjEgk@=X<^R#LbA$gf*AeV;60#$86wo18ew<(-y>!brU8y96_ z=C1c&xyj2oQ)FZL_CV!b$X7fnVcCx@@o{_5?no4)Fy4MErY0kDt|YeO4(*%|beG+v7=2L^hc_qv!_p0FQUNl#9AB4e5>yS0k|vIti3MiJRw!k->yM{}UomOg;Me5n?CbvD}xvRi8a}l{tmOYoK74 z<8_`SB2LLm#hG%MMTAO;z-IWIPM(|dmSFESg!BaodgGJ41&le&EY5R19}>lCtzdTr zAhOD(8C<bQFQKFBG4yNnk6Ul5&J-Xiap@S1ENOSq-oV?7J3qnG zeTI{hS55@se%LI)ld^`NX_ctkLE}{`=d@VrfGtE3gaynkotVnGsf}N#U!49Jx5~%_ z5Z1^&21KmrMiFJ=m+O1%)En@>^*u#iXbPO0WVwBLao9SX8!xu1Ywt3GtZYjTEdKFI z&_q|sG{DywY~(XnX-RTZV1!gFu#dub!>R-$NT=yq{y6hFHHg67Q_M=YqmM&GkLuZV z4-S?Z8sX{mw%oPF@8~+*Xlf{mQfsdLamzLwMP2|woUJ6KzH6hABC_ZH3Lukb`RFgtEco564pLU^34qx%OjuCrGfb7(4o%`i;7kQv zfz*6CZFNArE|Ep0i*y|;uJ*#y^F_Q|iXg&*BiEwdnIl)?L) z@fGQ*)HAb(XK>G!?8u3>M5h$Zr#yc7?eqTuho^P0eUX;v00000NkvXX Hu0mjfdGW*& literal 0 HcmV?d00001 diff --git a/Minecraft.Client/Assets/RmlUiAssets/panorama/Background_Night.png b/Minecraft.Client/Assets/RmlUiAssets/panorama/Background_Night.png new file mode 100644 index 0000000000000000000000000000000000000000..b915b51ab87e9ae8efe7722f22efadc47adcda86 GIT binary patch literal 145173 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�v=pvLsB2V)>omtGU&ay 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%0Dwx+Ro=UA(smXqVYSjJnCTM8hS^mJ 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

g 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 literal 0 HcmV?d00001 diff --git a/Minecraft.Client/CMakeLists.txt b/Minecraft.Client/CMakeLists.txt index 27885cd6..bdcea8f7 100644 --- a/Minecraft.Client/CMakeLists.txt +++ b/Minecraft.Client/CMakeLists.txt @@ -20,8 +20,7 @@ if(PLATFORM_NAME STREQUAL "Windows64") set_target_properties(Minecraft.Client PROPERTIES WIN32_EXECUTABLE TRUE) endif() -target_include_directories(Minecraft.Client - PRIVATE +target_include_directories(Minecraft.Client PRIVATE "${CMAKE_BINARY_DIR}/generated/" # This is for the generated BuildVer.h "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/Windows64/Libs/Iggy/inc" @@ -29,6 +28,73 @@ target_include_directories(Minecraft.Client "${CMAKE_CURRENT_SOURCE_DIR}/Common/Libs/zlib/inc" "${CMAKE_SOURCE_DIR}/include/" ) + +if(PLATFORM_NAME STREQUAL "Windows64") + target_include_directories(Minecraft.Client PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/Windows64" + "${CMAKE_CURRENT_SOURCE_DIR}/Windows64/Libs/4JLibs/inc" + "${CMAKE_CURRENT_SOURCE_DIR}/Windows64/Libs/4JLibs/impls/Windows_Libs/Render/vendor" + "${CMAKE_CURRENT_SOURCE_DIR}/Windows64/Libs/4JLibs/impls/Windows_Libs/Render/vendor/DirectXMath" + "${CMAKE_SOURCE_DIR}/Minecraft.World/x64headers" + ) + + # Build RmlUi as a static library (skip samples, tests, backends) + set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) + + # Download FreeType for RmlUi font engine + include(FetchContent) + set(CMAKE_POLICY_VERSION_MINIMUM 3.5) + set(FT_DISABLE_ZLIB ON CACHE BOOL "" FORCE) + set(FT_DISABLE_BZIP2 ON CACHE BOOL "" FORCE) + set(FT_DISABLE_PNG ON CACHE BOOL "" FORCE) + set(FT_DISABLE_HARFBUZZ ON CACHE BOOL "" FORCE) + set(FT_DISABLE_BROTLI ON CACHE BOOL "" FORCE) + FetchContent_Declare( + freetype + GIT_REPOSITORY https://gitlab.freedesktop.org/freetype/freetype.git + GIT_TAG VER-2-13-2 + GIT_SHALLOW TRUE + ) + FetchContent_MakeAvailable(freetype) + if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND MINGW) + target_compile_definitions(freetype PRIVATE _INC_SETJMPEX) + endif() + if(TARGET freetype-interface AND NOT TARGET Freetype::Freetype) + add_library(Freetype::Freetype ALIAS freetype-interface) + endif() + set(RMLUI_SAMPLES OFF CACHE BOOL "" FORCE) + set(RMLUI_TESTS OFF CACHE BOOL "" FORCE) + set(RMLUI_LUA_BINDINGS OFF CACHE BOOL "" FORCE) + set(RMLUI_LOTTIE_PLUGIN OFF CACHE BOOL "" FORCE) + set(RMLUI_SVG_PLUGIN OFF CACHE BOOL "" FORCE) + set(RMLUI_FONT_ENGINE "freetype" CACHE STRING "" FORCE) + set(CMAKE_DISABLE_FIND_PACKAGE_Freetype ON CACHE BOOL "" FORCE) + add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/Windows64/Libs/RmlUi" "${CMAKE_BINARY_DIR}/rmlui") + target_link_libraries(Minecraft.Client PRIVATE rmlui) + + # Add RmlUi include path only to files that include RmlUi headers, + # not to the whole target (avoids PCH macro conflicts with windows.h). + set(RMLUI_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/Windows64/Libs/RmlUi/Include") + set(_RMLUI_SOURCES + "${CMAKE_CURRENT_SOURCE_DIR}/Common/UI/UIScene_HelpAndOptionsMenu.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/Common/UI/UIScene_HelpAndOptionsMenu.h" + "${CMAKE_CURRENT_SOURCE_DIR}/Common/UI/UIScene_PauseMenu.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/Common/UI/UIScene_PauseMenu.h" + "${CMAKE_CURRENT_SOURCE_DIR}/Common/UI/UIScene_SettingsMenu.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/Common/UI/UIScene_SettingsMenu.h" + "${CMAKE_CURRENT_SOURCE_DIR}/Common/UI/UIScene_SkinSelectMenu.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/Common/UI/UIScene_SkinSelectMenu.h" + "${CMAKE_CURRENT_SOURCE_DIR}/Windows64/RmlUi_Renderer_D3D11.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/Windows64/RmlUi_Renderer_D3D11.h" + "${CMAKE_CURRENT_SOURCE_DIR}/Windows64/RmlUi_SystemInterface_Win64.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/Windows64/RmlUi_SystemInterface_Win64.h" + "${CMAKE_CURRENT_SOURCE_DIR}/Windows64/RmlUi_FileInterface.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/Windows64/RmlUi_FileInterface.h" + "${CMAKE_CURRENT_SOURCE_DIR}/RmlManager.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/RmlManager.h" + ) + set_property(SOURCE ${_RMLUI_SOURCES} APPEND PROPERTY INCLUDE_DIRECTORIES "${RMLUI_INCLUDE_DIR}") +endif() target_compile_definitions(Minecraft.Client PRIVATE ${MINECRAFT_SHARED_DEFINES} ) @@ -46,33 +112,28 @@ target_link_libraries(Minecraft.Client PRIVATE Minecraft.World d3d11 d3dcompiler + ws2_32 + $<$:legacy_stdio_definitions> + # Allow our override of CRT internal symbols (__acrt_iob_func) to take priority + $<$>:-Wl,--allow-multiple-definition> + # Group libs with circular deps so linker rescan resolves all symbols + $<$>:-Wl,--start-group> XInput9_1_0 - wsock32 - legacy_stdio_definitions $<$: # Debug 4J libraries - "${CMAKE_CURRENT_SOURCE_DIR}/Windows64/Libs/4JLibs/libs/4J_Input_d.lib" - "${CMAKE_CURRENT_SOURCE_DIR}/Windows64/Libs/4JLibs/libs/4J_Storage_d.lib" - "${CMAKE_CURRENT_SOURCE_DIR}/Windows64/Libs/4JLibs/libs/4J_Render_PC_d.lib" - # Debug Discord libraries - "${CMAKE_CURRENT_SOURCE_DIR}/Windows64/Libs/Discord/libs/discord-rpc_d.lib" - # Debug ZSTD libraries - "${CMAKE_CURRENT_SOURCE_DIR}/Windows64/Libs/zstd/libs/libzstd_static.lib" - # Debug ZLIB libraries - "${CMAKE_CURRENT_SOURCE_DIR}/Common/Libs/zlib/libs/zlibstatic.lib" + "${CMAKE_CURRENT_SOURCE_DIR}/Windows64/Libs/4JLibs/libs/Debug/4J_Input_d.a" + "${CMAKE_CURRENT_SOURCE_DIR}/Windows64/Libs/4JLibs/libs/Debug/4J_Storage_d.a" + "${CMAKE_CURRENT_SOURCE_DIR}/Windows64/Libs/4JLibs/libs/Debug/4J_Render_PC_d.a" > $<$>: # Release 4J libraries - "${CMAKE_CURRENT_SOURCE_DIR}/Windows64/Libs/4JLibs/libs/4J_Input.lib" - "${CMAKE_CURRENT_SOURCE_DIR}/Windows64/Libs/4JLibs/libs/4J_Storage.lib" - "${CMAKE_CURRENT_SOURCE_DIR}/Windows64/Libs/4JLibs/libs/4J_Render_PC.lib" - # Release Discord libraries - "${CMAKE_CURRENT_SOURCE_DIR}/Windows64/Libs/Discord/libs/discord-rpc.lib" - # Release ZSTD libraries - "${CMAKE_CURRENT_SOURCE_DIR}/Windows64/Libs/zstd/libs/libzstd_static.lib" - # Release ZLIB libraries - "${CMAKE_CURRENT_SOURCE_DIR}/Common/Libs/zlib/libs/zlibstatic.lib" + "${CMAKE_CURRENT_SOURCE_DIR}/Windows64/Libs/4JLibs/libs/Release/4J_Input.a" + "${CMAKE_CURRENT_SOURCE_DIR}/Windows64/Libs/4JLibs/libs/Release/4J_Storage.a" + "${CMAKE_CURRENT_SOURCE_DIR}/Windows64/Libs/4JLibs/libs/Release/4J_Render_PC.a" > + $<$>:z> + $<$>:zstd> + $<$>:-Wl,--end-group> ) # Iggy libs diff --git a/Minecraft.Client/Common/Consoles_App.h b/Minecraft.Client/Common/Consoles_App.h index ced6b2a3..54d1f46b 100644 --- a/Minecraft.Client/Common/Consoles_App.h +++ b/Minecraft.Client/Common/Consoles_App.h @@ -875,6 +875,7 @@ private: bool m_bResetNether; + wstring m_currentSaveFolderName; // 4J Added: for hardcore world deletion on Win64 DWORD m_dwRequiredTexturePackID; #ifdef _XBOX_ONE vector m_vTMSPPData; @@ -889,6 +890,11 @@ private: #endif + // 4J Added - for hardcore world deletion on Win64 +public: + void SetCurrentSaveFolderName(const wstring& name) { m_currentSaveFolderName = name; } + const wstring& GetCurrentSaveFolderName() const { return m_currentSaveFolderName; } + // 4J-PB - language and locale functions public: diff --git a/Minecraft.Client/Common/UI/IUIScene_PauseMenu.cpp b/Minecraft.Client/Common/UI/IUIScene_PauseMenu.cpp index 01ee6c59..a56d6bba 100644 --- a/Minecraft.Client/Common/UI/IUIScene_PauseMenu.cpp +++ b/Minecraft.Client/Common/UI/IUIScene_PauseMenu.cpp @@ -1,17 +1,19 @@ +#include "stdafx.h" #include "IUIScene_PauseMenu.h" -#include "..\..\Minecraft.h" -#include "..\..\MinecraftServer.h" -#include "..\..\MultiPlayerLevel.h" -#include "..\..\ProgressRenderer.h" -#include "..\..\..\Minecraft.World\net.minecraft.world.level.h" -#include "..\..\..\Minecraft.World\net.minecraft.world.phys.h" -#include "..\..\TexturePackRepository.h" -#include "..\..\TexturePack.h" -#include "..\..\DLCTexturePack.h" -#include "..\..\..\Minecraft.World\StringHelpers.h" +#include "../../Minecraft.h" +#include "../../MinecraftServer.h" +#include "../../MultiPlayerLevel.h" +#include "../../ProgressRenderer.h" +#include "../../../Minecraft.World/net.minecraft.world.level.h" +#include "../../../Minecraft.World/net.minecraft.world.phys.h" +#include "../../TexturePackRepository.h" +#include "../../TexturePack.h" +#include "../../../Minecraft.World/StringHelpers.h" +#ifndef _XBOX #include "UI.h" -#include "../DLC/DLCPack.h" +#endif + int IUIScene_PauseMenu::ExitGameDialogReturned(void *pParam,int iPad,C4JStorage::EMessageResult result) { @@ -40,43 +42,14 @@ int IUIScene_PauseMenu::ExitGameSaveDialogReturned(void *pParam,int iPad,C4JStor #endif // Exit with or without saving - // Decline means save in this dialog - if(result==C4JStorage::EMessage_ResultDecline || result==C4JStorage::EMessage_ResultThirdOption) + if(result==C4JStorage::EMessage_ResultAccept || result==C4JStorage::EMessage_ResultDecline || result==C4JStorage::EMessage_ResultThirdOption) { + if( result==C4JStorage::EMessage_ResultAccept ) // Cancel + { + return 0; + } if( result==C4JStorage::EMessage_ResultDecline ) // Save { - // 4J-PB - Is the player trying to save but they are using a trial texturepack ? - if(!Minecraft::GetInstance()->skins->isUsingDefaultSkin()) - { - TexturePack *tPack = Minecraft::GetInstance()->skins->getSelected(); - DLCTexturePack *pDLCTexPack=static_cast(tPack); - - DLCPack *pDLCPack=pDLCTexPack->getDLCInfoParentPack();//tPack->getDLCPack(); - if(!pDLCPack->hasPurchasedFile( DLCManager::e_DLCType_Texture, L"" )) - { -#ifdef _XBOX - // upsell - ULONGLONG ullOfferID_Full; - // get the dlc texture pack - DLCTexturePack *pDLCTexPack=(DLCTexturePack *)tPack; - - app.GetDLCFullOfferIDForPackID(pDLCTexPack->getDLCParentPackId(),&ullOfferID_Full); - - // tell sentient about the upsell of the full version of the skin pack - TelemetryManager->RecordUpsellPresented(iPad, eSet_UpsellID_Texture_DLC, ullOfferID_Full & 0xFFFFFFFF); -#endif - - UINT uiIDA[2]; - uiIDA[0]=IDS_CONFIRM_OK; - uiIDA[1]=IDS_CONFIRM_CANCEL; - - // Give the player a warning about the trial version of the texture pack - //ui.RequestAlertMessage(IDS_WARNING_DLC_TRIALTEXTUREPACK_TITLE, IDS_WARNING_DLC_TRIALTEXTUREPACK_TEXT, uiIDA, 2, ProfileManager.GetPrimaryPad() , &IUIScene_PauseMenu::WarningTrialTexturePackReturned, pParam); - - return S_OK; - } - } - // does the save exist? bool bSaveExists; StorageManager.DoesSaveExist(&bSaveExists); @@ -98,7 +71,7 @@ int IUIScene_PauseMenu::ExitGameSaveDialogReturned(void *pParam,int iPad,C4JStor MinecraftServer::getInstance()->setSaveOnExit( true ); } } - else + else // EMessage_ResultThirdOption - Don't Save { // been a few requests for a confirm on exit without saving UINT uiIDA[2]; @@ -212,141 +185,6 @@ int IUIScene_PauseMenu::ExitGameDeclineSaveReturned(void *pParam,int iPad,C4JSto -int IUIScene_PauseMenu::WarningTrialTexturePackReturned(void *pParam,int iPad,C4JStorage::EMessageResult result) -{ -#if defined(__PS3__) || defined(__ORBIS__) || defined(__PSVITA__) - if(result==C4JStorage::EMessage_ResultAccept) - { - if(!ProfileManager.IsSignedInLive(iPad)) - { - // you're not signed in to PSN! - - } - else - { - // 4J-PB - need to check this user can access the store - bool bContentRestricted; - ProfileManager.GetChatAndContentRestrictions(iPad,true,nullptr,&bContentRestricted,nullptr); - if(bContentRestricted) - { - UINT uiIDA[1]; - uiIDA[0]=IDS_CONFIRM_OK; - ui.RequestAlertMessage(IDS_ONLINE_SERVICE_TITLE, IDS_CONTENT_RESTRICTION, uiIDA, 1, iPad); - } - else - { - // need to get info on the pack to see if the user has already downloaded it - TexturePack *tPack = Minecraft::GetInstance()->skins->getSelected(); - DLCTexturePack *pDLCTexPack=(DLCTexturePack *)tPack; - - // retrieve the store name for the skin pack - DLCPack *pDLCPack=pDLCTexPack->getDLCInfoParentPack();//tPack->getDLCPack(); - const char *pchPackName=wstringtofilename(pDLCPack->getName()); - app.DebugPrintf("Texture Pack - %s\n",pchPackName); - SONYDLC *pSONYDLCInfo=app.GetSONYDLCInfo((char *)pchPackName); - - if(pSONYDLCInfo!=nullptr) - { - char chName[42]; - char chSkuID[SCE_NP_COMMERCE2_SKU_ID_LEN]; - - memset(chSkuID,0,SCE_NP_COMMERCE2_SKU_ID_LEN); - // find the info on the skin pack - // we have to retrieve the skuid from the store info, it can't be hardcoded since Sony may change it. - // So we assume the first sku for the product is the one we want -#ifdef __ORBIS__ - sprintf(chName,"%s",pSONYDLCInfo->chDLCKeyname); -#else - sprintf(chName,"%s-%s",app.GetCommerceCategory(),pSONYDLCInfo->chDLCKeyname); -#endif - app.GetDLCSkuIDFromProductList(chName,chSkuID); - // 4J-PB - need to check for an empty store -#if defined __ORBIS__ || defined __PSVITA__ || defined __PS3__ - if(app.CheckForEmptyStore(iPad)==false) -#endif - { - if(app.DLCAlreadyPurchased(chSkuID)) - { - app.DownloadAlreadyPurchased(chSkuID); - } - else - { - app.Checkout(chSkuID); - } - } - } - } - } - } -#endif // - -#ifdef _XBOX_ONE - IUIScene_PauseMenu* pScene = (IUIScene_PauseMenu*)pParam; - - if(result==C4JStorage::EMessage_ResultAccept) - { - if(ProfileManager.IsSignedIn(iPad)) - { - if (ProfileManager.IsSignedInLive(iPad)) - { - TexturePack *tPack = Minecraft::GetInstance()->skins->getSelected(); - // get the dlc texture pack - DLCTexturePack *pDLCTexPack=(DLCTexturePack *)tPack; - - DLCPack *pDLCPack=pDLCTexPack->getDLCInfoParentPack(); - - DLC_INFO *pDLCInfo=app.GetDLCInfoForProductName((WCHAR *)pDLCPack->getName().c_str()); - - StorageManager.InstallOffer(1,(WCHAR *)pDLCInfo->wsProductId.c_str(),nullptr,nullptr); - - // the license change coming in when the offer has been installed will cause this scene to refresh - } - else - { - // 4J-JEV: Fix for XB1: #165863 - XR-074: Compliance: With no active network connection user is unable to convert from Trial to Full texture pack and is not messaged why. - UINT uiIDA[1] = { IDS_CONFIRM_OK }; - ui.RequestErrorMessage(IDS_PRO_NOTONLINE_TITLE, IDS_PRO_XBOXLIVE_NOTIFICATION, uiIDA, 1, iPad); - } - } - } - -#endif - -#ifdef _XBOX - IUIScene_PauseMenu* pScene = (IUIScene_PauseMenu*)pParam; - - //pScene->m_bIgnoreInput = false; - pScene->ShowScene( true ); - if(result==C4JStorage::EMessage_ResultAccept) - { - if(ProfileManager.IsSignedIn(iPad)) - { - ULONGLONG ullIndexA[1]; - - TexturePack *tPack = Minecraft::GetInstance()->skins->getSelected(); - // get the dlc texture pack - DLCTexturePack *pDLCTexPack=(DLCTexturePack *)tPack; - - // Need to get the parent packs id, since this may be one of many child packs with their own ids - app.GetDLCFullOfferIDForPackID(pDLCTexPack->getDLCParentPackId(),&ullIndexA[0]); - - // need to allow downloads here, or the player would need to quit the game to let the download of a texture pack happen. This might affect the network traffic, since the download could take all the bandwidth... - XBackgroundDownloadSetMode(XBACKGROUND_DOWNLOAD_MODE_ALWAYS_ALLOW); - - StorageManager.InstallOffer(1,ullIndexA,nullptr,nullptr); - } - } - else - { - TelemetryManager->RecordUpsellResponded(iPad, eSet_UpsellID_Texture_DLC, ( pScene->m_pDLCPack->getPurchaseOfferId() & 0xFFFFFFFF ), eSen_UpsellOutcome_Declined); - } -#endif - - - return 0; -} - - int IUIScene_PauseMenu::SaveWorldThreadProc( LPVOID lpParameter ) { bool bAutosave=static_cast(lpParameter); @@ -407,11 +245,73 @@ int IUIScene_PauseMenu::ExitWorldThreadProc( void* lpParameter ) return S_OK; } +#ifdef _WINDOWS64 +static bool Win64_DeleteSaveDirectory(const wchar_t* wPath) +{ + wchar_t wSearch[MAX_PATH]; + swprintf_s(wSearch, MAX_PATH, L"%s\\*", wPath); + WIN32_FIND_DATAW fd; + HANDLE hFind = FindFirstFileW(wSearch, &fd); + if (hFind != INVALID_HANDLE_VALUE) + { + do + { + if (wcscmp(fd.cFileName, L".") == 0 || wcscmp(fd.cFileName, L"..") == 0) continue; + wchar_t wChild[MAX_PATH]; + swprintf_s(wChild, MAX_PATH, L"%s\\%s", wPath, fd.cFileName); + if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + Win64_DeleteSaveDirectory(wChild); + else + DeleteFileW(wChild); + } while (FindNextFileW(hFind, &fd)); + FindClose(hFind); + } + return RemoveDirectoryW(wPath) != 0; +} +#endif // _WINDOWS64 + // This function performs the meat of exiting from a level. It should be called from a thread other than the main thread. void IUIScene_PauseMenu::_ExitWorld(LPVOID lpParameter) { +#ifndef MINECRAFT_SERVER_BUILD Minecraft *pMinecraft=Minecraft::GetInstance(); + // 4J Added: Capture hardcore delete info before the server is destroyed +#ifdef _WINDOWS64 + bool shouldDeleteHardcoreWorld = false; + wstring hardcoreSaveFolderName; + if (MinecraftServer::getInstance() != nullptr && MinecraftServer::getInstance()->getDeleteWorldOnExit()) + { + shouldDeleteHardcoreWorld = true; + // Try 1: Use the save folder name stored by UIScene_LoadMenu::StartGameFromSave (works for existing saves) + hardcoreSaveFolderName = app.GetCurrentSaveFolderName(); + if (!hardcoreSaveFolderName.empty()) + { + app.DebugPrintf("Hardcore mode: save folder from app = '%ls'\n", hardcoreSaveFolderName.c_str()); + } + // Try 2: StorageManager (may work for new saves after first autosave) + if (hardcoreSaveFolderName.empty()) + { + char szSaveFolder[MAX_SAVEFILENAME_LENGTH] = {}; + StorageManager.GetSaveUniqueFilename(szSaveFolder); + if (szSaveFolder[0] != '\0') + { + wchar_t wSaveFolder[MAX_SAVEFILENAME_LENGTH] = {}; + mbstowcs(wSaveFolder, szSaveFolder, MAX_SAVEFILENAME_LENGTH - 1); + hardcoreSaveFolderName = wSaveFolder; + app.DebugPrintf("Hardcore mode: save folder from StorageManager = '%s'\n", szSaveFolder); + } + } + // Try 3: Stored during loadLevel + if (hardcoreSaveFolderName.empty()) + { + hardcoreSaveFolderName = MinecraftServer::getInstance()->getSaveFolderName(); + app.DebugPrintf("Hardcore mode: save folder from server = '%ls'\n", hardcoreSaveFolderName.c_str()); + } + MinecraftServer::getInstance()->setDeleteWorldOnExit(false); + } +#endif + int exitReasonStringId = pMinecraft->progressRenderer->getCurrentTitle(); int exitReasonTitleId = IDS_CONNECTION_LOST; @@ -622,6 +522,17 @@ void IUIScene_PauseMenu::_ExitWorld(LPVOID lpParameter) { Sleep(1); } + // 4J Added: Hardcore mode — delete world save data now that the server is fully stopped +#ifdef _WINDOWS64 + if (shouldDeleteHardcoreWorld && !hardcoreSaveFolderName.empty()) + { + wchar_t wFolderPath[MAX_PATH] = {}; + swprintf_s(wFolderPath, MAX_PATH, L"Windows64\\GameHDD\\%s", hardcoreSaveFolderName.c_str()); + app.DebugPrintf("Hardcore mode: Deleting world save folder '%ls'\n", wFolderPath); + Win64_DeleteSaveDirectory(wFolderPath); + } +#endif + pMinecraft->setLevel(nullptr,exitReasonStringId,nullptr,saveStats); TelemetryManager->Flush(); @@ -649,6 +560,7 @@ void IUIScene_PauseMenu::_ExitWorld(LPVOID lpParameter) // Make sure we don't think saving is disabled in the menus StorageManager.SetSaveDisabled(false); #endif +#endif } @@ -705,4 +617,4 @@ int IUIScene_PauseMenu::DisableAutosaveDialogReturned(void *pParam,int iPad,C4JS app.SetAction(iPad,eAppAction_SaveGame); } return 0; -} \ No newline at end of file +} diff --git a/Minecraft.Client/Common/UI/IUIScene_PauseMenu.h b/Minecraft.Client/Common/UI/IUIScene_PauseMenu.h index 7233df3a..7ce4e949 100644 --- a/Minecraft.Client/Common/UI/IUIScene_PauseMenu.h +++ b/Minecraft.Client/Common/UI/IUIScene_PauseMenu.h @@ -2,15 +2,11 @@ class IUIScene_PauseMenu { -protected: - DLCPack *m_pDLCPack; - public: static int ExitGameDialogReturned(void *pParam,int iPad,C4JStorage::EMessageResult result); static int ExitGameSaveDialogReturned(void *pParam,int iPad,C4JStorage::EMessageResult result); static int ExitGameAndSaveReturned(void *pParam,int iPad,C4JStorage::EMessageResult result); static int ExitGameDeclineSaveReturned(void *pParam,int iPad,C4JStorage::EMessageResult result); - static int WarningTrialTexturePackReturned(void *pParam,int iPad,C4JStorage::EMessageResult result); static int SaveGameDialogReturned(void *pParam,int iPad,C4JStorage::EMessageResult result); static int EnableAutosaveDialogReturned(void *pParam,int iPad,C4JStorage::EMessageResult result); static int DisableAutosaveDialogReturned(void *pParam,int iPad,C4JStorage::EMessageResult result); diff --git a/Minecraft.Client/Common/UI/IUIScene_StartGame.cpp b/Minecraft.Client/Common/UI/IUIScene_StartGame.cpp index 18d71289..ab70cc91 100644 --- a/Minecraft.Client/Common/UI/IUIScene_StartGame.cpp +++ b/Minecraft.Client/Common/UI/IUIScene_StartGame.cpp @@ -12,8 +12,7 @@ IUIScene_StartGame::IUIScene_StartGame(int iPad, UILayer *parentLayer) : UIScene m_bShowTexturePackDescription = false; m_iSetTexturePackDescription = -1; - Minecraft *pMinecraft = Minecraft::GetInstance(); - m_currentTexturePackIndex = pMinecraft->skins->getTexturePackIndex(0); + m_currentTexturePackIndex = 0; } void IUIScene_StartGame::HandleDLCMountingComplete() diff --git a/Minecraft.Client/Common/UI/UIControl_EnchantmentButton.cpp b/Minecraft.Client/Common/UI/UIControl_EnchantmentButton.cpp index c71f47c2..b5199010 100644 --- a/Minecraft.Client/Common/UI/UIControl_EnchantmentButton.cpp +++ b/Minecraft.Client/Common/UI/UIControl_EnchantmentButton.cpp @@ -5,6 +5,7 @@ #include "..\..\MultiplayerLocalPlayer.h" #include "../../Font.h" #include "..\..\..\Minecraft.World\StringHelpers.h" +#include #include "..\..\Windows64\KeyboardMouseInput.h" diff --git a/Minecraft.Client/Common/UI/UIEnums.h b/Minecraft.Client/Common/UI/UIEnums.h index d3c60419..752755af 100644 --- a/Minecraft.Client/Common/UI/UIEnums.h +++ b/Minecraft.Client/Common/UI/UIEnums.h @@ -81,6 +81,7 @@ enum EUIScene eUIComponent_Chat, eUIScene_ReinstallMenu, eUIScene_SkinSelectMenu, + eUIScene_AchievementsMenu, eUIScene_TextEntry, eUIScene_InGameHostOptionsMenu, eUIScene_InGamePlayerOptionsMenu, diff --git a/Minecraft.Client/Common/UI/UIScene.h b/Minecraft.Client/Common/UI/UIScene.h index 57054e1c..39ffd506 100644 --- a/Minecraft.Client/Common/UI/UIScene.h +++ b/Minecraft.Client/Common/UI/UIScene.h @@ -167,7 +167,7 @@ public: // Returns true if this scene has focus for the pad passed in virtual bool hasFocus(int iPad) { return bHasFocus && iPad == m_iPad; } - void gainFocus(); + virtual void gainFocus(); void loseFocus(); virtual void updateTooltips(); diff --git a/Minecraft.Client/Common/UI/UIScene_HelpAndOptionsMenu.cpp b/Minecraft.Client/Common/UI/UIScene_HelpAndOptionsMenu.cpp index 71926a4f..5240a60f 100644 --- a/Minecraft.Client/Common/UI/UIScene_HelpAndOptionsMenu.cpp +++ b/Minecraft.Client/Common/UI/UIScene_HelpAndOptionsMenu.cpp @@ -1,235 +1,260 @@ +#include "stdafx.h" + +// Non-RmlUi includes that depend on Windows macros #include "UI.h" +#include "../../Minecraft.h" + +// RmlUi includes (Windows macros temporarily suppressed) +#pragma push_macro("byte") +#pragma push_macro("GetNextSibling") +#pragma push_macro("GetFirstChild") +#undef byte +#undef GetNextSibling +#undef GetFirstChild + #include "UIScene_HelpAndOptionsMenu.h" -#include "..\..\Minecraft.h" +#include "RmlManager.h" +#include "../../Windows64/KeyboardMouseInput.h" +#include +#include -UIScene_HelpAndOptionsMenu::UIScene_HelpAndOptionsMenu(int iPad, void *initData, UILayer *parentLayer) : UIScene(iPad, parentLayer) +#pragma pop_macro("GetFirstChild") +#pragma pop_macro("GetNextSibling") +#pragma pop_macro("byte") + +UIScene_HelpAndOptionsMenu::UIScene_HelpAndOptionsMenu(int iPad, void *initData, UILayer *parentLayer) + : UIScene(iPad, parentLayer) + , m_document(nullptr) { - // Setup all the Iggy references we need for this scene - initialiseMovie(); - - m_bNotInGame=(Minecraft::GetInstance()->level==nullptr); - - m_buttons[BUTTON_HAO_CHANGESKIN].init(IDS_CHANGE_SKIN,BUTTON_HAO_CHANGESKIN); - m_buttons[BUTTON_HAO_HOWTOPLAY].init(IDS_HOW_TO_PLAY,BUTTON_HAO_HOWTOPLAY); - m_buttons[BUTTON_HAO_CONTROLS].init(IDS_CONTROLS,BUTTON_HAO_CONTROLS); - m_buttons[BUTTON_HAO_SETTINGS].init(IDS_SETTINGS,BUTTON_HAO_SETTINGS); - m_buttons[BUTTON_HAO_CREDITS].init(IDS_CREDITS,BUTTON_HAO_CREDITS); - //m_buttons[BUTTON_HAO_REINSTALL].init(app.GetString(IDS_REINSTALL_CONTENT),BUTTON_HAO_REINSTALL); - m_buttons[BUTTON_HAO_DEBUG].init(IDS_DEBUG_SETTINGS,BUTTON_HAO_DEBUG); - - /* 4J-TomK - we should never remove a control before the other buttons controls are initialised! - (because vita touchboxes are rebuilt on remove since the remaining positions might change) */ - // We don't have a reinstall content, so remove the button - removeControl( &m_buttons[BUTTON_HAO_REINSTALL], false ); - -#ifndef _DEBUG - removeControl( &m_buttons[BUTTON_HAO_DEBUG], false); -#else - if(!app.DebugSettingsOn()) removeControl( &m_buttons[BUTTON_HAO_DEBUG], false); -#endif - -#ifdef _XBOX_ONE - // 4J-PB - in order to buy the skin packs, we need the signed offer ids for them, which we get in the availability info - // we need to retrieve this info though, so do it here - app.AddDLCRequest(e_Marketplace_Content); // content is skin packs, texture packs and mash-up packs - - // we also need to mount the local DLC so we can tell what's been purchased - app.StartInstallDLCProcess(iPad); -#endif - - - - // 4J-PB - do not need a storage device to see this menu - just need one when you choose to re-install them - bool bNotInGame=(Minecraft::GetInstance()->level==nullptr); - - // any content to be re-installed? - if(m_iPad==ProfileManager.GetPrimaryPad() && bNotInGame) + Rml::Context* ctx = RmlManager::Get().GetContext(); + if (!ctx) { - // We should show the reinstall menu - app.DebugPrintf("Reinstall Menu required...\n"); + app.DebugPrintf("[RmlHelpAndOptions] No RmlUi context available\n"); + return; + } + + m_document = ctx->LoadDocument("HelpOptions.rml"); + if (m_document) + { + m_document->Show(); + + bool bNotInGame = (Minecraft::GetInstance()->level == nullptr); + + // Change Skin: hide if not full version + if (!ProfileManager.IsFullVersion()) + { + auto* el = m_document->GetElementById("change_skin"); + if (el) el->SetProperty("display", "none"); + } + + // Credits: hide in splitscreen + if (app.GetLocalPlayerCount() > 1) + { + auto* el = m_document->GetElementById("credits"); + if (el) el->SetProperty("display", "none"); + } + + // Register button click listeners + m_document->GetElementById("change_skin")->AddEventListener(Rml::EventId::Click, this); + m_document->GetElementById("how_to_play")->AddEventListener(Rml::EventId::Click, this); + m_document->GetElementById("controls")->AddEventListener(Rml::EventId::Click, this); + m_document->GetElementById("settings")->AddEventListener(Rml::EventId::Click, this); + m_document->GetElementById("credits")->AddEventListener(Rml::EventId::Click, this); + + // Focus first visible button + m_document->GetElementById("change_skin")->Focus(); } else { - removeControl( &m_buttons[BUTTON_HAO_REINSTALL], false); + app.DebugPrintf("[RmlHelpAndOptions] Failed to load HelpOptions.rml\n"); } - if(app.GetLocalPlayerCount()>1) - { - // no credits in splitscreen - removeControl( &m_buttons[BUTTON_HAO_CREDITS], false); + ui.HidePressStart(); -#if TO_BE_IMPLEMENTED - app.AdjustSplitscreenScene(m_hObj,&m_OriginalPosition,m_iPad,false); -#endif - if(ProfileManager.GetPrimaryPad()!=m_iPad) - { - removeControl( &m_buttons[BUTTON_HAO_REINSTALL], false); - } - } - - if(!ProfileManager.IsFullVersion() )//|| ProfileManager.IsGuest(m_iPad)) - { - removeControl( &m_buttons[BUTTON_HAO_CHANGESKIN], false); - } - - // 4J-TomK Moved horizontal resize check to the end to prevent horizontal scaling for buttons that might get removed anyways (debug options for example) - doHorizontalResizeCheck(); - - //StorageManager.TMSPP_GetUserQuotaInfo(C4JStorage::eGlobalStorage_TitleUser,iPad); - //StorageManager.WebServiceRequestGetFriends(iPad); + TelemetryManager->RecordMenuShown(m_iPad, eUIScene_HelpAndOptionsMenu, 0); } UIScene_HelpAndOptionsMenu::~UIScene_HelpAndOptionsMenu() { + if (m_document) + { + m_document->GetElementById("change_skin")->RemoveEventListener(Rml::EventId::Click, this); + m_document->GetElementById("how_to_play")->RemoveEventListener(Rml::EventId::Click, this); + m_document->GetElementById("controls")->RemoveEventListener(Rml::EventId::Click, this); + m_document->GetElementById("settings")->RemoveEventListener(Rml::EventId::Click, this); + m_document->GetElementById("credits")->RemoveEventListener(Rml::EventId::Click, this); + m_document->Close(); + m_document = nullptr; + } } -wstring UIScene_HelpAndOptionsMenu::getMoviePath() +void UIScene_HelpAndOptionsMenu::gainFocus() { - if(app.GetLocalPlayerCount() > 1) + if (!bHasFocus && stealsFocus()) { - return L"HelpAndOptionsMenuSplit"; + bHasFocus = true; + updateTooltips(); + updateComponents(); + if (!m_bFocussedOnce) + { + } + handleGainFocus(m_bFocussedOnce); + if (bHasFocus) + m_bFocussedOnce = true; + if (m_document) + m_document->Show(); } - else +} + +void UIScene_HelpAndOptionsMenu::handleLoseFocus() +{ + if (m_document) + m_document->Hide(); +} + +void UIScene_HelpAndOptionsMenu::reloadMovie(bool force) +{ +} + +void UIScene_HelpAndOptionsMenu::tick() +{ + m_hasTickedOnce = true; + + UIScene::tick(); + + Rml::Context* ctx = RmlManager::Get().GetContext(); + if (!ctx || !bHasFocus) + return; + + ctx->ProcessMouseMove(g_KBMInput.GetMouseX(), g_KBMInput.GetMouseY(), 0); + + if (g_KBMInput.IsMouseButtonPressed(KeyboardMouseInput::MOUSE_LEFT)) + ctx->ProcessMouseButtonDown(0, 0); + if (g_KBMInput.IsMouseButtonReleased(KeyboardMouseInput::MOUSE_LEFT)) + ctx->ProcessMouseButtonUp(0, 0); + if (g_KBMInput.IsMouseButtonPressed(KeyboardMouseInput::MOUSE_RIGHT)) + ctx->ProcessMouseButtonDown(1, 0); + if (g_KBMInput.IsMouseButtonReleased(KeyboardMouseInput::MOUSE_RIGHT)) + ctx->ProcessMouseButtonUp(1, 0); + + int wheelDelta = g_KBMInput.GetMouseWheel(); + if (wheelDelta != 0) + ctx->ProcessMouseWheel(wheelDelta / -120, 0); + + ctx->Update(); +} + +void UIScene_HelpAndOptionsMenu::render(S32 width, S32 height, C4JRender::eViewportType viewport) +{ +} + +void UIScene_HelpAndOptionsMenu::handleInput(int iPad, int key, bool repeat, bool pressed, bool released, bool &handled) +{ + Rml::Context* ctx = RmlManager::Get().GetContext(); + if (!ctx) + return; + + if (!pressed) + return; + + switch (key) { - return L"HelpAndOptionsMenu"; + case ACTION_MENU_UP: + ctx->ProcessKeyDown(Rml::Input::KI_UP, 0); + handled = true; + break; + case ACTION_MENU_DOWN: + ctx->ProcessKeyDown(Rml::Input::KI_DOWN, 0); + handled = true; + break; + case ACTION_MENU_OK: + if (g_KBMInput.IsKeyPressed(VK_RETURN)) + { + ctx->ProcessKeyDown(Rml::Input::KI_RETURN, 0); + handled = true; + } + break; + case ACTION_MENU_CANCEL: + case ACTION_MENU_PAUSEMENU: + navigateBack(); + handled = true; + break; + default: + break; + } +} + +void UIScene_HelpAndOptionsMenu::ProcessEvent(Rml::Event& event) +{ + const Rml::String& id = event.GetCurrentElement()->GetId(); + + if (event == Rml::EventId::Click) + { + if (id == "change_skin") OnChangeSkin(); + else if (id == "how_to_play") OnHowToPlay(); + else if (id == "controls") OnControls(); + else if (id == "settings") OnSettings(); + else if (id == "credits") OnCredits(); } } void UIScene_HelpAndOptionsMenu::updateTooltips() { - ui.SetTooltips( m_iPad, IDS_TOOLTIPS_SELECT,IDS_TOOLTIPS_BACK); } void UIScene_HelpAndOptionsMenu::updateComponents() { - bool bNotInGame=(Minecraft::GetInstance()->level==nullptr); - if(bNotInGame) + bool bNotInGame = (Minecraft::GetInstance()->level == nullptr); + if (bNotInGame) { - m_parentLayer->showComponent(m_iPad,eUIComponent_Panorama,true); - m_parentLayer->showComponent(m_iPad,eUIComponent_Logo,true); + m_parentLayer->showComponent(m_iPad, eUIComponent_Panorama, true); + m_parentLayer->showComponent(m_iPad, eUIComponent_Logo, true); } else { - m_parentLayer->showComponent(m_iPad,eUIComponent_Panorama,false); - - if( app.GetLocalPlayerCount() == 1 ) m_parentLayer->showComponent(m_iPad,eUIComponent_Logo,true); - else m_parentLayer->showComponent(m_iPad,eUIComponent_Logo,false); + m_parentLayer->showComponent(m_iPad, eUIComponent_Panorama, false); + if (app.GetLocalPlayerCount() == 1) + m_parentLayer->showComponent(m_iPad, eUIComponent_Logo, true); + else + m_parentLayer->showComponent(m_iPad, eUIComponent_Logo, false); } } -void UIScene_HelpAndOptionsMenu::handleReload() +void UIScene_HelpAndOptionsMenu::navigateBack() { -#ifndef _DEBUG // def _FINAL_BUILD // disable debug settings in release builds - removeControl( &m_buttons[BUTTON_HAO_DEBUG], false); -#else - if(!app.DebugSettingsOn()) removeControl( &m_buttons[BUTTON_HAO_DEBUG], false); -#endif - - // 4J-PB - do not need a storage device to see this menu - just need one when you choose to re-install them - bool bNotInGame=(Minecraft::GetInstance()->level==nullptr); - - // any content to be re-installed? - if(m_iPad==ProfileManager.GetPrimaryPad() && bNotInGame) - { - // We should show the reinstall menu - app.DebugPrintf("Reinstall Menu required...\n"); - } - else - { - removeControl( &m_buttons[BUTTON_HAO_REINSTALL], false); - } - - if(app.GetLocalPlayerCount()>1) - { - // no credits in splitscreen - removeControl( &m_buttons[BUTTON_HAO_CREDITS], false); - -#if TO_BE_IMPLEMENTED - app.AdjustSplitscreenScene(m_hObj,&m_OriginalPosition,m_iPad,false); -#endif - if(ProfileManager.GetPrimaryPad()!=m_iPad) - { - removeControl( &m_buttons[BUTTON_HAO_REINSTALL], false); - } - } - - if(!ProfileManager.IsFullVersion() )//|| ProfileManager.IsGuest(m_iPad)) - { -#if TO_BE_IMPLEMENTED - m_Buttons[BUTTON_HAO_CHANGESKIN].SetEnable(FALSE); - m_Buttons[BUTTON_HAO_CHANGESKIN].EnableInput(FALSE); - // set the focus to the second button - - XuiElementSetUserFocus(m_Buttons[BUTTON_HAO_HOWTOPLAY].m_hObj, m_iPad); -#endif - } - - if(!ProfileManager.IsFullVersion() )//|| ProfileManager.IsGuest(m_iPad)) - { - removeControl( &m_buttons[BUTTON_HAO_CHANGESKIN], false); - } - - doHorizontalResizeCheck(); + ui.PlayUISFX(eSFX_Back); + UIScene::navigateBack(); } -void UIScene_HelpAndOptionsMenu::handleInput(int iPad, int key, bool repeat, bool pressed, bool released, bool &handled) -{ - //app.DebugPrintf("UIScene_DebugOverlay handling input for pad %d, key %d, down- %s, pressed- %s, released- %s\n", iPad, key, down?"TRUE":"FALSE", pressed?"TRUE":"FALSE", released?"TRUE":"FALSE"); - - ui.AnimateKeyPress(m_iPad, key, repeat, pressed, released); - - switch(key) - { - case ACTION_MENU_CANCEL: - if(pressed && !repeat) - { - navigateBack(); - } - break; - case ACTION_MENU_OK: -#ifdef __ORBIS__ - case ACTION_MENU_TOUCHPAD_PRESS: -#endif - //CD - Added for audio - if(pressed) - { - ui.PlayUISFX(eSFX_Press); - } - - case ACTION_MENU_UP: - case ACTION_MENU_DOWN: - sendInputToMovie(key, repeat, pressed, released); - break; - } -} - -void UIScene_HelpAndOptionsMenu::handlePress(F64 controlId, F64 childId) +void UIScene_HelpAndOptionsMenu::OnChangeSkin() { ui.PlayUISFX(eSFX_Press); - - switch(static_cast(controlId)) - { - case BUTTON_HAO_CHANGESKIN: - ui.NavigateToScene(m_iPad, eUIScene_SkinSelectMenu); - break; - case BUTTON_HAO_HOWTOPLAY: - ui.NavigateToScene(m_iPad, eUIScene_HowToPlayMenu); - break; - case BUTTON_HAO_CONTROLS: - ui.NavigateToScene(m_iPad, eUIScene_ControlsMenu); - break; - case BUTTON_HAO_SETTINGS: - ui.NavigateToScene(m_iPad, eUIScene_SettingsMenu); - break; - case BUTTON_HAO_CREDITS: - ui.NavigateToScene(m_iPad, eUIScene_Credits); - break; - case BUTTON_HAO_REINSTALL: - ui.NavigateToScene(m_iPad, eUIScene_ReinstallMenu); - break; - case BUTTON_HAO_DEBUG: - ui.NavigateToScene(m_iPad, eUIScene_DebugOptions); - break; - } + ui.NavigateToScene(m_iPad, eUIScene_SkinSelectMenu); } + +void UIScene_HelpAndOptionsMenu::OnHowToPlay() +{ + ui.PlayUISFX(eSFX_Press); + ui.NavigateToScene(m_iPad, eUIScene_HowToPlayMenu); +} + +void UIScene_HelpAndOptionsMenu::OnControls() +{ + ui.PlayUISFX(eSFX_Press); + ui.NavigateToScene(m_iPad, eUIScene_ControlsMenu); +} + +void UIScene_HelpAndOptionsMenu::OnSettings() +{ + ui.PlayUISFX(eSFX_Press); + ui.NavigateToScene(m_iPad, eUIScene_SettingsMenu); +} + +void UIScene_HelpAndOptionsMenu::OnCredits() +{ + ui.PlayUISFX(eSFX_Press); + ui.NavigateToScene(m_iPad, eUIScene_Credits); +} + + diff --git a/Minecraft.Client/Common/UI/UIScene_HelpAndOptionsMenu.h b/Minecraft.Client/Common/UI/UIScene_HelpAndOptionsMenu.h index 203011d3..a8c720d4 100644 --- a/Minecraft.Client/Common/UI/UIScene_HelpAndOptionsMenu.h +++ b/Minecraft.Client/Common/UI/UIScene_HelpAndOptionsMenu.h @@ -1,50 +1,57 @@ #pragma once +#pragma push_macro("byte") +#pragma push_macro("GetNextSibling") +#pragma push_macro("GetFirstChild") +#undef byte +#undef GetNextSibling +#undef GetFirstChild + #include "UIScene.h" +#include +#include -#define BUTTON_HAO_CHANGESKIN 0 -#define BUTTON_HAO_HOWTOPLAY 1 -#define BUTTON_HAO_CONTROLS 2 -#define BUTTON_HAO_SETTINGS 3 -#define BUTTON_HAO_CREDITS 4 -#define BUTTON_HAO_REINSTALL 5 -#define BUTTON_HAO_DEBUG 6 -#define BUTTONS_HAO_MAX BUTTON_HAO_DEBUG + 1 +// NOTE: pop_macro at end of file -class UIScene_HelpAndOptionsMenu : public UIScene +class UIScene_HelpAndOptionsMenu : public UIScene, public Rml::EventListener { -private: - UIControl_Button m_buttons[BUTTONS_HAO_MAX]; - UI_BEGIN_MAP_ELEMENTS_AND_NAMES(UIScene) - UI_MAP_ELEMENT( m_buttons[BUTTON_HAO_CHANGESKIN], "Button1") - UI_MAP_ELEMENT( m_buttons[BUTTON_HAO_HOWTOPLAY], "Button2") - UI_MAP_ELEMENT( m_buttons[BUTTON_HAO_CONTROLS], "Button3") - UI_MAP_ELEMENT( m_buttons[BUTTON_HAO_SETTINGS], "Button4") - UI_MAP_ELEMENT( m_buttons[BUTTON_HAO_CREDITS], "Button5") - UI_MAP_ELEMENT( m_buttons[BUTTON_HAO_REINSTALL], "Button6") - UI_MAP_ELEMENT( m_buttons[BUTTON_HAO_DEBUG], "Button7") - UI_END_MAP_ELEMENTS_AND_NAMES() - - bool m_bNotInGame; public: UIScene_HelpAndOptionsMenu(int iPad, void *initData, UILayer *parentLayer); virtual ~UIScene_HelpAndOptionsMenu(); - virtual EUIScene getSceneType() { return eUIScene_HelpAndOptionsMenu;} - - virtual void updateTooltips(); - virtual void updateComponents(); + virtual EUIScene getSceneType() { return eUIScene_HelpAndOptionsMenu; } -protected: - // TODO: This should be pure virtual in this class - virtual wstring getMoviePath(); + virtual void tick(); + virtual void render(S32 width, S32 height, C4JRender::eViewportType viewport); -public: - virtual void handleReload(); - - // INPUT virtual void handleInput(int iPad, int key, bool repeat, bool pressed, bool released, bool &handled); + // Override focus/movie lifecycle for RmlUi (no Iggy movie) + virtual void gainFocus() override; + virtual void reloadMovie(bool force) override; + + // Rml::EventListener + void ProcessEvent(Rml::Event& event) override; + protected: - void handlePress(F64 controlId, F64 childId); -}; \ No newline at end of file + virtual wstring getMoviePath() { return L""; } + + virtual void handleLoseFocus() override; + virtual void updateTooltips() override; + virtual void updateComponents() override; + + void navigateBack(); + + void OnChangeSkin(); + void OnHowToPlay(); + void OnControls(); + void OnSettings(); + void OnCredits(); + +private: + Rml::ElementDocument* m_document; +}; + +#pragma pop_macro("GetFirstChild") +#pragma pop_macro("GetNextSibling") +#pragma pop_macro("byte") diff --git a/Minecraft.Client/Common/UI/UIScene_LoadCreateJoinMenu.cpp b/Minecraft.Client/Common/UI/UIScene_LoadCreateJoinMenu.cpp index 6b1bbc6d..9e3ac8d3 100644 --- a/Minecraft.Client/Common/UI/UIScene_LoadCreateJoinMenu.cpp +++ b/Minecraft.Client/Common/UI/UIScene_LoadCreateJoinMenu.cpp @@ -286,11 +286,16 @@ UIScene_LoadCreateJoinMenu::UIScene_LoadCreateJoinMenu(int iPad, void* initData, #endif // block input if we're waiting for DLC to install, and wipe the saves list. The end of dlc mounting custom message will fill the list again +#if defined(_WINDOWS64) + // On Windows64, DLC is file-based and doesn't block loading; initialise immediately + Initialise(); +#else if (app.StartInstallDLCProcess(m_iPad) == true || app.DLCInstallPending()) // if we're waiting for DLC to mount, don't fill the save list. The custom message on end of dlc mounting will do that m_bIgnoreInput = true; else Initialise(); +#endif #ifdef __PSVITA__ if (CGameNetworkManager::usingAdhocMode() && SQRNetworkManager_AdHoc_Vita::GetAdhocStatus()) @@ -554,6 +559,7 @@ void UIScene_LoadCreateJoinMenu::handleGainFocus(bool navBack) m_bIgnoreInput = false; +#if !defined(_WINDOWS64) if (app.StartInstallDLCProcess(m_iPad) == false) m_bIgnoreInput = false; @@ -567,6 +573,7 @@ void UIScene_LoadCreateJoinMenu::handleGainFocus(bool navBack) m_controlSavesTimer.setVisible(true); } +#endif if (m_bMultiplayerAllowed) diff --git a/Minecraft.Client/Common/UI/UIScene_PauseMenu.cpp b/Minecraft.Client/Common/UI/UIScene_PauseMenu.cpp index 8e1bdc6a..9c9ba14a 100644 --- a/Minecraft.Client/Common/UI/UIScene_PauseMenu.cpp +++ b/Minecraft.Client/Common/UI/UIScene_PauseMenu.cpp @@ -1,736 +1,488 @@ +#include "stdafx.h" + +// Non-RmlUi includes that depend on Windows macros #include "UI.h" +#include "../../MinecraftServer.h" +#include "../../MultiPlayerLocalPlayer.h" + +// RmlUi includes (Windows macros temporarily suppressed) +#pragma push_macro("byte") +#pragma push_macro("GetNextSibling") +#pragma push_macro("GetFirstChild") +#undef byte +#undef GetNextSibling +#undef GetFirstChild + #include "UIScene_PauseMenu.h" -#include "..\..\MinecraftServer.h" -#include "..\..\MultiplayerLocalPlayer.h" -#include "..\..\TexturePackRepository.h" -#include "..\..\TexturePack.h" -#include "..\..\DLCTexturePack.h" -#include "../DLC/DLCPack.h" -#include "../Tutorial/TutorialMode.h" -#include "..\..\..\Minecraft.World\StringHelpers.h" +#include "RmlManager.h" +#include "../../Windows64/KeyboardMouseInput.h" +#include +#include -UIScene_PauseMenu::UIScene_PauseMenu(int iPad, void *initData, UILayer *parentLayer) : UIScene(iPad, parentLayer) +#pragma pop_macro("GetFirstChild") +#pragma pop_macro("GetNextSibling") +#pragma pop_macro("byte") + +UIScene_PauseMenu::UIScene_PauseMenu(int iPad, void *initData, UILayer *parentLayer) + : UIScene(iPad, parentLayer) + , m_document(nullptr) + , m_bIgnoreInput(false) + , m_bExitDialog(false) + , m_bSaveDialog(false) { - // Setup all the Iggy references we need for this scene - initialiseMovie(); - m_bIgnoreInput=false; - m_eAction=eAction_None; - - m_buttons[BUTTON_PAUSE_RESUMEGAME].init(app.GetString(IDS_RESUME_GAME),BUTTON_PAUSE_RESUMEGAME); - m_buttons[BUTTON_PAUSE_HELPANDOPTIONS].init(app.GetString(IDS_HELP_AND_OPTIONS),BUTTON_PAUSE_HELPANDOPTIONS); - m_buttons[BUTTON_PAUSE_LEADERBOARDS].init(app.GetString(IDS_LEADERBOARDS),BUTTON_PAUSE_LEADERBOARDS); -#ifdef _DURANGO - m_buttons[BUTTON_PAUSE_XBOXHELP].init(app.GetString(IDS_XBOX_HELP_APP), BUTTON_PAUSE_XBOXHELP); -#else - m_buttons[BUTTON_PAUSE_ACHIEVEMENTS].init(app.GetString(IDS_ACHIEVEMENTS),BUTTON_PAUSE_ACHIEVEMENTS); -#endif -#if defined(_XBOX_ONE) || defined(__ORBIS__) - m_bTrialTexturePack = false; - if(!Minecraft::GetInstance()->skins->isUsingDefaultSkin()) + Rml::Context* ctx = RmlManager::Get().GetContext(); + if (!ctx) { - TexturePack *tPack = Minecraft::GetInstance()->skins->getSelected(); - DLCTexturePack *pDLCTexPack=(DLCTexturePack *)tPack; - - m_pDLCPack=pDLCTexPack->getDLCInfoParentPack();//tPack->getDLCPack(); - - if(!m_pDLCPack->hasPurchasedFile( DLCManager::e_DLCType_Texture, L"" )) - { - m_bTrialTexturePack = true; - } + app.DebugPrintf("[RmlPauseMenu] No RmlUi context available\n"); + return; } - // 4J-TomK - check for all possible labels being fed into BUTTON_PAUSE_SAVEGAME (Bug 163775) - // this has to be done before button initialisation! - wchar_t saveButtonLabels[2][256]; - swprintf( saveButtonLabels[0], 256, L"%ls", app.GetString( IDS_SAVE_GAME )); - swprintf( saveButtonLabels[1], 256, L"%ls", app.GetString( IDS_DISABLE_AUTOSAVE )); - m_buttons[BUTTON_PAUSE_SAVEGAME].setAllPossibleLabels(2,saveButtonLabels); - - if(app.GetGameHostOption(eGameHostOption_DisableSaving) || m_bTrialTexturePack) + m_document = ctx->LoadDocument("PauseMenu.rml"); + if (m_document) { - m_savesDisabled = true; - m_buttons[BUTTON_PAUSE_SAVEGAME].init(app.GetString(IDS_SAVE_GAME),BUTTON_PAUSE_SAVEGAME); + app.DebugPrintf("[RmlPauseMenu] Document loaded OK, showing...\n"); + m_document->Show(); + app.DebugPrintf("[RmlPauseMenu] Document shown, visible=%d, context docs=%d\n", + m_document->IsVisible(), ctx->GetNumDocuments()); + m_document->GetElementById("resume")->AddEventListener(Rml::EventId::Click, this); + m_document->GetElementById("help_options")->AddEventListener(Rml::EventId::Click, this); + m_document->GetElementById("achievements")->AddEventListener(Rml::EventId::Click, this); + m_document->GetElementById("save_game")->AddEventListener(Rml::EventId::Click, this); + m_document->GetElementById("exit_game")->AddEventListener(Rml::EventId::Click, this); + m_document->GetElementById("exit_save")->AddEventListener(Rml::EventId::Click, this); + m_document->GetElementById("exit_nosave")->AddEventListener(Rml::EventId::Click, this); + m_document->GetElementById("exit_cancel")->AddEventListener(Rml::EventId::Click, this); + m_document->GetElementById("save_cancel")->AddEventListener(Rml::EventId::Click, this); + m_document->GetElementById("save_confirm")->AddEventListener(Rml::EventId::Click, this); + m_document->GetElementById("resume")->Focus(); } else { - m_savesDisabled = false; - m_buttons[BUTTON_PAUSE_SAVEGAME].init(app.GetString(IDS_DISABLE_AUTOSAVE),BUTTON_PAUSE_SAVEGAME); - } -#else - m_buttons[BUTTON_PAUSE_SAVEGAME].init(app.GetString(IDS_SAVE_GAME),BUTTON_PAUSE_SAVEGAME); -#endif - m_buttons[BUTTON_PAUSE_EXITGAME].init(app.GetString(IDS_EXIT_GAME),BUTTON_PAUSE_EXITGAME); - - if(!ProfileManager.IsFullVersion()) - { - // hide the trial timer - ui.ShowTrialTimer(false); + app.DebugPrintf("[RmlPauseMenu] Failed to load PauseMenu.rml\n"); } - updateControlsVisibility(); - - doHorizontalResizeCheck(); - - // get rid of the quadrant display if it's on ui.HidePressStart(); -#if TO_BE_IMPLEMENTED - XuiSetTimer(m_hObj,IGNORE_KEYPRESS_TIMERID,IGNORE_KEYPRESS_TIME); -#endif + // parentLayer->addComponent(iPad,eUIComponent_Logo); - if( g_NetworkManager.IsLocalGame() && g_NetworkManager.GetPlayerCount() == 1 ) + if (g_NetworkManager.IsLocalGame() && g_NetworkManager.GetPlayerCount() == 1) { - app.SetXuiServerAction(ProfileManager.GetPrimaryPad(),eXuiServerAction_PauseServer,(void *)TRUE); + app.SetXuiServerAction(ProfileManager.GetPrimaryPad(), eXuiServerAction_PauseServer, (void*)TRUE); } TelemetryManager->RecordMenuShown(m_iPad, eUIScene_PauseMenu, 0); TelemetryManager->RecordPauseOrInactive(m_iPad); - - Minecraft *pMinecraft = Minecraft::GetInstance(); - if(pMinecraft != nullptr && pMinecraft->localgameModes[iPad] != nullptr ) - { - TutorialMode *gameMode = static_cast(pMinecraft->localgameModes[iPad]); - - // This just allows it to be shown - gameMode->getTutorial()->showTutorialPopup(false); - } - m_bErrorDialogRunning = false; } UIScene_PauseMenu::~UIScene_PauseMenu() { - Minecraft *pMinecraft = Minecraft::GetInstance(); - if(pMinecraft != nullptr && pMinecraft->localgameModes[m_iPad] != nullptr ) + if (m_document) { - TutorialMode *gameMode = static_cast(pMinecraft->localgameModes[m_iPad]); - - // This just allows it to be shown - gameMode->getTutorial()->showTutorialPopup(true); + m_document->GetElementById("resume")->RemoveEventListener(Rml::EventId::Click, this); + m_document->GetElementById("help_options")->RemoveEventListener(Rml::EventId::Click, this); + m_document->GetElementById("achievements")->RemoveEventListener(Rml::EventId::Click, this); + m_document->GetElementById("save_game")->RemoveEventListener(Rml::EventId::Click, this); + m_document->GetElementById("exit_game")->RemoveEventListener(Rml::EventId::Click, this); + m_document->GetElementById("exit_save")->RemoveEventListener(Rml::EventId::Click, this); + m_document->GetElementById("exit_nosave")->RemoveEventListener(Rml::EventId::Click, this); + m_document->GetElementById("exit_cancel")->RemoveEventListener(Rml::EventId::Click, this); + m_document->GetElementById("save_cancel")->RemoveEventListener(Rml::EventId::Click, this); + m_document->GetElementById("save_confirm")->RemoveEventListener(Rml::EventId::Click, this); + m_document->Close(); + m_document = nullptr; } - m_parentLayer->showComponent(m_iPad,eUIComponent_Panorama,false); - m_parentLayer->showComponent(m_iPad,eUIComponent_MenuBackground,false); - m_parentLayer->showComponent(m_iPad,eUIComponent_Logo,false); + m_parentLayer->showComponent(m_iPad, eUIComponent_Panorama, false); + m_parentLayer->showComponent(m_iPad, eUIComponent_MenuBackground, false); + m_parentLayer->showComponent(m_iPad, eUIComponent_Logo, false); } -wstring UIScene_PauseMenu::getMoviePath() +void UIScene_PauseMenu::gainFocus() { - if(app.GetLocalPlayerCount() > 1) + if (!bHasFocus && stealsFocus()) { - return L"PauseMenuSplit"; - } - else - { - return L"PauseMenu"; + bHasFocus = true; + updateTooltips(); + updateComponents(); + if (!m_bFocussedOnce) + { + // No Iggy set-focus call needed for RmlUi scene + } + handleGainFocus(m_bFocussedOnce); + if (bHasFocus) + m_bFocussedOnce = true; + if (m_document) + m_document->Show(); } } +void UIScene_PauseMenu::handleLoseFocus() +{ + if (m_document) + m_document->Hide(); +} + +void UIScene_PauseMenu::reloadMovie(bool force) +{ +} + void UIScene_PauseMenu::tick() { + // Signal the base class that we've ticked once, so it sets m_bCanHandleInput + // (the Iggy while-loop that normally sets this never runs without a loaded SWF) + m_hasTickedOnce = true; + UIScene::tick(); -#ifdef __PSVITA__ - // 4J-MGH - Need to check for installed DLC here, as we delay the installation of the key file on Vita - if(!app.DLCInstallProcessCompleted()) app.StartInstallDLCProcess(0); -#endif + Rml::Context* ctx = RmlManager::Get().GetContext(); + if (!ctx || !bHasFocus) + return; + // Forward mouse position (for hover effects and correct click targeting) + ctx->ProcessMouseMove(g_KBMInput.GetMouseX(), g_KBMInput.GetMouseY(), 0); -#if defined _XBOX_ONE || defined __ORBIS__ - if(!m_bTrialTexturePack && m_savesDisabled != (app.GetGameHostOption(eGameHostOption_DisableSaving) != 0) && ProfileManager.GetPrimaryPad() == m_iPad ) - { - // We show the save button if saves are disabled as this lets us show a prompt to enable them (via purchasing a texture pack) - if( app.GetGameHostOption(eGameHostOption_DisableSaving) ) - { - m_savesDisabled = true; - m_buttons[BUTTON_PAUSE_SAVEGAME].setLabel( app.GetString(IDS_SAVE_GAME) ); - } - else - { - m_savesDisabled = false; - m_buttons[BUTTON_PAUSE_SAVEGAME].setLabel( app.GetString(IDS_DISABLE_AUTOSAVE) ); - } - } -#endif + // Forward mouse button presses/releases + if (g_KBMInput.IsMouseButtonPressed(KeyboardMouseInput::MOUSE_LEFT)) + ctx->ProcessMouseButtonDown(0, 0); + if (g_KBMInput.IsMouseButtonReleased(KeyboardMouseInput::MOUSE_LEFT)) + ctx->ProcessMouseButtonUp(0, 0); + if (g_KBMInput.IsMouseButtonPressed(KeyboardMouseInput::MOUSE_RIGHT)) + ctx->ProcessMouseButtonDown(1, 0); + if (g_KBMInput.IsMouseButtonReleased(KeyboardMouseInput::MOUSE_RIGHT)) + ctx->ProcessMouseButtonUp(1, 0); -#ifdef __ORBIS__ - // Process the error dialog (for a patch being available) - if(m_bErrorDialogRunning) - { - SceErrorDialogStatus stat = sceErrorDialogUpdateStatus(); - if( stat == SCE_ERROR_DIALOG_STATUS_FINISHED ) - { - sceErrorDialogTerminate(); - m_bErrorDialogRunning=false; - } - } -#endif + // Forward scroll wheel + int wheelDelta = g_KBMInput.GetMouseWheel(); + if (wheelDelta != 0) + ctx->ProcessMouseWheel(wheelDelta / -120, 0); + + ctx->Update(); } -void UIScene_PauseMenu::updateTooltips() +void UIScene_PauseMenu::render(S32 width, S32 height, C4JRender::eViewportType viewport) { - bool bUserisClientSide = ProfileManager.IsSignedInLive(m_iPad); - bool bIsisPrimaryHost=g_NetworkManager.IsHost() && (ProfileManager.GetPrimaryPad()==m_iPad); - -#ifdef _XBOX_ONE - bool bDisplayBanTip = !g_NetworkManager.IsLocalGame() && !bIsisPrimaryHost && !ProfileManager.IsGuest(m_iPad); -#endif - - int iY = -1; -#if defined __PS3__ || defined __ORBIS__ - if(m_iPad == ProfileManager.GetPrimaryPad() ) iY = IDS_TOOLTIPS_GAME_INVITES; -#endif - int iRB = -1; - int iX = -1; - - if(ProfileManager.IsFullVersion()) - { - if(StorageManager.GetSaveDisabled()) - { - iX = bIsisPrimaryHost?IDS_TOOLTIPS_SELECTDEVICE:-1; -#ifdef _XBOX_ONE - iRB = bDisplayBanTip?IDS_TOOLTIPS_BANLEVEL:-1; -#endif - if( CSocialManager::Instance()->IsTitleAllowedToPostImages() && CSocialManager::Instance()->AreAllUsersAllowedToPostImages() && bUserisClientSide ) - { -#ifndef __PS3__ - iY = IDS_TOOLTIPS_SHARE; -#endif - } - } - else - { - iX = bIsisPrimaryHost?IDS_TOOLTIPS_CHANGEDEVICE:-1; -#ifdef _XBOX_ONE - iRB = bDisplayBanTip?IDS_TOOLTIPS_BANLEVEL:-1; -#endif - if( CSocialManager::Instance()->IsTitleAllowedToPostImages() && CSocialManager::Instance()->AreAllUsersAllowedToPostImages() && bUserisClientSide) - { -#ifndef __PS3__ - iY = IDS_TOOLTIPS_SHARE; -#endif - } - } - } - ui.SetTooltips( m_iPad, IDS_TOOLTIPS_SELECT,IDS_TOOLTIPS_BACK,iX,iY, -1,-1,-1,iRB); -} - -void UIScene_PauseMenu::updateComponents() -{ - m_parentLayer->showComponent(m_iPad,eUIComponent_Panorama,false); - m_parentLayer->showComponent(m_iPad,eUIComponent_MenuBackground,true); - - if( app.GetLocalPlayerCount() == 1 ) m_parentLayer->showComponent(m_iPad,eUIComponent_Logo,true); - else m_parentLayer->showComponent(m_iPad,eUIComponent_Logo,false); -} - -void UIScene_PauseMenu::handlePreReload() -{ -#if defined _XBOX_ONE || defined __ORBIS__ - if(ProfileManager.GetPrimaryPad() == m_iPad) - { - // 4J-TomK - check for all possible labels being fed into BUTTON_PAUSE_SAVEGAME (Bug 163775) - // this has to be done before button initialisation! - wchar_t saveButtonLabels[2][256]; - swprintf( saveButtonLabels[0], 256, L"%ls", app.GetString( IDS_SAVE_GAME )); - swprintf( saveButtonLabels[1], 256, L"%ls", app.GetString( IDS_DISABLE_AUTOSAVE )); - m_buttons[BUTTON_PAUSE_SAVEGAME].setAllPossibleLabels(2,saveButtonLabels); - } -#endif -} - -void UIScene_PauseMenu::handleReload() -{ - updateTooltips(); - updateControlsVisibility(); - -#if defined _XBOX_ONE || defined __ORBIS__ - if(ProfileManager.GetPrimaryPad() == m_iPad) - { - // We show the save button if saves are disabled as this lets us show a prompt to enable them (via purchasing a texture pack) - if( app.GetGameHostOption(eGameHostOption_DisableSaving) || m_bTrialTexturePack ) - { - m_savesDisabled = true; - m_buttons[BUTTON_PAUSE_SAVEGAME].setLabel( app.GetString(IDS_SAVE_GAME) ); - } - else - { - m_savesDisabled = false; - m_buttons[BUTTON_PAUSE_SAVEGAME].setLabel( app.GetString(IDS_DISABLE_AUTOSAVE) ); - } - } -#endif - - doHorizontalResizeCheck(); -} - -void UIScene_PauseMenu::updateControlsVisibility() -{ - // are we the primary player? - // 4J-PB - fix for 7844 & 7845 - - // TCR # 128: XLA Pause Menu: When in a multiplayer game as a client the Pause Menu does not have a Leaderboards option. - // TCR # 128: XLA Pause Menu: When in a multiplayer game as a client the Pause Menu does not have an Achievements option. - if(ProfileManager.GetPrimaryPad()==m_iPad) // && g_NetworkManager.IsHost()) - { - // are we in splitscreen? - // how many local players do we have? - if( app.GetLocalPlayerCount()>1 ) - { - // Hide the BUTTON_PAUSE_LEADERBOARDS and BUTTON_PAUSE_ACHIEVEMENTS - removeControl( &m_buttons[BUTTON_PAUSE_LEADERBOARDS], false ); -#ifndef _XBOX_ONE - removeControl( &m_buttons[BUTTON_PAUSE_ACHIEVEMENTS], false ); -#endif - } - - if( !g_NetworkManager.IsHost() ) - { - // Hide the BUTTON_PAUSE_SAVEGAME - removeControl( &m_buttons[BUTTON_PAUSE_SAVEGAME], false ); - } - } - else - { - // Hide the BUTTON_PAUSE_LEADERBOARDS, BUTTON_PAUSE_ACHIEVEMENTS and BUTTON_PAUSE_SAVEGAME - removeControl( &m_buttons[BUTTON_PAUSE_LEADERBOARDS], false ); -#ifndef _XBOX_ONE - removeControl( &m_buttons[BUTTON_PAUSE_ACHIEVEMENTS], false ); -#endif - removeControl( &m_buttons[BUTTON_PAUSE_SAVEGAME], false ); - } - - // is saving disabled? - if(StorageManager.GetSaveDisabled()) - { -#ifdef _XBOX - // disable save button - m_buttons[BUTTON_PAUSE_SAVEGAME].setEnable(false); -#endif - } - -#if defined(__PS3__) || defined (__PSVITA__) || defined(__ORBIS__) - // We don't have a way to display trophies/achievements, so remove the button, and we're allowed to not have it on Xbox One - removeControl( &m_buttons[BUTTON_PAUSE_ACHIEVEMENTS], false ); -#endif - } void UIScene_PauseMenu::handleInput(int iPad, int key, bool repeat, bool pressed, bool released, bool &handled) { - if(m_bIgnoreInput) - { + if (m_bIgnoreInput) return; + + if (pressed) + { + if (iPad == ProfileManager.GetPrimaryPad() && g_NetworkManager.IsLocalGame()) + { + app.SetXuiServerAction(ProfileManager.GetPrimaryPad(), eXuiServerAction_PauseServer, (void*)FALSE); + } } - //app.DebugPrintf("UIScene_DebugOverlay handling input for pad %d, key %d, down- %s, pressed- %s, released- %s\n", iPad, key, down?"TRUE":"FALSE", pressed?"TRUE":"FALSE", released?"TRUE":"FALSE"); - ui.AnimateKeyPress(iPad, key, repeat, pressed, released); + Rml::Context* ctx = RmlManager::Get().GetContext(); + if (!ctx) + return; -#ifdef _XBOX_ONE - bool bIsisPrimaryHost=g_NetworkManager.IsHost() && (ProfileManager.GetPrimaryPad()==iPad); - bool bDisplayBanTip = !g_NetworkManager.IsLocalGame() && !bIsisPrimaryHost && !ProfileManager.IsGuest(iPad); -#endif + // Translate game actions to RmlUi key events. + // Mouse clicks are forwarded in tick() via ProcessMouseButtonDown/Up, + // so here we only handle keyboard-originated actions. + if (!pressed) + return; - switch(key) + switch (key) { -#ifdef _DURANGO - case ACTION_MENU_GTC_RESUME: -#endif -#if defined(__PS3__) // not for Orbis - we want to use the pause menu (touchpad press) to select a menu item - case ACTION_MENU_PAUSEMENU: -#endif - case ACTION_MENU_CANCEL: - if(pressed) - { - if( iPad == ProfileManager.GetPrimaryPad() && g_NetworkManager.IsLocalGame() ) - { - app.SetXuiServerAction(ProfileManager.GetPrimaryPad(),eXuiServerAction_PauseServer,(void *)FALSE); - } - - ui.PlayUISFX(eSFX_Back); - navigateBack(); - if(!ProfileManager.IsFullVersion()) - { - ui.ShowTrialTimer(true); - } - } + case ACTION_MENU_UP: + ctx->ProcessKeyDown(Rml::Input::KI_UP, 0); + handled = true; + break; + case ACTION_MENU_DOWN: + ctx->ProcessKeyDown(Rml::Input::KI_DOWN, 0); + handled = true; break; case ACTION_MENU_OK: -#ifdef __ORBIS__ - case ACTION_MENU_TOUCHPAD_PRESS: -#endif - case ACTION_MENU_UP: - case ACTION_MENU_DOWN: - if(pressed) + // Only forward as keyboard Enter if actually coming from keyboard, + // not from mouse click (mouse is handled in tick()). + if (g_KBMInput.IsKeyPressed(VK_RETURN)) { - sendInputToMovie(key, repeat, pressed, released); + ctx->ProcessKeyDown(Rml::Input::KI_RETURN, 0); + handled = true; } break; - -#ifdef _XBOX_ONE - case ACTION_MENU_RIGHT_SCROLL: - if( bDisplayBanTip ) + case ACTION_MENU_CANCEL: + case ACTION_MENU_PAUSEMENU: + if (m_bExitDialog) { - UINT uiIDA[2]; - uiIDA[0]=IDS_CONFIRM_CANCEL; - uiIDA[1]=IDS_CONFIRM_OK; - ui.RequestAlertMessage(IDS_ACTION_BAN_LEVEL_TITLE, IDS_ACTION_BAN_LEVEL_DESCRIPTION, uiIDA, 2, iPad,&UIScene_PauseMenu::BanGameDialogReturned,(LPVOID)GetCallbackUniqueId() ); - - //rfHandled = TRUE; + OnExitCancel(); + handled = true; + } + else if (m_bSaveDialog) + { + OnSaveCancel(); + handled = true; + } + else + { + navigateBack(); + handled = true; } break; -#endif - } -} - -void UIScene_PauseMenu::handlePress(F64 controlId, F64 childId) -{ - ui.PlayUISFX(eSFX_Press); - - if(m_bIgnoreInput) return; - - switch(static_cast(controlId)) - { - case BUTTON_PAUSE_RESUMEGAME: - if( m_iPad == ProfileManager.GetPrimaryPad() && g_NetworkManager.IsLocalGame() ) - { - app.SetXuiServerAction(ProfileManager.GetPrimaryPad(),eXuiServerAction_PauseServer,(void *)FALSE); - } - navigateBack(); - break; - case BUTTON_PAUSE_LEADERBOARDS: - { - UINT uiIDA[1]; - uiIDA[0]=IDS_OK; - - //4J Gordon: Being used for the leaderboards proper now - // guests can't look at leaderboards - if(ProfileManager.IsGuest(m_iPad)) - { - ui.RequestAlertMessage(IDS_PRO_GUESTPROFILE_TITLE, IDS_PRO_GUESTPROFILE_TEXT, uiIDA, 1, ProfileManager.GetPrimaryPad()); - } - else if(!ProfileManager.IsSignedInLive(m_iPad)) - { - UINT uiIDA[1] = { IDS_OK }; - ui.RequestErrorMessage(IDS_PRO_NOTONLINE_TITLE, IDS_PRO_XBOXLIVE_NOTIFICATION, uiIDA, 1, m_iPad); - } - else - { - bool bContentRestricted=false; - - if(!bContentRestricted) - ui.NavigateToScene(m_iPad, eUIScene_LeaderboardsMenu); - } - } - break; - case BUTTON_PAUSE_HELPANDOPTIONS: - ui.NavigateToScene(m_iPad,eUIScene_HelpAndOptionsMenu); - break; - case BUTTON_PAUSE_SAVEGAME: - PerformActionSaveGame(); - break; - case BUTTON_PAUSE_EXITGAME: - { - Minecraft *pMinecraft = Minecraft::GetInstance(); - // Check if it's the trial version - if(ProfileManager.IsFullVersion()) - { - UINT uiIDA[3]; - - // is it the primary player exiting? - if(m_iPad==ProfileManager.GetPrimaryPad()) - { - int playTime = -1; - if( pMinecraft->localplayers[m_iPad] != nullptr ) - { - playTime = static_cast(pMinecraft->localplayers[m_iPad]->getSessionTimer()); - } - -#if defined(_XBOX_ONE) || defined(__ORBIS__) - uiIDA[0]=IDS_CONFIRM_CANCEL; - uiIDA[1]=IDS_CONFIRM_OK; - - if(g_NetworkManager.IsHost() && StorageManager.GetSaveDisabled()) - { - uiIDA[0]=IDS_CONFIRM_CANCEL; - uiIDA[1]=IDS_EXIT_GAME_SAVE; - uiIDA[2]=IDS_EXIT_GAME_NO_SAVE; - - if(g_NetworkManager.GetPlayerCount()>1) - { - ui.RequestAlertMessage(IDS_EXIT_GAME, IDS_CONFIRM_EXIT_GAME_CONFIRM_DISCONNECT_SAVE, uiIDA, 3, m_iPad,&UIScene_PauseMenu::ExitGameSaveDialogReturned, (LPVOID)GetCallbackUniqueId()); - } - else - { - ui.RequestAlertMessage(IDS_EXIT_GAME, IDS_CONFIRM_EXIT_GAME, uiIDA, 3, m_iPad,&UIScene_PauseMenu::ExitGameSaveDialogReturned, (LPVOID)GetCallbackUniqueId()); - } - } - else if(g_NetworkManager.IsHost() && g_NetworkManager.GetPlayerCount()>1) - { - ui.RequestAlertMessage(IDS_EXIT_GAME, IDS_CONFIRM_EXIT_GAME_CONFIRM_DISCONNECT, uiIDA, 2, m_iPad,&IUIScene_PauseMenu::ExitGameDialogReturned, (LPVOID)GetCallbackUniqueId()); - } - else - { - ui.RequestAlertMessage(IDS_EXIT_GAME, IDS_CONFIRM_EXIT_GAME, uiIDA, 2, m_iPad,&IUIScene_PauseMenu::ExitGameDialogReturned, (LPVOID)GetCallbackUniqueId()); - } -#else - if(StorageManager.GetSaveDisabled()) - { - uiIDA[0]=IDS_CONFIRM_CANCEL; - uiIDA[1]=IDS_CONFIRM_OK; - ui.RequestAlertMessage(IDS_EXIT_GAME, IDS_CONFIRM_EXIT_GAME_PROGRESS_LOST, uiIDA, 2, m_iPad,&IUIScene_PauseMenu::ExitGameDialogReturned, (LPVOID)GetCallbackUniqueId()); - } - else - { - if( g_NetworkManager.IsHost() ) - { - uiIDA[0]=IDS_CONFIRM_CANCEL; - uiIDA[1]=IDS_EXIT_GAME_SAVE; - uiIDA[2]=IDS_EXIT_GAME_NO_SAVE; - - if(g_NetworkManager.GetPlayerCount()>1) - { - ui.RequestAlertMessage(IDS_EXIT_GAME, IDS_CONFIRM_EXIT_GAME_CONFIRM_DISCONNECT_SAVE, uiIDA, 3, m_iPad,&UIScene_PauseMenu::ExitGameSaveDialogReturned, (LPVOID)GetCallbackUniqueId()); - } - else - { - ui.RequestAlertMessage(IDS_EXIT_GAME, IDS_CONFIRM_EXIT_GAME, uiIDA, 3, m_iPad,&UIScene_PauseMenu::ExitGameSaveDialogReturned, (LPVOID)GetCallbackUniqueId()); - } - } - else - { - uiIDA[0]=IDS_CONFIRM_CANCEL; - uiIDA[1]=IDS_CONFIRM_OK; - - ui.RequestAlertMessage(IDS_EXIT_GAME, IDS_CONFIRM_EXIT_GAME, uiIDA, 2, m_iPad,&IUIScene_PauseMenu::ExitGameDialogReturned, (LPVOID)GetCallbackUniqueId()); - } - } -#endif - } - else - { - int playTime = -1; - if( pMinecraft->localplayers[m_iPad] != nullptr ) - { - playTime = static_cast(pMinecraft->localplayers[m_iPad]->getSessionTimer()); - } - - TelemetryManager->RecordLevelExit(m_iPad, eSen_LevelExitStatus_Exited); - - - // just exit the player - app.SetAction(m_iPad,eAppAction_ExitPlayer); - } - } - else - { - // is it the primary player exiting? - if(m_iPad==ProfileManager.GetPrimaryPad()) - { - int playTime = -1; - if( pMinecraft->localplayers[m_iPad] != nullptr ) - { - playTime = static_cast(pMinecraft->localplayers[m_iPad]->getSessionTimer()); - } - - // adjust the trial time played - ui.ReduceTrialTimerValue(); - - // exit the level - UINT uiIDA[2]; - uiIDA[0]=IDS_CONFIRM_CANCEL; - uiIDA[1]=IDS_CONFIRM_OK; - ui.RequestAlertMessage(IDS_EXIT_GAME, IDS_CONFIRM_EXIT_GAME_PROGRESS_LOST, uiIDA, 2, m_iPad,&IUIScene_PauseMenu::ExitGameDialogReturned, (LPVOID)GetCallbackUniqueId()); - - } - else - { - int playTime = -1; - if( pMinecraft->localplayers[m_iPad] != nullptr ) - { - playTime = static_cast(pMinecraft->localplayers[m_iPad]->getSessionTimer()); - } - - TelemetryManager->RecordLevelExit(m_iPad, eSen_LevelExitStatus_Exited); - - // just exit the player - app.SetAction(m_iPad,eAppAction_ExitPlayer); - } - } - } + default: break; } } -void UIScene_PauseMenu::PerformActionSaveGame() +void UIScene_PauseMenu::ProcessEvent(Rml::Event& event) { - // 4J-PB - Is the player trying to save but they are using a trial texturepack ? - if(!Minecraft::GetInstance()->skins->isUsingDefaultSkin()) + if (m_bIgnoreInput) + return; + + const Rml::String& id = event.GetCurrentElement()->GetId(); + + if (event == Rml::EventId::Click) { - TexturePack *tPack = Minecraft::GetInstance()->skins->getSelected(); - DLCTexturePack *pDLCTexPack=static_cast(tPack); - - m_pDLCPack=pDLCTexPack->getDLCInfoParentPack();//tPack->getDLCPack(); - - if(!m_pDLCPack->hasPurchasedFile( DLCManager::e_DLCType_Texture, L"" )) - { - return; - } - else - { - m_bTrialTexturePack = false; - } - } - - // does the save exist? - bool bSaveExists; - C4JStorage::ESaveGameState result=StorageManager.DoesSaveExist(&bSaveExists); - -#ifdef _XBOX - if(result == C4JStorage::ELoadGame_DeviceRemoved) - { - // this will be a tester trying to be clever - UINT uiIDA[2]; - uiIDA[0]=IDS_SELECTANEWDEVICE; - uiIDA[1]=IDS_NODEVICE_DECLINE; - - ui.RequestAlertMessage(IDS_STORAGEDEVICEPROBLEM_TITLE, IDS_FAILED_TO_LOADSAVE_TEXT, uiIDA, 2, m_iPad,&IUIScene_PauseMenu::DeviceRemovedDialogReturned,(LPVOID)GetCallbackUniqueId()); - } - else -#endif - { -#if defined(_XBOX_ONE) || defined(__ORBIS__) - if(!m_savesDisabled) - { - UINT uiIDA[2]; - uiIDA[0]=IDS_CANCEL; - uiIDA[1]=IDS_CONFIRM_OK; - ui.RequestAlertMessage(IDS_TITLE_DISABLE_AUTOSAVE, IDS_CONFIRM_DISABLE_AUTOSAVE, uiIDA, 2, m_iPad,&IUIScene_PauseMenu::DisableAutosaveDialogReturned,(LPVOID)GetCallbackUniqueId()); - } - else -#endif - // we need to ask if they are sure they want to overwrite the existing game - if(bSaveExists) - { - UINT uiIDA[2]; - uiIDA[0]=IDS_CONFIRM_CANCEL; - uiIDA[1]=IDS_CONFIRM_OK; - ui.RequestAlertMessage(IDS_TITLE_SAVE_GAME, IDS_CONFIRM_SAVE_GAME, uiIDA, 2, m_iPad,&IUIScene_PauseMenu::SaveGameDialogReturned,(LPVOID)GetCallbackUniqueId()); - } - else - { -#if defined(_XBOX_ONE) || defined(__ORBIS__) - UINT uiIDA[2]; - uiIDA[0]=IDS_CONFIRM_CANCEL; - uiIDA[1]=IDS_CONFIRM_OK; - ui.RequestAlertMessage(IDS_TITLE_ENABLE_AUTOSAVE, IDS_CONFIRM_ENABLE_AUTOSAVE, uiIDA, 2, m_iPad,&IUIScene_PauseMenu::EnableAutosaveDialogReturned,(LPVOID)GetCallbackUniqueId()); -#else - // flag a app action of save game - app.SetAction(m_iPad,eAppAction_SaveGame); -#endif - } + if (id == "resume") OnResume(); + else if (id == "help_options") OnHelpAndOptions(); + else if (id == "achievements") OnAchievements(); + else if (id == "save_game") OnSaveGame(); + else if (id == "exit_game") OnExitGame(); + else if (id == "exit_save") OnExitSave(); + else if (id == "exit_nosave") OnExitNoSave(); + else if (id == "exit_cancel") OnExitCancel(); + else if (id == "save_confirm") OnSaveConfirm(); + else if (id == "save_cancel") OnSaveCancel(); } } void UIScene_PauseMenu::ShowScene(bool show) { - app.DebugPrintf("UIScene_PauseMenu::ShowScene is not implemented\n"); + if (m_document) + { + if (show) + m_document->Show(); + else + m_document->Hide(); + } } -void UIScene_PauseMenu::HandleDLCInstalled() +void UIScene_PauseMenu::navigateBack() { - // mounted DLC may have changed - if(app.StartInstallDLCProcess(m_iPad)==false) + if (m_iPad == ProfileManager.GetPrimaryPad() && g_NetworkManager.IsLocalGame()) { - // not doing a mount, so re-enable input - //m_bIgnoreInput=false; - app.DebugPrintf("UIScene_PauseMenu::HandleDLCInstalled - m_bIgnoreInput false\n"); + app.SetXuiServerAction(ProfileManager.GetPrimaryPad(), eXuiServerAction_PauseServer, (void*)FALSE); + } + + ui.PlayUISFX(eSFX_Back); + UIScene::navigateBack(); +} + +void UIScene_PauseMenu::OnResume() +{ + ui.PlayUISFX(eSFX_Back); + navigateBack(); +} + +void UIScene_PauseMenu::OnHelpAndOptions() +{ + ui.NavigateToScene(m_iPad, eUIScene_HelpAndOptionsMenu); +} + +void UIScene_PauseMenu::OnAchievements() +{ + ui.NavigateToScene(m_iPad, eUIScene_AchievementsMenu); +} + +void UIScene_PauseMenu::ShowSaveDialog(bool show) +{ + if (!m_document) + return; + + auto* saveDialog = m_document->GetElementById("save_dialog"); + auto* buttons = m_document->GetElementById("buttons"); + + if (show) + { + if (buttons) buttons->SetProperty("display", "none"); + if (saveDialog) saveDialog->SetProperty("display", "block"); + + auto* saveText = m_document->GetElementById("save_text"); + + if (!ProfileManager.IsFullVersion()) + { + if (saveText) saveText->SetInnerRML("You need to unlock the full game to save."); + } + else + { + if (saveText) saveText->SetInnerRML("Overwrite existing save?"); + } + + m_document->GetElementById("save_cancel")->Focus(); + m_bSaveDialog = true; } else { - // 4J-PB - Somehow, on th edisc build, we get in here, but don't call HandleDLCMountingComplete, so input locks up - //m_bIgnoreInput=true; - app.DebugPrintf("UIScene_PauseMenu::HandleDLCInstalled - m_bIgnoreInput true\n"); + if (saveDialog) saveDialog->SetProperty("display", "none"); + if (buttons) buttons->SetProperty("display", "block"); + + m_document->GetElementById("save_game")->Focus(); + m_bSaveDialog = false; } - // this will send a CustomMessage_DLCMountingComplete when done } - -void UIScene_PauseMenu::HandleDLCMountingComplete() -{ - // check if we should display the save option - - //m_bIgnoreInput=false; - app.DebugPrintf("UIScene_PauseMenu::HandleDLCMountingComplete - m_bIgnoreInput false \n"); - - // if(ProfileManager.IsFullVersion()) - // { - // bool bIsisPrimaryHost=g_NetworkManager.IsHost() && (ProfileManager.GetPrimaryPad()==m_iPad); - // - // if(bIsisPrimaryHost) - // { - // m_buttons[BUTTON_PAUSE_SAVEGAME].setEnable(true); - // } - // } -} - -int UIScene_PauseMenu::UnlockFullSaveReturned(void *pParam,int iPad,C4JStorage::EMessageResult result) +void UIScene_PauseMenu::OnSaveGame() { - Minecraft *pMinecraft=Minecraft::GetInstance(); + if (m_iPad != ProfileManager.GetPrimaryPad()) + return; - if(result==C4JStorage::EMessage_ResultAccept) + if (!ProfileManager.IsFullVersion()) { - if(ProfileManager.IsSignedInLive(pMinecraft->player->GetXboxPad())) + ShowSaveDialog(true); + return; + } + + bool bSaveExists; + StorageManager.DoesSaveExist(&bSaveExists); + + if (bSaveExists) + { + ShowSaveDialog(true); + } + else + { + app.SetAction(m_iPad, eAppAction_SaveGame); + } +} + +void UIScene_PauseMenu::OnSaveConfirm() +{ + ShowSaveDialog(false); + + if (!ProfileManager.IsFullVersion()) + { + ProfileManager.DisplayFullVersionPurchase(false, m_iPad, eSen_UpsellID_Full_Version_Of_Game); + } + else + { + app.SetAction(m_iPad, eAppAction_SaveGame); + } +} + +void UIScene_PauseMenu::OnSaveCancel() +{ + ShowSaveDialog(false); +} + +void UIScene_PauseMenu::ShowExitDialog(bool show) +{ + if (!m_document) + return; + + auto* exitDialog = m_document->GetElementById("exit_dialog"); + auto* buttons = m_document->GetElementById("buttons"); + + if (show) + { + Minecraft* pMinecraft = Minecraft::GetInstance(); + + if (buttons) buttons->SetProperty("display", "none"); + if (exitDialog) exitDialog->SetProperty("display", "block"); + + // Configure dialog for current state + auto* exitSave = m_document->GetElementById("exit_save"); + auto* exitNosave = m_document->GetElementById("exit_nosave"); + auto* exitCancel = m_document->GetElementById("exit_cancel"); + auto* exitText = m_document->GetElementById("exit_text"); + + if (ProfileManager.IsFullVersion() && m_iPad == ProfileManager.GetPrimaryPad()) { - // 4J-PB - need to check this user can access the store -#if defined(__PS3__) || defined(__PSVITA__) - bool bContentRestricted; - ProfileManager.GetChatAndContentRestrictions(ProfileManager.GetPrimaryPad(),true,nullptr,&bContentRestricted,nullptr); - if(bContentRestricted) + // Full version primary pad: show Save & Exit + Exit without Saving + Cancel + if (exitSave) exitSave->SetProperty("display", "inline-block"); + if (exitNosave) exitNosave->SetProperty("display", "inline-block"); + + if (g_NetworkManager.GetPlayerCount() > 1) { - UINT uiIDA[1]; - uiIDA[0]=IDS_CONFIRM_OK; - ui.RequestAlertMessage(IDS_ONLINE_SERVICE_TITLE, IDS_CONTENT_RESTRICTION, uiIDA, 1, ProfileManager.GetPrimaryPad()); + if (exitText) exitText->SetInnerRML("Exit Game (disconnect other players)?"); } else -#endif { - ProfileManager.DisplayFullVersionPurchase(false,pMinecraft->player->GetXboxPad(),eSen_UpsellID_Full_Version_Of_Game); + if (exitText) exitText->SetInnerRML("Exit Game?"); } + if (exitCancel) exitCancel->Focus(); } + else if (!ProfileManager.IsFullVersion() && m_iPad == ProfileManager.GetPrimaryPad()) + { + // Trial version: simple OK/Cancel, "progress will be lost" + if (exitSave) exitSave->SetProperty("display", "none"); + if (exitNosave) exitNosave->SetProperty("display", "inline-block"); + if (exitNosave) exitNosave->SetInnerRML("Yes"); + if (exitText) exitText->SetInnerRML("Exit Game (progress will be lost)?"); + if (exitCancel) exitCancel->Focus(); + } + + m_bExitDialog = true; } else { - //SentientManager.RecordUpsellResponded(iPad, eSen_UpsellID_Full_Version_Of_Game, app.m_dwOfferID, eSen_UpsellOutcome_Declined); - } + if (exitDialog) exitDialog->SetProperty("display", "none"); + if (buttons) buttons->SetProperty("display", "block"); - return 0; + // Restore default button state + auto* exitSave = m_document->GetElementById("exit_save"); + auto* exitNosave = m_document->GetElementById("exit_nosave"); + if (exitSave) exitSave->SetProperty("display", "inline-block"); + if (exitNosave) exitNosave->SetInnerRML("Exit without Saving"); + if (exitNosave) exitNosave->SetProperty("display", "inline-block"); + + m_document->GetElementById("exit_game")->Focus(); + m_bExitDialog = false; + } } -int UIScene_PauseMenu::SaveGame_SignInReturned(void *pParam,bool bContinue, int iPad) +void UIScene_PauseMenu::OnExitGame() { - UIScene_PauseMenu* pClass = static_cast(ui.GetSceneFromCallbackId((size_t)pParam)); - if(pClass) pClass->SetIgnoreInput(false); + Minecraft* pMinecraft = Minecraft::GetInstance(); - if(bContinue==true) + // Non-primary pad: exit directly without dialog + if (m_iPad != ProfileManager.GetPrimaryPad()) { - if(pClass) pClass->PerformActionSaveGame(); + if (pMinecraft->localplayers[m_iPad] != nullptr) + TelemetryManager->RecordLevelExit(m_iPad, eSen_LevelExitStatus_Exited); + app.SetAction(m_iPad, eAppAction_ExitPlayer); + return; } - return 0; + // Primary pad: show inline confirmation dialog (no Iggy MessageBox) + ShowExitDialog(true); } -#ifdef _XBOX_ONE -int UIScene_PauseMenu::BanGameDialogReturned(void *pParam,int iPad,C4JStorage::EMessageResult result) +void UIScene_PauseMenu::OnExitSave() { - // results switched for this dialog - if(result==C4JStorage::EMessage_ResultDecline) + ShowExitDialog(false); + SetIgnoreInput(true); + + Minecraft* pMinecraft = Minecraft::GetInstance(); + if (pMinecraft->localplayers[m_iPad] != nullptr) { - app.SetAction(iPad,eAppAction_BanLevel); + int playTime = static_cast(pMinecraft->localplayers[m_iPad]->getSessionTimer()); + TelemetryManager->RecordLevelExit(m_iPad, eSen_LevelExitStatus_Exited); } - return 0; -} -#endif -void UIScene_PauseMenu::SetIgnoreInput(bool ignoreInput) + MinecraftServer::getInstance()->setSaveOnExit(true); + app.SetAction(m_iPad, eAppAction_ExitWorld); +} + +void UIScene_PauseMenu::OnExitNoSave() { - m_bIgnoreInput = ignoreInput; -} \ No newline at end of file + ShowExitDialog(false); + SetIgnoreInput(true); + + Minecraft* pMinecraft = Minecraft::GetInstance(); + if (pMinecraft->localplayers[m_iPad] != nullptr) + { + int playTime = static_cast(pMinecraft->localplayers[m_iPad]->getSessionTimer()); + TelemetryManager->RecordLevelExit(m_iPad, eSen_LevelExitStatus_Exited); + } + + app.SetAction(m_iPad, eAppAction_ExitWorld); +} + +void UIScene_PauseMenu::OnExitCancel() +{ + ShowExitDialog(false); +} diff --git a/Minecraft.Client/Common/UI/UIScene_PauseMenu.h b/Minecraft.Client/Common/UI/UIScene_PauseMenu.h index 854b6aa5..9d67f593 100644 --- a/Minecraft.Client/Common/UI/UIScene_PauseMenu.h +++ b/Minecraft.Client/Common/UI/UIScene_PauseMenu.h @@ -1,103 +1,70 @@ #pragma once +#pragma push_macro("byte") +#pragma push_macro("GetNextSibling") +#pragma push_macro("GetFirstChild") +#undef byte +#undef GetNextSibling +#undef GetFirstChild + #include "UIScene.h" #include "IUIScene_PauseMenu.h" +#include +#include -#define BUTTON_PAUSE_RESUMEGAME 0 -#define BUTTON_PAUSE_HELPANDOPTIONS 1 -#define BUTTON_PAUSE_LEADERBOARDS 2 +// NOTE: pop_macro at end of file -#ifdef _XBOX_ONE -#define BUTTON_PAUSE_XBOXHELP 3 -#else -#define BUTTON_PAUSE_ACHIEVEMENTS 3 -#endif - -#define BUTTON_PAUSE_SAVEGAME 4 -#define BUTTON_PAUSE_EXITGAME 5 -#define BUTTONS_PAUSE_MAX BUTTON_PAUSE_EXITGAME + 1 - -class UIScene_PauseMenu : public UIScene, public IUIScene_PauseMenu +class UIScene_PauseMenu : public UIScene, public IUIScene_PauseMenu, public Rml::EventListener { -private: - bool m_savesDisabled; - bool m_bTrialTexturePack; - bool m_bErrorDialogRunning; - - enum eActions - { - eAction_None=0, -#if defined(__PS3__) || defined(__PSVITA__) || defined(__ORBIS__) - eAction_ViewLeaderboardsPSN, - eAction_ViewInvitesPSN, - eAction_SaveGamePSN, - eAction_BuyTexturePackPSN -#endif - - }; - eActions m_eAction; - - UIControl_Button m_buttons[BUTTONS_PAUSE_MAX]; - UI_BEGIN_MAP_ELEMENTS_AND_NAMES(UIScene) - UI_MAP_ELEMENT( m_buttons[BUTTON_PAUSE_RESUMEGAME], "Button1") - UI_MAP_ELEMENT( m_buttons[BUTTON_PAUSE_HELPANDOPTIONS], "Button2") - UI_MAP_ELEMENT( m_buttons[BUTTON_PAUSE_LEADERBOARDS], "Button3") -#ifdef _DURANGO - UI_MAP_ELEMENT( m_buttons[BUTTON_PAUSE_XBOXHELP], "Button4") -#else - UI_MAP_ELEMENT( m_buttons[BUTTON_PAUSE_ACHIEVEMENTS], "Button4") -#endif - UI_MAP_ELEMENT( m_buttons[BUTTON_PAUSE_SAVEGAME], "Button5") - UI_MAP_ELEMENT( m_buttons[BUTTON_PAUSE_EXITGAME], "Button6") - UI_END_MAP_ELEMENTS_AND_NAMES() - - virtual void HandleDLCMountingComplete(); - virtual void HandleDLCInstalled(); -#ifdef _XBOX_ONE - virtual void HandleDLCLicenseChange(); -#endif - static int UnlockFullSaveReturned(void *pParam,int iPad,C4JStorage::EMessageResult result); - static int SaveGame_SignInReturned(void *pParam,bool bContinue, int iPad); - public: UIScene_PauseMenu(int iPad, void *initData, UILayer *parentLayer); virtual ~UIScene_PauseMenu(); - virtual EUIScene getSceneType() { return eUIScene_PauseMenu;} + virtual EUIScene getSceneType() { return eUIScene_PauseMenu; } virtual void tick(); + virtual void render(S32 width, S32 height, C4JRender::eViewportType viewport); - virtual void updateTooltips(); - virtual void updateComponents(); - virtual void handlePreReload(); - virtual void handleReload(); - -protected: - void updateControlsVisibility(); - - // TODO: This should be pure virtual in this class - virtual wstring getMoviePath(); - -public: - // INPUT virtual void handleInput(int iPad, int key, bool repeat, bool pressed, bool released, bool &handled); + // Override focus/movie lifecycle for RmlUi (no Iggy movie) + virtual void gainFocus() override; + virtual void reloadMovie(bool force) override; + + // Rml::EventListener + void ProcessEvent(Rml::Event& event) override; + protected: - void handlePress(F64 controlId, F64 childId); - virtual void ShowScene(bool show); - virtual void SetIgnoreInput(bool ignoreInput); - bool m_bIgnoreInput; + virtual wstring getMoviePath() { return L""; } + + virtual void handleLoseFocus() override; + + void ShowScene(bool show) override; + void SetIgnoreInput(bool ignoreInput) override { m_bIgnoreInput = ignoreInput; } + + void navigateBack(); + + void OnResume(); + void OnHelpAndOptions(); + void OnAchievements(); + void OnSaveGame(); + void OnExitGame(); + void OnExitSave(); + void OnExitNoSave(); + void OnExitCancel(); + void OnSaveConfirm(); + void OnSaveCancel(); + + void ShowExitDialog(bool show); + void ShowSaveDialog(bool show); private: - void PerformActionSaveGame(); - -protected: -#ifdef _XBOX_ONE - static int BanGameDialogReturned(void *pParam,int iPad,C4JStorage::EMessageResult result); - virtual long long getDefaultGtcButtons() { return _360_GTC_BACK | _360_GTC_PLAY; } -#endif - -#ifdef __ORBIS__ - bool CheckForPatch(); -#endif + Rml::ElementDocument* m_document; + bool m_bIgnoreInput; + bool m_bExitDialog; + bool m_bSaveDialog; }; + +#pragma pop_macro("GetFirstChild") +#pragma pop_macro("GetNextSibling") +#pragma pop_macro("byte") diff --git a/Minecraft.Client/Common/UI/UIScene_SettingsMenu.cpp b/Minecraft.Client/Common/UI/UIScene_SettingsMenu.cpp index 497af6d1..4e70c087 100644 --- a/Minecraft.Client/Common/UI/UIScene_SettingsMenu.cpp +++ b/Minecraft.Client/Common/UI/UIScene_SettingsMenu.cpp @@ -1,168 +1,316 @@ +#include "stdafx.h" + +// Non-RmlUi includes that depend on Windows macros #include "UI.h" +#include "../../Minecraft.h" + +// RmlUi includes (Windows macros temporarily suppressed) +#pragma push_macro("byte") +#pragma push_macro("GetNextSibling") +#pragma push_macro("GetFirstChild") +#undef byte +#undef GetNextSibling +#undef GetFirstChild + #include "UIScene_SettingsMenu.h" -#include "..\..\Minecraft.h" +#include "RmlManager.h" +#include "../../Windows64/KeyboardMouseInput.h" +#include +#include -UIScene_SettingsMenu::UIScene_SettingsMenu(int iPad, void *initData, UILayer *parentLayer) : UIScene(iPad, parentLayer) +#pragma pop_macro("GetFirstChild") +#pragma pop_macro("GetNextSibling") +#pragma pop_macro("byte") + +UIScene_SettingsMenu::UIScene_SettingsMenu(int iPad, void *initData, UILayer *parentLayer) + : UIScene(iPad, parentLayer) + , m_document(nullptr) + , m_bConfirmDialog(false) { - // Setup all the Iggy references we need for this scene - initialiseMovie(); - - bool bNotInGame=(Minecraft::GetInstance()->level==nullptr); - - m_buttons[BUTTON_ALL_OPTIONS].init(IDS_OPTIONS,BUTTON_ALL_OPTIONS); - m_buttons[BUTTON_ALL_AUDIO].init(IDS_AUDIO,BUTTON_ALL_AUDIO); - m_buttons[BUTTON_ALL_CONTROL].init(IDS_CONTROL,BUTTON_ALL_CONTROL); - m_buttons[BUTTON_ALL_GRAPHICS].init(IDS_GRAPHICS,BUTTON_ALL_GRAPHICS); - m_buttons[BUTTON_ALL_UI].init(IDS_USER_INTERFACE,BUTTON_ALL_UI); - m_buttons[BUTTON_ALL_RESETTODEFAULTS].init(IDS_RESET_TO_DEFAULTS,BUTTON_ALL_RESETTODEFAULTS); - - if(ProfileManager.GetPrimaryPad()!=m_iPad) + Rml::Context* ctx = RmlManager::Get().GetContext(); + if (!ctx) { - removeControl( &m_buttons[BUTTON_ALL_AUDIO], bNotInGame); - removeControl( &m_buttons[BUTTON_ALL_GRAPHICS], bNotInGame); + app.DebugPrintf("[RmlSettingsMenu] No RmlUi context available\n"); + return; } - doHorizontalResizeCheck(); - - if(app.GetLocalPlayerCount()>1) + m_document = ctx->LoadDocument("SettingsMenu.rml"); + if (m_document) { -#if TO_BE_IMPLEMENTED - app.AdjustSplitscreenScene(m_hObj,&m_OriginalPosition,m_iPad,false); -#endif + m_document->Show(); + + // Apply conditional visibility matching original Iggy scene behavior + bool bNotInGame = (Minecraft::GetInstance()->level == nullptr); + + // Audio and Graphics: hidden if not primary pad + if (ProfileManager.GetPrimaryPad() != m_iPad) + { + auto* elAudio = m_document->GetElementById("audio"); + if (elAudio) elAudio->SetProperty("display", "none"); + auto* elGraphics = m_document->GetElementById("graphics"); + if (elGraphics) elGraphics->SetProperty("display", "none"); + } + + // Register button click listeners + m_document->GetElementById("options")->AddEventListener(Rml::EventId::Click, this); + m_document->GetElementById("audio")->AddEventListener(Rml::EventId::Click, this); + m_document->GetElementById("controls")->AddEventListener(Rml::EventId::Click, this); + m_document->GetElementById("graphics")->AddEventListener(Rml::EventId::Click, this); + m_document->GetElementById("ui")->AddEventListener(Rml::EventId::Click, this); + m_document->GetElementById("reset_defaults")->AddEventListener(Rml::EventId::Click, this); + m_document->GetElementById("confirm_yes")->AddEventListener(Rml::EventId::Click, this); + m_document->GetElementById("confirm_no")->AddEventListener(Rml::EventId::Click, this); + + // Focus first visible button + m_document->GetElementById("options")->Focus(); } + else + { + app.DebugPrintf("[RmlSettingsMenu] Failed to load SettingsMenu.rml\n"); + } + + updateComponents(); + + ui.HidePressStart(); + TelemetryManager->RecordMenuShown(m_iPad, eUIScene_SettingsMenu, 0); } UIScene_SettingsMenu::~UIScene_SettingsMenu() { -} - -wstring UIScene_SettingsMenu::getMoviePath() -{ - if(app.GetLocalPlayerCount() > 1) + if (m_document) { - return L"SettingsMenuSplit"; - } - else - { - return L"SettingsMenu"; + m_document->GetElementById("options")->RemoveEventListener(Rml::EventId::Click, this); + m_document->GetElementById("audio")->RemoveEventListener(Rml::EventId::Click, this); + m_document->GetElementById("controls")->RemoveEventListener(Rml::EventId::Click, this); + m_document->GetElementById("graphics")->RemoveEventListener(Rml::EventId::Click, this); + m_document->GetElementById("ui")->RemoveEventListener(Rml::EventId::Click, this); + m_document->GetElementById("reset_defaults")->RemoveEventListener(Rml::EventId::Click, this); + m_document->GetElementById("confirm_yes")->RemoveEventListener(Rml::EventId::Click, this); + m_document->GetElementById("confirm_no")->RemoveEventListener(Rml::EventId::Click, this); + m_document->Close(); + m_document = nullptr; } } -void UIScene_SettingsMenu::handleReload() +void UIScene_SettingsMenu::gainFocus() { - bool bNotInGame=(Minecraft::GetInstance()->level==nullptr); - if(ProfileManager.GetPrimaryPad()!=m_iPad) + if (!bHasFocus && stealsFocus()) { - removeControl( &m_buttons[BUTTON_ALL_AUDIO], bNotInGame); - removeControl( &m_buttons[BUTTON_ALL_GRAPHICS], bNotInGame); + bHasFocus = true; + updateTooltips(); + updateComponents(); + if (!m_bFocussedOnce) + { + } + handleGainFocus(m_bFocussedOnce); + if (bHasFocus) + m_bFocussedOnce = true; + if (m_document) + m_document->Show(); } +} - doHorizontalResizeCheck(); +void UIScene_SettingsMenu::handleLoseFocus() +{ + if (m_document) + m_document->Hide(); } void UIScene_SettingsMenu::updateTooltips() { - ui.SetTooltips( m_iPad, IDS_TOOLTIPS_SELECT,IDS_TOOLTIPS_BACK); } void UIScene_SettingsMenu::updateComponents() { - bool bNotInGame=(Minecraft::GetInstance()->level==nullptr); - if(bNotInGame) + bool bNotInGame = (Minecraft::GetInstance()->level == nullptr); + + if (bNotInGame) { - m_parentLayer->showComponent(m_iPad,eUIComponent_Panorama,true); - m_parentLayer->showComponent(m_iPad,eUIComponent_Logo,true); + m_parentLayer->showComponent(m_iPad, eUIComponent_Panorama, true); + m_parentLayer->showComponent(m_iPad, eUIComponent_Logo, true); } else { - m_parentLayer->showComponent(m_iPad,eUIComponent_Panorama,false); - - if( app.GetLocalPlayerCount() == 1 ) m_parentLayer->showComponent(m_iPad,eUIComponent_Logo,true); - else m_parentLayer->showComponent(m_iPad,eUIComponent_Logo,false); + m_parentLayer->showComponent(m_iPad, eUIComponent_Panorama, false); + if (app.GetLocalPlayerCount() == 1) + m_parentLayer->showComponent(m_iPad, eUIComponent_Logo, true); + else + m_parentLayer->showComponent(m_iPad, eUIComponent_Logo, false); } } +void UIScene_SettingsMenu::reloadMovie(bool force) +{ +} + +void UIScene_SettingsMenu::tick() +{ + m_hasTickedOnce = true; + + UIScene::tick(); + + Rml::Context* ctx = RmlManager::Get().GetContext(); + if (!ctx || !bHasFocus) + return; + + ctx->ProcessMouseMove(g_KBMInput.GetMouseX(), g_KBMInput.GetMouseY(), 0); + + if (g_KBMInput.IsMouseButtonPressed(KeyboardMouseInput::MOUSE_LEFT)) + ctx->ProcessMouseButtonDown(0, 0); + if (g_KBMInput.IsMouseButtonReleased(KeyboardMouseInput::MOUSE_LEFT)) + ctx->ProcessMouseButtonUp(0, 0); + if (g_KBMInput.IsMouseButtonPressed(KeyboardMouseInput::MOUSE_RIGHT)) + ctx->ProcessMouseButtonDown(1, 0); + if (g_KBMInput.IsMouseButtonReleased(KeyboardMouseInput::MOUSE_RIGHT)) + ctx->ProcessMouseButtonUp(1, 0); + + int wheelDelta = g_KBMInput.GetMouseWheel(); + if (wheelDelta != 0) + ctx->ProcessMouseWheel(wheelDelta / -120, 0); + + ctx->Update(); +} + +void UIScene_SettingsMenu::render(S32 width, S32 height, C4JRender::eViewportType viewport) +{ +} + void UIScene_SettingsMenu::handleInput(int iPad, int key, bool repeat, bool pressed, bool released, bool &handled) { - //app.DebugPrintf("UIScene_DebugOverlay handling input for pad %d, key %d, down- %s, pressed- %s, released- %s\n", iPad, key, down?"TRUE":"FALSE", pressed?"TRUE":"FALSE", released?"TRUE":"FALSE"); - ui.AnimateKeyPress(m_iPad, key, repeat, pressed, released); + Rml::Context* ctx = RmlManager::Get().GetContext(); + if (!ctx) + return; - switch(key) + if (!pressed) + return; + + switch (key) { - case ACTION_MENU_CANCEL: - if(pressed) - { - // if the profile data has been changed, then force a profile write - // It seems we're allowed to break the 5 minute rule if it's the result of a user action - - app.CheckGameSettingsChanged(true,iPad); - navigateBack(); - } + case ACTION_MENU_UP: + ctx->ProcessKeyDown(Rml::Input::KI_UP, 0); + handled = true; + break; + case ACTION_MENU_DOWN: + ctx->ProcessKeyDown(Rml::Input::KI_DOWN, 0); + handled = true; break; case ACTION_MENU_OK: -#ifdef __ORBIS__ - case ACTION_MENU_TOUCHPAD_PRESS: -#endif - sendInputToMovie(key, repeat, pressed, released); - break; - case ACTION_MENU_UP: - case ACTION_MENU_DOWN: - sendInputToMovie(key, repeat, pressed, released); - break; - } -} - -void UIScene_SettingsMenu::handlePress(F64 controlId, F64 childId) -{ - //CD - Added for audio - ui.PlayUISFX(eSFX_Press); - - switch(static_cast(controlId)) - { - case BUTTON_ALL_OPTIONS: - ui.NavigateToScene(m_iPad, eUIScene_SettingsOptionsMenu); - break; - case BUTTON_ALL_AUDIO: - ui.NavigateToScene(m_iPad, eUIScene_SettingsAudioMenu); - break; - case BUTTON_ALL_CONTROL: - ui.NavigateToScene(m_iPad, eUIScene_SettingsControlMenu); - break; - case BUTTON_ALL_GRAPHICS: - ui.NavigateToScene(m_iPad, eUIScene_SettingsGraphicsMenu); - break; - case BUTTON_ALL_UI: - ui.NavigateToScene(m_iPad, eUIScene_SettingsUIMenu); - break; - case BUTTON_ALL_RESETTODEFAULTS: + if (g_KBMInput.IsKeyPressed(VK_RETURN)) { - // check they really want to do this - UINT uiIDA[2]; - uiIDA[0]=IDS_CONFIRM_CANCEL; - uiIDA[1]=IDS_CONFIRM_OK; - - ui.RequestAlertMessage(IDS_DEFAULTS_TITLE, IDS_DEFAULTS_TEXT, uiIDA, 2, m_iPad,&UIScene_SettingsMenu::ResetDefaultsDialogReturned,this); - } + ctx->ProcessKeyDown(Rml::Input::KI_RETURN, 0); + handled = true; + } + break; + case ACTION_MENU_CANCEL: + case ACTION_MENU_PAUSEMENU: + if (m_bConfirmDialog) + { + OnConfirmNo(); + handled = true; + } + else + { + app.CheckGameSettingsChanged(true, iPad); + navigateBack(); + handled = true; + } + break; + default: break; } } -int UIScene_SettingsMenu::ResetDefaultsDialogReturned(void *pParam,int iPad,C4JStorage::EMessageResult result) +void UIScene_SettingsMenu::ProcessEvent(Rml::Event& event) { - UIScene_SettingsMenu* pClass = static_cast(pParam); + const Rml::String& id = event.GetCurrentElement()->GetId(); - // results switched for this dialog - if(result==C4JStorage::EMessage_ResultDecline) + if (event == Rml::EventId::Click) { -#if (defined __PS3__ || defined __ORBIS__ || defined _DURANGO || defined __PSVITA__) - app.SetDefaultOptions(StorageManager.GetDashboardProfileSettings(pClass->m_iPad),pClass->m_iPad); -#else - app.SetDefaultOptions(ProfileManager.GetDashboardProfileSettings(pClass->m_iPad),pClass->m_iPad); -#endif - // if the profile data has been changed, then force a profile write - // It seems we're allowed to break the 5 minute rule if it's the result of a user action - app.CheckGameSettingsChanged(true,iPad); + if (id == "options") OnOptions(); + else if (id == "audio") OnAudio(); + else if (id == "controls") OnControls(); + else if (id == "graphics") OnGraphics(); + else if (id == "ui") OnUI(); + else if (id == "reset_defaults") OnResetToDefaults(); + else if (id == "confirm_yes") OnConfirmYes(); + else if (id == "confirm_no") OnConfirmNo(); } - return 0; +} + +void UIScene_SettingsMenu::navigateBack() +{ + ui.PlayUISFX(eSFX_Back); + UIScene::navigateBack(); +} + +void UIScene_SettingsMenu::OnOptions() +{ + ui.PlayUISFX(eSFX_Press); + ui.NavigateToScene(m_iPad, eUIScene_SettingsOptionsMenu); +} + +void UIScene_SettingsMenu::OnAudio() +{ + ui.PlayUISFX(eSFX_Press); + ui.NavigateToScene(m_iPad, eUIScene_SettingsAudioMenu); +} + +void UIScene_SettingsMenu::OnControls() +{ + ui.PlayUISFX(eSFX_Press); + ui.NavigateToScene(m_iPad, eUIScene_SettingsControlMenu); +} + +void UIScene_SettingsMenu::OnGraphics() +{ + ui.PlayUISFX(eSFX_Press); + ui.NavigateToScene(m_iPad, eUIScene_SettingsGraphicsMenu); +} + +void UIScene_SettingsMenu::OnUI() +{ + ui.PlayUISFX(eSFX_Press); + ui.NavigateToScene(m_iPad, eUIScene_SettingsUIMenu); +} + +void UIScene_SettingsMenu::ShowConfirmDialog(bool show) +{ + if (!m_document) + return; + + auto* confirmDialog = m_document->GetElementById("confirm_dialog"); + auto* buttons = m_document->GetElementById("buttons"); + + if (show) + { + if (buttons) buttons->SetProperty("display", "none"); + if (confirmDialog) confirmDialog->SetProperty("display", "block"); + m_document->GetElementById("confirm_no")->Focus(); + m_bConfirmDialog = true; + } + else + { + if (confirmDialog) confirmDialog->SetProperty("display", "none"); + if (buttons) buttons->SetProperty("display", "block"); + m_document->GetElementById("reset_defaults")->Focus(); + m_bConfirmDialog = false; + } +} + +void UIScene_SettingsMenu::OnResetToDefaults() +{ + ShowConfirmDialog(true); +} + +void UIScene_SettingsMenu::OnConfirmYes() +{ + app.SetDefaultOptions(ProfileManager.GetDashboardProfileSettings(m_iPad), m_iPad); + app.CheckGameSettingsChanged(true, m_iPad); + navigateBack(); +} + +void UIScene_SettingsMenu::OnConfirmNo() +{ + ShowConfirmDialog(false); } diff --git a/Minecraft.Client/Common/UI/UIScene_SettingsMenu.h b/Minecraft.Client/Common/UI/UIScene_SettingsMenu.h index 7f5fe169..f63ebb32 100644 --- a/Minecraft.Client/Common/UI/UIScene_SettingsMenu.h +++ b/Minecraft.Client/Common/UI/UIScene_SettingsMenu.h @@ -1,47 +1,63 @@ #pragma once +#pragma push_macro("byte") +#pragma push_macro("GetNextSibling") +#pragma push_macro("GetFirstChild") +#undef byte +#undef GetNextSibling +#undef GetFirstChild + #include "UIScene.h" +#include +#include -#define BUTTON_ALL_OPTIONS 0 -#define BUTTON_ALL_AUDIO 1 -#define BUTTON_ALL_CONTROL 2 -#define BUTTON_ALL_GRAPHICS 4 -#define BUTTON_ALL_UI 5 -#define BUTTON_ALL_RESETTODEFAULTS 6 -#define BUTTONS_ALL_MAX BUTTON_ALL_RESETTODEFAULTS + 1 +// NOTE: pop_macro at end of file -class UIScene_SettingsMenu : public UIScene +class UIScene_SettingsMenu : public UIScene, public Rml::EventListener { -private: - UIControl_Button m_buttons[BUTTONS_ALL_MAX]; - UI_BEGIN_MAP_ELEMENTS_AND_NAMES(UIScene) - UI_MAP_ELEMENT( m_buttons[BUTTON_ALL_OPTIONS], "Button1") - UI_MAP_ELEMENT( m_buttons[BUTTON_ALL_AUDIO], "Button2") - UI_MAP_ELEMENT( m_buttons[BUTTON_ALL_CONTROL], "Button3") - UI_MAP_ELEMENT( m_buttons[BUTTON_ALL_GRAPHICS], "Button4") - UI_MAP_ELEMENT( m_buttons[BUTTON_ALL_UI], "Button5") - UI_MAP_ELEMENT( m_buttons[BUTTON_ALL_RESETTODEFAULTS], "Button6") - UI_END_MAP_ELEMENTS_AND_NAMES() public: UIScene_SettingsMenu(int iPad, void *initData, UILayer *parentLayer); virtual ~UIScene_SettingsMenu(); - virtual EUIScene getSceneType() { return eUIScene_SettingsMenu;} - - virtual void updateTooltips(); - virtual void updateComponents(); - virtual void handleReload(); + virtual EUIScene getSceneType() { return eUIScene_SettingsMenu; } -protected: - // TODO: This should be pure virtual in this class - virtual wstring getMoviePath(); + virtual void tick(); + virtual void render(S32 width, S32 height, C4JRender::eViewportType viewport); -public: - // INPUT virtual void handleInput(int iPad, int key, bool repeat, bool pressed, bool released, bool &handled); -protected: - void handlePress(F64 controlId, F64 childId); + // Override focus/movie lifecycle for RmlUi (no Iggy movie) + virtual void gainFocus() override; + virtual void reloadMovie(bool force) override; - static int ResetDefaultsDialogReturned(void *pParam,int iPad,C4JStorage::EMessageResult result); -}; \ No newline at end of file + // Rml::EventListener + void ProcessEvent(Rml::Event& event) override; + +protected: + virtual wstring getMoviePath() { return L""; } + + virtual void handleLoseFocus() override; + virtual void updateTooltips() override; + virtual void updateComponents() override; + + void navigateBack(); + + void OnOptions(); + void OnAudio(); + void OnControls(); + void OnGraphics(); + void OnUI(); + void OnResetToDefaults(); + void OnConfirmYes(); + void OnConfirmNo(); + + void ShowConfirmDialog(bool show); + +private: + Rml::ElementDocument* m_document; + bool m_bConfirmDialog; +}; + +#pragma pop_macro("GetFirstChild") +#pragma pop_macro("GetNextSibling") +#pragma pop_macro("byte") diff --git a/Minecraft.Client/Common/UI/UIStructs.h b/Minecraft.Client/Common/UI/UIStructs.h index aad77d3f..78869b1a 100644 --- a/Minecraft.Client/Common/UI/UIStructs.h +++ b/Minecraft.Client/Common/UI/UIStructs.h @@ -367,19 +367,19 @@ typedef struct _LaunchMoreOptionsMenuInitData _LaunchMoreOptionsMenuInitData() { - memset(this,0,sizeof(_LaunchMoreOptionsMenuInitData)); bOnlineGame = true; + bInviteOnly = false; bAllowFriendsOfFriends = true; + bGenerateOptions = false; + bStructures = false; + bFlatWorld = false; + bBonusChest = false; bPVP = true; + bTrust = false; bFireSpreads = true; bTNT = true; - iPad = -1; - worldSize = 3; - seed = L""; - bDisableSaving = false; - newWorldSize = e_worldSize_Unknown; - newWorldSizeOverwriteEdges = false; - + bHostPrivileges = false; + bResetNether = false; bMobGriefing = true; bKeepInventory = false; bDoMobSpawning = true; @@ -387,6 +387,15 @@ typedef struct _LaunchMoreOptionsMenuInitData bDoTileDrops = true; bNaturalRegeneration = true; bDoDaylightCycle = true; + bOnlineSettingChangedBySystem = false; + iPad = -1; + dwTexturePack = 0; + seed = L""; + worldSize = 3; + bDisableSaving = false; + currentWorldSize = e_worldSize_Unknown; + newWorldSize = e_worldSize_Unknown; + newWorldSizeOverwriteEdges = false; } } LaunchMoreOptionsMenuInitData; diff --git a/Minecraft.Client/GameRenderer.cpp b/Minecraft.Client/GameRenderer.cpp index 41894eb6..d853eae9 100644 --- a/Minecraft.Client/GameRenderer.cpp +++ b/Minecraft.Client/GameRenderer.cpp @@ -240,7 +240,7 @@ void GameRenderer::tick(bool first) // 4J - add bFirst PIXEndNamedEvent(); darkenWorldAmountO = darkenWorldAmount; - int idx = BossMobGuiInfo::getIndexFromDimension((int)mc->level->dimension); + int idx = BossMobGuiInfo::getIndexFromDimension((int)(intptr_t)mc->level->dimension); if (BossMobGuiInfo::darkenWorld[idx]) { darkenWorldAmount += 1.0f / (static_cast(SharedConstants::TICKS_PER_SECOND) * 1); diff --git a/Minecraft.Client/MinecraftServer.h b/Minecraft.Client/MinecraftServer.h index cf9c7bec..7674cecd 100644 --- a/Minecraft.Client/MinecraftServer.h +++ b/Minecraft.Client/MinecraftServer.h @@ -265,6 +265,8 @@ private: private: // 4J Added bool m_saveOnExit; + bool m_deleteWorldOnExit; // 4J Added - for hardcore mode world deletion + wstring m_saveFolderName; // 4J Added - stored for hardcore world deletion bool m_suspending; public: @@ -278,6 +280,9 @@ public: void chunkPacketManagement_PostTick(); void setSaveOnExit(bool save) { m_saveOnExit = save; s_bSaveOnExitAnswered = true; } + void setDeleteWorldOnExit(bool del) { m_deleteWorldOnExit = del; } + bool getDeleteWorldOnExit() const { return m_deleteWorldOnExit; } + const wstring& getSaveFolderName() const { return m_saveFolderName; } void Suspend(); bool IsSuspending(); diff --git a/Minecraft.Client/RmlManager.cpp b/Minecraft.Client/RmlManager.cpp new file mode 100644 index 00000000..4e5aa17a --- /dev/null +++ b/Minecraft.Client/RmlManager.cpp @@ -0,0 +1,156 @@ +#include "stdafx.h" +#include "RmlManager.h" + +extern ID3D11Device* g_pd3dDevice; +extern ID3D11DeviceContext* g_pImmediateContext; +extern ID3D11RenderTargetView* g_pRenderTargetView; + +RmlManager& RmlManager::Get() +{ + static RmlManager instance; + return instance; +} + +RmlManager::RmlManager() + : m_initialised(false) + , m_context(nullptr) +{ +} + +RmlManager::~RmlManager() +{ + Shutdown(); +} + +bool RmlManager::Init(int viewport_width, int viewport_height) +{ + if (m_initialised) + return true; + + m_file_interface = std::make_unique(); + m_system_interface = std::make_unique(); + m_renderer = std::make_unique(g_pd3dDevice, g_pImmediateContext); + + if (!m_renderer->IsInitialised()) + { + app.DebugPrintf("[RmlManager] Renderer failed to initialise\n"); + return false; + } + + // Set asset path for RmlUi files (relative to exe directory) + char exePath[MAX_PATH]; + GetModuleFileNameA(nullptr, exePath, MAX_PATH); + char* lastSlash = strrchr(exePath, '\\'); + if (lastSlash) + *(lastSlash + 1) = '\0'; + Rml::String assetPath = Rml::String(exePath) + "Assets/RmlUiAssets"; + m_file_interface->SetAssetPath(assetPath); + + Rml::SetRenderInterface(m_renderer.get()); + Rml::SetSystemInterface(m_system_interface.get()); + Rml::SetFileInterface(m_file_interface.get()); + + if (!Rml::Initialise()) + { + app.DebugPrintf("[RmlManager] RmlUi Core failed to initialise\n"); + return false; + } + + // Load default fonts + Rml::LoadFontFace("C:/Windows/Fonts/segoeui.ttf", false); + Rml::LoadFontFace("C:/Windows/Fonts/segoeuib.ttf", false); + Rml::LoadFontFace("C:/Windows/Fonts/segoeuii.ttf", false); + Rml::LoadFontFace("C:/Windows/Fonts/segoeuiz.ttf", false); + + // Minecraft's UI fonts (from tryashtar/minecraft-ttf) + if (!Rml::LoadFontFace(assetPath + "/fonts/Mojangles.ttf", "Minecraft Default", Rml::Style::FontStyle::Normal, Rml::Style::FontWeight::Normal, false)) + app.DebugPrintf("[RmlManager] FAILED loading Mojangles.ttf\n"); + if (!Rml::LoadFontFace(assetPath + "/fonts/MinecraftTen.ttf", "Minecraft Default", Rml::Style::FontStyle::Normal, Rml::Style::FontWeight::Bold, true)) + app.DebugPrintf("[RmlManager] FAILED loading MinecraftTen.ttf\n"); + + m_context = Rml::CreateContext("main", Rml::Vector2i(viewport_width, viewport_height)); + if (!m_context) + { + app.DebugPrintf("[RmlManager] Failed to create RmlUi context\n"); + Rml::Shutdown(); + return false; + } + app.DebugPrintf("[RmlManager] Context created (%dx%d)\n", viewport_width, viewport_height); + + m_renderer->SetViewport(viewport_width, viewport_height); + m_initialised = true; + app.DebugPrintf("[RmlManager] Initialised (%dx%d)\n", viewport_width, viewport_height); + return true; +} + +void RmlManager::Shutdown() +{ + if (!m_initialised) + return; + + if (m_context) + { + m_context->UnloadAllDocuments(); + Rml::RemoveContext(m_context->GetName()); + m_context = nullptr; + } + + Rml::Shutdown(); + + m_renderer.reset(); + m_system_interface.reset(); + m_file_interface.reset(); + + m_initialised = false; + app.DebugPrintf("[RmlManager] Shutdown\n"); +} + +void RmlManager::Update() +{ + if (!m_initialised || !m_context) + return; + m_context->Update(); +} + +void RmlManager::Render() +{ + if (!m_initialised || !m_context) + return; + + // Ensure we render to the backbuffer (gamma post-process may have restored a + // different RTV). Use the singleton backbuffer RTV that the rest of the engine + // binds at frame start. + if (g_pRenderTargetView) + { + ID3D11DepthStencilView* nullDSV = nullptr; + g_pImmediateContext->OMSetRenderTargets(1, &g_pRenderTargetView, nullDSV); + } + + // Clear any SRV still bound from gamma post-process so it doesn't + // interfere with RmlUi's own texture binding. + ID3D11ShaderResourceView* nullSRV[1] = { nullptr }; + g_pImmediateContext->PSSetShaderResources(0, 1, nullSRV); + + m_renderer->BeginFrame(); + + int docCount = 0; + for (int i = 0; i < m_context->GetNumDocuments(); i++) + { + auto* doc = m_context->GetDocument(i); + if (doc && doc->IsVisible()) + docCount++; + } + if (docCount == 0) + app.DebugPrintf("[RmlManager] WARNING: No visible documents in context\n"); + + m_context->Render(); + m_renderer->EndFrame(); +} + +void RmlManager::OnResize(int width, int height) +{ + if (!m_initialised || !m_context) + return; + m_context->SetDimensions(Rml::Vector2i(width, height)); + m_renderer->SetViewport(width, height); +} diff --git a/Minecraft.Client/RmlManager.h b/Minecraft.Client/RmlManager.h new file mode 100644 index 00000000..e9af36da --- /dev/null +++ b/Minecraft.Client/RmlManager.h @@ -0,0 +1,52 @@ +#pragma once + +#pragma push_macro("byte") +#pragma push_macro("GetNextSibling") +#pragma push_macro("GetFirstChild") +#undef byte +#undef GetNextSibling +#undef GetFirstChild + +#include +#include +#include +#include "RmlUi_Renderer_D3D11.h" +#include "RmlUi_SystemInterface_Win64.h" +#include "RmlUi_FileInterface.h" + +// NOTE: pop_macro at end of file + +class RmlManager +{ +public: + static RmlManager& Get(); + + bool Init(int viewport_width, int viewport_height); + void Shutdown(); + + void Update(); + void Render(); + + Rml::Context* GetContext() const { return m_context; } + RenderInterface_D3D11* GetRenderInterface() const { return m_renderer.get(); } + + void OnResize(int width, int height); + +private: + RmlManager(); + ~RmlManager(); + RmlManager(const RmlManager&) = delete; + RmlManager& operator=(const RmlManager&) = delete; + + bool m_initialised; + + std::unique_ptr m_renderer; + std::unique_ptr m_system_interface; + std::unique_ptr m_file_interface; + + Rml::Context* m_context; +}; + +#pragma pop_macro("GetFirstChild") +#pragma pop_macro("GetNextSibling") +#pragma pop_macro("byte") diff --git a/Minecraft.Client/Tesselator.h b/Minecraft.Client/Tesselator.h index 2edc4a24..d0be867a 100644 --- a/Minecraft.Client/Tesselator.h +++ b/Minecraft.Client/Tesselator.h @@ -1,5 +1,7 @@ #pragma once +#include + class ChunkRebuildData; class Tesselator { diff --git a/Minecraft.Client/TileRenderer.cpp b/Minecraft.Client/TileRenderer.cpp index a5ee810f..4356437d 100644 --- a/Minecraft.Client/TileRenderer.cpp +++ b/Minecraft.Client/TileRenderer.cpp @@ -156,7 +156,7 @@ TileRenderer::TileRenderer( LevelSource* level, int xMin, int yMin, int zMin, un this->zMin2 = zMin-2; this->tileIds = tileIds; cache = s_tlsCache; - std::memset(cache, 0, TILE_RENDERER_CACHE_SIZE * sizeof(unsigned int)); + memset(cache, 0, TILE_RENDERER_CACHE_SIZE * sizeof(unsigned int)); } TileRenderer::~TileRenderer() = default; diff --git a/Minecraft.Client/Windows64/Libs/4JLibs/impls/Windows_Libs/Render/vendor/DirectXMath/DirectXColors.h b/Minecraft.Client/Windows64/Libs/4JLibs/impls/Windows_Libs/Render/vendor/DirectXMath/DirectXColors.h new file mode 100644 index 00000000..18614505 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/4JLibs/impls/Windows_Libs/Render/vendor/DirectXMath/DirectXColors.h @@ -0,0 +1,311 @@ +//------------------------------------------------------------------------------------- +// DirectXColors.h -- C++ Color Math library +// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +// +// https://go.microsoft.com/fwlink/?LinkID=615560 +//------------------------------------------------------------------------------------- + +#pragma once + +#include "DirectXMath.h" + +namespace DirectX +{ + + namespace Colors + { + // Standard colors (Red/Green/Blue/Alpha) in sRGB colorspace + XMGLOBALCONST XMVECTORF32 AliceBlue = { { { 0.941176534f, 0.972549081f, 1.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 AntiqueWhite = { { { 0.980392218f, 0.921568692f, 0.843137324f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Aqua = { { { 0.f, 1.f, 1.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Aquamarine = { { { 0.498039246f, 1.f, 0.831372619f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Azure = { { { 0.941176534f, 1.f, 1.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Beige = { { { 0.960784376f, 0.960784376f, 0.862745166f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Bisque = { { { 1.f, 0.894117713f, 0.768627524f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Black = { { { 0.f, 0.f, 0.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 BlanchedAlmond = { { { 1.f, 0.921568692f, 0.803921640f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Blue = { { { 0.f, 0.f, 1.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 BlueViolet = { { { 0.541176498f, 0.168627456f, 0.886274576f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Brown = { { { 0.647058845f, 0.164705887f, 0.164705887f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 BurlyWood = { { { 0.870588303f, 0.721568644f, 0.529411793f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 CadetBlue = { { { 0.372549027f, 0.619607866f, 0.627451003f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Chartreuse = { { { 0.498039246f, 1.f, 0.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Chocolate = { { { 0.823529482f, 0.411764741f, 0.117647067f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Coral = { { { 1.f, 0.498039246f, 0.313725501f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 CornflowerBlue = { { { 0.392156899f, 0.584313750f, 0.929411829f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Cornsilk = { { { 1.f, 0.972549081f, 0.862745166f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Crimson = { { { 0.862745166f, 0.078431375f, 0.235294133f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Cyan = { { { 0.f, 1.f, 1.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 DarkBlue = { { { 0.f, 0.f, 0.545098066f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 DarkCyan = { { { 0.f, 0.545098066f, 0.545098066f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 DarkGoldenrod = { { { 0.721568644f, 0.525490224f, 0.043137256f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 DarkGray = { { { 0.662745118f, 0.662745118f, 0.662745118f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 DarkGreen = { { { 0.f, 0.392156899f, 0.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 DarkKhaki = { { { 0.741176486f, 0.717647076f, 0.419607878f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 DarkMagenta = { { { 0.545098066f, 0.f, 0.545098066f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 DarkOliveGreen = { { { 0.333333343f, 0.419607878f, 0.184313729f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 DarkOrange = { { { 1.f, 0.549019635f, 0.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 DarkOrchid = { { { 0.600000024f, 0.196078449f, 0.800000072f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 DarkRed = { { { 0.545098066f, 0.f, 0.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 DarkSalmon = { { { 0.913725555f, 0.588235319f, 0.478431404f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 DarkSeaGreen = { { { 0.560784340f, 0.737254918f, 0.545098066f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 DarkSlateBlue = { { { 0.282352954f, 0.239215702f, 0.545098066f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 DarkSlateGray = { { { 0.184313729f, 0.309803933f, 0.309803933f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 DarkTurquoise = { { { 0.f, 0.807843208f, 0.819607913f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 DarkViolet = { { { 0.580392182f, 0.f, 0.827451050f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 DeepPink = { { { 1.f, 0.078431375f, 0.576470613f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 DeepSkyBlue = { { { 0.f, 0.749019623f, 1.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 DimGray = { { { 0.411764741f, 0.411764741f, 0.411764741f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 DodgerBlue = { { { 0.117647067f, 0.564705908f, 1.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Firebrick = { { { 0.698039234f, 0.133333340f, 0.133333340f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 FloralWhite = { { { 1.f, 0.980392218f, 0.941176534f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 ForestGreen = { { { 0.133333340f, 0.545098066f, 0.133333340f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Fuchsia = { { { 1.f, 0.f, 1.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Gainsboro = { { { 0.862745166f, 0.862745166f, 0.862745166f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 GhostWhite = { { { 0.972549081f, 0.972549081f, 1.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Gold = { { { 1.f, 0.843137324f, 0.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Goldenrod = { { { 0.854902029f, 0.647058845f, 0.125490203f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Gray = { { { 0.501960814f, 0.501960814f, 0.501960814f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Green = { { { 0.f, 0.501960814f, 0.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 GreenYellow = { { { 0.678431392f, 1.f, 0.184313729f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Honeydew = { { { 0.941176534f, 1.f, 0.941176534f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 HotPink = { { { 1.f, 0.411764741f, 0.705882370f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 IndianRed = { { { 0.803921640f, 0.360784322f, 0.360784322f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Indigo = { { { 0.294117659f, 0.f, 0.509803951f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Ivory = { { { 1.f, 1.f, 0.941176534f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Khaki = { { { 0.941176534f, 0.901960850f, 0.549019635f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Lavender = { { { 0.901960850f, 0.901960850f, 0.980392218f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 LavenderBlush = { { { 1.f, 0.941176534f, 0.960784376f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 LawnGreen = { { { 0.486274540f, 0.988235354f, 0.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 LemonChiffon = { { { 1.f, 0.980392218f, 0.803921640f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 LightBlue = { { { 0.678431392f, 0.847058892f, 0.901960850f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 LightCoral = { { { 0.941176534f, 0.501960814f, 0.501960814f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 LightCyan = { { { 0.878431439f, 1.f, 1.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 LightGoldenrodYellow = { { { 0.980392218f, 0.980392218f, 0.823529482f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 LightGray = { { { 0.827451050f, 0.827451050f, 0.827451050f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 LightGreen = { { { 0.564705908f, 0.933333397f, 0.564705908f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 LightPink = { { { 1.f, 0.713725507f, 0.756862819f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 LightSalmon = { { { 1.f, 0.627451003f, 0.478431404f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 LightSeaGreen = { { { 0.125490203f, 0.698039234f, 0.666666687f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 LightSkyBlue = { { { 0.529411793f, 0.807843208f, 0.980392218f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 LightSlateGray = { { { 0.466666698f, 0.533333361f, 0.600000024f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 LightSteelBlue = { { { 0.690196097f, 0.768627524f, 0.870588303f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 LightYellow = { { { 1.f, 1.f, 0.878431439f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Lime = { { { 0.f, 1.f, 0.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 LimeGreen = { { { 0.196078449f, 0.803921640f, 0.196078449f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Linen = { { { 0.980392218f, 0.941176534f, 0.901960850f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Magenta = { { { 1.f, 0.f, 1.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Maroon = { { { 0.501960814f, 0.f, 0.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 MediumAquamarine = { { { 0.400000036f, 0.803921640f, 0.666666687f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 MediumBlue = { { { 0.f, 0.f, 0.803921640f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 MediumOrchid = { { { 0.729411781f, 0.333333343f, 0.827451050f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 MediumPurple = { { { 0.576470613f, 0.439215720f, 0.858823597f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 MediumSeaGreen = { { { 0.235294133f, 0.701960802f, 0.443137288f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 MediumSlateBlue = { { { 0.482352972f, 0.407843173f, 0.933333397f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 MediumSpringGreen = { { { 0.f, 0.980392218f, 0.603921592f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 MediumTurquoise = { { { 0.282352954f, 0.819607913f, 0.800000072f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 MediumVioletRed = { { { 0.780392230f, 0.082352944f, 0.521568656f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 MidnightBlue = { { { 0.098039225f, 0.098039225f, 0.439215720f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 MintCream = { { { 0.960784376f, 1.f, 0.980392218f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 MistyRose = { { { 1.f, 0.894117713f, 0.882353008f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Moccasin = { { { 1.f, 0.894117713f, 0.709803939f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 NavajoWhite = { { { 1.f, 0.870588303f, 0.678431392f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Navy = { { { 0.f, 0.f, 0.501960814f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 OldLace = { { { 0.992156923f, 0.960784376f, 0.901960850f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Olive = { { { 0.501960814f, 0.501960814f, 0.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 OliveDrab = { { { 0.419607878f, 0.556862772f, 0.137254909f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Orange = { { { 1.f, 0.647058845f, 0.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 OrangeRed = { { { 1.f, 0.270588249f, 0.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Orchid = { { { 0.854902029f, 0.439215720f, 0.839215755f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 PaleGoldenrod = { { { 0.933333397f, 0.909803987f, 0.666666687f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 PaleGreen = { { { 0.596078455f, 0.984313786f, 0.596078455f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 PaleTurquoise = { { { 0.686274529f, 0.933333397f, 0.933333397f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 PaleVioletRed = { { { 0.858823597f, 0.439215720f, 0.576470613f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 PapayaWhip = { { { 1.f, 0.937254965f, 0.835294187f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 PeachPuff = { { { 1.f, 0.854902029f, 0.725490212f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Peru = { { { 0.803921640f, 0.521568656f, 0.247058839f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Pink = { { { 1.f, 0.752941251f, 0.796078503f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Plum = { { { 0.866666734f, 0.627451003f, 0.866666734f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 PowderBlue = { { { 0.690196097f, 0.878431439f, 0.901960850f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Purple = { { { 0.501960814f, 0.f, 0.501960814f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Red = { { { 1.f, 0.f, 0.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 RosyBrown = { { { 0.737254918f, 0.560784340f, 0.560784340f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 RoyalBlue = { { { 0.254901975f, 0.411764741f, 0.882353008f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 SaddleBrown = { { { 0.545098066f, 0.270588249f, 0.074509807f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Salmon = { { { 0.980392218f, 0.501960814f, 0.447058856f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 SandyBrown = { { { 0.956862807f, 0.643137276f, 0.376470625f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 SeaGreen = { { { 0.180392161f, 0.545098066f, 0.341176480f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 SeaShell = { { { 1.f, 0.960784376f, 0.933333397f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Sienna = { { { 0.627451003f, 0.321568638f, 0.176470593f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Silver = { { { 0.752941251f, 0.752941251f, 0.752941251f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 SkyBlue = { { { 0.529411793f, 0.807843208f, 0.921568692f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 SlateBlue = { { { 0.415686309f, 0.352941185f, 0.803921640f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 SlateGray = { { { 0.439215720f, 0.501960814f, 0.564705908f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Snow = { { { 1.f, 0.980392218f, 0.980392218f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 SpringGreen = { { { 0.f, 1.f, 0.498039246f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 SteelBlue = { { { 0.274509817f, 0.509803951f, 0.705882370f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Tan = { { { 0.823529482f, 0.705882370f, 0.549019635f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Teal = { { { 0.f, 0.501960814f, 0.501960814f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Thistle = { { { 0.847058892f, 0.749019623f, 0.847058892f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Tomato = { { { 1.f, 0.388235331f, 0.278431386f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Transparent = { { { 0.f, 0.f, 0.f, 0.f } } }; + XMGLOBALCONST XMVECTORF32 Turquoise = { { { 0.250980407f, 0.878431439f, 0.815686345f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Violet = { { { 0.933333397f, 0.509803951f, 0.933333397f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Wheat = { { { 0.960784376f, 0.870588303f, 0.701960802f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 White = { { { 1.f, 1.f, 1.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 WhiteSmoke = { { { 0.960784376f, 0.960784376f, 0.960784376f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Yellow = { { { 1.f, 1.f, 0.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 YellowGreen = { { { 0.603921592f, 0.803921640f, 0.196078449f, 1.f } } }; + + } // namespace Colors + + namespace ColorsLinear + { + // Standard colors (Red/Green/Blue/Alpha) in linear colorspace + XMGLOBALCONST XMVECTORF32 AliceBlue = { { { 0.871367335f, 0.938685894f, 1.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 AntiqueWhite = { { { 0.955973506f, 0.830770075f, 0.679542601f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Aqua = { { { 0.f, 1.f, 1.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Aquamarine = { { { 0.212230787f, 1.f, 0.658374965f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Azure = { { { 0.871367335f, 1.f, 1.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Beige = { { { 0.913098991f, 0.913098991f, 0.715693772f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Bisque = { { { 1.f, 0.775822461f, 0.552011609f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Black = { { { 0.f, 0.f, 0.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 BlanchedAlmond = { { { 1.f, 0.830770075f, 0.610495746f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Blue = { { { 0.f, 0.f, 1.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 BlueViolet = { { { 0.254152179f, 0.024157630f, 0.760524750f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Brown = { { { 0.376262218f, 0.023153365f, 0.023153365f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 BurlyWood = { { { 0.730461001f, 0.479320228f, 0.242281199f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 CadetBlue = { { { 0.114435382f, 0.341914445f, 0.351532698f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Chartreuse = { { { 0.212230787f, 1.f, 0.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Chocolate = { { { 0.644479871f, 0.141263321f, 0.012983031f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Coral = { { { 1.f, 0.212230787f, 0.080219828f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 CornflowerBlue = { { { 0.127437726f, 0.300543845f, 0.846873462f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Cornsilk = { { { 1.f, 0.938685894f, 0.715693772f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Crimson = { { { 0.715693772f, 0.006995410f, 0.045186214f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Cyan = { { { 0.f, 1.f, 1.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 DarkBlue = { { { 0.f, 0.f, 0.258182913f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 DarkCyan = { { { 0.f, 0.258182913f, 0.258182913f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 DarkGoldenrod = { { { 0.479320228f, 0.238397658f, 0.003346536f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 DarkGray = { { { 0.396755308f, 0.396755308f, 0.396755308f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 DarkGreen = { { { 0.f, 0.127437726f, 0.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 DarkKhaki = { { { 0.508881450f, 0.473531544f, 0.147027299f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 DarkMagenta = { { { 0.258182913f, 0.f, 0.258182913f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 DarkOliveGreen = { { { 0.090841733f, 0.147027299f, 0.028426038f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 DarkOrange = { { { 1.f, 0.262250721f, 0.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 DarkOrchid = { { { 0.318546832f, 0.031896040f, 0.603827536f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 DarkRed = { { { 0.258182913f, 0.f, 0.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 DarkSalmon = { { { 0.814846814f, 0.304987371f, 0.194617867f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 DarkSeaGreen = { { { 0.274677366f, 0.502886593f, 0.258182913f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 DarkSlateBlue = { { { 0.064803280f, 0.046665095f, 0.258182913f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 DarkSlateGray = { { { 0.028426038f, 0.078187428f, 0.078187428f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 DarkTurquoise = { { { 0.f, 0.617206752f, 0.637597024f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 DarkViolet = { { { 0.296138316f, 0.f, 0.651405811f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 DeepPink = { { { 1.f, 0.006995410f, 0.291770697f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 DeepSkyBlue = { { { 0.f, 0.520995677f, 1.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 DimGray = { { { 0.141263321f, 0.141263321f, 0.141263321f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 DodgerBlue = { { { 0.012983031f, 0.278894335f, 1.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Firebrick = { { { 0.445201248f, 0.015996292f, 0.015996292f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 FloralWhite = { { { 1.f, 0.955973506f, 0.871367335f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 ForestGreen = { { { 0.015996292f, 0.258182913f, 0.015996292f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Fuchsia = { { { 1.f, 0.f, 1.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Gainsboro = { { { 0.715693772f, 0.715693772f, 0.715693772f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 GhostWhite = { { { 0.938685894f, 0.938685894f, 1.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Gold = { { { 1.f, 0.679542601f, 0.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Goldenrod = { { { 0.701102138f, 0.376262218f, 0.014443844f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Gray = { { { 0.215860531f, 0.215860531f, 0.215860531f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Green = { { { 0.f, 0.215860531f, 0.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 GreenYellow = { { { 0.417885154f, 1.f, 0.028426038f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Honeydew = { { { 0.871367335f, 1.f, 0.871367335f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 HotPink = { { { 1.f, 0.141263321f, 0.456411064f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 IndianRed = { { { 0.610495746f, 0.107023112f, 0.107023112f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Indigo = { { { 0.070360109f, 0.f, 0.223227978f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Ivory = { { { 1.f, 1.f, 0.871367335f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Khaki = { { { 0.871367335f, 0.791298151f, 0.262250721f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Lavender = { { { 0.791298151f, 0.791298151f, 0.955973506f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 LavenderBlush = { { { 1.f, 0.871367335f, 0.913098991f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 LawnGreen = { { { 0.201556295f, 0.973445475f, 0.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 LemonChiffon = { { { 1.f, 0.955973506f, 0.610495746f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 LightBlue = { { { 0.417885154f, 0.686685443f, 0.791298151f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 LightCoral = { { { 0.871367335f, 0.215860531f, 0.215860531f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 LightCyan = { { { 0.745404482f, 1.f, 1.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 LightGoldenrodYellow = { { { 0.955973506f, 0.955973506f, 0.644479871f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 LightGray = { { { 0.651405811f, 0.651405811f, 0.651405811f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 LightGreen = { { { 0.278894335f, 0.854992807f, 0.278894335f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 LightPink = { { { 1.f, 0.467783839f, 0.533276618f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 LightSalmon = { { { 1.f, 0.351532698f, 0.194617867f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 LightSeaGreen = { { { 0.014443844f, 0.445201248f, 0.401977867f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 LightSkyBlue = { { { 0.242281199f, 0.617206752f, 0.955973506f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 LightSlateGray = { { { 0.184475034f, 0.246201396f, 0.318546832f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 LightSteelBlue = { { { 0.434153706f, 0.552011609f, 0.730461001f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 LightYellow = { { { 1.f, 1.f, 0.745404482f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Lime = { { { 0.f, 1.f, 0.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 LimeGreen = { { { 0.031896040f, 0.610495746f, 0.031896040f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Linen = { { { 0.955973506f, 0.871367335f, 0.791298151f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Magenta = { { { 1.f, 0.f, 1.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Maroon = { { { 0.215860531f, 0.f, 0.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 MediumAquamarine = { { { 0.132868364f, 0.610495746f, 0.401977867f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 MediumBlue = { { { 0.f, 0.f, 0.610495746f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 MediumOrchid = { { { 0.491020888f, 0.090841733f, 0.651405811f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 MediumPurple = { { { 0.291770697f, 0.162029430f, 0.708376050f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 MediumSeaGreen = { { { 0.045186214f, 0.450785846f, 0.165132239f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 MediumSlateBlue = { { { 0.198069349f, 0.138431653f, 0.854992807f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 MediumSpringGreen = { { { 0.f, 0.955973506f, 0.323143244f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 MediumTurquoise = { { { 0.064803280f, 0.637597024f, 0.603827536f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 MediumVioletRed = { { { 0.571125031f, 0.007499032f, 0.234550655f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 MidnightBlue = { { { 0.009721218f, 0.009721218f, 0.162029430f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 MintCream = { { { 0.913098991f, 1.f, 0.955973506f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 MistyRose = { { { 1.f, 0.775822461f, 0.752942443f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Moccasin = { { { 1.f, 0.775822461f, 0.462077051f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 NavajoWhite = { { { 1.f, 0.730461001f, 0.417885154f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Navy = { { { 0.f, 0.f, 0.215860531f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 OldLace = { { { 0.982250869f, 0.913098991f, 0.791298151f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Olive = { { { 0.215860531f, 0.215860531f, 0.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 OliveDrab = { { { 0.147027299f, 0.270497859f, 0.016807375f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Orange = { { { 1.f, 0.376262218f, 0.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 OrangeRed = { { { 1.f, 0.059511241f, 0.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Orchid = { { { 0.701102138f, 0.162029430f, 0.672443330f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 PaleGoldenrod = { { { 0.854992807f, 0.806952477f, 0.401977867f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 PaleGreen = { { { 0.313988745f, 0.964686573f, 0.313988745f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 PaleTurquoise = { { { 0.428690553f, 0.854992807f, 0.854992807f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 PaleVioletRed = { { { 0.708376050f, 0.162029430f, 0.291770697f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 PapayaWhip = { { { 1.f, 0.863157392f, 0.665387452f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 PeachPuff = { { { 1.f, 0.701102138f, 0.485149980f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Peru = { { { 0.610495746f, 0.234550655f, 0.049706575f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Pink = { { { 1.f, 0.527115345f, 0.597202003f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Plum = { { { 0.723055363f, 0.351532698f, 0.723055363f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 PowderBlue = { { { 0.434153706f, 0.745404482f, 0.791298151f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Purple = { { { 0.215860531f, 0.f, 0.215860531f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Red = { { { 1.f, 0.f, 0.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 RosyBrown = { { { 0.502886593f, 0.274677366f, 0.274677366f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 RoyalBlue = { { { 0.052860655f, 0.141263321f, 0.752942443f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 SaddleBrown = { { { 0.258182913f, 0.059511241f, 0.006512091f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Salmon = { { { 0.955973506f, 0.215860531f, 0.168269455f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 SandyBrown = { { { 0.904661357f, 0.371237785f, 0.116970696f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 SeaGreen = { { { 0.027320892f, 0.258182913f, 0.095307484f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 SeaShell = { { { 1.f, 0.913098991f, 0.854992807f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Sienna = { { { 0.351532698f, 0.084376216f, 0.026241222f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Silver = { { { 0.527115345f, 0.527115345f, 0.527115345f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 SkyBlue = { { { 0.242281199f, 0.617206752f, 0.830770075f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 SlateBlue = { { { 0.144128501f, 0.102241747f, 0.610495746f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 SlateGray = { { { 0.162029430f, 0.215860531f, 0.278894335f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Snow = { { { 1.f, 0.955973506f, 0.955973506f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 SpringGreen = { { { 0.f, 1.f, 0.212230787f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 SteelBlue = { { { 0.061246071f, 0.223227978f, 0.456411064f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Tan = { { { 0.644479871f, 0.456411064f, 0.262250721f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Teal = { { { 0.f, 0.215860531f, 0.215860531f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Thistle = { { { 0.686685443f, 0.520995677f, 0.686685443f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Tomato = { { { 1.f, 0.124771863f, 0.063010029f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Transparent = { { { 0.f, 0.f, 0.f, 0.f } } }; + XMGLOBALCONST XMVECTORF32 Turquoise = { { { 0.051269468f, 0.745404482f, 0.630757332f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Violet = { { { 0.854992807f, 0.223227978f, 0.854992807f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Wheat = { { { 0.913098991f, 0.730461001f, 0.450785846f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 White = { { { 1.f, 1.f, 1.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 WhiteSmoke = { { { 0.913098991f, 0.913098991f, 0.913098991f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 Yellow = { { { 1.f, 1.f, 0.f, 1.f } } }; + XMGLOBALCONST XMVECTORF32 YellowGreen = { { { 0.323143244f, 0.610495746f, 0.031896040f, 1.f } } }; + + } // namespace ColorsLinear + +} // namespace DirectX diff --git a/Minecraft.Client/Windows64/Libs/4JLibs/impls/Windows_Libs/Render/vendor/DirectXMath/DirectXMath.h b/Minecraft.Client/Windows64/Libs/4JLibs/impls/Windows_Libs/Render/vendor/DirectXMath/DirectXMath.h new file mode 100644 index 00000000..ae3cce1d --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/4JLibs/impls/Windows_Libs/Render/vendor/DirectXMath/DirectXMath.h @@ -0,0 +1,2385 @@ +//------------------------------------------------------------------------------------- +// DirectXMath.h -- SIMD C++ Math library +// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +// +// https://go.microsoft.com/fwlink/?LinkID=615560 +//------------------------------------------------------------------------------------- + +#pragma once + +#ifndef __cplusplus +#error DirectX Math requires C++ +#endif + +#define DIRECTX_MATH_VERSION 321 + +#if defined(_MSC_VER) && (_MSC_VER < 1910) +#error DirectX Math requires Visual C++ 2017 or later. +#endif + +#if defined(_MSC_VER) && !defined(_M_ARM) && !defined(_M_ARM64) && !defined(_M_HYBRID_X86_ARM64) && !defined(_M_ARM64EC) && (!_MANAGED) && (!_M_CEE) && (!defined(_M_IX86_FP) || (_M_IX86_FP > 1)) && !defined(_XM_NO_INTRINSICS_) && !defined(_XM_VECTORCALL_) +#define _XM_VECTORCALL_ 1 +#endif + +#if _XM_VECTORCALL_ +#define XM_CALLCONV __vectorcall +#elif defined(__GNUC__) +#define XM_CALLCONV +#else +#define XM_CALLCONV __fastcall +#endif + +#ifndef XM_DEPRECATED +#if (__cplusplus >= 201402L) +#define XM_DEPRECATED [[deprecated]] +#elif defined(__GNUC__) +#define XM_DEPRECATED __attribute__ ((deprecated)) +#else +#define XM_DEPRECATED __declspec(deprecated("This is deprecated and will be removed in a future version.")) +#endif +#endif + +#if !defined(_XM_AVX2_INTRINSICS_) && defined(__AVX2__) && !defined(_XM_NO_INTRINSICS_) +#define _XM_AVX2_INTRINSICS_ +#endif + +#if !defined(_XM_FMA3_INTRINSICS_) && defined(_XM_AVX2_INTRINSICS_) && !defined(_XM_NO_INTRINSICS_) +#define _XM_FMA3_INTRINSICS_ +#endif + +#if !defined(_XM_F16C_INTRINSICS_) && defined(_XM_AVX2_INTRINSICS_) && !defined(_XM_NO_INTRINSICS_) +#define _XM_F16C_INTRINSICS_ +#endif + +#if !defined(_XM_F16C_INTRINSICS_) && defined(__F16C__) && !defined(_XM_NO_INTRINSICS_) +#define _XM_F16C_INTRINSICS_ +#endif + +#if defined(_XM_FMA3_INTRINSICS_) && !defined(_XM_AVX_INTRINSICS_) +#define _XM_AVX_INTRINSICS_ +#endif + +#if defined(_XM_F16C_INTRINSICS_) && !defined(_XM_AVX_INTRINSICS_) +#define _XM_AVX_INTRINSICS_ +#endif + +#if !defined(_XM_AVX_INTRINSICS_) && defined(__AVX__) && !defined(_XM_NO_INTRINSICS_) +#define _XM_AVX_INTRINSICS_ +#endif + +#if defined(_XM_AVX_INTRINSICS_) && !defined(_XM_SSE4_INTRINSICS_) +#define _XM_SSE4_INTRINSICS_ +#endif + +#if defined(_XM_SSE4_INTRINSICS_) && !defined(_XM_SSE3_INTRINSICS_) +#define _XM_SSE3_INTRINSICS_ +#endif + +#if defined(_XM_SSE3_INTRINSICS_) && !defined(_XM_SSE_INTRINSICS_) +#define _XM_SSE_INTRINSICS_ +#endif + +#if !defined(_XM_ARM_NEON_INTRINSICS_) && !defined(_XM_SSE_INTRINSICS_) && !defined(_XM_NO_INTRINSICS_) +#if (defined(_M_IX86) || defined(_M_X64) || __i386__ || __x86_64__ || __powerpc64__) && !defined(_M_HYBRID_X86_ARM64) && !defined(_M_ARM64EC) +#define _XM_SSE_INTRINSICS_ +#elif defined(_M_ARM) || defined(_M_ARM64) || defined(_M_HYBRID_X86_ARM64) || defined(_M_ARM64EC) || __arm__ || __aarch64__ +#define _XM_ARM_NEON_INTRINSICS_ +#elif !defined(_XM_NO_INTRINSICS_) +#error DirectX Math does not support this target +#endif +#endif // !_XM_ARM_NEON_INTRINSICS_ && !_XM_SSE_INTRINSICS_ && !_XM_NO_INTRINSICS_ + +#if defined(_XM_SSE_INTRINSICS_) && defined(_MSC_VER) && (_MSC_VER >= 1920) && !defined(__clang__) && !defined(_XM_SVML_INTRINSICS_) && !defined(_XM_DISABLE_INTEL_SVML_) +#define _XM_SVML_INTRINSICS_ +#endif + +#if !defined(_XM_NO_XMVECTOR_OVERLOADS_) && (defined(__clang__) || defined(__GNUC__)) && !defined(_XM_NO_INTRINSICS_) +#define _XM_NO_XMVECTOR_OVERLOADS_ +#endif + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4514 4820) +// C4514/4820: Off by default noise +#endif +#include +#include +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#ifndef _XM_NO_INTRINSICS_ + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4987) +// C4987: Off by default noise +#endif +#if defined(_MSC_VER) || defined(__MINGW32__) +#include +#endif +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#if (defined(__clang__) || defined(__GNUC__)) && (__x86_64__ || __i386__) && !defined(__MINGW32__) && !defined(_MSC_VER) +#include +#endif + +#ifdef _XM_SSE_INTRINSICS_ +#include +#include + +#ifdef _XM_SSE3_INTRINSICS_ +#include +#endif + +#ifdef _XM_SSE4_INTRINSICS_ +#include +#endif + +#ifdef _XM_AVX_INTRINSICS_ +#include +#endif + +#elif defined(_XM_ARM_NEON_INTRINSICS_) +#if defined(_MSC_VER) && !defined(__clang__) && (defined(_M_ARM64) || defined(_M_HYBRID_X86_ARM64) || defined(_M_ARM64EC)) +#include +#else +#include +#endif +#endif +#endif // !_XM_NO_INTRINSICS_ + +#include "sal.h" +#include + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4005 4668) +// C4005/4668: Old header issue +#endif +#include +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#if (__cplusplus >= 201703L) +#define XM_ALIGNED_DATA(x) alignas(x) +#define XM_ALIGNED_STRUCT(x) struct alignas(x) +#elif defined(__GNUC__) +#define XM_ALIGNED_DATA(x) __attribute__ ((aligned(x))) +#define XM_ALIGNED_STRUCT(x) struct __attribute__ ((aligned(x))) +#else +#define XM_ALIGNED_DATA(x) __declspec(align(x)) +#define XM_ALIGNED_STRUCT(x) __declspec(align(x)) struct +#endif + +#if (__cplusplus >= 202002L) +#include +#endif + +/**************************************************************************** + * + * Conditional intrinsics + * + ****************************************************************************/ + +#if defined(_XM_SSE_INTRINSICS_) && !defined(_XM_NO_INTRINSICS_) + +#if defined(_XM_NO_MOVNT_) +#define XM_STREAM_PS( p, a ) _mm_store_ps((p), (a)) +#define XM256_STREAM_PS( p, a ) _mm256_store_ps((p), (a)) +#define XM_SFENCE() +#else +#define XM_STREAM_PS( p, a ) _mm_stream_ps((p), (a)) +#define XM256_STREAM_PS( p, a ) _mm256_stream_ps((p), (a)) +#define XM_SFENCE() _mm_sfence() +#endif + +#if defined(_XM_FMA3_INTRINSICS_) +#define XM_FMADD_PS( a, b, c ) _mm_fmadd_ps((a), (b), (c)) +#define XM_FNMADD_PS( a, b, c ) _mm_fnmadd_ps((a), (b), (c)) +#else +#define XM_FMADD_PS( a, b, c ) _mm_add_ps(_mm_mul_ps((a), (b)), (c)) +#define XM_FNMADD_PS( a, b, c ) _mm_sub_ps((c), _mm_mul_ps((a), (b))) +#endif + +#if defined(_XM_AVX_INTRINSICS_) && defined(_XM_FAVOR_INTEL_) +#define XM_PERMUTE_PS( v, c ) _mm_permute_ps((v), c ) +#else +#define XM_PERMUTE_PS( v, c ) _mm_shuffle_ps((v), (v), c ) +#endif + +#if (defined(__GNUC__) && !defined(__clang__) && (__GNUC__ < 11)) || defined(__powerpc64__) +#define XM_LOADU_SI16( p ) _mm_cvtsi32_si128(*reinterpret_cast(p)) +#else +#define XM_LOADU_SI16( p ) _mm_loadu_si16(p) +#endif + +#endif // _XM_SSE_INTRINSICS_ && !_XM_NO_INTRINSICS_ + +#if defined(_XM_ARM_NEON_INTRINSICS_) && !defined(_XM_NO_INTRINSICS_) + +#if defined(__clang__) || defined(__GNUC__) +#define XM_PREFETCH( a ) __builtin_prefetch(a) +#elif defined(_MSC_VER) +#define XM_PREFETCH( a ) __prefetch(a) +#else +#define XM_PREFETCH( a ) +#endif + +#endif // _XM_ARM_NEON_INTRINSICS_ && !_XM_NO_INTRINSICS_ + +namespace DirectX +{ + + /**************************************************************************** + * + * Constant definitions + * + ****************************************************************************/ + +#if defined(__XNAMATH_H__) && defined(XM_PI) +#undef XM_PI +#undef XM_2PI +#undef XM_1DIVPI +#undef XM_1DIV2PI +#undef XM_PIDIV2 +#undef XM_PIDIV4 +#undef XM_SELECT_0 +#undef XM_SELECT_1 +#undef XM_PERMUTE_0X +#undef XM_PERMUTE_0Y +#undef XM_PERMUTE_0Z +#undef XM_PERMUTE_0W +#undef XM_PERMUTE_1X +#undef XM_PERMUTE_1Y +#undef XM_PERMUTE_1Z +#undef XM_PERMUTE_1W +#undef XM_CRMASK_CR6 +#undef XM_CRMASK_CR6TRUE +#undef XM_CRMASK_CR6FALSE +#undef XM_CRMASK_CR6BOUNDS +#undef XM_CACHE_LINE_SIZE +#endif + + constexpr float XM_PI = 3.141592654f; + constexpr float XM_2PI = 6.283185307f; + constexpr float XM_1DIVPI = 0.318309886f; + constexpr float XM_1DIV2PI = 0.159154943f; + constexpr float XM_PIDIV2 = 1.570796327f; + constexpr float XM_PIDIV4 = 0.785398163f; + + constexpr uint32_t XM_SELECT_0 = 0x00000000; + constexpr uint32_t XM_SELECT_1 = 0xFFFFFFFF; + + constexpr uint32_t XM_PERMUTE_0X = 0; + constexpr uint32_t XM_PERMUTE_0Y = 1; + constexpr uint32_t XM_PERMUTE_0Z = 2; + constexpr uint32_t XM_PERMUTE_0W = 3; + constexpr uint32_t XM_PERMUTE_1X = 4; + constexpr uint32_t XM_PERMUTE_1Y = 5; + constexpr uint32_t XM_PERMUTE_1Z = 6; + constexpr uint32_t XM_PERMUTE_1W = 7; + + constexpr uint32_t XM_SWIZZLE_X = 0; + constexpr uint32_t XM_SWIZZLE_Y = 1; + constexpr uint32_t XM_SWIZZLE_Z = 2; + constexpr uint32_t XM_SWIZZLE_W = 3; + + constexpr uint32_t XM_CRMASK_CR6 = 0x000000F0; + constexpr uint32_t XM_CRMASK_CR6TRUE = 0x00000080; + constexpr uint32_t XM_CRMASK_CR6FALSE = 0x00000020; + constexpr uint32_t XM_CRMASK_CR6BOUNDS = XM_CRMASK_CR6FALSE; + +#if defined(_M_ARM) || defined(_M_ARM64) || defined(_M_HYBRID_X86_ARM64) || defined(_M_ARM64EC) || __arm__ || __aarch64__ + constexpr size_t XM_CACHE_LINE_SIZE = 128; +#else + constexpr size_t XM_CACHE_LINE_SIZE = 64; +#endif + + + /**************************************************************************** + * + * Macros + * + ****************************************************************************/ + +#if defined(__XNAMATH_H__) && defined(XMComparisonAllTrue) +#undef XMComparisonAllTrue +#undef XMComparisonAnyTrue +#undef XMComparisonAllFalse +#undef XMComparisonAnyFalse +#undef XMComparisonMixed +#undef XMComparisonAllInBounds +#undef XMComparisonAnyOutOfBounds +#endif + + // Unit conversion + + constexpr float XMConvertToRadians(float fDegrees) noexcept { return fDegrees * (XM_PI / 180.0f); } + constexpr float XMConvertToDegrees(float fRadians) noexcept { return fRadians * (180.0f / XM_PI); } + + // Condition register evaluation proceeding a recording (R) comparison + + constexpr bool XMComparisonAllTrue(uint32_t CR) noexcept { return (CR & XM_CRMASK_CR6TRUE) == XM_CRMASK_CR6TRUE; } + constexpr bool XMComparisonAnyTrue(uint32_t CR) noexcept { return (CR & XM_CRMASK_CR6FALSE) != XM_CRMASK_CR6FALSE; } + constexpr bool XMComparisonAllFalse(uint32_t CR) noexcept { return (CR & XM_CRMASK_CR6FALSE) == XM_CRMASK_CR6FALSE; } + constexpr bool XMComparisonAnyFalse(uint32_t CR) noexcept { return (CR & XM_CRMASK_CR6TRUE) != XM_CRMASK_CR6TRUE; } + constexpr bool XMComparisonMixed(uint32_t CR) noexcept { return (CR & XM_CRMASK_CR6) == 0; } + constexpr bool XMComparisonAllInBounds(uint32_t CR) noexcept { return (CR & XM_CRMASK_CR6BOUNDS) == XM_CRMASK_CR6BOUNDS; } + constexpr bool XMComparisonAnyOutOfBounds(uint32_t CR) noexcept { return (CR & XM_CRMASK_CR6BOUNDS) != XM_CRMASK_CR6BOUNDS; } + + + /**************************************************************************** + * + * Data types + * + ****************************************************************************/ + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4068 4201 4365 4324 4820) + // C4068: ignore unknown pragmas + // C4201: nonstandard extension used : nameless struct/union + // C4365: Off by default noise + // C4324/4820: padding warnings +#endif + +#ifdef _PREFAST_ +#pragma prefast(push) +#pragma prefast(disable : 25000, "FXMVECTOR is 16 bytes") +#endif + +//------------------------------------------------------------------------------ +#if defined(_XM_NO_INTRINSICS_) + struct __vector4 + { + union + { + float vector4_f32[4]; + uint32_t vector4_u32[4]; + }; + }; +#endif // _XM_NO_INTRINSICS_ + + //------------------------------------------------------------------------------ + // Vector intrinsic: Four 32 bit floating point components aligned on a 16 byte + // boundary and mapped to hardware vector registers +#if defined(_XM_SSE_INTRINSICS_) && !defined(_XM_NO_INTRINSICS_) + using XMVECTOR = __m128; +#elif defined(_XM_ARM_NEON_INTRINSICS_) && !defined(_XM_NO_INTRINSICS_) + using XMVECTOR = float32x4_t; +#else + using XMVECTOR = __vector4; +#endif + + // Fix-up for (1st-3rd) XMVECTOR parameters that are pass-in-register for x86, ARM, ARM64, and vector call; by reference otherwise +#if ( defined(_M_IX86) || defined(_M_ARM) || defined(_M_ARM64) || _XM_VECTORCALL_ || __i386__ || __arm__ || __aarch64__ ) && !defined(_XM_NO_INTRINSICS_) + typedef const XMVECTOR FXMVECTOR; +#else + typedef const XMVECTOR& FXMVECTOR; +#endif + + // Fix-up for (4th) XMVECTOR parameter to pass in-register for ARM, ARM64, and vector call; by reference otherwise +#if ( defined(_M_ARM) || defined(_M_ARM64) || defined(_M_HYBRID_X86_ARM64) || defined(_M_ARM64EC) || _XM_VECTORCALL_ || __arm__ || __aarch64__ ) && !defined(_XM_NO_INTRINSICS_) + typedef const XMVECTOR GXMVECTOR; +#else + typedef const XMVECTOR& GXMVECTOR; +#endif + + // Fix-up for (5th & 6th) XMVECTOR parameter to pass in-register for ARM64 and vector call; by reference otherwise +#if ( defined(_M_ARM64) || defined(_M_HYBRID_X86_ARM64) || defined(_M_ARM64EC) || _XM_VECTORCALL_ || __aarch64__ ) && !defined(_XM_NO_INTRINSICS_) + typedef const XMVECTOR HXMVECTOR; +#else + typedef const XMVECTOR& HXMVECTOR; +#endif + + // Fix-up for (7th+) XMVECTOR parameters to pass by reference + typedef const XMVECTOR& CXMVECTOR; + + //------------------------------------------------------------------------------ + // Conversion types for constants + XM_ALIGNED_STRUCT(16) XMVECTORF32 + { + union + { + float f[4]; + XMVECTOR v; + }; + + inline operator XMVECTOR() const noexcept { return v; } + inline operator const float* () const noexcept { return f; } + #ifdef _XM_NO_INTRINSICS_ + #elif defined(_XM_SSE_INTRINSICS_) + inline operator __m128i() const noexcept { return _mm_castps_si128(v); } + inline operator __m128d() const noexcept { return _mm_castps_pd(v); } + #elif defined(_XM_ARM_NEON_INTRINSICS_) && (defined(__GNUC__) || defined(_ARM64_DISTINCT_NEON_TYPES)) + inline operator int32x4_t() const noexcept { return vreinterpretq_s32_f32(v); } + inline operator uint32x4_t() const noexcept { return vreinterpretq_u32_f32(v); } + #endif + }; + + XM_ALIGNED_STRUCT(16) XMVECTORI32 + { + union + { + int32_t i[4]; + XMVECTOR v; + }; + + inline operator XMVECTOR() const noexcept { return v; } + #ifdef _XM_NO_INTRINSICS_ + #elif defined(_XM_SSE_INTRINSICS_) + inline operator __m128i() const noexcept { return _mm_castps_si128(v); } + inline operator __m128d() const noexcept { return _mm_castps_pd(v); } + #elif defined(_XM_ARM_NEON_INTRINSICS_) && (defined(__GNUC__) || defined(_ARM64_DISTINCT_NEON_TYPES)) + inline operator int32x4_t() const noexcept { return vreinterpretq_s32_f32(v); } + inline operator uint32x4_t() const noexcept { return vreinterpretq_u32_f32(v); } + #endif + }; + + XM_ALIGNED_STRUCT(16) XMVECTORU8 + { + union + { + uint8_t u[16]; + XMVECTOR v; + }; + + inline operator XMVECTOR() const noexcept { return v; } + #ifdef _XM_NO_INTRINSICS_ + #elif defined(_XM_SSE_INTRINSICS_) + inline operator __m128i() const noexcept { return _mm_castps_si128(v); } + inline operator __m128d() const noexcept { return _mm_castps_pd(v); } + #elif defined(_XM_ARM_NEON_INTRINSICS_) && (defined(__GNUC__) || defined(_ARM64_DISTINCT_NEON_TYPES)) + inline operator int32x4_t() const noexcept { return vreinterpretq_s32_f32(v); } + inline operator uint32x4_t() const noexcept { return vreinterpretq_u32_f32(v); } + #endif + }; + + XM_ALIGNED_STRUCT(16) XMVECTORU32 + { + union + { + uint32_t u[4]; + XMVECTOR v; + }; + + inline operator XMVECTOR() const noexcept { return v; } + #ifdef _XM_NO_INTRINSICS_ + #elif defined(_XM_SSE_INTRINSICS_) + inline operator __m128i() const noexcept { return _mm_castps_si128(v); } + inline operator __m128d() const noexcept { return _mm_castps_pd(v); } + #elif defined(_XM_ARM_NEON_INTRINSICS_) && (defined(__GNUC__) || defined(_ARM64_DISTINCT_NEON_TYPES)) + inline operator int32x4_t() const noexcept { return vreinterpretq_s32_f32(v); } + inline operator uint32x4_t() const noexcept { return vreinterpretq_u32_f32(v); } + #endif + }; + + //------------------------------------------------------------------------------ + // Vector operators + +#ifndef _XM_NO_XMVECTOR_OVERLOADS_ + XMVECTOR XM_CALLCONV operator+ (FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV operator- (FXMVECTOR V) noexcept; + + XMVECTOR& XM_CALLCONV operator+= (XMVECTOR& V1, FXMVECTOR V2) noexcept; + XMVECTOR& XM_CALLCONV operator-= (XMVECTOR& V1, FXMVECTOR V2) noexcept; + XMVECTOR& XM_CALLCONV operator*= (XMVECTOR& V1, FXMVECTOR V2) noexcept; + XMVECTOR& XM_CALLCONV operator/= (XMVECTOR& V1, FXMVECTOR V2) noexcept; + + XMVECTOR& operator*= (XMVECTOR& V, float S) noexcept; + XMVECTOR& operator/= (XMVECTOR& V, float S) noexcept; + + XMVECTOR XM_CALLCONV operator+ (FXMVECTOR V1, FXMVECTOR V2) noexcept; + XMVECTOR XM_CALLCONV operator- (FXMVECTOR V1, FXMVECTOR V2) noexcept; + XMVECTOR XM_CALLCONV operator* (FXMVECTOR V1, FXMVECTOR V2) noexcept; + XMVECTOR XM_CALLCONV operator/ (FXMVECTOR V1, FXMVECTOR V2) noexcept; + XMVECTOR XM_CALLCONV operator* (FXMVECTOR V, float S) noexcept; + XMVECTOR XM_CALLCONV operator* (float S, FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV operator/ (FXMVECTOR V, float S) noexcept; +#endif /* !_XM_NO_XMVECTOR_OVERLOADS_ */ + + //------------------------------------------------------------------------------ + // Matrix type: Sixteen 32 bit floating point components aligned on a + // 16 byte boundary and mapped to four hardware vector registers + + struct XMMATRIX; + + // Fix-up for (1st) XMMATRIX parameter to pass in-register for ARM64 and vector call; by reference otherwise +#if ( defined(_M_ARM64) || defined(_M_HYBRID_X86_ARM64) || defined(_M_ARM64EC) || _XM_VECTORCALL_ || __aarch64__ ) && !defined(_XM_NO_INTRINSICS_) + typedef const XMMATRIX FXMMATRIX; +#else + typedef const XMMATRIX& FXMMATRIX; +#endif + + // Fix-up for (2nd+) XMMATRIX parameters to pass by reference + typedef const XMMATRIX& CXMMATRIX; + +#ifdef _XM_NO_INTRINSICS_ + struct XMMATRIX + #else + XM_ALIGNED_STRUCT(16) XMMATRIX + #endif + { + #ifdef _XM_NO_INTRINSICS_ + union + { + XMVECTOR r[4]; + struct + { + float _11, _12, _13, _14; + float _21, _22, _23, _24; + float _31, _32, _33, _34; + float _41, _42, _43, _44; + }; + float m[4][4]; + }; + #else + XMVECTOR r[4]; + #endif + + XMMATRIX() = default; + + XMMATRIX(const XMMATRIX&) = default; + + #if defined(_MSC_VER) && (_MSC_FULL_VER < 191426431) + XMMATRIX& operator= (const XMMATRIX& M) noexcept { r[0] = M.r[0]; r[1] = M.r[1]; r[2] = M.r[2]; r[3] = M.r[3]; return *this; } + #else + XMMATRIX& operator=(const XMMATRIX&) = default; + + XMMATRIX(XMMATRIX&&) = default; + XMMATRIX& operator=(XMMATRIX&&) = default; + #endif + + constexpr XMMATRIX(FXMVECTOR R0, FXMVECTOR R1, FXMVECTOR R2, CXMVECTOR R3) noexcept : r{ R0,R1,R2,R3 } {} + XMMATRIX(float m00, float m01, float m02, float m03, + float m10, float m11, float m12, float m13, + float m20, float m21, float m22, float m23, + float m30, float m31, float m32, float m33) noexcept; + explicit XMMATRIX(_In_reads_(16) const float* pArray) noexcept; + + #ifdef _XM_NO_INTRINSICS_ + float operator() (size_t Row, size_t Column) const noexcept { return m[Row][Column]; } + float& operator() (size_t Row, size_t Column) noexcept { return m[Row][Column]; } + #endif + + XMMATRIX operator+ () const noexcept { return *this; } + XMMATRIX operator- () const noexcept; + + XMMATRIX& XM_CALLCONV operator+= (FXMMATRIX M) noexcept; + XMMATRIX& XM_CALLCONV operator-= (FXMMATRIX M) noexcept; + XMMATRIX& XM_CALLCONV operator*= (FXMMATRIX M) noexcept; + XMMATRIX& operator*= (float S) noexcept; + XMMATRIX& operator/= (float S) noexcept; + + XMMATRIX XM_CALLCONV operator+ (FXMMATRIX M) const noexcept; + XMMATRIX XM_CALLCONV operator- (FXMMATRIX M) const noexcept; + XMMATRIX XM_CALLCONV operator* (FXMMATRIX M) const noexcept; + XMMATRIX operator* (float S) const noexcept; + XMMATRIX operator/ (float S) const noexcept; + + friend XMMATRIX XM_CALLCONV operator* (float S, FXMMATRIX M) noexcept; + }; + + //------------------------------------------------------------------------------ + // 2D Vector; 32 bit floating point components + struct XMFLOAT2 + { + float x; + float y; + + XMFLOAT2() = default; + + XMFLOAT2(const XMFLOAT2&) = default; + XMFLOAT2& operator=(const XMFLOAT2&) = default; + + XMFLOAT2(XMFLOAT2&&) = default; + XMFLOAT2& operator=(XMFLOAT2&&) = default; + + constexpr XMFLOAT2(float _x, float _y) noexcept : x(_x), y(_y) {} + explicit XMFLOAT2(_In_reads_(2) const float* pArray) noexcept : x(pArray[0]), y(pArray[1]) {} + + #if (__cplusplus >= 202002L) + bool operator == (const XMFLOAT2&) const = default; + auto operator <=> (const XMFLOAT2&) const = default; + #endif + }; + + // 2D Vector; 32 bit floating point components aligned on a 16 byte boundary + XM_ALIGNED_STRUCT(16) XMFLOAT2A : public XMFLOAT2 + { + using XMFLOAT2::XMFLOAT2; + }; + + //------------------------------------------------------------------------------ + // 2D Vector; 32 bit signed integer components + struct XMINT2 + { + int32_t x; + int32_t y; + + XMINT2() = default; + + XMINT2(const XMINT2&) = default; + XMINT2& operator=(const XMINT2&) = default; + + XMINT2(XMINT2&&) = default; + XMINT2& operator=(XMINT2&&) = default; + + constexpr XMINT2(int32_t _x, int32_t _y) noexcept : x(_x), y(_y) {} + explicit XMINT2(_In_reads_(2) const int32_t* pArray) noexcept : x(pArray[0]), y(pArray[1]) {} + + #if (__cplusplus >= 202002L) + bool operator == (const XMINT2&) const = default; + auto operator <=> (const XMINT2&) const = default; + #endif + }; + + // 2D Vector; 32 bit unsigned integer components + struct XMUINT2 + { + uint32_t x; + uint32_t y; + + XMUINT2() = default; + + XMUINT2(const XMUINT2&) = default; + XMUINT2& operator=(const XMUINT2&) = default; + + XMUINT2(XMUINT2&&) = default; + XMUINT2& operator=(XMUINT2&&) = default; + + constexpr XMUINT2(uint32_t _x, uint32_t _y) noexcept : x(_x), y(_y) {} + explicit XMUINT2(_In_reads_(2) const uint32_t* pArray) noexcept : x(pArray[0]), y(pArray[1]) {} + + #if (__cplusplus >= 202002L) + bool operator == (const XMUINT2&) const = default; + auto operator <=> (const XMUINT2&) const = default; + #endif + }; + + //------------------------------------------------------------------------------ + // 3D Vector; 32 bit floating point components + struct XMFLOAT3 + { + float x; + float y; + float z; + + XMFLOAT3() = default; + + XMFLOAT3(const XMFLOAT3&) = default; + XMFLOAT3& operator=(const XMFLOAT3&) = default; + + XMFLOAT3(XMFLOAT3&&) = default; + XMFLOAT3& operator=(XMFLOAT3&&) = default; + + constexpr XMFLOAT3(float _x, float _y, float _z) noexcept : x(_x), y(_y), z(_z) {} + explicit XMFLOAT3(_In_reads_(3) const float* pArray) noexcept : x(pArray[0]), y(pArray[1]), z(pArray[2]) {} + + #if (__cplusplus >= 202002L) + bool operator == (const XMFLOAT3&) const = default; + auto operator <=> (const XMFLOAT3&) const = default; + #endif + }; + + // 3D Vector; 32 bit floating point components aligned on a 16 byte boundary + XM_ALIGNED_STRUCT(16) XMFLOAT3A : public XMFLOAT3 + { + using XMFLOAT3::XMFLOAT3; + }; + + //------------------------------------------------------------------------------ + // 3D Vector; 32 bit signed integer components + struct XMINT3 + { + int32_t x; + int32_t y; + int32_t z; + + XMINT3() = default; + + XMINT3(const XMINT3&) = default; + XMINT3& operator=(const XMINT3&) = default; + + XMINT3(XMINT3&&) = default; + XMINT3& operator=(XMINT3&&) = default; + + constexpr XMINT3(int32_t _x, int32_t _y, int32_t _z) noexcept : x(_x), y(_y), z(_z) {} + explicit XMINT3(_In_reads_(3) const int32_t* pArray) noexcept : x(pArray[0]), y(pArray[1]), z(pArray[2]) {} + + #if (__cplusplus >= 202002L) + bool operator == (const XMINT3&) const = default; + auto operator <=> (const XMINT3&) const = default; + #endif + }; + + // 3D Vector; 32 bit unsigned integer components + struct XMUINT3 + { + uint32_t x; + uint32_t y; + uint32_t z; + + XMUINT3() = default; + + XMUINT3(const XMUINT3&) = default; + XMUINT3& operator=(const XMUINT3&) = default; + + XMUINT3(XMUINT3&&) = default; + XMUINT3& operator=(XMUINT3&&) = default; + + constexpr XMUINT3(uint32_t _x, uint32_t _y, uint32_t _z) noexcept : x(_x), y(_y), z(_z) {} + explicit XMUINT3(_In_reads_(3) const uint32_t* pArray) noexcept : x(pArray[0]), y(pArray[1]), z(pArray[2]) {} + + #if (__cplusplus >= 202002L) + bool operator == (const XMUINT3&) const = default; + auto operator <=> (const XMUINT3&) const = default; + #endif + }; + + //------------------------------------------------------------------------------ + // 4D Vector; 32 bit floating point components + struct XMFLOAT4 + { + float x; + float y; + float z; + float w; + + XMFLOAT4() = default; + + XMFLOAT4(const XMFLOAT4&) = default; + XMFLOAT4& operator=(const XMFLOAT4&) = default; + + XMFLOAT4(XMFLOAT4&&) = default; + XMFLOAT4& operator=(XMFLOAT4&&) = default; + + constexpr XMFLOAT4(float _x, float _y, float _z, float _w) noexcept : x(_x), y(_y), z(_z), w(_w) {} + explicit XMFLOAT4(_In_reads_(4) const float* pArray) noexcept : x(pArray[0]), y(pArray[1]), z(pArray[2]), w(pArray[3]) {} + + #if (__cplusplus >= 202002L) + bool operator == (const XMFLOAT4&) const = default; + auto operator <=> (const XMFLOAT4&) const = default; + #endif + }; + + // 4D Vector; 32 bit floating point components aligned on a 16 byte boundary + XM_ALIGNED_STRUCT(16) XMFLOAT4A : public XMFLOAT4 + { + using XMFLOAT4::XMFLOAT4; + }; + + //------------------------------------------------------------------------------ + // 4D Vector; 32 bit signed integer components + struct XMINT4 + { + int32_t x; + int32_t y; + int32_t z; + int32_t w; + + XMINT4() = default; + + XMINT4(const XMINT4&) = default; + XMINT4& operator=(const XMINT4&) = default; + + XMINT4(XMINT4&&) = default; + XMINT4& operator=(XMINT4&&) = default; + + constexpr XMINT4(int32_t _x, int32_t _y, int32_t _z, int32_t _w) noexcept : x(_x), y(_y), z(_z), w(_w) {} + explicit XMINT4(_In_reads_(4) const int32_t* pArray) noexcept : x(pArray[0]), y(pArray[1]), z(pArray[2]), w(pArray[3]) {} + + #if (__cplusplus >= 202002L) + bool operator == (const XMINT4&) const = default; + auto operator <=> (const XMINT4&) const = default; + #endif + }; + + // 4D Vector; 32 bit unsigned integer components + struct XMUINT4 + { + uint32_t x; + uint32_t y; + uint32_t z; + uint32_t w; + + XMUINT4() = default; + + XMUINT4(const XMUINT4&) = default; + XMUINT4& operator=(const XMUINT4&) = default; + + XMUINT4(XMUINT4&&) = default; + XMUINT4& operator=(XMUINT4&&) = default; + + constexpr XMUINT4(uint32_t _x, uint32_t _y, uint32_t _z, uint32_t _w) noexcept : x(_x), y(_y), z(_z), w(_w) {} + explicit XMUINT4(_In_reads_(4) const uint32_t* pArray) noexcept : x(pArray[0]), y(pArray[1]), z(pArray[2]), w(pArray[3]) {} + + #if (__cplusplus >= 202002L) + bool operator == (const XMUINT4&) const = default; + auto operator <=> (const XMUINT4&) const = default; + #endif + }; + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wgnu-anonymous-struct" +#pragma clang diagnostic ignored "-Wnested-anon-types" +#pragma clang diagnostic ignored "-Wunknown-warning-option" +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" +#endif + + //------------------------------------------------------------------------------ + // 3x3 Matrix: 32 bit floating point components + struct XMFLOAT3X3 + { + union + { + struct + { + float _11, _12, _13; + float _21, _22, _23; + float _31, _32, _33; + }; + float m[3][3]; + }; + + XMFLOAT3X3() = default; + + XMFLOAT3X3(const XMFLOAT3X3&) = default; + XMFLOAT3X3& operator=(const XMFLOAT3X3&) = default; + + XMFLOAT3X3(XMFLOAT3X3&&) = default; + XMFLOAT3X3& operator=(XMFLOAT3X3&&) = default; + + constexpr XMFLOAT3X3(float m00, float m01, float m02, + float m10, float m11, float m12, + float m20, float m21, float m22) noexcept + : _11(m00), _12(m01), _13(m02), + _21(m10), _22(m11), _23(m12), + _31(m20), _32(m21), _33(m22) + {} + explicit XMFLOAT3X3(_In_reads_(9) const float* pArray) noexcept; + + float operator() (size_t Row, size_t Column) const noexcept { return m[Row][Column]; } + float& operator() (size_t Row, size_t Column) noexcept { return m[Row][Column]; } + + #if (__cplusplus >= 202002L) + bool operator == (const XMFLOAT3X3& M) const noexcept + { + return _11 == M._11 && _12 == M._12 && _13 == M._13 + && _21 == M._21 && _22 == M._22 && _23 == M._23 + && _31 == M._31 && _32 == M._32 && _33 == M._33; + } + + auto operator <=> (const XMFLOAT3X3& M) const noexcept + { + if (auto cmp = _11 <=> M._11; cmp != 0) return cmp; + if (auto cmp = _12 <=> M._12; cmp != 0) return cmp; + if (auto cmp = _13 <=> M._13; cmp != 0) return cmp; + if (auto cmp = _21 <=> M._21; cmp != 0) return cmp; + if (auto cmp = _22 <=> M._22; cmp != 0) return cmp; + if (auto cmp = _23 <=> M._23; cmp != 0) return cmp; + if (auto cmp = _31 <=> M._31; cmp != 0) return cmp; + if (auto cmp = _32 <=> M._32; cmp != 0) return cmp; + return _33 <=> M._33; + } + #endif + }; + + //------------------------------------------------------------------------------ + // 4x3 Row-major Matrix: 32 bit floating point components + struct XMFLOAT4X3 + { + union + { + struct + { + float _11, _12, _13; + float _21, _22, _23; + float _31, _32, _33; + float _41, _42, _43; + }; + float m[4][3]; + float f[12]; + }; + + XMFLOAT4X3() = default; + + XMFLOAT4X3(const XMFLOAT4X3&) = default; + XMFLOAT4X3& operator=(const XMFLOAT4X3&) = default; + + XMFLOAT4X3(XMFLOAT4X3&&) = default; + XMFLOAT4X3& operator=(XMFLOAT4X3&&) = default; + + constexpr XMFLOAT4X3(float m00, float m01, float m02, + float m10, float m11, float m12, + float m20, float m21, float m22, + float m30, float m31, float m32) noexcept + : _11(m00), _12(m01), _13(m02), + _21(m10), _22(m11), _23(m12), + _31(m20), _32(m21), _33(m22), + _41(m30), _42(m31), _43(m32) + {} + explicit XMFLOAT4X3(_In_reads_(12) const float* pArray) noexcept; + + float operator() (size_t Row, size_t Column) const noexcept { return m[Row][Column]; } + float& operator() (size_t Row, size_t Column) noexcept { return m[Row][Column]; } + + #if (__cplusplus >= 202002L) + bool operator == (const XMFLOAT4X3& M) const noexcept + { + return _11 == M._11 && _12 == M._12 && _13 == M._13 + && _21 == M._21 && _22 == M._22 && _23 == M._23 + && _31 == M._31 && _32 == M._32 && _33 == M._33 + && _41 == M._41 && _42 == M._42 && _43 == M._43; + } + + auto operator <=> (const XMFLOAT4X3& M) const noexcept + { + if (auto cmp = _11 <=> M._11; cmp != 0) return cmp; + if (auto cmp = _12 <=> M._12; cmp != 0) return cmp; + if (auto cmp = _13 <=> M._13; cmp != 0) return cmp; + if (auto cmp = _21 <=> M._21; cmp != 0) return cmp; + if (auto cmp = _22 <=> M._22; cmp != 0) return cmp; + if (auto cmp = _23 <=> M._23; cmp != 0) return cmp; + if (auto cmp = _31 <=> M._31; cmp != 0) return cmp; + if (auto cmp = _32 <=> M._32; cmp != 0) return cmp; + if (auto cmp = _33 <=> M._33; cmp != 0) return cmp; + if (auto cmp = _41 <=> M._41; cmp != 0) return cmp; + if (auto cmp = _42 <=> M._42; cmp != 0) return cmp; + return _43 <=> M._43; + } + #endif + }; + + // 4x3 Row-major Matrix: 32 bit floating point components aligned on a 16 byte boundary + XM_ALIGNED_STRUCT(16) XMFLOAT4X3A : public XMFLOAT4X3 + { + using XMFLOAT4X3::XMFLOAT4X3; + }; + + //------------------------------------------------------------------------------ + // 3x4 Column-major Matrix: 32 bit floating point components + struct XMFLOAT3X4 + { + union + { + struct + { + float _11, _12, _13, _14; + float _21, _22, _23, _24; + float _31, _32, _33, _34; + }; + float m[3][4]; + float f[12]; + }; + + XMFLOAT3X4() = default; + + XMFLOAT3X4(const XMFLOAT3X4&) = default; + XMFLOAT3X4& operator=(const XMFLOAT3X4&) = default; + + XMFLOAT3X4(XMFLOAT3X4&&) = default; + XMFLOAT3X4& operator=(XMFLOAT3X4&&) = default; + + constexpr XMFLOAT3X4(float m00, float m01, float m02, float m03, + float m10, float m11, float m12, float m13, + float m20, float m21, float m22, float m23) noexcept + : _11(m00), _12(m01), _13(m02), _14(m03), + _21(m10), _22(m11), _23(m12), _24(m13), + _31(m20), _32(m21), _33(m22), _34(m23) + {} + explicit XMFLOAT3X4(_In_reads_(12) const float* pArray) noexcept; + + float operator() (size_t Row, size_t Column) const noexcept { return m[Row][Column]; } + float& operator() (size_t Row, size_t Column) noexcept { return m[Row][Column]; } + + #if (__cplusplus >= 202002L) + bool operator == (const XMFLOAT3X4& M) const noexcept + { + return _11 == M._11 && _12 == M._12 && _13 == M._13 && _14 == M._14 + && _21 == M._21 && _22 == M._22 && _23 == M._23 && _24 == M._24 + && _31 == M._31 && _32 == M._32 && _33 == M._33 && _34 == M._34; + } + + auto operator <=> (const XMFLOAT3X4& M) const noexcept + { + if (auto cmp = _11 <=> M._11; cmp != 0) return cmp; + if (auto cmp = _12 <=> M._12; cmp != 0) return cmp; + if (auto cmp = _13 <=> M._13; cmp != 0) return cmp; + if (auto cmp = _14 <=> M._14; cmp != 0) return cmp; + if (auto cmp = _21 <=> M._21; cmp != 0) return cmp; + if (auto cmp = _22 <=> M._22; cmp != 0) return cmp; + if (auto cmp = _23 <=> M._23; cmp != 0) return cmp; + if (auto cmp = _24 <=> M._24; cmp != 0) return cmp; + if (auto cmp = _31 <=> M._31; cmp != 0) return cmp; + if (auto cmp = _32 <=> M._32; cmp != 0) return cmp; + if (auto cmp = _33 <=> M._33; cmp != 0) return cmp; + return _34 <=> M._34; + } + #endif + }; + + // 3x4 Column-major Matrix: 32 bit floating point components aligned on a 16 byte boundary + XM_ALIGNED_STRUCT(16) XMFLOAT3X4A : public XMFLOAT3X4 + { + using XMFLOAT3X4::XMFLOAT3X4; + }; + + //------------------------------------------------------------------------------ + // 4x4 Matrix: 32 bit floating point components + struct XMFLOAT4X4 + { + union + { + struct + { + float _11, _12, _13, _14; + float _21, _22, _23, _24; + float _31, _32, _33, _34; + float _41, _42, _43, _44; + }; + float m[4][4]; + }; + + XMFLOAT4X4() = default; + + XMFLOAT4X4(const XMFLOAT4X4&) = default; + XMFLOAT4X4& operator=(const XMFLOAT4X4&) = default; + + XMFLOAT4X4(XMFLOAT4X4&&) = default; + XMFLOAT4X4& operator=(XMFLOAT4X4&&) = default; + + constexpr XMFLOAT4X4(float m00, float m01, float m02, float m03, + float m10, float m11, float m12, float m13, + float m20, float m21, float m22, float m23, + float m30, float m31, float m32, float m33) noexcept + : _11(m00), _12(m01), _13(m02), _14(m03), + _21(m10), _22(m11), _23(m12), _24(m13), + _31(m20), _32(m21), _33(m22), _34(m23), + _41(m30), _42(m31), _43(m32), _44(m33) + {} + explicit XMFLOAT4X4(_In_reads_(16) const float* pArray) noexcept; + + float operator() (size_t Row, size_t Column) const noexcept { return m[Row][Column]; } + float& operator() (size_t Row, size_t Column) noexcept { return m[Row][Column]; } + + #if (__cplusplus >= 202002L) + bool operator == (const XMFLOAT4X4& M) const noexcept + { + return _11 == M._11 && _12 == M._12 && _13 == M._13 && _14 == M._14 + && _21 == M._21 && _22 == M._22 && _23 == M._23 && _24 == M._24 + && _31 == M._31 && _32 == M._32 && _33 == M._33 && _34 == M._34 + && _41 == M._41 && _42 == M._42 && _43 == M._43 && _44 == M._44; + } + + auto operator <=> (const XMFLOAT4X4& M) const noexcept + { + if (auto cmp = _11 <=> M._11; cmp != 0) return cmp; + if (auto cmp = _12 <=> M._12; cmp != 0) return cmp; + if (auto cmp = _13 <=> M._13; cmp != 0) return cmp; + if (auto cmp = _14 <=> M._14; cmp != 0) return cmp; + if (auto cmp = _21 <=> M._21; cmp != 0) return cmp; + if (auto cmp = _22 <=> M._22; cmp != 0) return cmp; + if (auto cmp = _23 <=> M._23; cmp != 0) return cmp; + if (auto cmp = _24 <=> M._24; cmp != 0) return cmp; + if (auto cmp = _31 <=> M._31; cmp != 0) return cmp; + if (auto cmp = _32 <=> M._32; cmp != 0) return cmp; + if (auto cmp = _33 <=> M._33; cmp != 0) return cmp; + if (auto cmp = _34 <=> M._34; cmp != 0) return cmp; + if (auto cmp = _41 <=> M._41; cmp != 0) return cmp; + if (auto cmp = _42 <=> M._42; cmp != 0) return cmp; + if (auto cmp = _43 <=> M._43; cmp != 0) return cmp; + return _44 <=> M._44; + } + #endif + }; + + // 4x4 Matrix: 32 bit floating point components aligned on a 16 byte boundary + XM_ALIGNED_STRUCT(16) XMFLOAT4X4A : public XMFLOAT4X4 + { + using XMFLOAT4X4::XMFLOAT4X4; + }; + + //////////////////////////////////////////////////////////////////////////////// + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +#ifdef _PREFAST_ +#pragma prefast(pop) +#endif +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +/**************************************************************************** + * + * Data conversion operations + * + ****************************************************************************/ + + XMVECTOR XM_CALLCONV XMConvertVectorIntToFloat(FXMVECTOR VInt, uint32_t DivExponent) noexcept; + XMVECTOR XM_CALLCONV XMConvertVectorFloatToInt(FXMVECTOR VFloat, uint32_t MulExponent) noexcept; + XMVECTOR XM_CALLCONV XMConvertVectorUIntToFloat(FXMVECTOR VUInt, uint32_t DivExponent) noexcept; + XMVECTOR XM_CALLCONV XMConvertVectorFloatToUInt(FXMVECTOR VFloat, uint32_t MulExponent) noexcept; + +#if defined(__XNAMATH_H__) && defined(XMVectorSetBinaryConstant) +#undef XMVectorSetBinaryConstant +#undef XMVectorSplatConstant +#undef XMVectorSplatConstantInt +#endif + + XMVECTOR XM_CALLCONV XMVectorSetBinaryConstant(uint32_t C0, uint32_t C1, uint32_t C2, uint32_t C3) noexcept; + XMVECTOR XM_CALLCONV XMVectorSplatConstant(int32_t IntConstant, uint32_t DivExponent) noexcept; + XMVECTOR XM_CALLCONV XMVectorSplatConstantInt(int32_t IntConstant) noexcept; + + /**************************************************************************** + * + * Load operations + * + ****************************************************************************/ + + XMVECTOR XM_CALLCONV XMLoadInt(_In_ const uint32_t* pSource) noexcept; + XMVECTOR XM_CALLCONV XMLoadFloat(_In_ const float* pSource) noexcept; + + XMVECTOR XM_CALLCONV XMLoadInt2(_In_reads_(2) const uint32_t* pSource) noexcept; + XMVECTOR XM_CALLCONV XMLoadInt2A(_In_reads_(2) const uint32_t* PSource) noexcept; + XMVECTOR XM_CALLCONV XMLoadFloat2(_In_ const XMFLOAT2* pSource) noexcept; + XMVECTOR XM_CALLCONV XMLoadFloat2A(_In_ const XMFLOAT2A* pSource) noexcept; + XMVECTOR XM_CALLCONV XMLoadSInt2(_In_ const XMINT2* pSource) noexcept; + XMVECTOR XM_CALLCONV XMLoadUInt2(_In_ const XMUINT2* pSource) noexcept; + + XMVECTOR XM_CALLCONV XMLoadInt3(_In_reads_(3) const uint32_t* pSource) noexcept; + XMVECTOR XM_CALLCONV XMLoadInt3A(_In_reads_(3) const uint32_t* pSource) noexcept; + XMVECTOR XM_CALLCONV XMLoadFloat3(_In_ const XMFLOAT3* pSource) noexcept; + XMVECTOR XM_CALLCONV XMLoadFloat3A(_In_ const XMFLOAT3A* pSource) noexcept; + XMVECTOR XM_CALLCONV XMLoadSInt3(_In_ const XMINT3* pSource) noexcept; + XMVECTOR XM_CALLCONV XMLoadUInt3(_In_ const XMUINT3* pSource) noexcept; + + XMVECTOR XM_CALLCONV XMLoadInt4(_In_reads_(4) const uint32_t* pSource) noexcept; + XMVECTOR XM_CALLCONV XMLoadInt4A(_In_reads_(4) const uint32_t* pSource) noexcept; + XMVECTOR XM_CALLCONV XMLoadFloat4(_In_ const XMFLOAT4* pSource) noexcept; + XMVECTOR XM_CALLCONV XMLoadFloat4A(_In_ const XMFLOAT4A* pSource) noexcept; + XMVECTOR XM_CALLCONV XMLoadSInt4(_In_ const XMINT4* pSource) noexcept; + XMVECTOR XM_CALLCONV XMLoadUInt4(_In_ const XMUINT4* pSource) noexcept; + + XMMATRIX XM_CALLCONV XMLoadFloat3x3(_In_ const XMFLOAT3X3* pSource) noexcept; + XMMATRIX XM_CALLCONV XMLoadFloat4x3(_In_ const XMFLOAT4X3* pSource) noexcept; + XMMATRIX XM_CALLCONV XMLoadFloat4x3A(_In_ const XMFLOAT4X3A* pSource) noexcept; + XMMATRIX XM_CALLCONV XMLoadFloat3x4(_In_ const XMFLOAT3X4* pSource) noexcept; + XMMATRIX XM_CALLCONV XMLoadFloat3x4A(_In_ const XMFLOAT3X4A* pSource) noexcept; + XMMATRIX XM_CALLCONV XMLoadFloat4x4(_In_ const XMFLOAT4X4* pSource) noexcept; + XMMATRIX XM_CALLCONV XMLoadFloat4x4A(_In_ const XMFLOAT4X4A* pSource) noexcept; + + /**************************************************************************** + * + * Store operations + * + ****************************************************************************/ + + void XM_CALLCONV XMStoreInt(_Out_ uint32_t* pDestination, _In_ FXMVECTOR V) noexcept; + void XM_CALLCONV XMStoreFloat(_Out_ float* pDestination, _In_ FXMVECTOR V) noexcept; + + void XM_CALLCONV XMStoreInt2(_Out_writes_(2) uint32_t* pDestination, _In_ FXMVECTOR V) noexcept; + void XM_CALLCONV XMStoreInt2A(_Out_writes_(2) uint32_t* pDestination, _In_ FXMVECTOR V) noexcept; + void XM_CALLCONV XMStoreFloat2(_Out_ XMFLOAT2* pDestination, _In_ FXMVECTOR V) noexcept; + void XM_CALLCONV XMStoreFloat2A(_Out_ XMFLOAT2A* pDestination, _In_ FXMVECTOR V) noexcept; + void XM_CALLCONV XMStoreSInt2(_Out_ XMINT2* pDestination, _In_ FXMVECTOR V) noexcept; + void XM_CALLCONV XMStoreUInt2(_Out_ XMUINT2* pDestination, _In_ FXMVECTOR V) noexcept; + + void XM_CALLCONV XMStoreInt3(_Out_writes_(3) uint32_t* pDestination, _In_ FXMVECTOR V) noexcept; + void XM_CALLCONV XMStoreInt3A(_Out_writes_(3) uint32_t* pDestination, _In_ FXMVECTOR V) noexcept; + void XM_CALLCONV XMStoreFloat3(_Out_ XMFLOAT3* pDestination, _In_ FXMVECTOR V) noexcept; + void XM_CALLCONV XMStoreFloat3A(_Out_ XMFLOAT3A* pDestination, _In_ FXMVECTOR V) noexcept; + void XM_CALLCONV XMStoreSInt3(_Out_ XMINT3* pDestination, _In_ FXMVECTOR V) noexcept; + void XM_CALLCONV XMStoreUInt3(_Out_ XMUINT3* pDestination, _In_ FXMVECTOR V) noexcept; + + void XM_CALLCONV XMStoreInt4(_Out_writes_(4) uint32_t* pDestination, _In_ FXMVECTOR V) noexcept; + void XM_CALLCONV XMStoreInt4A(_Out_writes_(4) uint32_t* pDestination, _In_ FXMVECTOR V) noexcept; + void XM_CALLCONV XMStoreFloat4(_Out_ XMFLOAT4* pDestination, _In_ FXMVECTOR V) noexcept; + void XM_CALLCONV XMStoreFloat4A(_Out_ XMFLOAT4A* pDestination, _In_ FXMVECTOR V) noexcept; + void XM_CALLCONV XMStoreSInt4(_Out_ XMINT4* pDestination, _In_ FXMVECTOR V) noexcept; + void XM_CALLCONV XMStoreUInt4(_Out_ XMUINT4* pDestination, _In_ FXMVECTOR V) noexcept; + + void XM_CALLCONV XMStoreFloat3x3(_Out_ XMFLOAT3X3* pDestination, _In_ FXMMATRIX M) noexcept; + void XM_CALLCONV XMStoreFloat4x3(_Out_ XMFLOAT4X3* pDestination, _In_ FXMMATRIX M) noexcept; + void XM_CALLCONV XMStoreFloat4x3A(_Out_ XMFLOAT4X3A* pDestination, _In_ FXMMATRIX M) noexcept; + void XM_CALLCONV XMStoreFloat3x4(_Out_ XMFLOAT3X4* pDestination, _In_ FXMMATRIX M) noexcept; + void XM_CALLCONV XMStoreFloat3x4A(_Out_ XMFLOAT3X4A* pDestination, _In_ FXMMATRIX M) noexcept; + void XM_CALLCONV XMStoreFloat4x4(_Out_ XMFLOAT4X4* pDestination, _In_ FXMMATRIX M) noexcept; + void XM_CALLCONV XMStoreFloat4x4A(_Out_ XMFLOAT4X4A* pDestination, _In_ FXMMATRIX M) noexcept; + + /**************************************************************************** + * + * General vector operations + * + ****************************************************************************/ + + XMVECTOR XM_CALLCONV XMVectorZero() noexcept; + XMVECTOR XM_CALLCONV XMVectorSet(float x, float y, float z, float w) noexcept; + XMVECTOR XM_CALLCONV XMVectorSetInt(uint32_t x, uint32_t y, uint32_t z, uint32_t w) noexcept; + XMVECTOR XM_CALLCONV XMVectorReplicate(float Value) noexcept; + XMVECTOR XM_CALLCONV XMVectorReplicatePtr(_In_ const float* pValue) noexcept; + XMVECTOR XM_CALLCONV XMVectorReplicateInt(uint32_t Value) noexcept; + XMVECTOR XM_CALLCONV XMVectorReplicateIntPtr(_In_ const uint32_t* pValue) noexcept; + XMVECTOR XM_CALLCONV XMVectorTrueInt() noexcept; + XMVECTOR XM_CALLCONV XMVectorFalseInt() noexcept; + XMVECTOR XM_CALLCONV XMVectorSplatX(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVectorSplatY(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVectorSplatZ(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVectorSplatW(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVectorSplatOne() noexcept; + XMVECTOR XM_CALLCONV XMVectorSplatInfinity() noexcept; + XMVECTOR XM_CALLCONV XMVectorSplatQNaN() noexcept; + XMVECTOR XM_CALLCONV XMVectorSplatEpsilon() noexcept; + XMVECTOR XM_CALLCONV XMVectorSplatSignMask() noexcept; + + float XM_CALLCONV XMVectorGetByIndex(FXMVECTOR V, size_t i) noexcept; + float XM_CALLCONV XMVectorGetX(FXMVECTOR V) noexcept; + float XM_CALLCONV XMVectorGetY(FXMVECTOR V) noexcept; + float XM_CALLCONV XMVectorGetZ(FXMVECTOR V) noexcept; + float XM_CALLCONV XMVectorGetW(FXMVECTOR V) noexcept; + + void XM_CALLCONV XMVectorGetByIndexPtr(_Out_ float* f, _In_ FXMVECTOR V, _In_ size_t i) noexcept; + void XM_CALLCONV XMVectorGetXPtr(_Out_ float* x, _In_ FXMVECTOR V) noexcept; + void XM_CALLCONV XMVectorGetYPtr(_Out_ float* y, _In_ FXMVECTOR V) noexcept; + void XM_CALLCONV XMVectorGetZPtr(_Out_ float* z, _In_ FXMVECTOR V) noexcept; + void XM_CALLCONV XMVectorGetWPtr(_Out_ float* w, _In_ FXMVECTOR V) noexcept; + + uint32_t XM_CALLCONV XMVectorGetIntByIndex(FXMVECTOR V, size_t i) noexcept; + uint32_t XM_CALLCONV XMVectorGetIntX(FXMVECTOR V) noexcept; + uint32_t XM_CALLCONV XMVectorGetIntY(FXMVECTOR V) noexcept; + uint32_t XM_CALLCONV XMVectorGetIntZ(FXMVECTOR V) noexcept; + uint32_t XM_CALLCONV XMVectorGetIntW(FXMVECTOR V) noexcept; + + void XM_CALLCONV XMVectorGetIntByIndexPtr(_Out_ uint32_t* x, _In_ FXMVECTOR V, _In_ size_t i) noexcept; + void XM_CALLCONV XMVectorGetIntXPtr(_Out_ uint32_t* x, _In_ FXMVECTOR V) noexcept; + void XM_CALLCONV XMVectorGetIntYPtr(_Out_ uint32_t* y, _In_ FXMVECTOR V) noexcept; + void XM_CALLCONV XMVectorGetIntZPtr(_Out_ uint32_t* z, _In_ FXMVECTOR V) noexcept; + void XM_CALLCONV XMVectorGetIntWPtr(_Out_ uint32_t* w, _In_ FXMVECTOR V) noexcept; + + XMVECTOR XM_CALLCONV XMVectorSetByIndex(FXMVECTOR V, float f, size_t i) noexcept; + XMVECTOR XM_CALLCONV XMVectorSetX(FXMVECTOR V, float x) noexcept; + XMVECTOR XM_CALLCONV XMVectorSetY(FXMVECTOR V, float y) noexcept; + XMVECTOR XM_CALLCONV XMVectorSetZ(FXMVECTOR V, float z) noexcept; + XMVECTOR XM_CALLCONV XMVectorSetW(FXMVECTOR V, float w) noexcept; + + XMVECTOR XM_CALLCONV XMVectorSetByIndexPtr(_In_ FXMVECTOR V, _In_ const float* f, _In_ size_t i) noexcept; + XMVECTOR XM_CALLCONV XMVectorSetXPtr(_In_ FXMVECTOR V, _In_ const float* x) noexcept; + XMVECTOR XM_CALLCONV XMVectorSetYPtr(_In_ FXMVECTOR V, _In_ const float* y) noexcept; + XMVECTOR XM_CALLCONV XMVectorSetZPtr(_In_ FXMVECTOR V, _In_ const float* z) noexcept; + XMVECTOR XM_CALLCONV XMVectorSetWPtr(_In_ FXMVECTOR V, _In_ const float* w) noexcept; + + XMVECTOR XM_CALLCONV XMVectorSetIntByIndex(FXMVECTOR V, uint32_t x, size_t i) noexcept; + XMVECTOR XM_CALLCONV XMVectorSetIntX(FXMVECTOR V, uint32_t x) noexcept; + XMVECTOR XM_CALLCONV XMVectorSetIntY(FXMVECTOR V, uint32_t y) noexcept; + XMVECTOR XM_CALLCONV XMVectorSetIntZ(FXMVECTOR V, uint32_t z) noexcept; + XMVECTOR XM_CALLCONV XMVectorSetIntW(FXMVECTOR V, uint32_t w) noexcept; + + XMVECTOR XM_CALLCONV XMVectorSetIntByIndexPtr(_In_ FXMVECTOR V, _In_ const uint32_t* x, _In_ size_t i) noexcept; + XMVECTOR XM_CALLCONV XMVectorSetIntXPtr(_In_ FXMVECTOR V, _In_ const uint32_t* x) noexcept; + XMVECTOR XM_CALLCONV XMVectorSetIntYPtr(_In_ FXMVECTOR V, _In_ const uint32_t* y) noexcept; + XMVECTOR XM_CALLCONV XMVectorSetIntZPtr(_In_ FXMVECTOR V, _In_ const uint32_t* z) noexcept; + XMVECTOR XM_CALLCONV XMVectorSetIntWPtr(_In_ FXMVECTOR V, _In_ const uint32_t* w) noexcept; + +#if defined(__XNAMATH_H__) && defined(XMVectorSwizzle) +#undef XMVectorSwizzle +#endif + + XMVECTOR XM_CALLCONV XMVectorSwizzle(FXMVECTOR V, uint32_t E0, uint32_t E1, uint32_t E2, uint32_t E3) noexcept; + XMVECTOR XM_CALLCONV XMVectorPermute(FXMVECTOR V1, FXMVECTOR V2, uint32_t PermuteX, uint32_t PermuteY, uint32_t PermuteZ, uint32_t PermuteW) noexcept; + XMVECTOR XM_CALLCONV XMVectorSelectControl(uint32_t VectorIndex0, uint32_t VectorIndex1, uint32_t VectorIndex2, uint32_t VectorIndex3) noexcept; + XMVECTOR XM_CALLCONV XMVectorSelect(FXMVECTOR V1, FXMVECTOR V2, FXMVECTOR Control) noexcept; + XMVECTOR XM_CALLCONV XMVectorMergeXY(FXMVECTOR V1, FXMVECTOR V2) noexcept; + XMVECTOR XM_CALLCONV XMVectorMergeZW(FXMVECTOR V1, FXMVECTOR V2) noexcept; + +#if defined(__XNAMATH_H__) && defined(XMVectorShiftLeft) +#undef XMVectorShiftLeft +#undef XMVectorRotateLeft +#undef XMVectorRotateRight +#undef XMVectorInsert +#endif + + XMVECTOR XM_CALLCONV XMVectorShiftLeft(FXMVECTOR V1, FXMVECTOR V2, uint32_t Elements) noexcept; + XMVECTOR XM_CALLCONV XMVectorRotateLeft(FXMVECTOR V, uint32_t Elements) noexcept; + XMVECTOR XM_CALLCONV XMVectorRotateRight(FXMVECTOR V, uint32_t Elements) noexcept; + XMVECTOR XM_CALLCONV XMVectorInsert(FXMVECTOR VD, FXMVECTOR VS, uint32_t VSLeftRotateElements, + uint32_t Select0, uint32_t Select1, uint32_t Select2, uint32_t Select3) noexcept; + + XMVECTOR XM_CALLCONV XMVectorEqual(FXMVECTOR V1, FXMVECTOR V2) noexcept; + XMVECTOR XM_CALLCONV XMVectorEqualR(_Out_ uint32_t* pCR, _In_ FXMVECTOR V1, _In_ FXMVECTOR V2) noexcept; + XMVECTOR XM_CALLCONV XMVectorEqualInt(FXMVECTOR V1, FXMVECTOR V2) noexcept; + XMVECTOR XM_CALLCONV XMVectorEqualIntR(_Out_ uint32_t* pCR, _In_ FXMVECTOR V, _In_ FXMVECTOR V2) noexcept; + XMVECTOR XM_CALLCONV XMVectorNearEqual(FXMVECTOR V1, FXMVECTOR V2, FXMVECTOR Epsilon) noexcept; + XMVECTOR XM_CALLCONV XMVectorNotEqual(FXMVECTOR V1, FXMVECTOR V2) noexcept; + XMVECTOR XM_CALLCONV XMVectorNotEqualInt(FXMVECTOR V1, FXMVECTOR V2) noexcept; + XMVECTOR XM_CALLCONV XMVectorGreater(FXMVECTOR V1, FXMVECTOR V2) noexcept; + XMVECTOR XM_CALLCONV XMVectorGreaterR(_Out_ uint32_t* pCR, _In_ FXMVECTOR V1, _In_ FXMVECTOR V2) noexcept; + XMVECTOR XM_CALLCONV XMVectorGreaterOrEqual(FXMVECTOR V1, FXMVECTOR V2) noexcept; + XMVECTOR XM_CALLCONV XMVectorGreaterOrEqualR(_Out_ uint32_t* pCR, _In_ FXMVECTOR V1, _In_ FXMVECTOR V2) noexcept; + XMVECTOR XM_CALLCONV XMVectorLess(FXMVECTOR V1, FXMVECTOR V2) noexcept; + XMVECTOR XM_CALLCONV XMVectorLessOrEqual(FXMVECTOR V1, FXMVECTOR V2) noexcept; + XMVECTOR XM_CALLCONV XMVectorInBounds(FXMVECTOR V, FXMVECTOR Bounds) noexcept; + XMVECTOR XM_CALLCONV XMVectorInBoundsR(_Out_ uint32_t* pCR, _In_ FXMVECTOR V, _In_ FXMVECTOR Bounds) noexcept; + + XMVECTOR XM_CALLCONV XMVectorIsNaN(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVectorIsInfinite(FXMVECTOR V) noexcept; + + XMVECTOR XM_CALLCONV XMVectorMin(FXMVECTOR V1, FXMVECTOR V2) noexcept; + XMVECTOR XM_CALLCONV XMVectorMax(FXMVECTOR V1, FXMVECTOR V2) noexcept; + XMVECTOR XM_CALLCONV XMVectorRound(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVectorTruncate(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVectorFloor(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVectorCeiling(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVectorClamp(FXMVECTOR V, FXMVECTOR Min, FXMVECTOR Max) noexcept; + XMVECTOR XM_CALLCONV XMVectorSaturate(FXMVECTOR V) noexcept; + + XMVECTOR XM_CALLCONV XMVectorAndInt(FXMVECTOR V1, FXMVECTOR V2) noexcept; + XMVECTOR XM_CALLCONV XMVectorAndCInt(FXMVECTOR V1, FXMVECTOR V2) noexcept; + XMVECTOR XM_CALLCONV XMVectorOrInt(FXMVECTOR V1, FXMVECTOR V2) noexcept; + XMVECTOR XM_CALLCONV XMVectorNorInt(FXMVECTOR V1, FXMVECTOR V2) noexcept; + XMVECTOR XM_CALLCONV XMVectorXorInt(FXMVECTOR V1, FXMVECTOR V2) noexcept; + + XMVECTOR XM_CALLCONV XMVectorNegate(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVectorAdd(FXMVECTOR V1, FXMVECTOR V2) noexcept; + XMVECTOR XM_CALLCONV XMVectorSum(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVectorAddAngles(FXMVECTOR V1, FXMVECTOR V2) noexcept; + XMVECTOR XM_CALLCONV XMVectorSubtract(FXMVECTOR V1, FXMVECTOR V2) noexcept; + XMVECTOR XM_CALLCONV XMVectorSubtractAngles(FXMVECTOR V1, FXMVECTOR V2) noexcept; + XMVECTOR XM_CALLCONV XMVectorMultiply(FXMVECTOR V1, FXMVECTOR V2) noexcept; + XMVECTOR XM_CALLCONV XMVectorMultiplyAdd(FXMVECTOR V1, FXMVECTOR V2, FXMVECTOR V3) noexcept; + XMVECTOR XM_CALLCONV XMVectorDivide(FXMVECTOR V1, FXMVECTOR V2) noexcept; + XMVECTOR XM_CALLCONV XMVectorNegativeMultiplySubtract(FXMVECTOR V1, FXMVECTOR V2, FXMVECTOR V3) noexcept; + XMVECTOR XM_CALLCONV XMVectorScale(FXMVECTOR V, float ScaleFactor) noexcept; + XMVECTOR XM_CALLCONV XMVectorReciprocalEst(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVectorReciprocal(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVectorSqrtEst(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVectorSqrt(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVectorReciprocalSqrtEst(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVectorReciprocalSqrt(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVectorExp2(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVectorExp10(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVectorExpE(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVectorExp(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVectorLog2(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVectorLog10(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVectorLogE(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVectorLog(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVectorPow(FXMVECTOR V1, FXMVECTOR V2) noexcept; + XMVECTOR XM_CALLCONV XMVectorAbs(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVectorMod(FXMVECTOR V1, FXMVECTOR V2) noexcept; + XMVECTOR XM_CALLCONV XMVectorModAngles(FXMVECTOR Angles) noexcept; + XMVECTOR XM_CALLCONV XMVectorSin(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVectorSinEst(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVectorCos(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVectorCosEst(FXMVECTOR V) noexcept; + void XM_CALLCONV XMVectorSinCos(_Out_ XMVECTOR* pSin, _Out_ XMVECTOR* pCos, _In_ FXMVECTOR V) noexcept; + void XM_CALLCONV XMVectorSinCosEst(_Out_ XMVECTOR* pSin, _Out_ XMVECTOR* pCos, _In_ FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVectorTan(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVectorTanEst(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVectorSinH(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVectorCosH(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVectorTanH(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVectorASin(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVectorASinEst(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVectorACos(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVectorACosEst(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVectorATan(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVectorATanEst(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVectorATan2(FXMVECTOR Y, FXMVECTOR X) noexcept; + XMVECTOR XM_CALLCONV XMVectorATan2Est(FXMVECTOR Y, FXMVECTOR X) noexcept; + XMVECTOR XM_CALLCONV XMVectorLerp(FXMVECTOR V0, FXMVECTOR V1, float t) noexcept; + XMVECTOR XM_CALLCONV XMVectorLerpV(FXMVECTOR V0, FXMVECTOR V1, FXMVECTOR T) noexcept; + XMVECTOR XM_CALLCONV XMVectorHermite(FXMVECTOR Position0, FXMVECTOR Tangent0, FXMVECTOR Position1, GXMVECTOR Tangent1, float t) noexcept; + XMVECTOR XM_CALLCONV XMVectorHermiteV(FXMVECTOR Position0, FXMVECTOR Tangent0, FXMVECTOR Position1, GXMVECTOR Tangent1, HXMVECTOR T) noexcept; + XMVECTOR XM_CALLCONV XMVectorCatmullRom(FXMVECTOR Position0, FXMVECTOR Position1, FXMVECTOR Position2, GXMVECTOR Position3, float t) noexcept; + XMVECTOR XM_CALLCONV XMVectorCatmullRomV(FXMVECTOR Position0, FXMVECTOR Position1, FXMVECTOR Position2, GXMVECTOR Position3, HXMVECTOR T) noexcept; + XMVECTOR XM_CALLCONV XMVectorBaryCentric(FXMVECTOR Position0, FXMVECTOR Position1, FXMVECTOR Position2, float f, float g) noexcept; + XMVECTOR XM_CALLCONV XMVectorBaryCentricV(FXMVECTOR Position0, FXMVECTOR Position1, FXMVECTOR Position2, GXMVECTOR F, HXMVECTOR G) noexcept; + + /**************************************************************************** + * + * 2D vector operations + * + ****************************************************************************/ + + bool XM_CALLCONV XMVector2Equal(FXMVECTOR V1, FXMVECTOR V2) noexcept; + uint32_t XM_CALLCONV XMVector2EqualR(FXMVECTOR V1, FXMVECTOR V2) noexcept; + bool XM_CALLCONV XMVector2EqualInt(FXMVECTOR V1, FXMVECTOR V2) noexcept; + uint32_t XM_CALLCONV XMVector2EqualIntR(FXMVECTOR V1, FXMVECTOR V2) noexcept; + bool XM_CALLCONV XMVector2NearEqual(FXMVECTOR V1, FXMVECTOR V2, FXMVECTOR Epsilon) noexcept; + bool XM_CALLCONV XMVector2NotEqual(FXMVECTOR V1, FXMVECTOR V2) noexcept; + bool XM_CALLCONV XMVector2NotEqualInt(FXMVECTOR V1, FXMVECTOR V2) noexcept; + bool XM_CALLCONV XMVector2Greater(FXMVECTOR V1, FXMVECTOR V2) noexcept; + uint32_t XM_CALLCONV XMVector2GreaterR(FXMVECTOR V1, FXMVECTOR V2) noexcept; + bool XM_CALLCONV XMVector2GreaterOrEqual(FXMVECTOR V1, FXMVECTOR V2) noexcept; + uint32_t XM_CALLCONV XMVector2GreaterOrEqualR(FXMVECTOR V1, FXMVECTOR V2) noexcept; + bool XM_CALLCONV XMVector2Less(FXMVECTOR V1, FXMVECTOR V2) noexcept; + bool XM_CALLCONV XMVector2LessOrEqual(FXMVECTOR V1, FXMVECTOR V2) noexcept; + bool XM_CALLCONV XMVector2InBounds(FXMVECTOR V, FXMVECTOR Bounds) noexcept; + + bool XM_CALLCONV XMVector2IsNaN(FXMVECTOR V) noexcept; + bool XM_CALLCONV XMVector2IsInfinite(FXMVECTOR V) noexcept; + + XMVECTOR XM_CALLCONV XMVector2Dot(FXMVECTOR V1, FXMVECTOR V2) noexcept; + XMVECTOR XM_CALLCONV XMVector2Cross(FXMVECTOR V1, FXMVECTOR V2) noexcept; + XMVECTOR XM_CALLCONV XMVector2LengthSq(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVector2ReciprocalLengthEst(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVector2ReciprocalLength(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVector2LengthEst(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVector2Length(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVector2NormalizeEst(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVector2Normalize(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVector2ClampLength(FXMVECTOR V, float LengthMin, float LengthMax) noexcept; + XMVECTOR XM_CALLCONV XMVector2ClampLengthV(FXMVECTOR V, FXMVECTOR LengthMin, FXMVECTOR LengthMax) noexcept; + XMVECTOR XM_CALLCONV XMVector2Reflect(FXMVECTOR Incident, FXMVECTOR Normal) noexcept; + XMVECTOR XM_CALLCONV XMVector2Refract(FXMVECTOR Incident, FXMVECTOR Normal, float RefractionIndex) noexcept; + XMVECTOR XM_CALLCONV XMVector2RefractV(FXMVECTOR Incident, FXMVECTOR Normal, FXMVECTOR RefractionIndex) noexcept; + XMVECTOR XM_CALLCONV XMVector2Orthogonal(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVector2AngleBetweenNormalsEst(FXMVECTOR N1, FXMVECTOR N2) noexcept; + XMVECTOR XM_CALLCONV XMVector2AngleBetweenNormals(FXMVECTOR N1, FXMVECTOR N2) noexcept; + XMVECTOR XM_CALLCONV XMVector2AngleBetweenVectors(FXMVECTOR V1, FXMVECTOR V2) noexcept; + XMVECTOR XM_CALLCONV XMVector2LinePointDistance(FXMVECTOR LinePoint1, FXMVECTOR LinePoint2, FXMVECTOR Point) noexcept; + XMVECTOR XM_CALLCONV XMVector2IntersectLine(FXMVECTOR Line1Point1, FXMVECTOR Line1Point2, FXMVECTOR Line2Point1, GXMVECTOR Line2Point2) noexcept; + XMVECTOR XM_CALLCONV XMVector2Transform(FXMVECTOR V, FXMMATRIX M) noexcept; + XMFLOAT4* XM_CALLCONV XMVector2TransformStream(_Out_writes_bytes_(sizeof(XMFLOAT4) + OutputStride * (VectorCount - 1)) XMFLOAT4* pOutputStream, + _In_ size_t OutputStride, + _In_reads_bytes_(sizeof(XMFLOAT2) + InputStride * (VectorCount - 1)) const XMFLOAT2* pInputStream, + _In_ size_t InputStride, _In_ size_t VectorCount, _In_ FXMMATRIX M) noexcept; + XMVECTOR XM_CALLCONV XMVector2TransformCoord(FXMVECTOR V, FXMMATRIX M) noexcept; + XMFLOAT2* XM_CALLCONV XMVector2TransformCoordStream(_Out_writes_bytes_(sizeof(XMFLOAT2) + OutputStride * (VectorCount - 1)) XMFLOAT2* pOutputStream, + _In_ size_t OutputStride, + _In_reads_bytes_(sizeof(XMFLOAT2) + InputStride * (VectorCount - 1)) const XMFLOAT2* pInputStream, + _In_ size_t InputStride, _In_ size_t VectorCount, _In_ FXMMATRIX M) noexcept; + XMVECTOR XM_CALLCONV XMVector2TransformNormal(FXMVECTOR V, FXMMATRIX M) noexcept; + XMFLOAT2* XM_CALLCONV XMVector2TransformNormalStream(_Out_writes_bytes_(sizeof(XMFLOAT2) + OutputStride * (VectorCount - 1)) XMFLOAT2* pOutputStream, + _In_ size_t OutputStride, + _In_reads_bytes_(sizeof(XMFLOAT2) + InputStride * (VectorCount - 1)) const XMFLOAT2* pInputStream, + _In_ size_t InputStride, _In_ size_t VectorCount, _In_ FXMMATRIX M) noexcept; + + /**************************************************************************** + * + * 3D vector operations + * + ****************************************************************************/ + + bool XM_CALLCONV XMVector3Equal(FXMVECTOR V1, FXMVECTOR V2) noexcept; + uint32_t XM_CALLCONV XMVector3EqualR(FXMVECTOR V1, FXMVECTOR V2) noexcept; + bool XM_CALLCONV XMVector3EqualInt(FXMVECTOR V1, FXMVECTOR V2) noexcept; + uint32_t XM_CALLCONV XMVector3EqualIntR(FXMVECTOR V1, FXMVECTOR V2) noexcept; + bool XM_CALLCONV XMVector3NearEqual(FXMVECTOR V1, FXMVECTOR V2, FXMVECTOR Epsilon) noexcept; + bool XM_CALLCONV XMVector3NotEqual(FXMVECTOR V1, FXMVECTOR V2) noexcept; + bool XM_CALLCONV XMVector3NotEqualInt(FXMVECTOR V1, FXMVECTOR V2) noexcept; + bool XM_CALLCONV XMVector3Greater(FXMVECTOR V1, FXMVECTOR V2) noexcept; + uint32_t XM_CALLCONV XMVector3GreaterR(FXMVECTOR V1, FXMVECTOR V2) noexcept; + bool XM_CALLCONV XMVector3GreaterOrEqual(FXMVECTOR V1, FXMVECTOR V2) noexcept; + uint32_t XM_CALLCONV XMVector3GreaterOrEqualR(FXMVECTOR V1, FXMVECTOR V2) noexcept; + bool XM_CALLCONV XMVector3Less(FXMVECTOR V1, FXMVECTOR V2) noexcept; + bool XM_CALLCONV XMVector3LessOrEqual(FXMVECTOR V1, FXMVECTOR V2) noexcept; + bool XM_CALLCONV XMVector3InBounds(FXMVECTOR V, FXMVECTOR Bounds) noexcept; + + bool XM_CALLCONV XMVector3IsNaN(FXMVECTOR V) noexcept; + bool XM_CALLCONV XMVector3IsInfinite(FXMVECTOR V) noexcept; + + XMVECTOR XM_CALLCONV XMVector3Dot(FXMVECTOR V1, FXMVECTOR V2) noexcept; + XMVECTOR XM_CALLCONV XMVector3Cross(FXMVECTOR V1, FXMVECTOR V2) noexcept; + XMVECTOR XM_CALLCONV XMVector3LengthSq(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVector3ReciprocalLengthEst(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVector3ReciprocalLength(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVector3LengthEst(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVector3Length(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVector3NormalizeEst(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVector3Normalize(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVector3ClampLength(FXMVECTOR V, float LengthMin, float LengthMax) noexcept; + XMVECTOR XM_CALLCONV XMVector3ClampLengthV(FXMVECTOR V, FXMVECTOR LengthMin, FXMVECTOR LengthMax) noexcept; + XMVECTOR XM_CALLCONV XMVector3Reflect(FXMVECTOR Incident, FXMVECTOR Normal) noexcept; + XMVECTOR XM_CALLCONV XMVector3Refract(FXMVECTOR Incident, FXMVECTOR Normal, float RefractionIndex) noexcept; + XMVECTOR XM_CALLCONV XMVector3RefractV(FXMVECTOR Incident, FXMVECTOR Normal, FXMVECTOR RefractionIndex) noexcept; + XMVECTOR XM_CALLCONV XMVector3Orthogonal(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVector3AngleBetweenNormalsEst(FXMVECTOR N1, FXMVECTOR N2) noexcept; + XMVECTOR XM_CALLCONV XMVector3AngleBetweenNormals(FXMVECTOR N1, FXMVECTOR N2) noexcept; + XMVECTOR XM_CALLCONV XMVector3AngleBetweenVectors(FXMVECTOR V1, FXMVECTOR V2) noexcept; + XMVECTOR XM_CALLCONV XMVector3LinePointDistance(FXMVECTOR LinePoint1, FXMVECTOR LinePoint2, FXMVECTOR Point) noexcept; + void XM_CALLCONV XMVector3ComponentsFromNormal(_Out_ XMVECTOR* pParallel, _Out_ XMVECTOR* pPerpendicular, _In_ FXMVECTOR V, _In_ FXMVECTOR Normal) noexcept; + XMVECTOR XM_CALLCONV XMVector3Rotate(FXMVECTOR V, FXMVECTOR RotationQuaternion) noexcept; + XMVECTOR XM_CALLCONV XMVector3InverseRotate(FXMVECTOR V, FXMVECTOR RotationQuaternion) noexcept; + XMVECTOR XM_CALLCONV XMVector3Transform(FXMVECTOR V, FXMMATRIX M) noexcept; + XMFLOAT4* XM_CALLCONV XMVector3TransformStream(_Out_writes_bytes_(sizeof(XMFLOAT4) + OutputStride * (VectorCount - 1)) XMFLOAT4* pOutputStream, + _In_ size_t OutputStride, + _In_reads_bytes_(sizeof(XMFLOAT3) + InputStride * (VectorCount - 1)) const XMFLOAT3* pInputStream, + _In_ size_t InputStride, _In_ size_t VectorCount, _In_ FXMMATRIX M) noexcept; + XMVECTOR XM_CALLCONV XMVector3TransformCoord(FXMVECTOR V, FXMMATRIX M) noexcept; + XMFLOAT3* XM_CALLCONV XMVector3TransformCoordStream(_Out_writes_bytes_(sizeof(XMFLOAT3) + OutputStride * (VectorCount - 1)) XMFLOAT3* pOutputStream, + _In_ size_t OutputStride, + _In_reads_bytes_(sizeof(XMFLOAT3) + InputStride * (VectorCount - 1)) const XMFLOAT3* pInputStream, + _In_ size_t InputStride, _In_ size_t VectorCount, _In_ FXMMATRIX M) noexcept; + XMVECTOR XM_CALLCONV XMVector3TransformNormal(FXMVECTOR V, FXMMATRIX M) noexcept; + XMFLOAT3* XM_CALLCONV XMVector3TransformNormalStream(_Out_writes_bytes_(sizeof(XMFLOAT3) + OutputStride * (VectorCount - 1)) XMFLOAT3* pOutputStream, + _In_ size_t OutputStride, + _In_reads_bytes_(sizeof(XMFLOAT3) + InputStride * (VectorCount - 1)) const XMFLOAT3* pInputStream, + _In_ size_t InputStride, _In_ size_t VectorCount, _In_ FXMMATRIX M) noexcept; + XMVECTOR XM_CALLCONV XMVector3Project(FXMVECTOR V, float ViewportX, float ViewportY, float ViewportWidth, float ViewportHeight, float ViewportMinZ, float ViewportMaxZ, + FXMMATRIX Projection, CXMMATRIX View, CXMMATRIX World) noexcept; + XMFLOAT3* XM_CALLCONV XMVector3ProjectStream(_Out_writes_bytes_(sizeof(XMFLOAT3) + OutputStride * (VectorCount - 1)) XMFLOAT3* pOutputStream, + _In_ size_t OutputStride, + _In_reads_bytes_(sizeof(XMFLOAT3) + InputStride * (VectorCount - 1)) const XMFLOAT3* pInputStream, + _In_ size_t InputStride, _In_ size_t VectorCount, + _In_ float ViewportX, _In_ float ViewportY, _In_ float ViewportWidth, _In_ float ViewportHeight, _In_ float ViewportMinZ, _In_ float ViewportMaxZ, + _In_ FXMMATRIX Projection, _In_ CXMMATRIX View, _In_ CXMMATRIX World) noexcept; + XMVECTOR XM_CALLCONV XMVector3Unproject(FXMVECTOR V, float ViewportX, float ViewportY, float ViewportWidth, float ViewportHeight, float ViewportMinZ, float ViewportMaxZ, + FXMMATRIX Projection, CXMMATRIX View, CXMMATRIX World) noexcept; + XMFLOAT3* XM_CALLCONV XMVector3UnprojectStream(_Out_writes_bytes_(sizeof(XMFLOAT3) + OutputStride * (VectorCount - 1)) XMFLOAT3* pOutputStream, + _In_ size_t OutputStride, + _In_reads_bytes_(sizeof(XMFLOAT3) + InputStride * (VectorCount - 1)) const XMFLOAT3* pInputStream, + _In_ size_t InputStride, _In_ size_t VectorCount, + _In_ float ViewportX, _In_ float ViewportY, _In_ float ViewportWidth, _In_ float ViewportHeight, _In_ float ViewportMinZ, _In_ float ViewportMaxZ, + _In_ FXMMATRIX Projection, _In_ CXMMATRIX View, _In_ CXMMATRIX World) noexcept; + + /**************************************************************************** + * + * 4D vector operations + * + ****************************************************************************/ + + bool XM_CALLCONV XMVector4Equal(FXMVECTOR V1, FXMVECTOR V2) noexcept; + uint32_t XM_CALLCONV XMVector4EqualR(FXMVECTOR V1, FXMVECTOR V2) noexcept; + bool XM_CALLCONV XMVector4EqualInt(FXMVECTOR V1, FXMVECTOR V2) noexcept; + uint32_t XM_CALLCONV XMVector4EqualIntR(FXMVECTOR V1, FXMVECTOR V2) noexcept; + bool XM_CALLCONV XMVector4NearEqual(FXMVECTOR V1, FXMVECTOR V2, FXMVECTOR Epsilon) noexcept; + bool XM_CALLCONV XMVector4NotEqual(FXMVECTOR V1, FXMVECTOR V2) noexcept; + bool XM_CALLCONV XMVector4NotEqualInt(FXMVECTOR V1, FXMVECTOR V2) noexcept; + bool XM_CALLCONV XMVector4Greater(FXMVECTOR V1, FXMVECTOR V2) noexcept; + uint32_t XM_CALLCONV XMVector4GreaterR(FXMVECTOR V1, FXMVECTOR V2) noexcept; + bool XM_CALLCONV XMVector4GreaterOrEqual(FXMVECTOR V1, FXMVECTOR V2) noexcept; + uint32_t XM_CALLCONV XMVector4GreaterOrEqualR(FXMVECTOR V1, FXMVECTOR V2) noexcept; + bool XM_CALLCONV XMVector4Less(FXMVECTOR V1, FXMVECTOR V2) noexcept; + bool XM_CALLCONV XMVector4LessOrEqual(FXMVECTOR V1, FXMVECTOR V2) noexcept; + bool XM_CALLCONV XMVector4InBounds(FXMVECTOR V, FXMVECTOR Bounds) noexcept; + + bool XM_CALLCONV XMVector4IsNaN(FXMVECTOR V) noexcept; + bool XM_CALLCONV XMVector4IsInfinite(FXMVECTOR V) noexcept; + + XMVECTOR XM_CALLCONV XMVector4Dot(FXMVECTOR V1, FXMVECTOR V2) noexcept; + XMVECTOR XM_CALLCONV XMVector4Cross(FXMVECTOR V1, FXMVECTOR V2, FXMVECTOR V3) noexcept; + XMVECTOR XM_CALLCONV XMVector4LengthSq(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVector4ReciprocalLengthEst(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVector4ReciprocalLength(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVector4LengthEst(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVector4Length(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVector4NormalizeEst(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVector4Normalize(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVector4ClampLength(FXMVECTOR V, float LengthMin, float LengthMax) noexcept; + XMVECTOR XM_CALLCONV XMVector4ClampLengthV(FXMVECTOR V, FXMVECTOR LengthMin, FXMVECTOR LengthMax) noexcept; + XMVECTOR XM_CALLCONV XMVector4Reflect(FXMVECTOR Incident, FXMVECTOR Normal) noexcept; + XMVECTOR XM_CALLCONV XMVector4Refract(FXMVECTOR Incident, FXMVECTOR Normal, float RefractionIndex) noexcept; + XMVECTOR XM_CALLCONV XMVector4RefractV(FXMVECTOR Incident, FXMVECTOR Normal, FXMVECTOR RefractionIndex) noexcept; + XMVECTOR XM_CALLCONV XMVector4Orthogonal(FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMVector4AngleBetweenNormalsEst(FXMVECTOR N1, FXMVECTOR N2) noexcept; + XMVECTOR XM_CALLCONV XMVector4AngleBetweenNormals(FXMVECTOR N1, FXMVECTOR N2) noexcept; + XMVECTOR XM_CALLCONV XMVector4AngleBetweenVectors(FXMVECTOR V1, FXMVECTOR V2) noexcept; + XMVECTOR XM_CALLCONV XMVector4Transform(FXMVECTOR V, FXMMATRIX M) noexcept; + XMFLOAT4* XM_CALLCONV XMVector4TransformStream(_Out_writes_bytes_(sizeof(XMFLOAT4) + OutputStride * (VectorCount - 1)) XMFLOAT4* pOutputStream, + _In_ size_t OutputStride, + _In_reads_bytes_(sizeof(XMFLOAT4) + InputStride * (VectorCount - 1)) const XMFLOAT4* pInputStream, + _In_ size_t InputStride, _In_ size_t VectorCount, _In_ FXMMATRIX M) noexcept; + + /**************************************************************************** + * + * Matrix operations + * + ****************************************************************************/ + + bool XM_CALLCONV XMMatrixIsNaN(FXMMATRIX M) noexcept; + bool XM_CALLCONV XMMatrixIsInfinite(FXMMATRIX M) noexcept; + bool XM_CALLCONV XMMatrixIsIdentity(FXMMATRIX M) noexcept; + + XMMATRIX XM_CALLCONV XMMatrixMultiply(FXMMATRIX M1, CXMMATRIX M2) noexcept; + XMMATRIX XM_CALLCONV XMMatrixMultiplyTranspose(FXMMATRIX M1, CXMMATRIX M2) noexcept; + XMMATRIX XM_CALLCONV XMMatrixTranspose(FXMMATRIX M) noexcept; + XMMATRIX XM_CALLCONV XMMatrixInverse(_Out_opt_ XMVECTOR* pDeterminant, _In_ FXMMATRIX M) noexcept; + XMMATRIX XM_CALLCONV XMMatrixVectorTensorProduct(FXMVECTOR V1, FXMVECTOR V2) noexcept; + XMVECTOR XM_CALLCONV XMMatrixDeterminant(FXMMATRIX M) noexcept; + + _Success_(return) + bool XM_CALLCONV XMMatrixDecompose(_Out_ XMVECTOR* outScale, _Out_ XMVECTOR* outRotQuat, _Out_ XMVECTOR* outTrans, _In_ FXMMATRIX M) noexcept; + + XMMATRIX XM_CALLCONV XMMatrixIdentity() noexcept; + XMMATRIX XM_CALLCONV XMMatrixSet(float m00, float m01, float m02, float m03, + float m10, float m11, float m12, float m13, + float m20, float m21, float m22, float m23, + float m30, float m31, float m32, float m33) noexcept; + XMMATRIX XM_CALLCONV XMMatrixTranslation(float OffsetX, float OffsetY, float OffsetZ) noexcept; + XMMATRIX XM_CALLCONV XMMatrixTranslationFromVector(FXMVECTOR Offset) noexcept; + XMMATRIX XM_CALLCONV XMMatrixScaling(float ScaleX, float ScaleY, float ScaleZ) noexcept; + XMMATRIX XM_CALLCONV XMMatrixScalingFromVector(FXMVECTOR Scale) noexcept; + XMMATRIX XM_CALLCONV XMMatrixRotationX(float Angle) noexcept; + XMMATRIX XM_CALLCONV XMMatrixRotationY(float Angle) noexcept; + XMMATRIX XM_CALLCONV XMMatrixRotationZ(float Angle) noexcept; + + // Rotates about y-axis (Yaw), then x-axis (Pitch), then z-axis (Roll) + XMMATRIX XM_CALLCONV XMMatrixRotationRollPitchYaw(float Pitch, float Yaw, float Roll) noexcept; + + // Rotates about y-axis (Angles.y), then x-axis (Angles.x), then z-axis (Angles.z) + XMMATRIX XM_CALLCONV XMMatrixRotationRollPitchYawFromVector(FXMVECTOR Angles) noexcept; + + XMMATRIX XM_CALLCONV XMMatrixRotationNormal(FXMVECTOR NormalAxis, float Angle) noexcept; + XMMATRIX XM_CALLCONV XMMatrixRotationAxis(FXMVECTOR Axis, float Angle) noexcept; + XMMATRIX XM_CALLCONV XMMatrixRotationQuaternion(FXMVECTOR Quaternion) noexcept; + XMMATRIX XM_CALLCONV XMMatrixTransformation2D(FXMVECTOR ScalingOrigin, float ScalingOrientation, FXMVECTOR Scaling, + FXMVECTOR RotationOrigin, float Rotation, GXMVECTOR Translation) noexcept; + XMMATRIX XM_CALLCONV XMMatrixTransformation(FXMVECTOR ScalingOrigin, FXMVECTOR ScalingOrientationQuaternion, FXMVECTOR Scaling, + GXMVECTOR RotationOrigin, HXMVECTOR RotationQuaternion, HXMVECTOR Translation) noexcept; + XMMATRIX XM_CALLCONV XMMatrixAffineTransformation2D(FXMVECTOR Scaling, FXMVECTOR RotationOrigin, float Rotation, FXMVECTOR Translation) noexcept; + XMMATRIX XM_CALLCONV XMMatrixAffineTransformation(FXMVECTOR Scaling, FXMVECTOR RotationOrigin, FXMVECTOR RotationQuaternion, GXMVECTOR Translation) noexcept; + XMMATRIX XM_CALLCONV XMMatrixReflect(FXMVECTOR ReflectionPlane) noexcept; + XMMATRIX XM_CALLCONV XMMatrixShadow(FXMVECTOR ShadowPlane, FXMVECTOR LightPosition) noexcept; + + XMMATRIX XM_CALLCONV XMMatrixLookAtLH(FXMVECTOR EyePosition, FXMVECTOR FocusPosition, FXMVECTOR UpDirection) noexcept; + XMMATRIX XM_CALLCONV XMMatrixLookAtRH(FXMVECTOR EyePosition, FXMVECTOR FocusPosition, FXMVECTOR UpDirection) noexcept; + XMMATRIX XM_CALLCONV XMMatrixLookToLH(FXMVECTOR EyePosition, FXMVECTOR EyeDirection, FXMVECTOR UpDirection) noexcept; + XMMATRIX XM_CALLCONV XMMatrixLookToRH(FXMVECTOR EyePosition, FXMVECTOR EyeDirection, FXMVECTOR UpDirection) noexcept; + XMMATRIX XM_CALLCONV XMMatrixPerspectiveLH(float ViewWidth, float ViewHeight, float NearZ, float FarZ) noexcept; + XMMATRIX XM_CALLCONV XMMatrixPerspectiveRH(float ViewWidth, float ViewHeight, float NearZ, float FarZ) noexcept; + XMMATRIX XM_CALLCONV XMMatrixPerspectiveFovLH(float FovAngleY, float AspectRatio, float NearZ, float FarZ) noexcept; + XMMATRIX XM_CALLCONV XMMatrixPerspectiveFovRH(float FovAngleY, float AspectRatio, float NearZ, float FarZ) noexcept; + XMMATRIX XM_CALLCONV XMMatrixPerspectiveOffCenterLH(float ViewLeft, float ViewRight, float ViewBottom, float ViewTop, float NearZ, float FarZ) noexcept; + XMMATRIX XM_CALLCONV XMMatrixPerspectiveOffCenterRH(float ViewLeft, float ViewRight, float ViewBottom, float ViewTop, float NearZ, float FarZ) noexcept; + XMMATRIX XM_CALLCONV XMMatrixOrthographicLH(float ViewWidth, float ViewHeight, float NearZ, float FarZ) noexcept; + XMMATRIX XM_CALLCONV XMMatrixOrthographicRH(float ViewWidth, float ViewHeight, float NearZ, float FarZ) noexcept; + XMMATRIX XM_CALLCONV XMMatrixOrthographicOffCenterLH(float ViewLeft, float ViewRight, float ViewBottom, float ViewTop, float NearZ, float FarZ) noexcept; + XMMATRIX XM_CALLCONV XMMatrixOrthographicOffCenterRH(float ViewLeft, float ViewRight, float ViewBottom, float ViewTop, float NearZ, float FarZ) noexcept; + + + /**************************************************************************** + * + * Quaternion operations + * + ****************************************************************************/ + + bool XM_CALLCONV XMQuaternionEqual(FXMVECTOR Q1, FXMVECTOR Q2) noexcept; + bool XM_CALLCONV XMQuaternionNotEqual(FXMVECTOR Q1, FXMVECTOR Q2) noexcept; + + bool XM_CALLCONV XMQuaternionIsNaN(FXMVECTOR Q) noexcept; + bool XM_CALLCONV XMQuaternionIsInfinite(FXMVECTOR Q) noexcept; + bool XM_CALLCONV XMQuaternionIsIdentity(FXMVECTOR Q) noexcept; + + XMVECTOR XM_CALLCONV XMQuaternionDot(FXMVECTOR Q1, FXMVECTOR Q2) noexcept; + XMVECTOR XM_CALLCONV XMQuaternionMultiply(FXMVECTOR Q1, FXMVECTOR Q2) noexcept; + XMVECTOR XM_CALLCONV XMQuaternionLengthSq(FXMVECTOR Q) noexcept; + XMVECTOR XM_CALLCONV XMQuaternionReciprocalLength(FXMVECTOR Q) noexcept; + XMVECTOR XM_CALLCONV XMQuaternionLength(FXMVECTOR Q) noexcept; + XMVECTOR XM_CALLCONV XMQuaternionNormalizeEst(FXMVECTOR Q) noexcept; + XMVECTOR XM_CALLCONV XMQuaternionNormalize(FXMVECTOR Q) noexcept; + XMVECTOR XM_CALLCONV XMQuaternionConjugate(FXMVECTOR Q) noexcept; + XMVECTOR XM_CALLCONV XMQuaternionInverse(FXMVECTOR Q) noexcept; + XMVECTOR XM_CALLCONV XMQuaternionLn(FXMVECTOR Q) noexcept; + XMVECTOR XM_CALLCONV XMQuaternionExp(FXMVECTOR Q) noexcept; + XMVECTOR XM_CALLCONV XMQuaternionSlerp(FXMVECTOR Q0, FXMVECTOR Q1, float t) noexcept; + XMVECTOR XM_CALLCONV XMQuaternionSlerpV(FXMVECTOR Q0, FXMVECTOR Q1, FXMVECTOR T) noexcept; + XMVECTOR XM_CALLCONV XMQuaternionSquad(FXMVECTOR Q0, FXMVECTOR Q1, FXMVECTOR Q2, GXMVECTOR Q3, float t) noexcept; + XMVECTOR XM_CALLCONV XMQuaternionSquadV(FXMVECTOR Q0, FXMVECTOR Q1, FXMVECTOR Q2, GXMVECTOR Q3, HXMVECTOR T) noexcept; + void XM_CALLCONV XMQuaternionSquadSetup(_Out_ XMVECTOR* pA, _Out_ XMVECTOR* pB, _Out_ XMVECTOR* pC, _In_ FXMVECTOR Q0, _In_ FXMVECTOR Q1, _In_ FXMVECTOR Q2, _In_ GXMVECTOR Q3) noexcept; + XMVECTOR XM_CALLCONV XMQuaternionBaryCentric(FXMVECTOR Q0, FXMVECTOR Q1, FXMVECTOR Q2, float f, float g) noexcept; + XMVECTOR XM_CALLCONV XMQuaternionBaryCentricV(FXMVECTOR Q0, FXMVECTOR Q1, FXMVECTOR Q2, GXMVECTOR F, HXMVECTOR G) noexcept; + + XMVECTOR XM_CALLCONV XMQuaternionIdentity() noexcept; + + // Rotates about y-axis (Yaw), then x-axis (Pitch), then z-axis (Roll) + XMVECTOR XM_CALLCONV XMQuaternionRotationRollPitchYaw(float Pitch, float Yaw, float Roll) noexcept; + + // Rotates about y-axis (Angles.y), then x-axis (Angles.x), then z-axis (Angles.z) + XMVECTOR XM_CALLCONV XMQuaternionRotationRollPitchYawFromVector(FXMVECTOR Angles) noexcept; + + XMVECTOR XM_CALLCONV XMQuaternionRotationNormal(FXMVECTOR NormalAxis, float Angle) noexcept; + XMVECTOR XM_CALLCONV XMQuaternionRotationAxis(FXMVECTOR Axis, float Angle) noexcept; + XMVECTOR XM_CALLCONV XMQuaternionRotationMatrix(FXMMATRIX M) noexcept; + + void XM_CALLCONV XMQuaternionToAxisAngle(_Out_ XMVECTOR* pAxis, _Out_ float* pAngle, _In_ FXMVECTOR Q) noexcept; + + /**************************************************************************** + * + * Plane operations + * + ****************************************************************************/ + + bool XM_CALLCONV XMPlaneEqual(FXMVECTOR P1, FXMVECTOR P2) noexcept; + bool XM_CALLCONV XMPlaneNearEqual(FXMVECTOR P1, FXMVECTOR P2, FXMVECTOR Epsilon) noexcept; + bool XM_CALLCONV XMPlaneNotEqual(FXMVECTOR P1, FXMVECTOR P2) noexcept; + + bool XM_CALLCONV XMPlaneIsNaN(FXMVECTOR P) noexcept; + bool XM_CALLCONV XMPlaneIsInfinite(FXMVECTOR P) noexcept; + + XMVECTOR XM_CALLCONV XMPlaneDot(FXMVECTOR P, FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMPlaneDotCoord(FXMVECTOR P, FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMPlaneDotNormal(FXMVECTOR P, FXMVECTOR V) noexcept; + XMVECTOR XM_CALLCONV XMPlaneNormalizeEst(FXMVECTOR P) noexcept; + XMVECTOR XM_CALLCONV XMPlaneNormalize(FXMVECTOR P) noexcept; + XMVECTOR XM_CALLCONV XMPlaneIntersectLine(FXMVECTOR P, FXMVECTOR LinePoint1, FXMVECTOR LinePoint2) noexcept; + void XM_CALLCONV XMPlaneIntersectPlane(_Out_ XMVECTOR* pLinePoint1, _Out_ XMVECTOR* pLinePoint2, _In_ FXMVECTOR P1, _In_ FXMVECTOR P2) noexcept; + + // Transforms a plane given an inverse transpose matrix + XMVECTOR XM_CALLCONV XMPlaneTransform(FXMVECTOR P, FXMMATRIX ITM) noexcept; + + // Transforms an array of planes given an inverse transpose matrix + XMFLOAT4* XM_CALLCONV XMPlaneTransformStream(_Out_writes_bytes_(sizeof(XMFLOAT4) + OutputStride * (PlaneCount - 1)) XMFLOAT4* pOutputStream, + _In_ size_t OutputStride, + _In_reads_bytes_(sizeof(XMFLOAT4) + InputStride * (PlaneCount - 1)) const XMFLOAT4* pInputStream, + _In_ size_t InputStride, _In_ size_t PlaneCount, _In_ FXMMATRIX ITM) noexcept; + + XMVECTOR XM_CALLCONV XMPlaneFromPointNormal(FXMVECTOR Point, FXMVECTOR Normal) noexcept; + XMVECTOR XM_CALLCONV XMPlaneFromPoints(FXMVECTOR Point1, FXMVECTOR Point2, FXMVECTOR Point3) noexcept; + + /**************************************************************************** + * + * Color operations + * + ****************************************************************************/ + + bool XM_CALLCONV XMColorEqual(FXMVECTOR C1, FXMVECTOR C2) noexcept; + bool XM_CALLCONV XMColorNotEqual(FXMVECTOR C1, FXMVECTOR C2) noexcept; + bool XM_CALLCONV XMColorGreater(FXMVECTOR C1, FXMVECTOR C2) noexcept; + bool XM_CALLCONV XMColorGreaterOrEqual(FXMVECTOR C1, FXMVECTOR C2) noexcept; + bool XM_CALLCONV XMColorLess(FXMVECTOR C1, FXMVECTOR C2) noexcept; + bool XM_CALLCONV XMColorLessOrEqual(FXMVECTOR C1, FXMVECTOR C2) noexcept; + + bool XM_CALLCONV XMColorIsNaN(FXMVECTOR C) noexcept; + bool XM_CALLCONV XMColorIsInfinite(FXMVECTOR C) noexcept; + + XMVECTOR XM_CALLCONV XMColorNegative(FXMVECTOR C) noexcept; + XMVECTOR XM_CALLCONV XMColorModulate(FXMVECTOR C1, FXMVECTOR C2) noexcept; + XMVECTOR XM_CALLCONV XMColorAdjustSaturation(FXMVECTOR C, float Saturation) noexcept; + XMVECTOR XM_CALLCONV XMColorAdjustContrast(FXMVECTOR C, float Contrast) noexcept; + + XMVECTOR XM_CALLCONV XMColorRGBToHSL(FXMVECTOR rgb) noexcept; + XMVECTOR XM_CALLCONV XMColorHSLToRGB(FXMVECTOR hsl) noexcept; + + XMVECTOR XM_CALLCONV XMColorRGBToHSV(FXMVECTOR rgb) noexcept; + XMVECTOR XM_CALLCONV XMColorHSVToRGB(FXMVECTOR hsv) noexcept; + + XMVECTOR XM_CALLCONV XMColorRGBToYUV(FXMVECTOR rgb) noexcept; + XMVECTOR XM_CALLCONV XMColorYUVToRGB(FXMVECTOR yuv) noexcept; + + XMVECTOR XM_CALLCONV XMColorRGBToYUV_HD(FXMVECTOR rgb) noexcept; + XMVECTOR XM_CALLCONV XMColorYUVToRGB_HD(FXMVECTOR yuv) noexcept; + + XMVECTOR XM_CALLCONV XMColorRGBToYUV_UHD(FXMVECTOR rgb) noexcept; + XMVECTOR XM_CALLCONV XMColorYUVToRGB_UHD(FXMVECTOR yuv) noexcept; + + XMVECTOR XM_CALLCONV XMColorRGBToXYZ(FXMVECTOR rgb) noexcept; + XMVECTOR XM_CALLCONV XMColorXYZToRGB(FXMVECTOR xyz) noexcept; + + XMVECTOR XM_CALLCONV XMColorXYZToSRGB(FXMVECTOR xyz) noexcept; + XMVECTOR XM_CALLCONV XMColorSRGBToXYZ(FXMVECTOR srgb) noexcept; + + XMVECTOR XM_CALLCONV XMColorRGBToSRGB(FXMVECTOR rgb) noexcept; + XMVECTOR XM_CALLCONV XMColorSRGBToRGB(FXMVECTOR srgb) noexcept; + + + /**************************************************************************** + * + * Miscellaneous operations + * + ****************************************************************************/ + + bool XMVerifyCPUSupport() noexcept; + + XMVECTOR XM_CALLCONV XMFresnelTerm(FXMVECTOR CosIncidentAngle, FXMVECTOR RefractionIndex) noexcept; + + bool XMScalarNearEqual(float S1, float S2, float Epsilon) noexcept; + float XMScalarModAngle(float Value) noexcept; + + float XMScalarSin(float Value) noexcept; + float XMScalarSinEst(float Value) noexcept; + + float XMScalarCos(float Value) noexcept; + float XMScalarCosEst(float Value) noexcept; + + void XMScalarSinCos(_Out_ float* pSin, _Out_ float* pCos, float Value) noexcept; + void XMScalarSinCosEst(_Out_ float* pSin, _Out_ float* pCos, float Value) noexcept; + + float XMScalarASin(float Value) noexcept; + float XMScalarASinEst(float Value) noexcept; + + float XMScalarACos(float Value) noexcept; + float XMScalarACosEst(float Value) noexcept; + + /**************************************************************************** + * + * Templates + * + ****************************************************************************/ + +#if defined(__XNAMATH_H__) && defined(XMMin) +#undef XMMin +#undef XMMax +#endif + + template inline T XMMin(T a, T b) noexcept { return (a < b) ? a : b; } + template inline T XMMax(T a, T b) noexcept { return (a > b) ? a : b; } + + //------------------------------------------------------------------------------ + +#if defined(_XM_SSE_INTRINSICS_) && !defined(_XM_NO_INTRINSICS_) + +// PermuteHelper internal template (SSE only) + namespace MathInternal + { + // Slow path fallback for permutes that do not map to a single SSE shuffle opcode. + template struct PermuteHelper + { + static XMVECTOR XM_CALLCONV Permute(FXMVECTOR v1, FXMVECTOR v2) noexcept + { + static const XMVECTORU32 selectMask = + { { { + WhichX ? 0xFFFFFFFF : 0, + WhichY ? 0xFFFFFFFF : 0, + WhichZ ? 0xFFFFFFFF : 0, + WhichW ? 0xFFFFFFFF : 0, + } } }; + + XMVECTOR shuffled1 = XM_PERMUTE_PS(v1, Shuffle); + XMVECTOR shuffled2 = XM_PERMUTE_PS(v2, Shuffle); + + XMVECTOR masked1 = _mm_andnot_ps(selectMask, shuffled1); + XMVECTOR masked2 = _mm_and_ps(selectMask, shuffled2); + + return _mm_or_ps(masked1, masked2); + } + }; + + // Fast path for permutes that only read from the first vector. + template struct PermuteHelper + { + static XMVECTOR XM_CALLCONV Permute(FXMVECTOR v1, FXMVECTOR) noexcept { return XM_PERMUTE_PS(v1, Shuffle); } + }; + + // Fast path for permutes that only read from the second vector. + template struct PermuteHelper + { + static XMVECTOR XM_CALLCONV Permute(FXMVECTOR, FXMVECTOR v2) noexcept { return XM_PERMUTE_PS(v2, Shuffle); } + }; + + // Fast path for permutes that read XY from the first vector, ZW from the second. + template struct PermuteHelper + { + static XMVECTOR XM_CALLCONV Permute(FXMVECTOR v1, FXMVECTOR v2) noexcept { return _mm_shuffle_ps(v1, v2, Shuffle); } + }; + + // Fast path for permutes that read XY from the second vector, ZW from the first. + template struct PermuteHelper + { + static XMVECTOR XM_CALLCONV Permute(FXMVECTOR v1, FXMVECTOR v2) noexcept { return _mm_shuffle_ps(v2, v1, Shuffle); } + }; + } + +#endif // _XM_SSE_INTRINSICS_ && !_XM_NO_INTRINSICS_ + + // General permute template + template + inline XMVECTOR XM_CALLCONV XMVectorPermute(FXMVECTOR V1, FXMVECTOR V2) noexcept + { + static_assert(PermuteX <= 7, "PermuteX template parameter out of range"); + static_assert(PermuteY <= 7, "PermuteY template parameter out of range"); + static_assert(PermuteZ <= 7, "PermuteZ template parameter out of range"); + static_assert(PermuteW <= 7, "PermuteW template parameter out of range"); + + #if defined(_XM_SSE_INTRINSICS_) && !defined(_XM_NO_INTRINSICS_) + constexpr uint32_t Shuffle = _MM_SHUFFLE(PermuteW & 3, PermuteZ & 3, PermuteY & 3, PermuteX & 3); + + constexpr bool WhichX = PermuteX > 3; + constexpr bool WhichY = PermuteY > 3; + constexpr bool WhichZ = PermuteZ > 3; + constexpr bool WhichW = PermuteW > 3; + + return MathInternal::PermuteHelper::Permute(V1, V2); + #else + + return XMVectorPermute(V1, V2, PermuteX, PermuteY, PermuteZ, PermuteW); + + #endif + } + + // Special-case permute templates + template<> constexpr XMVECTOR XM_CALLCONV XMVectorPermute<0, 1, 2, 3>(FXMVECTOR V1, FXMVECTOR) noexcept { return V1; } + template<> constexpr XMVECTOR XM_CALLCONV XMVectorPermute<4, 5, 6, 7>(FXMVECTOR, FXMVECTOR V2) noexcept { return V2; } + +#if defined(_XM_SSE_INTRINSICS_) && !defined(_XM_NO_INTRINSICS_) + template<> inline XMVECTOR XM_CALLCONV XMVectorPermute<0, 1, 4, 5>(FXMVECTOR V1, FXMVECTOR V2) noexcept { return _mm_movelh_ps(V1, V2); } + template<> inline XMVECTOR XM_CALLCONV XMVectorPermute<6, 7, 2, 3>(FXMVECTOR V1, FXMVECTOR V2) noexcept { return _mm_movehl_ps(V1, V2); } + template<> inline XMVECTOR XM_CALLCONV XMVectorPermute<0, 4, 1, 5>(FXMVECTOR V1, FXMVECTOR V2) noexcept { return _mm_unpacklo_ps(V1, V2); } + template<> inline XMVECTOR XM_CALLCONV XMVectorPermute<2, 6, 3, 7>(FXMVECTOR V1, FXMVECTOR V2) noexcept { return _mm_unpackhi_ps(V1, V2); } + template<> inline XMVECTOR XM_CALLCONV XMVectorPermute<2, 3, 6, 7>(FXMVECTOR V1, FXMVECTOR V2) noexcept { return _mm_castpd_ps(_mm_unpackhi_pd(_mm_castps_pd(V1), _mm_castps_pd(V2))); } +#endif + +#if defined(_XM_SSE4_INTRINSICS_) && !defined(_XM_NO_INTRINSICS_) + template<> inline XMVECTOR XM_CALLCONV XMVectorPermute<4, 1, 2, 3>(FXMVECTOR V1, FXMVECTOR V2) noexcept { return _mm_blend_ps(V1, V2, 0x1); } + template<> inline XMVECTOR XM_CALLCONV XMVectorPermute<0, 5, 2, 3>(FXMVECTOR V1, FXMVECTOR V2) noexcept { return _mm_blend_ps(V1, V2, 0x2); } + template<> inline XMVECTOR XM_CALLCONV XMVectorPermute<4, 5, 2, 3>(FXMVECTOR V1, FXMVECTOR V2) noexcept { return _mm_blend_ps(V1, V2, 0x3); } + template<> inline XMVECTOR XM_CALLCONV XMVectorPermute<0, 1, 6, 3>(FXMVECTOR V1, FXMVECTOR V2) noexcept { return _mm_blend_ps(V1, V2, 0x4); } + template<> inline XMVECTOR XM_CALLCONV XMVectorPermute<4, 1, 6, 3>(FXMVECTOR V1, FXMVECTOR V2) noexcept { return _mm_blend_ps(V1, V2, 0x5); } + template<> inline XMVECTOR XM_CALLCONV XMVectorPermute<0, 5, 6, 3>(FXMVECTOR V1, FXMVECTOR V2) noexcept { return _mm_blend_ps(V1, V2, 0x6); } + template<> inline XMVECTOR XM_CALLCONV XMVectorPermute<4, 5, 6, 3>(FXMVECTOR V1, FXMVECTOR V2) noexcept { return _mm_blend_ps(V1, V2, 0x7); } + template<> inline XMVECTOR XM_CALLCONV XMVectorPermute<0, 1, 2, 7>(FXMVECTOR V1, FXMVECTOR V2) noexcept { return _mm_blend_ps(V1, V2, 0x8); } + template<> inline XMVECTOR XM_CALLCONV XMVectorPermute<4, 1, 2, 7>(FXMVECTOR V1, FXMVECTOR V2) noexcept { return _mm_blend_ps(V1, V2, 0x9); } + template<> inline XMVECTOR XM_CALLCONV XMVectorPermute<0, 5, 2, 7>(FXMVECTOR V1, FXMVECTOR V2) noexcept { return _mm_blend_ps(V1, V2, 0xA); } + template<> inline XMVECTOR XM_CALLCONV XMVectorPermute<4, 5, 2, 7>(FXMVECTOR V1, FXMVECTOR V2) noexcept { return _mm_blend_ps(V1, V2, 0xB); } + template<> inline XMVECTOR XM_CALLCONV XMVectorPermute<0, 1, 6, 7>(FXMVECTOR V1, FXMVECTOR V2) noexcept { return _mm_blend_ps(V1, V2, 0xC); } + template<> inline XMVECTOR XM_CALLCONV XMVectorPermute<4, 1, 6, 7>(FXMVECTOR V1, FXMVECTOR V2) noexcept { return _mm_blend_ps(V1, V2, 0xD); } + template<> inline XMVECTOR XM_CALLCONV XMVectorPermute<0, 5, 6, 7>(FXMVECTOR V1, FXMVECTOR V2) noexcept { return _mm_blend_ps(V1, V2, 0xE); } +#endif + +#if defined(_XM_ARM_NEON_INTRINSICS_) && !defined(_XM_NO_INTRINSICS_) + + // If the indices are all in the range 0-3 or 4-7, then use XMVectorSwizzle instead + // The mirror cases are not spelled out here as the programmer can always swap the arguments + // (i.e. prefer permutes where the X element comes from the V1 vector instead of the V2 vector) + + template<> inline XMVECTOR XM_CALLCONV XMVectorPermute<0, 1, 4, 5>(FXMVECTOR V1, FXMVECTOR V2) noexcept { return vcombine_f32(vget_low_f32(V1), vget_low_f32(V2)); } + template<> inline XMVECTOR XM_CALLCONV XMVectorPermute<1, 0, 4, 5>(FXMVECTOR V1, FXMVECTOR V2) noexcept { return vcombine_f32(vrev64_f32(vget_low_f32(V1)), vget_low_f32(V2)); } + template<> inline XMVECTOR XM_CALLCONV XMVectorPermute<0, 1, 5, 4>(FXMVECTOR V1, FXMVECTOR V2) noexcept { return vcombine_f32(vget_low_f32(V1), vrev64_f32(vget_low_f32(V2))); } + template<> inline XMVECTOR XM_CALLCONV XMVectorPermute<1, 0, 5, 4>(FXMVECTOR V1, FXMVECTOR V2) noexcept { return vcombine_f32(vrev64_f32(vget_low_f32(V1)), vrev64_f32(vget_low_f32(V2))); } + + template<> inline XMVECTOR XM_CALLCONV XMVectorPermute<2, 3, 6, 7>(FXMVECTOR V1, FXMVECTOR V2) noexcept { return vcombine_f32(vget_high_f32(V1), vget_high_f32(V2)); } + template<> inline XMVECTOR XM_CALLCONV XMVectorPermute<3, 2, 6, 7>(FXMVECTOR V1, FXMVECTOR V2) noexcept { return vcombine_f32(vrev64_f32(vget_high_f32(V1)), vget_high_f32(V2)); } + template<> inline XMVECTOR XM_CALLCONV XMVectorPermute<2, 3, 7, 6>(FXMVECTOR V1, FXMVECTOR V2) noexcept { return vcombine_f32(vget_high_f32(V1), vrev64_f32(vget_high_f32(V2))); } + template<> inline XMVECTOR XM_CALLCONV XMVectorPermute<3, 2, 7, 6>(FXMVECTOR V1, FXMVECTOR V2) noexcept { return vcombine_f32(vrev64_f32(vget_high_f32(V1)), vrev64_f32(vget_high_f32(V2))); } + + template<> inline XMVECTOR XM_CALLCONV XMVectorPermute<0, 1, 6, 7>(FXMVECTOR V1, FXMVECTOR V2) noexcept { return vcombine_f32(vget_low_f32(V1), vget_high_f32(V2)); } + template<> inline XMVECTOR XM_CALLCONV XMVectorPermute<1, 0, 6, 7>(FXMVECTOR V1, FXMVECTOR V2) noexcept { return vcombine_f32(vrev64_f32(vget_low_f32(V1)), vget_high_f32(V2)); } + template<> inline XMVECTOR XM_CALLCONV XMVectorPermute<0, 1, 7, 6>(FXMVECTOR V1, FXMVECTOR V2) noexcept { return vcombine_f32(vget_low_f32(V1), vrev64_f32(vget_high_f32(V2))); } + template<> inline XMVECTOR XM_CALLCONV XMVectorPermute<1, 0, 7, 6>(FXMVECTOR V1, FXMVECTOR V2) noexcept { return vcombine_f32(vrev64_f32(vget_low_f32(V1)), vrev64_f32(vget_high_f32(V2))); } + + template<> inline XMVECTOR XM_CALLCONV XMVectorPermute<3, 2, 4, 5>(FXMVECTOR V1, FXMVECTOR V2) noexcept { return vcombine_f32(vrev64_f32(vget_high_f32(V1)), vget_low_f32(V2)); } + template<> inline XMVECTOR XM_CALLCONV XMVectorPermute<2, 3, 5, 4>(FXMVECTOR V1, FXMVECTOR V2) noexcept { return vcombine_f32(vget_high_f32(V1), vrev64_f32(vget_low_f32(V2))); } + template<> inline XMVECTOR XM_CALLCONV XMVectorPermute<3, 2, 5, 4>(FXMVECTOR V1, FXMVECTOR V2) noexcept { return vcombine_f32(vrev64_f32(vget_high_f32(V1)), vrev64_f32(vget_low_f32(V2))); } + + template<> inline XMVECTOR XM_CALLCONV XMVectorPermute<0, 4, 2, 6>(FXMVECTOR V1, FXMVECTOR V2) noexcept { return vtrnq_f32(V1, V2).val[0]; } + template<> inline XMVECTOR XM_CALLCONV XMVectorPermute<1, 5, 3, 7>(FXMVECTOR V1, FXMVECTOR V2) noexcept { return vtrnq_f32(V1, V2).val[1]; } + + template<> inline XMVECTOR XM_CALLCONV XMVectorPermute<0, 4, 1, 5>(FXMVECTOR V1, FXMVECTOR V2) noexcept { return vzipq_f32(V1, V2).val[0]; } + template<> inline XMVECTOR XM_CALLCONV XMVectorPermute<2, 6, 3, 7>(FXMVECTOR V1, FXMVECTOR V2) noexcept { return vzipq_f32(V1, V2).val[1]; } + + template<> inline XMVECTOR XM_CALLCONV XMVectorPermute<0, 2, 4, 6>(FXMVECTOR V1, FXMVECTOR V2) noexcept { return vuzpq_f32(V1, V2).val[0]; } + template<> inline XMVECTOR XM_CALLCONV XMVectorPermute<1, 3, 5, 7>(FXMVECTOR V1, FXMVECTOR V2) noexcept { return vuzpq_f32(V1, V2).val[1]; } + + template<> inline XMVECTOR XM_CALLCONV XMVectorPermute<1, 2, 3, 4>(FXMVECTOR V1, FXMVECTOR V2) noexcept { return vextq_f32(V1, V2, 1); } + template<> inline XMVECTOR XM_CALLCONV XMVectorPermute<2, 3, 4, 5>(FXMVECTOR V1, FXMVECTOR V2) noexcept { return vextq_f32(V1, V2, 2); } + template<> inline XMVECTOR XM_CALLCONV XMVectorPermute<3, 4, 5, 6>(FXMVECTOR V1, FXMVECTOR V2) noexcept { return vextq_f32(V1, V2, 3); } + +#endif // _XM_ARM_NEON_INTRINSICS_ && !_XM_NO_INTRINSICS_ + + //------------------------------------------------------------------------------ + + // General swizzle template + template + inline XMVECTOR XM_CALLCONV XMVectorSwizzle(FXMVECTOR V) noexcept + { + static_assert(SwizzleX <= 3, "SwizzleX template parameter out of range"); + static_assert(SwizzleY <= 3, "SwizzleY template parameter out of range"); + static_assert(SwizzleZ <= 3, "SwizzleZ template parameter out of range"); + static_assert(SwizzleW <= 3, "SwizzleW template parameter out of range"); + + #if defined(_XM_SSE_INTRINSICS_) && !defined(_XM_NO_INTRINSICS_) + return XM_PERMUTE_PS(V, _MM_SHUFFLE(SwizzleW, SwizzleZ, SwizzleY, SwizzleX)); + #else + + return XMVectorSwizzle(V, SwizzleX, SwizzleY, SwizzleZ, SwizzleW); + + #endif + } + + // Specialized swizzles + template<> constexpr XMVECTOR XM_CALLCONV XMVectorSwizzle<0, 1, 2, 3>(FXMVECTOR V) noexcept { return V; } + +#if defined(_XM_SSE_INTRINSICS_) && !defined(_XM_NO_INTRINSICS_) + template<> inline XMVECTOR XM_CALLCONV XMVectorSwizzle<0, 1, 0, 1>(FXMVECTOR V) noexcept { return _mm_movelh_ps(V, V); } + template<> inline XMVECTOR XM_CALLCONV XMVectorSwizzle<2, 3, 2, 3>(FXMVECTOR V) noexcept { return _mm_movehl_ps(V, V); } + template<> inline XMVECTOR XM_CALLCONV XMVectorSwizzle<0, 0, 1, 1>(FXMVECTOR V) noexcept { return _mm_unpacklo_ps(V, V); } + template<> inline XMVECTOR XM_CALLCONV XMVectorSwizzle<2, 2, 3, 3>(FXMVECTOR V) noexcept { return _mm_unpackhi_ps(V, V); } +#endif + +#if defined(_XM_SSE3_INTRINSICS_) && !defined(_XM_NO_INTRINSICS_) + template<> inline XMVECTOR XM_CALLCONV XMVectorSwizzle<0, 0, 2, 2>(FXMVECTOR V) noexcept { return _mm_moveldup_ps(V); } + template<> inline XMVECTOR XM_CALLCONV XMVectorSwizzle<1, 1, 3, 3>(FXMVECTOR V) noexcept { return _mm_movehdup_ps(V); } +#endif + +#if defined(_XM_AVX2_INTRINSICS_) && !defined(_XM_NO_INTRINSICS_) && defined(_XM_FAVOR_INTEL_) + template<> inline XMVECTOR XM_CALLCONV XMVectorSwizzle<0, 0, 0, 0>(FXMVECTOR V) noexcept { return _mm_broadcastss_ps(V); } +#endif + +#if defined(_XM_ARM_NEON_INTRINSICS_) && !defined(_XM_NO_INTRINSICS_) + + template<> inline XMVECTOR XM_CALLCONV XMVectorSwizzle<0, 0, 0, 0>(FXMVECTOR V) noexcept { return vdupq_lane_f32(vget_low_f32(V), 0); } + template<> inline XMVECTOR XM_CALLCONV XMVectorSwizzle<1, 1, 1, 1>(FXMVECTOR V) noexcept { return vdupq_lane_f32(vget_low_f32(V), 1); } + template<> inline XMVECTOR XM_CALLCONV XMVectorSwizzle<2, 2, 2, 2>(FXMVECTOR V) noexcept { return vdupq_lane_f32(vget_high_f32(V), 0); } + template<> inline XMVECTOR XM_CALLCONV XMVectorSwizzle<3, 3, 3, 3>(FXMVECTOR V) noexcept { return vdupq_lane_f32(vget_high_f32(V), 1); } + + template<> inline XMVECTOR XM_CALLCONV XMVectorSwizzle<1, 0, 3, 2>(FXMVECTOR V) noexcept { return vrev64q_f32(V); } + + template<> inline XMVECTOR XM_CALLCONV XMVectorSwizzle<0, 1, 0, 1>(FXMVECTOR V) noexcept { float32x2_t vt = vget_low_f32(V); return vcombine_f32(vt, vt); } + template<> inline XMVECTOR XM_CALLCONV XMVectorSwizzle<2, 3, 2, 3>(FXMVECTOR V) noexcept { float32x2_t vt = vget_high_f32(V); return vcombine_f32(vt, vt); } + template<> inline XMVECTOR XM_CALLCONV XMVectorSwizzle<1, 0, 1, 0>(FXMVECTOR V) noexcept { float32x2_t vt = vrev64_f32(vget_low_f32(V)); return vcombine_f32(vt, vt); } + template<> inline XMVECTOR XM_CALLCONV XMVectorSwizzle<3, 2, 3, 2>(FXMVECTOR V) noexcept { float32x2_t vt = vrev64_f32(vget_high_f32(V)); return vcombine_f32(vt, vt); } + + template<> inline XMVECTOR XM_CALLCONV XMVectorSwizzle<0, 1, 3, 2>(FXMVECTOR V) noexcept { return vcombine_f32(vget_low_f32(V), vrev64_f32(vget_high_f32(V))); } + template<> inline XMVECTOR XM_CALLCONV XMVectorSwizzle<1, 0, 2, 3>(FXMVECTOR V) noexcept { return vcombine_f32(vrev64_f32(vget_low_f32(V)), vget_high_f32(V)); } + template<> inline XMVECTOR XM_CALLCONV XMVectorSwizzle<2, 3, 1, 0>(FXMVECTOR V) noexcept { return vcombine_f32(vget_high_f32(V), vrev64_f32(vget_low_f32(V))); } + template<> inline XMVECTOR XM_CALLCONV XMVectorSwizzle<3, 2, 0, 1>(FXMVECTOR V) noexcept { return vcombine_f32(vrev64_f32(vget_high_f32(V)), vget_low_f32(V)); } + template<> inline XMVECTOR XM_CALLCONV XMVectorSwizzle<3, 2, 1, 0>(FXMVECTOR V) noexcept { return vcombine_f32(vrev64_f32(vget_high_f32(V)), vrev64_f32(vget_low_f32(V))); } + + template<> inline XMVECTOR XM_CALLCONV XMVectorSwizzle<0, 0, 2, 2>(FXMVECTOR V) noexcept { return vtrnq_f32(V, V).val[0]; } + template<> inline XMVECTOR XM_CALLCONV XMVectorSwizzle<1, 1, 3, 3>(FXMVECTOR V) noexcept { return vtrnq_f32(V, V).val[1]; } + + template<> inline XMVECTOR XM_CALLCONV XMVectorSwizzle<0, 0, 1, 1>(FXMVECTOR V) noexcept { return vzipq_f32(V, V).val[0]; } + template<> inline XMVECTOR XM_CALLCONV XMVectorSwizzle<2, 2, 3, 3>(FXMVECTOR V) noexcept { return vzipq_f32(V, V).val[1]; } + + template<> inline XMVECTOR XM_CALLCONV XMVectorSwizzle<0, 2, 0, 2>(FXMVECTOR V) noexcept { return vuzpq_f32(V, V).val[0]; } + template<> inline XMVECTOR XM_CALLCONV XMVectorSwizzle<1, 3, 1, 3>(FXMVECTOR V) noexcept { return vuzpq_f32(V, V).val[1]; } + + template<> inline XMVECTOR XM_CALLCONV XMVectorSwizzle<1, 2, 3, 0>(FXMVECTOR V) noexcept { return vextq_f32(V, V, 1); } + template<> inline XMVECTOR XM_CALLCONV XMVectorSwizzle<2, 3, 0, 1>(FXMVECTOR V) noexcept { return vextq_f32(V, V, 2); } + template<> inline XMVECTOR XM_CALLCONV XMVectorSwizzle<3, 0, 1, 2>(FXMVECTOR V) noexcept { return vextq_f32(V, V, 3); } + +#endif // _XM_ARM_NEON_INTRINSICS_ && !_XM_NO_INTRINSICS_ + + //------------------------------------------------------------------------------ + + template + inline XMVECTOR XM_CALLCONV XMVectorShiftLeft(FXMVECTOR V1, FXMVECTOR V2) noexcept + { + static_assert(Elements < 4, "Elements template parameter out of range"); + return XMVectorPermute(V1, V2); + } + + template + inline XMVECTOR XM_CALLCONV XMVectorRotateLeft(FXMVECTOR V) noexcept + { + static_assert(Elements < 4, "Elements template parameter out of range"); + return XMVectorSwizzle(V); + } + + template + inline XMVECTOR XM_CALLCONV XMVectorRotateRight(FXMVECTOR V) noexcept + { + static_assert(Elements < 4, "Elements template parameter out of range"); + return XMVectorSwizzle<(4 - Elements) & 3, (5 - Elements) & 3, (6 - Elements) & 3, (7 - Elements) & 3>(V); + } + + template + inline XMVECTOR XM_CALLCONV XMVectorInsert(FXMVECTOR VD, FXMVECTOR VS) noexcept + { + XMVECTOR Control = XMVectorSelectControl(Select0 & 1, Select1 & 1, Select2 & 1, Select3 & 1); + return XMVectorSelect(VD, XMVectorRotateLeft(VS), Control); + } + + /**************************************************************************** + * + * Globals + * + ****************************************************************************/ + + // The purpose of the following global constants is to prevent redundant + // reloading of the constants when they are referenced by more than one + // separate inline math routine called within the same function. Declaring + // a constant locally within a routine is sufficient to prevent redundant + // reloads of that constant when that single routine is called multiple + // times in a function, but if the constant is used (and declared) in a + // separate math routine it would be reloaded. + +#ifndef XMGLOBALCONST +#if __cplusplus >= 201703L +#define XMGLOBALCONST inline constexpr +#elif defined(__GNUC__) && !defined(__MINGW32__) +#define XMGLOBALCONST extern const __attribute__((weak)) +#else +#define XMGLOBALCONST extern const __declspec(selectany) +#endif +#endif + + XMGLOBALCONST XMVECTORF32 g_XMSinCoefficients0 = { { { -0.16666667f, +0.0083333310f, -0.00019840874f, +2.7525562e-06f } } }; + XMGLOBALCONST XMVECTORF32 g_XMSinCoefficients1 = { { { -2.3889859e-08f, -0.16665852f /*Est1*/, +0.0083139502f /*Est2*/, -0.00018524670f /*Est3*/ } } }; + XMGLOBALCONST XMVECTORF32 g_XMCosCoefficients0 = { { { -0.5f, +0.041666638f, -0.0013888378f, +2.4760495e-05f } } }; + XMGLOBALCONST XMVECTORF32 g_XMCosCoefficients1 = { { { -2.6051615e-07f, -0.49992746f /*Est1*/, +0.041493919f /*Est2*/, -0.0012712436f /*Est3*/ } } }; + XMGLOBALCONST XMVECTORF32 g_XMTanCoefficients0 = { { { 1.0f, 0.333333333f, 0.133333333f, 5.396825397e-2f } } }; + XMGLOBALCONST XMVECTORF32 g_XMTanCoefficients1 = { { { 2.186948854e-2f, 8.863235530e-3f, 3.592128167e-3f, 1.455834485e-3f } } }; + XMGLOBALCONST XMVECTORF32 g_XMTanCoefficients2 = { { { 5.900274264e-4f, 2.391290764e-4f, 9.691537707e-5f, 3.927832950e-5f } } }; + XMGLOBALCONST XMVECTORF32 g_XMArcCoefficients0 = { { { +1.5707963050f, -0.2145988016f, +0.0889789874f, -0.0501743046f } } }; + XMGLOBALCONST XMVECTORF32 g_XMArcCoefficients1 = { { { +0.0308918810f, -0.0170881256f, +0.0066700901f, -0.0012624911f } } }; + XMGLOBALCONST XMVECTORF32 g_XMATanCoefficients0 = { { { -0.3333314528f, +0.1999355085f, -0.1420889944f, +0.1065626393f } } }; + XMGLOBALCONST XMVECTORF32 g_XMATanCoefficients1 = { { { -0.0752896400f, +0.0429096138f, -0.0161657367f, +0.0028662257f } } }; + XMGLOBALCONST XMVECTORF32 g_XMATanEstCoefficients0 = { { { +0.999866f, +0.999866f, +0.999866f, +0.999866f } } }; + XMGLOBALCONST XMVECTORF32 g_XMATanEstCoefficients1 = { { { -0.3302995f, +0.180141f, -0.085133f, +0.0208351f } } }; + XMGLOBALCONST XMVECTORF32 g_XMTanEstCoefficients = { { { 2.484f, -1.954923183e-1f, 2.467401101f, XM_1DIVPI } } }; + XMGLOBALCONST XMVECTORF32 g_XMArcEstCoefficients = { { { +1.5707288f, -0.2121144f, +0.0742610f, -0.0187293f } } }; + XMGLOBALCONST XMVECTORF32 g_XMPiConstants0 = { { { XM_PI, XM_2PI, XM_1DIVPI, XM_1DIV2PI } } }; + XMGLOBALCONST XMVECTORF32 g_XMIdentityR0 = { { { 1.0f, 0.0f, 0.0f, 0.0f } } }; + XMGLOBALCONST XMVECTORF32 g_XMIdentityR1 = { { { 0.0f, 1.0f, 0.0f, 0.0f } } }; + XMGLOBALCONST XMVECTORF32 g_XMIdentityR2 = { { { 0.0f, 0.0f, 1.0f, 0.0f } } }; + XMGLOBALCONST XMVECTORF32 g_XMIdentityR3 = { { { 0.0f, 0.0f, 0.0f, 1.0f } } }; + XMGLOBALCONST XMVECTORF32 g_XMNegIdentityR0 = { { { -1.0f, 0.0f, 0.0f, 0.0f } } }; + XMGLOBALCONST XMVECTORF32 g_XMNegIdentityR1 = { { { 0.0f, -1.0f, 0.0f, 0.0f } } }; + XMGLOBALCONST XMVECTORF32 g_XMNegIdentityR2 = { { { 0.0f, 0.0f, -1.0f, 0.0f } } }; + XMGLOBALCONST XMVECTORF32 g_XMNegIdentityR3 = { { { 0.0f, 0.0f, 0.0f, -1.0f } } }; + XMGLOBALCONST XMVECTORU32 g_XMNegativeZero = { { { 0x80000000, 0x80000000, 0x80000000, 0x80000000 } } }; + XMGLOBALCONST XMVECTORU32 g_XMNegate3 = { { { 0x80000000, 0x80000000, 0x80000000, 0x00000000 } } }; + XMGLOBALCONST XMVECTORU32 g_XMMaskXY = { { { 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, 0x00000000 } } }; + XMGLOBALCONST XMVECTORU32 g_XMMask3 = { { { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000 } } }; + XMGLOBALCONST XMVECTORU32 g_XMMaskX = { { { 0xFFFFFFFF, 0x00000000, 0x00000000, 0x00000000 } } }; + XMGLOBALCONST XMVECTORU32 g_XMMaskY = { { { 0x00000000, 0xFFFFFFFF, 0x00000000, 0x00000000 } } }; + XMGLOBALCONST XMVECTORU32 g_XMMaskZ = { { { 0x00000000, 0x00000000, 0xFFFFFFFF, 0x00000000 } } }; + XMGLOBALCONST XMVECTORU32 g_XMMaskW = { { { 0x00000000, 0x00000000, 0x00000000, 0xFFFFFFFF } } }; + XMGLOBALCONST XMVECTORF32 g_XMOne = { { { 1.0f, 1.0f, 1.0f, 1.0f } } }; + XMGLOBALCONST XMVECTORF32 g_XMOne3 = { { { 1.0f, 1.0f, 1.0f, 0.0f } } }; + XMGLOBALCONST XMVECTORF32 g_XMZero = { { { 0.0f, 0.0f, 0.0f, 0.0f } } }; + XMGLOBALCONST XMVECTORF32 g_XMTwo = { { { 2.f, 2.f, 2.f, 2.f } } }; + XMGLOBALCONST XMVECTORF32 g_XMFour = { { { 4.f, 4.f, 4.f, 4.f } } }; + XMGLOBALCONST XMVECTORF32 g_XMSix = { { { 6.f, 6.f, 6.f, 6.f } } }; + XMGLOBALCONST XMVECTORF32 g_XMNegativeOne = { { { -1.0f, -1.0f, -1.0f, -1.0f } } }; + XMGLOBALCONST XMVECTORF32 g_XMOneHalf = { { { 0.5f, 0.5f, 0.5f, 0.5f } } }; + XMGLOBALCONST XMVECTORF32 g_XMNegativeOneHalf = { { { -0.5f, -0.5f, -0.5f, -0.5f } } }; + XMGLOBALCONST XMVECTORF32 g_XMNegativeTwoPi = { { { -XM_2PI, -XM_2PI, -XM_2PI, -XM_2PI } } }; + XMGLOBALCONST XMVECTORF32 g_XMNegativePi = { { { -XM_PI, -XM_PI, -XM_PI, -XM_PI } } }; + XMGLOBALCONST XMVECTORF32 g_XMHalfPi = { { { XM_PIDIV2, XM_PIDIV2, XM_PIDIV2, XM_PIDIV2 } } }; + XMGLOBALCONST XMVECTORF32 g_XMPi = { { { XM_PI, XM_PI, XM_PI, XM_PI } } }; + XMGLOBALCONST XMVECTORF32 g_XMReciprocalPi = { { { XM_1DIVPI, XM_1DIVPI, XM_1DIVPI, XM_1DIVPI } } }; + XMGLOBALCONST XMVECTORF32 g_XMTwoPi = { { { XM_2PI, XM_2PI, XM_2PI, XM_2PI } } }; + XMGLOBALCONST XMVECTORF32 g_XMReciprocalTwoPi = { { { XM_1DIV2PI, XM_1DIV2PI, XM_1DIV2PI, XM_1DIV2PI } } }; + XMGLOBALCONST XMVECTORF32 g_XMEpsilon = { { { 1.192092896e-7f, 1.192092896e-7f, 1.192092896e-7f, 1.192092896e-7f } } }; + XMGLOBALCONST XMVECTORI32 g_XMInfinity = { { { 0x7F800000, 0x7F800000, 0x7F800000, 0x7F800000 } } }; + XMGLOBALCONST XMVECTORI32 g_XMQNaN = { { { 0x7FC00000, 0x7FC00000, 0x7FC00000, 0x7FC00000 } } }; + XMGLOBALCONST XMVECTORI32 g_XMQNaNTest = { { { 0x007FFFFF, 0x007FFFFF, 0x007FFFFF, 0x007FFFFF } } }; + XMGLOBALCONST XMVECTORI32 g_XMAbsMask = { { { 0x7FFFFFFF, 0x7FFFFFFF, 0x7FFFFFFF, 0x7FFFFFFF } } }; + XMGLOBALCONST XMVECTORI32 g_XMFltMin = { { { 0x00800000, 0x00800000, 0x00800000, 0x00800000 } } }; + XMGLOBALCONST XMVECTORI32 g_XMFltMax = { { { 0x7F7FFFFF, 0x7F7FFFFF, 0x7F7FFFFF, 0x7F7FFFFF } } }; + XMGLOBALCONST XMVECTORU32 g_XMNegOneMask = { { { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF } } }; + XMGLOBALCONST XMVECTORU32 g_XMMaskA8R8G8B8 = { { { 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000 } } }; + XMGLOBALCONST XMVECTORU32 g_XMFlipA8R8G8B8 = { { { 0x00000000, 0x00000000, 0x00000000, 0x80000000 } } }; + XMGLOBALCONST XMVECTORF32 g_XMFixAA8R8G8B8 = { { { 0.0f, 0.0f, 0.0f, float(0x80000000U) } } }; + XMGLOBALCONST XMVECTORF32 g_XMNormalizeA8R8G8B8 = { { { 1.0f / (255.0f * float(0x10000)), 1.0f / (255.0f * float(0x100)), 1.0f / 255.0f, 1.0f / (255.0f * float(0x1000000)) } } }; + XMGLOBALCONST XMVECTORU32 g_XMMaskA2B10G10R10 = { { { 0x000003FF, 0x000FFC00, 0x3FF00000, 0xC0000000 } } }; + XMGLOBALCONST XMVECTORU32 g_XMFlipA2B10G10R10 = { { { 0x00000200, 0x00080000, 0x20000000, 0x80000000 } } }; + XMGLOBALCONST XMVECTORF32 g_XMFixAA2B10G10R10 = { { { -512.0f, -512.0f * float(0x400), -512.0f * float(0x100000), float(0x80000000U) } } }; + XMGLOBALCONST XMVECTORF32 g_XMNormalizeA2B10G10R10 = { { { 1.0f / 511.0f, 1.0f / (511.0f * float(0x400)), 1.0f / (511.0f * float(0x100000)), 1.0f / (3.0f * float(0x40000000)) } } }; + XMGLOBALCONST XMVECTORU32 g_XMMaskX16Y16 = { { { 0x0000FFFF, 0xFFFF0000, 0x00000000, 0x00000000 } } }; + XMGLOBALCONST XMVECTORI32 g_XMFlipX16Y16 = { { { 0x00008000, 0x00000000, 0x00000000, 0x00000000 } } }; + XMGLOBALCONST XMVECTORF32 g_XMFixX16Y16 = { { { -32768.0f, 0.0f, 0.0f, 0.0f } } }; + XMGLOBALCONST XMVECTORF32 g_XMNormalizeX16Y16 = { { { 1.0f / 32767.0f, 1.0f / (32767.0f * 65536.0f), 0.0f, 0.0f } } }; + XMGLOBALCONST XMVECTORU32 g_XMMaskX16Y16Z16W16 = { { { 0x0000FFFF, 0x0000FFFF, 0xFFFF0000, 0xFFFF0000 } } }; + XMGLOBALCONST XMVECTORI32 g_XMFlipX16Y16Z16W16 = { { { 0x00008000, 0x00008000, 0x00000000, 0x00000000 } } }; + XMGLOBALCONST XMVECTORF32 g_XMFixX16Y16Z16W16 = { { { -32768.0f, -32768.0f, 0.0f, 0.0f } } }; + XMGLOBALCONST XMVECTORF32 g_XMNormalizeX16Y16Z16W16 = { { { 1.0f / 32767.0f, 1.0f / 32767.0f, 1.0f / (32767.0f * 65536.0f), 1.0f / (32767.0f * 65536.0f) } } }; + XMGLOBALCONST XMVECTORF32 g_XMNoFraction = { { { 8388608.0f, 8388608.0f, 8388608.0f, 8388608.0f } } }; + XMGLOBALCONST XMVECTORI32 g_XMMaskByte = { { { 0x000000FF, 0x000000FF, 0x000000FF, 0x000000FF } } }; + XMGLOBALCONST XMVECTORF32 g_XMNegateX = { { { -1.0f, 1.0f, 1.0f, 1.0f } } }; + XMGLOBALCONST XMVECTORF32 g_XMNegateY = { { { 1.0f, -1.0f, 1.0f, 1.0f } } }; + XMGLOBALCONST XMVECTORF32 g_XMNegateZ = { { { 1.0f, 1.0f, -1.0f, 1.0f } } }; + XMGLOBALCONST XMVECTORF32 g_XMNegateW = { { { 1.0f, 1.0f, 1.0f, -1.0f } } }; + XMGLOBALCONST XMVECTORU32 g_XMSelect0101 = { { { XM_SELECT_0, XM_SELECT_1, XM_SELECT_0, XM_SELECT_1 } } }; + XMGLOBALCONST XMVECTORU32 g_XMSelect1010 = { { { XM_SELECT_1, XM_SELECT_0, XM_SELECT_1, XM_SELECT_0 } } }; + XMGLOBALCONST XMVECTORI32 g_XMOneHalfMinusEpsilon = { { { 0x3EFFFFFD, 0x3EFFFFFD, 0x3EFFFFFD, 0x3EFFFFFD } } }; + XMGLOBALCONST XMVECTORU32 g_XMSelect1000 = { { { XM_SELECT_1, XM_SELECT_0, XM_SELECT_0, XM_SELECT_0 } } }; + XMGLOBALCONST XMVECTORU32 g_XMSelect1100 = { { { XM_SELECT_1, XM_SELECT_1, XM_SELECT_0, XM_SELECT_0 } } }; + XMGLOBALCONST XMVECTORU32 g_XMSelect1110 = { { { XM_SELECT_1, XM_SELECT_1, XM_SELECT_1, XM_SELECT_0 } } }; + XMGLOBALCONST XMVECTORU32 g_XMSelect1011 = { { { XM_SELECT_1, XM_SELECT_0, XM_SELECT_1, XM_SELECT_1 } } }; + XMGLOBALCONST XMVECTORF32 g_XMFixupY16 = { { { 1.0f, 1.0f / 65536.0f, 0.0f, 0.0f } } }; + XMGLOBALCONST XMVECTORF32 g_XMFixupY16W16 = { { { 1.0f, 1.0f, 1.0f / 65536.0f, 1.0f / 65536.0f } } }; + XMGLOBALCONST XMVECTORU32 g_XMFlipY = { { { 0, 0x80000000, 0, 0 } } }; + XMGLOBALCONST XMVECTORU32 g_XMFlipZ = { { { 0, 0, 0x80000000, 0 } } }; + XMGLOBALCONST XMVECTORU32 g_XMFlipW = { { { 0, 0, 0, 0x80000000 } } }; + XMGLOBALCONST XMVECTORU32 g_XMFlipYZ = { { { 0, 0x80000000, 0x80000000, 0 } } }; + XMGLOBALCONST XMVECTORU32 g_XMFlipZW = { { { 0, 0, 0x80000000, 0x80000000 } } }; + XMGLOBALCONST XMVECTORU32 g_XMFlipYW = { { { 0, 0x80000000, 0, 0x80000000 } } }; + XMGLOBALCONST XMVECTORI32 g_XMMaskDec4 = { { { 0x3FF, 0x3FF << 10, 0x3FF << 20, static_cast(0xC0000000) } } }; + XMGLOBALCONST XMVECTORI32 g_XMXorDec4 = { { { 0x200, 0x200 << 10, 0x200 << 20, 0 } } }; + XMGLOBALCONST XMVECTORF32 g_XMAddUDec4 = { { { 0, 0, 0, 32768.0f * 65536.0f } } }; + XMGLOBALCONST XMVECTORF32 g_XMAddDec4 = { { { -512.0f, -512.0f * 1024.0f, -512.0f * 1024.0f * 1024.0f, 0 } } }; + XMGLOBALCONST XMVECTORF32 g_XMMulDec4 = { { { 1.0f, 1.0f / 1024.0f, 1.0f / (1024.0f * 1024.0f), 1.0f / (1024.0f * 1024.0f * 1024.0f) } } }; + XMGLOBALCONST XMVECTORU32 g_XMMaskByte4 = { { { 0xFF, 0xFF00, 0xFF0000, 0xFF000000 } } }; + XMGLOBALCONST XMVECTORI32 g_XMXorByte4 = { { { 0x80, 0x8000, 0x800000, 0x00000000 } } }; + XMGLOBALCONST XMVECTORF32 g_XMAddByte4 = { { { -128.0f, -128.0f * 256.0f, -128.0f * 65536.0f, 0 } } }; + XMGLOBALCONST XMVECTORF32 g_XMFixUnsigned = { { { 32768.0f * 65536.0f, 32768.0f * 65536.0f, 32768.0f * 65536.0f, 32768.0f * 65536.0f } } }; + XMGLOBALCONST XMVECTORF32 g_XMMaxInt = { { { 65536.0f * 32768.0f - 128.0f, 65536.0f * 32768.0f - 128.0f, 65536.0f * 32768.0f - 128.0f, 65536.0f * 32768.0f - 128.0f } } }; + XMGLOBALCONST XMVECTORF32 g_XMMaxUInt = { { { 65536.0f * 65536.0f - 256.0f, 65536.0f * 65536.0f - 256.0f, 65536.0f * 65536.0f - 256.0f, 65536.0f * 65536.0f - 256.0f } } }; + XMGLOBALCONST XMVECTORF32 g_XMUnsignedFix = { { { 32768.0f * 65536.0f, 32768.0f * 65536.0f, 32768.0f * 65536.0f, 32768.0f * 65536.0f } } }; + XMGLOBALCONST XMVECTORF32 g_XMsrgbScale = { { { 12.92f, 12.92f, 12.92f, 1.0f } } }; + XMGLOBALCONST XMVECTORF32 g_XMsrgbA = { { { 0.055f, 0.055f, 0.055f, 0.0f } } }; + XMGLOBALCONST XMVECTORF32 g_XMsrgbA1 = { { { 1.055f, 1.055f, 1.055f, 1.0f } } }; + XMGLOBALCONST XMVECTORI32 g_XMExponentBias = { { { 127, 127, 127, 127 } } }; + XMGLOBALCONST XMVECTORI32 g_XMSubnormalExponent = { { { -126, -126, -126, -126 } } }; + XMGLOBALCONST XMVECTORI32 g_XMNumTrailing = { { { 23, 23, 23, 23 } } }; + XMGLOBALCONST XMVECTORI32 g_XMMinNormal = { { { 0x00800000, 0x00800000, 0x00800000, 0x00800000 } } }; + XMGLOBALCONST XMVECTORU32 g_XMNegInfinity = { { { 0xFF800000, 0xFF800000, 0xFF800000, 0xFF800000 } } }; + XMGLOBALCONST XMVECTORU32 g_XMNegQNaN = { { { 0xFFC00000, 0xFFC00000, 0xFFC00000, 0xFFC00000 } } }; + XMGLOBALCONST XMVECTORI32 g_XMBin128 = { { { 0x43000000, 0x43000000, 0x43000000, 0x43000000 } } }; + XMGLOBALCONST XMVECTORU32 g_XMBinNeg150 = { { { 0xC3160000, 0xC3160000, 0xC3160000, 0xC3160000 } } }; + XMGLOBALCONST XMVECTORI32 g_XM253 = { { { 253, 253, 253, 253 } } }; + XMGLOBALCONST XMVECTORF32 g_XMExpEst1 = { { { -6.93147182e-1f, -6.93147182e-1f, -6.93147182e-1f, -6.93147182e-1f } } }; + XMGLOBALCONST XMVECTORF32 g_XMExpEst2 = { { { +2.40226462e-1f, +2.40226462e-1f, +2.40226462e-1f, +2.40226462e-1f } } }; + XMGLOBALCONST XMVECTORF32 g_XMExpEst3 = { { { -5.55036440e-2f, -5.55036440e-2f, -5.55036440e-2f, -5.55036440e-2f } } }; + XMGLOBALCONST XMVECTORF32 g_XMExpEst4 = { { { +9.61597636e-3f, +9.61597636e-3f, +9.61597636e-3f, +9.61597636e-3f } } }; + XMGLOBALCONST XMVECTORF32 g_XMExpEst5 = { { { -1.32823968e-3f, -1.32823968e-3f, -1.32823968e-3f, -1.32823968e-3f } } }; + XMGLOBALCONST XMVECTORF32 g_XMExpEst6 = { { { +1.47491097e-4f, +1.47491097e-4f, +1.47491097e-4f, +1.47491097e-4f } } }; + XMGLOBALCONST XMVECTORF32 g_XMExpEst7 = { { { -1.08635004e-5f, -1.08635004e-5f, -1.08635004e-5f, -1.08635004e-5f } } }; + XMGLOBALCONST XMVECTORF32 g_XMLogEst0 = { { { +1.442693f, +1.442693f, +1.442693f, +1.442693f } } }; + XMGLOBALCONST XMVECTORF32 g_XMLogEst1 = { { { -0.721242f, -0.721242f, -0.721242f, -0.721242f } } }; + XMGLOBALCONST XMVECTORF32 g_XMLogEst2 = { { { +0.479384f, +0.479384f, +0.479384f, +0.479384f } } }; + XMGLOBALCONST XMVECTORF32 g_XMLogEst3 = { { { -0.350295f, -0.350295f, -0.350295f, -0.350295f } } }; + XMGLOBALCONST XMVECTORF32 g_XMLogEst4 = { { { +0.248590f, +0.248590f, +0.248590f, +0.248590f } } }; + XMGLOBALCONST XMVECTORF32 g_XMLogEst5 = { { { -0.145700f, -0.145700f, -0.145700f, -0.145700f } } }; + XMGLOBALCONST XMVECTORF32 g_XMLogEst6 = { { { +0.057148f, +0.057148f, +0.057148f, +0.057148f } } }; + XMGLOBALCONST XMVECTORF32 g_XMLogEst7 = { { { -0.010578f, -0.010578f, -0.010578f, -0.010578f } } }; + XMGLOBALCONST XMVECTORF32 g_XMLgE = { { { +1.442695f, +1.442695f, +1.442695f, +1.442695f } } }; + XMGLOBALCONST XMVECTORF32 g_XMInvLgE = { { { +6.93147182e-1f, +6.93147182e-1f, +6.93147182e-1f, +6.93147182e-1f } } }; + XMGLOBALCONST XMVECTORF32 g_XMLg10 = { { { +3.321928f, +3.321928f, +3.321928f, +3.321928f } } }; + XMGLOBALCONST XMVECTORF32 g_XMInvLg10 = { { { +3.010299956e-1f, +3.010299956e-1f, +3.010299956e-1f, +3.010299956e-1f } } }; + XMGLOBALCONST XMVECTORF32 g_UByteMax = { { { 255.0f, 255.0f, 255.0f, 255.0f } } }; + XMGLOBALCONST XMVECTORF32 g_ByteMin = { { { -127.0f, -127.0f, -127.0f, -127.0f } } }; + XMGLOBALCONST XMVECTORF32 g_ByteMax = { { { 127.0f, 127.0f, 127.0f, 127.0f } } }; + XMGLOBALCONST XMVECTORF32 g_ShortMin = { { { -32767.0f, -32767.0f, -32767.0f, -32767.0f } } }; + XMGLOBALCONST XMVECTORF32 g_ShortMax = { { { 32767.0f, 32767.0f, 32767.0f, 32767.0f } } }; + XMGLOBALCONST XMVECTORF32 g_UShortMax = { { { 65535.0f, 65535.0f, 65535.0f, 65535.0f } } }; + + /**************************************************************************** + * + * Implementation + * + ****************************************************************************/ + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4068 4214 4204 4365 4616 4640 6001 6101) + // C4068/4616: ignore unknown pragmas + // C4214/4204: nonstandard extension used + // C4365/4640: Off by default noise + // C6001/6101: False positives +#endif + +#ifdef _PREFAST_ +#pragma prefast(push) +#pragma prefast(disable : 25000, "FXMVECTOR is 16 bytes") +#pragma prefast(disable : 26495, "Union initialization confuses /analyze") +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wfloat-equal" +#pragma clang diagnostic ignored "-Wundefined-reinterpret-cast" +#pragma clang diagnostic ignored "-Wunknown-warning-option" +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" +#endif + +//------------------------------------------------------------------------------ + + inline XMVECTOR XM_CALLCONV XMVectorSetBinaryConstant(uint32_t C0, uint32_t C1, uint32_t C2, uint32_t C3) noexcept + { + #if defined(_XM_NO_INTRINSICS_) + XMVECTORU32 vResult; + vResult.u[0] = (0 - (C0 & 1)) & 0x3F800000; + vResult.u[1] = (0 - (C1 & 1)) & 0x3F800000; + vResult.u[2] = (0 - (C2 & 1)) & 0x3F800000; + vResult.u[3] = (0 - (C3 & 1)) & 0x3F800000; + return vResult.v; + #elif defined(_XM_ARM_NEON_INTRINSICS_) + XMVECTORU32 vResult; + vResult.u[0] = (0 - (C0 & 1)) & 0x3F800000; + vResult.u[1] = (0 - (C1 & 1)) & 0x3F800000; + vResult.u[2] = (0 - (C2 & 1)) & 0x3F800000; + vResult.u[3] = (0 - (C3 & 1)) & 0x3F800000; + return vResult.v; + #else // XM_SSE_INTRINSICS_ + static const XMVECTORU32 g_vMask1 = { { { 1, 1, 1, 1 } } }; + // Move the parms to a vector + __m128i vTemp = _mm_set_epi32(static_cast(C3), static_cast(C2), static_cast(C1), static_cast(C0)); + // Mask off the low bits + vTemp = _mm_and_si128(vTemp, g_vMask1); + // 0xFFFFFFFF on true bits + vTemp = _mm_cmpeq_epi32(vTemp, g_vMask1); + // 0xFFFFFFFF -> 1.0f, 0x00000000 -> 0.0f + vTemp = _mm_and_si128(vTemp, g_XMOne); + return _mm_castsi128_ps(vTemp); + #endif + } + + //------------------------------------------------------------------------------ + + inline XMVECTOR XM_CALLCONV XMVectorSplatConstant(int32_t IntConstant, uint32_t DivExponent) noexcept + { + assert(IntConstant >= -16 && IntConstant <= 15); + assert(DivExponent < 32); + #if defined(_XM_NO_INTRINSICS_) + + using DirectX::XMConvertVectorIntToFloat; + + XMVECTORI32 V = { { { IntConstant, IntConstant, IntConstant, IntConstant } } }; + return XMConvertVectorIntToFloat(V.v, DivExponent); + + #elif defined(_XM_ARM_NEON_INTRINSICS_) + // Splat the int + int32x4_t vScale = vdupq_n_s32(IntConstant); + // Convert to a float + XMVECTOR vResult = vcvtq_f32_s32(vScale); + // Convert DivExponent into 1.0f/(1<(&vScale)[0]); + return vResult; + #else // XM_SSE_INTRINSICS_ + // Splat the int + __m128i vScale = _mm_set1_epi32(IntConstant); + // Convert to a float + XMVECTOR vResult = _mm_cvtepi32_ps(vScale); + // Convert DivExponent into 1.0f/(1<(uScale)); + // Multiply by the reciprocal (Perform a right shift by DivExponent) + vResult = _mm_mul_ps(vResult, _mm_castsi128_ps(vScale)); + return vResult; + #endif + } + + //------------------------------------------------------------------------------ + + inline XMVECTOR XM_CALLCONV XMVectorSplatConstantInt(int32_t IntConstant) noexcept + { + assert(IntConstant >= -16 && IntConstant <= 15); + #if defined(_XM_NO_INTRINSICS_) + + XMVECTORI32 V = { { { IntConstant, IntConstant, IntConstant, IntConstant } } }; + return V.v; + + #elif defined(_XM_ARM_NEON_INTRINSICS_) + int32x4_t V = vdupq_n_s32(IntConstant); + return reinterpret_cast(&V)[0]; + #else // XM_SSE_INTRINSICS_ + __m128i V = _mm_set1_epi32(IntConstant); + return _mm_castsi128_ps(V); + #endif + } + +#include "DirectXMathConvert.inl" +#include "DirectXMathVector.inl" +#include "DirectXMathMatrix.inl" +#include "DirectXMathMisc.inl" + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +#ifdef _PREFAST_ +#pragma prefast(pop) +#endif +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +} // namespace DirectX + diff --git a/Minecraft.Client/Windows64/Libs/4JLibs/impls/Windows_Libs/Render/vendor/DirectXMath/DirectXMathConvert.inl b/Minecraft.Client/Windows64/Libs/4JLibs/impls/Windows_Libs/Render/vendor/DirectXMath/DirectXMathConvert.inl new file mode 100644 index 00000000..e3a6bdc6 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/4JLibs/impls/Windows_Libs/Render/vendor/DirectXMath/DirectXMathConvert.inl @@ -0,0 +1,2219 @@ +//------------------------------------------------------------------------------------- +// DirectXMathConvert.inl -- SIMD C++ Math library +// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +// +// https://go.microsoft.com/fwlink/?LinkID=615560 +//------------------------------------------------------------------------------------- + +#pragma once + +/**************************************************************************** + * + * Data conversion + * + ****************************************************************************/ + +//------------------------------------------------------------------------------ + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4701) +// C4701: false positives +#endif + +inline XMVECTOR XM_CALLCONV XMConvertVectorIntToFloat +( + FXMVECTOR VInt, + uint32_t DivExponent +) noexcept +{ + assert(DivExponent < 32); +#if defined(_XM_NO_INTRINSICS_) + float fScale = 1.0f / static_cast(1U << DivExponent); + uint32_t ElementIndex = 0; + XMVECTOR Result; + do + { + auto iTemp = static_cast(VInt.vector4_u32[ElementIndex]); + Result.vector4_f32[ElementIndex] = static_cast(iTemp)* fScale; + } + while (++ElementIndex < 4); + return Result; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float fScale = 1.0f / static_cast(1U << DivExponent); + float32x4_t vResult = vcvtq_f32_s32(vreinterpretq_s32_f32(VInt)); + return vmulq_n_f32(vResult, fScale); +#else // _XM_SSE_INTRINSICS_ + // Convert to floats + XMVECTOR vResult = _mm_cvtepi32_ps(_mm_castps_si128(VInt)); + // Convert DivExponent into 1.0f/(1<(uScale)); + vResult = _mm_mul_ps(vResult, _mm_castsi128_ps(vScale)); + return vResult; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMConvertVectorFloatToInt +( + FXMVECTOR VFloat, + uint32_t MulExponent +) noexcept +{ + assert(MulExponent < 32); +#if defined(_XM_NO_INTRINSICS_) + // Get the scalar factor. + auto fScale = static_cast(1U << MulExponent); + uint32_t ElementIndex = 0; + XMVECTOR Result; + do + { + int32_t iResult; + float fTemp = VFloat.vector4_f32[ElementIndex] * fScale; + if (fTemp <= -(65536.0f * 32768.0f)) + { + iResult = (-0x7FFFFFFF) - 1; + } + else if (fTemp > (65536.0f * 32768.0f) - 128.0f) + { + iResult = 0x7FFFFFFF; + } + else + { + iResult = static_cast(fTemp); + } + Result.vector4_u32[ElementIndex] = static_cast(iResult); + } + while (++ElementIndex < 4); + return Result; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x4_t vResult = vmulq_n_f32(VFloat, static_cast(1U << MulExponent)); + // In case of positive overflow, detect it + uint32x4_t vOverflow = vcgtq_f32(vResult, g_XMMaxInt); + // Float to int conversion + int32x4_t vResulti = vcvtq_s32_f32(vResult); + // If there was positive overflow, set to 0x7FFFFFFF + vResult = vreinterpretq_f32_u32(vandq_u32(vOverflow, g_XMAbsMask)); + vOverflow = vbicq_u32(vreinterpretq_u32_s32(vResulti), vOverflow); + vOverflow = vorrq_u32(vOverflow, vreinterpretq_u32_f32(vResult)); + return vreinterpretq_f32_u32(vOverflow); +#else // _XM_SSE_INTRINSICS_ + XMVECTOR vResult = _mm_set_ps1(static_cast(1U << MulExponent)); + vResult = _mm_mul_ps(vResult, VFloat); + // In case of positive overflow, detect it + XMVECTOR vOverflow = _mm_cmpgt_ps(vResult, g_XMMaxInt); + // Float to int conversion + __m128i vResulti = _mm_cvttps_epi32(vResult); + // If there was positive overflow, set to 0x7FFFFFFF + vResult = _mm_and_ps(vOverflow, g_XMAbsMask); + vOverflow = _mm_andnot_ps(vOverflow, _mm_castsi128_ps(vResulti)); + vOverflow = _mm_or_ps(vOverflow, vResult); + return vOverflow; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMConvertVectorUIntToFloat +( + FXMVECTOR VUInt, + uint32_t DivExponent +) noexcept +{ + assert(DivExponent < 32); +#if defined(_XM_NO_INTRINSICS_) + float fScale = 1.0f / static_cast(1U << DivExponent); + uint32_t ElementIndex = 0; + XMVECTOR Result; + do + { + Result.vector4_f32[ElementIndex] = static_cast(VUInt.vector4_u32[ElementIndex])* fScale; + } + while (++ElementIndex < 4); + return Result; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float fScale = 1.0f / static_cast(1U << DivExponent); + float32x4_t vResult = vcvtq_f32_u32(vreinterpretq_u32_f32(VUInt)); + return vmulq_n_f32(vResult, fScale); +#else // _XM_SSE_INTRINSICS_ + // For the values that are higher than 0x7FFFFFFF, a fixup is needed + // Determine which ones need the fix. + XMVECTOR vMask = _mm_and_ps(VUInt, g_XMNegativeZero); + // Force all values positive + XMVECTOR vResult = _mm_xor_ps(VUInt, vMask); + // Convert to floats + vResult = _mm_cvtepi32_ps(_mm_castps_si128(vResult)); + // Convert 0x80000000 -> 0xFFFFFFFF + __m128i iMask = _mm_srai_epi32(_mm_castps_si128(vMask), 31); + // For only the ones that are too big, add the fixup + vMask = _mm_and_ps(_mm_castsi128_ps(iMask), g_XMFixUnsigned); + vResult = _mm_add_ps(vResult, vMask); + // Convert DivExponent into 1.0f/(1<(uScale)); + vResult = _mm_mul_ps(vResult, _mm_castsi128_ps(iMask)); + return vResult; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMConvertVectorFloatToUInt +( + FXMVECTOR VFloat, + uint32_t MulExponent +) noexcept +{ + assert(MulExponent < 32); +#if defined(_XM_NO_INTRINSICS_) + // Get the scalar factor. + auto fScale = static_cast(1U << MulExponent); + uint32_t ElementIndex = 0; + XMVECTOR Result; + do + { + uint32_t uResult; + float fTemp = VFloat.vector4_f32[ElementIndex] * fScale; + if (fTemp <= 0.0f) + { + uResult = 0; + } + else if (fTemp >= (65536.0f * 65536.0f)) + { + uResult = 0xFFFFFFFFU; + } + else + { + uResult = static_cast(fTemp); + } + Result.vector4_u32[ElementIndex] = uResult; + } + while (++ElementIndex < 4); + return Result; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x4_t vResult = vmulq_n_f32(VFloat, static_cast(1U << MulExponent)); + // In case of overflow, detect it + uint32x4_t vOverflow = vcgtq_f32(vResult, g_XMMaxUInt); + // Float to int conversion + uint32x4_t vResulti = vcvtq_u32_f32(vResult); + // If there was overflow, set to 0xFFFFFFFFU + vResult = vreinterpretq_f32_u32(vbicq_u32(vResulti, vOverflow)); + vOverflow = vorrq_u32(vOverflow, vreinterpretq_u32_f32(vResult)); + return vreinterpretq_f32_u32(vOverflow); +#else // _XM_SSE_INTRINSICS_ + XMVECTOR vResult = _mm_set_ps1(static_cast(1U << MulExponent)); + vResult = _mm_mul_ps(vResult, VFloat); + // Clamp to >=0 + vResult = _mm_max_ps(vResult, g_XMZero); + // Any numbers that are too big, set to 0xFFFFFFFFU + XMVECTOR vOverflow = _mm_cmpgt_ps(vResult, g_XMMaxUInt); + XMVECTOR vValue = g_XMUnsignedFix; + // Too large for a signed integer? + XMVECTOR vMask = _mm_cmpge_ps(vResult, vValue); + // Zero for number's lower than 0x80000000, 32768.0f*65536.0f otherwise + vValue = _mm_and_ps(vValue, vMask); + // Perform fixup only on numbers too large (Keeps low bit precision) + vResult = _mm_sub_ps(vResult, vValue); + __m128i vResulti = _mm_cvttps_epi32(vResult); + // Convert from signed to unsigned pnly if greater than 0x80000000 + vMask = _mm_and_ps(vMask, g_XMNegativeZero); + vResult = _mm_xor_ps(_mm_castsi128_ps(vResulti), vMask); + // On those that are too large, set to 0xFFFFFFFF + vResult = _mm_or_ps(vResult, vOverflow); + return vResult; +#endif +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +/**************************************************************************** + * + * Vector and matrix load operations + * + ****************************************************************************/ + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMLoadInt(const uint32_t* pSource) noexcept +{ + assert(pSource); +#if defined(_XM_NO_INTRINSICS_) + XMVECTOR V; + V.vector4_u32[0] = *pSource; + V.vector4_u32[1] = 0; + V.vector4_u32[2] = 0; + V.vector4_u32[3] = 0; + return V; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x4_t zero = vdupq_n_u32(0); + return vreinterpretq_f32_u32(vld1q_lane_u32(pSource, zero, 0)); +#elif defined(_XM_SSE_INTRINSICS_) + return _mm_load_ss(reinterpret_cast(pSource)); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMLoadFloat(const float* pSource) noexcept +{ + assert(pSource); +#if defined(_XM_NO_INTRINSICS_) + XMVECTOR V; + V.vector4_f32[0] = *pSource; + V.vector4_f32[1] = 0.f; + V.vector4_f32[2] = 0.f; + V.vector4_f32[3] = 0.f; + return V; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x4_t zero = vdupq_n_f32(0); + return vld1q_lane_f32(pSource, zero, 0); +#elif defined(_XM_SSE_INTRINSICS_) + return _mm_load_ss(pSource); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMLoadInt2(const uint32_t* pSource) noexcept +{ + assert(pSource); +#if defined(_XM_NO_INTRINSICS_) + XMVECTOR V; + V.vector4_u32[0] = pSource[0]; + V.vector4_u32[1] = pSource[1]; + V.vector4_u32[2] = 0; + V.vector4_u32[3] = 0; + return V; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x2_t x = vld1_u32(pSource); + uint32x2_t zero = vdup_n_u32(0); + return vreinterpretq_f32_u32(vcombine_u32(x, zero)); +#elif defined(_XM_SSE_INTRINSICS_) + return _mm_castpd_ps(_mm_load_sd(reinterpret_cast(pSource))); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMLoadInt2A(const uint32_t* pSource) noexcept +{ + assert(pSource); + assert((reinterpret_cast(pSource) & 0xF) == 0); +#if defined(_XM_NO_INTRINSICS_) + XMVECTOR V; + V.vector4_u32[0] = pSource[0]; + V.vector4_u32[1] = pSource[1]; + V.vector4_u32[2] = 0; + V.vector4_u32[3] = 0; + return V; +#elif defined(_XM_ARM_NEON_INTRINSICS_) +#if defined(_MSC_VER) && !defined(__clang__) && !defined(_ARM64_DISTINCT_NEON_TYPES) + uint32x2_t x = vld1_u32_ex(pSource, 64); +#else + uint32x2_t x = vld1_u32(pSource); +#endif + uint32x2_t zero = vdup_n_u32(0); + return vreinterpretq_f32_u32(vcombine_u32(x, zero)); +#elif defined(_XM_SSE_INTRINSICS_) + return _mm_castpd_ps(_mm_load_sd(reinterpret_cast(pSource))); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMLoadFloat2(const XMFLOAT2* pSource) noexcept +{ + assert(pSource); +#if defined(_XM_NO_INTRINSICS_) + XMVECTOR V; + V.vector4_f32[0] = pSource->x; + V.vector4_f32[1] = pSource->y; + V.vector4_f32[2] = 0.f; + V.vector4_f32[3] = 0.f; + return V; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x2_t x = vld1_f32(reinterpret_cast(pSource)); + float32x2_t zero = vdup_n_f32(0); + return vcombine_f32(x, zero); +#elif defined(_XM_SSE_INTRINSICS_) + return _mm_castpd_ps(_mm_load_sd(reinterpret_cast(pSource))); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMLoadFloat2A(const XMFLOAT2A* pSource) noexcept +{ + assert(pSource); + assert((reinterpret_cast(pSource) & 0xF) == 0); +#if defined(_XM_NO_INTRINSICS_) + XMVECTOR V; + V.vector4_f32[0] = pSource->x; + V.vector4_f32[1] = pSource->y; + V.vector4_f32[2] = 0.f; + V.vector4_f32[3] = 0.f; + return V; +#elif defined(_XM_ARM_NEON_INTRINSICS_) +#if defined(_MSC_VER) && !defined(__clang__) && !defined(_ARM64_DISTINCT_NEON_TYPES) + float32x2_t x = vld1_f32_ex(reinterpret_cast(pSource), 64); +#else + float32x2_t x = vld1_f32(reinterpret_cast(pSource)); +#endif + float32x2_t zero = vdup_n_f32(0); + return vcombine_f32(x, zero); +#elif defined(_XM_SSE_INTRINSICS_) + return _mm_castpd_ps(_mm_load_sd(reinterpret_cast(pSource))); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMLoadSInt2(const XMINT2* pSource) noexcept +{ + assert(pSource); +#if defined(_XM_NO_INTRINSICS_) + XMVECTOR V; + V.vector4_f32[0] = static_cast(pSource->x); + V.vector4_f32[1] = static_cast(pSource->y); + V.vector4_f32[2] = 0.f; + V.vector4_f32[3] = 0.f; + return V; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + int32x2_t x = vld1_s32(reinterpret_cast(pSource)); + float32x2_t v = vcvt_f32_s32(x); + float32x2_t zero = vdup_n_f32(0); + return vcombine_f32(v, zero); +#elif defined(_XM_SSE_INTRINSICS_) + __m128 V = _mm_castpd_ps(_mm_load_sd(reinterpret_cast(pSource))); + return _mm_cvtepi32_ps(_mm_castps_si128(V)); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMLoadUInt2(const XMUINT2* pSource) noexcept +{ + assert(pSource); +#if defined(_XM_NO_INTRINSICS_) + XMVECTOR V; + V.vector4_f32[0] = static_cast(pSource->x); + V.vector4_f32[1] = static_cast(pSource->y); + V.vector4_f32[2] = 0.f; + V.vector4_f32[3] = 0.f; + return V; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x2_t x = vld1_u32(reinterpret_cast(pSource)); + float32x2_t v = vcvt_f32_u32(x); + float32x2_t zero = vdup_n_f32(0); + return vcombine_f32(v, zero); +#elif defined(_XM_SSE_INTRINSICS_) + __m128 V = _mm_castpd_ps(_mm_load_sd(reinterpret_cast(pSource))); + // For the values that are higher than 0x7FFFFFFF, a fixup is needed + // Determine which ones need the fix. + XMVECTOR vMask = _mm_and_ps(V, g_XMNegativeZero); + // Force all values positive + XMVECTOR vResult = _mm_xor_ps(V, vMask); + // Convert to floats + vResult = _mm_cvtepi32_ps(_mm_castps_si128(vResult)); + // Convert 0x80000000 -> 0xFFFFFFFF + __m128i iMask = _mm_srai_epi32(_mm_castps_si128(vMask), 31); + // For only the ones that are too big, add the fixup + vMask = _mm_and_ps(_mm_castsi128_ps(iMask), g_XMFixUnsigned); + vResult = _mm_add_ps(vResult, vMask); + return vResult; +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMLoadInt3(const uint32_t* pSource) noexcept +{ + assert(pSource); +#if defined(_XM_NO_INTRINSICS_) + XMVECTOR V; + V.vector4_u32[0] = pSource[0]; + V.vector4_u32[1] = pSource[1]; + V.vector4_u32[2] = pSource[2]; + V.vector4_u32[3] = 0; + return V; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x2_t x = vld1_u32(pSource); + uint32x2_t zero = vdup_n_u32(0); + uint32x2_t y = vld1_lane_u32(pSource + 2, zero, 0); + return vreinterpretq_f32_u32(vcombine_u32(x, y)); +#elif defined(_XM_SSE4_INTRINSICS_) + __m128 xy = _mm_castpd_ps(_mm_load_sd(reinterpret_cast(pSource))); + __m128 z = _mm_load_ss(reinterpret_cast(pSource + 2)); + return _mm_insert_ps(xy, z, 0x20); +#elif defined(_XM_SSE_INTRINSICS_) + __m128 xy = _mm_castpd_ps(_mm_load_sd(reinterpret_cast(pSource))); + __m128 z = _mm_load_ss(reinterpret_cast(pSource + 2)); + return _mm_movelh_ps(xy, z); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMLoadInt3A(const uint32_t* pSource) noexcept +{ + assert(pSource); + assert((reinterpret_cast(pSource) & 0xF) == 0); +#if defined(_XM_NO_INTRINSICS_) + XMVECTOR V; + V.vector4_u32[0] = pSource[0]; + V.vector4_u32[1] = pSource[1]; + V.vector4_u32[2] = pSource[2]; + V.vector4_u32[3] = 0; + return V; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + // Reads an extra integer which is zero'd +#if defined(_MSC_VER) && !defined(__clang__) && !defined(_ARM64_DISTINCT_NEON_TYPES) + uint32x4_t V = vld1q_u32_ex(pSource, 128); +#else + uint32x4_t V = vld1q_u32(pSource); +#endif + return vreinterpretq_f32_u32(vsetq_lane_u32(0, V, 3)); +#elif defined(_XM_SSE4_INTRINSICS_) + __m128 xy = _mm_castpd_ps(_mm_load_sd(reinterpret_cast(pSource))); + __m128 z = _mm_load_ss(reinterpret_cast(pSource + 2)); + return _mm_insert_ps(xy, z, 0x20); +#elif defined(_XM_SSE_INTRINSICS_) + __m128 xy = _mm_castpd_ps(_mm_load_sd(reinterpret_cast(pSource))); + __m128 z = _mm_load_ss(reinterpret_cast(pSource + 2)); + return _mm_movelh_ps(xy, z); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMLoadFloat3(const XMFLOAT3* pSource) noexcept +{ + assert(pSource); +#if defined(_XM_NO_INTRINSICS_) + XMVECTOR V; + V.vector4_f32[0] = pSource->x; + V.vector4_f32[1] = pSource->y; + V.vector4_f32[2] = pSource->z; + V.vector4_f32[3] = 0.f; + return V; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x2_t x = vld1_f32(reinterpret_cast(pSource)); + float32x2_t zero = vdup_n_f32(0); + float32x2_t y = vld1_lane_f32(reinterpret_cast(pSource) + 2, zero, 0); + return vcombine_f32(x, y); +#elif defined(_XM_SSE4_INTRINSICS_) + __m128 xy = _mm_castpd_ps(_mm_load_sd(reinterpret_cast(pSource))); + __m128 z = _mm_load_ss(&pSource->z); + return _mm_insert_ps(xy, z, 0x20); +#elif defined(_XM_SSE_INTRINSICS_) + __m128 xy = _mm_castpd_ps(_mm_load_sd(reinterpret_cast(pSource))); + __m128 z = _mm_load_ss(&pSource->z); + return _mm_movelh_ps(xy, z); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMLoadFloat3A(const XMFLOAT3A* pSource) noexcept +{ + assert(pSource); + assert((reinterpret_cast(pSource) & 0xF) == 0); +#if defined(_XM_NO_INTRINSICS_) + XMVECTOR V; + V.vector4_f32[0] = pSource->x; + V.vector4_f32[1] = pSource->y; + V.vector4_f32[2] = pSource->z; + V.vector4_f32[3] = 0.f; + return V; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + // Reads an extra float which is zero'd +#if defined(_MSC_VER) && !defined(__clang__) && !defined(_ARM64_DISTINCT_NEON_TYPES) + float32x4_t V = vld1q_f32_ex(reinterpret_cast(pSource), 128); +#else + float32x4_t V = vld1q_f32(reinterpret_cast(pSource)); +#endif + return vsetq_lane_f32(0, V, 3); +#elif defined(_XM_SSE4_INTRINSICS_) + // Reads an extra float which is zero'd + __m128 V = _mm_load_ps(&pSource->x); + return _mm_blend_ps(_mm_setzero_ps(), V, 0x7); +#elif defined(_XM_SSE_INTRINSICS_) + // Reads an extra float which is zero'd + __m128 V = _mm_load_ps(&pSource->x); + return _mm_and_ps(V, g_XMMask3); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMLoadSInt3(const XMINT3* pSource) noexcept +{ + assert(pSource); +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR V; + V.vector4_f32[0] = static_cast(pSource->x); + V.vector4_f32[1] = static_cast(pSource->y); + V.vector4_f32[2] = static_cast(pSource->z); + V.vector4_f32[3] = 0.f; + return V; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + int32x2_t x = vld1_s32(reinterpret_cast(pSource)); + int32x2_t zero = vdup_n_s32(0); + int32x2_t y = vld1_lane_s32(reinterpret_cast(pSource) + 2, zero, 0); + int32x4_t v = vcombine_s32(x, y); + return vcvtq_f32_s32(v); +#elif defined(_XM_SSE_INTRINSICS_) + __m128 xy = _mm_castpd_ps(_mm_load_sd(reinterpret_cast(pSource))); + __m128 z = _mm_load_ss(reinterpret_cast(&pSource->z)); + __m128 V = _mm_movelh_ps(xy, z); + return _mm_cvtepi32_ps(_mm_castps_si128(V)); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMLoadUInt3(const XMUINT3* pSource) noexcept +{ + assert(pSource); +#if defined(_XM_NO_INTRINSICS_) + XMVECTOR V; + V.vector4_f32[0] = static_cast(pSource->x); + V.vector4_f32[1] = static_cast(pSource->y); + V.vector4_f32[2] = static_cast(pSource->z); + V.vector4_f32[3] = 0.f; + return V; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x2_t x = vld1_u32(reinterpret_cast(pSource)); + uint32x2_t zero = vdup_n_u32(0); + uint32x2_t y = vld1_lane_u32(reinterpret_cast(pSource) + 2, zero, 0); + uint32x4_t v = vcombine_u32(x, y); + return vcvtq_f32_u32(v); +#elif defined(_XM_SSE_INTRINSICS_) + __m128 xy = _mm_castpd_ps(_mm_load_sd(reinterpret_cast(pSource))); + __m128 z = _mm_load_ss(reinterpret_cast(&pSource->z)); + __m128 V = _mm_movelh_ps(xy, z); + // For the values that are higher than 0x7FFFFFFF, a fixup is needed + // Determine which ones need the fix. + XMVECTOR vMask = _mm_and_ps(V, g_XMNegativeZero); + // Force all values positive + XMVECTOR vResult = _mm_xor_ps(V, vMask); + // Convert to floats + vResult = _mm_cvtepi32_ps(_mm_castps_si128(vResult)); + // Convert 0x80000000 -> 0xFFFFFFFF + __m128i iMask = _mm_srai_epi32(_mm_castps_si128(vMask), 31); + // For only the ones that are too big, add the fixup + vMask = _mm_and_ps(_mm_castsi128_ps(iMask), g_XMFixUnsigned); + vResult = _mm_add_ps(vResult, vMask); + return vResult; +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMLoadInt4(const uint32_t* pSource) noexcept +{ + assert(pSource); + +#if defined(_XM_NO_INTRINSICS_) + XMVECTOR V; + V.vector4_u32[0] = pSource[0]; + V.vector4_u32[1] = pSource[1]; + V.vector4_u32[2] = pSource[2]; + V.vector4_u32[3] = pSource[3]; + return V; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vreinterpretq_f32_u32(vld1q_u32(pSource)); +#elif defined(_XM_SSE_INTRINSICS_) + __m128i V = _mm_loadu_si128(reinterpret_cast(pSource)); + return _mm_castsi128_ps(V); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMLoadInt4A(const uint32_t* pSource) noexcept +{ + assert(pSource); + assert((reinterpret_cast(pSource) & 0xF) == 0); +#if defined(_XM_NO_INTRINSICS_) + XMVECTOR V; + V.vector4_u32[0] = pSource[0]; + V.vector4_u32[1] = pSource[1]; + V.vector4_u32[2] = pSource[2]; + V.vector4_u32[3] = pSource[3]; + return V; +#elif defined(_XM_ARM_NEON_INTRINSICS_) +#if defined(_MSC_VER) && !defined(__clang__) && !defined(_ARM64_DISTINCT_NEON_TYPES) + return vld1q_u32_ex(pSource, 128); +#else + return vreinterpretq_f32_u32(vld1q_u32(pSource)); +#endif +#elif defined(_XM_SSE_INTRINSICS_) + __m128i V = _mm_load_si128(reinterpret_cast(pSource)); + return _mm_castsi128_ps(V); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMLoadFloat4(const XMFLOAT4* pSource) noexcept +{ + assert(pSource); +#if defined(_XM_NO_INTRINSICS_) + XMVECTOR V; + V.vector4_f32[0] = pSource->x; + V.vector4_f32[1] = pSource->y; + V.vector4_f32[2] = pSource->z; + V.vector4_f32[3] = pSource->w; + return V; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vld1q_f32(reinterpret_cast(pSource)); +#elif defined(_XM_SSE_INTRINSICS_) + return _mm_loadu_ps(&pSource->x); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMLoadFloat4A(const XMFLOAT4A* pSource) noexcept +{ + assert(pSource); + assert((reinterpret_cast(pSource) & 0xF) == 0); +#if defined(_XM_NO_INTRINSICS_) + XMVECTOR V; + V.vector4_f32[0] = pSource->x; + V.vector4_f32[1] = pSource->y; + V.vector4_f32[2] = pSource->z; + V.vector4_f32[3] = pSource->w; + return V; +#elif defined(_XM_ARM_NEON_INTRINSICS_) +#if defined(_MSC_VER) && !defined(__clang__) && !defined(_ARM64_DISTINCT_NEON_TYPES) + return vld1q_f32_ex(reinterpret_cast(pSource), 128); +#else + return vld1q_f32(reinterpret_cast(pSource)); +#endif +#elif defined(_XM_SSE_INTRINSICS_) + return _mm_load_ps(&pSource->x); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMLoadSInt4(const XMINT4* pSource) noexcept +{ + assert(pSource); +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR V; + V.vector4_f32[0] = static_cast(pSource->x); + V.vector4_f32[1] = static_cast(pSource->y); + V.vector4_f32[2] = static_cast(pSource->z); + V.vector4_f32[3] = static_cast(pSource->w); + return V; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + int32x4_t v = vld1q_s32(reinterpret_cast(pSource)); + return vcvtq_f32_s32(v); +#elif defined(_XM_SSE_INTRINSICS_) + __m128i V = _mm_loadu_si128(reinterpret_cast(pSource)); + return _mm_cvtepi32_ps(V); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMLoadUInt4(const XMUINT4* pSource) noexcept +{ + assert(pSource); +#if defined(_XM_NO_INTRINSICS_) + XMVECTOR V; + V.vector4_f32[0] = static_cast(pSource->x); + V.vector4_f32[1] = static_cast(pSource->y); + V.vector4_f32[2] = static_cast(pSource->z); + V.vector4_f32[3] = static_cast(pSource->w); + return V; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x4_t v = vld1q_u32(reinterpret_cast(pSource)); + return vcvtq_f32_u32(v); +#elif defined(_XM_SSE_INTRINSICS_) + __m128i V = _mm_loadu_si128(reinterpret_cast(pSource)); + // For the values that are higher than 0x7FFFFFFF, a fixup is needed + // Determine which ones need the fix. + XMVECTOR vMask = _mm_and_ps(_mm_castsi128_ps(V), g_XMNegativeZero); + // Force all values positive + XMVECTOR vResult = _mm_xor_ps(_mm_castsi128_ps(V), vMask); + // Convert to floats + vResult = _mm_cvtepi32_ps(_mm_castps_si128(vResult)); + // Convert 0x80000000 -> 0xFFFFFFFF + __m128i iMask = _mm_srai_epi32(_mm_castps_si128(vMask), 31); + // For only the ones that are too big, add the fixup + vMask = _mm_and_ps(_mm_castsi128_ps(iMask), g_XMFixUnsigned); + vResult = _mm_add_ps(vResult, vMask); + return vResult; +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMMATRIX XM_CALLCONV XMLoadFloat3x3(const XMFLOAT3X3* pSource) noexcept +{ + assert(pSource); +#if defined(_XM_NO_INTRINSICS_) + + XMMATRIX M; + M.r[0].vector4_f32[0] = pSource->m[0][0]; + M.r[0].vector4_f32[1] = pSource->m[0][1]; + M.r[0].vector4_f32[2] = pSource->m[0][2]; + M.r[0].vector4_f32[3] = 0.0f; + + M.r[1].vector4_f32[0] = pSource->m[1][0]; + M.r[1].vector4_f32[1] = pSource->m[1][1]; + M.r[1].vector4_f32[2] = pSource->m[1][2]; + M.r[1].vector4_f32[3] = 0.0f; + + M.r[2].vector4_f32[0] = pSource->m[2][0]; + M.r[2].vector4_f32[1] = pSource->m[2][1]; + M.r[2].vector4_f32[2] = pSource->m[2][2]; + M.r[2].vector4_f32[3] = 0.0f; + M.r[3].vector4_f32[0] = 0.0f; + M.r[3].vector4_f32[1] = 0.0f; + M.r[3].vector4_f32[2] = 0.0f; + M.r[3].vector4_f32[3] = 1.0f; + return M; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x4_t v0 = vld1q_f32(&pSource->m[0][0]); + float32x4_t v1 = vld1q_f32(&pSource->m[1][1]); + float32x2_t v2 = vcreate_f32(static_cast(*reinterpret_cast(&pSource->m[2][2]))); + float32x4_t T = vextq_f32(v0, v1, 3); + + XMMATRIX M; + M.r[0] = vreinterpretq_f32_u32(vandq_u32(vreinterpretq_u32_f32(v0), g_XMMask3)); + M.r[1] = vreinterpretq_f32_u32(vandq_u32(vreinterpretq_u32_f32(T), g_XMMask3)); + M.r[2] = vcombine_f32(vget_high_f32(v1), v2); + M.r[3] = g_XMIdentityR3; + return M; +#elif defined(_XM_SSE_INTRINSICS_) + __m128 Z = _mm_setzero_ps(); + + __m128 V1 = _mm_loadu_ps(&pSource->m[0][0]); + __m128 V2 = _mm_loadu_ps(&pSource->m[1][1]); + __m128 V3 = _mm_load_ss(&pSource->m[2][2]); + + __m128 T1 = _mm_unpackhi_ps(V1, Z); + __m128 T2 = _mm_unpacklo_ps(V2, Z); + __m128 T3 = _mm_shuffle_ps(V3, T2, _MM_SHUFFLE(0, 1, 0, 0)); + __m128 T4 = _mm_movehl_ps(T2, T3); + __m128 T5 = _mm_movehl_ps(Z, T1); + + XMMATRIX M; + M.r[0] = _mm_movelh_ps(V1, T1); + M.r[1] = _mm_add_ps(T4, T5); + M.r[2] = _mm_shuffle_ps(V2, V3, _MM_SHUFFLE(1, 0, 3, 2)); + M.r[3] = g_XMIdentityR3; + return M; +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMMATRIX XM_CALLCONV XMLoadFloat4x3(const XMFLOAT4X3* pSource) noexcept +{ + assert(pSource); +#if defined(_XM_NO_INTRINSICS_) + + XMMATRIX M; + M.r[0].vector4_f32[0] = pSource->m[0][0]; + M.r[0].vector4_f32[1] = pSource->m[0][1]; + M.r[0].vector4_f32[2] = pSource->m[0][2]; + M.r[0].vector4_f32[3] = 0.0f; + + M.r[1].vector4_f32[0] = pSource->m[1][0]; + M.r[1].vector4_f32[1] = pSource->m[1][1]; + M.r[1].vector4_f32[2] = pSource->m[1][2]; + M.r[1].vector4_f32[3] = 0.0f; + + M.r[2].vector4_f32[0] = pSource->m[2][0]; + M.r[2].vector4_f32[1] = pSource->m[2][1]; + M.r[2].vector4_f32[2] = pSource->m[2][2]; + M.r[2].vector4_f32[3] = 0.0f; + + M.r[3].vector4_f32[0] = pSource->m[3][0]; + M.r[3].vector4_f32[1] = pSource->m[3][1]; + M.r[3].vector4_f32[2] = pSource->m[3][2]; + M.r[3].vector4_f32[3] = 1.0f; + return M; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x4_t v0 = vld1q_f32(&pSource->m[0][0]); + float32x4_t v1 = vld1q_f32(&pSource->m[1][1]); + float32x4_t v2 = vld1q_f32(&pSource->m[2][2]); + + float32x4_t T1 = vextq_f32(v0, v1, 3); + float32x4_t T2 = vcombine_f32(vget_high_f32(v1), vget_low_f32(v2)); + float32x4_t T3 = vextq_f32(v2, v2, 1); + + XMMATRIX M; + M.r[0] = vreinterpretq_f32_u32(vandq_u32(vreinterpretq_u32_f32(v0), g_XMMask3)); + M.r[1] = vreinterpretq_f32_u32(vandq_u32(vreinterpretq_u32_f32(T1), g_XMMask3)); + M.r[2] = vreinterpretq_f32_u32(vandq_u32(vreinterpretq_u32_f32(T2), g_XMMask3)); + M.r[3] = vsetq_lane_f32(1.f, T3, 3); + return M; +#elif defined(_XM_SSE_INTRINSICS_) + // Use unaligned load instructions to + // load the 12 floats + // vTemp1 = x1,y1,z1,x2 + XMVECTOR vTemp1 = _mm_loadu_ps(&pSource->m[0][0]); + // vTemp2 = y2,z2,x3,y3 + XMVECTOR vTemp2 = _mm_loadu_ps(&pSource->m[1][1]); + // vTemp4 = z3,x4,y4,z4 + XMVECTOR vTemp4 = _mm_loadu_ps(&pSource->m[2][2]); + // vTemp3 = x3,y3,z3,z3 + XMVECTOR vTemp3 = _mm_shuffle_ps(vTemp2, vTemp4, _MM_SHUFFLE(0, 0, 3, 2)); + // vTemp2 = y2,z2,x2,x2 + vTemp2 = _mm_shuffle_ps(vTemp2, vTemp1, _MM_SHUFFLE(3, 3, 1, 0)); + // vTemp2 = x2,y2,z2,z2 + vTemp2 = XM_PERMUTE_PS(vTemp2, _MM_SHUFFLE(1, 1, 0, 2)); + // vTemp1 = x1,y1,z1,0 + // vTemp2 = x2,y2,z2,0 + // vTemp3 = x3,y3,z3,0 +#ifdef _XM_SSE4_INTRINSICS_ + XMVECTOR zero = _mm_setzero_ps(); + vTemp1 = _mm_blend_ps(zero, vTemp1, 0x7); + vTemp2 = _mm_blend_ps(zero, vTemp2, 0x7); + vTemp3 = _mm_blend_ps(zero, vTemp3, 0x7); +#else + vTemp1 = _mm_and_ps(vTemp1, g_XMMask3); + vTemp2 = _mm_and_ps(vTemp2, g_XMMask3); + vTemp3 = _mm_and_ps(vTemp3, g_XMMask3); +#endif + // vTemp4i = x4,y4,z4,0 + __m128i vTemp4i = _mm_srli_si128(_mm_castps_si128(vTemp4), 32 / 8); + // vTemp4i = x4,y4,z4,1.0f + vTemp4i = _mm_or_si128(vTemp4i, g_XMIdentityR3); + XMMATRIX M(vTemp1, + vTemp2, + vTemp3, + _mm_castsi128_ps(vTemp4i)); + return M; +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMMATRIX XM_CALLCONV XMLoadFloat4x3A(const XMFLOAT4X3A* pSource) noexcept +{ + assert(pSource); + assert((reinterpret_cast(pSource) & 0xF) == 0); +#if defined(_XM_NO_INTRINSICS_) + + XMMATRIX M; + M.r[0].vector4_f32[0] = pSource->m[0][0]; + M.r[0].vector4_f32[1] = pSource->m[0][1]; + M.r[0].vector4_f32[2] = pSource->m[0][2]; + M.r[0].vector4_f32[3] = 0.0f; + + M.r[1].vector4_f32[0] = pSource->m[1][0]; + M.r[1].vector4_f32[1] = pSource->m[1][1]; + M.r[1].vector4_f32[2] = pSource->m[1][2]; + M.r[1].vector4_f32[3] = 0.0f; + + M.r[2].vector4_f32[0] = pSource->m[2][0]; + M.r[2].vector4_f32[1] = pSource->m[2][1]; + M.r[2].vector4_f32[2] = pSource->m[2][2]; + M.r[2].vector4_f32[3] = 0.0f; + + M.r[3].vector4_f32[0] = pSource->m[3][0]; + M.r[3].vector4_f32[1] = pSource->m[3][1]; + M.r[3].vector4_f32[2] = pSource->m[3][2]; + M.r[3].vector4_f32[3] = 1.0f; + return M; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) +#if defined(_MSC_VER) && !defined(__clang__) && !defined(_ARM64_DISTINCT_NEON_TYPES) + float32x4_t v0 = vld1q_f32_ex(&pSource->m[0][0], 128); + float32x4_t v1 = vld1q_f32_ex(&pSource->m[1][1], 128); + float32x4_t v2 = vld1q_f32_ex(&pSource->m[2][2], 128); +#else + float32x4_t v0 = vld1q_f32(&pSource->m[0][0]); + float32x4_t v1 = vld1q_f32(&pSource->m[1][1]); + float32x4_t v2 = vld1q_f32(&pSource->m[2][2]); +#endif + + float32x4_t T1 = vextq_f32(v0, v1, 3); + float32x4_t T2 = vcombine_f32(vget_high_f32(v1), vget_low_f32(v2)); + float32x4_t T3 = vextq_f32(v2, v2, 1); + + XMMATRIX M; + M.r[0] = vreinterpretq_f32_u32(vandq_u32(vreinterpretq_u32_f32(v0), g_XMMask3)); + M.r[1] = vreinterpretq_f32_u32(vandq_u32(vreinterpretq_u32_f32(T1), g_XMMask3)); + M.r[2] = vreinterpretq_f32_u32(vandq_u32(vreinterpretq_u32_f32(T2), g_XMMask3)); + M.r[3] = vsetq_lane_f32(1.f, T3, 3); + return M; +#elif defined(_XM_SSE_INTRINSICS_) + // Use aligned load instructions to + // load the 12 floats + // vTemp1 = x1,y1,z1,x2 + XMVECTOR vTemp1 = _mm_load_ps(&pSource->m[0][0]); + // vTemp2 = y2,z2,x3,y3 + XMVECTOR vTemp2 = _mm_load_ps(&pSource->m[1][1]); + // vTemp4 = z3,x4,y4,z4 + XMVECTOR vTemp4 = _mm_load_ps(&pSource->m[2][2]); + // vTemp3 = x3,y3,z3,z3 + XMVECTOR vTemp3 = _mm_shuffle_ps(vTemp2, vTemp4, _MM_SHUFFLE(0, 0, 3, 2)); + // vTemp2 = y2,z2,x2,x2 + vTemp2 = _mm_shuffle_ps(vTemp2, vTemp1, _MM_SHUFFLE(3, 3, 1, 0)); + // vTemp2 = x2,y2,z2,z2 + vTemp2 = XM_PERMUTE_PS(vTemp2, _MM_SHUFFLE(1, 1, 0, 2)); + // vTemp1 = x1,y1,z1,0 + // vTemp2 = x2,y2,z2,0 + // vTemp3 = x3,y3,z3,0 +#ifdef _XM_SSE4_INTRINSICS_ + XMVECTOR zero = _mm_setzero_ps(); + vTemp1 = _mm_blend_ps(zero, vTemp1, 0x7); + vTemp2 = _mm_blend_ps(zero, vTemp2, 0x7); + vTemp3 = _mm_blend_ps(zero, vTemp3, 0x7); +#else + vTemp1 = _mm_and_ps(vTemp1, g_XMMask3); + vTemp2 = _mm_and_ps(vTemp2, g_XMMask3); + vTemp3 = _mm_and_ps(vTemp3, g_XMMask3); +#endif + // vTemp4i = x4,y4,z4,0 + __m128i vTemp4i = _mm_srli_si128(_mm_castps_si128(vTemp4), 32 / 8); + // vTemp4i = x4,y4,z4,1.0f + vTemp4i = _mm_or_si128(vTemp4i, g_XMIdentityR3); + XMMATRIX M(vTemp1, + vTemp2, + vTemp3, + _mm_castsi128_ps(vTemp4i)); + return M; +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMMATRIX XM_CALLCONV XMLoadFloat3x4(const XMFLOAT3X4* pSource) noexcept +{ + assert(pSource); +#if defined(_XM_NO_INTRINSICS_) + + XMMATRIX M; + M.r[0].vector4_f32[0] = pSource->m[0][0]; + M.r[0].vector4_f32[1] = pSource->m[1][0]; + M.r[0].vector4_f32[2] = pSource->m[2][0]; + M.r[0].vector4_f32[3] = 0.0f; + + M.r[1].vector4_f32[0] = pSource->m[0][1]; + M.r[1].vector4_f32[1] = pSource->m[1][1]; + M.r[1].vector4_f32[2] = pSource->m[2][1]; + M.r[1].vector4_f32[3] = 0.0f; + + M.r[2].vector4_f32[0] = pSource->m[0][2]; + M.r[2].vector4_f32[1] = pSource->m[1][2]; + M.r[2].vector4_f32[2] = pSource->m[2][2]; + M.r[2].vector4_f32[3] = 0.0f; + + M.r[3].vector4_f32[0] = pSource->m[0][3]; + M.r[3].vector4_f32[1] = pSource->m[1][3]; + M.r[3].vector4_f32[2] = pSource->m[2][3]; + M.r[3].vector4_f32[3] = 1.0f; + return M; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x2x4_t vTemp0 = vld4_f32(&pSource->_11); + float32x4_t vTemp1 = vld1q_f32(&pSource->_31); + + float32x2_t l = vget_low_f32(vTemp1); + float32x4_t T0 = vcombine_f32(vTemp0.val[0], l); + float32x2_t rl = vrev64_f32(l); + float32x4_t T1 = vcombine_f32(vTemp0.val[1], rl); + + float32x2_t h = vget_high_f32(vTemp1); + float32x4_t T2 = vcombine_f32(vTemp0.val[2], h); + float32x2_t rh = vrev64_f32(h); + float32x4_t T3 = vcombine_f32(vTemp0.val[3], rh); + + XMMATRIX M = {}; + M.r[0] = vreinterpretq_f32_u32(vandq_u32(vreinterpretq_u32_f32(T0), g_XMMask3)); + M.r[1] = vreinterpretq_f32_u32(vandq_u32(vreinterpretq_u32_f32(T1), g_XMMask3)); + M.r[2] = vreinterpretq_f32_u32(vandq_u32(vreinterpretq_u32_f32(T2), g_XMMask3)); + M.r[3] = vsetq_lane_f32(1.f, T3, 3); + return M; +#elif defined(_XM_SSE_INTRINSICS_) + XMMATRIX M; + M.r[0] = _mm_loadu_ps(&pSource->_11); + M.r[1] = _mm_loadu_ps(&pSource->_21); + M.r[2] = _mm_loadu_ps(&pSource->_31); + M.r[3] = g_XMIdentityR3; + + // x.x,x.y,y.x,y.y + XMVECTOR vTemp1 = _mm_shuffle_ps(M.r[0], M.r[1], _MM_SHUFFLE(1, 0, 1, 0)); + // x.z,x.w,y.z,y.w + XMVECTOR vTemp3 = _mm_shuffle_ps(M.r[0], M.r[1], _MM_SHUFFLE(3, 2, 3, 2)); + // z.x,z.y,w.x,w.y + XMVECTOR vTemp2 = _mm_shuffle_ps(M.r[2], M.r[3], _MM_SHUFFLE(1, 0, 1, 0)); + // z.z,z.w,w.z,w.w + XMVECTOR vTemp4 = _mm_shuffle_ps(M.r[2], M.r[3], _MM_SHUFFLE(3, 2, 3, 2)); + XMMATRIX mResult; + + // x.x,y.x,z.x,w.x + mResult.r[0] = _mm_shuffle_ps(vTemp1, vTemp2, _MM_SHUFFLE(2, 0, 2, 0)); + // x.y,y.y,z.y,w.y + mResult.r[1] = _mm_shuffle_ps(vTemp1, vTemp2, _MM_SHUFFLE(3, 1, 3, 1)); + // x.z,y.z,z.z,w.z + mResult.r[2] = _mm_shuffle_ps(vTemp3, vTemp4, _MM_SHUFFLE(2, 0, 2, 0)); + // x.w,y.w,z.w,w.w + mResult.r[3] = _mm_shuffle_ps(vTemp3, vTemp4, _MM_SHUFFLE(3, 1, 3, 1)); + return mResult; +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMMATRIX XM_CALLCONV XMLoadFloat3x4A(const XMFLOAT3X4A* pSource) noexcept +{ + assert(pSource); + assert((reinterpret_cast(pSource) & 0xF) == 0); +#if defined(_XM_NO_INTRINSICS_) + + XMMATRIX M; + M.r[0].vector4_f32[0] = pSource->m[0][0]; + M.r[0].vector4_f32[1] = pSource->m[1][0]; + M.r[0].vector4_f32[2] = pSource->m[2][0]; + M.r[0].vector4_f32[3] = 0.0f; + + M.r[1].vector4_f32[0] = pSource->m[0][1]; + M.r[1].vector4_f32[1] = pSource->m[1][1]; + M.r[1].vector4_f32[2] = pSource->m[2][1]; + M.r[1].vector4_f32[3] = 0.0f; + + M.r[2].vector4_f32[0] = pSource->m[0][2]; + M.r[2].vector4_f32[1] = pSource->m[1][2]; + M.r[2].vector4_f32[2] = pSource->m[2][2]; + M.r[2].vector4_f32[3] = 0.0f; + + M.r[3].vector4_f32[0] = pSource->m[0][3]; + M.r[3].vector4_f32[1] = pSource->m[1][3]; + M.r[3].vector4_f32[2] = pSource->m[2][3]; + M.r[3].vector4_f32[3] = 1.0f; + return M; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) +#if defined(_MSC_VER) && !defined(__clang__) && !defined(_ARM64_DISTINCT_NEON_TYPES) + float32x2x4_t vTemp0 = vld4_f32_ex(&pSource->_11, 128); + float32x4_t vTemp1 = vld1q_f32_ex(&pSource->_31, 128); +#else + float32x2x4_t vTemp0 = vld4_f32(&pSource->_11); + float32x4_t vTemp1 = vld1q_f32(&pSource->_31); +#endif + + float32x2_t l = vget_low_f32(vTemp1); + float32x4_t T0 = vcombine_f32(vTemp0.val[0], l); + float32x2_t rl = vrev64_f32(l); + float32x4_t T1 = vcombine_f32(vTemp0.val[1], rl); + + float32x2_t h = vget_high_f32(vTemp1); + float32x4_t T2 = vcombine_f32(vTemp0.val[2], h); + float32x2_t rh = vrev64_f32(h); + float32x4_t T3 = vcombine_f32(vTemp0.val[3], rh); + + XMMATRIX M = {}; + M.r[0] = vreinterpretq_f32_u32(vandq_u32(vreinterpretq_u32_f32(T0), g_XMMask3)); + M.r[1] = vreinterpretq_f32_u32(vandq_u32(vreinterpretq_u32_f32(T1), g_XMMask3)); + M.r[2] = vreinterpretq_f32_u32(vandq_u32(vreinterpretq_u32_f32(T2), g_XMMask3)); + M.r[3] = vsetq_lane_f32(1.f, T3, 3); + return M; +#elif defined(_XM_SSE_INTRINSICS_) + XMMATRIX M; + M.r[0] = _mm_load_ps(&pSource->_11); + M.r[1] = _mm_load_ps(&pSource->_21); + M.r[2] = _mm_load_ps(&pSource->_31); + M.r[3] = g_XMIdentityR3; + + // x.x,x.y,y.x,y.y + XMVECTOR vTemp1 = _mm_shuffle_ps(M.r[0], M.r[1], _MM_SHUFFLE(1, 0, 1, 0)); + // x.z,x.w,y.z,y.w + XMVECTOR vTemp3 = _mm_shuffle_ps(M.r[0], M.r[1], _MM_SHUFFLE(3, 2, 3, 2)); + // z.x,z.y,w.x,w.y + XMVECTOR vTemp2 = _mm_shuffle_ps(M.r[2], M.r[3], _MM_SHUFFLE(1, 0, 1, 0)); + // z.z,z.w,w.z,w.w + XMVECTOR vTemp4 = _mm_shuffle_ps(M.r[2], M.r[3], _MM_SHUFFLE(3, 2, 3, 2)); + XMMATRIX mResult; + + // x.x,y.x,z.x,w.x + mResult.r[0] = _mm_shuffle_ps(vTemp1, vTemp2, _MM_SHUFFLE(2, 0, 2, 0)); + // x.y,y.y,z.y,w.y + mResult.r[1] = _mm_shuffle_ps(vTemp1, vTemp2, _MM_SHUFFLE(3, 1, 3, 1)); + // x.z,y.z,z.z,w.z + mResult.r[2] = _mm_shuffle_ps(vTemp3, vTemp4, _MM_SHUFFLE(2, 0, 2, 0)); + // x.w,y.w,z.w,w.w + mResult.r[3] = _mm_shuffle_ps(vTemp3, vTemp4, _MM_SHUFFLE(3, 1, 3, 1)); + return mResult; +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMMATRIX XM_CALLCONV XMLoadFloat4x4(const XMFLOAT4X4* pSource) noexcept +{ + assert(pSource); +#if defined(_XM_NO_INTRINSICS_) + + XMMATRIX M; + M.r[0].vector4_f32[0] = pSource->m[0][0]; + M.r[0].vector4_f32[1] = pSource->m[0][1]; + M.r[0].vector4_f32[2] = pSource->m[0][2]; + M.r[0].vector4_f32[3] = pSource->m[0][3]; + + M.r[1].vector4_f32[0] = pSource->m[1][0]; + M.r[1].vector4_f32[1] = pSource->m[1][1]; + M.r[1].vector4_f32[2] = pSource->m[1][2]; + M.r[1].vector4_f32[3] = pSource->m[1][3]; + + M.r[2].vector4_f32[0] = pSource->m[2][0]; + M.r[2].vector4_f32[1] = pSource->m[2][1]; + M.r[2].vector4_f32[2] = pSource->m[2][2]; + M.r[2].vector4_f32[3] = pSource->m[2][3]; + + M.r[3].vector4_f32[0] = pSource->m[3][0]; + M.r[3].vector4_f32[1] = pSource->m[3][1]; + M.r[3].vector4_f32[2] = pSource->m[3][2]; + M.r[3].vector4_f32[3] = pSource->m[3][3]; + return M; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + XMMATRIX M; + M.r[0] = vld1q_f32(reinterpret_cast(&pSource->_11)); + M.r[1] = vld1q_f32(reinterpret_cast(&pSource->_21)); + M.r[2] = vld1q_f32(reinterpret_cast(&pSource->_31)); + M.r[3] = vld1q_f32(reinterpret_cast(&pSource->_41)); + return M; +#elif defined(_XM_SSE_INTRINSICS_) + XMMATRIX M; + M.r[0] = _mm_loadu_ps(&pSource->_11); + M.r[1] = _mm_loadu_ps(&pSource->_21); + M.r[2] = _mm_loadu_ps(&pSource->_31); + M.r[3] = _mm_loadu_ps(&pSource->_41); + return M; +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMMATRIX XM_CALLCONV XMLoadFloat4x4A(const XMFLOAT4X4A* pSource) noexcept +{ + assert(pSource); + assert((reinterpret_cast(pSource) & 0xF) == 0); +#if defined(_XM_NO_INTRINSICS_) + + XMMATRIX M; + M.r[0].vector4_f32[0] = pSource->m[0][0]; + M.r[0].vector4_f32[1] = pSource->m[0][1]; + M.r[0].vector4_f32[2] = pSource->m[0][2]; + M.r[0].vector4_f32[3] = pSource->m[0][3]; + + M.r[1].vector4_f32[0] = pSource->m[1][0]; + M.r[1].vector4_f32[1] = pSource->m[1][1]; + M.r[1].vector4_f32[2] = pSource->m[1][2]; + M.r[1].vector4_f32[3] = pSource->m[1][3]; + + M.r[2].vector4_f32[0] = pSource->m[2][0]; + M.r[2].vector4_f32[1] = pSource->m[2][1]; + M.r[2].vector4_f32[2] = pSource->m[2][2]; + M.r[2].vector4_f32[3] = pSource->m[2][3]; + + M.r[3].vector4_f32[0] = pSource->m[3][0]; + M.r[3].vector4_f32[1] = pSource->m[3][1]; + M.r[3].vector4_f32[2] = pSource->m[3][2]; + M.r[3].vector4_f32[3] = pSource->m[3][3]; + return M; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + XMMATRIX M; +#if defined(_MSC_VER) && !defined(__clang__) && !defined(_ARM64_DISTINCT_NEON_TYPES) + M.r[0] = vld1q_f32_ex(reinterpret_cast(&pSource->_11), 128); + M.r[1] = vld1q_f32_ex(reinterpret_cast(&pSource->_21), 128); + M.r[2] = vld1q_f32_ex(reinterpret_cast(&pSource->_31), 128); + M.r[3] = vld1q_f32_ex(reinterpret_cast(&pSource->_41), 128); +#else + M.r[0] = vld1q_f32(reinterpret_cast(&pSource->_11)); + M.r[1] = vld1q_f32(reinterpret_cast(&pSource->_21)); + M.r[2] = vld1q_f32(reinterpret_cast(&pSource->_31)); + M.r[3] = vld1q_f32(reinterpret_cast(&pSource->_41)); +#endif + return M; +#elif defined(_XM_SSE_INTRINSICS_) + XMMATRIX M; + M.r[0] = _mm_load_ps(&pSource->_11); + M.r[1] = _mm_load_ps(&pSource->_21); + M.r[2] = _mm_load_ps(&pSource->_31); + M.r[3] = _mm_load_ps(&pSource->_41); + return M; +#endif +} + +/**************************************************************************** + * + * Vector and matrix store operations + * + ****************************************************************************/ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreInt +( + uint32_t* pDestination, + FXMVECTOR V +) noexcept +{ + assert(pDestination); +#if defined(_XM_NO_INTRINSICS_) + *pDestination = XMVectorGetIntX(V); +#elif defined(_XM_ARM_NEON_INTRINSICS_) + vst1q_lane_u32(pDestination, *reinterpret_cast(&V), 0); +#elif defined(_XM_SSE_INTRINSICS_) + _mm_store_ss(reinterpret_cast(pDestination), V); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreFloat +( + float* pDestination, + FXMVECTOR V +) noexcept +{ + assert(pDestination); +#if defined(_XM_NO_INTRINSICS_) + *pDestination = XMVectorGetX(V); +#elif defined(_XM_ARM_NEON_INTRINSICS_) + vst1q_lane_f32(pDestination, V, 0); +#elif defined(_XM_SSE_INTRINSICS_) + _mm_store_ss(pDestination, V); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreInt2 +( + uint32_t* pDestination, + FXMVECTOR V +) noexcept +{ + assert(pDestination); +#if defined(_XM_NO_INTRINSICS_) + pDestination[0] = V.vector4_u32[0]; + pDestination[1] = V.vector4_u32[1]; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x2_t VL = vget_low_u32(vreinterpretq_u32_f32(V)); + vst1_u32(pDestination, VL); +#elif defined(_XM_SSE_INTRINSICS_) + _mm_store_sd(reinterpret_cast(pDestination), _mm_castps_pd(V)); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreInt2A +( + uint32_t* pDestination, + FXMVECTOR V +) noexcept +{ + assert(pDestination); + assert((reinterpret_cast(pDestination) & 0xF) == 0); +#if defined(_XM_NO_INTRINSICS_) + pDestination[0] = V.vector4_u32[0]; + pDestination[1] = V.vector4_u32[1]; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x2_t VL = vget_low_u32(vreinterpretq_u32_f32(V)); +#if defined(_MSC_VER) && !defined(__clang__) && !defined(_ARM64_DISTINCT_NEON_TYPES) + vst1_u32_ex(pDestination, VL, 64); +#else + vst1_u32(pDestination, VL); +#endif +#elif defined(_XM_SSE_INTRINSICS_) + _mm_store_sd(reinterpret_cast(pDestination), _mm_castps_pd(V)); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreFloat2 +( + XMFLOAT2* pDestination, + FXMVECTOR V +) noexcept +{ + assert(pDestination); +#if defined(_XM_NO_INTRINSICS_) + pDestination->x = V.vector4_f32[0]; + pDestination->y = V.vector4_f32[1]; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x2_t VL = vget_low_f32(V); + vst1_f32(reinterpret_cast(pDestination), VL); +#elif defined(_XM_SSE_INTRINSICS_) + _mm_store_sd(reinterpret_cast(pDestination), _mm_castps_pd(V)); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreFloat2A +( + XMFLOAT2A* pDestination, + FXMVECTOR V +) noexcept +{ + assert(pDestination); + assert((reinterpret_cast(pDestination) & 0xF) == 0); +#if defined(_XM_NO_INTRINSICS_) + pDestination->x = V.vector4_f32[0]; + pDestination->y = V.vector4_f32[1]; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x2_t VL = vget_low_f32(V); +#if defined(_MSC_VER) && !defined(__clang__) && !defined(_ARM64_DISTINCT_NEON_TYPES) + vst1_f32_ex(reinterpret_cast(pDestination), VL, 64); +#else + vst1_f32(reinterpret_cast(pDestination), VL); +#endif +#elif defined(_XM_SSE_INTRINSICS_) + _mm_store_sd(reinterpret_cast(pDestination), _mm_castps_pd(V)); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreSInt2 +( + XMINT2* pDestination, + FXMVECTOR V +) noexcept +{ + assert(pDestination); +#if defined(_XM_NO_INTRINSICS_) + pDestination->x = static_cast(V.vector4_f32[0]); + pDestination->y = static_cast(V.vector4_f32[1]); +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x2_t v = vget_low_f32(V); + int32x2_t iv = vcvt_s32_f32(v); + vst1_s32(reinterpret_cast(pDestination), iv); +#elif defined(_XM_SSE_INTRINSICS_) + // In case of positive overflow, detect it + XMVECTOR vOverflow = _mm_cmpgt_ps(V, g_XMMaxInt); + // Float to int conversion + __m128i vResulti = _mm_cvttps_epi32(V); + // If there was positive overflow, set to 0x7FFFFFFF + XMVECTOR vResult = _mm_and_ps(vOverflow, g_XMAbsMask); + vOverflow = _mm_andnot_ps(vOverflow, _mm_castsi128_ps(vResulti)); + vOverflow = _mm_or_ps(vOverflow, vResult); + // Write two ints + _mm_store_sd(reinterpret_cast(pDestination), _mm_castps_pd(vOverflow)); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreUInt2 +( + XMUINT2* pDestination, + FXMVECTOR V +) noexcept +{ + assert(pDestination); +#if defined(_XM_NO_INTRINSICS_) + pDestination->x = static_cast(V.vector4_f32[0]); + pDestination->y = static_cast(V.vector4_f32[1]); +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x2_t v = vget_low_f32(V); + uint32x2_t iv = vcvt_u32_f32(v); + vst1_u32(reinterpret_cast(pDestination), iv); +#elif defined(_XM_SSE_INTRINSICS_) + // Clamp to >=0 + XMVECTOR vResult = _mm_max_ps(V, g_XMZero); + // Any numbers that are too big, set to 0xFFFFFFFFU + XMVECTOR vOverflow = _mm_cmpgt_ps(vResult, g_XMMaxUInt); + XMVECTOR vValue = g_XMUnsignedFix; + // Too large for a signed integer? + XMVECTOR vMask = _mm_cmpge_ps(vResult, vValue); + // Zero for number's lower than 0x80000000, 32768.0f*65536.0f otherwise + vValue = _mm_and_ps(vValue, vMask); + // Perform fixup only on numbers too large (Keeps low bit precision) + vResult = _mm_sub_ps(vResult, vValue); + __m128i vResulti = _mm_cvttps_epi32(vResult); + // Convert from signed to unsigned pnly if greater than 0x80000000 + vMask = _mm_and_ps(vMask, g_XMNegativeZero); + vResult = _mm_xor_ps(_mm_castsi128_ps(vResulti), vMask); + // On those that are too large, set to 0xFFFFFFFF + vResult = _mm_or_ps(vResult, vOverflow); + // Write two uints + _mm_store_sd(reinterpret_cast(pDestination), _mm_castps_pd(vResult)); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreInt3 +( + uint32_t* pDestination, + FXMVECTOR V +) noexcept +{ + assert(pDestination); +#if defined(_XM_NO_INTRINSICS_) + pDestination[0] = V.vector4_u32[0]; + pDestination[1] = V.vector4_u32[1]; + pDestination[2] = V.vector4_u32[2]; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x2_t VL = vget_low_u32(vreinterpretq_u32_f32(V)); + vst1_u32(pDestination, VL); + vst1q_lane_u32(pDestination + 2, *reinterpret_cast(&V), 2); +#elif defined(_XM_SSE_INTRINSICS_) + _mm_store_sd(reinterpret_cast(pDestination), _mm_castps_pd(V)); + __m128 z = XM_PERMUTE_PS(V, _MM_SHUFFLE(2, 2, 2, 2)); + _mm_store_ss(reinterpret_cast(&pDestination[2]), z); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreInt3A +( + uint32_t* pDestination, + FXMVECTOR V +) noexcept +{ + assert(pDestination); + assert((reinterpret_cast(pDestination) & 0xF) == 0); +#if defined(_XM_NO_INTRINSICS_) + pDestination[0] = V.vector4_u32[0]; + pDestination[1] = V.vector4_u32[1]; + pDestination[2] = V.vector4_u32[2]; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x2_t VL = vget_low_u32(vreinterpretq_u32_f32(V)); +#if defined(_MSC_VER) && !defined(__clang__) && !defined(_ARM64_DISTINCT_NEON_TYPES) + vst1_u32_ex(pDestination, VL, 64); +#else + vst1_u32(pDestination, VL); +#endif + vst1q_lane_u32(pDestination + 2, *reinterpret_cast(&V), 2); +#elif defined(_XM_SSE_INTRINSICS_) + _mm_store_sd(reinterpret_cast(pDestination), _mm_castps_pd(V)); + __m128 z = _mm_movehl_ps(V, V); + _mm_store_ss(reinterpret_cast(&pDestination[2]), z); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreFloat3 +( + XMFLOAT3* pDestination, + FXMVECTOR V +) noexcept +{ + assert(pDestination); +#if defined(_XM_NO_INTRINSICS_) + pDestination->x = V.vector4_f32[0]; + pDestination->y = V.vector4_f32[1]; + pDestination->z = V.vector4_f32[2]; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x2_t VL = vget_low_f32(V); + vst1_f32(reinterpret_cast(pDestination), VL); + vst1q_lane_f32(reinterpret_cast(pDestination) + 2, V, 2); +#elif defined(_XM_SSE4_INTRINSICS_) + * reinterpret_cast(&pDestination->x) = _mm_extract_ps(V, 0); + *reinterpret_cast(&pDestination->y) = _mm_extract_ps(V, 1); + *reinterpret_cast(&pDestination->z) = _mm_extract_ps(V, 2); +#elif defined(_XM_SSE_INTRINSICS_) + _mm_store_sd(reinterpret_cast(pDestination), _mm_castps_pd(V)); + __m128 z = XM_PERMUTE_PS(V, _MM_SHUFFLE(2, 2, 2, 2)); + _mm_store_ss(&pDestination->z, z); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreFloat3A +( + XMFLOAT3A* pDestination, + FXMVECTOR V +) noexcept +{ + assert(pDestination); + assert((reinterpret_cast(pDestination) & 0xF) == 0); +#if defined(_XM_NO_INTRINSICS_) + pDestination->x = V.vector4_f32[0]; + pDestination->y = V.vector4_f32[1]; + pDestination->z = V.vector4_f32[2]; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x2_t VL = vget_low_f32(V); +#if defined(_MSC_VER) && !defined(__clang__) && !defined(_ARM64_DISTINCT_NEON_TYPES) + vst1_f32_ex(reinterpret_cast(pDestination), VL, 64); +#else + vst1_f32(reinterpret_cast(pDestination), VL); +#endif + vst1q_lane_f32(reinterpret_cast(pDestination) + 2, V, 2); +#elif defined(_XM_SSE4_INTRINSICS_) + _mm_store_sd(reinterpret_cast(pDestination), _mm_castps_pd(V)); + *reinterpret_cast(&pDestination->z) = _mm_extract_ps(V, 2); +#elif defined(_XM_SSE_INTRINSICS_) + _mm_store_sd(reinterpret_cast(pDestination), _mm_castps_pd(V)); + __m128 z = _mm_movehl_ps(V, V); + _mm_store_ss(&pDestination->z, z); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreSInt3 +( + XMINT3* pDestination, + FXMVECTOR V +) noexcept +{ + assert(pDestination); +#if defined(_XM_NO_INTRINSICS_) + pDestination->x = static_cast(V.vector4_f32[0]); + pDestination->y = static_cast(V.vector4_f32[1]); + pDestination->z = static_cast(V.vector4_f32[2]); +#elif defined(_XM_ARM_NEON_INTRINSICS_) + int32x4_t v = vcvtq_s32_f32(V); + int32x2_t vL = vget_low_s32(v); + vst1_s32(reinterpret_cast(pDestination), vL); + vst1q_lane_s32(reinterpret_cast(pDestination) + 2, v, 2); +#elif defined(_XM_SSE_INTRINSICS_) + // In case of positive overflow, detect it + XMVECTOR vOverflow = _mm_cmpgt_ps(V, g_XMMaxInt); + // Float to int conversion + __m128i vResulti = _mm_cvttps_epi32(V); + // If there was positive overflow, set to 0x7FFFFFFF + XMVECTOR vResult = _mm_and_ps(vOverflow, g_XMAbsMask); + vOverflow = _mm_andnot_ps(vOverflow, _mm_castsi128_ps(vResulti)); + vOverflow = _mm_or_ps(vOverflow, vResult); + // Write 3 uints + _mm_store_sd(reinterpret_cast(pDestination), _mm_castps_pd(vOverflow)); + __m128 z = XM_PERMUTE_PS(vOverflow, _MM_SHUFFLE(2, 2, 2, 2)); + _mm_store_ss(reinterpret_cast(&pDestination->z), z); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreUInt3 +( + XMUINT3* pDestination, + FXMVECTOR V +) noexcept +{ + assert(pDestination); +#if defined(_XM_NO_INTRINSICS_) + pDestination->x = static_cast(V.vector4_f32[0]); + pDestination->y = static_cast(V.vector4_f32[1]); + pDestination->z = static_cast(V.vector4_f32[2]); +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x4_t v = vcvtq_u32_f32(V); + uint32x2_t vL = vget_low_u32(v); + vst1_u32(reinterpret_cast(pDestination), vL); + vst1q_lane_u32(reinterpret_cast(pDestination) + 2, v, 2); +#elif defined(_XM_SSE_INTRINSICS_) + // Clamp to >=0 + XMVECTOR vResult = _mm_max_ps(V, g_XMZero); + // Any numbers that are too big, set to 0xFFFFFFFFU + XMVECTOR vOverflow = _mm_cmpgt_ps(vResult, g_XMMaxUInt); + XMVECTOR vValue = g_XMUnsignedFix; + // Too large for a signed integer? + XMVECTOR vMask = _mm_cmpge_ps(vResult, vValue); + // Zero for number's lower than 0x80000000, 32768.0f*65536.0f otherwise + vValue = _mm_and_ps(vValue, vMask); + // Perform fixup only on numbers too large (Keeps low bit precision) + vResult = _mm_sub_ps(vResult, vValue); + __m128i vResulti = _mm_cvttps_epi32(vResult); + // Convert from signed to unsigned pnly if greater than 0x80000000 + vMask = _mm_and_ps(vMask, g_XMNegativeZero); + vResult = _mm_xor_ps(_mm_castsi128_ps(vResulti), vMask); + // On those that are too large, set to 0xFFFFFFFF + vResult = _mm_or_ps(vResult, vOverflow); + // Write 3 uints + _mm_store_sd(reinterpret_cast(pDestination), _mm_castps_pd(vResult)); + __m128 z = XM_PERMUTE_PS(vResult, _MM_SHUFFLE(2, 2, 2, 2)); + _mm_store_ss(reinterpret_cast(&pDestination->z), z); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreInt4 +( + uint32_t* pDestination, + FXMVECTOR V +) noexcept +{ + assert(pDestination); +#if defined(_XM_NO_INTRINSICS_) + pDestination[0] = V.vector4_u32[0]; + pDestination[1] = V.vector4_u32[1]; + pDestination[2] = V.vector4_u32[2]; + pDestination[3] = V.vector4_u32[3]; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + vst1q_u32(pDestination, vreinterpretq_u32_f32(V)); +#elif defined(_XM_SSE_INTRINSICS_) + _mm_storeu_si128(reinterpret_cast<__m128i*>(pDestination), _mm_castps_si128(V)); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreInt4A +( + uint32_t* pDestination, + FXMVECTOR V +) noexcept +{ + assert(pDestination); + assert((reinterpret_cast(pDestination) & 0xF) == 0); +#if defined(_XM_NO_INTRINSICS_) + pDestination[0] = V.vector4_u32[0]; + pDestination[1] = V.vector4_u32[1]; + pDestination[2] = V.vector4_u32[2]; + pDestination[3] = V.vector4_u32[3]; +#elif defined(_XM_ARM_NEON_INTRINSICS_) +#if defined(_MSC_VER) && !defined(__clang__) && !defined(_ARM64_DISTINCT_NEON_TYPES) + vst1q_u32_ex(pDestination, V, 128); +#else + vst1q_u32(pDestination, vreinterpretq_u32_f32(V)); +#endif +#elif defined(_XM_SSE_INTRINSICS_) + _mm_store_si128(reinterpret_cast<__m128i*>(pDestination), _mm_castps_si128(V)); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreFloat4 +( + XMFLOAT4* pDestination, + FXMVECTOR V +) noexcept +{ + assert(pDestination); +#if defined(_XM_NO_INTRINSICS_) + pDestination->x = V.vector4_f32[0]; + pDestination->y = V.vector4_f32[1]; + pDestination->z = V.vector4_f32[2]; + pDestination->w = V.vector4_f32[3]; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + vst1q_f32(reinterpret_cast(pDestination), V); +#elif defined(_XM_SSE_INTRINSICS_) + _mm_storeu_ps(&pDestination->x, V); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreFloat4A +( + XMFLOAT4A* pDestination, + FXMVECTOR V +) noexcept +{ + assert(pDestination); + assert((reinterpret_cast(pDestination) & 0xF) == 0); +#if defined(_XM_NO_INTRINSICS_) + pDestination->x = V.vector4_f32[0]; + pDestination->y = V.vector4_f32[1]; + pDestination->z = V.vector4_f32[2]; + pDestination->w = V.vector4_f32[3]; +#elif defined(_XM_ARM_NEON_INTRINSICS_) +#if defined(_MSC_VER) && !defined(__clang__) && !defined(_ARM64_DISTINCT_NEON_TYPES) + vst1q_f32_ex(reinterpret_cast(pDestination), V, 128); +#else + vst1q_f32(reinterpret_cast(pDestination), V); +#endif +#elif defined(_XM_SSE_INTRINSICS_) + _mm_store_ps(&pDestination->x, V); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreSInt4 +( + XMINT4* pDestination, + FXMVECTOR V +) noexcept +{ + assert(pDestination); +#if defined(_XM_NO_INTRINSICS_) + pDestination->x = static_cast(V.vector4_f32[0]); + pDestination->y = static_cast(V.vector4_f32[1]); + pDestination->z = static_cast(V.vector4_f32[2]); + pDestination->w = static_cast(V.vector4_f32[3]); +#elif defined(_XM_ARM_NEON_INTRINSICS_) + int32x4_t v = vcvtq_s32_f32(V); + vst1q_s32(reinterpret_cast(pDestination), v); +#elif defined(_XM_SSE_INTRINSICS_) + // In case of positive overflow, detect it + XMVECTOR vOverflow = _mm_cmpgt_ps(V, g_XMMaxInt); + // Float to int conversion + __m128i vResulti = _mm_cvttps_epi32(V); + // If there was positive overflow, set to 0x7FFFFFFF + XMVECTOR vResult = _mm_and_ps(vOverflow, g_XMAbsMask); + vOverflow = _mm_andnot_ps(vOverflow, _mm_castsi128_ps(vResulti)); + vOverflow = _mm_or_ps(vOverflow, vResult); + _mm_storeu_si128(reinterpret_cast<__m128i*>(pDestination), _mm_castps_si128(vOverflow)); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreUInt4 +( + XMUINT4* pDestination, + FXMVECTOR V +) noexcept +{ + assert(pDestination); +#if defined(_XM_NO_INTRINSICS_) + pDestination->x = static_cast(V.vector4_f32[0]); + pDestination->y = static_cast(V.vector4_f32[1]); + pDestination->z = static_cast(V.vector4_f32[2]); + pDestination->w = static_cast(V.vector4_f32[3]); +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x4_t v = vcvtq_u32_f32(V); + vst1q_u32(reinterpret_cast(pDestination), v); +#elif defined(_XM_SSE_INTRINSICS_) + // Clamp to >=0 + XMVECTOR vResult = _mm_max_ps(V, g_XMZero); + // Any numbers that are too big, set to 0xFFFFFFFFU + XMVECTOR vOverflow = _mm_cmpgt_ps(vResult, g_XMMaxUInt); + XMVECTOR vValue = g_XMUnsignedFix; + // Too large for a signed integer? + XMVECTOR vMask = _mm_cmpge_ps(vResult, vValue); + // Zero for number's lower than 0x80000000, 32768.0f*65536.0f otherwise + vValue = _mm_and_ps(vValue, vMask); + // Perform fixup only on numbers too large (Keeps low bit precision) + vResult = _mm_sub_ps(vResult, vValue); + __m128i vResulti = _mm_cvttps_epi32(vResult); + // Convert from signed to unsigned pnly if greater than 0x80000000 + vMask = _mm_and_ps(vMask, g_XMNegativeZero); + vResult = _mm_xor_ps(_mm_castsi128_ps(vResulti), vMask); + // On those that are too large, set to 0xFFFFFFFF + vResult = _mm_or_ps(vResult, vOverflow); + _mm_storeu_si128(reinterpret_cast<__m128i*>(pDestination), _mm_castps_si128(vResult)); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreFloat3x3 +( + XMFLOAT3X3* pDestination, + FXMMATRIX M +) noexcept +{ + assert(pDestination); +#if defined(_XM_NO_INTRINSICS_) + + pDestination->m[0][0] = M.r[0].vector4_f32[0]; + pDestination->m[0][1] = M.r[0].vector4_f32[1]; + pDestination->m[0][2] = M.r[0].vector4_f32[2]; + + pDestination->m[1][0] = M.r[1].vector4_f32[0]; + pDestination->m[1][1] = M.r[1].vector4_f32[1]; + pDestination->m[1][2] = M.r[1].vector4_f32[2]; + + pDestination->m[2][0] = M.r[2].vector4_f32[0]; + pDestination->m[2][1] = M.r[2].vector4_f32[1]; + pDestination->m[2][2] = M.r[2].vector4_f32[2]; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x4_t T1 = vextq_f32(M.r[0], M.r[1], 1); + float32x4_t T2 = vbslq_f32(g_XMMask3, M.r[0], T1); + vst1q_f32(&pDestination->m[0][0], T2); + + T1 = vextq_f32(M.r[1], M.r[1], 1); + T2 = vcombine_f32(vget_low_f32(T1), vget_low_f32(M.r[2])); + vst1q_f32(&pDestination->m[1][1], T2); + + vst1q_lane_f32(&pDestination->m[2][2], M.r[2], 2); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vTemp1 = M.r[0]; + XMVECTOR vTemp2 = M.r[1]; + XMVECTOR vTemp3 = M.r[2]; + XMVECTOR vWork = _mm_shuffle_ps(vTemp1, vTemp2, _MM_SHUFFLE(0, 0, 2, 2)); + vTemp1 = _mm_shuffle_ps(vTemp1, vWork, _MM_SHUFFLE(2, 0, 1, 0)); + _mm_storeu_ps(&pDestination->m[0][0], vTemp1); + vTemp2 = _mm_shuffle_ps(vTemp2, vTemp3, _MM_SHUFFLE(1, 0, 2, 1)); + _mm_storeu_ps(&pDestination->m[1][1], vTemp2); + vTemp3 = XM_PERMUTE_PS(vTemp3, _MM_SHUFFLE(2, 2, 2, 2)); + _mm_store_ss(&pDestination->m[2][2], vTemp3); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreFloat4x3 +( + XMFLOAT4X3* pDestination, + FXMMATRIX M +) noexcept +{ + assert(pDestination); +#if defined(_XM_NO_INTRINSICS_) + + pDestination->m[0][0] = M.r[0].vector4_f32[0]; + pDestination->m[0][1] = M.r[0].vector4_f32[1]; + pDestination->m[0][2] = M.r[0].vector4_f32[2]; + + pDestination->m[1][0] = M.r[1].vector4_f32[0]; + pDestination->m[1][1] = M.r[1].vector4_f32[1]; + pDestination->m[1][2] = M.r[1].vector4_f32[2]; + + pDestination->m[2][0] = M.r[2].vector4_f32[0]; + pDestination->m[2][1] = M.r[2].vector4_f32[1]; + pDestination->m[2][2] = M.r[2].vector4_f32[2]; + + pDestination->m[3][0] = M.r[3].vector4_f32[0]; + pDestination->m[3][1] = M.r[3].vector4_f32[1]; + pDestination->m[3][2] = M.r[3].vector4_f32[2]; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x4_t T1 = vextq_f32(M.r[0], M.r[1], 1); + float32x4_t T2 = vbslq_f32(g_XMMask3, M.r[0], T1); + vst1q_f32(&pDestination->m[0][0], T2); + + T1 = vextq_f32(M.r[1], M.r[1], 1); + T2 = vcombine_f32(vget_low_f32(T1), vget_low_f32(M.r[2])); + vst1q_f32(&pDestination->m[1][1], T2); + + T1 = vdupq_lane_f32(vget_high_f32(M.r[2]), 0); + T2 = vextq_f32(T1, M.r[3], 3); + vst1q_f32(&pDestination->m[2][2], T2); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vTemp1 = M.r[0]; + XMVECTOR vTemp2 = M.r[1]; + XMVECTOR vTemp3 = M.r[2]; + XMVECTOR vTemp4 = M.r[3]; + XMVECTOR vTemp2x = _mm_shuffle_ps(vTemp2, vTemp3, _MM_SHUFFLE(1, 0, 2, 1)); + vTemp2 = _mm_shuffle_ps(vTemp2, vTemp1, _MM_SHUFFLE(2, 2, 0, 0)); + vTemp1 = _mm_shuffle_ps(vTemp1, vTemp2, _MM_SHUFFLE(0, 2, 1, 0)); + vTemp3 = _mm_shuffle_ps(vTemp3, vTemp4, _MM_SHUFFLE(0, 0, 2, 2)); + vTemp3 = _mm_shuffle_ps(vTemp3, vTemp4, _MM_SHUFFLE(2, 1, 2, 0)); + _mm_storeu_ps(&pDestination->m[0][0], vTemp1); + _mm_storeu_ps(&pDestination->m[1][1], vTemp2x); + _mm_storeu_ps(&pDestination->m[2][2], vTemp3); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreFloat4x3A +( + XMFLOAT4X3A* pDestination, + FXMMATRIX M +) noexcept +{ + assert(pDestination); + assert((reinterpret_cast(pDestination) & 0xF) == 0); +#if defined(_XM_NO_INTRINSICS_) + + pDestination->m[0][0] = M.r[0].vector4_f32[0]; + pDestination->m[0][1] = M.r[0].vector4_f32[1]; + pDestination->m[0][2] = M.r[0].vector4_f32[2]; + + pDestination->m[1][0] = M.r[1].vector4_f32[0]; + pDestination->m[1][1] = M.r[1].vector4_f32[1]; + pDestination->m[1][2] = M.r[1].vector4_f32[2]; + + pDestination->m[2][0] = M.r[2].vector4_f32[0]; + pDestination->m[2][1] = M.r[2].vector4_f32[1]; + pDestination->m[2][2] = M.r[2].vector4_f32[2]; + + pDestination->m[3][0] = M.r[3].vector4_f32[0]; + pDestination->m[3][1] = M.r[3].vector4_f32[1]; + pDestination->m[3][2] = M.r[3].vector4_f32[2]; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) +#if defined(_MSC_VER) && !defined(__clang__) && !defined(_ARM64_DISTINCT_NEON_TYPES) + float32x4_t T1 = vextq_f32(M.r[0], M.r[1], 1); + float32x4_t T2 = vbslq_f32(g_XMMask3, M.r[0], T1); + vst1q_f32_ex(&pDestination->m[0][0], T2, 128); + + T1 = vextq_f32(M.r[1], M.r[1], 1); + T2 = vcombine_f32(vget_low_f32(T1), vget_low_f32(M.r[2])); + vst1q_f32_ex(&pDestination->m[1][1], T2, 128); + + T1 = vdupq_lane_f32(vget_high_f32(M.r[2]), 0); + T2 = vextq_f32(T1, M.r[3], 3); + vst1q_f32_ex(&pDestination->m[2][2], T2, 128); +#else + float32x4_t T1 = vextq_f32(M.r[0], M.r[1], 1); + float32x4_t T2 = vbslq_f32(g_XMMask3, M.r[0], T1); + vst1q_f32(&pDestination->m[0][0], T2); + + T1 = vextq_f32(M.r[1], M.r[1], 1); + T2 = vcombine_f32(vget_low_f32(T1), vget_low_f32(M.r[2])); + vst1q_f32(&pDestination->m[1][1], T2); + + T1 = vdupq_lane_f32(vget_high_f32(M.r[2]), 0); + T2 = vextq_f32(T1, M.r[3], 3); + vst1q_f32(&pDestination->m[2][2], T2); +#endif +#elif defined(_XM_SSE_INTRINSICS_) + // x1,y1,z1,w1 + XMVECTOR vTemp1 = M.r[0]; + // x2,y2,z2,w2 + XMVECTOR vTemp2 = M.r[1]; + // x3,y3,z3,w3 + XMVECTOR vTemp3 = M.r[2]; + // x4,y4,z4,w4 + XMVECTOR vTemp4 = M.r[3]; + // z1,z1,x2,y2 + XMVECTOR vTemp = _mm_shuffle_ps(vTemp1, vTemp2, _MM_SHUFFLE(1, 0, 2, 2)); + // y2,z2,x3,y3 (Final) + vTemp2 = _mm_shuffle_ps(vTemp2, vTemp3, _MM_SHUFFLE(1, 0, 2, 1)); + // x1,y1,z1,x2 (Final) + vTemp1 = _mm_shuffle_ps(vTemp1, vTemp, _MM_SHUFFLE(2, 0, 1, 0)); + // z3,z3,x4,x4 + vTemp3 = _mm_shuffle_ps(vTemp3, vTemp4, _MM_SHUFFLE(0, 0, 2, 2)); + // z3,x4,y4,z4 (Final) + vTemp3 = _mm_shuffle_ps(vTemp3, vTemp4, _MM_SHUFFLE(2, 1, 2, 0)); + // Store in 3 operations + _mm_store_ps(&pDestination->m[0][0], vTemp1); + _mm_store_ps(&pDestination->m[1][1], vTemp2); + _mm_store_ps(&pDestination->m[2][2], vTemp3); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreFloat3x4 +( + XMFLOAT3X4* pDestination, + FXMMATRIX M +) noexcept +{ + assert(pDestination); +#if defined(_XM_NO_INTRINSICS_) + + pDestination->m[0][0] = M.r[0].vector4_f32[0]; + pDestination->m[0][1] = M.r[1].vector4_f32[0]; + pDestination->m[0][2] = M.r[2].vector4_f32[0]; + pDestination->m[0][3] = M.r[3].vector4_f32[0]; + + pDestination->m[1][0] = M.r[0].vector4_f32[1]; + pDestination->m[1][1] = M.r[1].vector4_f32[1]; + pDestination->m[1][2] = M.r[2].vector4_f32[1]; + pDestination->m[1][3] = M.r[3].vector4_f32[1]; + + pDestination->m[2][0] = M.r[0].vector4_f32[2]; + pDestination->m[2][1] = M.r[1].vector4_f32[2]; + pDestination->m[2][2] = M.r[2].vector4_f32[2]; + pDestination->m[2][3] = M.r[3].vector4_f32[2]; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x4x2_t P0 = vzipq_f32(M.r[0], M.r[2]); + float32x4x2_t P1 = vzipq_f32(M.r[1], M.r[3]); + + float32x4x2_t T0 = vzipq_f32(P0.val[0], P1.val[0]); + float32x4x2_t T1 = vzipq_f32(P0.val[1], P1.val[1]); + + vst1q_f32(&pDestination->m[0][0], T0.val[0]); + vst1q_f32(&pDestination->m[1][0], T0.val[1]); + vst1q_f32(&pDestination->m[2][0], T1.val[0]); +#elif defined(_XM_SSE_INTRINSICS_) + // x.x,x.y,y.x,y.y + XMVECTOR vTemp1 = _mm_shuffle_ps(M.r[0], M.r[1], _MM_SHUFFLE(1, 0, 1, 0)); + // x.z,x.w,y.z,y.w + XMVECTOR vTemp3 = _mm_shuffle_ps(M.r[0], M.r[1], _MM_SHUFFLE(3, 2, 3, 2)); + // z.x,z.y,w.x,w.y + XMVECTOR vTemp2 = _mm_shuffle_ps(M.r[2], M.r[3], _MM_SHUFFLE(1, 0, 1, 0)); + // z.z,z.w,w.z,w.w + XMVECTOR vTemp4 = _mm_shuffle_ps(M.r[2], M.r[3], _MM_SHUFFLE(3, 2, 3, 2)); + + // x.x,y.x,z.x,w.x + XMVECTOR r0 = _mm_shuffle_ps(vTemp1, vTemp2, _MM_SHUFFLE(2, 0, 2, 0)); + // x.y,y.y,z.y,w.y + XMVECTOR r1 = _mm_shuffle_ps(vTemp1, vTemp2, _MM_SHUFFLE(3, 1, 3, 1)); + // x.z,y.z,z.z,w.z + XMVECTOR r2 = _mm_shuffle_ps(vTemp3, vTemp4, _MM_SHUFFLE(2, 0, 2, 0)); + + _mm_storeu_ps(&pDestination->m[0][0], r0); + _mm_storeu_ps(&pDestination->m[1][0], r1); + _mm_storeu_ps(&pDestination->m[2][0], r2); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreFloat3x4A +( + XMFLOAT3X4A* pDestination, + FXMMATRIX M +) noexcept +{ + assert(pDestination); + assert((reinterpret_cast(pDestination) & 0xF) == 0); +#if defined(_XM_NO_INTRINSICS_) + + pDestination->m[0][0] = M.r[0].vector4_f32[0]; + pDestination->m[0][1] = M.r[1].vector4_f32[0]; + pDestination->m[0][2] = M.r[2].vector4_f32[0]; + pDestination->m[0][3] = M.r[3].vector4_f32[0]; + + pDestination->m[1][0] = M.r[0].vector4_f32[1]; + pDestination->m[1][1] = M.r[1].vector4_f32[1]; + pDestination->m[1][2] = M.r[2].vector4_f32[1]; + pDestination->m[1][3] = M.r[3].vector4_f32[1]; + + pDestination->m[2][0] = M.r[0].vector4_f32[2]; + pDestination->m[2][1] = M.r[1].vector4_f32[2]; + pDestination->m[2][2] = M.r[2].vector4_f32[2]; + pDestination->m[2][3] = M.r[3].vector4_f32[2]; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x4x2_t P0 = vzipq_f32(M.r[0], M.r[2]); + float32x4x2_t P1 = vzipq_f32(M.r[1], M.r[3]); + + float32x4x2_t T0 = vzipq_f32(P0.val[0], P1.val[0]); + float32x4x2_t T1 = vzipq_f32(P0.val[1], P1.val[1]); + +#if defined(_MSC_VER) && !defined(__clang__) && !defined(_ARM64_DISTINCT_NEON_TYPES) + vst1q_f32_ex(&pDestination->m[0][0], T0.val[0], 128); + vst1q_f32_ex(&pDestination->m[1][0], T0.val[1], 128); + vst1q_f32_ex(&pDestination->m[2][0], T1.val[0], 128); +#else + vst1q_f32(&pDestination->m[0][0], T0.val[0]); + vst1q_f32(&pDestination->m[1][0], T0.val[1]); + vst1q_f32(&pDestination->m[2][0], T1.val[0]); +#endif +#elif defined(_XM_SSE_INTRINSICS_) + // x.x,x.y,y.x,y.y + XMVECTOR vTemp1 = _mm_shuffle_ps(M.r[0], M.r[1], _MM_SHUFFLE(1, 0, 1, 0)); + // x.z,x.w,y.z,y.w + XMVECTOR vTemp3 = _mm_shuffle_ps(M.r[0], M.r[1], _MM_SHUFFLE(3, 2, 3, 2)); + // z.x,z.y,w.x,w.y + XMVECTOR vTemp2 = _mm_shuffle_ps(M.r[2], M.r[3], _MM_SHUFFLE(1, 0, 1, 0)); + // z.z,z.w,w.z,w.w + XMVECTOR vTemp4 = _mm_shuffle_ps(M.r[2], M.r[3], _MM_SHUFFLE(3, 2, 3, 2)); + + // x.x,y.x,z.x,w.x + XMVECTOR r0 = _mm_shuffle_ps(vTemp1, vTemp2, _MM_SHUFFLE(2, 0, 2, 0)); + // x.y,y.y,z.y,w.y + XMVECTOR r1 = _mm_shuffle_ps(vTemp1, vTemp2, _MM_SHUFFLE(3, 1, 3, 1)); + // x.z,y.z,z.z,w.z + XMVECTOR r2 = _mm_shuffle_ps(vTemp3, vTemp4, _MM_SHUFFLE(2, 0, 2, 0)); + + _mm_store_ps(&pDestination->m[0][0], r0); + _mm_store_ps(&pDestination->m[1][0], r1); + _mm_store_ps(&pDestination->m[2][0], r2); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreFloat4x4 +( + XMFLOAT4X4* pDestination, + FXMMATRIX M +) noexcept +{ + assert(pDestination); +#if defined(_XM_NO_INTRINSICS_) + + pDestination->m[0][0] = M.r[0].vector4_f32[0]; + pDestination->m[0][1] = M.r[0].vector4_f32[1]; + pDestination->m[0][2] = M.r[0].vector4_f32[2]; + pDestination->m[0][3] = M.r[0].vector4_f32[3]; + + pDestination->m[1][0] = M.r[1].vector4_f32[0]; + pDestination->m[1][1] = M.r[1].vector4_f32[1]; + pDestination->m[1][2] = M.r[1].vector4_f32[2]; + pDestination->m[1][3] = M.r[1].vector4_f32[3]; + + pDestination->m[2][0] = M.r[2].vector4_f32[0]; + pDestination->m[2][1] = M.r[2].vector4_f32[1]; + pDestination->m[2][2] = M.r[2].vector4_f32[2]; + pDestination->m[2][3] = M.r[2].vector4_f32[3]; + + pDestination->m[3][0] = M.r[3].vector4_f32[0]; + pDestination->m[3][1] = M.r[3].vector4_f32[1]; + pDestination->m[3][2] = M.r[3].vector4_f32[2]; + pDestination->m[3][3] = M.r[3].vector4_f32[3]; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + vst1q_f32(reinterpret_cast(&pDestination->_11), M.r[0]); + vst1q_f32(reinterpret_cast(&pDestination->_21), M.r[1]); + vst1q_f32(reinterpret_cast(&pDestination->_31), M.r[2]); + vst1q_f32(reinterpret_cast(&pDestination->_41), M.r[3]); +#elif defined(_XM_SSE_INTRINSICS_) + _mm_storeu_ps(&pDestination->_11, M.r[0]); + _mm_storeu_ps(&pDestination->_21, M.r[1]); + _mm_storeu_ps(&pDestination->_31, M.r[2]); + _mm_storeu_ps(&pDestination->_41, M.r[3]); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreFloat4x4A +( + XMFLOAT4X4A* pDestination, + FXMMATRIX M +) noexcept +{ + assert(pDestination); + assert((reinterpret_cast(pDestination) & 0xF) == 0); +#if defined(_XM_NO_INTRINSICS_) + + pDestination->m[0][0] = M.r[0].vector4_f32[0]; + pDestination->m[0][1] = M.r[0].vector4_f32[1]; + pDestination->m[0][2] = M.r[0].vector4_f32[2]; + pDestination->m[0][3] = M.r[0].vector4_f32[3]; + + pDestination->m[1][0] = M.r[1].vector4_f32[0]; + pDestination->m[1][1] = M.r[1].vector4_f32[1]; + pDestination->m[1][2] = M.r[1].vector4_f32[2]; + pDestination->m[1][3] = M.r[1].vector4_f32[3]; + + pDestination->m[2][0] = M.r[2].vector4_f32[0]; + pDestination->m[2][1] = M.r[2].vector4_f32[1]; + pDestination->m[2][2] = M.r[2].vector4_f32[2]; + pDestination->m[2][3] = M.r[2].vector4_f32[3]; + + pDestination->m[3][0] = M.r[3].vector4_f32[0]; + pDestination->m[3][1] = M.r[3].vector4_f32[1]; + pDestination->m[3][2] = M.r[3].vector4_f32[2]; + pDestination->m[3][3] = M.r[3].vector4_f32[3]; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) +#if defined(_MSC_VER) && !defined(__clang__) && !defined(_ARM64_DISTINCT_NEON_TYPES) + vst1q_f32_ex(reinterpret_cast(&pDestination->_11), M.r[0], 128); + vst1q_f32_ex(reinterpret_cast(&pDestination->_21), M.r[1], 128); + vst1q_f32_ex(reinterpret_cast(&pDestination->_31), M.r[2], 128); + vst1q_f32_ex(reinterpret_cast(&pDestination->_41), M.r[3], 128); +#else + vst1q_f32(reinterpret_cast(&pDestination->_11), M.r[0]); + vst1q_f32(reinterpret_cast(&pDestination->_21), M.r[1]); + vst1q_f32(reinterpret_cast(&pDestination->_31), M.r[2]); + vst1q_f32(reinterpret_cast(&pDestination->_41), M.r[3]); +#endif +#elif defined(_XM_SSE_INTRINSICS_) + _mm_store_ps(&pDestination->_11, M.r[0]); + _mm_store_ps(&pDestination->_21, M.r[1]); + _mm_store_ps(&pDestination->_31, M.r[2]); + _mm_store_ps(&pDestination->_41, M.r[3]); +#endif +} + diff --git a/Minecraft.Client/Windows64/Libs/4JLibs/impls/Windows_Libs/Render/vendor/DirectXMath/DirectXMathMatrix.inl b/Minecraft.Client/Windows64/Libs/4JLibs/impls/Windows_Libs/Render/vendor/DirectXMath/DirectXMathMatrix.inl new file mode 100644 index 00000000..5e96a331 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/4JLibs/impls/Windows_Libs/Render/vendor/DirectXMath/DirectXMathMatrix.inl @@ -0,0 +1,3554 @@ +//------------------------------------------------------------------------------------- +// DirectXMathMatrix.inl -- SIMD C++ Math library +// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +// +// https://go.microsoft.com/fwlink/?LinkID=615560 +//------------------------------------------------------------------------------------- + +#pragma once + +/**************************************************************************** + * + * Matrix + * + ****************************************************************************/ + +//------------------------------------------------------------------------------ +// Comparison operations +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ + +#if !defined(_XM_NO_INTRINSICS_) && defined(_MSC_VER) && !defined(__INTEL_COMPILER) +#pragma float_control(push) +#pragma float_control(precise, on) +#endif + +// Return true if any entry in the matrix is NaN +inline bool XM_CALLCONV XMMatrixIsNaN(FXMMATRIX M) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + size_t i = 16; + auto pWork = reinterpret_cast(&M.m[0][0]); + do + { + // Fetch value into integer unit + uint32_t uTest = pWork[0]; + // Remove sign + uTest &= 0x7FFFFFFFU; + // NaN is 0x7F800001 through 0x7FFFFFFF inclusive + uTest -= 0x7F800001U; + if (uTest < 0x007FFFFFU) + { + break; // NaN found + } + ++pWork; // Next entry + } + while (--i); + return (i != 0); // i == 0 if nothing matched +#elif defined(_XM_ARM_NEON_INTRINSICS_) + // Load in registers + float32x4_t vX = M.r[0]; + float32x4_t vY = M.r[1]; + float32x4_t vZ = M.r[2]; + float32x4_t vW = M.r[3]; + // Test themselves to check for NaN + uint32x4_t xmask = vmvnq_u32(vceqq_f32(vX, vX)); + uint32x4_t ymask = vmvnq_u32(vceqq_f32(vY, vY)); + uint32x4_t zmask = vmvnq_u32(vceqq_f32(vZ, vZ)); + uint32x4_t wmask = vmvnq_u32(vceqq_f32(vW, vW)); + // Or all the results + xmask = vorrq_u32(xmask, zmask); + ymask = vorrq_u32(ymask, wmask); + xmask = vorrq_u32(xmask, ymask); + // If any tested true, return true + uint8x8x2_t vTemp = vzip_u8( + vget_low_u8(vreinterpretq_u8_u32(xmask)), + vget_high_u8(vreinterpretq_u8_u32(xmask))); + uint16x4x2_t vTemp2 = vzip_u16(vreinterpret_u16_u8(vTemp.val[0]), vreinterpret_u16_u8(vTemp.val[1])); + uint32_t r = vget_lane_u32(vreinterpret_u32_u16(vTemp2.val[1]), 1); + return (r != 0); +#elif defined(_XM_SSE_INTRINSICS_) + // Load in registers + XMVECTOR vX = M.r[0]; + XMVECTOR vY = M.r[1]; + XMVECTOR vZ = M.r[2]; + XMVECTOR vW = M.r[3]; + // Test themselves to check for NaN + vX = _mm_cmpneq_ps(vX, vX); + vY = _mm_cmpneq_ps(vY, vY); + vZ = _mm_cmpneq_ps(vZ, vZ); + vW = _mm_cmpneq_ps(vW, vW); + // Or all the results + vX = _mm_or_ps(vX, vZ); + vY = _mm_or_ps(vY, vW); + vX = _mm_or_ps(vX, vY); + // If any tested true, return true + return (_mm_movemask_ps(vX) != 0); +#else +#endif +} + +#if !defined(_XM_NO_INTRINSICS_) && defined(_MSC_VER) && !defined(__INTEL_COMPILER) +#pragma float_control(pop) +#endif + +//------------------------------------------------------------------------------ + +// Return true if any entry in the matrix is +/-INF +inline bool XM_CALLCONV XMMatrixIsInfinite(FXMMATRIX M) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + size_t i = 16; + auto pWork = reinterpret_cast(&M.m[0][0]); + do + { + // Fetch value into integer unit + uint32_t uTest = pWork[0]; + // Remove sign + uTest &= 0x7FFFFFFFU; + // INF is 0x7F800000 + if (uTest == 0x7F800000U) + { + break; // INF found + } + ++pWork; // Next entry + } + while (--i); + return (i != 0); // i == 0 if nothing matched +#elif defined(_XM_ARM_NEON_INTRINSICS_) + // Load in registers + float32x4_t vX = M.r[0]; + float32x4_t vY = M.r[1]; + float32x4_t vZ = M.r[2]; + float32x4_t vW = M.r[3]; + // Mask off the sign bits + vX = vreinterpretq_f32_u32(vandq_u32(vreinterpretq_u32_f32(vX), g_XMAbsMask)); + vY = vreinterpretq_f32_u32(vandq_u32(vreinterpretq_u32_f32(vY), g_XMAbsMask)); + vZ = vreinterpretq_f32_u32(vandq_u32(vreinterpretq_u32_f32(vZ), g_XMAbsMask)); + vW = vreinterpretq_f32_u32(vandq_u32(vreinterpretq_u32_f32(vW), g_XMAbsMask)); + // Compare to infinity + uint32x4_t xmask = vceqq_f32(vX, g_XMInfinity); + uint32x4_t ymask = vceqq_f32(vY, g_XMInfinity); + uint32x4_t zmask = vceqq_f32(vZ, g_XMInfinity); + uint32x4_t wmask = vceqq_f32(vW, g_XMInfinity); + // Or the answers together + xmask = vorrq_u32(xmask, zmask); + ymask = vorrq_u32(ymask, wmask); + xmask = vorrq_u32(xmask, ymask); + // If any tested true, return true + uint8x8x2_t vTemp = vzip_u8( + vget_low_u8(vreinterpretq_u8_u32(xmask)), + vget_high_u8(vreinterpretq_u8_u32(xmask))); + uint16x4x2_t vTemp2 = vzip_u16(vreinterpret_u16_u8(vTemp.val[0]), vreinterpret_u16_u8(vTemp.val[1])); + uint32_t r = vget_lane_u32(vreinterpret_u32_u16(vTemp2.val[1]), 1); + return (r != 0); +#elif defined(_XM_SSE_INTRINSICS_) + // Mask off the sign bits + XMVECTOR vTemp1 = _mm_and_ps(M.r[0], g_XMAbsMask); + XMVECTOR vTemp2 = _mm_and_ps(M.r[1], g_XMAbsMask); + XMVECTOR vTemp3 = _mm_and_ps(M.r[2], g_XMAbsMask); + XMVECTOR vTemp4 = _mm_and_ps(M.r[3], g_XMAbsMask); + // Compare to infinity + vTemp1 = _mm_cmpeq_ps(vTemp1, g_XMInfinity); + vTemp2 = _mm_cmpeq_ps(vTemp2, g_XMInfinity); + vTemp3 = _mm_cmpeq_ps(vTemp3, g_XMInfinity); + vTemp4 = _mm_cmpeq_ps(vTemp4, g_XMInfinity); + // Or the answers together + vTemp1 = _mm_or_ps(vTemp1, vTemp2); + vTemp3 = _mm_or_ps(vTemp3, vTemp4); + vTemp1 = _mm_or_ps(vTemp1, vTemp3); + // If any are infinity, the signs are true. + return (_mm_movemask_ps(vTemp1) != 0); +#endif +} + +//------------------------------------------------------------------------------ + +// Return true if the XMMatrix is equal to identity +inline bool XM_CALLCONV XMMatrixIsIdentity(FXMMATRIX M) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + // Use the integer pipeline to reduce branching to a minimum + auto pWork = reinterpret_cast(&M.m[0][0]); + // Convert 1.0f to zero and or them together + uint32_t uOne = pWork[0] ^ 0x3F800000U; + // Or all the 0.0f entries together + uint32_t uZero = pWork[1]; + uZero |= pWork[2]; + uZero |= pWork[3]; + // 2nd row + uZero |= pWork[4]; + uOne |= pWork[5] ^ 0x3F800000U; + uZero |= pWork[6]; + uZero |= pWork[7]; + // 3rd row + uZero |= pWork[8]; + uZero |= pWork[9]; + uOne |= pWork[10] ^ 0x3F800000U; + uZero |= pWork[11]; + // 4th row + uZero |= pWork[12]; + uZero |= pWork[13]; + uZero |= pWork[14]; + uOne |= pWork[15] ^ 0x3F800000U; + // If all zero entries are zero, the uZero==0 + uZero &= 0x7FFFFFFF; // Allow -0.0f + // If all 1.0f entries are 1.0f, then uOne==0 + uOne |= uZero; + return (uOne == 0); +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x4_t xmask = vceqq_f32(M.r[0], g_XMIdentityR0); + uint32x4_t ymask = vceqq_f32(M.r[1], g_XMIdentityR1); + uint32x4_t zmask = vceqq_f32(M.r[2], g_XMIdentityR2); + uint32x4_t wmask = vceqq_f32(M.r[3], g_XMIdentityR3); + xmask = vandq_u32(xmask, zmask); + ymask = vandq_u32(ymask, wmask); + xmask = vandq_u32(xmask, ymask); + uint8x8x2_t vTemp = vzip_u8(vget_low_u8(vreinterpretq_u8_u32(xmask)), vget_high_u8(vreinterpretq_u8_u32(xmask))); + uint16x4x2_t vTemp2 = vzip_u16(vreinterpret_u16_u8(vTemp.val[0]), vreinterpret_u16_u8(vTemp.val[1])); + uint32_t r = vget_lane_u32(vreinterpret_u32_u16(vTemp2.val[1]), 1); + return (r == 0xFFFFFFFFU); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vTemp1 = _mm_cmpeq_ps(M.r[0], g_XMIdentityR0); + XMVECTOR vTemp2 = _mm_cmpeq_ps(M.r[1], g_XMIdentityR1); + XMVECTOR vTemp3 = _mm_cmpeq_ps(M.r[2], g_XMIdentityR2); + XMVECTOR vTemp4 = _mm_cmpeq_ps(M.r[3], g_XMIdentityR3); + vTemp1 = _mm_and_ps(vTemp1, vTemp2); + vTemp3 = _mm_and_ps(vTemp3, vTemp4); + vTemp1 = _mm_and_ps(vTemp1, vTemp3); + return (_mm_movemask_ps(vTemp1) == 0x0f); +#endif +} + +//------------------------------------------------------------------------------ +// Computation operations +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ +// Perform a 4x4 matrix multiply by a 4x4 matrix +inline XMMATRIX XM_CALLCONV XMMatrixMultiply +( + FXMMATRIX M1, + CXMMATRIX M2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + XMMATRIX mResult; + // Cache the invariants in registers + float x = M1.m[0][0]; + float y = M1.m[0][1]; + float z = M1.m[0][2]; + float w = M1.m[0][3]; + // Perform the operation on the first row + mResult.m[0][0] = (M2.m[0][0] * x) + (M2.m[1][0] * y) + (M2.m[2][0] * z) + (M2.m[3][0] * w); + mResult.m[0][1] = (M2.m[0][1] * x) + (M2.m[1][1] * y) + (M2.m[2][1] * z) + (M2.m[3][1] * w); + mResult.m[0][2] = (M2.m[0][2] * x) + (M2.m[1][2] * y) + (M2.m[2][2] * z) + (M2.m[3][2] * w); + mResult.m[0][3] = (M2.m[0][3] * x) + (M2.m[1][3] * y) + (M2.m[2][3] * z) + (M2.m[3][3] * w); + // Repeat for all the other rows + x = M1.m[1][0]; + y = M1.m[1][1]; + z = M1.m[1][2]; + w = M1.m[1][3]; + mResult.m[1][0] = (M2.m[0][0] * x) + (M2.m[1][0] * y) + (M2.m[2][0] * z) + (M2.m[3][0] * w); + mResult.m[1][1] = (M2.m[0][1] * x) + (M2.m[1][1] * y) + (M2.m[2][1] * z) + (M2.m[3][1] * w); + mResult.m[1][2] = (M2.m[0][2] * x) + (M2.m[1][2] * y) + (M2.m[2][2] * z) + (M2.m[3][2] * w); + mResult.m[1][3] = (M2.m[0][3] * x) + (M2.m[1][3] * y) + (M2.m[2][3] * z) + (M2.m[3][3] * w); + x = M1.m[2][0]; + y = M1.m[2][1]; + z = M1.m[2][2]; + w = M1.m[2][3]; + mResult.m[2][0] = (M2.m[0][0] * x) + (M2.m[1][0] * y) + (M2.m[2][0] * z) + (M2.m[3][0] * w); + mResult.m[2][1] = (M2.m[0][1] * x) + (M2.m[1][1] * y) + (M2.m[2][1] * z) + (M2.m[3][1] * w); + mResult.m[2][2] = (M2.m[0][2] * x) + (M2.m[1][2] * y) + (M2.m[2][2] * z) + (M2.m[3][2] * w); + mResult.m[2][3] = (M2.m[0][3] * x) + (M2.m[1][3] * y) + (M2.m[2][3] * z) + (M2.m[3][3] * w); + x = M1.m[3][0]; + y = M1.m[3][1]; + z = M1.m[3][2]; + w = M1.m[3][3]; + mResult.m[3][0] = (M2.m[0][0] * x) + (M2.m[1][0] * y) + (M2.m[2][0] * z) + (M2.m[3][0] * w); + mResult.m[3][1] = (M2.m[0][1] * x) + (M2.m[1][1] * y) + (M2.m[2][1] * z) + (M2.m[3][1] * w); + mResult.m[3][2] = (M2.m[0][2] * x) + (M2.m[1][2] * y) + (M2.m[2][2] * z) + (M2.m[3][2] * w); + mResult.m[3][3] = (M2.m[0][3] * x) + (M2.m[1][3] * y) + (M2.m[2][3] * z) + (M2.m[3][3] * w); + return mResult; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + XMMATRIX mResult; + float32x2_t VL = vget_low_f32(M1.r[0]); + float32x2_t VH = vget_high_f32(M1.r[0]); + // Perform the operation on the first row + float32x4_t vX = vmulq_lane_f32(M2.r[0], VL, 0); + float32x4_t vY = vmulq_lane_f32(M2.r[1], VL, 1); + float32x4_t vZ = vmlaq_lane_f32(vX, M2.r[2], VH, 0); + float32x4_t vW = vmlaq_lane_f32(vY, M2.r[3], VH, 1); + mResult.r[0] = vaddq_f32(vZ, vW); + // Repeat for the other 3 rows + VL = vget_low_f32(M1.r[1]); + VH = vget_high_f32(M1.r[1]); + vX = vmulq_lane_f32(M2.r[0], VL, 0); + vY = vmulq_lane_f32(M2.r[1], VL, 1); + vZ = vmlaq_lane_f32(vX, M2.r[2], VH, 0); + vW = vmlaq_lane_f32(vY, M2.r[3], VH, 1); + mResult.r[1] = vaddq_f32(vZ, vW); + VL = vget_low_f32(M1.r[2]); + VH = vget_high_f32(M1.r[2]); + vX = vmulq_lane_f32(M2.r[0], VL, 0); + vY = vmulq_lane_f32(M2.r[1], VL, 1); + vZ = vmlaq_lane_f32(vX, M2.r[2], VH, 0); + vW = vmlaq_lane_f32(vY, M2.r[3], VH, 1); + mResult.r[2] = vaddq_f32(vZ, vW); + VL = vget_low_f32(M1.r[3]); + VH = vget_high_f32(M1.r[3]); + vX = vmulq_lane_f32(M2.r[0], VL, 0); + vY = vmulq_lane_f32(M2.r[1], VL, 1); + vZ = vmlaq_lane_f32(vX, M2.r[2], VH, 0); + vW = vmlaq_lane_f32(vY, M2.r[3], VH, 1); + mResult.r[3] = vaddq_f32(vZ, vW); + return mResult; +#elif defined(_XM_AVX2_INTRINSICS_) + __m256 t0 = _mm256_castps128_ps256(M1.r[0]); + t0 = _mm256_insertf128_ps(t0, M1.r[1], 1); + __m256 t1 = _mm256_castps128_ps256(M1.r[2]); + t1 = _mm256_insertf128_ps(t1, M1.r[3], 1); + + __m256 u0 = _mm256_castps128_ps256(M2.r[0]); + u0 = _mm256_insertf128_ps(u0, M2.r[1], 1); + __m256 u1 = _mm256_castps128_ps256(M2.r[2]); + u1 = _mm256_insertf128_ps(u1, M2.r[3], 1); + + __m256 a0 = _mm256_shuffle_ps(t0, t0, _MM_SHUFFLE(0, 0, 0, 0)); + __m256 a1 = _mm256_shuffle_ps(t1, t1, _MM_SHUFFLE(0, 0, 0, 0)); + __m256 b0 = _mm256_permute2f128_ps(u0, u0, 0x00); + __m256 c0 = _mm256_mul_ps(a0, b0); + __m256 c1 = _mm256_mul_ps(a1, b0); + + a0 = _mm256_shuffle_ps(t0, t0, _MM_SHUFFLE(1, 1, 1, 1)); + a1 = _mm256_shuffle_ps(t1, t1, _MM_SHUFFLE(1, 1, 1, 1)); + b0 = _mm256_permute2f128_ps(u0, u0, 0x11); + __m256 c2 = _mm256_fmadd_ps(a0, b0, c0); + __m256 c3 = _mm256_fmadd_ps(a1, b0, c1); + + a0 = _mm256_shuffle_ps(t0, t0, _MM_SHUFFLE(2, 2, 2, 2)); + a1 = _mm256_shuffle_ps(t1, t1, _MM_SHUFFLE(2, 2, 2, 2)); + __m256 b1 = _mm256_permute2f128_ps(u1, u1, 0x00); + __m256 c4 = _mm256_mul_ps(a0, b1); + __m256 c5 = _mm256_mul_ps(a1, b1); + + a0 = _mm256_shuffle_ps(t0, t0, _MM_SHUFFLE(3, 3, 3, 3)); + a1 = _mm256_shuffle_ps(t1, t1, _MM_SHUFFLE(3, 3, 3, 3)); + b1 = _mm256_permute2f128_ps(u1, u1, 0x11); + __m256 c6 = _mm256_fmadd_ps(a0, b1, c4); + __m256 c7 = _mm256_fmadd_ps(a1, b1, c5); + + t0 = _mm256_add_ps(c2, c6); + t1 = _mm256_add_ps(c3, c7); + + XMMATRIX mResult; + mResult.r[0] = _mm256_castps256_ps128(t0); + mResult.r[1] = _mm256_extractf128_ps(t0, 1); + mResult.r[2] = _mm256_castps256_ps128(t1); + mResult.r[3] = _mm256_extractf128_ps(t1, 1); + return mResult; +#elif defined(_XM_SSE_INTRINSICS_) + XMMATRIX mResult; + // Splat the component X,Y,Z then W +#if defined(_XM_AVX_INTRINSICS_) + XMVECTOR vX = _mm_broadcast_ss(reinterpret_cast(&M1.r[0]) + 0); + XMVECTOR vY = _mm_broadcast_ss(reinterpret_cast(&M1.r[0]) + 1); + XMVECTOR vZ = _mm_broadcast_ss(reinterpret_cast(&M1.r[0]) + 2); + XMVECTOR vW = _mm_broadcast_ss(reinterpret_cast(&M1.r[0]) + 3); +#else + // Use vW to hold the original row + XMVECTOR vW = M1.r[0]; + XMVECTOR vX = XM_PERMUTE_PS(vW, _MM_SHUFFLE(0, 0, 0, 0)); + XMVECTOR vY = XM_PERMUTE_PS(vW, _MM_SHUFFLE(1, 1, 1, 1)); + XMVECTOR vZ = XM_PERMUTE_PS(vW, _MM_SHUFFLE(2, 2, 2, 2)); + vW = XM_PERMUTE_PS(vW, _MM_SHUFFLE(3, 3, 3, 3)); +#endif + // Perform the operation on the first row + vX = _mm_mul_ps(vX, M2.r[0]); + vY = _mm_mul_ps(vY, M2.r[1]); + vZ = _mm_mul_ps(vZ, M2.r[2]); + vW = _mm_mul_ps(vW, M2.r[3]); + // Perform a binary add to reduce cumulative errors + vX = _mm_add_ps(vX, vZ); + vY = _mm_add_ps(vY, vW); + vX = _mm_add_ps(vX, vY); + mResult.r[0] = vX; + // Repeat for the other 3 rows +#if defined(_XM_AVX_INTRINSICS_) + vX = _mm_broadcast_ss(reinterpret_cast(&M1.r[1]) + 0); + vY = _mm_broadcast_ss(reinterpret_cast(&M1.r[1]) + 1); + vZ = _mm_broadcast_ss(reinterpret_cast(&M1.r[1]) + 2); + vW = _mm_broadcast_ss(reinterpret_cast(&M1.r[1]) + 3); +#else + vW = M1.r[1]; + vX = XM_PERMUTE_PS(vW, _MM_SHUFFLE(0, 0, 0, 0)); + vY = XM_PERMUTE_PS(vW, _MM_SHUFFLE(1, 1, 1, 1)); + vZ = XM_PERMUTE_PS(vW, _MM_SHUFFLE(2, 2, 2, 2)); + vW = XM_PERMUTE_PS(vW, _MM_SHUFFLE(3, 3, 3, 3)); +#endif + vX = _mm_mul_ps(vX, M2.r[0]); + vY = _mm_mul_ps(vY, M2.r[1]); + vZ = _mm_mul_ps(vZ, M2.r[2]); + vW = _mm_mul_ps(vW, M2.r[3]); + vX = _mm_add_ps(vX, vZ); + vY = _mm_add_ps(vY, vW); + vX = _mm_add_ps(vX, vY); + mResult.r[1] = vX; +#if defined(_XM_AVX_INTRINSICS_) + vX = _mm_broadcast_ss(reinterpret_cast(&M1.r[2]) + 0); + vY = _mm_broadcast_ss(reinterpret_cast(&M1.r[2]) + 1); + vZ = _mm_broadcast_ss(reinterpret_cast(&M1.r[2]) + 2); + vW = _mm_broadcast_ss(reinterpret_cast(&M1.r[2]) + 3); +#else + vW = M1.r[2]; + vX = XM_PERMUTE_PS(vW, _MM_SHUFFLE(0, 0, 0, 0)); + vY = XM_PERMUTE_PS(vW, _MM_SHUFFLE(1, 1, 1, 1)); + vZ = XM_PERMUTE_PS(vW, _MM_SHUFFLE(2, 2, 2, 2)); + vW = XM_PERMUTE_PS(vW, _MM_SHUFFLE(3, 3, 3, 3)); +#endif + vX = _mm_mul_ps(vX, M2.r[0]); + vY = _mm_mul_ps(vY, M2.r[1]); + vZ = _mm_mul_ps(vZ, M2.r[2]); + vW = _mm_mul_ps(vW, M2.r[3]); + vX = _mm_add_ps(vX, vZ); + vY = _mm_add_ps(vY, vW); + vX = _mm_add_ps(vX, vY); + mResult.r[2] = vX; +#if defined(_XM_AVX_INTRINSICS_) + vX = _mm_broadcast_ss(reinterpret_cast(&M1.r[3]) + 0); + vY = _mm_broadcast_ss(reinterpret_cast(&M1.r[3]) + 1); + vZ = _mm_broadcast_ss(reinterpret_cast(&M1.r[3]) + 2); + vW = _mm_broadcast_ss(reinterpret_cast(&M1.r[3]) + 3); +#else + vW = M1.r[3]; + vX = XM_PERMUTE_PS(vW, _MM_SHUFFLE(0, 0, 0, 0)); + vY = XM_PERMUTE_PS(vW, _MM_SHUFFLE(1, 1, 1, 1)); + vZ = XM_PERMUTE_PS(vW, _MM_SHUFFLE(2, 2, 2, 2)); + vW = XM_PERMUTE_PS(vW, _MM_SHUFFLE(3, 3, 3, 3)); +#endif + vX = _mm_mul_ps(vX, M2.r[0]); + vY = _mm_mul_ps(vY, M2.r[1]); + vZ = _mm_mul_ps(vZ, M2.r[2]); + vW = _mm_mul_ps(vW, M2.r[3]); + vX = _mm_add_ps(vX, vZ); + vY = _mm_add_ps(vY, vW); + vX = _mm_add_ps(vX, vY); + mResult.r[3] = vX; + return mResult; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMMATRIX XM_CALLCONV XMMatrixMultiplyTranspose +( + FXMMATRIX M1, + CXMMATRIX M2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + XMMATRIX mResult; + // Cache the invariants in registers + float x = M2.m[0][0]; + float y = M2.m[1][0]; + float z = M2.m[2][0]; + float w = M2.m[3][0]; + // Perform the operation on the first row + mResult.m[0][0] = (M1.m[0][0] * x) + (M1.m[0][1] * y) + (M1.m[0][2] * z) + (M1.m[0][3] * w); + mResult.m[0][1] = (M1.m[1][0] * x) + (M1.m[1][1] * y) + (M1.m[1][2] * z) + (M1.m[1][3] * w); + mResult.m[0][2] = (M1.m[2][0] * x) + (M1.m[2][1] * y) + (M1.m[2][2] * z) + (M1.m[2][3] * w); + mResult.m[0][3] = (M1.m[3][0] * x) + (M1.m[3][1] * y) + (M1.m[3][2] * z) + (M1.m[3][3] * w); + // Repeat for all the other rows + x = M2.m[0][1]; + y = M2.m[1][1]; + z = M2.m[2][1]; + w = M2.m[3][1]; + mResult.m[1][0] = (M1.m[0][0] * x) + (M1.m[0][1] * y) + (M1.m[0][2] * z) + (M1.m[0][3] * w); + mResult.m[1][1] = (M1.m[1][0] * x) + (M1.m[1][1] * y) + (M1.m[1][2] * z) + (M1.m[1][3] * w); + mResult.m[1][2] = (M1.m[2][0] * x) + (M1.m[2][1] * y) + (M1.m[2][2] * z) + (M1.m[2][3] * w); + mResult.m[1][3] = (M1.m[3][0] * x) + (M1.m[3][1] * y) + (M1.m[3][2] * z) + (M1.m[3][3] * w); + x = M2.m[0][2]; + y = M2.m[1][2]; + z = M2.m[2][2]; + w = M2.m[3][2]; + mResult.m[2][0] = (M1.m[0][0] * x) + (M1.m[0][1] * y) + (M1.m[0][2] * z) + (M1.m[0][3] * w); + mResult.m[2][1] = (M1.m[1][0] * x) + (M1.m[1][1] * y) + (M1.m[1][2] * z) + (M1.m[1][3] * w); + mResult.m[2][2] = (M1.m[2][0] * x) + (M1.m[2][1] * y) + (M1.m[2][2] * z) + (M1.m[2][3] * w); + mResult.m[2][3] = (M1.m[3][0] * x) + (M1.m[3][1] * y) + (M1.m[3][2] * z) + (M1.m[3][3] * w); + x = M2.m[0][3]; + y = M2.m[1][3]; + z = M2.m[2][3]; + w = M2.m[3][3]; + mResult.m[3][0] = (M1.m[0][0] * x) + (M1.m[0][1] * y) + (M1.m[0][2] * z) + (M1.m[0][3] * w); + mResult.m[3][1] = (M1.m[1][0] * x) + (M1.m[1][1] * y) + (M1.m[1][2] * z) + (M1.m[1][3] * w); + mResult.m[3][2] = (M1.m[2][0] * x) + (M1.m[2][1] * y) + (M1.m[2][2] * z) + (M1.m[2][3] * w); + mResult.m[3][3] = (M1.m[3][0] * x) + (M1.m[3][1] * y) + (M1.m[3][2] * z) + (M1.m[3][3] * w); + return mResult; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x2_t VL = vget_low_f32(M1.r[0]); + float32x2_t VH = vget_high_f32(M1.r[0]); + // Perform the operation on the first row + float32x4_t vX = vmulq_lane_f32(M2.r[0], VL, 0); + float32x4_t vY = vmulq_lane_f32(M2.r[1], VL, 1); + float32x4_t vZ = vmlaq_lane_f32(vX, M2.r[2], VH, 0); + float32x4_t vW = vmlaq_lane_f32(vY, M2.r[3], VH, 1); + float32x4_t r0 = vaddq_f32(vZ, vW); + // Repeat for the other 3 rows + VL = vget_low_f32(M1.r[1]); + VH = vget_high_f32(M1.r[1]); + vX = vmulq_lane_f32(M2.r[0], VL, 0); + vY = vmulq_lane_f32(M2.r[1], VL, 1); + vZ = vmlaq_lane_f32(vX, M2.r[2], VH, 0); + vW = vmlaq_lane_f32(vY, M2.r[3], VH, 1); + float32x4_t r1 = vaddq_f32(vZ, vW); + VL = vget_low_f32(M1.r[2]); + VH = vget_high_f32(M1.r[2]); + vX = vmulq_lane_f32(M2.r[0], VL, 0); + vY = vmulq_lane_f32(M2.r[1], VL, 1); + vZ = vmlaq_lane_f32(vX, M2.r[2], VH, 0); + vW = vmlaq_lane_f32(vY, M2.r[3], VH, 1); + float32x4_t r2 = vaddq_f32(vZ, vW); + VL = vget_low_f32(M1.r[3]); + VH = vget_high_f32(M1.r[3]); + vX = vmulq_lane_f32(M2.r[0], VL, 0); + vY = vmulq_lane_f32(M2.r[1], VL, 1); + vZ = vmlaq_lane_f32(vX, M2.r[2], VH, 0); + vW = vmlaq_lane_f32(vY, M2.r[3], VH, 1); + float32x4_t r3 = vaddq_f32(vZ, vW); + + // Transpose result + float32x4x2_t P0 = vzipq_f32(r0, r2); + float32x4x2_t P1 = vzipq_f32(r1, r3); + + float32x4x2_t T0 = vzipq_f32(P0.val[0], P1.val[0]); + float32x4x2_t T1 = vzipq_f32(P0.val[1], P1.val[1]); + + XMMATRIX mResult; + mResult.r[0] = T0.val[0]; + mResult.r[1] = T0.val[1]; + mResult.r[2] = T1.val[0]; + mResult.r[3] = T1.val[1]; + return mResult; +#elif defined(_XM_AVX2_INTRINSICS_) + __m256 t0 = _mm256_castps128_ps256(M1.r[0]); + t0 = _mm256_insertf128_ps(t0, M1.r[1], 1); + __m256 t1 = _mm256_castps128_ps256(M1.r[2]); + t1 = _mm256_insertf128_ps(t1, M1.r[3], 1); + + __m256 u0 = _mm256_castps128_ps256(M2.r[0]); + u0 = _mm256_insertf128_ps(u0, M2.r[1], 1); + __m256 u1 = _mm256_castps128_ps256(M2.r[2]); + u1 = _mm256_insertf128_ps(u1, M2.r[3], 1); + + __m256 a0 = _mm256_shuffle_ps(t0, t0, _MM_SHUFFLE(0, 0, 0, 0)); + __m256 a1 = _mm256_shuffle_ps(t1, t1, _MM_SHUFFLE(0, 0, 0, 0)); + __m256 b0 = _mm256_permute2f128_ps(u0, u0, 0x00); + __m256 c0 = _mm256_mul_ps(a0, b0); + __m256 c1 = _mm256_mul_ps(a1, b0); + + a0 = _mm256_shuffle_ps(t0, t0, _MM_SHUFFLE(1, 1, 1, 1)); + a1 = _mm256_shuffle_ps(t1, t1, _MM_SHUFFLE(1, 1, 1, 1)); + b0 = _mm256_permute2f128_ps(u0, u0, 0x11); + __m256 c2 = _mm256_fmadd_ps(a0, b0, c0); + __m256 c3 = _mm256_fmadd_ps(a1, b0, c1); + + a0 = _mm256_shuffle_ps(t0, t0, _MM_SHUFFLE(2, 2, 2, 2)); + a1 = _mm256_shuffle_ps(t1, t1, _MM_SHUFFLE(2, 2, 2, 2)); + __m256 b1 = _mm256_permute2f128_ps(u1, u1, 0x00); + __m256 c4 = _mm256_mul_ps(a0, b1); + __m256 c5 = _mm256_mul_ps(a1, b1); + + a0 = _mm256_shuffle_ps(t0, t0, _MM_SHUFFLE(3, 3, 3, 3)); + a1 = _mm256_shuffle_ps(t1, t1, _MM_SHUFFLE(3, 3, 3, 3)); + b1 = _mm256_permute2f128_ps(u1, u1, 0x11); + __m256 c6 = _mm256_fmadd_ps(a0, b1, c4); + __m256 c7 = _mm256_fmadd_ps(a1, b1, c5); + + t0 = _mm256_add_ps(c2, c6); + t1 = _mm256_add_ps(c3, c7); + + // Transpose result + __m256 vTemp = _mm256_unpacklo_ps(t0, t1); + __m256 vTemp2 = _mm256_unpackhi_ps(t0, t1); + __m256 vTemp3 = _mm256_permute2f128_ps(vTemp, vTemp2, 0x20); + __m256 vTemp4 = _mm256_permute2f128_ps(vTemp, vTemp2, 0x31); + vTemp = _mm256_unpacklo_ps(vTemp3, vTemp4); + vTemp2 = _mm256_unpackhi_ps(vTemp3, vTemp4); + t0 = _mm256_permute2f128_ps(vTemp, vTemp2, 0x20); + t1 = _mm256_permute2f128_ps(vTemp, vTemp2, 0x31); + + XMMATRIX mResult; + mResult.r[0] = _mm256_castps256_ps128(t0); + mResult.r[1] = _mm256_extractf128_ps(t0, 1); + mResult.r[2] = _mm256_castps256_ps128(t1); + mResult.r[3] = _mm256_extractf128_ps(t1, 1); + return mResult; +#elif defined(_XM_SSE_INTRINSICS_) + // Splat the component X,Y,Z then W +#if defined(_XM_AVX_INTRINSICS_) + XMVECTOR vX = _mm_broadcast_ss(reinterpret_cast(&M1.r[0]) + 0); + XMVECTOR vY = _mm_broadcast_ss(reinterpret_cast(&M1.r[0]) + 1); + XMVECTOR vZ = _mm_broadcast_ss(reinterpret_cast(&M1.r[0]) + 2); + XMVECTOR vW = _mm_broadcast_ss(reinterpret_cast(&M1.r[0]) + 3); +#else + // Use vW to hold the original row + XMVECTOR vW = M1.r[0]; + XMVECTOR vX = XM_PERMUTE_PS(vW, _MM_SHUFFLE(0, 0, 0, 0)); + XMVECTOR vY = XM_PERMUTE_PS(vW, _MM_SHUFFLE(1, 1, 1, 1)); + XMVECTOR vZ = XM_PERMUTE_PS(vW, _MM_SHUFFLE(2, 2, 2, 2)); + vW = XM_PERMUTE_PS(vW, _MM_SHUFFLE(3, 3, 3, 3)); +#endif + // Perform the operation on the first row + vX = _mm_mul_ps(vX, M2.r[0]); + vY = _mm_mul_ps(vY, M2.r[1]); + vZ = _mm_mul_ps(vZ, M2.r[2]); + vW = _mm_mul_ps(vW, M2.r[3]); + // Perform a binary add to reduce cumulative errors + vX = _mm_add_ps(vX, vZ); + vY = _mm_add_ps(vY, vW); + vX = _mm_add_ps(vX, vY); + XMVECTOR r0 = vX; + // Repeat for the other 3 rows +#if defined(_XM_AVX_INTRINSICS_) + vX = _mm_broadcast_ss(reinterpret_cast(&M1.r[1]) + 0); + vY = _mm_broadcast_ss(reinterpret_cast(&M1.r[1]) + 1); + vZ = _mm_broadcast_ss(reinterpret_cast(&M1.r[1]) + 2); + vW = _mm_broadcast_ss(reinterpret_cast(&M1.r[1]) + 3); +#else + vW = M1.r[1]; + vX = XM_PERMUTE_PS(vW, _MM_SHUFFLE(0, 0, 0, 0)); + vY = XM_PERMUTE_PS(vW, _MM_SHUFFLE(1, 1, 1, 1)); + vZ = XM_PERMUTE_PS(vW, _MM_SHUFFLE(2, 2, 2, 2)); + vW = XM_PERMUTE_PS(vW, _MM_SHUFFLE(3, 3, 3, 3)); +#endif + vX = _mm_mul_ps(vX, M2.r[0]); + vY = _mm_mul_ps(vY, M2.r[1]); + vZ = _mm_mul_ps(vZ, M2.r[2]); + vW = _mm_mul_ps(vW, M2.r[3]); + vX = _mm_add_ps(vX, vZ); + vY = _mm_add_ps(vY, vW); + vX = _mm_add_ps(vX, vY); + XMVECTOR r1 = vX; +#if defined(_XM_AVX_INTRINSICS_) + vX = _mm_broadcast_ss(reinterpret_cast(&M1.r[2]) + 0); + vY = _mm_broadcast_ss(reinterpret_cast(&M1.r[2]) + 1); + vZ = _mm_broadcast_ss(reinterpret_cast(&M1.r[2]) + 2); + vW = _mm_broadcast_ss(reinterpret_cast(&M1.r[2]) + 3); +#else + vW = M1.r[2]; + vX = XM_PERMUTE_PS(vW, _MM_SHUFFLE(0, 0, 0, 0)); + vY = XM_PERMUTE_PS(vW, _MM_SHUFFLE(1, 1, 1, 1)); + vZ = XM_PERMUTE_PS(vW, _MM_SHUFFLE(2, 2, 2, 2)); + vW = XM_PERMUTE_PS(vW, _MM_SHUFFLE(3, 3, 3, 3)); +#endif + vX = _mm_mul_ps(vX, M2.r[0]); + vY = _mm_mul_ps(vY, M2.r[1]); + vZ = _mm_mul_ps(vZ, M2.r[2]); + vW = _mm_mul_ps(vW, M2.r[3]); + vX = _mm_add_ps(vX, vZ); + vY = _mm_add_ps(vY, vW); + vX = _mm_add_ps(vX, vY); + XMVECTOR r2 = vX; +#if defined(_XM_AVX_INTRINSICS_) + vX = _mm_broadcast_ss(reinterpret_cast(&M1.r[3]) + 0); + vY = _mm_broadcast_ss(reinterpret_cast(&M1.r[3]) + 1); + vZ = _mm_broadcast_ss(reinterpret_cast(&M1.r[3]) + 2); + vW = _mm_broadcast_ss(reinterpret_cast(&M1.r[3]) + 3); +#else + vW = M1.r[3]; + vX = XM_PERMUTE_PS(vW, _MM_SHUFFLE(0, 0, 0, 0)); + vY = XM_PERMUTE_PS(vW, _MM_SHUFFLE(1, 1, 1, 1)); + vZ = XM_PERMUTE_PS(vW, _MM_SHUFFLE(2, 2, 2, 2)); + vW = XM_PERMUTE_PS(vW, _MM_SHUFFLE(3, 3, 3, 3)); +#endif + vX = _mm_mul_ps(vX, M2.r[0]); + vY = _mm_mul_ps(vY, M2.r[1]); + vZ = _mm_mul_ps(vZ, M2.r[2]); + vW = _mm_mul_ps(vW, M2.r[3]); + vX = _mm_add_ps(vX, vZ); + vY = _mm_add_ps(vY, vW); + vX = _mm_add_ps(vX, vY); + XMVECTOR r3 = vX; + + // Transpose result + // x.x,x.y,y.x,y.y + XMVECTOR vTemp1 = _mm_shuffle_ps(r0, r1, _MM_SHUFFLE(1, 0, 1, 0)); + // x.z,x.w,y.z,y.w + XMVECTOR vTemp3 = _mm_shuffle_ps(r0, r1, _MM_SHUFFLE(3, 2, 3, 2)); + // z.x,z.y,w.x,w.y + XMVECTOR vTemp2 = _mm_shuffle_ps(r2, r3, _MM_SHUFFLE(1, 0, 1, 0)); + // z.z,z.w,w.z,w.w + XMVECTOR vTemp4 = _mm_shuffle_ps(r2, r3, _MM_SHUFFLE(3, 2, 3, 2)); + + XMMATRIX mResult; + // x.x,y.x,z.x,w.x + mResult.r[0] = _mm_shuffle_ps(vTemp1, vTemp2, _MM_SHUFFLE(2, 0, 2, 0)); + // x.y,y.y,z.y,w.y + mResult.r[1] = _mm_shuffle_ps(vTemp1, vTemp2, _MM_SHUFFLE(3, 1, 3, 1)); + // x.z,y.z,z.z,w.z + mResult.r[2] = _mm_shuffle_ps(vTemp3, vTemp4, _MM_SHUFFLE(2, 0, 2, 0)); + // x.w,y.w,z.w,w.w + mResult.r[3] = _mm_shuffle_ps(vTemp3, vTemp4, _MM_SHUFFLE(3, 1, 3, 1)); + return mResult; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMMATRIX XM_CALLCONV XMMatrixTranspose(FXMMATRIX M) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + // Original matrix: + // + // m00m01m02m03 + // m10m11m12m13 + // m20m21m22m23 + // m30m31m32m33 + + XMMATRIX P; + P.r[0] = XMVectorMergeXY(M.r[0], M.r[2]); // m00m20m01m21 + P.r[1] = XMVectorMergeXY(M.r[1], M.r[3]); // m10m30m11m31 + P.r[2] = XMVectorMergeZW(M.r[0], M.r[2]); // m02m22m03m23 + P.r[3] = XMVectorMergeZW(M.r[1], M.r[3]); // m12m32m13m33 + + XMMATRIX MT; + MT.r[0] = XMVectorMergeXY(P.r[0], P.r[1]); // m00m10m20m30 + MT.r[1] = XMVectorMergeZW(P.r[0], P.r[1]); // m01m11m21m31 + MT.r[2] = XMVectorMergeXY(P.r[2], P.r[3]); // m02m12m22m32 + MT.r[3] = XMVectorMergeZW(P.r[2], P.r[3]); // m03m13m23m33 + return MT; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x4x2_t P0 = vzipq_f32(M.r[0], M.r[2]); + float32x4x2_t P1 = vzipq_f32(M.r[1], M.r[3]); + + float32x4x2_t T0 = vzipq_f32(P0.val[0], P1.val[0]); + float32x4x2_t T1 = vzipq_f32(P0.val[1], P1.val[1]); + + XMMATRIX mResult; + mResult.r[0] = T0.val[0]; + mResult.r[1] = T0.val[1]; + mResult.r[2] = T1.val[0]; + mResult.r[3] = T1.val[1]; + return mResult; +#elif defined(_XM_AVX2_INTRINSICS_) + __m256 t0 = _mm256_castps128_ps256(M.r[0]); + t0 = _mm256_insertf128_ps(t0, M.r[1], 1); + __m256 t1 = _mm256_castps128_ps256(M.r[2]); + t1 = _mm256_insertf128_ps(t1, M.r[3], 1); + + __m256 vTemp = _mm256_unpacklo_ps(t0, t1); + __m256 vTemp2 = _mm256_unpackhi_ps(t0, t1); + __m256 vTemp3 = _mm256_permute2f128_ps(vTemp, vTemp2, 0x20); + __m256 vTemp4 = _mm256_permute2f128_ps(vTemp, vTemp2, 0x31); + vTemp = _mm256_unpacklo_ps(vTemp3, vTemp4); + vTemp2 = _mm256_unpackhi_ps(vTemp3, vTemp4); + t0 = _mm256_permute2f128_ps(vTemp, vTemp2, 0x20); + t1 = _mm256_permute2f128_ps(vTemp, vTemp2, 0x31); + + XMMATRIX mResult; + mResult.r[0] = _mm256_castps256_ps128(t0); + mResult.r[1] = _mm256_extractf128_ps(t0, 1); + mResult.r[2] = _mm256_castps256_ps128(t1); + mResult.r[3] = _mm256_extractf128_ps(t1, 1); + return mResult; +#elif defined(_XM_SSE_INTRINSICS_) + // x.x,x.y,y.x,y.y + XMVECTOR vTemp1 = _mm_shuffle_ps(M.r[0], M.r[1], _MM_SHUFFLE(1, 0, 1, 0)); + // x.z,x.w,y.z,y.w + XMVECTOR vTemp3 = _mm_shuffle_ps(M.r[0], M.r[1], _MM_SHUFFLE(3, 2, 3, 2)); + // z.x,z.y,w.x,w.y + XMVECTOR vTemp2 = _mm_shuffle_ps(M.r[2], M.r[3], _MM_SHUFFLE(1, 0, 1, 0)); + // z.z,z.w,w.z,w.w + XMVECTOR vTemp4 = _mm_shuffle_ps(M.r[2], M.r[3], _MM_SHUFFLE(3, 2, 3, 2)); + + XMMATRIX mResult; + // x.x,y.x,z.x,w.x + mResult.r[0] = _mm_shuffle_ps(vTemp1, vTemp2, _MM_SHUFFLE(2, 0, 2, 0)); + // x.y,y.y,z.y,w.y + mResult.r[1] = _mm_shuffle_ps(vTemp1, vTemp2, _MM_SHUFFLE(3, 1, 3, 1)); + // x.z,y.z,z.z,w.z + mResult.r[2] = _mm_shuffle_ps(vTemp3, vTemp4, _MM_SHUFFLE(2, 0, 2, 0)); + // x.w,y.w,z.w,w.w + mResult.r[3] = _mm_shuffle_ps(vTemp3, vTemp4, _MM_SHUFFLE(3, 1, 3, 1)); + return mResult; +#endif +} + +//------------------------------------------------------------------------------ +// Return the inverse and the determinant of a 4x4 matrix +_Use_decl_annotations_ +inline XMMATRIX XM_CALLCONV XMMatrixInverse +( + XMVECTOR* pDeterminant, + FXMMATRIX M +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) || defined(_XM_ARM_NEON_INTRINSICS_) + + XMMATRIX MT = XMMatrixTranspose(M); + + XMVECTOR V0[4], V1[4]; + V0[0] = XMVectorSwizzle(MT.r[2]); + V1[0] = XMVectorSwizzle(MT.r[3]); + V0[1] = XMVectorSwizzle(MT.r[0]); + V1[1] = XMVectorSwizzle(MT.r[1]); + V0[2] = XMVectorPermute(MT.r[2], MT.r[0]); + V1[2] = XMVectorPermute(MT.r[3], MT.r[1]); + + XMVECTOR D0 = XMVectorMultiply(V0[0], V1[0]); + XMVECTOR D1 = XMVectorMultiply(V0[1], V1[1]); + XMVECTOR D2 = XMVectorMultiply(V0[2], V1[2]); + + V0[0] = XMVectorSwizzle(MT.r[2]); + V1[0] = XMVectorSwizzle(MT.r[3]); + V0[1] = XMVectorSwizzle(MT.r[0]); + V1[1] = XMVectorSwizzle(MT.r[1]); + V0[2] = XMVectorPermute(MT.r[2], MT.r[0]); + V1[2] = XMVectorPermute(MT.r[3], MT.r[1]); + + D0 = XMVectorNegativeMultiplySubtract(V0[0], V1[0], D0); + D1 = XMVectorNegativeMultiplySubtract(V0[1], V1[1], D1); + D2 = XMVectorNegativeMultiplySubtract(V0[2], V1[2], D2); + + V0[0] = XMVectorSwizzle(MT.r[1]); + V1[0] = XMVectorPermute(D0, D2); + V0[1] = XMVectorSwizzle(MT.r[0]); + V1[1] = XMVectorPermute(D0, D2); + V0[2] = XMVectorSwizzle(MT.r[3]); + V1[2] = XMVectorPermute(D1, D2); + V0[3] = XMVectorSwizzle(MT.r[2]); + V1[3] = XMVectorPermute(D1, D2); + + XMVECTOR C0 = XMVectorMultiply(V0[0], V1[0]); + XMVECTOR C2 = XMVectorMultiply(V0[1], V1[1]); + XMVECTOR C4 = XMVectorMultiply(V0[2], V1[2]); + XMVECTOR C6 = XMVectorMultiply(V0[3], V1[3]); + + V0[0] = XMVectorSwizzle(MT.r[1]); + V1[0] = XMVectorPermute(D0, D2); + V0[1] = XMVectorSwizzle(MT.r[0]); + V1[1] = XMVectorPermute(D0, D2); + V0[2] = XMVectorSwizzle(MT.r[3]); + V1[2] = XMVectorPermute(D1, D2); + V0[3] = XMVectorSwizzle(MT.r[2]); + V1[3] = XMVectorPermute(D1, D2); + + C0 = XMVectorNegativeMultiplySubtract(V0[0], V1[0], C0); + C2 = XMVectorNegativeMultiplySubtract(V0[1], V1[1], C2); + C4 = XMVectorNegativeMultiplySubtract(V0[2], V1[2], C4); + C6 = XMVectorNegativeMultiplySubtract(V0[3], V1[3], C6); + + V0[0] = XMVectorSwizzle(MT.r[1]); + V1[0] = XMVectorPermute(D0, D2); + V0[1] = XMVectorSwizzle(MT.r[0]); + V1[1] = XMVectorPermute(D0, D2); + V0[2] = XMVectorSwizzle(MT.r[3]); + V1[2] = XMVectorPermute(D1, D2); + V0[3] = XMVectorSwizzle(MT.r[2]); + V1[3] = XMVectorPermute(D1, D2); + + XMVECTOR C1 = XMVectorNegativeMultiplySubtract(V0[0], V1[0], C0); + C0 = XMVectorMultiplyAdd(V0[0], V1[0], C0); + XMVECTOR C3 = XMVectorMultiplyAdd(V0[1], V1[1], C2); + C2 = XMVectorNegativeMultiplySubtract(V0[1], V1[1], C2); + XMVECTOR C5 = XMVectorNegativeMultiplySubtract(V0[2], V1[2], C4); + C4 = XMVectorMultiplyAdd(V0[2], V1[2], C4); + XMVECTOR C7 = XMVectorMultiplyAdd(V0[3], V1[3], C6); + C6 = XMVectorNegativeMultiplySubtract(V0[3], V1[3], C6); + + XMMATRIX R; + R.r[0] = XMVectorSelect(C0, C1, g_XMSelect0101.v); + R.r[1] = XMVectorSelect(C2, C3, g_XMSelect0101.v); + R.r[2] = XMVectorSelect(C4, C5, g_XMSelect0101.v); + R.r[3] = XMVectorSelect(C6, C7, g_XMSelect0101.v); + + XMVECTOR Determinant = XMVector4Dot(R.r[0], MT.r[0]); + + if (pDeterminant != nullptr) + *pDeterminant = Determinant; + + XMVECTOR Reciprocal = XMVectorReciprocal(Determinant); + + XMMATRIX Result; + Result.r[0] = XMVectorMultiply(R.r[0], Reciprocal); + Result.r[1] = XMVectorMultiply(R.r[1], Reciprocal); + Result.r[2] = XMVectorMultiply(R.r[2], Reciprocal); + Result.r[3] = XMVectorMultiply(R.r[3], Reciprocal); + return Result; + +#elif defined(_XM_SSE_INTRINSICS_) + // Transpose matrix + XMVECTOR vTemp1 = _mm_shuffle_ps(M.r[0], M.r[1], _MM_SHUFFLE(1, 0, 1, 0)); + XMVECTOR vTemp3 = _mm_shuffle_ps(M.r[0], M.r[1], _MM_SHUFFLE(3, 2, 3, 2)); + XMVECTOR vTemp2 = _mm_shuffle_ps(M.r[2], M.r[3], _MM_SHUFFLE(1, 0, 1, 0)); + XMVECTOR vTemp4 = _mm_shuffle_ps(M.r[2], M.r[3], _MM_SHUFFLE(3, 2, 3, 2)); + + XMMATRIX MT; + MT.r[0] = _mm_shuffle_ps(vTemp1, vTemp2, _MM_SHUFFLE(2, 0, 2, 0)); + MT.r[1] = _mm_shuffle_ps(vTemp1, vTemp2, _MM_SHUFFLE(3, 1, 3, 1)); + MT.r[2] = _mm_shuffle_ps(vTemp3, vTemp4, _MM_SHUFFLE(2, 0, 2, 0)); + MT.r[3] = _mm_shuffle_ps(vTemp3, vTemp4, _MM_SHUFFLE(3, 1, 3, 1)); + + XMVECTOR V00 = XM_PERMUTE_PS(MT.r[2], _MM_SHUFFLE(1, 1, 0, 0)); + XMVECTOR V10 = XM_PERMUTE_PS(MT.r[3], _MM_SHUFFLE(3, 2, 3, 2)); + XMVECTOR V01 = XM_PERMUTE_PS(MT.r[0], _MM_SHUFFLE(1, 1, 0, 0)); + XMVECTOR V11 = XM_PERMUTE_PS(MT.r[1], _MM_SHUFFLE(3, 2, 3, 2)); + XMVECTOR V02 = _mm_shuffle_ps(MT.r[2], MT.r[0], _MM_SHUFFLE(2, 0, 2, 0)); + XMVECTOR V12 = _mm_shuffle_ps(MT.r[3], MT.r[1], _MM_SHUFFLE(3, 1, 3, 1)); + + XMVECTOR D0 = _mm_mul_ps(V00, V10); + XMVECTOR D1 = _mm_mul_ps(V01, V11); + XMVECTOR D2 = _mm_mul_ps(V02, V12); + + V00 = XM_PERMUTE_PS(MT.r[2], _MM_SHUFFLE(3, 2, 3, 2)); + V10 = XM_PERMUTE_PS(MT.r[3], _MM_SHUFFLE(1, 1, 0, 0)); + V01 = XM_PERMUTE_PS(MT.r[0], _MM_SHUFFLE(3, 2, 3, 2)); + V11 = XM_PERMUTE_PS(MT.r[1], _MM_SHUFFLE(1, 1, 0, 0)); + V02 = _mm_shuffle_ps(MT.r[2], MT.r[0], _MM_SHUFFLE(3, 1, 3, 1)); + V12 = _mm_shuffle_ps(MT.r[3], MT.r[1], _MM_SHUFFLE(2, 0, 2, 0)); + + D0 = XM_FNMADD_PS(V00, V10, D0); + D1 = XM_FNMADD_PS(V01, V11, D1); + D2 = XM_FNMADD_PS(V02, V12, D2); + // V11 = D0Y,D0W,D2Y,D2Y + V11 = _mm_shuffle_ps(D0, D2, _MM_SHUFFLE(1, 1, 3, 1)); + V00 = XM_PERMUTE_PS(MT.r[1], _MM_SHUFFLE(1, 0, 2, 1)); + V10 = _mm_shuffle_ps(V11, D0, _MM_SHUFFLE(0, 3, 0, 2)); + V01 = XM_PERMUTE_PS(MT.r[0], _MM_SHUFFLE(0, 1, 0, 2)); + V11 = _mm_shuffle_ps(V11, D0, _MM_SHUFFLE(2, 1, 2, 1)); + // V13 = D1Y,D1W,D2W,D2W + XMVECTOR V13 = _mm_shuffle_ps(D1, D2, _MM_SHUFFLE(3, 3, 3, 1)); + V02 = XM_PERMUTE_PS(MT.r[3], _MM_SHUFFLE(1, 0, 2, 1)); + V12 = _mm_shuffle_ps(V13, D1, _MM_SHUFFLE(0, 3, 0, 2)); + XMVECTOR V03 = XM_PERMUTE_PS(MT.r[2], _MM_SHUFFLE(0, 1, 0, 2)); + V13 = _mm_shuffle_ps(V13, D1, _MM_SHUFFLE(2, 1, 2, 1)); + + XMVECTOR C0 = _mm_mul_ps(V00, V10); + XMVECTOR C2 = _mm_mul_ps(V01, V11); + XMVECTOR C4 = _mm_mul_ps(V02, V12); + XMVECTOR C6 = _mm_mul_ps(V03, V13); + + // V11 = D0X,D0Y,D2X,D2X + V11 = _mm_shuffle_ps(D0, D2, _MM_SHUFFLE(0, 0, 1, 0)); + V00 = XM_PERMUTE_PS(MT.r[1], _MM_SHUFFLE(2, 1, 3, 2)); + V10 = _mm_shuffle_ps(D0, V11, _MM_SHUFFLE(2, 1, 0, 3)); + V01 = XM_PERMUTE_PS(MT.r[0], _MM_SHUFFLE(1, 3, 2, 3)); + V11 = _mm_shuffle_ps(D0, V11, _MM_SHUFFLE(0, 2, 1, 2)); + // V13 = D1X,D1Y,D2Z,D2Z + V13 = _mm_shuffle_ps(D1, D2, _MM_SHUFFLE(2, 2, 1, 0)); + V02 = XM_PERMUTE_PS(MT.r[3], _MM_SHUFFLE(2, 1, 3, 2)); + V12 = _mm_shuffle_ps(D1, V13, _MM_SHUFFLE(2, 1, 0, 3)); + V03 = XM_PERMUTE_PS(MT.r[2], _MM_SHUFFLE(1, 3, 2, 3)); + V13 = _mm_shuffle_ps(D1, V13, _MM_SHUFFLE(0, 2, 1, 2)); + + C0 = XM_FNMADD_PS(V00, V10, C0); + C2 = XM_FNMADD_PS(V01, V11, C2); + C4 = XM_FNMADD_PS(V02, V12, C4); + C6 = XM_FNMADD_PS(V03, V13, C6); + + V00 = XM_PERMUTE_PS(MT.r[1], _MM_SHUFFLE(0, 3, 0, 3)); + // V10 = D0Z,D0Z,D2X,D2Y + V10 = _mm_shuffle_ps(D0, D2, _MM_SHUFFLE(1, 0, 2, 2)); + V10 = XM_PERMUTE_PS(V10, _MM_SHUFFLE(0, 2, 3, 0)); + V01 = XM_PERMUTE_PS(MT.r[0], _MM_SHUFFLE(2, 0, 3, 1)); + // V11 = D0X,D0W,D2X,D2Y + V11 = _mm_shuffle_ps(D0, D2, _MM_SHUFFLE(1, 0, 3, 0)); + V11 = XM_PERMUTE_PS(V11, _MM_SHUFFLE(2, 1, 0, 3)); + V02 = XM_PERMUTE_PS(MT.r[3], _MM_SHUFFLE(0, 3, 0, 3)); + // V12 = D1Z,D1Z,D2Z,D2W + V12 = _mm_shuffle_ps(D1, D2, _MM_SHUFFLE(3, 2, 2, 2)); + V12 = XM_PERMUTE_PS(V12, _MM_SHUFFLE(0, 2, 3, 0)); + V03 = XM_PERMUTE_PS(MT.r[2], _MM_SHUFFLE(2, 0, 3, 1)); + // V13 = D1X,D1W,D2Z,D2W + V13 = _mm_shuffle_ps(D1, D2, _MM_SHUFFLE(3, 2, 3, 0)); + V13 = XM_PERMUTE_PS(V13, _MM_SHUFFLE(2, 1, 0, 3)); + + V00 = _mm_mul_ps(V00, V10); + V01 = _mm_mul_ps(V01, V11); + V02 = _mm_mul_ps(V02, V12); + V03 = _mm_mul_ps(V03, V13); + XMVECTOR C1 = _mm_sub_ps(C0, V00); + C0 = _mm_add_ps(C0, V00); + XMVECTOR C3 = _mm_add_ps(C2, V01); + C2 = _mm_sub_ps(C2, V01); + XMVECTOR C5 = _mm_sub_ps(C4, V02); + C4 = _mm_add_ps(C4, V02); + XMVECTOR C7 = _mm_add_ps(C6, V03); + C6 = _mm_sub_ps(C6, V03); + + C0 = _mm_shuffle_ps(C0, C1, _MM_SHUFFLE(3, 1, 2, 0)); + C2 = _mm_shuffle_ps(C2, C3, _MM_SHUFFLE(3, 1, 2, 0)); + C4 = _mm_shuffle_ps(C4, C5, _MM_SHUFFLE(3, 1, 2, 0)); + C6 = _mm_shuffle_ps(C6, C7, _MM_SHUFFLE(3, 1, 2, 0)); + C0 = XM_PERMUTE_PS(C0, _MM_SHUFFLE(3, 1, 2, 0)); + C2 = XM_PERMUTE_PS(C2, _MM_SHUFFLE(3, 1, 2, 0)); + C4 = XM_PERMUTE_PS(C4, _MM_SHUFFLE(3, 1, 2, 0)); + C6 = XM_PERMUTE_PS(C6, _MM_SHUFFLE(3, 1, 2, 0)); + // Get the determinant + XMVECTOR vTemp = XMVector4Dot(C0, MT.r[0]); + if (pDeterminant != nullptr) + *pDeterminant = vTemp; + vTemp = _mm_div_ps(g_XMOne, vTemp); + XMMATRIX mResult; + mResult.r[0] = _mm_mul_ps(C0, vTemp); + mResult.r[1] = _mm_mul_ps(C2, vTemp); + mResult.r[2] = _mm_mul_ps(C4, vTemp); + mResult.r[3] = _mm_mul_ps(C6, vTemp); + return mResult; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMMATRIX XM_CALLCONV XMMatrixVectorTensorProduct +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ + XMMATRIX mResult; + mResult.r[0] = XMVectorMultiply(XMVectorSwizzle<0, 0, 0, 0>(V1), V2); + mResult.r[1] = XMVectorMultiply(XMVectorSwizzle<1, 1, 1, 1>(V1), V2); + mResult.r[2] = XMVectorMultiply(XMVectorSwizzle<2, 2, 2, 2>(V1), V2); + mResult.r[3] = XMVectorMultiply(XMVectorSwizzle<3, 3, 3, 3>(V1), V2); + return mResult; +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMMatrixDeterminant(FXMMATRIX M) noexcept +{ + static const XMVECTORF32 Sign = { { { 1.0f, -1.0f, 1.0f, -1.0f } } }; + + XMVECTOR V0 = XMVectorSwizzle(M.r[2]); + XMVECTOR V1 = XMVectorSwizzle(M.r[3]); + XMVECTOR V2 = XMVectorSwizzle(M.r[2]); + XMVECTOR V3 = XMVectorSwizzle(M.r[3]); + XMVECTOR V4 = XMVectorSwizzle(M.r[2]); + XMVECTOR V5 = XMVectorSwizzle(M.r[3]); + + XMVECTOR P0 = XMVectorMultiply(V0, V1); + XMVECTOR P1 = XMVectorMultiply(V2, V3); + XMVECTOR P2 = XMVectorMultiply(V4, V5); + + V0 = XMVectorSwizzle(M.r[2]); + V1 = XMVectorSwizzle(M.r[3]); + V2 = XMVectorSwizzle(M.r[2]); + V3 = XMVectorSwizzle(M.r[3]); + V4 = XMVectorSwizzle(M.r[2]); + V5 = XMVectorSwizzle(M.r[3]); + + P0 = XMVectorNegativeMultiplySubtract(V0, V1, P0); + P1 = XMVectorNegativeMultiplySubtract(V2, V3, P1); + P2 = XMVectorNegativeMultiplySubtract(V4, V5, P2); + + V0 = XMVectorSwizzle(M.r[1]); + V1 = XMVectorSwizzle(M.r[1]); + V2 = XMVectorSwizzle(M.r[1]); + + XMVECTOR S = XMVectorMultiply(M.r[0], Sign.v); + XMVECTOR R = XMVectorMultiply(V0, P0); + R = XMVectorNegativeMultiplySubtract(V1, P1, R); + R = XMVectorMultiplyAdd(V2, P2, R); + + return XMVector4Dot(S, R); +} + +#define XM3RANKDECOMPOSE(a, b, c, x, y, z) \ + if((x) < (y)) \ + { \ + if((y) < (z)) \ + { \ + (a) = 2; \ + (b) = 1; \ + (c) = 0; \ + } \ + else \ + { \ + (a) = 1; \ + \ + if((x) < (z)) \ + { \ + (b) = 2; \ + (c) = 0; \ + } \ + else \ + { \ + (b) = 0; \ + (c) = 2; \ + } \ + } \ + } \ + else \ + { \ + if((x) < (z)) \ + { \ + (a) = 2; \ + (b) = 0; \ + (c) = 1; \ + } \ + else \ + { \ + (a) = 0; \ + \ + if((y) < (z)) \ + { \ + (b) = 2; \ + (c) = 1; \ + } \ + else \ + { \ + (b) = 1; \ + (c) = 2; \ + } \ + } \ + } + +#define XM3_DECOMP_EPSILON 0.0001f + +_Use_decl_annotations_ +inline bool XM_CALLCONV XMMatrixDecompose +( + XMVECTOR* outScale, + XMVECTOR* outRotQuat, + XMVECTOR* outTrans, + FXMMATRIX M +) noexcept +{ + static const XMVECTOR* pvCanonicalBasis[3] = { + &g_XMIdentityR0.v, + &g_XMIdentityR1.v, + &g_XMIdentityR2.v + }; + + assert(outScale != nullptr); + assert(outRotQuat != nullptr); + assert(outTrans != nullptr); + + // Get the translation + outTrans[0] = M.r[3]; + + XMVECTOR* ppvBasis[3]; + XMMATRIX matTemp; + ppvBasis[0] = &matTemp.r[0]; + ppvBasis[1] = &matTemp.r[1]; + ppvBasis[2] = &matTemp.r[2]; + + matTemp.r[0] = M.r[0]; + matTemp.r[1] = M.r[1]; + matTemp.r[2] = M.r[2]; + matTemp.r[3] = g_XMIdentityR3.v; + + auto pfScales = reinterpret_cast(outScale); + + size_t a, b, c; + XMVectorGetXPtr(&pfScales[0], XMVector3Length(ppvBasis[0][0])); + XMVectorGetXPtr(&pfScales[1], XMVector3Length(ppvBasis[1][0])); + XMVectorGetXPtr(&pfScales[2], XMVector3Length(ppvBasis[2][0])); + pfScales[3] = 0.f; + + XM3RANKDECOMPOSE(a, b, c, pfScales[0], pfScales[1], pfScales[2]) + + if (pfScales[a] < XM3_DECOMP_EPSILON) + { + ppvBasis[a][0] = pvCanonicalBasis[a][0]; + } + ppvBasis[a][0] = XMVector3Normalize(ppvBasis[a][0]); + + if (pfScales[b] < XM3_DECOMP_EPSILON) + { + size_t aa, bb, cc; + float fAbsX, fAbsY, fAbsZ; + + fAbsX = fabsf(XMVectorGetX(ppvBasis[a][0])); + fAbsY = fabsf(XMVectorGetY(ppvBasis[a][0])); + fAbsZ = fabsf(XMVectorGetZ(ppvBasis[a][0])); + + XM3RANKDECOMPOSE(aa, bb, cc, fAbsX, fAbsY, fAbsZ) + + ppvBasis[b][0] = XMVector3Cross(ppvBasis[a][0], pvCanonicalBasis[cc][0]); + } + + ppvBasis[b][0] = XMVector3Normalize(ppvBasis[b][0]); + + if (pfScales[c] < XM3_DECOMP_EPSILON) + { + ppvBasis[c][0] = XMVector3Cross(ppvBasis[a][0], ppvBasis[b][0]); + } + + ppvBasis[c][0] = XMVector3Normalize(ppvBasis[c][0]); + + float fDet = XMVectorGetX(XMMatrixDeterminant(matTemp)); + + // use Kramer's rule to check for handedness of coordinate system + if (fDet < 0.0f) + { + // switch coordinate system by negating the scale and inverting the basis vector on the x-axis + pfScales[a] = -pfScales[a]; + ppvBasis[a][0] = XMVectorNegate(ppvBasis[a][0]); + + fDet = -fDet; + } + + fDet -= 1.0f; + fDet *= fDet; + + if (XM3_DECOMP_EPSILON < fDet) + { + // Non-SRT matrix encountered + return false; + } + + // generate the quaternion from the matrix + outRotQuat[0] = XMQuaternionRotationMatrix(matTemp); + return true; +} + +#undef XM3_DECOMP_EPSILON +#undef XM3RANKDECOMPOSE + +//------------------------------------------------------------------------------ +// Transformation operations +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ + +inline XMMATRIX XM_CALLCONV XMMatrixIdentity() noexcept +{ + XMMATRIX M; + M.r[0] = g_XMIdentityR0.v; + M.r[1] = g_XMIdentityR1.v; + M.r[2] = g_XMIdentityR2.v; + M.r[3] = g_XMIdentityR3.v; + return M; +} + +//------------------------------------------------------------------------------ + +inline XMMATRIX XM_CALLCONV XMMatrixSet +( + float m00, float m01, float m02, float m03, + float m10, float m11, float m12, float m13, + float m20, float m21, float m22, float m23, + float m30, float m31, float m32, float m33 +) noexcept +{ + XMMATRIX M; +#if defined(_XM_NO_INTRINSICS_) + M.m[0][0] = m00; M.m[0][1] = m01; M.m[0][2] = m02; M.m[0][3] = m03; + M.m[1][0] = m10; M.m[1][1] = m11; M.m[1][2] = m12; M.m[1][3] = m13; + M.m[2][0] = m20; M.m[2][1] = m21; M.m[2][2] = m22; M.m[2][3] = m23; + M.m[3][0] = m30; M.m[3][1] = m31; M.m[3][2] = m32; M.m[3][3] = m33; +#else + M.r[0] = XMVectorSet(m00, m01, m02, m03); + M.r[1] = XMVectorSet(m10, m11, m12, m13); + M.r[2] = XMVectorSet(m20, m21, m22, m23); + M.r[3] = XMVectorSet(m30, m31, m32, m33); +#endif + return M; +} + +//------------------------------------------------------------------------------ + +inline XMMATRIX XM_CALLCONV XMMatrixTranslation +( + float OffsetX, + float OffsetY, + float OffsetZ +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMMATRIX M; + M.m[0][0] = 1.0f; + M.m[0][1] = 0.0f; + M.m[0][2] = 0.0f; + M.m[0][3] = 0.0f; + + M.m[1][0] = 0.0f; + M.m[1][1] = 1.0f; + M.m[1][2] = 0.0f; + M.m[1][3] = 0.0f; + + M.m[2][0] = 0.0f; + M.m[2][1] = 0.0f; + M.m[2][2] = 1.0f; + M.m[2][3] = 0.0f; + + M.m[3][0] = OffsetX; + M.m[3][1] = OffsetY; + M.m[3][2] = OffsetZ; + M.m[3][3] = 1.0f; + return M; + +#elif defined(_XM_SSE_INTRINSICS_) || defined(_XM_ARM_NEON_INTRINSICS_) + XMMATRIX M; + M.r[0] = g_XMIdentityR0.v; + M.r[1] = g_XMIdentityR1.v; + M.r[2] = g_XMIdentityR2.v; + M.r[3] = XMVectorSet(OffsetX, OffsetY, OffsetZ, 1.f); + return M; +#endif +} + + +//------------------------------------------------------------------------------ + +inline XMMATRIX XM_CALLCONV XMMatrixTranslationFromVector(FXMVECTOR Offset) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMMATRIX M; + M.m[0][0] = 1.0f; + M.m[0][1] = 0.0f; + M.m[0][2] = 0.0f; + M.m[0][3] = 0.0f; + + M.m[1][0] = 0.0f; + M.m[1][1] = 1.0f; + M.m[1][2] = 0.0f; + M.m[1][3] = 0.0f; + + M.m[2][0] = 0.0f; + M.m[2][1] = 0.0f; + M.m[2][2] = 1.0f; + M.m[2][3] = 0.0f; + + M.m[3][0] = Offset.vector4_f32[0]; + M.m[3][1] = Offset.vector4_f32[1]; + M.m[3][2] = Offset.vector4_f32[2]; + M.m[3][3] = 1.0f; + return M; + +#elif defined(_XM_SSE_INTRINSICS_) || defined(_XM_ARM_NEON_INTRINSICS_) + XMMATRIX M; + M.r[0] = g_XMIdentityR0.v; + M.r[1] = g_XMIdentityR1.v; + M.r[2] = g_XMIdentityR2.v; + M.r[3] = XMVectorSelect(g_XMIdentityR3.v, Offset, g_XMSelect1110.v); + return M; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMMATRIX XM_CALLCONV XMMatrixScaling +( + float ScaleX, + float ScaleY, + float ScaleZ +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMMATRIX M; + M.m[0][0] = ScaleX; + M.m[0][1] = 0.0f; + M.m[0][2] = 0.0f; + M.m[0][3] = 0.0f; + + M.m[1][0] = 0.0f; + M.m[1][1] = ScaleY; + M.m[1][2] = 0.0f; + M.m[1][3] = 0.0f; + + M.m[2][0] = 0.0f; + M.m[2][1] = 0.0f; + M.m[2][2] = ScaleZ; + M.m[2][3] = 0.0f; + + M.m[3][0] = 0.0f; + M.m[3][1] = 0.0f; + M.m[3][2] = 0.0f; + M.m[3][3] = 1.0f; + return M; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + const XMVECTOR Zero = vdupq_n_f32(0); + XMMATRIX M; + M.r[0] = vsetq_lane_f32(ScaleX, Zero, 0); + M.r[1] = vsetq_lane_f32(ScaleY, Zero, 1); + M.r[2] = vsetq_lane_f32(ScaleZ, Zero, 2); + M.r[3] = g_XMIdentityR3.v; + return M; +#elif defined(_XM_SSE_INTRINSICS_) + XMMATRIX M; + M.r[0] = _mm_set_ps(0, 0, 0, ScaleX); + M.r[1] = _mm_set_ps(0, 0, ScaleY, 0); + M.r[2] = _mm_set_ps(0, ScaleZ, 0, 0); + M.r[3] = g_XMIdentityR3.v; + return M; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMMATRIX XM_CALLCONV XMMatrixScalingFromVector(FXMVECTOR Scale) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMMATRIX M; + M.m[0][0] = Scale.vector4_f32[0]; + M.m[0][1] = 0.0f; + M.m[0][2] = 0.0f; + M.m[0][3] = 0.0f; + + M.m[1][0] = 0.0f; + M.m[1][1] = Scale.vector4_f32[1]; + M.m[1][2] = 0.0f; + M.m[1][3] = 0.0f; + + M.m[2][0] = 0.0f; + M.m[2][1] = 0.0f; + M.m[2][2] = Scale.vector4_f32[2]; + M.m[2][3] = 0.0f; + + M.m[3][0] = 0.0f; + M.m[3][1] = 0.0f; + M.m[3][2] = 0.0f; + M.m[3][3] = 1.0f; + return M; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + XMMATRIX M; + M.r[0] = vreinterpretq_f32_u32(vandq_u32(vreinterpretq_u32_f32(Scale), g_XMMaskX)); + M.r[1] = vreinterpretq_f32_u32(vandq_u32(vreinterpretq_u32_f32(Scale), g_XMMaskY)); + M.r[2] = vreinterpretq_f32_u32(vandq_u32(vreinterpretq_u32_f32(Scale), g_XMMaskZ)); + M.r[3] = g_XMIdentityR3.v; + return M; +#elif defined(_XM_SSE_INTRINSICS_) + XMMATRIX M; + M.r[0] = _mm_and_ps(Scale, g_XMMaskX); + M.r[1] = _mm_and_ps(Scale, g_XMMaskY); + M.r[2] = _mm_and_ps(Scale, g_XMMaskZ); + M.r[3] = g_XMIdentityR3.v; + return M; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMMATRIX XM_CALLCONV XMMatrixRotationX(float Angle) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + float fSinAngle; + float fCosAngle; + XMScalarSinCos(&fSinAngle, &fCosAngle, Angle); + + XMMATRIX M; + M.m[0][0] = 1.0f; + M.m[0][1] = 0.0f; + M.m[0][2] = 0.0f; + M.m[0][3] = 0.0f; + + M.m[1][0] = 0.0f; + M.m[1][1] = fCosAngle; + M.m[1][2] = fSinAngle; + M.m[1][3] = 0.0f; + + M.m[2][0] = 0.0f; + M.m[2][1] = -fSinAngle; + M.m[2][2] = fCosAngle; + M.m[2][3] = 0.0f; + + M.m[3][0] = 0.0f; + M.m[3][1] = 0.0f; + M.m[3][2] = 0.0f; + M.m[3][3] = 1.0f; + return M; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float fSinAngle; + float fCosAngle; + XMScalarSinCos(&fSinAngle, &fCosAngle, Angle); + + const float32x4_t Zero = vdupq_n_f32(0); + + float32x4_t T1 = vsetq_lane_f32(fCosAngle, Zero, 1); + T1 = vsetq_lane_f32(fSinAngle, T1, 2); + + float32x4_t T2 = vsetq_lane_f32(-fSinAngle, Zero, 1); + T2 = vsetq_lane_f32(fCosAngle, T2, 2); + + XMMATRIX M; + M.r[0] = g_XMIdentityR0.v; + M.r[1] = T1; + M.r[2] = T2; + M.r[3] = g_XMIdentityR3.v; + return M; +#elif defined(_XM_SSE_INTRINSICS_) + float SinAngle; + float CosAngle; + XMScalarSinCos(&SinAngle, &CosAngle, Angle); + + XMVECTOR vSin = _mm_set_ss(SinAngle); + XMVECTOR vCos = _mm_set_ss(CosAngle); + // x = 0,y = cos,z = sin, w = 0 + vCos = _mm_shuffle_ps(vCos, vSin, _MM_SHUFFLE(3, 0, 0, 3)); + XMMATRIX M; + M.r[0] = g_XMIdentityR0; + M.r[1] = vCos; + // x = 0,y = sin,z = cos, w = 0 + vCos = XM_PERMUTE_PS(vCos, _MM_SHUFFLE(3, 1, 2, 0)); + // x = 0,y = -sin,z = cos, w = 0 + vCos = _mm_mul_ps(vCos, g_XMNegateY); + M.r[2] = vCos; + M.r[3] = g_XMIdentityR3; + return M; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMMATRIX XM_CALLCONV XMMatrixRotationY(float Angle) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + float fSinAngle; + float fCosAngle; + XMScalarSinCos(&fSinAngle, &fCosAngle, Angle); + + XMMATRIX M; + M.m[0][0] = fCosAngle; + M.m[0][1] = 0.0f; + M.m[0][2] = -fSinAngle; + M.m[0][3] = 0.0f; + + M.m[1][0] = 0.0f; + M.m[1][1] = 1.0f; + M.m[1][2] = 0.0f; + M.m[1][3] = 0.0f; + + M.m[2][0] = fSinAngle; + M.m[2][1] = 0.0f; + M.m[2][2] = fCosAngle; + M.m[2][3] = 0.0f; + + M.m[3][0] = 0.0f; + M.m[3][1] = 0.0f; + M.m[3][2] = 0.0f; + M.m[3][3] = 1.0f; + return M; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float fSinAngle; + float fCosAngle; + XMScalarSinCos(&fSinAngle, &fCosAngle, Angle); + + const float32x4_t Zero = vdupq_n_f32(0); + + float32x4_t T0 = vsetq_lane_f32(fCosAngle, Zero, 0); + T0 = vsetq_lane_f32(-fSinAngle, T0, 2); + + float32x4_t T2 = vsetq_lane_f32(fSinAngle, Zero, 0); + T2 = vsetq_lane_f32(fCosAngle, T2, 2); + + XMMATRIX M; + M.r[0] = T0; + M.r[1] = g_XMIdentityR1.v; + M.r[2] = T2; + M.r[3] = g_XMIdentityR3.v; + return M; +#elif defined(_XM_SSE_INTRINSICS_) + float SinAngle; + float CosAngle; + XMScalarSinCos(&SinAngle, &CosAngle, Angle); + + XMVECTOR vSin = _mm_set_ss(SinAngle); + XMVECTOR vCos = _mm_set_ss(CosAngle); + // x = sin,y = 0,z = cos, w = 0 + vSin = _mm_shuffle_ps(vSin, vCos, _MM_SHUFFLE(3, 0, 3, 0)); + XMMATRIX M; + M.r[2] = vSin; + M.r[1] = g_XMIdentityR1; + // x = cos,y = 0,z = sin, w = 0 + vSin = XM_PERMUTE_PS(vSin, _MM_SHUFFLE(3, 0, 1, 2)); + // x = cos,y = 0,z = -sin, w = 0 + vSin = _mm_mul_ps(vSin, g_XMNegateZ); + M.r[0] = vSin; + M.r[3] = g_XMIdentityR3; + return M; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMMATRIX XM_CALLCONV XMMatrixRotationZ(float Angle) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + float fSinAngle; + float fCosAngle; + XMScalarSinCos(&fSinAngle, &fCosAngle, Angle); + + XMMATRIX M; + M.m[0][0] = fCosAngle; + M.m[0][1] = fSinAngle; + M.m[0][2] = 0.0f; + M.m[0][3] = 0.0f; + + M.m[1][0] = -fSinAngle; + M.m[1][1] = fCosAngle; + M.m[1][2] = 0.0f; + M.m[1][3] = 0.0f; + + M.m[2][0] = 0.0f; + M.m[2][1] = 0.0f; + M.m[2][2] = 1.0f; + M.m[2][3] = 0.0f; + + M.m[3][0] = 0.0f; + M.m[3][1] = 0.0f; + M.m[3][2] = 0.0f; + M.m[3][3] = 1.0f; + return M; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float fSinAngle; + float fCosAngle; + XMScalarSinCos(&fSinAngle, &fCosAngle, Angle); + + const float32x4_t Zero = vdupq_n_f32(0); + + float32x4_t T0 = vsetq_lane_f32(fCosAngle, Zero, 0); + T0 = vsetq_lane_f32(fSinAngle, T0, 1); + + float32x4_t T1 = vsetq_lane_f32(-fSinAngle, Zero, 0); + T1 = vsetq_lane_f32(fCosAngle, T1, 1); + + XMMATRIX M; + M.r[0] = T0; + M.r[1] = T1; + M.r[2] = g_XMIdentityR2.v; + M.r[3] = g_XMIdentityR3.v; + return M; +#elif defined(_XM_SSE_INTRINSICS_) + float SinAngle; + float CosAngle; + XMScalarSinCos(&SinAngle, &CosAngle, Angle); + + XMVECTOR vSin = _mm_set_ss(SinAngle); + XMVECTOR vCos = _mm_set_ss(CosAngle); + // x = cos,y = sin,z = 0, w = 0 + vCos = _mm_unpacklo_ps(vCos, vSin); + XMMATRIX M; + M.r[0] = vCos; + // x = sin,y = cos,z = 0, w = 0 + vCos = XM_PERMUTE_PS(vCos, _MM_SHUFFLE(3, 2, 0, 1)); + // x = cos,y = -sin,z = 0, w = 0 + vCos = _mm_mul_ps(vCos, g_XMNegateX); + M.r[1] = vCos; + M.r[2] = g_XMIdentityR2; + M.r[3] = g_XMIdentityR3; + return M; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMMATRIX XM_CALLCONV XMMatrixRotationRollPitchYaw +( + float Pitch, + float Yaw, + float Roll +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + float cp = cosf(Pitch); + float sp = sinf(Pitch); + + float cy = cosf(Yaw); + float sy = sinf(Yaw); + + float cr = cosf(Roll); + float sr = sinf(Roll); + + XMMATRIX M; + M.m[0][0] = cr * cy + sr * sp * sy; + M.m[0][1] = sr * cp; + M.m[0][2] = sr * sp * cy - cr * sy; + M.m[0][3] = 0.0f; + + M.m[1][0] = cr * sp * sy - sr * cy; + M.m[1][1] = cr * cp; + M.m[1][2] = sr * sy + cr * sp * cy; + M.m[1][3] = 0.0f; + + M.m[2][0] = cp * sy; + M.m[2][1] = -sp; + M.m[2][2] = cp * cy; + M.m[2][3] = 0.0f; + + M.m[3][0] = 0.0f; + M.m[3][1] = 0.0f; + M.m[3][2] = 0.0f; + M.m[3][3] = 1.0f; + return M; +#else + XMVECTOR Angles = XMVectorSet(Pitch, Yaw, Roll, 0.0f); + return XMMatrixRotationRollPitchYawFromVector(Angles); +#endif +} + +//------------------------------------------------------------------------------ + +inline XMMATRIX XM_CALLCONV XMMatrixRotationRollPitchYawFromVector +( + FXMVECTOR Angles // +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + float cp = cosf(Angles.vector4_f32[0]); + float sp = sinf(Angles.vector4_f32[0]); + + float cy = cosf(Angles.vector4_f32[1]); + float sy = sinf(Angles.vector4_f32[1]); + + float cr = cosf(Angles.vector4_f32[2]); + float sr = sinf(Angles.vector4_f32[2]); + + XMMATRIX M; + M.m[0][0] = cr * cy + sr * sp * sy; + M.m[0][1] = sr * cp; + M.m[0][2] = sr * sp * cy - cr * sy; + M.m[0][3] = 0.0f; + + M.m[1][0] = cr * sp * sy - sr * cy; + M.m[1][1] = cr * cp; + M.m[1][2] = sr * sy + cr * sp * cy; + M.m[1][3] = 0.0f; + + M.m[2][0] = cp * sy; + M.m[2][1] = -sp; + M.m[2][2] = cp * cy; + M.m[2][3] = 0.0f; + + M.m[3][0] = 0.0f; + M.m[3][1] = 0.0f; + M.m[3][2] = 0.0f; + M.m[3][3] = 1.0f; + return M; +#else + static const XMVECTORF32 Sign = { { { 1.0f, -1.0f, -1.0f, 1.0f } } }; + + XMVECTOR SinAngles, CosAngles; + XMVectorSinCos(&SinAngles, &CosAngles, Angles); + + XMVECTOR P0 = XMVectorPermute(SinAngles, CosAngles); + XMVECTOR Y0 = XMVectorPermute(SinAngles, CosAngles); + XMVECTOR P1 = XMVectorPermute(SinAngles, CosAngles); + XMVECTOR Y1 = XMVectorPermute(SinAngles, CosAngles); + XMVECTOR P2 = XMVectorPermute(SinAngles, CosAngles); + XMVECTOR P3 = XMVectorPermute(SinAngles, CosAngles); + XMVECTOR Y2 = XMVectorSplatX(SinAngles); + XMVECTOR NS = XMVectorNegate(SinAngles); + + XMVECTOR Q0 = XMVectorMultiply(P0, Y0); + XMVECTOR Q1 = XMVectorMultiply(P1, Sign.v); + Q1 = XMVectorMultiply(Q1, Y1); + XMVECTOR Q2 = XMVectorMultiply(P2, Y2); + Q2 = XMVectorMultiplyAdd(Q2, P3, Q1); + + XMVECTOR V0 = XMVectorPermute(Q0, Q2); + XMVECTOR V1 = XMVectorPermute(Q0, Q2); + XMVECTOR V2 = XMVectorPermute(Q0, NS); + + XMMATRIX M; + M.r[0] = XMVectorSelect(g_XMZero, V0, g_XMSelect1110.v); + M.r[1] = XMVectorSelect(g_XMZero, V1, g_XMSelect1110.v); + M.r[2] = XMVectorSelect(g_XMZero, V2, g_XMSelect1110.v); + M.r[3] = g_XMIdentityR3; + return M; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMMATRIX XM_CALLCONV XMMatrixRotationNormal +( + FXMVECTOR NormalAxis, + float Angle +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) || defined(_XM_ARM_NEON_INTRINSICS_) + + float fSinAngle; + float fCosAngle; + XMScalarSinCos(&fSinAngle, &fCosAngle, Angle); + + XMVECTOR A = XMVectorSet(fSinAngle, fCosAngle, 1.0f - fCosAngle, 0.0f); + + XMVECTOR C2 = XMVectorSplatZ(A); + XMVECTOR C1 = XMVectorSplatY(A); + XMVECTOR C0 = XMVectorSplatX(A); + + XMVECTOR N0 = XMVectorSwizzle(NormalAxis); + XMVECTOR N1 = XMVectorSwizzle(NormalAxis); + + XMVECTOR V0 = XMVectorMultiply(C2, N0); + V0 = XMVectorMultiply(V0, N1); + + XMVECTOR R0 = XMVectorMultiply(C2, NormalAxis); + R0 = XMVectorMultiplyAdd(R0, NormalAxis, C1); + + XMVECTOR R1 = XMVectorMultiplyAdd(C0, NormalAxis, V0); + XMVECTOR R2 = XMVectorNegativeMultiplySubtract(C0, NormalAxis, V0); + + V0 = XMVectorSelect(A, R0, g_XMSelect1110.v); + XMVECTOR V1 = XMVectorPermute(R1, R2); + XMVECTOR V2 = XMVectorPermute(R1, R2); + + XMMATRIX M; + M.r[0] = XMVectorPermute(V0, V1); + M.r[1] = XMVectorPermute(V0, V1); + M.r[2] = XMVectorPermute(V0, V2); + M.r[3] = g_XMIdentityR3.v; + return M; + +#elif defined(_XM_SSE_INTRINSICS_) + float fSinAngle; + float fCosAngle; + XMScalarSinCos(&fSinAngle, &fCosAngle, Angle); + + XMVECTOR C2 = _mm_set_ps1(1.0f - fCosAngle); + XMVECTOR C1 = _mm_set_ps1(fCosAngle); + XMVECTOR C0 = _mm_set_ps1(fSinAngle); + + XMVECTOR N0 = XM_PERMUTE_PS(NormalAxis, _MM_SHUFFLE(3, 0, 2, 1)); + XMVECTOR N1 = XM_PERMUTE_PS(NormalAxis, _MM_SHUFFLE(3, 1, 0, 2)); + + XMVECTOR V0 = _mm_mul_ps(C2, N0); + V0 = _mm_mul_ps(V0, N1); + + XMVECTOR R0 = _mm_mul_ps(C2, NormalAxis); + R0 = _mm_mul_ps(R0, NormalAxis); + R0 = _mm_add_ps(R0, C1); + + XMVECTOR R1 = _mm_mul_ps(C0, NormalAxis); + R1 = _mm_add_ps(R1, V0); + XMVECTOR R2 = _mm_mul_ps(C0, NormalAxis); + R2 = _mm_sub_ps(V0, R2); + + V0 = _mm_and_ps(R0, g_XMMask3); + XMVECTOR V1 = _mm_shuffle_ps(R1, R2, _MM_SHUFFLE(2, 1, 2, 0)); + V1 = XM_PERMUTE_PS(V1, _MM_SHUFFLE(0, 3, 2, 1)); + XMVECTOR V2 = _mm_shuffle_ps(R1, R2, _MM_SHUFFLE(0, 0, 1, 1)); + V2 = XM_PERMUTE_PS(V2, _MM_SHUFFLE(2, 0, 2, 0)); + + R2 = _mm_shuffle_ps(V0, V1, _MM_SHUFFLE(1, 0, 3, 0)); + R2 = XM_PERMUTE_PS(R2, _MM_SHUFFLE(1, 3, 2, 0)); + + XMMATRIX M; + M.r[0] = R2; + + R2 = _mm_shuffle_ps(V0, V1, _MM_SHUFFLE(3, 2, 3, 1)); + R2 = XM_PERMUTE_PS(R2, _MM_SHUFFLE(1, 3, 0, 2)); + M.r[1] = R2; + + V2 = _mm_shuffle_ps(V2, V0, _MM_SHUFFLE(3, 2, 1, 0)); + M.r[2] = V2; + M.r[3] = g_XMIdentityR3.v; + return M; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMMATRIX XM_CALLCONV XMMatrixRotationAxis +( + FXMVECTOR Axis, + float Angle +) noexcept +{ + assert(!XMVector3Equal(Axis, XMVectorZero())); + assert(!XMVector3IsInfinite(Axis)); + + XMVECTOR Normal = XMVector3Normalize(Axis); + return XMMatrixRotationNormal(Normal, Angle); +} + +//------------------------------------------------------------------------------ + +inline XMMATRIX XM_CALLCONV XMMatrixRotationQuaternion(FXMVECTOR Quaternion) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + float qx = Quaternion.vector4_f32[0]; + float qxx = qx * qx; + + float qy = Quaternion.vector4_f32[1]; + float qyy = qy * qy; + + float qz = Quaternion.vector4_f32[2]; + float qzz = qz * qz; + + float qw = Quaternion.vector4_f32[3]; + + XMMATRIX M; + M.m[0][0] = 1.f - 2.f * qyy - 2.f * qzz; + M.m[0][1] = 2.f * qx * qy + 2.f * qz * qw; + M.m[0][2] = 2.f * qx * qz - 2.f * qy * qw; + M.m[0][3] = 0.f; + + M.m[1][0] = 2.f * qx * qy - 2.f * qz * qw; + M.m[1][1] = 1.f - 2.f * qxx - 2.f * qzz; + M.m[1][2] = 2.f * qy * qz + 2.f * qx * qw; + M.m[1][3] = 0.f; + + M.m[2][0] = 2.f * qx * qz + 2.f * qy * qw; + M.m[2][1] = 2.f * qy * qz - 2.f * qx * qw; + M.m[2][2] = 1.f - 2.f * qxx - 2.f * qyy; + M.m[2][3] = 0.f; + + M.m[3][0] = 0.f; + M.m[3][1] = 0.f; + M.m[3][2] = 0.f; + M.m[3][3] = 1.0f; + return M; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + static const XMVECTORF32 Constant1110 = { { { 1.0f, 1.0f, 1.0f, 0.0f } } }; + + XMVECTOR Q0 = XMVectorAdd(Quaternion, Quaternion); + XMVECTOR Q1 = XMVectorMultiply(Quaternion, Q0); + + XMVECTOR V0 = XMVectorPermute(Q1, Constant1110.v); + XMVECTOR V1 = XMVectorPermute(Q1, Constant1110.v); + XMVECTOR R0 = XMVectorSubtract(Constant1110, V0); + R0 = XMVectorSubtract(R0, V1); + + V0 = XMVectorSwizzle(Quaternion); + V1 = XMVectorSwizzle(Q0); + V0 = XMVectorMultiply(V0, V1); + + V1 = XMVectorSplatW(Quaternion); + XMVECTOR V2 = XMVectorSwizzle(Q0); + V1 = XMVectorMultiply(V1, V2); + + XMVECTOR R1 = XMVectorAdd(V0, V1); + XMVECTOR R2 = XMVectorSubtract(V0, V1); + + V0 = XMVectorPermute(R1, R2); + V1 = XMVectorPermute(R1, R2); + + XMMATRIX M; + M.r[0] = XMVectorPermute(R0, V0); + M.r[1] = XMVectorPermute(R0, V0); + M.r[2] = XMVectorPermute(R0, V1); + M.r[3] = g_XMIdentityR3.v; + return M; + +#elif defined(_XM_SSE_INTRINSICS_) + static const XMVECTORF32 Constant1110 = { { { 1.0f, 1.0f, 1.0f, 0.0f } } }; + + XMVECTOR Q0 = _mm_add_ps(Quaternion, Quaternion); + XMVECTOR Q1 = _mm_mul_ps(Quaternion, Q0); + + XMVECTOR V0 = XM_PERMUTE_PS(Q1, _MM_SHUFFLE(3, 0, 0, 1)); + V0 = _mm_and_ps(V0, g_XMMask3); + XMVECTOR V1 = XM_PERMUTE_PS(Q1, _MM_SHUFFLE(3, 1, 2, 2)); + V1 = _mm_and_ps(V1, g_XMMask3); + XMVECTOR R0 = _mm_sub_ps(Constant1110, V0); + R0 = _mm_sub_ps(R0, V1); + + V0 = XM_PERMUTE_PS(Quaternion, _MM_SHUFFLE(3, 1, 0, 0)); + V1 = XM_PERMUTE_PS(Q0, _MM_SHUFFLE(3, 2, 1, 2)); + V0 = _mm_mul_ps(V0, V1); + + V1 = XM_PERMUTE_PS(Quaternion, _MM_SHUFFLE(3, 3, 3, 3)); + XMVECTOR V2 = XM_PERMUTE_PS(Q0, _MM_SHUFFLE(3, 0, 2, 1)); + V1 = _mm_mul_ps(V1, V2); + + XMVECTOR R1 = _mm_add_ps(V0, V1); + XMVECTOR R2 = _mm_sub_ps(V0, V1); + + V0 = _mm_shuffle_ps(R1, R2, _MM_SHUFFLE(1, 0, 2, 1)); + V0 = XM_PERMUTE_PS(V0, _MM_SHUFFLE(1, 3, 2, 0)); + V1 = _mm_shuffle_ps(R1, R2, _MM_SHUFFLE(2, 2, 0, 0)); + V1 = XM_PERMUTE_PS(V1, _MM_SHUFFLE(2, 0, 2, 0)); + + Q1 = _mm_shuffle_ps(R0, V0, _MM_SHUFFLE(1, 0, 3, 0)); + Q1 = XM_PERMUTE_PS(Q1, _MM_SHUFFLE(1, 3, 2, 0)); + + XMMATRIX M; + M.r[0] = Q1; + + Q1 = _mm_shuffle_ps(R0, V0, _MM_SHUFFLE(3, 2, 3, 1)); + Q1 = XM_PERMUTE_PS(Q1, _MM_SHUFFLE(1, 3, 0, 2)); + M.r[1] = Q1; + + Q1 = _mm_shuffle_ps(V1, R0, _MM_SHUFFLE(3, 2, 1, 0)); + M.r[2] = Q1; + M.r[3] = g_XMIdentityR3; + return M; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMMATRIX XM_CALLCONV XMMatrixTransformation2D +( + FXMVECTOR ScalingOrigin, + float ScalingOrientation, + FXMVECTOR Scaling, + FXMVECTOR RotationOrigin, + float Rotation, + GXMVECTOR Translation +) noexcept +{ + // M = Inverse(MScalingOrigin) * Transpose(MScalingOrientation) * MScaling * MScalingOrientation * + // MScalingOrigin * Inverse(MRotationOrigin) * MRotation * MRotationOrigin * MTranslation; + + XMVECTOR VScalingOrigin = XMVectorSelect(g_XMSelect1100.v, ScalingOrigin, g_XMSelect1100.v); + XMVECTOR NegScalingOrigin = XMVectorNegate(VScalingOrigin); + + XMMATRIX MScalingOriginI = XMMatrixTranslationFromVector(NegScalingOrigin); + XMMATRIX MScalingOrientation = XMMatrixRotationZ(ScalingOrientation); + XMMATRIX MScalingOrientationT = XMMatrixTranspose(MScalingOrientation); + XMVECTOR VScaling = XMVectorSelect(g_XMOne.v, Scaling, g_XMSelect1100.v); + XMMATRIX MScaling = XMMatrixScalingFromVector(VScaling); + XMVECTOR VRotationOrigin = XMVectorSelect(g_XMSelect1100.v, RotationOrigin, g_XMSelect1100.v); + XMMATRIX MRotation = XMMatrixRotationZ(Rotation); + XMVECTOR VTranslation = XMVectorSelect(g_XMSelect1100.v, Translation, g_XMSelect1100.v); + + XMMATRIX M = XMMatrixMultiply(MScalingOriginI, MScalingOrientationT); + M = XMMatrixMultiply(M, MScaling); + M = XMMatrixMultiply(M, MScalingOrientation); + M.r[3] = XMVectorAdd(M.r[3], VScalingOrigin); + M.r[3] = XMVectorSubtract(M.r[3], VRotationOrigin); + M = XMMatrixMultiply(M, MRotation); + M.r[3] = XMVectorAdd(M.r[3], VRotationOrigin); + M.r[3] = XMVectorAdd(M.r[3], VTranslation); + + return M; +} + +//------------------------------------------------------------------------------ + +inline XMMATRIX XM_CALLCONV XMMatrixTransformation +( + FXMVECTOR ScalingOrigin, + FXMVECTOR ScalingOrientationQuaternion, + FXMVECTOR Scaling, + GXMVECTOR RotationOrigin, + HXMVECTOR RotationQuaternion, + HXMVECTOR Translation +) noexcept +{ + // M = Inverse(MScalingOrigin) * Transpose(MScalingOrientation) * MScaling * MScalingOrientation * + // MScalingOrigin * Inverse(MRotationOrigin) * MRotation * MRotationOrigin * MTranslation; + + XMVECTOR VScalingOrigin = XMVectorSelect(g_XMSelect1110.v, ScalingOrigin, g_XMSelect1110.v); + XMVECTOR NegScalingOrigin = XMVectorNegate(ScalingOrigin); + + XMMATRIX MScalingOriginI = XMMatrixTranslationFromVector(NegScalingOrigin); + XMMATRIX MScalingOrientation = XMMatrixRotationQuaternion(ScalingOrientationQuaternion); + XMMATRIX MScalingOrientationT = XMMatrixTranspose(MScalingOrientation); + XMMATRIX MScaling = XMMatrixScalingFromVector(Scaling); + XMVECTOR VRotationOrigin = XMVectorSelect(g_XMSelect1110.v, RotationOrigin, g_XMSelect1110.v); + XMMATRIX MRotation = XMMatrixRotationQuaternion(RotationQuaternion); + XMVECTOR VTranslation = XMVectorSelect(g_XMSelect1110.v, Translation, g_XMSelect1110.v); + + XMMATRIX M; + M = XMMatrixMultiply(MScalingOriginI, MScalingOrientationT); + M = XMMatrixMultiply(M, MScaling); + M = XMMatrixMultiply(M, MScalingOrientation); + M.r[3] = XMVectorAdd(M.r[3], VScalingOrigin); + M.r[3] = XMVectorSubtract(M.r[3], VRotationOrigin); + M = XMMatrixMultiply(M, MRotation); + M.r[3] = XMVectorAdd(M.r[3], VRotationOrigin); + M.r[3] = XMVectorAdd(M.r[3], VTranslation); + return M; +} + +//------------------------------------------------------------------------------ + +inline XMMATRIX XM_CALLCONV XMMatrixAffineTransformation2D +( + FXMVECTOR Scaling, + FXMVECTOR RotationOrigin, + float Rotation, + FXMVECTOR Translation +) noexcept +{ + // M = MScaling * Inverse(MRotationOrigin) * MRotation * MRotationOrigin * MTranslation; + + XMVECTOR VScaling = XMVectorSelect(g_XMOne.v, Scaling, g_XMSelect1100.v); + XMMATRIX MScaling = XMMatrixScalingFromVector(VScaling); + XMVECTOR VRotationOrigin = XMVectorSelect(g_XMSelect1100.v, RotationOrigin, g_XMSelect1100.v); + XMMATRIX MRotation = XMMatrixRotationZ(Rotation); + XMVECTOR VTranslation = XMVectorSelect(g_XMSelect1100.v, Translation, g_XMSelect1100.v); + + XMMATRIX M; + M = MScaling; + M.r[3] = XMVectorSubtract(M.r[3], VRotationOrigin); + M = XMMatrixMultiply(M, MRotation); + M.r[3] = XMVectorAdd(M.r[3], VRotationOrigin); + M.r[3] = XMVectorAdd(M.r[3], VTranslation); + return M; +} + +//------------------------------------------------------------------------------ + +inline XMMATRIX XM_CALLCONV XMMatrixAffineTransformation +( + FXMVECTOR Scaling, + FXMVECTOR RotationOrigin, + FXMVECTOR RotationQuaternion, + GXMVECTOR Translation +) noexcept +{ + // M = MScaling * Inverse(MRotationOrigin) * MRotation * MRotationOrigin * MTranslation; + + XMMATRIX MScaling = XMMatrixScalingFromVector(Scaling); + XMVECTOR VRotationOrigin = XMVectorSelect(g_XMSelect1110.v, RotationOrigin, g_XMSelect1110.v); + XMMATRIX MRotation = XMMatrixRotationQuaternion(RotationQuaternion); + XMVECTOR VTranslation = XMVectorSelect(g_XMSelect1110.v, Translation, g_XMSelect1110.v); + + XMMATRIX M; + M = MScaling; + M.r[3] = XMVectorSubtract(M.r[3], VRotationOrigin); + M = XMMatrixMultiply(M, MRotation); + M.r[3] = XMVectorAdd(M.r[3], VRotationOrigin); + M.r[3] = XMVectorAdd(M.r[3], VTranslation); + return M; +} + +//------------------------------------------------------------------------------ + +inline XMMATRIX XM_CALLCONV XMMatrixReflect(FXMVECTOR ReflectionPlane) noexcept +{ + assert(!XMVector3Equal(ReflectionPlane, XMVectorZero())); + assert(!XMPlaneIsInfinite(ReflectionPlane)); + + static const XMVECTORF32 NegativeTwo = { { { -2.0f, -2.0f, -2.0f, 0.0f } } }; + + XMVECTOR P = XMPlaneNormalize(ReflectionPlane); + XMVECTOR S = XMVectorMultiply(P, NegativeTwo); + + XMVECTOR A = XMVectorSplatX(P); + XMVECTOR B = XMVectorSplatY(P); + XMVECTOR C = XMVectorSplatZ(P); + XMVECTOR D = XMVectorSplatW(P); + + XMMATRIX M; + M.r[0] = XMVectorMultiplyAdd(A, S, g_XMIdentityR0.v); + M.r[1] = XMVectorMultiplyAdd(B, S, g_XMIdentityR1.v); + M.r[2] = XMVectorMultiplyAdd(C, S, g_XMIdentityR2.v); + M.r[3] = XMVectorMultiplyAdd(D, S, g_XMIdentityR3.v); + return M; +} + +//------------------------------------------------------------------------------ + +inline XMMATRIX XM_CALLCONV XMMatrixShadow +( + FXMVECTOR ShadowPlane, + FXMVECTOR LightPosition +) noexcept +{ + static const XMVECTORU32 Select0001 = { { { XM_SELECT_0, XM_SELECT_0, XM_SELECT_0, XM_SELECT_1 } } }; + + assert(!XMVector3Equal(ShadowPlane, XMVectorZero())); + assert(!XMPlaneIsInfinite(ShadowPlane)); + + XMVECTOR P = XMPlaneNormalize(ShadowPlane); + XMVECTOR Dot = XMPlaneDot(P, LightPosition); + P = XMVectorNegate(P); + XMVECTOR D = XMVectorSplatW(P); + XMVECTOR C = XMVectorSplatZ(P); + XMVECTOR B = XMVectorSplatY(P); + XMVECTOR A = XMVectorSplatX(P); + Dot = XMVectorSelect(Select0001.v, Dot, Select0001.v); + + XMMATRIX M; + M.r[3] = XMVectorMultiplyAdd(D, LightPosition, Dot); + Dot = XMVectorRotateLeft(Dot, 1); + M.r[2] = XMVectorMultiplyAdd(C, LightPosition, Dot); + Dot = XMVectorRotateLeft(Dot, 1); + M.r[1] = XMVectorMultiplyAdd(B, LightPosition, Dot); + Dot = XMVectorRotateLeft(Dot, 1); + M.r[0] = XMVectorMultiplyAdd(A, LightPosition, Dot); + return M; +} + +//------------------------------------------------------------------------------ +// View and projection initialization operations +//------------------------------------------------------------------------------ + +inline XMMATRIX XM_CALLCONV XMMatrixLookAtLH +( + FXMVECTOR EyePosition, + FXMVECTOR FocusPosition, + FXMVECTOR UpDirection +) noexcept +{ + XMVECTOR EyeDirection = XMVectorSubtract(FocusPosition, EyePosition); + return XMMatrixLookToLH(EyePosition, EyeDirection, UpDirection); +} + +//------------------------------------------------------------------------------ + +inline XMMATRIX XM_CALLCONV XMMatrixLookAtRH +( + FXMVECTOR EyePosition, + FXMVECTOR FocusPosition, + FXMVECTOR UpDirection +) noexcept +{ + XMVECTOR NegEyeDirection = XMVectorSubtract(EyePosition, FocusPosition); + return XMMatrixLookToLH(EyePosition, NegEyeDirection, UpDirection); +} + +//------------------------------------------------------------------------------ + +inline XMMATRIX XM_CALLCONV XMMatrixLookToLH +( + FXMVECTOR EyePosition, + FXMVECTOR EyeDirection, + FXMVECTOR UpDirection +) noexcept +{ + assert(!XMVector3Equal(EyeDirection, XMVectorZero())); + assert(!XMVector3IsInfinite(EyeDirection)); + assert(!XMVector3Equal(UpDirection, XMVectorZero())); + assert(!XMVector3IsInfinite(UpDirection)); + + XMVECTOR R2 = XMVector3Normalize(EyeDirection); + + XMVECTOR R0 = XMVector3Cross(UpDirection, R2); + R0 = XMVector3Normalize(R0); + + XMVECTOR R1 = XMVector3Cross(R2, R0); + + XMVECTOR NegEyePosition = XMVectorNegate(EyePosition); + + XMVECTOR D0 = XMVector3Dot(R0, NegEyePosition); + XMVECTOR D1 = XMVector3Dot(R1, NegEyePosition); + XMVECTOR D2 = XMVector3Dot(R2, NegEyePosition); + + XMMATRIX M; + M.r[0] = XMVectorSelect(D0, R0, g_XMSelect1110.v); + M.r[1] = XMVectorSelect(D1, R1, g_XMSelect1110.v); + M.r[2] = XMVectorSelect(D2, R2, g_XMSelect1110.v); + M.r[3] = g_XMIdentityR3.v; + + M = XMMatrixTranspose(M); + + return M; +} + +//------------------------------------------------------------------------------ + +inline XMMATRIX XM_CALLCONV XMMatrixLookToRH +( + FXMVECTOR EyePosition, + FXMVECTOR EyeDirection, + FXMVECTOR UpDirection +) noexcept +{ + XMVECTOR NegEyeDirection = XMVectorNegate(EyeDirection); + return XMMatrixLookToLH(EyePosition, NegEyeDirection, UpDirection); +} + +//------------------------------------------------------------------------------ + +#ifdef _PREFAST_ +#pragma prefast(push) +#pragma prefast(disable:28931, "PREfast noise: Esp:1266") +#endif + +inline XMMATRIX XM_CALLCONV XMMatrixPerspectiveLH +( + float ViewWidth, + float ViewHeight, + float NearZ, + float FarZ +) noexcept +{ + assert(NearZ > 0.f && FarZ > 0.f); + assert(!XMScalarNearEqual(ViewWidth, 0.0f, 0.00001f)); + assert(!XMScalarNearEqual(ViewHeight, 0.0f, 0.00001f)); + assert(!XMScalarNearEqual(FarZ, NearZ, 0.00001f)); + +#if defined(_XM_NO_INTRINSICS_) + + float TwoNearZ = NearZ + NearZ; + float fRange = FarZ / (FarZ - NearZ); + + XMMATRIX M; + M.m[0][0] = TwoNearZ / ViewWidth; + M.m[0][1] = 0.0f; + M.m[0][2] = 0.0f; + M.m[0][3] = 0.0f; + + M.m[1][0] = 0.0f; + M.m[1][1] = TwoNearZ / ViewHeight; + M.m[1][2] = 0.0f; + M.m[1][3] = 0.0f; + + M.m[2][0] = 0.0f; + M.m[2][1] = 0.0f; + M.m[2][2] = fRange; + M.m[2][3] = 1.0f; + + M.m[3][0] = 0.0f; + M.m[3][1] = 0.0f; + M.m[3][2] = -fRange * NearZ; + M.m[3][3] = 0.0f; + return M; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float TwoNearZ = NearZ + NearZ; + float fRange = FarZ / (FarZ - NearZ); + const float32x4_t Zero = vdupq_n_f32(0); + XMMATRIX M; + M.r[0] = vsetq_lane_f32(TwoNearZ / ViewWidth, Zero, 0); + M.r[1] = vsetq_lane_f32(TwoNearZ / ViewHeight, Zero, 1); + M.r[2] = vsetq_lane_f32(fRange, g_XMIdentityR3.v, 2); + M.r[3] = vsetq_lane_f32(-fRange * NearZ, Zero, 2); + return M; +#elif defined(_XM_SSE_INTRINSICS_) + XMMATRIX M; + float TwoNearZ = NearZ + NearZ; + float fRange = FarZ / (FarZ - NearZ); + // Note: This is recorded on the stack + XMVECTOR rMem = { + TwoNearZ / ViewWidth, + TwoNearZ / ViewHeight, + fRange, + -fRange * NearZ + }; + // Copy from memory to SSE register + XMVECTOR vValues = rMem; + XMVECTOR vTemp = _mm_setzero_ps(); + // Copy x only + vTemp = _mm_move_ss(vTemp, vValues); + // TwoNearZ / ViewWidth,0,0,0 + M.r[0] = vTemp; + // 0,TwoNearZ / ViewHeight,0,0 + vTemp = vValues; + vTemp = _mm_and_ps(vTemp, g_XMMaskY); + M.r[1] = vTemp; + // x=fRange,y=-fRange * NearZ,0,1.0f + vValues = _mm_shuffle_ps(vValues, g_XMIdentityR3, _MM_SHUFFLE(3, 2, 3, 2)); + // 0,0,fRange,1.0f + vTemp = _mm_setzero_ps(); + vTemp = _mm_shuffle_ps(vTemp, vValues, _MM_SHUFFLE(3, 0, 0, 0)); + M.r[2] = vTemp; + // 0,0,-fRange * NearZ,0 + vTemp = _mm_shuffle_ps(vTemp, vValues, _MM_SHUFFLE(2, 1, 0, 0)); + M.r[3] = vTemp; + return M; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMMATRIX XM_CALLCONV XMMatrixPerspectiveRH +( + float ViewWidth, + float ViewHeight, + float NearZ, + float FarZ +) noexcept +{ + assert(NearZ > 0.f && FarZ > 0.f); + assert(!XMScalarNearEqual(ViewWidth, 0.0f, 0.00001f)); + assert(!XMScalarNearEqual(ViewHeight, 0.0f, 0.00001f)); + assert(!XMScalarNearEqual(FarZ, NearZ, 0.00001f)); + +#if defined(_XM_NO_INTRINSICS_) + + float TwoNearZ = NearZ + NearZ; + float fRange = FarZ / (NearZ - FarZ); + + XMMATRIX M; + M.m[0][0] = TwoNearZ / ViewWidth; + M.m[0][1] = 0.0f; + M.m[0][2] = 0.0f; + M.m[0][3] = 0.0f; + + M.m[1][0] = 0.0f; + M.m[1][1] = TwoNearZ / ViewHeight; + M.m[1][2] = 0.0f; + M.m[1][3] = 0.0f; + + M.m[2][0] = 0.0f; + M.m[2][1] = 0.0f; + M.m[2][2] = fRange; + M.m[2][3] = -1.0f; + + M.m[3][0] = 0.0f; + M.m[3][1] = 0.0f; + M.m[3][2] = fRange * NearZ; + M.m[3][3] = 0.0f; + return M; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float TwoNearZ = NearZ + NearZ; + float fRange = FarZ / (NearZ - FarZ); + const float32x4_t Zero = vdupq_n_f32(0); + + XMMATRIX M; + M.r[0] = vsetq_lane_f32(TwoNearZ / ViewWidth, Zero, 0); + M.r[1] = vsetq_lane_f32(TwoNearZ / ViewHeight, Zero, 1); + M.r[2] = vsetq_lane_f32(fRange, g_XMNegIdentityR3.v, 2); + M.r[3] = vsetq_lane_f32(fRange * NearZ, Zero, 2); + return M; +#elif defined(_XM_SSE_INTRINSICS_) + XMMATRIX M; + float TwoNearZ = NearZ + NearZ; + float fRange = FarZ / (NearZ - FarZ); + // Note: This is recorded on the stack + XMVECTOR rMem = { + TwoNearZ / ViewWidth, + TwoNearZ / ViewHeight, + fRange, + fRange * NearZ + }; + // Copy from memory to SSE register + XMVECTOR vValues = rMem; + XMVECTOR vTemp = _mm_setzero_ps(); + // Copy x only + vTemp = _mm_move_ss(vTemp, vValues); + // TwoNearZ / ViewWidth,0,0,0 + M.r[0] = vTemp; + // 0,TwoNearZ / ViewHeight,0,0 + vTemp = vValues; + vTemp = _mm_and_ps(vTemp, g_XMMaskY); + M.r[1] = vTemp; + // x=fRange,y=-fRange * NearZ,0,-1.0f + vValues = _mm_shuffle_ps(vValues, g_XMNegIdentityR3, _MM_SHUFFLE(3, 2, 3, 2)); + // 0,0,fRange,-1.0f + vTemp = _mm_setzero_ps(); + vTemp = _mm_shuffle_ps(vTemp, vValues, _MM_SHUFFLE(3, 0, 0, 0)); + M.r[2] = vTemp; + // 0,0,-fRange * NearZ,0 + vTemp = _mm_shuffle_ps(vTemp, vValues, _MM_SHUFFLE(2, 1, 0, 0)); + M.r[3] = vTemp; + return M; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMMATRIX XM_CALLCONV XMMatrixPerspectiveFovLH +( + float FovAngleY, + float AspectRatio, + float NearZ, + float FarZ +) noexcept +{ + assert(NearZ > 0.f && FarZ > 0.f); + assert(!XMScalarNearEqual(FovAngleY, 0.0f, 0.00001f * 2.0f)); + assert(!XMScalarNearEqual(AspectRatio, 0.0f, 0.00001f)); + assert(!XMScalarNearEqual(FarZ, NearZ, 0.00001f)); + +#if defined(_XM_NO_INTRINSICS_) + + float SinFov; + float CosFov; + XMScalarSinCos(&SinFov, &CosFov, 0.5f * FovAngleY); + + float Height = CosFov / SinFov; + float Width = Height / AspectRatio; + float fRange = FarZ / (FarZ - NearZ); + + XMMATRIX M; + M.m[0][0] = Width; + M.m[0][1] = 0.0f; + M.m[0][2] = 0.0f; + M.m[0][3] = 0.0f; + + M.m[1][0] = 0.0f; + M.m[1][1] = Height; + M.m[1][2] = 0.0f; + M.m[1][3] = 0.0f; + + M.m[2][0] = 0.0f; + M.m[2][1] = 0.0f; + M.m[2][2] = fRange; + M.m[2][3] = 1.0f; + + M.m[3][0] = 0.0f; + M.m[3][1] = 0.0f; + M.m[3][2] = -fRange * NearZ; + M.m[3][3] = 0.0f; + return M; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float SinFov; + float CosFov; + XMScalarSinCos(&SinFov, &CosFov, 0.5f * FovAngleY); + + float fRange = FarZ / (FarZ - NearZ); + float Height = CosFov / SinFov; + float Width = Height / AspectRatio; + const float32x4_t Zero = vdupq_n_f32(0); + + XMMATRIX M; + M.r[0] = vsetq_lane_f32(Width, Zero, 0); + M.r[1] = vsetq_lane_f32(Height, Zero, 1); + M.r[2] = vsetq_lane_f32(fRange, g_XMIdentityR3.v, 2); + M.r[3] = vsetq_lane_f32(-fRange * NearZ, Zero, 2); + return M; +#elif defined(_XM_SSE_INTRINSICS_) + float SinFov; + float CosFov; + XMScalarSinCos(&SinFov, &CosFov, 0.5f * FovAngleY); + + float fRange = FarZ / (FarZ - NearZ); + // Note: This is recorded on the stack + float Height = CosFov / SinFov; + XMVECTOR rMem = { + Height / AspectRatio, + Height, + fRange, + -fRange * NearZ + }; + // Copy from memory to SSE register + XMVECTOR vValues = rMem; + XMVECTOR vTemp = _mm_setzero_ps(); + // Copy x only + vTemp = _mm_move_ss(vTemp, vValues); + // Height / AspectRatio,0,0,0 + XMMATRIX M; + M.r[0] = vTemp; + // 0,Height,0,0 + vTemp = vValues; + vTemp = _mm_and_ps(vTemp, g_XMMaskY); + M.r[1] = vTemp; + // x=fRange,y=-fRange * NearZ,0,1.0f + vTemp = _mm_setzero_ps(); + vValues = _mm_shuffle_ps(vValues, g_XMIdentityR3, _MM_SHUFFLE(3, 2, 3, 2)); + // 0,0,fRange,1.0f + vTemp = _mm_shuffle_ps(vTemp, vValues, _MM_SHUFFLE(3, 0, 0, 0)); + M.r[2] = vTemp; + // 0,0,-fRange * NearZ,0.0f + vTemp = _mm_shuffle_ps(vTemp, vValues, _MM_SHUFFLE(2, 1, 0, 0)); + M.r[3] = vTemp; + return M; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMMATRIX XM_CALLCONV XMMatrixPerspectiveFovRH +( + float FovAngleY, + float AspectRatio, + float NearZ, + float FarZ +) noexcept +{ + assert(NearZ > 0.f && FarZ > 0.f); + assert(!XMScalarNearEqual(FovAngleY, 0.0f, 0.00001f * 2.0f)); + assert(!XMScalarNearEqual(AspectRatio, 0.0f, 0.00001f)); + assert(!XMScalarNearEqual(FarZ, NearZ, 0.00001f)); + +#if defined(_XM_NO_INTRINSICS_) + + float SinFov; + float CosFov; + XMScalarSinCos(&SinFov, &CosFov, 0.5f * FovAngleY); + + float Height = CosFov / SinFov; + float Width = Height / AspectRatio; + float fRange = FarZ / (NearZ - FarZ); + + XMMATRIX M; + M.m[0][0] = Width; + M.m[0][1] = 0.0f; + M.m[0][2] = 0.0f; + M.m[0][3] = 0.0f; + + M.m[1][0] = 0.0f; + M.m[1][1] = Height; + M.m[1][2] = 0.0f; + M.m[1][3] = 0.0f; + + M.m[2][0] = 0.0f; + M.m[2][1] = 0.0f; + M.m[2][2] = fRange; + M.m[2][3] = -1.0f; + + M.m[3][0] = 0.0f; + M.m[3][1] = 0.0f; + M.m[3][2] = fRange * NearZ; + M.m[3][3] = 0.0f; + return M; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float SinFov; + float CosFov; + XMScalarSinCos(&SinFov, &CosFov, 0.5f * FovAngleY); + float fRange = FarZ / (NearZ - FarZ); + float Height = CosFov / SinFov; + float Width = Height / AspectRatio; + const float32x4_t Zero = vdupq_n_f32(0); + + XMMATRIX M; + M.r[0] = vsetq_lane_f32(Width, Zero, 0); + M.r[1] = vsetq_lane_f32(Height, Zero, 1); + M.r[2] = vsetq_lane_f32(fRange, g_XMNegIdentityR3.v, 2); + M.r[3] = vsetq_lane_f32(fRange * NearZ, Zero, 2); + return M; +#elif defined(_XM_SSE_INTRINSICS_) + float SinFov; + float CosFov; + XMScalarSinCos(&SinFov, &CosFov, 0.5f * FovAngleY); + float fRange = FarZ / (NearZ - FarZ); + // Note: This is recorded on the stack + float Height = CosFov / SinFov; + XMVECTOR rMem = { + Height / AspectRatio, + Height, + fRange, + fRange * NearZ + }; + // Copy from memory to SSE register + XMVECTOR vValues = rMem; + XMVECTOR vTemp = _mm_setzero_ps(); + // Copy x only + vTemp = _mm_move_ss(vTemp, vValues); + // Height / AspectRatio,0,0,0 + XMMATRIX M; + M.r[0] = vTemp; + // 0,Height,0,0 + vTemp = vValues; + vTemp = _mm_and_ps(vTemp, g_XMMaskY); + M.r[1] = vTemp; + // x=fRange,y=-fRange * NearZ,0,-1.0f + vTemp = _mm_setzero_ps(); + vValues = _mm_shuffle_ps(vValues, g_XMNegIdentityR3, _MM_SHUFFLE(3, 2, 3, 2)); + // 0,0,fRange,-1.0f + vTemp = _mm_shuffle_ps(vTemp, vValues, _MM_SHUFFLE(3, 0, 0, 0)); + M.r[2] = vTemp; + // 0,0,fRange * NearZ,0.0f + vTemp = _mm_shuffle_ps(vTemp, vValues, _MM_SHUFFLE(2, 1, 0, 0)); + M.r[3] = vTemp; + return M; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMMATRIX XM_CALLCONV XMMatrixPerspectiveOffCenterLH +( + float ViewLeft, + float ViewRight, + float ViewBottom, + float ViewTop, + float NearZ, + float FarZ +) noexcept +{ + assert(NearZ > 0.f && FarZ > 0.f); + assert(!XMScalarNearEqual(ViewRight, ViewLeft, 0.00001f)); + assert(!XMScalarNearEqual(ViewTop, ViewBottom, 0.00001f)); + assert(!XMScalarNearEqual(FarZ, NearZ, 0.00001f)); + +#if defined(_XM_NO_INTRINSICS_) + + float TwoNearZ = NearZ + NearZ; + float ReciprocalWidth = 1.0f / (ViewRight - ViewLeft); + float ReciprocalHeight = 1.0f / (ViewTop - ViewBottom); + float fRange = FarZ / (FarZ - NearZ); + + XMMATRIX M; + M.m[0][0] = TwoNearZ * ReciprocalWidth; + M.m[0][1] = 0.0f; + M.m[0][2] = 0.0f; + M.m[0][3] = 0.0f; + + M.m[1][0] = 0.0f; + M.m[1][1] = TwoNearZ * ReciprocalHeight; + M.m[1][2] = 0.0f; + M.m[1][3] = 0.0f; + + M.m[2][0] = -(ViewLeft + ViewRight) * ReciprocalWidth; + M.m[2][1] = -(ViewTop + ViewBottom) * ReciprocalHeight; + M.m[2][2] = fRange; + M.m[2][3] = 1.0f; + + M.m[3][0] = 0.0f; + M.m[3][1] = 0.0f; + M.m[3][2] = -fRange * NearZ; + M.m[3][3] = 0.0f; + return M; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float TwoNearZ = NearZ + NearZ; + float ReciprocalWidth = 1.0f / (ViewRight - ViewLeft); + float ReciprocalHeight = 1.0f / (ViewTop - ViewBottom); + float fRange = FarZ / (FarZ - NearZ); + const float32x4_t Zero = vdupq_n_f32(0); + + XMMATRIX M; + M.r[0] = vsetq_lane_f32(TwoNearZ * ReciprocalWidth, Zero, 0); + M.r[1] = vsetq_lane_f32(TwoNearZ * ReciprocalHeight, Zero, 1); + M.r[2] = XMVectorSet(-(ViewLeft + ViewRight) * ReciprocalWidth, + -(ViewTop + ViewBottom) * ReciprocalHeight, + fRange, + 1.0f); + M.r[3] = vsetq_lane_f32(-fRange * NearZ, Zero, 2); + return M; +#elif defined(_XM_SSE_INTRINSICS_) + XMMATRIX M; + float TwoNearZ = NearZ + NearZ; + float ReciprocalWidth = 1.0f / (ViewRight - ViewLeft); + float ReciprocalHeight = 1.0f / (ViewTop - ViewBottom); + float fRange = FarZ / (FarZ - NearZ); + // Note: This is recorded on the stack + XMVECTOR rMem = { + TwoNearZ * ReciprocalWidth, + TwoNearZ * ReciprocalHeight, + -fRange * NearZ, + 0 + }; + // Copy from memory to SSE register + XMVECTOR vValues = rMem; + XMVECTOR vTemp = _mm_setzero_ps(); + // Copy x only + vTemp = _mm_move_ss(vTemp, vValues); + // TwoNearZ*ReciprocalWidth,0,0,0 + M.r[0] = vTemp; + // 0,TwoNearZ*ReciprocalHeight,0,0 + vTemp = vValues; + vTemp = _mm_and_ps(vTemp, g_XMMaskY); + M.r[1] = vTemp; + // 0,0,fRange,1.0f + M.r[2] = XMVectorSet(-(ViewLeft + ViewRight) * ReciprocalWidth, + -(ViewTop + ViewBottom) * ReciprocalHeight, + fRange, + 1.0f); + // 0,0,-fRange * NearZ,0.0f + vValues = _mm_and_ps(vValues, g_XMMaskZ); + M.r[3] = vValues; + return M; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMMATRIX XM_CALLCONV XMMatrixPerspectiveOffCenterRH +( + float ViewLeft, + float ViewRight, + float ViewBottom, + float ViewTop, + float NearZ, + float FarZ +) noexcept +{ + assert(NearZ > 0.f && FarZ > 0.f); + assert(!XMScalarNearEqual(ViewRight, ViewLeft, 0.00001f)); + assert(!XMScalarNearEqual(ViewTop, ViewBottom, 0.00001f)); + assert(!XMScalarNearEqual(FarZ, NearZ, 0.00001f)); + +#if defined(_XM_NO_INTRINSICS_) + + float TwoNearZ = NearZ + NearZ; + float ReciprocalWidth = 1.0f / (ViewRight - ViewLeft); + float ReciprocalHeight = 1.0f / (ViewTop - ViewBottom); + float fRange = FarZ / (NearZ - FarZ); + + XMMATRIX M; + M.m[0][0] = TwoNearZ * ReciprocalWidth; + M.m[0][1] = 0.0f; + M.m[0][2] = 0.0f; + M.m[0][3] = 0.0f; + + M.m[1][0] = 0.0f; + M.m[1][1] = TwoNearZ * ReciprocalHeight; + M.m[1][2] = 0.0f; + M.m[1][3] = 0.0f; + + M.m[2][0] = (ViewLeft + ViewRight) * ReciprocalWidth; + M.m[2][1] = (ViewTop + ViewBottom) * ReciprocalHeight; + M.m[2][2] = fRange; + M.m[2][3] = -1.0f; + + M.m[3][0] = 0.0f; + M.m[3][1] = 0.0f; + M.m[3][2] = fRange * NearZ; + M.m[3][3] = 0.0f; + return M; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float TwoNearZ = NearZ + NearZ; + float ReciprocalWidth = 1.0f / (ViewRight - ViewLeft); + float ReciprocalHeight = 1.0f / (ViewTop - ViewBottom); + float fRange = FarZ / (NearZ - FarZ); + const float32x4_t Zero = vdupq_n_f32(0); + + XMMATRIX M; + M.r[0] = vsetq_lane_f32(TwoNearZ * ReciprocalWidth, Zero, 0); + M.r[1] = vsetq_lane_f32(TwoNearZ * ReciprocalHeight, Zero, 1); + M.r[2] = XMVectorSet((ViewLeft + ViewRight) * ReciprocalWidth, + (ViewTop + ViewBottom) * ReciprocalHeight, + fRange, + -1.0f); + M.r[3] = vsetq_lane_f32(fRange * NearZ, Zero, 2); + return M; +#elif defined(_XM_SSE_INTRINSICS_) + XMMATRIX M; + float TwoNearZ = NearZ + NearZ; + float ReciprocalWidth = 1.0f / (ViewRight - ViewLeft); + float ReciprocalHeight = 1.0f / (ViewTop - ViewBottom); + float fRange = FarZ / (NearZ - FarZ); + // Note: This is recorded on the stack + XMVECTOR rMem = { + TwoNearZ * ReciprocalWidth, + TwoNearZ * ReciprocalHeight, + fRange * NearZ, + 0 + }; + // Copy from memory to SSE register + XMVECTOR vValues = rMem; + XMVECTOR vTemp = _mm_setzero_ps(); + // Copy x only + vTemp = _mm_move_ss(vTemp, vValues); + // TwoNearZ*ReciprocalWidth,0,0,0 + M.r[0] = vTemp; + // 0,TwoNearZ*ReciprocalHeight,0,0 + vTemp = vValues; + vTemp = _mm_and_ps(vTemp, g_XMMaskY); + M.r[1] = vTemp; + // 0,0,fRange,1.0f + M.r[2] = XMVectorSet((ViewLeft + ViewRight) * ReciprocalWidth, + (ViewTop + ViewBottom) * ReciprocalHeight, + fRange, + -1.0f); + // 0,0,-fRange * NearZ,0.0f + vValues = _mm_and_ps(vValues, g_XMMaskZ); + M.r[3] = vValues; + return M; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMMATRIX XM_CALLCONV XMMatrixOrthographicLH +( + float ViewWidth, + float ViewHeight, + float NearZ, + float FarZ +) noexcept +{ + assert(!XMScalarNearEqual(ViewWidth, 0.0f, 0.00001f)); + assert(!XMScalarNearEqual(ViewHeight, 0.0f, 0.00001f)); + assert(!XMScalarNearEqual(FarZ, NearZ, 0.00001f)); + +#if defined(_XM_NO_INTRINSICS_) + + float fRange = 1.0f / (FarZ - NearZ); + + XMMATRIX M; + M.m[0][0] = 2.0f / ViewWidth; + M.m[0][1] = 0.0f; + M.m[0][2] = 0.0f; + M.m[0][3] = 0.0f; + + M.m[1][0] = 0.0f; + M.m[1][1] = 2.0f / ViewHeight; + M.m[1][2] = 0.0f; + M.m[1][3] = 0.0f; + + M.m[2][0] = 0.0f; + M.m[2][1] = 0.0f; + M.m[2][2] = fRange; + M.m[2][3] = 0.0f; + + M.m[3][0] = 0.0f; + M.m[3][1] = 0.0f; + M.m[3][2] = -fRange * NearZ; + M.m[3][3] = 1.0f; + return M; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float fRange = 1.0f / (FarZ - NearZ); + + const float32x4_t Zero = vdupq_n_f32(0); + XMMATRIX M; + M.r[0] = vsetq_lane_f32(2.0f / ViewWidth, Zero, 0); + M.r[1] = vsetq_lane_f32(2.0f / ViewHeight, Zero, 1); + M.r[2] = vsetq_lane_f32(fRange, Zero, 2); + M.r[3] = vsetq_lane_f32(-fRange * NearZ, g_XMIdentityR3.v, 2); + return M; +#elif defined(_XM_SSE_INTRINSICS_) + XMMATRIX M; + float fRange = 1.0f / (FarZ - NearZ); + // Note: This is recorded on the stack + XMVECTOR rMem = { + 2.0f / ViewWidth, + 2.0f / ViewHeight, + fRange, + -fRange * NearZ + }; + // Copy from memory to SSE register + XMVECTOR vValues = rMem; + XMVECTOR vTemp = _mm_setzero_ps(); + // Copy x only + vTemp = _mm_move_ss(vTemp, vValues); + // 2.0f / ViewWidth,0,0,0 + M.r[0] = vTemp; + // 0,2.0f / ViewHeight,0,0 + vTemp = vValues; + vTemp = _mm_and_ps(vTemp, g_XMMaskY); + M.r[1] = vTemp; + // x=fRange,y=-fRange * NearZ,0,1.0f + vTemp = _mm_setzero_ps(); + vValues = _mm_shuffle_ps(vValues, g_XMIdentityR3, _MM_SHUFFLE(3, 2, 3, 2)); + // 0,0,fRange,0.0f + vTemp = _mm_shuffle_ps(vTemp, vValues, _MM_SHUFFLE(2, 0, 0, 0)); + M.r[2] = vTemp; + // 0,0,-fRange * NearZ,1.0f + vTemp = _mm_shuffle_ps(vTemp, vValues, _MM_SHUFFLE(3, 1, 0, 0)); + M.r[3] = vTemp; + return M; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMMATRIX XM_CALLCONV XMMatrixOrthographicRH +( + float ViewWidth, + float ViewHeight, + float NearZ, + float FarZ +) noexcept +{ + assert(!XMScalarNearEqual(ViewWidth, 0.0f, 0.00001f)); + assert(!XMScalarNearEqual(ViewHeight, 0.0f, 0.00001f)); + assert(!XMScalarNearEqual(FarZ, NearZ, 0.00001f)); + +#if defined(_XM_NO_INTRINSICS_) + + float fRange = 1.0f / (NearZ - FarZ); + + XMMATRIX M; + M.m[0][0] = 2.0f / ViewWidth; + M.m[0][1] = 0.0f; + M.m[0][2] = 0.0f; + M.m[0][3] = 0.0f; + + M.m[1][0] = 0.0f; + M.m[1][1] = 2.0f / ViewHeight; + M.m[1][2] = 0.0f; + M.m[1][3] = 0.0f; + + M.m[2][0] = 0.0f; + M.m[2][1] = 0.0f; + M.m[2][2] = fRange; + M.m[2][3] = 0.0f; + + M.m[3][0] = 0.0f; + M.m[3][1] = 0.0f; + M.m[3][2] = fRange * NearZ; + M.m[3][3] = 1.0f; + return M; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float fRange = 1.0f / (NearZ - FarZ); + + const float32x4_t Zero = vdupq_n_f32(0); + XMMATRIX M; + M.r[0] = vsetq_lane_f32(2.0f / ViewWidth, Zero, 0); + M.r[1] = vsetq_lane_f32(2.0f / ViewHeight, Zero, 1); + M.r[2] = vsetq_lane_f32(fRange, Zero, 2); + M.r[3] = vsetq_lane_f32(fRange * NearZ, g_XMIdentityR3.v, 2); + return M; +#elif defined(_XM_SSE_INTRINSICS_) + XMMATRIX M; + float fRange = 1.0f / (NearZ - FarZ); + // Note: This is recorded on the stack + XMVECTOR rMem = { + 2.0f / ViewWidth, + 2.0f / ViewHeight, + fRange, + fRange * NearZ + }; + // Copy from memory to SSE register + XMVECTOR vValues = rMem; + XMVECTOR vTemp = _mm_setzero_ps(); + // Copy x only + vTemp = _mm_move_ss(vTemp, vValues); + // 2.0f / ViewWidth,0,0,0 + M.r[0] = vTemp; + // 0,2.0f / ViewHeight,0,0 + vTemp = vValues; + vTemp = _mm_and_ps(vTemp, g_XMMaskY); + M.r[1] = vTemp; + // x=fRange,y=fRange * NearZ,0,1.0f + vTemp = _mm_setzero_ps(); + vValues = _mm_shuffle_ps(vValues, g_XMIdentityR3, _MM_SHUFFLE(3, 2, 3, 2)); + // 0,0,fRange,0.0f + vTemp = _mm_shuffle_ps(vTemp, vValues, _MM_SHUFFLE(2, 0, 0, 0)); + M.r[2] = vTemp; + // 0,0,fRange * NearZ,1.0f + vTemp = _mm_shuffle_ps(vTemp, vValues, _MM_SHUFFLE(3, 1, 0, 0)); + M.r[3] = vTemp; + return M; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMMATRIX XM_CALLCONV XMMatrixOrthographicOffCenterLH +( + float ViewLeft, + float ViewRight, + float ViewBottom, + float ViewTop, + float NearZ, + float FarZ +) noexcept +{ + assert(!XMScalarNearEqual(ViewRight, ViewLeft, 0.00001f)); + assert(!XMScalarNearEqual(ViewTop, ViewBottom, 0.00001f)); + assert(!XMScalarNearEqual(FarZ, NearZ, 0.00001f)); + +#if defined(_XM_NO_INTRINSICS_) + + float ReciprocalWidth = 1.0f / (ViewRight - ViewLeft); + float ReciprocalHeight = 1.0f / (ViewTop - ViewBottom); + float fRange = 1.0f / (FarZ - NearZ); + + XMMATRIX M; + M.m[0][0] = ReciprocalWidth + ReciprocalWidth; + M.m[0][1] = 0.0f; + M.m[0][2] = 0.0f; + M.m[0][3] = 0.0f; + + M.m[1][0] = 0.0f; + M.m[1][1] = ReciprocalHeight + ReciprocalHeight; + M.m[1][2] = 0.0f; + M.m[1][3] = 0.0f; + + M.m[2][0] = 0.0f; + M.m[2][1] = 0.0f; + M.m[2][2] = fRange; + M.m[2][3] = 0.0f; + + M.m[3][0] = -(ViewLeft + ViewRight) * ReciprocalWidth; + M.m[3][1] = -(ViewTop + ViewBottom) * ReciprocalHeight; + M.m[3][2] = -fRange * NearZ; + M.m[3][3] = 1.0f; + return M; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float ReciprocalWidth = 1.0f / (ViewRight - ViewLeft); + float ReciprocalHeight = 1.0f / (ViewTop - ViewBottom); + float fRange = 1.0f / (FarZ - NearZ); + const float32x4_t Zero = vdupq_n_f32(0); + XMMATRIX M; + M.r[0] = vsetq_lane_f32(ReciprocalWidth + ReciprocalWidth, Zero, 0); + M.r[1] = vsetq_lane_f32(ReciprocalHeight + ReciprocalHeight, Zero, 1); + M.r[2] = vsetq_lane_f32(fRange, Zero, 2); + M.r[3] = XMVectorSet(-(ViewLeft + ViewRight) * ReciprocalWidth, + -(ViewTop + ViewBottom) * ReciprocalHeight, + -fRange * NearZ, + 1.0f); + return M; +#elif defined(_XM_SSE_INTRINSICS_) + XMMATRIX M; + float fReciprocalWidth = 1.0f / (ViewRight - ViewLeft); + float fReciprocalHeight = 1.0f / (ViewTop - ViewBottom); + float fRange = 1.0f / (FarZ - NearZ); + // Note: This is recorded on the stack + XMVECTOR rMem = { + fReciprocalWidth, + fReciprocalHeight, + fRange, + 1.0f + }; + XMVECTOR rMem2 = { + -(ViewLeft + ViewRight), + -(ViewTop + ViewBottom), + -NearZ, + 1.0f + }; + // Copy from memory to SSE register + XMVECTOR vValues = rMem; + XMVECTOR vTemp = _mm_setzero_ps(); + // Copy x only + vTemp = _mm_move_ss(vTemp, vValues); + // fReciprocalWidth*2,0,0,0 + vTemp = _mm_add_ss(vTemp, vTemp); + M.r[0] = vTemp; + // 0,fReciprocalHeight*2,0,0 + vTemp = vValues; + vTemp = _mm_and_ps(vTemp, g_XMMaskY); + vTemp = _mm_add_ps(vTemp, vTemp); + M.r[1] = vTemp; + // 0,0,fRange,0.0f + vTemp = vValues; + vTemp = _mm_and_ps(vTemp, g_XMMaskZ); + M.r[2] = vTemp; + // -(ViewLeft + ViewRight)*fReciprocalWidth,-(ViewTop + ViewBottom)*fReciprocalHeight,fRange*-NearZ,1.0f + vValues = _mm_mul_ps(vValues, rMem2); + M.r[3] = vValues; + return M; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMMATRIX XM_CALLCONV XMMatrixOrthographicOffCenterRH +( + float ViewLeft, + float ViewRight, + float ViewBottom, + float ViewTop, + float NearZ, + float FarZ +) noexcept +{ + assert(!XMScalarNearEqual(ViewRight, ViewLeft, 0.00001f)); + assert(!XMScalarNearEqual(ViewTop, ViewBottom, 0.00001f)); + assert(!XMScalarNearEqual(FarZ, NearZ, 0.00001f)); + +#if defined(_XM_NO_INTRINSICS_) + + float ReciprocalWidth = 1.0f / (ViewRight - ViewLeft); + float ReciprocalHeight = 1.0f / (ViewTop - ViewBottom); + float fRange = 1.0f / (NearZ - FarZ); + + XMMATRIX M; + M.m[0][0] = ReciprocalWidth + ReciprocalWidth; + M.m[0][1] = 0.0f; + M.m[0][2] = 0.0f; + M.m[0][3] = 0.0f; + + M.m[1][0] = 0.0f; + M.m[1][1] = ReciprocalHeight + ReciprocalHeight; + M.m[1][2] = 0.0f; + M.m[1][3] = 0.0f; + + M.m[2][0] = 0.0f; + M.m[2][1] = 0.0f; + M.m[2][2] = fRange; + M.m[2][3] = 0.0f; + + M.r[3] = XMVectorSet(-(ViewLeft + ViewRight) * ReciprocalWidth, + -(ViewTop + ViewBottom) * ReciprocalHeight, + fRange * NearZ, + 1.0f); + return M; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float ReciprocalWidth = 1.0f / (ViewRight - ViewLeft); + float ReciprocalHeight = 1.0f / (ViewTop - ViewBottom); + float fRange = 1.0f / (NearZ - FarZ); + const float32x4_t Zero = vdupq_n_f32(0); + XMMATRIX M; + M.r[0] = vsetq_lane_f32(ReciprocalWidth + ReciprocalWidth, Zero, 0); + M.r[1] = vsetq_lane_f32(ReciprocalHeight + ReciprocalHeight, Zero, 1); + M.r[2] = vsetq_lane_f32(fRange, Zero, 2); + M.r[3] = XMVectorSet(-(ViewLeft + ViewRight) * ReciprocalWidth, + -(ViewTop + ViewBottom) * ReciprocalHeight, + fRange * NearZ, + 1.0f); + return M; +#elif defined(_XM_SSE_INTRINSICS_) + XMMATRIX M; + float fReciprocalWidth = 1.0f / (ViewRight - ViewLeft); + float fReciprocalHeight = 1.0f / (ViewTop - ViewBottom); + float fRange = 1.0f / (NearZ - FarZ); + // Note: This is recorded on the stack + XMVECTOR rMem = { + fReciprocalWidth, + fReciprocalHeight, + fRange, + 1.0f + }; + XMVECTOR rMem2 = { + -(ViewLeft + ViewRight), + -(ViewTop + ViewBottom), + NearZ, + 1.0f + }; + // Copy from memory to SSE register + XMVECTOR vValues = rMem; + XMVECTOR vTemp = _mm_setzero_ps(); + // Copy x only + vTemp = _mm_move_ss(vTemp, vValues); + // fReciprocalWidth*2,0,0,0 + vTemp = _mm_add_ss(vTemp, vTemp); + M.r[0] = vTemp; + // 0,fReciprocalHeight*2,0,0 + vTemp = vValues; + vTemp = _mm_and_ps(vTemp, g_XMMaskY); + vTemp = _mm_add_ps(vTemp, vTemp); + M.r[1] = vTemp; + // 0,0,fRange,0.0f + vTemp = vValues; + vTemp = _mm_and_ps(vTemp, g_XMMaskZ); + M.r[2] = vTemp; + // -(ViewLeft + ViewRight)*fReciprocalWidth,-(ViewTop + ViewBottom)*fReciprocalHeight,fRange*-NearZ,1.0f + vValues = _mm_mul_ps(vValues, rMem2); + M.r[3] = vValues; + return M; +#endif +} + +#ifdef _PREFAST_ +#pragma prefast(pop) +#endif + +/**************************************************************************** + * + * XMMATRIX operators and methods + * + ****************************************************************************/ + +//------------------------------------------------------------------------------ + +inline XMMATRIX::XMMATRIX +( + float m00, float m01, float m02, float m03, + float m10, float m11, float m12, float m13, + float m20, float m21, float m22, float m23, + float m30, float m31, float m32, float m33 +) noexcept +{ + r[0] = XMVectorSet(m00, m01, m02, m03); + r[1] = XMVectorSet(m10, m11, m12, m13); + r[2] = XMVectorSet(m20, m21, m22, m23); + r[3] = XMVectorSet(m30, m31, m32, m33); +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMMATRIX::XMMATRIX(const float* pArray) noexcept +{ + assert(pArray != nullptr); + r[0] = XMLoadFloat4(reinterpret_cast(pArray)); + r[1] = XMLoadFloat4(reinterpret_cast(pArray + 4)); + r[2] = XMLoadFloat4(reinterpret_cast(pArray + 8)); + r[3] = XMLoadFloat4(reinterpret_cast(pArray + 12)); +} + +//------------------------------------------------------------------------------ + +inline XMMATRIX XMMATRIX::operator- () const noexcept +{ + XMMATRIX R; + R.r[0] = XMVectorNegate(r[0]); + R.r[1] = XMVectorNegate(r[1]); + R.r[2] = XMVectorNegate(r[2]); + R.r[3] = XMVectorNegate(r[3]); + return R; +} + +//------------------------------------------------------------------------------ + +inline XMMATRIX& XM_CALLCONV XMMATRIX::operator+= (FXMMATRIX M) noexcept +{ + r[0] = XMVectorAdd(r[0], M.r[0]); + r[1] = XMVectorAdd(r[1], M.r[1]); + r[2] = XMVectorAdd(r[2], M.r[2]); + r[3] = XMVectorAdd(r[3], M.r[3]); + return *this; +} + +//------------------------------------------------------------------------------ + +inline XMMATRIX& XM_CALLCONV XMMATRIX::operator-= (FXMMATRIX M) noexcept +{ + r[0] = XMVectorSubtract(r[0], M.r[0]); + r[1] = XMVectorSubtract(r[1], M.r[1]); + r[2] = XMVectorSubtract(r[2], M.r[2]); + r[3] = XMVectorSubtract(r[3], M.r[3]); + return *this; +} + +//------------------------------------------------------------------------------ + +inline XMMATRIX& XM_CALLCONV XMMATRIX::operator*=(FXMMATRIX M) noexcept +{ + *this = XMMatrixMultiply(*this, M); + return *this; +} + +//------------------------------------------------------------------------------ + +inline XMMATRIX& XMMATRIX::operator*= (float S) noexcept +{ + r[0] = XMVectorScale(r[0], S); + r[1] = XMVectorScale(r[1], S); + r[2] = XMVectorScale(r[2], S); + r[3] = XMVectorScale(r[3], S); + return *this; +} + +//------------------------------------------------------------------------------ + +inline XMMATRIX& XMMATRIX::operator/= (float S) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + XMVECTOR vS = XMVectorReplicate(S); + r[0] = XMVectorDivide(r[0], vS); + r[1] = XMVectorDivide(r[1], vS); + r[2] = XMVectorDivide(r[2], vS); + r[3] = XMVectorDivide(r[3], vS); + return *this; +#elif defined(_XM_ARM_NEON_INTRINSICS_) +#if defined(_M_ARM64) || defined(_M_HYBRID_X86_ARM64) || defined(_M_ARM64EC) || __aarch64__ + float32x4_t vS = vdupq_n_f32(S); + r[0] = vdivq_f32(r[0], vS); + r[1] = vdivq_f32(r[1], vS); + r[2] = vdivq_f32(r[2], vS); + r[3] = vdivq_f32(r[3], vS); +#else + // 2 iterations of Newton-Raphson refinement of reciprocal + float32x2_t vS = vdup_n_f32(S); + float32x2_t R0 = vrecpe_f32(vS); + float32x2_t S0 = vrecps_f32(R0, vS); + R0 = vmul_f32(S0, R0); + S0 = vrecps_f32(R0, vS); + R0 = vmul_f32(S0, R0); + float32x4_t Reciprocal = vcombine_f32(R0, R0); + r[0] = vmulq_f32(r[0], Reciprocal); + r[1] = vmulq_f32(r[1], Reciprocal); + r[2] = vmulq_f32(r[2], Reciprocal); + r[3] = vmulq_f32(r[3], Reciprocal); +#endif + return *this; +#elif defined(_XM_SSE_INTRINSICS_) + __m128 vS = _mm_set_ps1(S); + r[0] = _mm_div_ps(r[0], vS); + r[1] = _mm_div_ps(r[1], vS); + r[2] = _mm_div_ps(r[2], vS); + r[3] = _mm_div_ps(r[3], vS); + return *this; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMMATRIX XM_CALLCONV XMMATRIX::operator+ (FXMMATRIX M) const noexcept +{ + XMMATRIX R; + R.r[0] = XMVectorAdd(r[0], M.r[0]); + R.r[1] = XMVectorAdd(r[1], M.r[1]); + R.r[2] = XMVectorAdd(r[2], M.r[2]); + R.r[3] = XMVectorAdd(r[3], M.r[3]); + return R; +} + +//------------------------------------------------------------------------------ + +inline XMMATRIX XM_CALLCONV XMMATRIX::operator- (FXMMATRIX M) const noexcept +{ + XMMATRIX R; + R.r[0] = XMVectorSubtract(r[0], M.r[0]); + R.r[1] = XMVectorSubtract(r[1], M.r[1]); + R.r[2] = XMVectorSubtract(r[2], M.r[2]); + R.r[3] = XMVectorSubtract(r[3], M.r[3]); + return R; +} + +//------------------------------------------------------------------------------ + +inline XMMATRIX XM_CALLCONV XMMATRIX::operator*(FXMMATRIX M) const noexcept +{ + return XMMatrixMultiply(*this, M); +} + +//------------------------------------------------------------------------------ + +inline XMMATRIX XMMATRIX::operator* (float S) const noexcept +{ + XMMATRIX R; + R.r[0] = XMVectorScale(r[0], S); + R.r[1] = XMVectorScale(r[1], S); + R.r[2] = XMVectorScale(r[2], S); + R.r[3] = XMVectorScale(r[3], S); + return R; +} + +//------------------------------------------------------------------------------ + +inline XMMATRIX XMMATRIX::operator/ (float S) const noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + XMVECTOR vS = XMVectorReplicate(S); + XMMATRIX R; + R.r[0] = XMVectorDivide(r[0], vS); + R.r[1] = XMVectorDivide(r[1], vS); + R.r[2] = XMVectorDivide(r[2], vS); + R.r[3] = XMVectorDivide(r[3], vS); + return R; +#elif defined(_XM_ARM_NEON_INTRINSICS_) +#if defined(_M_ARM64) || defined(_M_HYBRID_X86_ARM64) || defined(_M_ARM64EC) || __aarch64__ + float32x4_t vS = vdupq_n_f32(S); + XMMATRIX R; + R.r[0] = vdivq_f32(r[0], vS); + R.r[1] = vdivq_f32(r[1], vS); + R.r[2] = vdivq_f32(r[2], vS); + R.r[3] = vdivq_f32(r[3], vS); +#else + // 2 iterations of Newton-Raphson refinement of reciprocal + float32x2_t vS = vdup_n_f32(S); + float32x2_t R0 = vrecpe_f32(vS); + float32x2_t S0 = vrecps_f32(R0, vS); + R0 = vmul_f32(S0, R0); + S0 = vrecps_f32(R0, vS); + R0 = vmul_f32(S0, R0); + float32x4_t Reciprocal = vcombine_f32(R0, R0); + XMMATRIX R; + R.r[0] = vmulq_f32(r[0], Reciprocal); + R.r[1] = vmulq_f32(r[1], Reciprocal); + R.r[2] = vmulq_f32(r[2], Reciprocal); + R.r[3] = vmulq_f32(r[3], Reciprocal); +#endif + return R; +#elif defined(_XM_SSE_INTRINSICS_) + __m128 vS = _mm_set_ps1(S); + XMMATRIX R; + R.r[0] = _mm_div_ps(r[0], vS); + R.r[1] = _mm_div_ps(r[1], vS); + R.r[2] = _mm_div_ps(r[2], vS); + R.r[3] = _mm_div_ps(r[3], vS); + return R; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMMATRIX XM_CALLCONV operator* +( + float S, + FXMMATRIX M + ) noexcept +{ + XMMATRIX R; + R.r[0] = XMVectorScale(M.r[0], S); + R.r[1] = XMVectorScale(M.r[1], S); + R.r[2] = XMVectorScale(M.r[2], S); + R.r[3] = XMVectorScale(M.r[3], S); + return R; +} + +/**************************************************************************** + * + * XMFLOAT3X3 operators + * + ****************************************************************************/ + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMFLOAT3X3::XMFLOAT3X3(const float* pArray) noexcept +{ + assert(pArray != nullptr); + for (size_t Row = 0; Row < 3; Row++) + { + for (size_t Column = 0; Column < 3; Column++) + { + m[Row][Column] = pArray[Row * 3 + Column]; + } + } +} + +/**************************************************************************** + * + * XMFLOAT4X3 operators + * + ****************************************************************************/ + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMFLOAT4X3::XMFLOAT4X3(const float* pArray) noexcept +{ + assert(pArray != nullptr); + + m[0][0] = pArray[0]; + m[0][1] = pArray[1]; + m[0][2] = pArray[2]; + + m[1][0] = pArray[3]; + m[1][1] = pArray[4]; + m[1][2] = pArray[5]; + + m[2][0] = pArray[6]; + m[2][1] = pArray[7]; + m[2][2] = pArray[8]; + + m[3][0] = pArray[9]; + m[3][1] = pArray[10]; + m[3][2] = pArray[11]; +} + +/**************************************************************************** +* +* XMFLOAT3X4 operators +* +****************************************************************************/ + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMFLOAT3X4::XMFLOAT3X4(const float* pArray) noexcept +{ + assert(pArray != nullptr); + + m[0][0] = pArray[0]; + m[0][1] = pArray[1]; + m[0][2] = pArray[2]; + m[0][3] = pArray[3]; + + m[1][0] = pArray[4]; + m[1][1] = pArray[5]; + m[1][2] = pArray[6]; + m[1][3] = pArray[7]; + + m[2][0] = pArray[8]; + m[2][1] = pArray[9]; + m[2][2] = pArray[10]; + m[2][3] = pArray[11]; +} + +/**************************************************************************** + * + * XMFLOAT4X4 operators + * + ****************************************************************************/ + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMFLOAT4X4::XMFLOAT4X4(const float* pArray) noexcept +{ + assert(pArray != nullptr); + + m[0][0] = pArray[0]; + m[0][1] = pArray[1]; + m[0][2] = pArray[2]; + m[0][3] = pArray[3]; + + m[1][0] = pArray[4]; + m[1][1] = pArray[5]; + m[1][2] = pArray[6]; + m[1][3] = pArray[7]; + + m[2][0] = pArray[8]; + m[2][1] = pArray[9]; + m[2][2] = pArray[10]; + m[2][3] = pArray[11]; + + m[3][0] = pArray[12]; + m[3][1] = pArray[13]; + m[3][2] = pArray[14]; + m[3][3] = pArray[15]; +} + diff --git a/Minecraft.Client/Windows64/Libs/4JLibs/impls/Windows_Libs/Render/vendor/DirectXMath/DirectXMathMisc.inl b/Minecraft.Client/Windows64/Libs/4JLibs/impls/Windows_Libs/Render/vendor/DirectXMath/DirectXMathMisc.inl new file mode 100644 index 00000000..d7981043 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/4JLibs/impls/Windows_Libs/Render/vendor/DirectXMath/DirectXMathMisc.inl @@ -0,0 +1,2493 @@ +//------------------------------------------------------------------------------------- +// DirectXMathMisc.inl -- SIMD C++ Math library +// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +// +// https://go.microsoft.com/fwlink/?LinkID=615560 +//------------------------------------------------------------------------------------- + +#pragma once + +/**************************************************************************** + * + * Quaternion + * + ****************************************************************************/ + +//------------------------------------------------------------------------------ +// Comparison operations +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ + +inline bool XM_CALLCONV XMQuaternionEqual +( + FXMVECTOR Q1, + FXMVECTOR Q2 +) noexcept +{ + return XMVector4Equal(Q1, Q2); +} + +//------------------------------------------------------------------------------ + +inline bool XM_CALLCONV XMQuaternionNotEqual +( + FXMVECTOR Q1, + FXMVECTOR Q2 +) noexcept +{ + return XMVector4NotEqual(Q1, Q2); +} + +//------------------------------------------------------------------------------ + +inline bool XM_CALLCONV XMQuaternionIsNaN(FXMVECTOR Q) noexcept +{ + return XMVector4IsNaN(Q); +} + +//------------------------------------------------------------------------------ + +inline bool XM_CALLCONV XMQuaternionIsInfinite(FXMVECTOR Q) noexcept +{ + return XMVector4IsInfinite(Q); +} + +//------------------------------------------------------------------------------ + +inline bool XM_CALLCONV XMQuaternionIsIdentity(FXMVECTOR Q) noexcept +{ + return XMVector4Equal(Q, g_XMIdentityR3.v); +} + +//------------------------------------------------------------------------------ +// Computation operations +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMQuaternionDot +( + FXMVECTOR Q1, + FXMVECTOR Q2 +) noexcept +{ + return XMVector4Dot(Q1, Q2); +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMQuaternionMultiply +( + FXMVECTOR Q1, + FXMVECTOR Q2 +) noexcept +{ + // Returns the product Q2*Q1 (which is the concatenation of a rotation Q1 followed by the rotation Q2) + + // [ (Q2.w * Q1.x) + (Q2.x * Q1.w) + (Q2.y * Q1.z) - (Q2.z * Q1.y), + // (Q2.w * Q1.y) - (Q2.x * Q1.z) + (Q2.y * Q1.w) + (Q2.z * Q1.x), + // (Q2.w * Q1.z) + (Q2.x * Q1.y) - (Q2.y * Q1.x) + (Q2.z * Q1.w), + // (Q2.w * Q1.w) - (Q2.x * Q1.x) - (Q2.y * Q1.y) - (Q2.z * Q1.z) ] + +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 Result = { { { + (Q2.vector4_f32[3] * Q1.vector4_f32[0]) + (Q2.vector4_f32[0] * Q1.vector4_f32[3]) + (Q2.vector4_f32[1] * Q1.vector4_f32[2]) - (Q2.vector4_f32[2] * Q1.vector4_f32[1]), + (Q2.vector4_f32[3] * Q1.vector4_f32[1]) - (Q2.vector4_f32[0] * Q1.vector4_f32[2]) + (Q2.vector4_f32[1] * Q1.vector4_f32[3]) + (Q2.vector4_f32[2] * Q1.vector4_f32[0]), + (Q2.vector4_f32[3] * Q1.vector4_f32[2]) + (Q2.vector4_f32[0] * Q1.vector4_f32[1]) - (Q2.vector4_f32[1] * Q1.vector4_f32[0]) + (Q2.vector4_f32[2] * Q1.vector4_f32[3]), + (Q2.vector4_f32[3] * Q1.vector4_f32[3]) - (Q2.vector4_f32[0] * Q1.vector4_f32[0]) - (Q2.vector4_f32[1] * Q1.vector4_f32[1]) - (Q2.vector4_f32[2] * Q1.vector4_f32[2]) + } } }; + return Result.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + static const XMVECTORF32 ControlWZYX = { { { 1.0f, -1.0f, 1.0f, -1.0f } } }; + static const XMVECTORF32 ControlZWXY = { { { 1.0f, 1.0f, -1.0f, -1.0f } } }; + static const XMVECTORF32 ControlYXWZ = { { { -1.0f, 1.0f, 1.0f, -1.0f } } }; + + float32x2_t Q2L = vget_low_f32(Q2); + float32x2_t Q2H = vget_high_f32(Q2); + + float32x4_t Q2X = vdupq_lane_f32(Q2L, 0); + float32x4_t Q2Y = vdupq_lane_f32(Q2L, 1); + float32x4_t Q2Z = vdupq_lane_f32(Q2H, 0); + XMVECTOR vResult = vmulq_lane_f32(Q1, Q2H, 1); + + // Mul by Q1WZYX + float32x4_t vTemp = vrev64q_f32(Q1); + vTemp = vcombine_f32(vget_high_f32(vTemp), vget_low_f32(vTemp)); + Q2X = vmulq_f32(Q2X, vTemp); + vResult = vmlaq_f32(vResult, Q2X, ControlWZYX); + + // Mul by Q1ZWXY + vTemp = vreinterpretq_f32_u32(vrev64q_u32(vreinterpretq_u32_f32(vTemp))); + Q2Y = vmulq_f32(Q2Y, vTemp); + vResult = vmlaq_f32(vResult, Q2Y, ControlZWXY); + + // Mul by Q1YXWZ + vTemp = vreinterpretq_f32_u32(vrev64q_u32(vreinterpretq_u32_f32(vTemp))); + vTemp = vcombine_f32(vget_high_f32(vTemp), vget_low_f32(vTemp)); + Q2Z = vmulq_f32(Q2Z, vTemp); + vResult = vmlaq_f32(vResult, Q2Z, ControlYXWZ); + return vResult; +#elif defined(_XM_SSE_INTRINSICS_) + static const XMVECTORF32 ControlWZYX = { { { 1.0f, -1.0f, 1.0f, -1.0f } } }; + static const XMVECTORF32 ControlZWXY = { { { 1.0f, 1.0f, -1.0f, -1.0f } } }; + static const XMVECTORF32 ControlYXWZ = { { { -1.0f, 1.0f, 1.0f, -1.0f } } }; + // Copy to SSE registers and use as few as possible for x86 + XMVECTOR Q2X = Q2; + XMVECTOR Q2Y = Q2; + XMVECTOR Q2Z = Q2; + XMVECTOR vResult = Q2; + // Splat with one instruction + vResult = XM_PERMUTE_PS(vResult, _MM_SHUFFLE(3, 3, 3, 3)); + Q2X = XM_PERMUTE_PS(Q2X, _MM_SHUFFLE(0, 0, 0, 0)); + Q2Y = XM_PERMUTE_PS(Q2Y, _MM_SHUFFLE(1, 1, 1, 1)); + Q2Z = XM_PERMUTE_PS(Q2Z, _MM_SHUFFLE(2, 2, 2, 2)); + // Retire Q1 and perform Q1*Q2W + vResult = _mm_mul_ps(vResult, Q1); + XMVECTOR Q1Shuffle = Q1; + // Shuffle the copies of Q1 + Q1Shuffle = XM_PERMUTE_PS(Q1Shuffle, _MM_SHUFFLE(0, 1, 2, 3)); + // Mul by Q1WZYX + Q2X = _mm_mul_ps(Q2X, Q1Shuffle); + Q1Shuffle = XM_PERMUTE_PS(Q1Shuffle, _MM_SHUFFLE(2, 3, 0, 1)); + // Flip the signs on y and z + vResult = XM_FMADD_PS(Q2X, ControlWZYX, vResult); + // Mul by Q1ZWXY + Q2Y = _mm_mul_ps(Q2Y, Q1Shuffle); + Q1Shuffle = XM_PERMUTE_PS(Q1Shuffle, _MM_SHUFFLE(0, 1, 2, 3)); + // Flip the signs on z and w + Q2Y = _mm_mul_ps(Q2Y, ControlZWXY); + // Mul by Q1YXWZ + Q2Z = _mm_mul_ps(Q2Z, Q1Shuffle); + // Flip the signs on x and w + Q2Y = XM_FMADD_PS(Q2Z, ControlYXWZ, Q2Y); + vResult = _mm_add_ps(vResult, Q2Y); + return vResult; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMQuaternionLengthSq(FXMVECTOR Q) noexcept +{ + return XMVector4LengthSq(Q); +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMQuaternionReciprocalLength(FXMVECTOR Q) noexcept +{ + return XMVector4ReciprocalLength(Q); +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMQuaternionLength(FXMVECTOR Q) noexcept +{ + return XMVector4Length(Q); +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMQuaternionNormalizeEst(FXMVECTOR Q) noexcept +{ + return XMVector4NormalizeEst(Q); +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMQuaternionNormalize(FXMVECTOR Q) noexcept +{ + return XMVector4Normalize(Q); +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMQuaternionConjugate(FXMVECTOR Q) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 Result = { { { + -Q.vector4_f32[0], + -Q.vector4_f32[1], + -Q.vector4_f32[2], + Q.vector4_f32[3] + } } }; + return Result.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + static const XMVECTORF32 NegativeOne3 = { { { -1.0f, -1.0f, -1.0f, 1.0f } } }; + return vmulq_f32(Q, NegativeOne3.v); +#elif defined(_XM_SSE_INTRINSICS_) + static const XMVECTORF32 NegativeOne3 = { { { -1.0f, -1.0f, -1.0f, 1.0f } } }; + return _mm_mul_ps(Q, NegativeOne3); +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMQuaternionInverse(FXMVECTOR Q) noexcept +{ + XMVECTOR L = XMVector4LengthSq(Q); + XMVECTOR Conjugate = XMQuaternionConjugate(Q); + + XMVECTOR Control = XMVectorLessOrEqual(L, g_XMEpsilon.v); + + XMVECTOR Result = XMVectorDivide(Conjugate, L); + + Result = XMVectorSelect(Result, g_XMZero, Control); + + return Result; +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMQuaternionLn(FXMVECTOR Q) noexcept +{ + static const XMVECTORF32 OneMinusEpsilon = { { { 1.0f - 0.00001f, 1.0f - 0.00001f, 1.0f - 0.00001f, 1.0f - 0.00001f } } }; + + XMVECTOR QW = XMVectorSplatW(Q); + XMVECTOR Q0 = XMVectorSelect(g_XMSelect1110.v, Q, g_XMSelect1110.v); + + XMVECTOR ControlW = XMVectorInBounds(QW, OneMinusEpsilon.v); + + XMVECTOR Theta = XMVectorACos(QW); + XMVECTOR SinTheta = XMVectorSin(Theta); + + XMVECTOR S = XMVectorDivide(Theta, SinTheta); + + XMVECTOR Result = XMVectorMultiply(Q0, S); + Result = XMVectorSelect(Q0, Result, ControlW); + + return Result; +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMQuaternionExp(FXMVECTOR Q) noexcept +{ + XMVECTOR Theta = XMVector3Length(Q); + + XMVECTOR SinTheta, CosTheta; + XMVectorSinCos(&SinTheta, &CosTheta, Theta); + + XMVECTOR S = XMVectorDivide(SinTheta, Theta); + + XMVECTOR Result = XMVectorMultiply(Q, S); + + const XMVECTOR Zero = XMVectorZero(); + XMVECTOR Control = XMVectorNearEqual(Theta, Zero, g_XMEpsilon.v); + Result = XMVectorSelect(Result, Q, Control); + + Result = XMVectorSelect(CosTheta, Result, g_XMSelect1110.v); + + return Result; +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMQuaternionSlerp +( + FXMVECTOR Q0, + FXMVECTOR Q1, + float t +) noexcept +{ + XMVECTOR T = XMVectorReplicate(t); + return XMQuaternionSlerpV(Q0, Q1, T); +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMQuaternionSlerpV +( + FXMVECTOR Q0, + FXMVECTOR Q1, + FXMVECTOR T +) noexcept +{ + assert((XMVectorGetY(T) == XMVectorGetX(T)) && (XMVectorGetZ(T) == XMVectorGetX(T)) && (XMVectorGetW(T) == XMVectorGetX(T))); + + // Result = Q0 * sin((1.0 - t) * Omega) / sin(Omega) + Q1 * sin(t * Omega) / sin(Omega) + +#if defined(_XM_NO_INTRINSICS_) || defined(_XM_ARM_NEON_INTRINSICS_) + + const XMVECTORF32 OneMinusEpsilon = { { { 1.0f - 0.00001f, 1.0f - 0.00001f, 1.0f - 0.00001f, 1.0f - 0.00001f } } }; + + XMVECTOR CosOmega = XMQuaternionDot(Q0, Q1); + + const XMVECTOR Zero = XMVectorZero(); + XMVECTOR Control = XMVectorLess(CosOmega, Zero); + XMVECTOR Sign = XMVectorSelect(g_XMOne.v, g_XMNegativeOne.v, Control); + + CosOmega = XMVectorMultiply(CosOmega, Sign); + + Control = XMVectorLess(CosOmega, OneMinusEpsilon); + + XMVECTOR SinOmega = XMVectorNegativeMultiplySubtract(CosOmega, CosOmega, g_XMOne.v); + SinOmega = XMVectorSqrt(SinOmega); + + XMVECTOR Omega = XMVectorATan2(SinOmega, CosOmega); + + XMVECTOR SignMask = XMVectorSplatSignMask(); + XMVECTOR V01 = XMVectorShiftLeft(T, Zero, 2); + SignMask = XMVectorShiftLeft(SignMask, Zero, 3); + V01 = XMVectorXorInt(V01, SignMask); + V01 = XMVectorAdd(g_XMIdentityR0.v, V01); + + XMVECTOR InvSinOmega = XMVectorReciprocal(SinOmega); + + XMVECTOR S0 = XMVectorMultiply(V01, Omega); + S0 = XMVectorSin(S0); + S0 = XMVectorMultiply(S0, InvSinOmega); + + S0 = XMVectorSelect(V01, S0, Control); + + XMVECTOR S1 = XMVectorSplatY(S0); + S0 = XMVectorSplatX(S0); + + S1 = XMVectorMultiply(S1, Sign); + + XMVECTOR Result = XMVectorMultiply(Q0, S0); + Result = XMVectorMultiplyAdd(Q1, S1, Result); + + return Result; + +#elif defined(_XM_SSE_INTRINSICS_) + static const XMVECTORF32 OneMinusEpsilon = { { { 1.0f - 0.00001f, 1.0f - 0.00001f, 1.0f - 0.00001f, 1.0f - 0.00001f } } }; + static const XMVECTORU32 SignMask2 = { { { 0x80000000, 0x00000000, 0x00000000, 0x00000000 } } }; + + XMVECTOR CosOmega = XMQuaternionDot(Q0, Q1); + + const XMVECTOR Zero = XMVectorZero(); + XMVECTOR Control = XMVectorLess(CosOmega, Zero); + XMVECTOR Sign = XMVectorSelect(g_XMOne, g_XMNegativeOne, Control); + + CosOmega = _mm_mul_ps(CosOmega, Sign); + + Control = XMVectorLess(CosOmega, OneMinusEpsilon); + + XMVECTOR SinOmega = _mm_mul_ps(CosOmega, CosOmega); + SinOmega = _mm_sub_ps(g_XMOne, SinOmega); + SinOmega = _mm_sqrt_ps(SinOmega); + + XMVECTOR Omega = XMVectorATan2(SinOmega, CosOmega); + + XMVECTOR V01 = XM_PERMUTE_PS(T, _MM_SHUFFLE(2, 3, 0, 1)); + V01 = _mm_and_ps(V01, g_XMMaskXY); + V01 = _mm_xor_ps(V01, SignMask2); + V01 = _mm_add_ps(g_XMIdentityR0, V01); + + XMVECTOR S0 = _mm_mul_ps(V01, Omega); + S0 = XMVectorSin(S0); + S0 = _mm_div_ps(S0, SinOmega); + + S0 = XMVectorSelect(V01, S0, Control); + + XMVECTOR S1 = XMVectorSplatY(S0); + S0 = XMVectorSplatX(S0); + + S1 = _mm_mul_ps(S1, Sign); + XMVECTOR Result = _mm_mul_ps(Q0, S0); + S1 = _mm_mul_ps(S1, Q1); + Result = _mm_add_ps(Result, S1); + return Result; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMQuaternionSquad +( + FXMVECTOR Q0, + FXMVECTOR Q1, + FXMVECTOR Q2, + GXMVECTOR Q3, + float t +) noexcept +{ + XMVECTOR T = XMVectorReplicate(t); + return XMQuaternionSquadV(Q0, Q1, Q2, Q3, T); +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMQuaternionSquadV +( + FXMVECTOR Q0, + FXMVECTOR Q1, + FXMVECTOR Q2, + GXMVECTOR Q3, + HXMVECTOR T +) noexcept +{ + assert((XMVectorGetY(T) == XMVectorGetX(T)) && (XMVectorGetZ(T) == XMVectorGetX(T)) && (XMVectorGetW(T) == XMVectorGetX(T))); + + XMVECTOR TP = T; + const XMVECTOR Two = XMVectorSplatConstant(2, 0); + + XMVECTOR Q03 = XMQuaternionSlerpV(Q0, Q3, T); + XMVECTOR Q12 = XMQuaternionSlerpV(Q1, Q2, T); + + TP = XMVectorNegativeMultiplySubtract(TP, TP, TP); + TP = XMVectorMultiply(TP, Two); + + XMVECTOR Result = XMQuaternionSlerpV(Q03, Q12, TP); + + return Result; +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMQuaternionSquadSetup +( + XMVECTOR* pA, + XMVECTOR* pB, + XMVECTOR* pC, + FXMVECTOR Q0, + FXMVECTOR Q1, + FXMVECTOR Q2, + GXMVECTOR Q3 +) noexcept +{ + assert(pA); + assert(pB); + assert(pC); + + XMVECTOR LS12 = XMQuaternionLengthSq(XMVectorAdd(Q1, Q2)); + XMVECTOR LD12 = XMQuaternionLengthSq(XMVectorSubtract(Q1, Q2)); + XMVECTOR SQ2 = XMVectorNegate(Q2); + + XMVECTOR Control1 = XMVectorLess(LS12, LD12); + SQ2 = XMVectorSelect(Q2, SQ2, Control1); + + XMVECTOR LS01 = XMQuaternionLengthSq(XMVectorAdd(Q0, Q1)); + XMVECTOR LD01 = XMQuaternionLengthSq(XMVectorSubtract(Q0, Q1)); + XMVECTOR SQ0 = XMVectorNegate(Q0); + + XMVECTOR LS23 = XMQuaternionLengthSq(XMVectorAdd(SQ2, Q3)); + XMVECTOR LD23 = XMQuaternionLengthSq(XMVectorSubtract(SQ2, Q3)); + XMVECTOR SQ3 = XMVectorNegate(Q3); + + XMVECTOR Control0 = XMVectorLess(LS01, LD01); + XMVECTOR Control2 = XMVectorLess(LS23, LD23); + + SQ0 = XMVectorSelect(Q0, SQ0, Control0); + SQ3 = XMVectorSelect(Q3, SQ3, Control2); + + XMVECTOR InvQ1 = XMQuaternionInverse(Q1); + XMVECTOR InvQ2 = XMQuaternionInverse(SQ2); + + XMVECTOR LnQ0 = XMQuaternionLn(XMQuaternionMultiply(InvQ1, SQ0)); + XMVECTOR LnQ2 = XMQuaternionLn(XMQuaternionMultiply(InvQ1, SQ2)); + XMVECTOR LnQ1 = XMQuaternionLn(XMQuaternionMultiply(InvQ2, Q1)); + XMVECTOR LnQ3 = XMQuaternionLn(XMQuaternionMultiply(InvQ2, SQ3)); + + const XMVECTOR NegativeOneQuarter = XMVectorSplatConstant(-1, 2); + + XMVECTOR ExpQ02 = XMVectorMultiply(XMVectorAdd(LnQ0, LnQ2), NegativeOneQuarter); + XMVECTOR ExpQ13 = XMVectorMultiply(XMVectorAdd(LnQ1, LnQ3), NegativeOneQuarter); + ExpQ02 = XMQuaternionExp(ExpQ02); + ExpQ13 = XMQuaternionExp(ExpQ13); + + *pA = XMQuaternionMultiply(Q1, ExpQ02); + *pB = XMQuaternionMultiply(SQ2, ExpQ13); + *pC = SQ2; +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMQuaternionBaryCentric +( + FXMVECTOR Q0, + FXMVECTOR Q1, + FXMVECTOR Q2, + float f, + float g +) noexcept +{ + float s = f + g; + + XMVECTOR Result; + if ((s < 0.00001f) && (s > -0.00001f)) + { + Result = Q0; + } + else + { + XMVECTOR Q01 = XMQuaternionSlerp(Q0, Q1, s); + XMVECTOR Q02 = XMQuaternionSlerp(Q0, Q2, s); + + Result = XMQuaternionSlerp(Q01, Q02, g / s); + } + + return Result; +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMQuaternionBaryCentricV +( + FXMVECTOR Q0, + FXMVECTOR Q1, + FXMVECTOR Q2, + GXMVECTOR F, + HXMVECTOR G +) noexcept +{ + assert((XMVectorGetY(F) == XMVectorGetX(F)) && (XMVectorGetZ(F) == XMVectorGetX(F)) && (XMVectorGetW(F) == XMVectorGetX(F))); + assert((XMVectorGetY(G) == XMVectorGetX(G)) && (XMVectorGetZ(G) == XMVectorGetX(G)) && (XMVectorGetW(G) == XMVectorGetX(G))); + + const XMVECTOR Epsilon = XMVectorSplatConstant(1, 16); + + XMVECTOR S = XMVectorAdd(F, G); + + XMVECTOR Result; + if (XMVector4InBounds(S, Epsilon)) + { + Result = Q0; + } + else + { + XMVECTOR Q01 = XMQuaternionSlerpV(Q0, Q1, S); + XMVECTOR Q02 = XMQuaternionSlerpV(Q0, Q2, S); + XMVECTOR GS = XMVectorReciprocal(S); + GS = XMVectorMultiply(G, GS); + + Result = XMQuaternionSlerpV(Q01, Q02, GS); + } + + return Result; +} + +//------------------------------------------------------------------------------ +// Transformation operations +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMQuaternionIdentity() noexcept +{ + return g_XMIdentityR3.v; +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMQuaternionRotationRollPitchYaw +( + float Pitch, + float Yaw, + float Roll +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + const float halfpitch = Pitch * 0.5f; + float cp = cosf(halfpitch); + float sp = sinf(halfpitch); + + const float halfyaw = Yaw * 0.5f; + float cy = cosf(halfyaw); + float sy = sinf(halfyaw); + + const float halfroll = Roll * 0.5f; + float cr = cosf(halfroll); + float sr = sinf(halfroll); + + XMVECTORF32 vResult = { { { + cr * sp * cy + sr * cp * sy, + cr * cp * sy - sr * sp * cy, + sr * cp * cy - cr * sp * sy, + cr * cp * cy + sr * sp * sy + } } }; + return vResult; +#else + XMVECTOR Angles = XMVectorSet(Pitch, Yaw, Roll, 0.0f); + return XMQuaternionRotationRollPitchYawFromVector(Angles); +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMQuaternionRotationRollPitchYawFromVector +( + FXMVECTOR Angles // +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + const float halfpitch = Angles.vector4_f32[0] * 0.5f; + float cp = cosf(halfpitch); + float sp = sinf(halfpitch); + + const float halfyaw = Angles.vector4_f32[1] * 0.5f; + float cy = cosf(halfyaw); + float sy = sinf(halfyaw); + + const float halfroll = Angles.vector4_f32[2] * 0.5f; + float cr = cosf(halfroll); + float sr = sinf(halfroll); + + XMVECTORF32 vResult = { { { + cr * sp * cy + sr * cp * sy, + cr * cp * sy - sr * sp * cy, + sr * cp * cy - cr * sp * sy, + cr * cp * cy + sr * sp * sy + } } }; + return vResult; +#else + static const XMVECTORF32 Sign = { { { 1.0f, -1.0f, -1.0f, 1.0f } } }; + + XMVECTOR HalfAngles = XMVectorMultiply(Angles, g_XMOneHalf.v); + + XMVECTOR SinAngles, CosAngles; + XMVectorSinCos(&SinAngles, &CosAngles, HalfAngles); + + XMVECTOR P0 = XMVectorPermute(SinAngles, CosAngles); + XMVECTOR Y0 = XMVectorPermute(SinAngles, CosAngles); + XMVECTOR R0 = XMVectorPermute(SinAngles, CosAngles); + XMVECTOR P1 = XMVectorPermute(CosAngles, SinAngles); + XMVECTOR Y1 = XMVectorPermute(CosAngles, SinAngles); + XMVECTOR R1 = XMVectorPermute(CosAngles, SinAngles); + + XMVECTOR Q1 = XMVectorMultiply(P1, Sign.v); + XMVECTOR Q0 = XMVectorMultiply(P0, Y0); + Q1 = XMVectorMultiply(Q1, Y1); + Q0 = XMVectorMultiply(Q0, R0); + XMVECTOR Q = XMVectorMultiplyAdd(Q1, R1, Q0); + + return Q; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMQuaternionRotationNormal +( + FXMVECTOR NormalAxis, + float Angle +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) || defined(_XM_ARM_NEON_INTRINSICS_) + + XMVECTOR N = XMVectorSelect(g_XMOne.v, NormalAxis, g_XMSelect1110.v); + + float SinV, CosV; + XMScalarSinCos(&SinV, &CosV, 0.5f * Angle); + + XMVECTOR Scale = XMVectorSet(SinV, SinV, SinV, CosV); + return XMVectorMultiply(N, Scale); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR N = _mm_and_ps(NormalAxis, g_XMMask3); + N = _mm_or_ps(N, g_XMIdentityR3); + XMVECTOR Scale = _mm_set_ps1(0.5f * Angle); + XMVECTOR vSine; + XMVECTOR vCosine; + XMVectorSinCos(&vSine, &vCosine, Scale); + Scale = _mm_and_ps(vSine, g_XMMask3); + vCosine = _mm_and_ps(vCosine, g_XMMaskW); + Scale = _mm_or_ps(Scale, vCosine); + N = _mm_mul_ps(N, Scale); + return N; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMQuaternionRotationAxis +( + FXMVECTOR Axis, + float Angle +) noexcept +{ + assert(!XMVector3Equal(Axis, XMVectorZero())); + assert(!XMVector3IsInfinite(Axis)); + + XMVECTOR Normal = XMVector3Normalize(Axis); + XMVECTOR Q = XMQuaternionRotationNormal(Normal, Angle); + return Q; +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMQuaternionRotationMatrix(FXMMATRIX M) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTORF32 q; + float r22 = M.m[2][2]; + if (r22 <= 0.f) // x^2 + y^2 >= z^2 + w^2 + { + float dif10 = M.m[1][1] - M.m[0][0]; + float omr22 = 1.f - r22; + if (dif10 <= 0.f) // x^2 >= y^2 + { + float fourXSqr = omr22 - dif10; + float inv4x = 0.5f / sqrtf(fourXSqr); + q.f[0] = fourXSqr * inv4x; + q.f[1] = (M.m[0][1] + M.m[1][0]) * inv4x; + q.f[2] = (M.m[0][2] + M.m[2][0]) * inv4x; + q.f[3] = (M.m[1][2] - M.m[2][1]) * inv4x; + } + else // y^2 >= x^2 + { + float fourYSqr = omr22 + dif10; + float inv4y = 0.5f / sqrtf(fourYSqr); + q.f[0] = (M.m[0][1] + M.m[1][0]) * inv4y; + q.f[1] = fourYSqr * inv4y; + q.f[2] = (M.m[1][2] + M.m[2][1]) * inv4y; + q.f[3] = (M.m[2][0] - M.m[0][2]) * inv4y; + } + } + else // z^2 + w^2 >= x^2 + y^2 + { + float sum10 = M.m[1][1] + M.m[0][0]; + float opr22 = 1.f + r22; + if (sum10 <= 0.f) // z^2 >= w^2 + { + float fourZSqr = opr22 - sum10; + float inv4z = 0.5f / sqrtf(fourZSqr); + q.f[0] = (M.m[0][2] + M.m[2][0]) * inv4z; + q.f[1] = (M.m[1][2] + M.m[2][1]) * inv4z; + q.f[2] = fourZSqr * inv4z; + q.f[3] = (M.m[0][1] - M.m[1][0]) * inv4z; + } + else // w^2 >= z^2 + { + float fourWSqr = opr22 + sum10; + float inv4w = 0.5f / sqrtf(fourWSqr); + q.f[0] = (M.m[1][2] - M.m[2][1]) * inv4w; + q.f[1] = (M.m[2][0] - M.m[0][2]) * inv4w; + q.f[2] = (M.m[0][1] - M.m[1][0]) * inv4w; + q.f[3] = fourWSqr * inv4w; + } + } + return q.v; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + static const XMVECTORF32 XMPMMP = { { { +1.0f, -1.0f, -1.0f, +1.0f } } }; + static const XMVECTORF32 XMMPMP = { { { -1.0f, +1.0f, -1.0f, +1.0f } } }; + static const XMVECTORF32 XMMMPP = { { { -1.0f, -1.0f, +1.0f, +1.0f } } }; + static const XMVECTORU32 Select0110 = { { { XM_SELECT_0, XM_SELECT_1, XM_SELECT_1, XM_SELECT_0 } } }; + static const XMVECTORU32 Select0010 = { { { XM_SELECT_0, XM_SELECT_0, XM_SELECT_1, XM_SELECT_0 } } }; + + float32x4_t r0 = M.r[0]; + float32x4_t r1 = M.r[1]; + float32x4_t r2 = M.r[2]; + + float32x4_t r00 = vdupq_lane_f32(vget_low_f32(r0), 0); + float32x4_t r11 = vdupq_lane_f32(vget_low_f32(r1), 1); + float32x4_t r22 = vdupq_lane_f32(vget_high_f32(r2), 0); + + // x^2 >= y^2 equivalent to r11 - r00 <= 0 + float32x4_t r11mr00 = vsubq_f32(r11, r00); + uint32x4_t x2gey2 = vcleq_f32(r11mr00, g_XMZero); + + // z^2 >= w^2 equivalent to r11 + r00 <= 0 + float32x4_t r11pr00 = vaddq_f32(r11, r00); + uint32x4_t z2gew2 = vcleq_f32(r11pr00, g_XMZero); + + // x^2 + y^2 >= z^2 + w^2 equivalent to r22 <= 0 + uint32x4_t x2py2gez2pw2 = vcleq_f32(r22, g_XMZero); + + // (4*x^2, 4*y^2, 4*z^2, 4*w^2) + float32x4_t t0 = vmulq_f32(XMPMMP, r00); + float32x4_t x2y2z2w2 = vmlaq_f32(t0, XMMPMP, r11); + x2y2z2w2 = vmlaq_f32(x2y2z2w2, XMMMPP, r22); + x2y2z2w2 = vaddq_f32(x2y2z2w2, g_XMOne); + + // (r01, r02, r12, r11) + t0 = vextq_f32(r0, r0, 1); + float32x4_t t1 = vextq_f32(r1, r1, 1); + t0 = vcombine_f32(vget_low_f32(t0), vrev64_f32(vget_low_f32(t1))); + + // (r10, r20, r21, r10) + t1 = vextq_f32(r2, r2, 3); + float32x4_t r10 = vdupq_lane_f32(vget_low_f32(r1), 0); + t1 = vbslq_f32(Select0110, t1, r10); + + // (4*x*y, 4*x*z, 4*y*z, unused) + float32x4_t xyxzyz = vaddq_f32(t0, t1); + + // (r21, r20, r10, r10) + t0 = vcombine_f32(vrev64_f32(vget_low_f32(r2)), vget_low_f32(r10)); + + // (r12, r02, r01, r12) + float32x4_t t2 = vcombine_f32(vrev64_f32(vget_high_f32(r0)), vrev64_f32(vget_low_f32(r0))); + float32x4_t t3 = vdupq_lane_f32(vget_high_f32(r1), 0); + t1 = vbslq_f32(Select0110, t2, t3); + + // (4*x*w, 4*y*w, 4*z*w, unused) + float32x4_t xwywzw = vsubq_f32(t0, t1); + xwywzw = vmulq_f32(XMMPMP, xwywzw); + + // (4*x*x, 4*x*y, 4*x*z, 4*x*w) + t0 = vextq_f32(xyxzyz, xyxzyz, 3); + t1 = vbslq_f32(Select0110, t0, x2y2z2w2); + t2 = vdupq_lane_f32(vget_low_f32(xwywzw), 0); + float32x4_t tensor0 = vbslq_f32(g_XMSelect1110, t1, t2); + + // (4*y*x, 4*y*y, 4*y*z, 4*y*w) + t0 = vbslq_f32(g_XMSelect1011, xyxzyz, x2y2z2w2); + t1 = vdupq_lane_f32(vget_low_f32(xwywzw), 1); + float32x4_t tensor1 = vbslq_f32(g_XMSelect1110, t0, t1); + + // (4*z*x, 4*z*y, 4*z*z, 4*z*w) + t0 = vextq_f32(xyxzyz, xyxzyz, 1); + t1 = vcombine_f32(vget_low_f32(t0), vrev64_f32(vget_high_f32(xwywzw))); + float32x4_t tensor2 = vbslq_f32(Select0010, x2y2z2w2, t1); + + // (4*w*x, 4*w*y, 4*w*z, 4*w*w) + float32x4_t tensor3 = vbslq_f32(g_XMSelect1110, xwywzw, x2y2z2w2); + + // Select the row of the tensor-product matrix that has the largest + // magnitude. + t0 = vbslq_f32(x2gey2, tensor0, tensor1); + t1 = vbslq_f32(z2gew2, tensor2, tensor3); + t2 = vbslq_f32(x2py2gez2pw2, t0, t1); + + // Normalize the row. No division by zero is possible because the + // quaternion is unit-length (and the row is a nonzero multiple of + // the quaternion). + t0 = XMVector4Length(t2); + return XMVectorDivide(t2, t0); +#elif defined(_XM_SSE_INTRINSICS_) + static const XMVECTORF32 XMPMMP = { { { +1.0f, -1.0f, -1.0f, +1.0f } } }; + static const XMVECTORF32 XMMPMP = { { { -1.0f, +1.0f, -1.0f, +1.0f } } }; + static const XMVECTORF32 XMMMPP = { { { -1.0f, -1.0f, +1.0f, +1.0f } } }; + + XMVECTOR r0 = M.r[0]; // (r00, r01, r02, 0) + XMVECTOR r1 = M.r[1]; // (r10, r11, r12, 0) + XMVECTOR r2 = M.r[2]; // (r20, r21, r22, 0) + + // (r00, r00, r00, r00) + XMVECTOR r00 = XM_PERMUTE_PS(r0, _MM_SHUFFLE(0, 0, 0, 0)); + // (r11, r11, r11, r11) + XMVECTOR r11 = XM_PERMUTE_PS(r1, _MM_SHUFFLE(1, 1, 1, 1)); + // (r22, r22, r22, r22) + XMVECTOR r22 = XM_PERMUTE_PS(r2, _MM_SHUFFLE(2, 2, 2, 2)); + + // x^2 >= y^2 equivalent to r11 - r00 <= 0 + // (r11 - r00, r11 - r00, r11 - r00, r11 - r00) + XMVECTOR r11mr00 = _mm_sub_ps(r11, r00); + XMVECTOR x2gey2 = _mm_cmple_ps(r11mr00, g_XMZero); + + // z^2 >= w^2 equivalent to r11 + r00 <= 0 + // (r11 + r00, r11 + r00, r11 + r00, r11 + r00) + XMVECTOR r11pr00 = _mm_add_ps(r11, r00); + XMVECTOR z2gew2 = _mm_cmple_ps(r11pr00, g_XMZero); + + // x^2 + y^2 >= z^2 + w^2 equivalent to r22 <= 0 + XMVECTOR x2py2gez2pw2 = _mm_cmple_ps(r22, g_XMZero); + + // (4*x^2, 4*y^2, 4*z^2, 4*w^2) + XMVECTOR t0 = XM_FMADD_PS(XMPMMP, r00, g_XMOne); + XMVECTOR t1 = _mm_mul_ps(XMMPMP, r11); + XMVECTOR t2 = XM_FMADD_PS(XMMMPP, r22, t0); + XMVECTOR x2y2z2w2 = _mm_add_ps(t1, t2); + + // (r01, r02, r12, r11) + t0 = _mm_shuffle_ps(r0, r1, _MM_SHUFFLE(1, 2, 2, 1)); + // (r10, r10, r20, r21) + t1 = _mm_shuffle_ps(r1, r2, _MM_SHUFFLE(1, 0, 0, 0)); + // (r10, r20, r21, r10) + t1 = XM_PERMUTE_PS(t1, _MM_SHUFFLE(1, 3, 2, 0)); + // (4*x*y, 4*x*z, 4*y*z, unused) + XMVECTOR xyxzyz = _mm_add_ps(t0, t1); + + // (r21, r20, r10, r10) + t0 = _mm_shuffle_ps(r2, r1, _MM_SHUFFLE(0, 0, 0, 1)); + // (r12, r12, r02, r01) + t1 = _mm_shuffle_ps(r1, r0, _MM_SHUFFLE(1, 2, 2, 2)); + // (r12, r02, r01, r12) + t1 = XM_PERMUTE_PS(t1, _MM_SHUFFLE(1, 3, 2, 0)); + // (4*x*w, 4*y*w, 4*z*w, unused) + XMVECTOR xwywzw = _mm_sub_ps(t0, t1); + xwywzw = _mm_mul_ps(XMMPMP, xwywzw); + + // (4*x^2, 4*y^2, 4*x*y, unused) + t0 = _mm_shuffle_ps(x2y2z2w2, xyxzyz, _MM_SHUFFLE(0, 0, 1, 0)); + // (4*z^2, 4*w^2, 4*z*w, unused) + t1 = _mm_shuffle_ps(x2y2z2w2, xwywzw, _MM_SHUFFLE(0, 2, 3, 2)); + // (4*x*z, 4*y*z, 4*x*w, 4*y*w) + t2 = _mm_shuffle_ps(xyxzyz, xwywzw, _MM_SHUFFLE(1, 0, 2, 1)); + + // (4*x*x, 4*x*y, 4*x*z, 4*x*w) + XMVECTOR tensor0 = _mm_shuffle_ps(t0, t2, _MM_SHUFFLE(2, 0, 2, 0)); + // (4*y*x, 4*y*y, 4*y*z, 4*y*w) + XMVECTOR tensor1 = _mm_shuffle_ps(t0, t2, _MM_SHUFFLE(3, 1, 1, 2)); + // (4*z*x, 4*z*y, 4*z*z, 4*z*w) + XMVECTOR tensor2 = _mm_shuffle_ps(t2, t1, _MM_SHUFFLE(2, 0, 1, 0)); + // (4*w*x, 4*w*y, 4*w*z, 4*w*w) + XMVECTOR tensor3 = _mm_shuffle_ps(t2, t1, _MM_SHUFFLE(1, 2, 3, 2)); + + // Select the row of the tensor-product matrix that has the largest + // magnitude. + t0 = _mm_and_ps(x2gey2, tensor0); + t1 = _mm_andnot_ps(x2gey2, tensor1); + t0 = _mm_or_ps(t0, t1); + t1 = _mm_and_ps(z2gew2, tensor2); + t2 = _mm_andnot_ps(z2gew2, tensor3); + t1 = _mm_or_ps(t1, t2); + t0 = _mm_and_ps(x2py2gez2pw2, t0); + t1 = _mm_andnot_ps(x2py2gez2pw2, t1); + t2 = _mm_or_ps(t0, t1); + + // Normalize the row. No division by zero is possible because the + // quaternion is unit-length (and the row is a nonzero multiple of + // the quaternion). + t0 = XMVector4Length(t2); + return _mm_div_ps(t2, t0); +#endif +} + +//------------------------------------------------------------------------------ +// Conversion operations +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMQuaternionToAxisAngle +( + XMVECTOR* pAxis, + float* pAngle, + FXMVECTOR Q +) noexcept +{ + assert(pAxis); + assert(pAngle); + + *pAxis = Q; + + *pAngle = 2.0f * XMScalarACos(XMVectorGetW(Q)); +} + +/**************************************************************************** + * + * Plane + * + ****************************************************************************/ + +//------------------------------------------------------------------------------ +// Comparison operations +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ + +inline bool XM_CALLCONV XMPlaneEqual +( + FXMVECTOR P1, + FXMVECTOR P2 +) noexcept +{ + return XMVector4Equal(P1, P2); +} + +//------------------------------------------------------------------------------ + +inline bool XM_CALLCONV XMPlaneNearEqual +( + FXMVECTOR P1, + FXMVECTOR P2, + FXMVECTOR Epsilon +) noexcept +{ + XMVECTOR NP1 = XMPlaneNormalize(P1); + XMVECTOR NP2 = XMPlaneNormalize(P2); + return XMVector4NearEqual(NP1, NP2, Epsilon); +} + +//------------------------------------------------------------------------------ + +inline bool XM_CALLCONV XMPlaneNotEqual +( + FXMVECTOR P1, + FXMVECTOR P2 +) noexcept +{ + return XMVector4NotEqual(P1, P2); +} + +//------------------------------------------------------------------------------ + +inline bool XM_CALLCONV XMPlaneIsNaN(FXMVECTOR P) noexcept +{ + return XMVector4IsNaN(P); +} + +//------------------------------------------------------------------------------ + +inline bool XM_CALLCONV XMPlaneIsInfinite(FXMVECTOR P) noexcept +{ + return XMVector4IsInfinite(P); +} + +//------------------------------------------------------------------------------ +// Computation operations +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMPlaneDot +( + FXMVECTOR P, + FXMVECTOR V +) noexcept +{ + return XMVector4Dot(P, V); +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMPlaneDotCoord +( + FXMVECTOR P, + FXMVECTOR V +) noexcept +{ + // Result = P[0] * V[0] + P[1] * V[1] + P[2] * V[2] + P[3] + + XMVECTOR V3 = XMVectorSelect(g_XMOne.v, V, g_XMSelect1110.v); + XMVECTOR Result = XMVector4Dot(P, V3); + return Result; +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMPlaneDotNormal +( + FXMVECTOR P, + FXMVECTOR V +) noexcept +{ + return XMVector3Dot(P, V); +} + +//------------------------------------------------------------------------------ +// XMPlaneNormalizeEst uses a reciprocal estimate and +// returns QNaN on zero and infinite vectors. + +inline XMVECTOR XM_CALLCONV XMPlaneNormalizeEst(FXMVECTOR P) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) || defined(_XM_ARM_NEON_INTRINSICS_) + + XMVECTOR Result = XMVector3ReciprocalLengthEst(P); + return XMVectorMultiply(P, Result); + +#elif defined(_XM_SSE4_INTRINSICS_) + XMVECTOR vTemp = _mm_dp_ps(P, P, 0x7f); + XMVECTOR vResult = _mm_rsqrt_ps(vTemp); + return _mm_mul_ps(vResult, P); +#elif defined(_XM_SSE_INTRINSICS_) + // Perform the dot product + XMVECTOR vDot = _mm_mul_ps(P, P); + // x=Dot.y, y=Dot.z + XMVECTOR vTemp = XM_PERMUTE_PS(vDot, _MM_SHUFFLE(2, 1, 2, 1)); + // Result.x = x+y + vDot = _mm_add_ss(vDot, vTemp); + // x=Dot.z + vTemp = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(1, 1, 1, 1)); + // Result.x = (x+y)+z + vDot = _mm_add_ss(vDot, vTemp); + // Splat x + vDot = XM_PERMUTE_PS(vDot, _MM_SHUFFLE(0, 0, 0, 0)); + // Get the reciprocal + vDot = _mm_rsqrt_ps(vDot); + // Get the reciprocal + vDot = _mm_mul_ps(vDot, P); + return vDot; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMPlaneNormalize(FXMVECTOR P) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + float fLengthSq = sqrtf((P.vector4_f32[0] * P.vector4_f32[0]) + (P.vector4_f32[1] * P.vector4_f32[1]) + (P.vector4_f32[2] * P.vector4_f32[2])); + // Prevent divide by zero + if (fLengthSq > 0) + { + fLengthSq = 1.0f / fLengthSq; + } + XMVECTORF32 vResult = { { { + P.vector4_f32[0] * fLengthSq, + P.vector4_f32[1] * fLengthSq, + P.vector4_f32[2] * fLengthSq, + P.vector4_f32[3] * fLengthSq + } } }; + return vResult.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + XMVECTOR vLength = XMVector3ReciprocalLength(P); + return XMVectorMultiply(P, vLength); +#elif defined(_XM_SSE4_INTRINSICS_) + XMVECTOR vLengthSq = _mm_dp_ps(P, P, 0x7f); + // Prepare for the division + XMVECTOR vResult = _mm_sqrt_ps(vLengthSq); + // Failsafe on zero (Or epsilon) length planes + // If the length is infinity, set the elements to zero + vLengthSq = _mm_cmpneq_ps(vLengthSq, g_XMInfinity); + // Reciprocal mul to perform the normalization + vResult = _mm_div_ps(P, vResult); + // Any that are infinity, set to zero + vResult = _mm_and_ps(vResult, vLengthSq); + return vResult; +#elif defined(_XM_SSE_INTRINSICS_) + // Perform the dot product on x,y and z only + XMVECTOR vLengthSq = _mm_mul_ps(P, P); + XMVECTOR vTemp = XM_PERMUTE_PS(vLengthSq, _MM_SHUFFLE(2, 1, 2, 1)); + vLengthSq = _mm_add_ss(vLengthSq, vTemp); + vTemp = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(1, 1, 1, 1)); + vLengthSq = _mm_add_ss(vLengthSq, vTemp); + vLengthSq = XM_PERMUTE_PS(vLengthSq, _MM_SHUFFLE(0, 0, 0, 0)); + // Prepare for the division + XMVECTOR vResult = _mm_sqrt_ps(vLengthSq); + // Failsafe on zero (Or epsilon) length planes + // If the length is infinity, set the elements to zero + vLengthSq = _mm_cmpneq_ps(vLengthSq, g_XMInfinity); + // Reciprocal mul to perform the normalization + vResult = _mm_div_ps(P, vResult); + // Any that are infinity, set to zero + vResult = _mm_and_ps(vResult, vLengthSq); + return vResult; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMPlaneIntersectLine +( + FXMVECTOR P, + FXMVECTOR LinePoint1, + FXMVECTOR LinePoint2 +) noexcept +{ + XMVECTOR V1 = XMVector3Dot(P, LinePoint1); + XMVECTOR V2 = XMVector3Dot(P, LinePoint2); + XMVECTOR D = XMVectorSubtract(V1, V2); + + XMVECTOR VT = XMPlaneDotCoord(P, LinePoint1); + VT = XMVectorDivide(VT, D); + + XMVECTOR Point = XMVectorSubtract(LinePoint2, LinePoint1); + Point = XMVectorMultiplyAdd(Point, VT, LinePoint1); + + const XMVECTOR Zero = XMVectorZero(); + XMVECTOR Control = XMVectorNearEqual(D, Zero, g_XMEpsilon.v); + + return XMVectorSelect(Point, g_XMQNaN.v, Control); +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMPlaneIntersectPlane +( + XMVECTOR* pLinePoint1, + XMVECTOR* pLinePoint2, + FXMVECTOR P1, + FXMVECTOR P2 +) noexcept +{ + assert(pLinePoint1); + assert(pLinePoint2); + + XMVECTOR V1 = XMVector3Cross(P2, P1); + + XMVECTOR LengthSq = XMVector3LengthSq(V1); + + XMVECTOR V2 = XMVector3Cross(P2, V1); + + XMVECTOR P1W = XMVectorSplatW(P1); + XMVECTOR Point = XMVectorMultiply(V2, P1W); + + XMVECTOR V3 = XMVector3Cross(V1, P1); + + XMVECTOR P2W = XMVectorSplatW(P2); + Point = XMVectorMultiplyAdd(V3, P2W, Point); + + XMVECTOR LinePoint1 = XMVectorDivide(Point, LengthSq); + + XMVECTOR LinePoint2 = XMVectorAdd(LinePoint1, V1); + + XMVECTOR Control = XMVectorLessOrEqual(LengthSq, g_XMEpsilon.v); + *pLinePoint1 = XMVectorSelect(LinePoint1, g_XMQNaN.v, Control); + *pLinePoint2 = XMVectorSelect(LinePoint2, g_XMQNaN.v, Control); +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMPlaneTransform +( + FXMVECTOR P, + FXMMATRIX ITM +) noexcept +{ + XMVECTOR W = XMVectorSplatW(P); + XMVECTOR Z = XMVectorSplatZ(P); + XMVECTOR Y = XMVectorSplatY(P); + XMVECTOR X = XMVectorSplatX(P); + + XMVECTOR Result = XMVectorMultiply(W, ITM.r[3]); + Result = XMVectorMultiplyAdd(Z, ITM.r[2], Result); + Result = XMVectorMultiplyAdd(Y, ITM.r[1], Result); + Result = XMVectorMultiplyAdd(X, ITM.r[0], Result); + return Result; +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMFLOAT4* XM_CALLCONV XMPlaneTransformStream +( + XMFLOAT4* pOutputStream, + size_t OutputStride, + const XMFLOAT4* pInputStream, + size_t InputStride, + size_t PlaneCount, + FXMMATRIX ITM +) noexcept +{ + return XMVector4TransformStream(pOutputStream, + OutputStride, + pInputStream, + InputStride, + PlaneCount, + ITM); +} + +//------------------------------------------------------------------------------ +// Conversion operations +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMPlaneFromPointNormal +( + FXMVECTOR Point, + FXMVECTOR Normal +) noexcept +{ + XMVECTOR W = XMVector3Dot(Point, Normal); + W = XMVectorNegate(W); + return XMVectorSelect(W, Normal, g_XMSelect1110.v); +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMPlaneFromPoints +( + FXMVECTOR Point1, + FXMVECTOR Point2, + FXMVECTOR Point3 +) noexcept +{ + XMVECTOR V21 = XMVectorSubtract(Point1, Point2); + XMVECTOR V31 = XMVectorSubtract(Point1, Point3); + + XMVECTOR N = XMVector3Cross(V21, V31); + N = XMVector3Normalize(N); + + XMVECTOR D = XMPlaneDotNormal(N, Point1); + D = XMVectorNegate(D); + + XMVECTOR Result = XMVectorSelect(D, N, g_XMSelect1110.v); + + return Result; +} + +/**************************************************************************** + * + * Color + * + ****************************************************************************/ + +//------------------------------------------------------------------------------ +// Comparison operations +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ + +inline bool XM_CALLCONV XMColorEqual +( + FXMVECTOR C1, + FXMVECTOR C2 +) noexcept +{ + return XMVector4Equal(C1, C2); +} + +//------------------------------------------------------------------------------ + +inline bool XM_CALLCONV XMColorNotEqual +( + FXMVECTOR C1, + FXMVECTOR C2 +) noexcept +{ + return XMVector4NotEqual(C1, C2); +} + +//------------------------------------------------------------------------------ + +inline bool XM_CALLCONV XMColorGreater +( + FXMVECTOR C1, + FXMVECTOR C2 +) noexcept +{ + return XMVector4Greater(C1, C2); +} + +//------------------------------------------------------------------------------ + +inline bool XM_CALLCONV XMColorGreaterOrEqual +( + FXMVECTOR C1, + FXMVECTOR C2 +) noexcept +{ + return XMVector4GreaterOrEqual(C1, C2); +} + +//------------------------------------------------------------------------------ + +inline bool XM_CALLCONV XMColorLess +( + FXMVECTOR C1, + FXMVECTOR C2 +) noexcept +{ + return XMVector4Less(C1, C2); +} + +//------------------------------------------------------------------------------ + +inline bool XM_CALLCONV XMColorLessOrEqual +( + FXMVECTOR C1, + FXMVECTOR C2 +) noexcept +{ + return XMVector4LessOrEqual(C1, C2); +} + +//------------------------------------------------------------------------------ + +inline bool XM_CALLCONV XMColorIsNaN(FXMVECTOR C) noexcept +{ + return XMVector4IsNaN(C); +} + +//------------------------------------------------------------------------------ + +inline bool XM_CALLCONV XMColorIsInfinite(FXMVECTOR C) noexcept +{ + return XMVector4IsInfinite(C); +} + +//------------------------------------------------------------------------------ +// Computation operations +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMColorNegative(FXMVECTOR vColor) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 vResult = { { { + 1.0f - vColor.vector4_f32[0], + 1.0f - vColor.vector4_f32[1], + 1.0f - vColor.vector4_f32[2], + vColor.vector4_f32[3] + } } }; + return vResult.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x4_t vTemp = veorq_u32(vreinterpretq_u32_f32(vColor), g_XMNegate3); + return vaddq_f32(vreinterpretq_f32_u32(vTemp), g_XMOne3); +#elif defined(_XM_SSE_INTRINSICS_) + // Negate only x,y and z. + XMVECTOR vTemp = _mm_xor_ps(vColor, g_XMNegate3); + // Add 1,1,1,0 to -x,-y,-z,w + return _mm_add_ps(vTemp, g_XMOne3); +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMColorModulate +( + FXMVECTOR C1, + FXMVECTOR C2 +) noexcept +{ + return XMVectorMultiply(C1, C2); +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMColorAdjustSaturation +( + FXMVECTOR vColor, + float fSaturation +) noexcept +{ + // Luminance = 0.2125f * C[0] + 0.7154f * C[1] + 0.0721f * C[2]; + // Result = (C - Luminance) * Saturation + Luminance; + + const XMVECTORF32 gvLuminance = { { { 0.2125f, 0.7154f, 0.0721f, 0.0f } } }; +#if defined(_XM_NO_INTRINSICS_) + float fLuminance = (vColor.vector4_f32[0] * gvLuminance.f[0]) + (vColor.vector4_f32[1] * gvLuminance.f[1]) + (vColor.vector4_f32[2] * gvLuminance.f[2]); + XMVECTOR vResult; + vResult.vector4_f32[0] = ((vColor.vector4_f32[0] - fLuminance) * fSaturation) + fLuminance; + vResult.vector4_f32[1] = ((vColor.vector4_f32[1] - fLuminance) * fSaturation) + fLuminance; + vResult.vector4_f32[2] = ((vColor.vector4_f32[2] - fLuminance) * fSaturation) + fLuminance; + vResult.vector4_f32[3] = vColor.vector4_f32[3]; + return vResult; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + XMVECTOR vLuminance = XMVector3Dot(vColor, gvLuminance); + XMVECTOR vResult = vsubq_f32(vColor, vLuminance); + vResult = vmlaq_n_f32(vLuminance, vResult, fSaturation); + return vbslq_f32(g_XMSelect1110, vResult, vColor); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vLuminance = XMVector3Dot(vColor, gvLuminance); + // Splat fSaturation + XMVECTOR vSaturation = _mm_set_ps1(fSaturation); + // vResult = ((vColor-vLuminance)*vSaturation)+vLuminance; + XMVECTOR vResult = _mm_sub_ps(vColor, vLuminance); + vResult = XM_FMADD_PS(vResult, vSaturation, vLuminance); + // Retain w from the source color + vLuminance = _mm_shuffle_ps(vResult, vColor, _MM_SHUFFLE(3, 2, 2, 2)); // x = vResult.z,y = vResult.z,z = vColor.z,w=vColor.w + vResult = _mm_shuffle_ps(vResult, vLuminance, _MM_SHUFFLE(3, 0, 1, 0)); // x = vResult.x,y = vResult.y,z = vResult.z,w=vColor.w + return vResult; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMColorAdjustContrast +( + FXMVECTOR vColor, + float fContrast +) noexcept +{ + // Result = (vColor - 0.5f) * fContrast + 0.5f; + +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 vResult = { { { + ((vColor.vector4_f32[0] - 0.5f) * fContrast) + 0.5f, + ((vColor.vector4_f32[1] - 0.5f) * fContrast) + 0.5f, + ((vColor.vector4_f32[2] - 0.5f) * fContrast) + 0.5f, + vColor.vector4_f32[3] // Leave W untouched + } } }; + return vResult.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + XMVECTOR vResult = vsubq_f32(vColor, g_XMOneHalf.v); + vResult = vmlaq_n_f32(g_XMOneHalf.v, vResult, fContrast); + return vbslq_f32(g_XMSelect1110, vResult, vColor); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vScale = _mm_set_ps1(fContrast); // Splat the scale + XMVECTOR vResult = _mm_sub_ps(vColor, g_XMOneHalf); // Subtract 0.5f from the source (Saving source) + vResult = XM_FMADD_PS(vResult, vScale, g_XMOneHalf); +// Retain w from the source color + vScale = _mm_shuffle_ps(vResult, vColor, _MM_SHUFFLE(3, 2, 2, 2)); // x = vResult.z,y = vResult.z,z = vColor.z,w=vColor.w + vResult = _mm_shuffle_ps(vResult, vScale, _MM_SHUFFLE(3, 0, 1, 0)); // x = vResult.x,y = vResult.y,z = vResult.z,w=vColor.w + return vResult; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMColorRGBToHSL(FXMVECTOR rgb) noexcept +{ + XMVECTOR r = XMVectorSplatX(rgb); + XMVECTOR g = XMVectorSplatY(rgb); + XMVECTOR b = XMVectorSplatZ(rgb); + + XMVECTOR min = XMVectorMin(r, XMVectorMin(g, b)); + XMVECTOR max = XMVectorMax(r, XMVectorMax(g, b)); + + XMVECTOR l = XMVectorMultiply(XMVectorAdd(min, max), g_XMOneHalf); + + XMVECTOR d = XMVectorSubtract(max, min); + + XMVECTOR la = XMVectorSelect(rgb, l, g_XMSelect1110); + + if (XMVector3Less(d, g_XMEpsilon)) + { + // Achromatic, assume H and S of 0 + return XMVectorSelect(la, g_XMZero, g_XMSelect1100); + } + else + { + XMVECTOR s, h; + + XMVECTOR d2 = XMVectorAdd(min, max); + + if (XMVector3Greater(l, g_XMOneHalf)) + { + // d / (2-max-min) + s = XMVectorDivide(d, XMVectorSubtract(g_XMTwo, d2)); + } + else + { + // d / (max+min) + s = XMVectorDivide(d, d2); + } + + if (XMVector3Equal(r, max)) + { + // Red is max + h = XMVectorDivide(XMVectorSubtract(g, b), d); + } + else if (XMVector3Equal(g, max)) + { + // Green is max + h = XMVectorDivide(XMVectorSubtract(b, r), d); + h = XMVectorAdd(h, g_XMTwo); + } + else + { + // Blue is max + h = XMVectorDivide(XMVectorSubtract(r, g), d); + h = XMVectorAdd(h, g_XMFour); + } + + h = XMVectorDivide(h, g_XMSix); + + if (XMVector3Less(h, g_XMZero)) + h = XMVectorAdd(h, g_XMOne); + + XMVECTOR lha = XMVectorSelect(la, h, g_XMSelect1100); + return XMVectorSelect(s, lha, g_XMSelect1011); + } +} + +//------------------------------------------------------------------------------ + +namespace MathInternal +{ + + inline XMVECTOR XM_CALLCONV XMColorHue2Clr(FXMVECTOR p, FXMVECTOR q, FXMVECTOR h) noexcept + { + static const XMVECTORF32 oneSixth = { { { 1.0f / 6.0f, 1.0f / 6.0f, 1.0f / 6.0f, 1.0f / 6.0f } } }; + static const XMVECTORF32 twoThirds = { { { 2.0f / 3.0f, 2.0f / 3.0f, 2.0f / 3.0f, 2.0f / 3.0f } } }; + + XMVECTOR t = h; + + if (XMVector3Less(t, g_XMZero)) + t = XMVectorAdd(t, g_XMOne); + + if (XMVector3Greater(t, g_XMOne)) + t = XMVectorSubtract(t, g_XMOne); + + if (XMVector3Less(t, oneSixth)) + { + // p + (q - p) * 6 * t + XMVECTOR t1 = XMVectorSubtract(q, p); + XMVECTOR t2 = XMVectorMultiply(g_XMSix, t); + return XMVectorMultiplyAdd(t1, t2, p); + } + + if (XMVector3Less(t, g_XMOneHalf)) + return q; + + if (XMVector3Less(t, twoThirds)) + { + // p + (q - p) * 6 * (2/3 - t) + XMVECTOR t1 = XMVectorSubtract(q, p); + XMVECTOR t2 = XMVectorMultiply(g_XMSix, XMVectorSubtract(twoThirds, t)); + return XMVectorMultiplyAdd(t1, t2, p); + } + + return p; + } + +} // namespace MathInternal + +inline XMVECTOR XM_CALLCONV XMColorHSLToRGB(FXMVECTOR hsl) noexcept +{ + static const XMVECTORF32 oneThird = { { { 1.0f / 3.0f, 1.0f / 3.0f, 1.0f / 3.0f, 1.0f / 3.0f } } }; + + XMVECTOR s = XMVectorSplatY(hsl); + XMVECTOR l = XMVectorSplatZ(hsl); + + if (XMVector3NearEqual(s, g_XMZero, g_XMEpsilon)) + { + // Achromatic + return XMVectorSelect(hsl, l, g_XMSelect1110); + } + else + { + XMVECTOR h = XMVectorSplatX(hsl); + + XMVECTOR q; + if (XMVector3Less(l, g_XMOneHalf)) + { + q = XMVectorMultiply(l, XMVectorAdd(g_XMOne, s)); + } + else + { + q = XMVectorSubtract(XMVectorAdd(l, s), XMVectorMultiply(l, s)); + } + + XMVECTOR p = XMVectorSubtract(XMVectorMultiply(g_XMTwo, l), q); + + XMVECTOR r = DirectX::MathInternal::XMColorHue2Clr(p, q, XMVectorAdd(h, oneThird)); + XMVECTOR g = DirectX::MathInternal::XMColorHue2Clr(p, q, h); + XMVECTOR b = DirectX::MathInternal::XMColorHue2Clr(p, q, XMVectorSubtract(h, oneThird)); + + XMVECTOR rg = XMVectorSelect(g, r, g_XMSelect1000); + XMVECTOR ba = XMVectorSelect(hsl, b, g_XMSelect1110); + + return XMVectorSelect(ba, rg, g_XMSelect1100); + } +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMColorRGBToHSV(FXMVECTOR rgb) noexcept +{ + XMVECTOR r = XMVectorSplatX(rgb); + XMVECTOR g = XMVectorSplatY(rgb); + XMVECTOR b = XMVectorSplatZ(rgb); + + XMVECTOR min = XMVectorMin(r, XMVectorMin(g, b)); + XMVECTOR v = XMVectorMax(r, XMVectorMax(g, b)); + + XMVECTOR d = XMVectorSubtract(v, min); + + XMVECTOR s = (XMVector3NearEqual(v, g_XMZero, g_XMEpsilon)) ? g_XMZero : XMVectorDivide(d, v); + + if (XMVector3Less(d, g_XMEpsilon)) + { + // Achromatic, assume H of 0 + XMVECTOR hv = XMVectorSelect(v, g_XMZero, g_XMSelect1000); + XMVECTOR hva = XMVectorSelect(rgb, hv, g_XMSelect1110); + return XMVectorSelect(s, hva, g_XMSelect1011); + } + else + { + XMVECTOR h; + + if (XMVector3Equal(r, v)) + { + // Red is max + h = XMVectorDivide(XMVectorSubtract(g, b), d); + + if (XMVector3Less(g, b)) + h = XMVectorAdd(h, g_XMSix); + } + else if (XMVector3Equal(g, v)) + { + // Green is max + h = XMVectorDivide(XMVectorSubtract(b, r), d); + h = XMVectorAdd(h, g_XMTwo); + } + else + { + // Blue is max + h = XMVectorDivide(XMVectorSubtract(r, g), d); + h = XMVectorAdd(h, g_XMFour); + } + + h = XMVectorDivide(h, g_XMSix); + + XMVECTOR hv = XMVectorSelect(v, h, g_XMSelect1000); + XMVECTOR hva = XMVectorSelect(rgb, hv, g_XMSelect1110); + return XMVectorSelect(s, hva, g_XMSelect1011); + } +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMColorHSVToRGB(FXMVECTOR hsv) noexcept +{ + XMVECTOR h = XMVectorSplatX(hsv); + XMVECTOR s = XMVectorSplatY(hsv); + XMVECTOR v = XMVectorSplatZ(hsv); + + XMVECTOR h6 = XMVectorMultiply(h, g_XMSix); + + XMVECTOR i = XMVectorFloor(h6); + XMVECTOR f = XMVectorSubtract(h6, i); + + // p = v* (1-s) + XMVECTOR p = XMVectorMultiply(v, XMVectorSubtract(g_XMOne, s)); + + // q = v*(1-f*s) + XMVECTOR q = XMVectorMultiply(v, XMVectorSubtract(g_XMOne, XMVectorMultiply(f, s))); + + // t = v*(1 - (1-f)*s) + XMVECTOR t = XMVectorMultiply(v, XMVectorSubtract(g_XMOne, XMVectorMultiply(XMVectorSubtract(g_XMOne, f), s))); + + auto ii = static_cast(XMVectorGetX(XMVectorMod(i, g_XMSix))); + + XMVECTOR _rgb; + + switch (ii) + { + case 0: // rgb = vtp + { + XMVECTOR vt = XMVectorSelect(t, v, g_XMSelect1000); + _rgb = XMVectorSelect(p, vt, g_XMSelect1100); + } + break; + case 1: // rgb = qvp + { + XMVECTOR qv = XMVectorSelect(v, q, g_XMSelect1000); + _rgb = XMVectorSelect(p, qv, g_XMSelect1100); + } + break; + case 2: // rgb = pvt + { + XMVECTOR pv = XMVectorSelect(v, p, g_XMSelect1000); + _rgb = XMVectorSelect(t, pv, g_XMSelect1100); + } + break; + case 3: // rgb = pqv + { + XMVECTOR pq = XMVectorSelect(q, p, g_XMSelect1000); + _rgb = XMVectorSelect(v, pq, g_XMSelect1100); + } + break; + case 4: // rgb = tpv + { + XMVECTOR tp = XMVectorSelect(p, t, g_XMSelect1000); + _rgb = XMVectorSelect(v, tp, g_XMSelect1100); + } + break; + default: // rgb = vpq + { + XMVECTOR vp = XMVectorSelect(p, v, g_XMSelect1000); + _rgb = XMVectorSelect(q, vp, g_XMSelect1100); + } + break; + } + + return XMVectorSelect(hsv, _rgb, g_XMSelect1110); +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMColorRGBToYUV(FXMVECTOR rgb) noexcept +{ + static const XMVECTORF32 Scale0 = { { { 0.299f, -0.147f, 0.615f, 0.0f } } }; + static const XMVECTORF32 Scale1 = { { { 0.587f, -0.289f, -0.515f, 0.0f } } }; + static const XMVECTORF32 Scale2 = { { { 0.114f, 0.436f, -0.100f, 0.0f } } }; + + XMMATRIX M(Scale0, Scale1, Scale2, g_XMZero); + XMVECTOR clr = XMVector3Transform(rgb, M); + + return XMVectorSelect(rgb, clr, g_XMSelect1110); +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMColorYUVToRGB(FXMVECTOR yuv) noexcept +{ + static const XMVECTORF32 Scale1 = { { { 0.0f, -0.395f, 2.032f, 0.0f } } }; + static const XMVECTORF32 Scale2 = { { { 1.140f, -0.581f, 0.0f, 0.0f } } }; + + XMMATRIX M(g_XMOne, Scale1, Scale2, g_XMZero); + XMVECTOR clr = XMVector3Transform(yuv, M); + + return XMVectorSelect(yuv, clr, g_XMSelect1110); +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMColorRGBToYUV_HD(FXMVECTOR rgb) noexcept +{ + static const XMVECTORF32 Scale0 = { { { 0.2126f, -0.0997f, 0.6150f, 0.0f } } }; + static const XMVECTORF32 Scale1 = { { { 0.7152f, -0.3354f, -0.5586f, 0.0f } } }; + static const XMVECTORF32 Scale2 = { { { 0.0722f, 0.4351f, -0.0564f, 0.0f } } }; + + XMMATRIX M(Scale0, Scale1, Scale2, g_XMZero); + XMVECTOR clr = XMVector3Transform(rgb, M); + + return XMVectorSelect(rgb, clr, g_XMSelect1110); +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMColorYUVToRGB_HD(FXMVECTOR yuv) noexcept +{ + static const XMVECTORF32 Scale1 = { { { 0.0f, -0.2153f, 2.1324f, 0.0f } } }; + static const XMVECTORF32 Scale2 = { { { 1.2803f, -0.3806f, 0.0f, 0.0f } } }; + + XMMATRIX M(g_XMOne, Scale1, Scale2, g_XMZero); + XMVECTOR clr = XMVector3Transform(yuv, M); + + return XMVectorSelect(yuv, clr, g_XMSelect1110); +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMColorRGBToYUV_UHD(FXMVECTOR rgb) noexcept +{ + static const XMVECTORF32 Scale0 = { { { 0.2627f, -0.1215f, 0.6150f, 0.0f } } }; + static const XMVECTORF32 Scale1 = { { { 0.6780f, -0.3136f, -0.5655f, 0.0f } } }; + static const XMVECTORF32 Scale2 = { { { 0.0593f, 0.4351f, -0.0495f, 0.0f } } }; + + XMMATRIX M(Scale0, Scale1, Scale2, g_XMZero); + XMVECTOR clr = XMVector3Transform(rgb, M); + + return XMVectorSelect(rgb, clr, g_XMSelect1110); +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMColorYUVToRGB_UHD(FXMVECTOR yuv) noexcept +{ + static const XMVECTORF32 Scale1 = { { { 0.0f, -0.1891f, 2.1620f, 0.0f } } }; + static const XMVECTORF32 Scale2 = { { { 1.1989f, -0.4645f, 0.0f, 0.0f } } }; + + XMMATRIX M(g_XMOne, Scale1, Scale2, g_XMZero); + XMVECTOR clr = XMVector3Transform(yuv, M); + + return XMVectorSelect(yuv, clr, g_XMSelect1110); +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMColorRGBToXYZ(FXMVECTOR rgb) noexcept +{ + static const XMVECTORF32 Scale0 = { { { 0.4887180f, 0.1762044f, 0.0000000f, 0.0f } } }; + static const XMVECTORF32 Scale1 = { { { 0.3106803f, 0.8129847f, 0.0102048f, 0.0f } } }; + static const XMVECTORF32 Scale2 = { { { 0.2006017f, 0.0108109f, 0.9897952f, 0.0f } } }; + static const XMVECTORF32 Scale = { { { 1.f / 0.17697f, 1.f / 0.17697f, 1.f / 0.17697f, 0.0f } } }; + + XMMATRIX M(Scale0, Scale1, Scale2, g_XMZero); + XMVECTOR clr = XMVectorMultiply(XMVector3Transform(rgb, M), Scale); + + return XMVectorSelect(rgb, clr, g_XMSelect1110); +} + +inline XMVECTOR XM_CALLCONV XMColorXYZToRGB(FXMVECTOR xyz) noexcept +{ + static const XMVECTORF32 Scale0 = { { { 2.3706743f, -0.5138850f, 0.0052982f, 0.0f } } }; + static const XMVECTORF32 Scale1 = { { { -0.9000405f, 1.4253036f, -0.0146949f, 0.0f } } }; + static const XMVECTORF32 Scale2 = { { { -0.4706338f, 0.0885814f, 1.0093968f, 0.0f } } }; + static const XMVECTORF32 Scale = { { { 0.17697f, 0.17697f, 0.17697f, 0.0f } } }; + + XMMATRIX M(Scale0, Scale1, Scale2, g_XMZero); + XMVECTOR clr = XMVector3Transform(XMVectorMultiply(xyz, Scale), M); + + return XMVectorSelect(xyz, clr, g_XMSelect1110); +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMColorXYZToSRGB(FXMVECTOR xyz) noexcept +{ + static const XMVECTORF32 Scale0 = { { { 3.2406f, -0.9689f, 0.0557f, 0.0f } } }; + static const XMVECTORF32 Scale1 = { { { -1.5372f, 1.8758f, -0.2040f, 0.0f } } }; + static const XMVECTORF32 Scale2 = { { { -0.4986f, 0.0415f, 1.0570f, 0.0f } } }; + static const XMVECTORF32 Cutoff = { { { 0.0031308f, 0.0031308f, 0.0031308f, 0.0f } } }; + static const XMVECTORF32 Exp = { { { 1.0f / 2.4f, 1.0f / 2.4f, 1.0f / 2.4f, 1.0f } } }; + + XMMATRIX M(Scale0, Scale1, Scale2, g_XMZero); + XMVECTOR lclr = XMVector3Transform(xyz, M); + + XMVECTOR sel = XMVectorGreater(lclr, Cutoff); + + // clr = 12.92 * lclr for lclr <= 0.0031308f + XMVECTOR smallC = XMVectorMultiply(lclr, g_XMsrgbScale); + + // clr = (1+a)*pow(lclr, 1/2.4) - a for lclr > 0.0031308 (where a = 0.055) + XMVECTOR largeC = XMVectorSubtract(XMVectorMultiply(g_XMsrgbA1, XMVectorPow(lclr, Exp)), g_XMsrgbA); + + XMVECTOR clr = XMVectorSelect(smallC, largeC, sel); + + return XMVectorSelect(xyz, clr, g_XMSelect1110); +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMColorSRGBToXYZ(FXMVECTOR srgb) noexcept +{ + static const XMVECTORF32 Scale0 = { { { 0.4124f, 0.2126f, 0.0193f, 0.0f } } }; + static const XMVECTORF32 Scale1 = { { { 0.3576f, 0.7152f, 0.1192f, 0.0f } } }; + static const XMVECTORF32 Scale2 = { { { 0.1805f, 0.0722f, 0.9505f, 0.0f } } }; + static const XMVECTORF32 Cutoff = { { { 0.04045f, 0.04045f, 0.04045f, 0.0f } } }; + static const XMVECTORF32 Exp = { { { 2.4f, 2.4f, 2.4f, 1.0f } } }; + + XMVECTOR sel = XMVectorGreater(srgb, Cutoff); + + // lclr = clr / 12.92 + XMVECTOR smallC = XMVectorDivide(srgb, g_XMsrgbScale); + + // lclr = pow( (clr + a) / (1+a), 2.4 ) + XMVECTOR largeC = XMVectorPow(XMVectorDivide(XMVectorAdd(srgb, g_XMsrgbA), g_XMsrgbA1), Exp); + + XMVECTOR lclr = XMVectorSelect(smallC, largeC, sel); + + XMMATRIX M(Scale0, Scale1, Scale2, g_XMZero); + XMVECTOR clr = XMVector3Transform(lclr, M); + + return XMVectorSelect(srgb, clr, g_XMSelect1110); +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMColorRGBToSRGB(FXMVECTOR rgb) noexcept +{ + static const XMVECTORF32 Cutoff = { { { 0.0031308f, 0.0031308f, 0.0031308f, 1.f } } }; + static const XMVECTORF32 Linear = { { { 12.92f, 12.92f, 12.92f, 1.f } } }; + static const XMVECTORF32 Scale = { { { 1.055f, 1.055f, 1.055f, 1.f } } }; + static const XMVECTORF32 Bias = { { { 0.055f, 0.055f, 0.055f, 0.f } } }; + static const XMVECTORF32 InvGamma = { { { 1.0f / 2.4f, 1.0f / 2.4f, 1.0f / 2.4f, 1.f } } }; + + XMVECTOR V = XMVectorSaturate(rgb); + XMVECTOR V0 = XMVectorMultiply(V, Linear); + XMVECTOR V1 = XMVectorSubtract(XMVectorMultiply(Scale, XMVectorPow(V, InvGamma)), Bias); + XMVECTOR select = XMVectorLess(V, Cutoff); + V = XMVectorSelect(V1, V0, select); + return XMVectorSelect(rgb, V, g_XMSelect1110); +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMColorSRGBToRGB(FXMVECTOR srgb) noexcept +{ + static const XMVECTORF32 Cutoff = { { { 0.04045f, 0.04045f, 0.04045f, 1.f } } }; + static const XMVECTORF32 ILinear = { { { 1.f / 12.92f, 1.f / 12.92f, 1.f / 12.92f, 1.f } } }; + static const XMVECTORF32 Scale = { { { 1.f / 1.055f, 1.f / 1.055f, 1.f / 1.055f, 1.f } } }; + static const XMVECTORF32 Bias = { { { 0.055f, 0.055f, 0.055f, 0.f } } }; + static const XMVECTORF32 Gamma = { { { 2.4f, 2.4f, 2.4f, 1.f } } }; + + XMVECTOR V = XMVectorSaturate(srgb); + XMVECTOR V0 = XMVectorMultiply(V, ILinear); + XMVECTOR V1 = XMVectorPow(XMVectorMultiply(XMVectorAdd(V, Bias), Scale), Gamma); + XMVECTOR select = XMVectorGreater(V, Cutoff); + V = XMVectorSelect(V0, V1, select); + return XMVectorSelect(srgb, V, g_XMSelect1110); +} + +/**************************************************************************** + * + * Miscellaneous + * + ****************************************************************************/ + +//------------------------------------------------------------------------------ + +inline bool XMVerifyCPUSupport() noexcept +{ +#if defined(_XM_SSE_INTRINSICS_) && !defined(__powerpc64__) && !defined(_XM_NO_INTRINSICS_) + int CPUInfo[4] = { -1 }; +#if (defined(__clang__) || defined(__GNUC__)) && !defined(_MSC_VER) && !defined(__MINGW32__) + __cpuid(0, CPUInfo[0], CPUInfo[1], CPUInfo[2], CPUInfo[3]); +#else + __cpuid(CPUInfo, 0); +#endif + +#ifdef __AVX2__ + if (CPUInfo[0] < 7) + return false; +#else + if (CPUInfo[0] < 1) + return false; +#endif + +#if (defined(__clang__) || defined(__GNUC__)) && !defined(_MSC_VER) && !defined(__MINGW32__) + __cpuid(1, CPUInfo[0], CPUInfo[1], CPUInfo[2], CPUInfo[3]); +#else + __cpuid(CPUInfo, 1); +#endif + +#if defined(__AVX2__) || defined(_XM_AVX2_INTRINSICS_) + // The compiler can emit FMA3 instructions even without explicit intrinsics use + if ((CPUInfo[2] & 0x38081001) != 0x38081001) + return false; // No F16C/AVX/OSXSAVE/SSE4.1/FMA3/SSE3 support +#elif defined(_XM_FMA3_INTRINSICS_) && defined(_XM_F16C_INTRINSICS_) + if ((CPUInfo[2] & 0x38081001) != 0x38081001) + return false; // No F16C/AVX/OSXSAVE/SSE4.1/FMA3/SSE3 support +#elif defined(_XM_FMA3_INTRINSICS_) + if ((CPUInfo[2] & 0x18081001) != 0x18081001) + return false; // No AVX/OSXSAVE/SSE4.1/FMA3/SSE3 support +#elif defined(_XM_F16C_INTRINSICS_) + if ((CPUInfo[2] & 0x38080001) != 0x38080001) + return false; // No F16C/AVX/OSXSAVE/SSE4.1/SSE3 support +#elif defined(__AVX__) || defined(_XM_AVX_INTRINSICS_) + if ((CPUInfo[2] & 0x18080001) != 0x18080001) + return false; // No AVX/OSXSAVE/SSE4.1/SSE3 support +#elif defined(_XM_SSE4_INTRINSICS_) + if ((CPUInfo[2] & 0x80001) != 0x80001) + return false; // No SSE3/SSE4.1 support +#elif defined(_XM_SSE3_INTRINSICS_) + if (!(CPUInfo[2] & 0x1)) + return false; // No SSE3 support +#endif + + // The x64 processor model requires SSE2 support, but no harm in checking + if ((CPUInfo[3] & 0x6000000) != 0x6000000) + return false; // No SSE2/SSE support + +#if defined(__AVX2__) || defined(_XM_AVX2_INTRINSICS_) +#if (defined(__clang__) || defined(__GNUC__)) && !defined(_MSC_VER) && !defined(__MINGW32__) + __cpuid_count(7, 0, CPUInfo[0], CPUInfo[1], CPUInfo[2], CPUInfo[3]); +#else + __cpuidex(CPUInfo, 7, 0); +#endif + if (!(CPUInfo[1] & 0x20)) + return false; // No AVX2 support +#endif + + return true; +#elif defined(_XM_ARM_NEON_INTRINSICS_) && !defined(_XM_NO_INTRINSICS_) + // ARM-NEON support is required for the Windows on ARM platform + return true; +#else + // No intrinsics path always supported + return true; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMFresnelTerm +( + FXMVECTOR CosIncidentAngle, + FXMVECTOR RefractionIndex +) noexcept +{ + assert(!XMVector4IsInfinite(CosIncidentAngle)); + + // Result = 0.5f * (g - c)^2 / (g + c)^2 * ((c * (g + c) - 1)^2 / (c * (g - c) + 1)^2 + 1) where + // c = CosIncidentAngle + // g = sqrt(c^2 + RefractionIndex^2 - 1) + +#if defined(_XM_NO_INTRINSICS_) || defined(_XM_ARM_NEON_INTRINSICS_) + + XMVECTOR G = XMVectorMultiplyAdd(RefractionIndex, RefractionIndex, g_XMNegativeOne.v); + G = XMVectorMultiplyAdd(CosIncidentAngle, CosIncidentAngle, G); + G = XMVectorAbs(G); + G = XMVectorSqrt(G); + + XMVECTOR S = XMVectorAdd(G, CosIncidentAngle); + XMVECTOR D = XMVectorSubtract(G, CosIncidentAngle); + + XMVECTOR V0 = XMVectorMultiply(D, D); + XMVECTOR V1 = XMVectorMultiply(S, S); + V1 = XMVectorReciprocal(V1); + V0 = XMVectorMultiply(g_XMOneHalf.v, V0); + V0 = XMVectorMultiply(V0, V1); + + XMVECTOR V2 = XMVectorMultiplyAdd(CosIncidentAngle, S, g_XMNegativeOne.v); + XMVECTOR V3 = XMVectorMultiplyAdd(CosIncidentAngle, D, g_XMOne.v); + V2 = XMVectorMultiply(V2, V2); + V3 = XMVectorMultiply(V3, V3); + V3 = XMVectorReciprocal(V3); + V2 = XMVectorMultiplyAdd(V2, V3, g_XMOne.v); + + XMVECTOR Result = XMVectorMultiply(V0, V2); + + Result = XMVectorSaturate(Result); + + return Result; + +#elif defined(_XM_SSE_INTRINSICS_) + // G = sqrt(abs((RefractionIndex^2-1) + CosIncidentAngle^2)) + XMVECTOR G = _mm_mul_ps(RefractionIndex, RefractionIndex); + XMVECTOR vTemp = _mm_mul_ps(CosIncidentAngle, CosIncidentAngle); + G = _mm_sub_ps(G, g_XMOne); + vTemp = _mm_add_ps(vTemp, G); + // max((0-vTemp),vTemp) == abs(vTemp) + // The abs is needed to deal with refraction and cosine being zero + G = _mm_setzero_ps(); + G = _mm_sub_ps(G, vTemp); + G = _mm_max_ps(G, vTemp); + // Last operation, the sqrt() + G = _mm_sqrt_ps(G); + + // Calc G-C and G+C + XMVECTOR GAddC = _mm_add_ps(G, CosIncidentAngle); + XMVECTOR GSubC = _mm_sub_ps(G, CosIncidentAngle); + // Perform the term (0.5f *(g - c)^2) / (g + c)^2 + XMVECTOR vResult = _mm_mul_ps(GSubC, GSubC); + vTemp = _mm_mul_ps(GAddC, GAddC); + vResult = _mm_mul_ps(vResult, g_XMOneHalf); + vResult = _mm_div_ps(vResult, vTemp); + // Perform the term ((c * (g + c) - 1)^2 / (c * (g - c) + 1)^2 + 1) + GAddC = _mm_mul_ps(GAddC, CosIncidentAngle); + GSubC = _mm_mul_ps(GSubC, CosIncidentAngle); + GAddC = _mm_sub_ps(GAddC, g_XMOne); + GSubC = _mm_add_ps(GSubC, g_XMOne); + GAddC = _mm_mul_ps(GAddC, GAddC); + GSubC = _mm_mul_ps(GSubC, GSubC); + GAddC = _mm_div_ps(GAddC, GSubC); + GAddC = _mm_add_ps(GAddC, g_XMOne); + // Multiply the two term parts + vResult = _mm_mul_ps(vResult, GAddC); + // Clamp to 0.0 - 1.0f + vResult = _mm_max_ps(vResult, g_XMZero); + vResult = _mm_min_ps(vResult, g_XMOne); + return vResult; +#endif +} + +//------------------------------------------------------------------------------ + +inline bool XMScalarNearEqual +( + float S1, + float S2, + float Epsilon +) noexcept +{ + float Delta = S1 - S2; + return (fabsf(Delta) <= Epsilon); +} + +//------------------------------------------------------------------------------ +// Modulo the range of the given angle such that -XM_PI <= Angle < XM_PI +inline float XMScalarModAngle(float Angle) noexcept +{ + // Note: The modulo is performed with unsigned math only to work + // around a precision error on numbers that are close to PI + + // Normalize the range from 0.0f to XM_2PI + Angle = Angle + XM_PI; + // Perform the modulo, unsigned + float fTemp = fabsf(Angle); + fTemp = fTemp - (XM_2PI * static_cast(static_cast(fTemp / XM_2PI))); + // Restore the number to the range of -XM_PI to XM_PI-epsilon + fTemp = fTemp - XM_PI; + // If the modulo'd value was negative, restore negation + if (Angle < 0.0f) + { + fTemp = -fTemp; + } + return fTemp; +} + +//------------------------------------------------------------------------------ + +inline float XMScalarSin(float Value) noexcept +{ + // Map Value to y in [-pi,pi], x = 2*pi*quotient + remainder. + float quotient = XM_1DIV2PI * Value; + if (Value >= 0.0f) + { + quotient = static_cast(static_cast(quotient + 0.5f)); + } + else + { + quotient = static_cast(static_cast(quotient - 0.5f)); + } + float y = Value - XM_2PI * quotient; + + // Map y to [-pi/2,pi/2] with sin(y) = sin(Value). + if (y > XM_PIDIV2) + { + y = XM_PI - y; + } + else if (y < -XM_PIDIV2) + { + y = -XM_PI - y; + } + + // 11-degree minimax approximation + float y2 = y * y; + return (((((-2.3889859e-08f * y2 + 2.7525562e-06f) * y2 - 0.00019840874f) * y2 + 0.0083333310f) * y2 - 0.16666667f) * y2 + 1.0f) * y; +} + +//------------------------------------------------------------------------------ + +inline float XMScalarSinEst(float Value) noexcept +{ + // Map Value to y in [-pi,pi], x = 2*pi*quotient + remainder. + float quotient = XM_1DIV2PI * Value; + if (Value >= 0.0f) + { + quotient = static_cast(static_cast(quotient + 0.5f)); + } + else + { + quotient = static_cast(static_cast(quotient - 0.5f)); + } + float y = Value - XM_2PI * quotient; + + // Map y to [-pi/2,pi/2] with sin(y) = sin(Value). + if (y > XM_PIDIV2) + { + y = XM_PI - y; + } + else if (y < -XM_PIDIV2) + { + y = -XM_PI - y; + } + + // 7-degree minimax approximation + float y2 = y * y; + return (((-0.00018524670f * y2 + 0.0083139502f) * y2 - 0.16665852f) * y2 + 1.0f) * y; +} + +//------------------------------------------------------------------------------ + +inline float XMScalarCos(float Value) noexcept +{ + // Map Value to y in [-pi,pi], x = 2*pi*quotient + remainder. + float quotient = XM_1DIV2PI * Value; + if (Value >= 0.0f) + { + quotient = static_cast(static_cast(quotient + 0.5f)); + } + else + { + quotient = static_cast(static_cast(quotient - 0.5f)); + } + float y = Value - XM_2PI * quotient; + + // Map y to [-pi/2,pi/2] with cos(y) = sign*cos(x). + float sign; + if (y > XM_PIDIV2) + { + y = XM_PI - y; + sign = -1.0f; + } + else if (y < -XM_PIDIV2) + { + y = -XM_PI - y; + sign = -1.0f; + } + else + { + sign = +1.0f; + } + + // 10-degree minimax approximation + float y2 = y * y; + float p = ((((-2.6051615e-07f * y2 + 2.4760495e-05f) * y2 - 0.0013888378f) * y2 + 0.041666638f) * y2 - 0.5f) * y2 + 1.0f; + return sign * p; +} + +//------------------------------------------------------------------------------ + +inline float XMScalarCosEst(float Value) noexcept +{ + // Map Value to y in [-pi,pi], x = 2*pi*quotient + remainder. + float quotient = XM_1DIV2PI * Value; + if (Value >= 0.0f) + { + quotient = static_cast(static_cast(quotient + 0.5f)); + } + else + { + quotient = static_cast(static_cast(quotient - 0.5f)); + } + float y = Value - XM_2PI * quotient; + + // Map y to [-pi/2,pi/2] with cos(y) = sign*cos(x). + float sign; + if (y > XM_PIDIV2) + { + y = XM_PI - y; + sign = -1.0f; + } + else if (y < -XM_PIDIV2) + { + y = -XM_PI - y; + sign = -1.0f; + } + else + { + sign = +1.0f; + } + + // 6-degree minimax approximation + float y2 = y * y; + float p = ((-0.0012712436f * y2 + 0.041493919f) * y2 - 0.49992746f) * y2 + 1.0f; + return sign * p; +} + +//------------------------------------------------------------------------------ + +_Use_decl_annotations_ +inline void XMScalarSinCos +( + float* pSin, + float* pCos, + float Value +) noexcept +{ + assert(pSin); + assert(pCos); + + // Map Value to y in [-pi,pi], x = 2*pi*quotient + remainder. + float quotient = XM_1DIV2PI * Value; + if (Value >= 0.0f) + { + quotient = static_cast(static_cast(quotient + 0.5f)); + } + else + { + quotient = static_cast(static_cast(quotient - 0.5f)); + } + float y = Value - XM_2PI * quotient; + + // Map y to [-pi/2,pi/2] with sin(y) = sin(Value). + float sign; + if (y > XM_PIDIV2) + { + y = XM_PI - y; + sign = -1.0f; + } + else if (y < -XM_PIDIV2) + { + y = -XM_PI - y; + sign = -1.0f; + } + else + { + sign = +1.0f; + } + + float y2 = y * y; + + // 11-degree minimax approximation + *pSin = (((((-2.3889859e-08f * y2 + 2.7525562e-06f) * y2 - 0.00019840874f) * y2 + 0.0083333310f) * y2 - 0.16666667f) * y2 + 1.0f) * y; + + // 10-degree minimax approximation + float p = ((((-2.6051615e-07f * y2 + 2.4760495e-05f) * y2 - 0.0013888378f) * y2 + 0.041666638f) * y2 - 0.5f) * y2 + 1.0f; + *pCos = sign * p; +} + +//------------------------------------------------------------------------------ + +_Use_decl_annotations_ +inline void XMScalarSinCosEst +( + float* pSin, + float* pCos, + float Value +) noexcept +{ + assert(pSin); + assert(pCos); + + // Map Value to y in [-pi,pi], x = 2*pi*quotient + remainder. + float quotient = XM_1DIV2PI * Value; + if (Value >= 0.0f) + { + quotient = static_cast(static_cast(quotient + 0.5f)); + } + else + { + quotient = static_cast(static_cast(quotient - 0.5f)); + } + float y = Value - XM_2PI * quotient; + + // Map y to [-pi/2,pi/2] with sin(y) = sin(Value). + float sign; + if (y > XM_PIDIV2) + { + y = XM_PI - y; + sign = -1.0f; + } + else if (y < -XM_PIDIV2) + { + y = -XM_PI - y; + sign = -1.0f; + } + else + { + sign = +1.0f; + } + + float y2 = y * y; + + // 7-degree minimax approximation + *pSin = (((-0.00018524670f * y2 + 0.0083139502f) * y2 - 0.16665852f) * y2 + 1.0f) * y; + + // 6-degree minimax approximation + float p = ((-0.0012712436f * y2 + 0.041493919f) * y2 - 0.49992746f) * y2 + 1.0f; + *pCos = sign * p; +} + +//------------------------------------------------------------------------------ + +inline float XMScalarASin(float Value) noexcept +{ + // Clamp input to [-1,1]. + bool nonnegative = (Value >= 0.0f); + float x = fabsf(Value); + float omx = 1.0f - x; + if (omx < 0.0f) + { + omx = 0.0f; + } + float root = sqrtf(omx); + + // 7-degree minimax approximation + float result = ((((((-0.0012624911f * x + 0.0066700901f) * x - 0.0170881256f) * x + 0.0308918810f) * x - 0.0501743046f) * x + 0.0889789874f) * x - 0.2145988016f) * x + 1.5707963050f; + result *= root; // acos(|x|) + + // acos(x) = pi - acos(-x) when x < 0, asin(x) = pi/2 - acos(x) + return (nonnegative ? XM_PIDIV2 - result : result - XM_PIDIV2); +} + +//------------------------------------------------------------------------------ + +inline float XMScalarASinEst(float Value) noexcept +{ + // Clamp input to [-1,1]. + bool nonnegative = (Value >= 0.0f); + float x = fabsf(Value); + float omx = 1.0f - x; + if (omx < 0.0f) + { + omx = 0.0f; + } + float root = sqrtf(omx); + + // 3-degree minimax approximation + float result = ((-0.0187293f * x + 0.0742610f) * x - 0.2121144f) * x + 1.5707288f; + result *= root; // acos(|x|) + + // acos(x) = pi - acos(-x) when x < 0, asin(x) = pi/2 - acos(x) + return (nonnegative ? XM_PIDIV2 - result : result - XM_PIDIV2); +} + +//------------------------------------------------------------------------------ + +inline float XMScalarACos(float Value) noexcept +{ + // Clamp input to [-1,1]. + bool nonnegative = (Value >= 0.0f); + float x = fabsf(Value); + float omx = 1.0f - x; + if (omx < 0.0f) + { + omx = 0.0f; + } + float root = sqrtf(omx); + + // 7-degree minimax approximation + float result = ((((((-0.0012624911f * x + 0.0066700901f) * x - 0.0170881256f) * x + 0.0308918810f) * x - 0.0501743046f) * x + 0.0889789874f) * x - 0.2145988016f) * x + 1.5707963050f; + result *= root; + + // acos(x) = pi - acos(-x) when x < 0 + return (nonnegative ? result : XM_PI - result); +} + +//------------------------------------------------------------------------------ + +inline float XMScalarACosEst(float Value) noexcept +{ + // Clamp input to [-1,1]. + bool nonnegative = (Value >= 0.0f); + float x = fabsf(Value); + float omx = 1.0f - x; + if (omx < 0.0f) + { + omx = 0.0f; + } + float root = sqrtf(omx); + + // 3-degree minimax approximation + float result = ((-0.0187293f * x + 0.0742610f) * x - 0.2121144f) * x + 1.5707288f; + result *= root; + + // acos(x) = pi - acos(-x) when x < 0 + return (nonnegative ? result : XM_PI - result); +} + diff --git a/Minecraft.Client/Windows64/Libs/4JLibs/impls/Windows_Libs/Render/vendor/DirectXMath/DirectXMathVector.inl b/Minecraft.Client/Windows64/Libs/4JLibs/impls/Windows_Libs/Render/vendor/DirectXMath/DirectXMathVector.inl new file mode 100644 index 00000000..20bbab1d --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/4JLibs/impls/Windows_Libs/Render/vendor/DirectXMath/DirectXMathVector.inl @@ -0,0 +1,14883 @@ +//------------------------------------------------------------------------------------- +// DirectXMathVector.inl -- SIMD C++ Math library +// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +// +// https://go.microsoft.com/fwlink/?LinkID=615560 +//------------------------------------------------------------------------------------- + +#pragma once + +#if defined(_XM_NO_INTRINSICS_) +#define XMISNAN(x) isnan(x) +#define XMISINF(x) isinf(x) +#endif + +#if defined(_XM_SSE_INTRINSICS_) + +#define XM3UNPACK3INTO4(l1, l2, l3) \ + XMVECTOR V3 = _mm_shuffle_ps(l2, l3, _MM_SHUFFLE(0, 0, 3, 2));\ + XMVECTOR V2 = _mm_shuffle_ps(l2, l1, _MM_SHUFFLE(3, 3, 1, 0));\ + V2 = XM_PERMUTE_PS(V2, _MM_SHUFFLE(1, 1, 0, 2));\ + XMVECTOR V4 = _mm_castsi128_ps(_mm_srli_si128(_mm_castps_si128(L3), 32 / 8)) + +#define XM3PACK4INTO3(v2x) \ + v2x = _mm_shuffle_ps(V2, V3, _MM_SHUFFLE(1, 0, 2, 1));\ + V2 = _mm_shuffle_ps(V2, V1, _MM_SHUFFLE(2, 2, 0, 0));\ + V1 = _mm_shuffle_ps(V1, V2, _MM_SHUFFLE(0, 2, 1, 0));\ + V3 = _mm_shuffle_ps(V3, V4, _MM_SHUFFLE(0, 0, 2, 2));\ + V3 = _mm_shuffle_ps(V3, V4, _MM_SHUFFLE(2, 1, 2, 0)) + +#endif + +/**************************************************************************** + * + * General Vector + * + ****************************************************************************/ + +//------------------------------------------------------------------------------ +// Assignment operations +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ +// Return a vector with all elements equaling zero +inline XMVECTOR XM_CALLCONV XMVectorZero() noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 vResult = { { { 0.0f, 0.0f, 0.0f, 0.0f } } }; + return vResult.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vdupq_n_f32(0); +#elif defined(_XM_SSE_INTRINSICS_) + return _mm_setzero_ps(); +#endif +} + +//------------------------------------------------------------------------------ +// Initialize a vector with four floating point values +inline XMVECTOR XM_CALLCONV XMVectorSet +( + float x, + float y, + float z, + float w +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 vResult = { { { x, y, z, w } } }; + return vResult.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x2_t V0 = vcreate_f32( + static_cast(*reinterpret_cast(&x)) + | (static_cast(*reinterpret_cast(&y)) << 32)); + float32x2_t V1 = vcreate_f32( + static_cast(*reinterpret_cast(&z)) + | (static_cast(*reinterpret_cast(&w)) << 32)); + return vcombine_f32(V0, V1); +#elif defined(_XM_SSE_INTRINSICS_) + return _mm_set_ps(w, z, y, x); +#endif +} + +//------------------------------------------------------------------------------ +// Initialize a vector with four integer values +inline XMVECTOR XM_CALLCONV XMVectorSetInt +( + uint32_t x, + uint32_t y, + uint32_t z, + uint32_t w +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + XMVECTORU32 vResult = { { { x, y, z, w } } }; + return vResult.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x2_t V0 = vcreate_u32(static_cast(x) | (static_cast(y) << 32)); + uint32x2_t V1 = vcreate_u32(static_cast(z) | (static_cast(w) << 32)); + return vreinterpretq_f32_u32(vcombine_u32(V0, V1)); +#elif defined(_XM_SSE_INTRINSICS_) + __m128i V = _mm_set_epi32(static_cast(w), static_cast(z), static_cast(y), static_cast(x)); + return _mm_castsi128_ps(V); +#endif +} + +//------------------------------------------------------------------------------ +// Initialize a vector with a replicated floating point value +inline XMVECTOR XM_CALLCONV XMVectorReplicate(float Value) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 vResult; + vResult.f[0] = + vResult.f[1] = + vResult.f[2] = + vResult.f[3] = Value; + return vResult.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vdupq_n_f32(Value); +#elif defined(_XM_SSE_INTRINSICS_) + return _mm_set_ps1(Value); +#endif +} + +//------------------------------------------------------------------------------ +// Initialize a vector with a replicated floating point value passed by pointer +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMVectorReplicatePtr(const float* pValue) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + float Value = pValue[0]; + XMVECTORF32 vResult; + vResult.f[0] = + vResult.f[1] = + vResult.f[2] = + vResult.f[3] = Value; + return vResult.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vld1q_dup_f32(pValue); +#elif defined(_XM_AVX_INTRINSICS_) + return _mm_broadcast_ss(pValue); +#elif defined(_XM_SSE_INTRINSICS_) + return _mm_load_ps1(pValue); +#endif +} + +//------------------------------------------------------------------------------ +// Initialize a vector with a replicated integer value +inline XMVECTOR XM_CALLCONV XMVectorReplicateInt(uint32_t Value) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + XMVECTORU32 vResult; + vResult.u[0] = + vResult.u[1] = + vResult.u[2] = + vResult.u[3] = Value; + return vResult.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vreinterpretq_f32_u32(vdupq_n_u32(Value)); +#elif defined(_XM_SSE_INTRINSICS_) + __m128i vTemp = _mm_set1_epi32(static_cast(Value)); + return _mm_castsi128_ps(vTemp); +#endif +} + +//------------------------------------------------------------------------------ +// Initialize a vector with a replicated integer value passed by pointer +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMVectorReplicateIntPtr(const uint32_t* pValue) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + uint32_t Value = pValue[0]; + XMVECTORU32 vResult; + vResult.u[0] = + vResult.u[1] = + vResult.u[2] = + vResult.u[3] = Value; + return vResult.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vreinterpretq_f32_u32(vld1q_dup_u32(pValue)); +#elif defined(_XM_SSE_INTRINSICS_) + return _mm_load_ps1(reinterpret_cast(pValue)); +#endif +} + +//------------------------------------------------------------------------------ +// Initialize a vector with all bits set (true mask) +inline XMVECTOR XM_CALLCONV XMVectorTrueInt() noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + XMVECTORU32 vResult = { { { 0xFFFFFFFFU, 0xFFFFFFFFU, 0xFFFFFFFFU, 0xFFFFFFFFU } } }; + return vResult.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vreinterpretq_f32_s32(vdupq_n_s32(-1)); +#elif defined(_XM_SSE_INTRINSICS_) + __m128i V = _mm_set1_epi32(-1); + return _mm_castsi128_ps(V); +#endif +} + +//------------------------------------------------------------------------------ +// Initialize a vector with all bits clear (false mask) +inline XMVECTOR XM_CALLCONV XMVectorFalseInt() noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 vResult = { { { 0.0f, 0.0f, 0.0f, 0.0f } } }; + return vResult; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vreinterpretq_f32_u32(vdupq_n_u32(0)); +#elif defined(_XM_SSE_INTRINSICS_) + return _mm_setzero_ps(); +#endif +} + +//------------------------------------------------------------------------------ +// Replicate the x component of the vector +inline XMVECTOR XM_CALLCONV XMVectorSplatX(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 vResult; + vResult.f[0] = + vResult.f[1] = + vResult.f[2] = + vResult.f[3] = V.vector4_f32[0]; + return vResult.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vdupq_lane_f32(vget_low_f32(V), 0); +#elif defined(_XM_AVX2_INTRINSICS_) && defined(_XM_FAVOR_INTEL_) + return _mm_broadcastss_ps(V); +#elif defined(_XM_SSE_INTRINSICS_) + return XM_PERMUTE_PS(V, _MM_SHUFFLE(0, 0, 0, 0)); +#endif +} + +//------------------------------------------------------------------------------ +// Replicate the y component of the vector +inline XMVECTOR XM_CALLCONV XMVectorSplatY(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 vResult; + vResult.f[0] = + vResult.f[1] = + vResult.f[2] = + vResult.f[3] = V.vector4_f32[1]; + return vResult.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vdupq_lane_f32(vget_low_f32(V), 1); +#elif defined(_XM_SSE_INTRINSICS_) + return XM_PERMUTE_PS(V, _MM_SHUFFLE(1, 1, 1, 1)); +#endif +} + +//------------------------------------------------------------------------------ +// Replicate the z component of the vector +inline XMVECTOR XM_CALLCONV XMVectorSplatZ(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 vResult; + vResult.f[0] = + vResult.f[1] = + vResult.f[2] = + vResult.f[3] = V.vector4_f32[2]; + return vResult.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vdupq_lane_f32(vget_high_f32(V), 0); +#elif defined(_XM_SSE_INTRINSICS_) + return XM_PERMUTE_PS(V, _MM_SHUFFLE(2, 2, 2, 2)); +#endif +} + +//------------------------------------------------------------------------------ +// Replicate the w component of the vector +inline XMVECTOR XM_CALLCONV XMVectorSplatW(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 vResult; + vResult.f[0] = + vResult.f[1] = + vResult.f[2] = + vResult.f[3] = V.vector4_f32[3]; + return vResult.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vdupq_lane_f32(vget_high_f32(V), 1); +#elif defined(_XM_SSE_INTRINSICS_) + return XM_PERMUTE_PS(V, _MM_SHUFFLE(3, 3, 3, 3)); +#endif +} + +//------------------------------------------------------------------------------ +// Return a vector of 1.0f,1.0f,1.0f,1.0f +inline XMVECTOR XM_CALLCONV XMVectorSplatOne() noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 vResult; + vResult.f[0] = + vResult.f[1] = + vResult.f[2] = + vResult.f[3] = 1.0f; + return vResult.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vdupq_n_f32(1.0f); +#elif defined(_XM_SSE_INTRINSICS_) + return g_XMOne; +#endif +} + +//------------------------------------------------------------------------------ +// Return a vector of INF,INF,INF,INF +inline XMVECTOR XM_CALLCONV XMVectorSplatInfinity() noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + XMVECTORU32 vResult; + vResult.u[0] = + vResult.u[1] = + vResult.u[2] = + vResult.u[3] = 0x7F800000; + return vResult.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vreinterpretq_f32_u32(vdupq_n_u32(0x7F800000)); +#elif defined(_XM_SSE_INTRINSICS_) + return g_XMInfinity; +#endif +} + +//------------------------------------------------------------------------------ +// Return a vector of Q_NAN,Q_NAN,Q_NAN,Q_NAN +inline XMVECTOR XM_CALLCONV XMVectorSplatQNaN() noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + XMVECTORU32 vResult; + vResult.u[0] = + vResult.u[1] = + vResult.u[2] = + vResult.u[3] = 0x7FC00000; + return vResult.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vreinterpretq_f32_u32(vdupq_n_u32(0x7FC00000)); +#elif defined(_XM_SSE_INTRINSICS_) + return g_XMQNaN; +#endif +} + +//------------------------------------------------------------------------------ +// Return a vector of 1.192092896e-7f,1.192092896e-7f,1.192092896e-7f,1.192092896e-7f +inline XMVECTOR XM_CALLCONV XMVectorSplatEpsilon() noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + XMVECTORU32 vResult; + vResult.u[0] = + vResult.u[1] = + vResult.u[2] = + vResult.u[3] = 0x34000000; + return vResult.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vreinterpretq_f32_u32(vdupq_n_u32(0x34000000)); +#elif defined(_XM_SSE_INTRINSICS_) + return g_XMEpsilon; +#endif +} + +//------------------------------------------------------------------------------ +// Return a vector of -0.0f (0x80000000),-0.0f,-0.0f,-0.0f +inline XMVECTOR XM_CALLCONV XMVectorSplatSignMask() noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + XMVECTORU32 vResult; + vResult.u[0] = + vResult.u[1] = + vResult.u[2] = + vResult.u[3] = 0x80000000U; + return vResult.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vreinterpretq_f32_u32(vdupq_n_u32(0x80000000U)); +#elif defined(_XM_SSE_INTRINSICS_) + __m128i V = _mm_set1_epi32(static_cast(0x80000000)); + return _mm_castsi128_ps(V); +#endif +} + +//------------------------------------------------------------------------------ +// Return a floating point value via an index. This is not a recommended +// function to use due to performance loss. +inline float XM_CALLCONV XMVectorGetByIndex(FXMVECTOR V, size_t i) noexcept +{ + assert(i < 4); + _Analysis_assume_(i < 4); +#if defined(_XM_NO_INTRINSICS_) + return V.vector4_f32[i]; +#else + XMVECTORF32 U; + U.v = V; + return U.f[i]; +#endif +} + +//------------------------------------------------------------------------------ +// Return the X component in an FPU register. +inline float XM_CALLCONV XMVectorGetX(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + return V.vector4_f32[0]; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vgetq_lane_f32(V, 0); +#elif defined(_XM_SSE_INTRINSICS_) + return _mm_cvtss_f32(V); +#endif +} + +// Return the Y component in an FPU register. +inline float XM_CALLCONV XMVectorGetY(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + return V.vector4_f32[1]; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vgetq_lane_f32(V, 1); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vTemp = XM_PERMUTE_PS(V, _MM_SHUFFLE(1, 1, 1, 1)); + return _mm_cvtss_f32(vTemp); +#endif +} + +// Return the Z component in an FPU register. +inline float XM_CALLCONV XMVectorGetZ(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + return V.vector4_f32[2]; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vgetq_lane_f32(V, 2); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vTemp = XM_PERMUTE_PS(V, _MM_SHUFFLE(2, 2, 2, 2)); + return _mm_cvtss_f32(vTemp); +#endif +} + +// Return the W component in an FPU register. +inline float XM_CALLCONV XMVectorGetW(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + return V.vector4_f32[3]; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vgetq_lane_f32(V, 3); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vTemp = XM_PERMUTE_PS(V, _MM_SHUFFLE(3, 3, 3, 3)); + return _mm_cvtss_f32(vTemp); +#endif +} + +//------------------------------------------------------------------------------ + +// Store a component indexed by i into a 32 bit float location in memory. +_Use_decl_annotations_ +inline void XM_CALLCONV XMVectorGetByIndexPtr(float* f, FXMVECTOR V, size_t i) noexcept +{ + assert(f != nullptr); + assert(i < 4); + _Analysis_assume_(i < 4); +#if defined(_XM_NO_INTRINSICS_) + *f = V.vector4_f32[i]; +#else + XMVECTORF32 U; + U.v = V; + *f = U.f[i]; +#endif +} + +//------------------------------------------------------------------------------ + +// Store the X component into a 32 bit float location in memory. +_Use_decl_annotations_ +inline void XM_CALLCONV XMVectorGetXPtr(float* x, FXMVECTOR V) noexcept +{ + assert(x != nullptr); +#if defined(_XM_NO_INTRINSICS_) + *x = V.vector4_f32[0]; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + vst1q_lane_f32(x, V, 0); +#elif defined(_XM_SSE_INTRINSICS_) + _mm_store_ss(x, V); +#endif +} + +// Store the Y component into a 32 bit float location in memory. +_Use_decl_annotations_ +inline void XM_CALLCONV XMVectorGetYPtr(float* y, FXMVECTOR V) noexcept +{ + assert(y != nullptr); +#if defined(_XM_NO_INTRINSICS_) + *y = V.vector4_f32[1]; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + vst1q_lane_f32(y, V, 1); +#elif defined(_XM_SSE4_INTRINSICS_) + * (reinterpret_cast(y)) = _mm_extract_ps(V, 1); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vResult = XM_PERMUTE_PS(V, _MM_SHUFFLE(1, 1, 1, 1)); + _mm_store_ss(y, vResult); +#endif +} + +// Store the Z component into a 32 bit float location in memory. +_Use_decl_annotations_ +inline void XM_CALLCONV XMVectorGetZPtr(float* z, FXMVECTOR V) noexcept +{ + assert(z != nullptr); +#if defined(_XM_NO_INTRINSICS_) + *z = V.vector4_f32[2]; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + vst1q_lane_f32(z, V, 2); +#elif defined(_XM_SSE4_INTRINSICS_) + * (reinterpret_cast(z)) = _mm_extract_ps(V, 2); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vResult = XM_PERMUTE_PS(V, _MM_SHUFFLE(2, 2, 2, 2)); + _mm_store_ss(z, vResult); +#endif +} + +// Store the W component into a 32 bit float location in memory. +_Use_decl_annotations_ +inline void XM_CALLCONV XMVectorGetWPtr(float* w, FXMVECTOR V) noexcept +{ + assert(w != nullptr); +#if defined(_XM_NO_INTRINSICS_) + *w = V.vector4_f32[3]; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + vst1q_lane_f32(w, V, 3); +#elif defined(_XM_SSE4_INTRINSICS_) + * (reinterpret_cast(w)) = _mm_extract_ps(V, 3); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vResult = XM_PERMUTE_PS(V, _MM_SHUFFLE(3, 3, 3, 3)); + _mm_store_ss(w, vResult); +#endif +} + +//------------------------------------------------------------------------------ + +// Return an integer value via an index. This is not a recommended +// function to use due to performance loss. +inline uint32_t XM_CALLCONV XMVectorGetIntByIndex(FXMVECTOR V, size_t i) noexcept +{ + assert(i < 4); + _Analysis_assume_(i < 4); +#if defined(_XM_NO_INTRINSICS_) + return V.vector4_u32[i]; +#else + XMVECTORU32 U; + U.v = V; + return U.u[i]; +#endif +} + +//------------------------------------------------------------------------------ + +// Return the X component in an integer register. +inline uint32_t XM_CALLCONV XMVectorGetIntX(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + return V.vector4_u32[0]; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vgetq_lane_u32(vreinterpretq_u32_f32(V), 0); +#elif defined(_XM_SSE_INTRINSICS_) + return static_cast(_mm_cvtsi128_si32(_mm_castps_si128(V))); +#endif +} + +// Return the Y component in an integer register. +inline uint32_t XM_CALLCONV XMVectorGetIntY(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + return V.vector4_u32[1]; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vgetq_lane_u32(vreinterpretq_u32_f32(V), 1); +#elif defined(_XM_SSE4_INTRINSICS_) + __m128i V1 = _mm_castps_si128(V); + return static_cast(_mm_extract_epi32(V1, 1)); +#elif defined(_XM_SSE_INTRINSICS_) + __m128i vResulti = _mm_shuffle_epi32(_mm_castps_si128(V), _MM_SHUFFLE(1, 1, 1, 1)); + return static_cast(_mm_cvtsi128_si32(vResulti)); +#endif +} + +// Return the Z component in an integer register. +inline uint32_t XM_CALLCONV XMVectorGetIntZ(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + return V.vector4_u32[2]; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vgetq_lane_u32(vreinterpretq_u32_f32(V), 2); +#elif defined(_XM_SSE4_INTRINSICS_) + __m128i V1 = _mm_castps_si128(V); + return static_cast(_mm_extract_epi32(V1, 2)); +#elif defined(_XM_SSE_INTRINSICS_) + __m128i vResulti = _mm_shuffle_epi32(_mm_castps_si128(V), _MM_SHUFFLE(2, 2, 2, 2)); + return static_cast(_mm_cvtsi128_si32(vResulti)); +#endif +} + +// Return the W component in an integer register. +inline uint32_t XM_CALLCONV XMVectorGetIntW(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + return V.vector4_u32[3]; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vgetq_lane_u32(vreinterpretq_u32_f32(V), 3); +#elif defined(_XM_SSE4_INTRINSICS_) + __m128i V1 = _mm_castps_si128(V); + return static_cast(_mm_extract_epi32(V1, 3)); +#elif defined(_XM_SSE_INTRINSICS_) + __m128i vResulti = _mm_shuffle_epi32(_mm_castps_si128(V), _MM_SHUFFLE(3, 3, 3, 3)); + return static_cast(_mm_cvtsi128_si32(vResulti)); +#endif +} + +//------------------------------------------------------------------------------ + +// Store a component indexed by i into a 32 bit integer location in memory. +_Use_decl_annotations_ +inline void XM_CALLCONV XMVectorGetIntByIndexPtr(uint32_t* x, FXMVECTOR V, size_t i) noexcept +{ + assert(x != nullptr); + assert(i < 4); + _Analysis_assume_(i < 4); +#if defined(_XM_NO_INTRINSICS_) + *x = V.vector4_u32[i]; +#else + XMVECTORU32 U; + U.v = V; + *x = U.u[i]; +#endif +} + +//------------------------------------------------------------------------------ + +// Store the X component into a 32 bit integer location in memory. +_Use_decl_annotations_ +inline void XM_CALLCONV XMVectorGetIntXPtr(uint32_t* x, FXMVECTOR V) noexcept +{ + assert(x != nullptr); +#if defined(_XM_NO_INTRINSICS_) + *x = V.vector4_u32[0]; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + vst1q_lane_u32(x, *reinterpret_cast(&V), 0); +#elif defined(_XM_SSE_INTRINSICS_) + _mm_store_ss(reinterpret_cast(x), V); +#endif +} + +// Store the Y component into a 32 bit integer location in memory. +_Use_decl_annotations_ +inline void XM_CALLCONV XMVectorGetIntYPtr(uint32_t* y, FXMVECTOR V) noexcept +{ + assert(y != nullptr); +#if defined(_XM_NO_INTRINSICS_) + *y = V.vector4_u32[1]; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + vst1q_lane_u32(y, *reinterpret_cast(&V), 1); +#elif defined(_XM_SSE4_INTRINSICS_) + __m128i V1 = _mm_castps_si128(V); + *y = static_cast(_mm_extract_epi32(V1, 1)); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vResult = XM_PERMUTE_PS(V, _MM_SHUFFLE(1, 1, 1, 1)); + _mm_store_ss(reinterpret_cast(y), vResult); +#endif +} + +// Store the Z component into a 32 bit integer locaCantion in memory. +_Use_decl_annotations_ +inline void XM_CALLCONV XMVectorGetIntZPtr(uint32_t* z, FXMVECTOR V) noexcept +{ + assert(z != nullptr); +#if defined(_XM_NO_INTRINSICS_) + *z = V.vector4_u32[2]; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + vst1q_lane_u32(z, *reinterpret_cast(&V), 2); +#elif defined(_XM_SSE4_INTRINSICS_) + __m128i V1 = _mm_castps_si128(V); + *z = static_cast(_mm_extract_epi32(V1, 2)); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vResult = XM_PERMUTE_PS(V, _MM_SHUFFLE(2, 2, 2, 2)); + _mm_store_ss(reinterpret_cast(z), vResult); +#endif +} + +// Store the W component into a 32 bit integer location in memory. +_Use_decl_annotations_ +inline void XM_CALLCONV XMVectorGetIntWPtr(uint32_t* w, FXMVECTOR V) noexcept +{ + assert(w != nullptr); +#if defined(_XM_NO_INTRINSICS_) + *w = V.vector4_u32[3]; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + vst1q_lane_u32(w, *reinterpret_cast(&V), 3); +#elif defined(_XM_SSE4_INTRINSICS_) + __m128i V1 = _mm_castps_si128(V); + *w = static_cast(_mm_extract_epi32(V1, 3)); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vResult = XM_PERMUTE_PS(V, _MM_SHUFFLE(3, 3, 3, 3)); + _mm_store_ss(reinterpret_cast(w), vResult); +#endif +} + +//------------------------------------------------------------------------------ + +// Set a single indexed floating point component +inline XMVECTOR XM_CALLCONV XMVectorSetByIndex(FXMVECTOR V, float f, size_t i) noexcept +{ + assert(i < 4); + _Analysis_assume_(i < 4); + XMVECTORF32 U; + U.v = V; + U.f[i] = f; + return U.v; +} + +//------------------------------------------------------------------------------ + +// Sets the X component of a vector to a passed floating point value +inline XMVECTOR XM_CALLCONV XMVectorSetX(FXMVECTOR V, float x) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 U = { { { + x, + V.vector4_f32[1], + V.vector4_f32[2], + V.vector4_f32[3] + } } }; + return U.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vsetq_lane_f32(x, V, 0); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vResult = _mm_set_ss(x); + vResult = _mm_move_ss(V, vResult); + return vResult; +#endif +} + +// Sets the Y component of a vector to a passed floating point value +inline XMVECTOR XM_CALLCONV XMVectorSetY(FXMVECTOR V, float y) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 U = { { { + V.vector4_f32[0], + y, + V.vector4_f32[2], + V.vector4_f32[3] + } } }; + return U.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vsetq_lane_f32(y, V, 1); +#elif defined(_XM_SSE4_INTRINSICS_) + XMVECTOR vResult = _mm_set_ss(y); + vResult = _mm_insert_ps(V, vResult, 0x10); + return vResult; +#elif defined(_XM_SSE_INTRINSICS_) + // Swap y and x + XMVECTOR vResult = XM_PERMUTE_PS(V, _MM_SHUFFLE(3, 2, 0, 1)); + // Convert input to vector + XMVECTOR vTemp = _mm_set_ss(y); + // Replace the x component + vResult = _mm_move_ss(vResult, vTemp); + // Swap y and x again + vResult = XM_PERMUTE_PS(vResult, _MM_SHUFFLE(3, 2, 0, 1)); + return vResult; +#endif +} +// Sets the Z component of a vector to a passed floating point value +inline XMVECTOR XM_CALLCONV XMVectorSetZ(FXMVECTOR V, float z) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 U = { { { + V.vector4_f32[0], + V.vector4_f32[1], + z, + V.vector4_f32[3] + } } }; + return U.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vsetq_lane_f32(z, V, 2); +#elif defined(_XM_SSE4_INTRINSICS_) + XMVECTOR vResult = _mm_set_ss(z); + vResult = _mm_insert_ps(V, vResult, 0x20); + return vResult; +#elif defined(_XM_SSE_INTRINSICS_) + // Swap z and x + XMVECTOR vResult = XM_PERMUTE_PS(V, _MM_SHUFFLE(3, 0, 1, 2)); + // Convert input to vector + XMVECTOR vTemp = _mm_set_ss(z); + // Replace the x component + vResult = _mm_move_ss(vResult, vTemp); + // Swap z and x again + vResult = XM_PERMUTE_PS(vResult, _MM_SHUFFLE(3, 0, 1, 2)); + return vResult; +#endif +} + +// Sets the W component of a vector to a passed floating point value +inline XMVECTOR XM_CALLCONV XMVectorSetW(FXMVECTOR V, float w) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 U = { { { + V.vector4_f32[0], + V.vector4_f32[1], + V.vector4_f32[2], + w + } } }; + return U.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vsetq_lane_f32(w, V, 3); +#elif defined(_XM_SSE4_INTRINSICS_) + XMVECTOR vResult = _mm_set_ss(w); + vResult = _mm_insert_ps(V, vResult, 0x30); + return vResult; +#elif defined(_XM_SSE_INTRINSICS_) + // Swap w and x + XMVECTOR vResult = XM_PERMUTE_PS(V, _MM_SHUFFLE(0, 2, 1, 3)); + // Convert input to vector + XMVECTOR vTemp = _mm_set_ss(w); + // Replace the x component + vResult = _mm_move_ss(vResult, vTemp); + // Swap w and x again + vResult = XM_PERMUTE_PS(vResult, _MM_SHUFFLE(0, 2, 1, 3)); + return vResult; +#endif +} + +//------------------------------------------------------------------------------ + +// Sets a component of a vector to a floating point value passed by pointer +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMVectorSetByIndexPtr(FXMVECTOR V, const float* f, size_t i) noexcept +{ + assert(f != nullptr); + assert(i < 4); + _Analysis_assume_(i < 4); + XMVECTORF32 U; + U.v = V; + U.f[i] = *f; + return U.v; +} + +//------------------------------------------------------------------------------ + +// Sets the X component of a vector to a floating point value passed by pointer +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMVectorSetXPtr(FXMVECTOR V, const float* x) noexcept +{ + assert(x != nullptr); +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 U = { { { + *x, + V.vector4_f32[1], + V.vector4_f32[2], + V.vector4_f32[3] + } } }; + return U.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vld1q_lane_f32(x, V, 0); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vResult = _mm_load_ss(x); + vResult = _mm_move_ss(V, vResult); + return vResult; +#endif +} + +// Sets the Y component of a vector to a floating point value passed by pointer +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMVectorSetYPtr(FXMVECTOR V, const float* y) noexcept +{ + assert(y != nullptr); +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 U = { { { + V.vector4_f32[0], + *y, + V.vector4_f32[2], + V.vector4_f32[3] + } } }; + return U.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vld1q_lane_f32(y, V, 1); +#elif defined(_XM_SSE_INTRINSICS_) + // Swap y and x + XMVECTOR vResult = XM_PERMUTE_PS(V, _MM_SHUFFLE(3, 2, 0, 1)); + // Convert input to vector + XMVECTOR vTemp = _mm_load_ss(y); + // Replace the x component + vResult = _mm_move_ss(vResult, vTemp); + // Swap y and x again + vResult = XM_PERMUTE_PS(vResult, _MM_SHUFFLE(3, 2, 0, 1)); + return vResult; +#endif +} + +// Sets the Z component of a vector to a floating point value passed by pointer +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMVectorSetZPtr(FXMVECTOR V, const float* z) noexcept +{ + assert(z != nullptr); +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 U = { { { + V.vector4_f32[0], + V.vector4_f32[1], + *z, + V.vector4_f32[3] + } } }; + return U.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vld1q_lane_f32(z, V, 2); +#elif defined(_XM_SSE_INTRINSICS_) + // Swap z and x + XMVECTOR vResult = XM_PERMUTE_PS(V, _MM_SHUFFLE(3, 0, 1, 2)); + // Convert input to vector + XMVECTOR vTemp = _mm_load_ss(z); + // Replace the x component + vResult = _mm_move_ss(vResult, vTemp); + // Swap z and x again + vResult = XM_PERMUTE_PS(vResult, _MM_SHUFFLE(3, 0, 1, 2)); + return vResult; +#endif +} + +// Sets the W component of a vector to a floating point value passed by pointer +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMVectorSetWPtr(FXMVECTOR V, const float* w) noexcept +{ + assert(w != nullptr); +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 U = { { { + V.vector4_f32[0], + V.vector4_f32[1], + V.vector4_f32[2], + *w + } } }; + return U.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vld1q_lane_f32(w, V, 3); +#elif defined(_XM_SSE_INTRINSICS_) + // Swap w and x + XMVECTOR vResult = XM_PERMUTE_PS(V, _MM_SHUFFLE(0, 2, 1, 3)); + // Convert input to vector + XMVECTOR vTemp = _mm_load_ss(w); + // Replace the x component + vResult = _mm_move_ss(vResult, vTemp); + // Swap w and x again + vResult = XM_PERMUTE_PS(vResult, _MM_SHUFFLE(0, 2, 1, 3)); + return vResult; +#endif +} + +//------------------------------------------------------------------------------ + +// Sets a component of a vector to an integer passed by value +inline XMVECTOR XM_CALLCONV XMVectorSetIntByIndex(FXMVECTOR V, uint32_t x, size_t i) noexcept +{ + assert(i < 4); + _Analysis_assume_(i < 4); + XMVECTORU32 tmp; + tmp.v = V; + tmp.u[i] = x; + return tmp; +} + +//------------------------------------------------------------------------------ + +// Sets the X component of a vector to an integer passed by value +inline XMVECTOR XM_CALLCONV XMVectorSetIntX(FXMVECTOR V, uint32_t x) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + XMVECTORU32 U = { { { + x, + V.vector4_u32[1], + V.vector4_u32[2], + V.vector4_u32[3] + } } }; + return U.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vreinterpretq_f32_u32(vsetq_lane_u32(x, vreinterpretq_u32_f32(V), 0)); +#elif defined(_XM_SSE_INTRINSICS_) + __m128i vTemp = _mm_cvtsi32_si128(static_cast(x)); + XMVECTOR vResult = _mm_move_ss(V, _mm_castsi128_ps(vTemp)); + return vResult; +#endif +} + +// Sets the Y component of a vector to an integer passed by value +inline XMVECTOR XM_CALLCONV XMVectorSetIntY(FXMVECTOR V, uint32_t y) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + XMVECTORU32 U = { { { + V.vector4_u32[0], + y, + V.vector4_u32[2], + V.vector4_u32[3] + } } }; + return U.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vreinterpretq_f32_u32(vsetq_lane_u32(y, vreinterpretq_u32_f32(V), 1)); +#elif defined(_XM_SSE4_INTRINSICS_) + __m128i vResult = _mm_castps_si128(V); + vResult = _mm_insert_epi32(vResult, static_cast(y), 1); + return _mm_castsi128_ps(vResult); +#elif defined(_XM_SSE_INTRINSICS_) + // Swap y and x + XMVECTOR vResult = XM_PERMUTE_PS(V, _MM_SHUFFLE(3, 2, 0, 1)); + // Convert input to vector + __m128i vTemp = _mm_cvtsi32_si128(static_cast(y)); + // Replace the x component + vResult = _mm_move_ss(vResult, _mm_castsi128_ps(vTemp)); + // Swap y and x again + vResult = XM_PERMUTE_PS(vResult, _MM_SHUFFLE(3, 2, 0, 1)); + return vResult; +#endif +} + +// Sets the Z component of a vector to an integer passed by value +inline XMVECTOR XM_CALLCONV XMVectorSetIntZ(FXMVECTOR V, uint32_t z) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + XMVECTORU32 U = { { { + V.vector4_u32[0], + V.vector4_u32[1], + z, + V.vector4_u32[3] + } } }; + return U.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vreinterpretq_f32_u32(vsetq_lane_u32(z, vreinterpretq_u32_f32(V), 2)); +#elif defined(_XM_SSE4_INTRINSICS_) + __m128i vResult = _mm_castps_si128(V); + vResult = _mm_insert_epi32(vResult, static_cast(z), 2); + return _mm_castsi128_ps(vResult); +#elif defined(_XM_SSE_INTRINSICS_) + // Swap z and x + XMVECTOR vResult = XM_PERMUTE_PS(V, _MM_SHUFFLE(3, 0, 1, 2)); + // Convert input to vector + __m128i vTemp = _mm_cvtsi32_si128(static_cast(z)); + // Replace the x component + vResult = _mm_move_ss(vResult, _mm_castsi128_ps(vTemp)); + // Swap z and x again + vResult = XM_PERMUTE_PS(vResult, _MM_SHUFFLE(3, 0, 1, 2)); + return vResult; +#endif +} + +// Sets the W component of a vector to an integer passed by value +inline XMVECTOR XM_CALLCONV XMVectorSetIntW(FXMVECTOR V, uint32_t w) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + XMVECTORU32 U = { { { + V.vector4_u32[0], + V.vector4_u32[1], + V.vector4_u32[2], + w + } } }; + return U.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vreinterpretq_f32_u32(vsetq_lane_u32(w, vreinterpretq_u32_f32(V), 3)); +#elif defined(_XM_SSE4_INTRINSICS_) + __m128i vResult = _mm_castps_si128(V); + vResult = _mm_insert_epi32(vResult, static_cast(w), 3); + return _mm_castsi128_ps(vResult); +#elif defined(_XM_SSE_INTRINSICS_) + // Swap w and x + XMVECTOR vResult = XM_PERMUTE_PS(V, _MM_SHUFFLE(0, 2, 1, 3)); + // Convert input to vector + __m128i vTemp = _mm_cvtsi32_si128(static_cast(w)); + // Replace the x component + vResult = _mm_move_ss(vResult, _mm_castsi128_ps(vTemp)); + // Swap w and x again + vResult = XM_PERMUTE_PS(vResult, _MM_SHUFFLE(0, 2, 1, 3)); + return vResult; +#endif +} + +//------------------------------------------------------------------------------ + +// Sets a component of a vector to an integer value passed by pointer +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMVectorSetIntByIndexPtr(FXMVECTOR V, const uint32_t* x, size_t i) noexcept +{ + assert(x != nullptr); + assert(i < 4); + _Analysis_assume_(i < 4); + XMVECTORU32 tmp; + tmp.v = V; + tmp.u[i] = *x; + return tmp; +} + +//------------------------------------------------------------------------------ + +// Sets the X component of a vector to an integer value passed by pointer +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMVectorSetIntXPtr(FXMVECTOR V, const uint32_t* x) noexcept +{ + assert(x != nullptr); +#if defined(_XM_NO_INTRINSICS_) + XMVECTORU32 U = { { { + *x, + V.vector4_u32[1], + V.vector4_u32[2], + V.vector4_u32[3] + } } }; + return U.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vreinterpretq_f32_u32(vld1q_lane_u32(x, *reinterpret_cast(&V), 0)); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vTemp = _mm_load_ss(reinterpret_cast(x)); + XMVECTOR vResult = _mm_move_ss(V, vTemp); + return vResult; +#endif +} + +// Sets the Y component of a vector to an integer value passed by pointer +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMVectorSetIntYPtr(FXMVECTOR V, const uint32_t* y) noexcept +{ + assert(y != nullptr); +#if defined(_XM_NO_INTRINSICS_) + XMVECTORU32 U = { { { + V.vector4_u32[0], + *y, + V.vector4_u32[2], + V.vector4_u32[3] + } } }; + return U.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vreinterpretq_f32_u32(vld1q_lane_u32(y, *reinterpret_cast(&V), 1)); +#elif defined(_XM_SSE_INTRINSICS_) + // Swap y and x + XMVECTOR vResult = XM_PERMUTE_PS(V, _MM_SHUFFLE(3, 2, 0, 1)); + // Convert input to vector + XMVECTOR vTemp = _mm_load_ss(reinterpret_cast(y)); + // Replace the x component + vResult = _mm_move_ss(vResult, vTemp); + // Swap y and x again + vResult = XM_PERMUTE_PS(vResult, _MM_SHUFFLE(3, 2, 0, 1)); + return vResult; +#endif +} + +// Sets the Z component of a vector to an integer value passed by pointer +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMVectorSetIntZPtr(FXMVECTOR V, const uint32_t* z) noexcept +{ + assert(z != nullptr); +#if defined(_XM_NO_INTRINSICS_) + XMVECTORU32 U = { { { + V.vector4_u32[0], + V.vector4_u32[1], + *z, + V.vector4_u32[3] + } } }; + return U.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vreinterpretq_f32_u32(vld1q_lane_u32(z, *reinterpret_cast(&V), 2)); +#elif defined(_XM_SSE_INTRINSICS_) + // Swap z and x + XMVECTOR vResult = XM_PERMUTE_PS(V, _MM_SHUFFLE(3, 0, 1, 2)); + // Convert input to vector + XMVECTOR vTemp = _mm_load_ss(reinterpret_cast(z)); + // Replace the x component + vResult = _mm_move_ss(vResult, vTemp); + // Swap z and x again + vResult = XM_PERMUTE_PS(vResult, _MM_SHUFFLE(3, 0, 1, 2)); + return vResult; +#endif +} + +// Sets the W component of a vector to an integer value passed by pointer +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMVectorSetIntWPtr(FXMVECTOR V, const uint32_t* w) noexcept +{ + assert(w != nullptr); +#if defined(_XM_NO_INTRINSICS_) + XMVECTORU32 U = { { { + V.vector4_u32[0], + V.vector4_u32[1], + V.vector4_u32[2], + *w + } } }; + return U.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vreinterpretq_f32_u32(vld1q_lane_u32(w, *reinterpret_cast(&V), 3)); +#elif defined(_XM_SSE_INTRINSICS_) + // Swap w and x + XMVECTOR vResult = XM_PERMUTE_PS(V, _MM_SHUFFLE(0, 2, 1, 3)); + // Convert input to vector + XMVECTOR vTemp = _mm_load_ss(reinterpret_cast(w)); + // Replace the x component + vResult = _mm_move_ss(vResult, vTemp); + // Swap w and x again + vResult = XM_PERMUTE_PS(vResult, _MM_SHUFFLE(0, 2, 1, 3)); + return vResult; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorSwizzle +( + FXMVECTOR V, + uint32_t E0, + uint32_t E1, + uint32_t E2, + uint32_t E3 +) noexcept +{ + assert((E0 < 4) && (E1 < 4) && (E2 < 4) && (E3 < 4)); + _Analysis_assume_((E0 < 4) && (E1 < 4) && (E2 < 4) && (E3 < 4)); +#if defined(_XM_NO_INTRINSICS_) + + XMVECTORF32 Result = { { { + V.vector4_f32[E0], + V.vector4_f32[E1], + V.vector4_f32[E2], + V.vector4_f32[E3] + } } }; + return Result.v; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + static const uint32_t ControlElement[4] = + { + 0x03020100, // XM_SWIZZLE_X + 0x07060504, // XM_SWIZZLE_Y + 0x0B0A0908, // XM_SWIZZLE_Z + 0x0F0E0D0C, // XM_SWIZZLE_W + }; + + uint8x8x2_t tbl; + tbl.val[0] = vreinterpret_u8_f32(vget_low_f32(V)); + tbl.val[1] = vreinterpret_u8_f32(vget_high_f32(V)); + + uint32x2_t idx = vcreate_u32(static_cast(ControlElement[E0]) | (static_cast(ControlElement[E1]) << 32)); + const uint8x8_t rL = vtbl2_u8(tbl, vreinterpret_u8_u32(idx)); + + idx = vcreate_u32(static_cast(ControlElement[E2]) | (static_cast(ControlElement[E3]) << 32)); + const uint8x8_t rH = vtbl2_u8(tbl, vreinterpret_u8_u32(idx)); + + return vcombine_f32(vreinterpret_f32_u8(rL), vreinterpret_f32_u8(rH)); +#elif defined(_XM_AVX_INTRINSICS_) + unsigned int elem[4] = { E0, E1, E2, E3 }; + __m128i vControl = _mm_loadu_si128(reinterpret_cast(&elem[0])); + return _mm_permutevar_ps(V, vControl); +#else +#if defined(__GNUC__) && !defined(__clang__) + // workaround some GCC optimization behavior that breaks this function + XMVECTORU32 T; + T.v = V; + auto aPtr = reinterpret_cast(&T); +#else + auto aPtr = reinterpret_cast(&V); +#endif + + XMVECTORU32 vResult; + vResult.u[0] = aPtr[E0]; + vResult.u[1] = aPtr[E1]; + vResult.u[2] = aPtr[E2]; + vResult.u[3] = aPtr[E3]; + + return vResult.v; +#endif +} + +//------------------------------------------------------------------------------ +inline XMVECTOR XM_CALLCONV XMVectorPermute +( + FXMVECTOR V1, + FXMVECTOR V2, + uint32_t PermuteX, + uint32_t PermuteY, + uint32_t PermuteZ, + uint32_t PermuteW +) noexcept +{ + assert(PermuteX <= 7 && PermuteY <= 7 && PermuteZ <= 7 && PermuteW <= 7); + _Analysis_assume_(PermuteX <= 7 && PermuteY <= 7 && PermuteZ <= 7 && PermuteW <= 7); + +#if defined(_XM_ARM_NEON_INTRINSICS_) && !defined(_XM_NO_INTRINSICS_) + static const uint32_t ControlElement[8] = + { + 0x03020100, // XM_PERMUTE_0X + 0x07060504, // XM_PERMUTE_0Y + 0x0B0A0908, // XM_PERMUTE_0Z + 0x0F0E0D0C, // XM_PERMUTE_0W + 0x13121110, // XM_PERMUTE_1X + 0x17161514, // XM_PERMUTE_1Y + 0x1B1A1918, // XM_PERMUTE_1Z + 0x1F1E1D1C, // XM_PERMUTE_1W + }; + + uint8x8x4_t tbl; + tbl.val[0] = vreinterpret_u8_f32(vget_low_f32(V1)); + tbl.val[1] = vreinterpret_u8_f32(vget_high_f32(V1)); + tbl.val[2] = vreinterpret_u8_f32(vget_low_f32(V2)); + tbl.val[3] = vreinterpret_u8_f32(vget_high_f32(V2)); + + uint32x2_t idx = vcreate_u32(static_cast(ControlElement[PermuteX]) | (static_cast(ControlElement[PermuteY]) << 32)); + const uint8x8_t rL = vtbl4_u8(tbl, vreinterpret_u8_u32(idx)); + + idx = vcreate_u32(static_cast(ControlElement[PermuteZ]) | (static_cast(ControlElement[PermuteW]) << 32)); + const uint8x8_t rH = vtbl4_u8(tbl, vreinterpret_u8_u32(idx)); + + return vcombine_f32(vreinterpret_f32_u8(rL), vreinterpret_f32_u8(rH)); +#elif defined(_XM_AVX_INTRINSICS_) && !defined(_XM_NO_INTRINSICS_) + static const XMVECTORU32 three = { { { 3, 3, 3, 3 } } }; + + XM_ALIGNED_DATA(16) unsigned int elem[4] = { PermuteX, PermuteY, PermuteZ, PermuteW }; + __m128i vControl = _mm_load_si128(reinterpret_cast(&elem[0])); + + __m128i vSelect = _mm_cmpgt_epi32(vControl, three); + vControl = _mm_castps_si128(_mm_and_ps(_mm_castsi128_ps(vControl), three)); + + __m128 shuffled1 = _mm_permutevar_ps(V1, vControl); + __m128 shuffled2 = _mm_permutevar_ps(V2, vControl); + + __m128 masked1 = _mm_andnot_ps(_mm_castsi128_ps(vSelect), shuffled1); + __m128 masked2 = _mm_and_ps(_mm_castsi128_ps(vSelect), shuffled2); + + return _mm_or_ps(masked1, masked2); +#else + + const uint32_t* aPtr[2]; + +#if defined(__GNUC__) && !defined(__clang__) + // workaround some GCC optimization behavior that breaks this function + XMVECTORU32 T1; + T1.v = V1; + XMVECTORU32 T2; + T2.v = V2; + aPtr[0] = reinterpret_cast(&T1); + aPtr[1] = reinterpret_cast(&T2); +#else + aPtr[0] = reinterpret_cast(&V1); + aPtr[1] = reinterpret_cast(&V2); +#endif + + XMVECTORU32 vResult; + const uint32_t i0 = PermuteX & 3; + const uint32_t vi0 = PermuteX >> 2; + vResult.u[0] = aPtr[vi0][i0]; + + const uint32_t i1 = PermuteY & 3; + const uint32_t vi1 = PermuteY >> 2; + vResult.u[1] = aPtr[vi1][i1]; + + const uint32_t i2 = PermuteZ & 3; + const uint32_t vi2 = PermuteZ >> 2; + vResult.u[2] = aPtr[vi2][i2]; + + const uint32_t i3 = PermuteW & 3; + const uint32_t vi3 = PermuteW >> 2; + vResult.u[3] = aPtr[vi3][i3]; + + return vResult.v; +#endif +} + +//------------------------------------------------------------------------------ +// Define a control vector to be used in XMVectorSelect +// operations. The four integers specified in XMVectorSelectControl +// serve as indices to select between components in two vectors. +// The first index controls selection for the first component of +// the vectors involved in a select operation, the second index +// controls selection for the second component etc. A value of +// zero for an index causes the corresponding component from the first +// vector to be selected whereas a one causes the component from the +// second vector to be selected instead. + +inline XMVECTOR XM_CALLCONV XMVectorSelectControl +( + uint32_t VectorIndex0, + uint32_t VectorIndex1, + uint32_t VectorIndex2, + uint32_t VectorIndex3 +) noexcept +{ +#if defined(_XM_SSE_INTRINSICS_) && !defined(_XM_NO_INTRINSICS_) + // x=Index0,y=Index1,z=Index2,w=Index3 + __m128i vTemp = _mm_set_epi32(static_cast(VectorIndex3), static_cast(VectorIndex2), static_cast(VectorIndex1), static_cast(VectorIndex0)); + // Any non-zero entries become 0xFFFFFFFF else 0 + vTemp = _mm_cmpgt_epi32(vTemp, g_XMZero); + return _mm_castsi128_ps(vTemp); +#elif defined(_XM_ARM_NEON_INTRINSICS_) && !defined(_XM_NO_INTRINSICS_) + int32x2_t V0 = vcreate_s32(static_cast(VectorIndex0) | (static_cast(VectorIndex1) << 32)); + int32x2_t V1 = vcreate_s32(static_cast(VectorIndex2) | (static_cast(VectorIndex3) << 32)); + int32x4_t vTemp = vcombine_s32(V0, V1); + // Any non-zero entries become 0xFFFFFFFF else 0 + return vreinterpretq_f32_u32(vcgtq_s32(vTemp, g_XMZero)); +#else + XMVECTOR ControlVector; + const uint32_t ControlElement[] = + { + XM_SELECT_0, + XM_SELECT_1 + }; + + assert(VectorIndex0 < 2); + assert(VectorIndex1 < 2); + assert(VectorIndex2 < 2); + assert(VectorIndex3 < 2); + _Analysis_assume_(VectorIndex0 < 2); + _Analysis_assume_(VectorIndex1 < 2); + _Analysis_assume_(VectorIndex2 < 2); + _Analysis_assume_(VectorIndex3 < 2); + + ControlVector.vector4_u32[0] = ControlElement[VectorIndex0]; + ControlVector.vector4_u32[1] = ControlElement[VectorIndex1]; + ControlVector.vector4_u32[2] = ControlElement[VectorIndex2]; + ControlVector.vector4_u32[3] = ControlElement[VectorIndex3]; + + return ControlVector; + +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorSelect +( + FXMVECTOR V1, + FXMVECTOR V2, + FXMVECTOR Control +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTORU32 Result = { { { + (V1.vector4_u32[0] & ~Control.vector4_u32[0]) | (V2.vector4_u32[0] & Control.vector4_u32[0]), + (V1.vector4_u32[1] & ~Control.vector4_u32[1]) | (V2.vector4_u32[1] & Control.vector4_u32[1]), + (V1.vector4_u32[2] & ~Control.vector4_u32[2]) | (V2.vector4_u32[2] & Control.vector4_u32[2]), + (V1.vector4_u32[3] & ~Control.vector4_u32[3]) | (V2.vector4_u32[3] & Control.vector4_u32[3]), + } } }; + return Result.v; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vbslq_f32(vreinterpretq_u32_f32(Control), V2, V1); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vTemp1 = _mm_andnot_ps(Control, V1); + XMVECTOR vTemp2 = _mm_and_ps(V2, Control); + return _mm_or_ps(vTemp1, vTemp2); +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorMergeXY +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTORU32 Result = { { { + V1.vector4_u32[0], + V2.vector4_u32[0], + V1.vector4_u32[1], + V2.vector4_u32[1], + } } }; + return Result.v; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vzipq_f32(V1, V2).val[0]; +#elif defined(_XM_SSE_INTRINSICS_) + return _mm_unpacklo_ps(V1, V2); +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorMergeZW +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTORU32 Result = { { { + V1.vector4_u32[2], + V2.vector4_u32[2], + V1.vector4_u32[3], + V2.vector4_u32[3] + } } }; + return Result.v; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vzipq_f32(V1, V2).val[1]; +#elif defined(_XM_SSE_INTRINSICS_) + return _mm_unpackhi_ps(V1, V2); +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorShiftLeft(FXMVECTOR V1, FXMVECTOR V2, uint32_t Elements) noexcept +{ + assert(Elements < 4); + _Analysis_assume_(Elements < 4); + return XMVectorPermute(V1, V2, Elements, ((Elements)+1), ((Elements)+2), ((Elements)+3)); +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorRotateLeft(FXMVECTOR V, uint32_t Elements) noexcept +{ + assert(Elements < 4); + _Analysis_assume_(Elements < 4); + return XMVectorSwizzle(V, Elements & 3, (Elements + 1) & 3, (Elements + 2) & 3, (Elements + 3) & 3); +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorRotateRight(FXMVECTOR V, uint32_t Elements) noexcept +{ + assert(Elements < 4); + _Analysis_assume_(Elements < 4); + return XMVectorSwizzle(V, (4 - (Elements)) & 3, (5 - (Elements)) & 3, (6 - (Elements)) & 3, (7 - (Elements)) & 3); +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorInsert( + FXMVECTOR VD, FXMVECTOR VS, + uint32_t VSLeftRotateElements, + uint32_t Select0, uint32_t Select1, uint32_t Select2, uint32_t Select3) noexcept +{ + XMVECTOR Control = XMVectorSelectControl(Select0 & 1, Select1 & 1, Select2 & 1, Select3 & 1); + return XMVectorSelect(VD, XMVectorRotateLeft(VS, VSLeftRotateElements), Control); +} + +//------------------------------------------------------------------------------ +// Comparison operations +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorEqual +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTORU32 Control = { { { + (V1.vector4_f32[0] == V2.vector4_f32[0]) ? 0xFFFFFFFF : 0, + (V1.vector4_f32[1] == V2.vector4_f32[1]) ? 0xFFFFFFFF : 0, + (V1.vector4_f32[2] == V2.vector4_f32[2]) ? 0xFFFFFFFF : 0, + (V1.vector4_f32[3] == V2.vector4_f32[3]) ? 0xFFFFFFFF : 0, + } } }; + return Control.v; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vreinterpretq_f32_u32(vceqq_f32(V1, V2)); +#elif defined(_XM_SSE_INTRINSICS_) + return _mm_cmpeq_ps(V1, V2); +#endif +} + +//------------------------------------------------------------------------------ + +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMVectorEqualR +( + uint32_t* pCR, + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ + assert(pCR != nullptr); +#if defined(_XM_NO_INTRINSICS_) + uint32_t ux = (V1.vector4_f32[0] == V2.vector4_f32[0]) ? 0xFFFFFFFFU : 0; + uint32_t uy = (V1.vector4_f32[1] == V2.vector4_f32[1]) ? 0xFFFFFFFFU : 0; + uint32_t uz = (V1.vector4_f32[2] == V2.vector4_f32[2]) ? 0xFFFFFFFFU : 0; + uint32_t uw = (V1.vector4_f32[3] == V2.vector4_f32[3]) ? 0xFFFFFFFFU : 0; + uint32_t CR = 0; + if (ux & uy & uz & uw) + { + // All elements are greater + CR = XM_CRMASK_CR6TRUE; + } + else if (!(ux | uy | uz | uw)) + { + // All elements are not greater + CR = XM_CRMASK_CR6FALSE; + } + *pCR = CR; + + XMVECTORU32 Control = { { { ux, uy, uz, uw } } }; + return Control; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x4_t vResult = vceqq_f32(V1, V2); + uint8x8x2_t vTemp = vzip_u8(vreinterpret_u8_u32(vget_low_u32(vResult)), vreinterpret_u8_u32(vget_high_u32(vResult))); + uint16x4x2_t vTemp2 = vzip_u16(vreinterpret_u16_u8(vTemp.val[0]), vreinterpret_u16_u8(vTemp.val[1])); + uint32_t r = vget_lane_u32(vreinterpret_u32_u16(vTemp2.val[1]), 1); + uint32_t CR = 0; + if (r == 0xFFFFFFFFU) + { + // All elements are equal + CR = XM_CRMASK_CR6TRUE; + } + else if (!r) + { + // All elements are not equal + CR = XM_CRMASK_CR6FALSE; + } + *pCR = CR; + return vreinterpretq_f32_u32(vResult); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vTemp = _mm_cmpeq_ps(V1, V2); + uint32_t CR = 0; + int iTest = _mm_movemask_ps(vTemp); + if (iTest == 0xf) + { + CR = XM_CRMASK_CR6TRUE; + } + else if (!iTest) + { + // All elements are not greater + CR = XM_CRMASK_CR6FALSE; + } + *pCR = CR; + return vTemp; +#endif +} + +//------------------------------------------------------------------------------ +// Treat the components of the vectors as unsigned integers and +// compare individual bits between the two. This is useful for +// comparing control vectors and result vectors returned from +// other comparison operations. + +inline XMVECTOR XM_CALLCONV XMVectorEqualInt +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTORU32 Control = { { { + (V1.vector4_u32[0] == V2.vector4_u32[0]) ? 0xFFFFFFFF : 0, + (V1.vector4_u32[1] == V2.vector4_u32[1]) ? 0xFFFFFFFF : 0, + (V1.vector4_u32[2] == V2.vector4_u32[2]) ? 0xFFFFFFFF : 0, + (V1.vector4_u32[3] == V2.vector4_u32[3]) ? 0xFFFFFFFF : 0, + } } }; + return Control.v; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vreinterpretq_f32_u32(vceqq_s32(vreinterpretq_s32_f32(V1), vreinterpretq_s32_f32(V2))); +#elif defined(_XM_SSE_INTRINSICS_) + __m128i V = _mm_cmpeq_epi32(_mm_castps_si128(V1), _mm_castps_si128(V2)); + return _mm_castsi128_ps(V); +#endif +} + +//------------------------------------------------------------------------------ + +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMVectorEqualIntR +( + uint32_t* pCR, + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ + assert(pCR != nullptr); +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR Control = XMVectorEqualInt(V1, V2); + + *pCR = 0; + if (XMVector4EqualInt(Control, XMVectorTrueInt())) + { + // All elements are equal + *pCR |= XM_CRMASK_CR6TRUE; + } + else if (XMVector4EqualInt(Control, XMVectorFalseInt())) + { + // All elements are not equal + *pCR |= XM_CRMASK_CR6FALSE; + } + return Control; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x4_t vResult = vceqq_u32(vreinterpretq_u32_f32(V1), vreinterpretq_u32_f32(V2)); + uint8x8x2_t vTemp = vzip_u8(vget_low_u8(vreinterpretq_u8_u32(vResult)), vget_high_u8(vreinterpretq_u8_u32(vResult))); + uint16x4x2_t vTemp2 = vzip_u16(vreinterpret_u16_u8(vTemp.val[0]), vreinterpret_u16_u8(vTemp.val[1])); + uint32_t r = vget_lane_u32(vreinterpret_u32_u16(vTemp2.val[1]), 1); + uint32_t CR = 0; + if (r == 0xFFFFFFFFU) + { + // All elements are equal + CR = XM_CRMASK_CR6TRUE; + } + else if (!r) + { + // All elements are not equal + CR = XM_CRMASK_CR6FALSE; + } + *pCR = CR; + return vreinterpretq_f32_u32(vResult); +#elif defined(_XM_SSE_INTRINSICS_) + __m128i V = _mm_cmpeq_epi32(_mm_castps_si128(V1), _mm_castps_si128(V2)); + int iTemp = _mm_movemask_ps(_mm_castsi128_ps(V)); + uint32_t CR = 0; + if (iTemp == 0x0F) + { + CR = XM_CRMASK_CR6TRUE; + } + else if (!iTemp) + { + CR = XM_CRMASK_CR6FALSE; + } + *pCR = CR; + return _mm_castsi128_ps(V); +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorNearEqual +( + FXMVECTOR V1, + FXMVECTOR V2, + FXMVECTOR Epsilon +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + float fDeltax = V1.vector4_f32[0] - V2.vector4_f32[0]; + float fDeltay = V1.vector4_f32[1] - V2.vector4_f32[1]; + float fDeltaz = V1.vector4_f32[2] - V2.vector4_f32[2]; + float fDeltaw = V1.vector4_f32[3] - V2.vector4_f32[3]; + + fDeltax = fabsf(fDeltax); + fDeltay = fabsf(fDeltay); + fDeltaz = fabsf(fDeltaz); + fDeltaw = fabsf(fDeltaw); + + XMVECTORU32 Control = { { { + (fDeltax <= Epsilon.vector4_f32[0]) ? 0xFFFFFFFFU : 0, + (fDeltay <= Epsilon.vector4_f32[1]) ? 0xFFFFFFFFU : 0, + (fDeltaz <= Epsilon.vector4_f32[2]) ? 0xFFFFFFFFU : 0, + (fDeltaw <= Epsilon.vector4_f32[3]) ? 0xFFFFFFFFU : 0, + } } }; + return Control.v; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x4_t vDelta = vsubq_f32(V1, V2); +#if defined(_MSC_VER) && !defined(__clang__) && !defined(_ARM64_DISTINCT_NEON_TYPES) + return vacleq_f32(vDelta, Epsilon); +#else + return vreinterpretq_f32_u32(vcleq_f32(vabsq_f32(vDelta), Epsilon)); +#endif +#elif defined(_XM_SSE_INTRINSICS_) + // Get the difference + XMVECTOR vDelta = _mm_sub_ps(V1, V2); + // Get the absolute value of the difference + XMVECTOR vTemp = _mm_setzero_ps(); + vTemp = _mm_sub_ps(vTemp, vDelta); + vTemp = _mm_max_ps(vTemp, vDelta); + vTemp = _mm_cmple_ps(vTemp, Epsilon); + return vTemp; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorNotEqual +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTORU32 Control = { { { + (V1.vector4_f32[0] != V2.vector4_f32[0]) ? 0xFFFFFFFF : 0, + (V1.vector4_f32[1] != V2.vector4_f32[1]) ? 0xFFFFFFFF : 0, + (V1.vector4_f32[2] != V2.vector4_f32[2]) ? 0xFFFFFFFF : 0, + (V1.vector4_f32[3] != V2.vector4_f32[3]) ? 0xFFFFFFFF : 0, + } } }; + return Control.v; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vreinterpretq_f32_u32(vmvnq_u32(vceqq_f32(V1, V2))); +#elif defined(_XM_SSE_INTRINSICS_) + return _mm_cmpneq_ps(V1, V2); +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorNotEqualInt +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTORU32 Control = { { { + (V1.vector4_u32[0] != V2.vector4_u32[0]) ? 0xFFFFFFFFU : 0, + (V1.vector4_u32[1] != V2.vector4_u32[1]) ? 0xFFFFFFFFU : 0, + (V1.vector4_u32[2] != V2.vector4_u32[2]) ? 0xFFFFFFFFU : 0, + (V1.vector4_u32[3] != V2.vector4_u32[3]) ? 0xFFFFFFFFU : 0 + } } }; + return Control.v; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vreinterpretq_f32_u32(vmvnq_u32( + vceqq_u32(vreinterpretq_u32_f32(V1), vreinterpretq_u32_f32(V2)))); +#elif defined(_XM_SSE_INTRINSICS_) + __m128i V = _mm_cmpeq_epi32(_mm_castps_si128(V1), _mm_castps_si128(V2)); + return _mm_xor_ps(_mm_castsi128_ps(V), g_XMNegOneMask); +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorGreater +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTORU32 Control = { { { + (V1.vector4_f32[0] > V2.vector4_f32[0]) ? 0xFFFFFFFF : 0, + (V1.vector4_f32[1] > V2.vector4_f32[1]) ? 0xFFFFFFFF : 0, + (V1.vector4_f32[2] > V2.vector4_f32[2]) ? 0xFFFFFFFF : 0, + (V1.vector4_f32[3] > V2.vector4_f32[3]) ? 0xFFFFFFFF : 0 + } } }; + return Control.v; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vreinterpretq_f32_u32(vcgtq_f32(V1, V2)); +#elif defined(_XM_SSE_INTRINSICS_) + return _mm_cmpgt_ps(V1, V2); +#endif +} + +//------------------------------------------------------------------------------ + +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMVectorGreaterR +( + uint32_t* pCR, + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ + assert(pCR != nullptr); +#if defined(_XM_NO_INTRINSICS_) + + uint32_t ux = (V1.vector4_f32[0] > V2.vector4_f32[0]) ? 0xFFFFFFFFU : 0; + uint32_t uy = (V1.vector4_f32[1] > V2.vector4_f32[1]) ? 0xFFFFFFFFU : 0; + uint32_t uz = (V1.vector4_f32[2] > V2.vector4_f32[2]) ? 0xFFFFFFFFU : 0; + uint32_t uw = (V1.vector4_f32[3] > V2.vector4_f32[3]) ? 0xFFFFFFFFU : 0; + uint32_t CR = 0; + if (ux & uy & uz & uw) + { + // All elements are greater + CR = XM_CRMASK_CR6TRUE; + } + else if (!(ux | uy | uz | uw)) + { + // All elements are not greater + CR = XM_CRMASK_CR6FALSE; + } + *pCR = CR; + + XMVECTORU32 Control = { { { ux, uy, uz, uw } } }; + return Control.v; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x4_t vResult = vcgtq_f32(V1, V2); + uint8x8x2_t vTemp = vzip_u8(vget_low_u8(vreinterpretq_u8_u32(vResult)), vget_high_u8(vreinterpretq_u8_u32(vResult))); + uint16x4x2_t vTemp2 = vzip_u16(vreinterpret_u16_u8(vTemp.val[0]), vreinterpret_u16_u8(vTemp.val[1])); + uint32_t r = vget_lane_u32(vreinterpret_u32_u16(vTemp2.val[1]), 1); + uint32_t CR = 0; + if (r == 0xFFFFFFFFU) + { + // All elements are greater + CR = XM_CRMASK_CR6TRUE; + } + else if (!r) + { + // All elements are not greater + CR = XM_CRMASK_CR6FALSE; + } + *pCR = CR; + return vreinterpretq_f32_u32(vResult); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vTemp = _mm_cmpgt_ps(V1, V2); + uint32_t CR = 0; + int iTest = _mm_movemask_ps(vTemp); + if (iTest == 0xf) + { + CR = XM_CRMASK_CR6TRUE; + } + else if (!iTest) + { + // All elements are not greater + CR = XM_CRMASK_CR6FALSE; + } + *pCR = CR; + return vTemp; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorGreaterOrEqual +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTORU32 Control = { { { + (V1.vector4_f32[0] >= V2.vector4_f32[0]) ? 0xFFFFFFFF : 0, + (V1.vector4_f32[1] >= V2.vector4_f32[1]) ? 0xFFFFFFFF : 0, + (V1.vector4_f32[2] >= V2.vector4_f32[2]) ? 0xFFFFFFFF : 0, + (V1.vector4_f32[3] >= V2.vector4_f32[3]) ? 0xFFFFFFFF : 0 + } } }; + return Control.v; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vreinterpretq_f32_u32(vcgeq_f32(V1, V2)); +#elif defined(_XM_SSE_INTRINSICS_) + return _mm_cmpge_ps(V1, V2); +#endif +} + +//------------------------------------------------------------------------------ + +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMVectorGreaterOrEqualR +( + uint32_t* pCR, + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ + assert(pCR != nullptr); +#if defined(_XM_NO_INTRINSICS_) + + uint32_t ux = (V1.vector4_f32[0] >= V2.vector4_f32[0]) ? 0xFFFFFFFFU : 0; + uint32_t uy = (V1.vector4_f32[1] >= V2.vector4_f32[1]) ? 0xFFFFFFFFU : 0; + uint32_t uz = (V1.vector4_f32[2] >= V2.vector4_f32[2]) ? 0xFFFFFFFFU : 0; + uint32_t uw = (V1.vector4_f32[3] >= V2.vector4_f32[3]) ? 0xFFFFFFFFU : 0; + uint32_t CR = 0; + if (ux & uy & uz & uw) + { + // All elements are greater + CR = XM_CRMASK_CR6TRUE; + } + else if (!(ux | uy | uz | uw)) + { + // All elements are not greater + CR = XM_CRMASK_CR6FALSE; + } + *pCR = CR; + + XMVECTORU32 Control = { { { ux, uy, uz, uw } } }; + return Control.v; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x4_t vResult = vcgeq_f32(V1, V2); + uint8x8x2_t vTemp = vzip_u8(vget_low_u8(vreinterpretq_u8_u32(vResult)), vget_high_u8(vreinterpretq_u8_u32(vResult))); + uint16x4x2_t vTemp2 = vzip_u16(vreinterpret_u16_u8(vTemp.val[0]), vreinterpret_u16_u8(vTemp.val[1])); + uint32_t r = vget_lane_u32(vreinterpret_u32_u16(vTemp2.val[1]), 1); + uint32_t CR = 0; + if (r == 0xFFFFFFFFU) + { + // All elements are greater or equal + CR = XM_CRMASK_CR6TRUE; + } + else if (!r) + { + // All elements are not greater or equal + CR = XM_CRMASK_CR6FALSE; + } + *pCR = CR; + return vreinterpretq_f32_u32(vResult); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vTemp = _mm_cmpge_ps(V1, V2); + uint32_t CR = 0; + int iTest = _mm_movemask_ps(vTemp); + if (iTest == 0xf) + { + CR = XM_CRMASK_CR6TRUE; + } + else if (!iTest) + { + // All elements are not greater + CR = XM_CRMASK_CR6FALSE; + } + *pCR = CR; + return vTemp; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorLess +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTORU32 Control = { { { + (V1.vector4_f32[0] < V2.vector4_f32[0]) ? 0xFFFFFFFF : 0, + (V1.vector4_f32[1] < V2.vector4_f32[1]) ? 0xFFFFFFFF : 0, + (V1.vector4_f32[2] < V2.vector4_f32[2]) ? 0xFFFFFFFF : 0, + (V1.vector4_f32[3] < V2.vector4_f32[3]) ? 0xFFFFFFFF : 0 + } } }; + return Control.v; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vreinterpretq_f32_u32(vcltq_f32(V1, V2)); +#elif defined(_XM_SSE_INTRINSICS_) + return _mm_cmplt_ps(V1, V2); +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorLessOrEqual +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTORU32 Control = { { { + (V1.vector4_f32[0] <= V2.vector4_f32[0]) ? 0xFFFFFFFF : 0, + (V1.vector4_f32[1] <= V2.vector4_f32[1]) ? 0xFFFFFFFF : 0, + (V1.vector4_f32[2] <= V2.vector4_f32[2]) ? 0xFFFFFFFF : 0, + (V1.vector4_f32[3] <= V2.vector4_f32[3]) ? 0xFFFFFFFF : 0 + } } }; + return Control.v; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vreinterpretq_f32_u32(vcleq_f32(V1, V2)); +#elif defined(_XM_SSE_INTRINSICS_) + return _mm_cmple_ps(V1, V2); +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorInBounds +( + FXMVECTOR V, + FXMVECTOR Bounds +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTORU32 Control = { { { + (V.vector4_f32[0] <= Bounds.vector4_f32[0] && V.vector4_f32[0] >= -Bounds.vector4_f32[0]) ? 0xFFFFFFFF : 0, + (V.vector4_f32[1] <= Bounds.vector4_f32[1] && V.vector4_f32[1] >= -Bounds.vector4_f32[1]) ? 0xFFFFFFFF : 0, + (V.vector4_f32[2] <= Bounds.vector4_f32[2] && V.vector4_f32[2] >= -Bounds.vector4_f32[2]) ? 0xFFFFFFFF : 0, + (V.vector4_f32[3] <= Bounds.vector4_f32[3] && V.vector4_f32[3] >= -Bounds.vector4_f32[3]) ? 0xFFFFFFFF : 0 + } } }; + return Control.v; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + // Test if less than or equal + uint32x4_t vTemp1 = vcleq_f32(V, Bounds); + // Negate the bounds + uint32x4_t vTemp2 = vreinterpretq_u32_f32(vnegq_f32(Bounds)); + // Test if greater or equal (Reversed) + vTemp2 = vcleq_f32(vreinterpretq_f32_u32(vTemp2), V); + // Blend answers + vTemp1 = vandq_u32(vTemp1, vTemp2); + return vreinterpretq_f32_u32(vTemp1); +#elif defined(_XM_SSE_INTRINSICS_) + // Test if less than or equal + XMVECTOR vTemp1 = _mm_cmple_ps(V, Bounds); + // Negate the bounds + XMVECTOR vTemp2 = _mm_mul_ps(Bounds, g_XMNegativeOne); + // Test if greater or equal (Reversed) + vTemp2 = _mm_cmple_ps(vTemp2, V); + // Blend answers + vTemp1 = _mm_and_ps(vTemp1, vTemp2); + return vTemp1; +#endif +} + +//------------------------------------------------------------------------------ + +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMVectorInBoundsR +( + uint32_t* pCR, + FXMVECTOR V, + FXMVECTOR Bounds +) noexcept +{ + assert(pCR != nullptr); +#if defined(_XM_NO_INTRINSICS_) + + uint32_t ux = (V.vector4_f32[0] <= Bounds.vector4_f32[0] && V.vector4_f32[0] >= -Bounds.vector4_f32[0]) ? 0xFFFFFFFFU : 0; + uint32_t uy = (V.vector4_f32[1] <= Bounds.vector4_f32[1] && V.vector4_f32[1] >= -Bounds.vector4_f32[1]) ? 0xFFFFFFFFU : 0; + uint32_t uz = (V.vector4_f32[2] <= Bounds.vector4_f32[2] && V.vector4_f32[2] >= -Bounds.vector4_f32[2]) ? 0xFFFFFFFFU : 0; + uint32_t uw = (V.vector4_f32[3] <= Bounds.vector4_f32[3] && V.vector4_f32[3] >= -Bounds.vector4_f32[3]) ? 0xFFFFFFFFU : 0; + + uint32_t CR = 0; + if (ux & uy & uz & uw) + { + // All elements are in bounds + CR = XM_CRMASK_CR6BOUNDS; + } + *pCR = CR; + + XMVECTORU32 Control = { { { ux, uy, uz, uw } } }; + return Control.v; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + // Test if less than or equal + uint32x4_t vTemp1 = vcleq_f32(V, Bounds); + // Negate the bounds + uint32x4_t vTemp2 = vreinterpretq_u32_f32(vnegq_f32(Bounds)); + // Test if greater or equal (Reversed) + vTemp2 = vcleq_f32(vreinterpretq_f32_u32(vTemp2), V); + // Blend answers + vTemp1 = vandq_u32(vTemp1, vTemp2); + uint8x8x2_t vTemp = vzip_u8(vget_low_u8(vreinterpretq_u8_u32(vTemp1)), vget_high_u8(vreinterpretq_u8_u32(vTemp1))); + uint16x4x2_t vTemp3 = vzip_u16(vreinterpret_u16_u8(vTemp.val[0]), vreinterpret_u16_u8(vTemp.val[1])); + uint32_t r = vget_lane_u32(vreinterpret_u32_u16(vTemp3.val[1]), 1); + uint32_t CR = 0; + if (r == 0xFFFFFFFFU) + { + // All elements are in bounds + CR = XM_CRMASK_CR6BOUNDS; + } + *pCR = CR; + return vreinterpretq_f32_u32(vTemp1); +#elif defined(_XM_SSE_INTRINSICS_) + // Test if less than or equal + XMVECTOR vTemp1 = _mm_cmple_ps(V, Bounds); + // Negate the bounds + XMVECTOR vTemp2 = _mm_mul_ps(Bounds, g_XMNegativeOne); + // Test if greater or equal (Reversed) + vTemp2 = _mm_cmple_ps(vTemp2, V); + // Blend answers + vTemp1 = _mm_and_ps(vTemp1, vTemp2); + + uint32_t CR = 0; + if (_mm_movemask_ps(vTemp1) == 0xf) + { + // All elements are in bounds + CR = XM_CRMASK_CR6BOUNDS; + } + *pCR = CR; + return vTemp1; +#endif +} + +//------------------------------------------------------------------------------ + +#if !defined(_XM_NO_INTRINSICS_) && defined(_MSC_VER) && !defined(__INTEL_COMPILER) +#pragma float_control(push) +#pragma float_control(precise, on) +#endif + +inline XMVECTOR XM_CALLCONV XMVectorIsNaN(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTORU32 Control = { { { + XMISNAN(V.vector4_f32[0]) ? 0xFFFFFFFFU : 0, + XMISNAN(V.vector4_f32[1]) ? 0xFFFFFFFFU : 0, + XMISNAN(V.vector4_f32[2]) ? 0xFFFFFFFFU : 0, + XMISNAN(V.vector4_f32[3]) ? 0xFFFFFFFFU : 0 + } } }; + return Control.v; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) +#if defined(__clang__) && defined(__FINITE_MATH_ONLY__) + XMVECTORU32 vResult = { { { + isnan(vgetq_lane_f32(V, 0)) ? 0xFFFFFFFFU : 0, + isnan(vgetq_lane_f32(V, 1)) ? 0xFFFFFFFFU : 0, + isnan(vgetq_lane_f32(V, 2)) ? 0xFFFFFFFFU : 0, + isnan(vgetq_lane_f32(V, 3)) ? 0xFFFFFFFFU : 0 } } }; + return vResult.v; +#else +// Test against itself. NaN is always not equal + uint32x4_t vTempNan = vceqq_f32(V, V); + // Flip results + return vreinterpretq_f32_u32(vmvnq_u32(vTempNan)); +#endif +#elif defined(_XM_SSE_INTRINSICS_) +#if defined(__clang__) && defined(__FINITE_MATH_ONLY__) + XM_ALIGNED_DATA(16) float tmp[4]; + _mm_store_ps(tmp, V); + XMVECTORU32 vResult = { { { + isnan(tmp[0]) ? 0xFFFFFFFFU : 0, + isnan(tmp[1]) ? 0xFFFFFFFFU : 0, + isnan(tmp[2]) ? 0xFFFFFFFFU : 0, + isnan(tmp[3]) ? 0xFFFFFFFFU : 0 } } }; + return vResult.v; +#else +// Test against itself. NaN is always not equal + return _mm_cmpneq_ps(V, V); +#endif +#endif +} + +#if !defined(_XM_NO_INTRINSICS_) && defined(_MSC_VER) && !defined(__INTEL_COMPILER) +#pragma float_control(pop) +#endif + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorIsInfinite(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTORU32 Control = { { { + XMISINF(V.vector4_f32[0]) ? 0xFFFFFFFFU : 0, + XMISINF(V.vector4_f32[1]) ? 0xFFFFFFFFU : 0, + XMISINF(V.vector4_f32[2]) ? 0xFFFFFFFFU : 0, + XMISINF(V.vector4_f32[3]) ? 0xFFFFFFFFU : 0 + } } }; + return Control.v; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + // Mask off the sign bit + uint32x4_t vTemp = vandq_u32(vreinterpretq_u32_f32(V), g_XMAbsMask); + // Compare to infinity + vTemp = vceqq_f32(vreinterpretq_f32_u32(vTemp), g_XMInfinity); + // If any are infinity, the signs are true. + return vreinterpretq_f32_u32(vTemp); +#elif defined(_XM_SSE_INTRINSICS_) + // Mask off the sign bit + __m128 vTemp = _mm_and_ps(V, g_XMAbsMask); + // Compare to infinity + vTemp = _mm_cmpeq_ps(vTemp, g_XMInfinity); + // If any are infinity, the signs are true. + return vTemp; +#endif +} + +//------------------------------------------------------------------------------ +// Rounding and clamping operations +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorMin +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTORF32 Result = { { { + (V1.vector4_f32[0] < V2.vector4_f32[0]) ? V1.vector4_f32[0] : V2.vector4_f32[0], + (V1.vector4_f32[1] < V2.vector4_f32[1]) ? V1.vector4_f32[1] : V2.vector4_f32[1], + (V1.vector4_f32[2] < V2.vector4_f32[2]) ? V1.vector4_f32[2] : V2.vector4_f32[2], + (V1.vector4_f32[3] < V2.vector4_f32[3]) ? V1.vector4_f32[3] : V2.vector4_f32[3] + } } }; + return Result.v; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vminq_f32(V1, V2); +#elif defined(_XM_SSE_INTRINSICS_) + return _mm_min_ps(V1, V2); +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorMax +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTORF32 Result = { { { + (V1.vector4_f32[0] > V2.vector4_f32[0]) ? V1.vector4_f32[0] : V2.vector4_f32[0], + (V1.vector4_f32[1] > V2.vector4_f32[1]) ? V1.vector4_f32[1] : V2.vector4_f32[1], + (V1.vector4_f32[2] > V2.vector4_f32[2]) ? V1.vector4_f32[2] : V2.vector4_f32[2], + (V1.vector4_f32[3] > V2.vector4_f32[3]) ? V1.vector4_f32[3] : V2.vector4_f32[3] + } } }; + return Result.v; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vmaxq_f32(V1, V2); +#elif defined(_XM_SSE_INTRINSICS_) + return _mm_max_ps(V1, V2); +#endif +} + +//------------------------------------------------------------------------------ + +namespace MathInternal +{ + // Round to nearest (even) a.k.a. banker's rounding + inline float round_to_nearest(float x) noexcept + { + float i = floorf(x); + x -= i; + if (x < 0.5f) + return i; + if (x > 0.5f) + return i + 1.f; + + float int_part; + (void)modff(i / 2.f, &int_part); + if ((2.f * int_part) == i) + { + return i; + } + + return i + 1.f; + } +} + +#if !defined(_XM_NO_INTRINSICS_) && defined(_MSC_VER) && !defined(__INTEL_COMPILER) +#pragma float_control(push) +#pragma float_control(precise, on) +#endif + +inline XMVECTOR XM_CALLCONV XMVectorRound(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTORF32 Result = { { { + MathInternal::round_to_nearest(V.vector4_f32[0]), + MathInternal::round_to_nearest(V.vector4_f32[1]), + MathInternal::round_to_nearest(V.vector4_f32[2]), + MathInternal::round_to_nearest(V.vector4_f32[3]) + } } }; + return Result.v; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) +#if defined(_M_ARM64) || defined(_M_HYBRID_X86_ARM64) || defined(_M_ARM64EC) || __aarch64__ + return vrndnq_f32(V); +#else + uint32x4_t sign = vandq_u32(vreinterpretq_u32_f32(V), g_XMNegativeZero); + float32x4_t sMagic = vreinterpretq_f32_u32(vorrq_u32(g_XMNoFraction, sign)); + float32x4_t R1 = vaddq_f32(V, sMagic); + R1 = vsubq_f32(R1, sMagic); + float32x4_t R2 = vabsq_f32(V); + uint32x4_t mask = vcleq_f32(R2, g_XMNoFraction); + return vbslq_f32(mask, R1, V); +#endif +#elif defined(_XM_SSE4_INTRINSICS_) + return _mm_round_ps(V, _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC); +#elif defined(_XM_SSE_INTRINSICS_) + __m128 sign = _mm_and_ps(V, g_XMNegativeZero); + __m128 sMagic = _mm_or_ps(g_XMNoFraction, sign); + __m128 R1 = _mm_add_ps(V, sMagic); + R1 = _mm_sub_ps(R1, sMagic); + __m128 R2 = _mm_and_ps(V, g_XMAbsMask); + __m128 mask = _mm_cmple_ps(R2, g_XMNoFraction); + R2 = _mm_andnot_ps(mask, V); + R1 = _mm_and_ps(R1, mask); + XMVECTOR vResult = _mm_xor_ps(R1, R2); + return vResult; +#endif +} + +#if !defined(_XM_NO_INTRINSICS_) && defined(_MSC_VER) && !defined(__INTEL_COMPILER) +#pragma float_control(pop) +#endif + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorTruncate(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + XMVECTOR Result; + uint32_t i; + + // Avoid C4701 + Result.vector4_f32[0] = 0.0f; + + for (i = 0; i < 4; i++) + { + if (XMISNAN(V.vector4_f32[i])) + { + Result.vector4_u32[i] = 0x7FC00000; + } + else if (fabsf(V.vector4_f32[i]) < 8388608.0f) + { + Result.vector4_f32[i] = static_cast(static_cast(V.vector4_f32[i])); + } + else + { + Result.vector4_f32[i] = V.vector4_f32[i]; + } + } + return Result; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) +#if defined(_M_ARM64) || defined(_M_HYBRID_X86_ARM64) || defined(_M_ARM64EC) || __aarch64__ + return vrndq_f32(V); +#else + float32x4_t vTest = vabsq_f32(V); + vTest = vreinterpretq_f32_u32(vcltq_f32(vTest, g_XMNoFraction)); + + int32x4_t vInt = vcvtq_s32_f32(V); + float32x4_t vResult = vcvtq_f32_s32(vInt); + + // All numbers less than 8388608 will use the round to int + // All others, use the ORIGINAL value + return vbslq_f32(vreinterpretq_u32_f32(vTest), vResult, V); +#endif +#elif defined(_XM_SSE4_INTRINSICS_) + return _mm_round_ps(V, _MM_FROUND_TO_ZERO | _MM_FROUND_NO_EXC); +#elif defined(_XM_SSE_INTRINSICS_) + // To handle NAN, INF and numbers greater than 8388608, use masking + // Get the abs value + __m128i vTest = _mm_and_si128(_mm_castps_si128(V), g_XMAbsMask); + // Test for greater than 8388608 (All floats with NO fractionals, NAN and INF + vTest = _mm_cmplt_epi32(vTest, g_XMNoFraction); + // Convert to int and back to float for rounding with truncation + __m128i vInt = _mm_cvttps_epi32(V); + // Convert back to floats + XMVECTOR vResult = _mm_cvtepi32_ps(vInt); + // All numbers less than 8388608 will use the round to int + vResult = _mm_and_ps(vResult, _mm_castsi128_ps(vTest)); + // All others, use the ORIGINAL value + vTest = _mm_andnot_si128(vTest, _mm_castps_si128(V)); + vResult = _mm_or_ps(vResult, _mm_castsi128_ps(vTest)); + return vResult; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorFloor(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 Result = { { { + floorf(V.vector4_f32[0]), + floorf(V.vector4_f32[1]), + floorf(V.vector4_f32[2]), + floorf(V.vector4_f32[3]) + } } }; + return Result.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) +#if defined(_M_ARM64) || defined(_M_HYBRID_X86_ARM64) || defined(_M_ARM64EC) || __aarch64__ + return vrndmq_f32(V); +#else + float32x4_t vTest = vabsq_f32(V); + vTest = vreinterpretq_f32_u32(vcltq_f32(vTest, g_XMNoFraction)); + // Truncate + int32x4_t vInt = vcvtq_s32_f32(V); + float32x4_t vResult = vcvtq_f32_s32(vInt); + uint32x4_t vLargerMask = vcgtq_f32(vResult, V); + // 0 -> 0, 0xffffffff -> -1.0f + float32x4_t vLarger = vcvtq_f32_s32(vreinterpretq_s32_u32(vLargerMask)); + vResult = vaddq_f32(vResult, vLarger); + // All numbers less than 8388608 will use the round to int + // All others, use the ORIGINAL value + return vbslq_f32(vreinterpretq_u32_f32(vTest), vResult, V); +#endif +#elif defined(_XM_SSE4_INTRINSICS_) + return _mm_floor_ps(V); +#elif defined(_XM_SSE_INTRINSICS_) + // To handle NAN, INF and numbers greater than 8388608, use masking + __m128i vTest = _mm_and_si128(_mm_castps_si128(V), g_XMAbsMask); + vTest = _mm_cmplt_epi32(vTest, g_XMNoFraction); + // Truncate + __m128i vInt = _mm_cvttps_epi32(V); + XMVECTOR vResult = _mm_cvtepi32_ps(vInt); + __m128 vLarger = _mm_cmpgt_ps(vResult, V); + // 0 -> 0, 0xffffffff -> -1.0f + vLarger = _mm_cvtepi32_ps(_mm_castps_si128(vLarger)); + vResult = _mm_add_ps(vResult, vLarger); + // All numbers less than 8388608 will use the round to int + vResult = _mm_and_ps(vResult, _mm_castsi128_ps(vTest)); + // All others, use the ORIGINAL value + vTest = _mm_andnot_si128(vTest, _mm_castps_si128(V)); + vResult = _mm_or_ps(vResult, _mm_castsi128_ps(vTest)); + return vResult; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorCeiling(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 Result = { { { + ceilf(V.vector4_f32[0]), + ceilf(V.vector4_f32[1]), + ceilf(V.vector4_f32[2]), + ceilf(V.vector4_f32[3]) + } } }; + return Result.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) +#if defined(_M_ARM64) || defined(_M_HYBRID_X86_ARM64) || defined(_M_ARM64EC) || __aarch64__ + return vrndpq_f32(V); +#else + float32x4_t vTest = vabsq_f32(V); + vTest = vreinterpretq_f32_u32(vcltq_f32(vTest, g_XMNoFraction)); + // Truncate + int32x4_t vInt = vcvtq_s32_f32(V); + float32x4_t vResult = vcvtq_f32_s32(vInt); + uint32x4_t vSmallerMask = vcltq_f32(vResult, V); + // 0 -> 0, 0xffffffff -> -1.0f + float32x4_t vSmaller = vcvtq_f32_s32(vreinterpretq_s32_u32(vSmallerMask)); + vResult = vsubq_f32(vResult, vSmaller); + // All numbers less than 8388608 will use the round to int + // All others, use the ORIGINAL value + return vbslq_f32(vreinterpretq_u32_f32(vTest), vResult, V); +#endif +#elif defined(_XM_SSE4_INTRINSICS_) + return _mm_ceil_ps(V); +#elif defined(_XM_SSE_INTRINSICS_) + // To handle NAN, INF and numbers greater than 8388608, use masking + __m128i vTest = _mm_and_si128(_mm_castps_si128(V), g_XMAbsMask); + vTest = _mm_cmplt_epi32(vTest, g_XMNoFraction); + // Truncate + __m128i vInt = _mm_cvttps_epi32(V); + XMVECTOR vResult = _mm_cvtepi32_ps(vInt); + __m128 vSmaller = _mm_cmplt_ps(vResult, V); + // 0 -> 0, 0xffffffff -> -1.0f + vSmaller = _mm_cvtepi32_ps(_mm_castps_si128(vSmaller)); + vResult = _mm_sub_ps(vResult, vSmaller); + // All numbers less than 8388608 will use the round to int + vResult = _mm_and_ps(vResult, _mm_castsi128_ps(vTest)); + // All others, use the ORIGINAL value + vTest = _mm_andnot_si128(vTest, _mm_castps_si128(V)); + vResult = _mm_or_ps(vResult, _mm_castsi128_ps(vTest)); + return vResult; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorClamp +( + FXMVECTOR V, + FXMVECTOR Min, + FXMVECTOR Max +) noexcept +{ + assert(XMVector4LessOrEqual(Min, Max)); + +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR Result; + Result = XMVectorMax(Min, V); + Result = XMVectorMin(Max, Result); + return Result; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x4_t vResult = vmaxq_f32(Min, V); + vResult = vminq_f32(Max, vResult); + return vResult; +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vResult; + vResult = _mm_max_ps(Min, V); + vResult = _mm_min_ps(Max, vResult); + return vResult; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorSaturate(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + const XMVECTOR Zero = XMVectorZero(); + + return XMVectorClamp(V, Zero, g_XMOne.v); + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + // Set <0 to 0 + float32x4_t vResult = vmaxq_f32(V, vdupq_n_f32(0)); + // Set>1 to 1 + return vminq_f32(vResult, vdupq_n_f32(1.0f)); +#elif defined(_XM_SSE_INTRINSICS_) + // Set <0 to 0 + XMVECTOR vResult = _mm_max_ps(V, g_XMZero); + // Set>1 to 1 + return _mm_min_ps(vResult, g_XMOne); +#endif +} + +//------------------------------------------------------------------------------ +// Bitwise logical operations +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorAndInt +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTORU32 Result = { { { + V1.vector4_u32[0] & V2.vector4_u32[0], + V1.vector4_u32[1] & V2.vector4_u32[1], + V1.vector4_u32[2] & V2.vector4_u32[2], + V1.vector4_u32[3] & V2.vector4_u32[3] + } } }; + return Result; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vreinterpretq_f32_u32(vandq_u32(vreinterpretq_u32_f32(V1), vreinterpretq_u32_f32(V2))); +#elif defined(_XM_SSE_INTRINSICS_) + return _mm_and_ps(V1, V2); +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorAndCInt +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTORU32 Result = { { { + V1.vector4_u32[0] & ~V2.vector4_u32[0], + V1.vector4_u32[1] & ~V2.vector4_u32[1], + V1.vector4_u32[2] & ~V2.vector4_u32[2], + V1.vector4_u32[3] & ~V2.vector4_u32[3] + } } }; + return Result.v; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vreinterpretq_f32_u32(vbicq_u32(vreinterpretq_u32_f32(V1), vreinterpretq_u32_f32(V2))); +#elif defined(_XM_SSE_INTRINSICS_) + __m128i V = _mm_andnot_si128(_mm_castps_si128(V2), _mm_castps_si128(V1)); + return _mm_castsi128_ps(V); +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorOrInt +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTORU32 Result = { { { + V1.vector4_u32[0] | V2.vector4_u32[0], + V1.vector4_u32[1] | V2.vector4_u32[1], + V1.vector4_u32[2] | V2.vector4_u32[2], + V1.vector4_u32[3] | V2.vector4_u32[3] + } } }; + return Result.v; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vreinterpretq_f32_u32(vorrq_u32(vreinterpretq_u32_f32(V1), vreinterpretq_u32_f32(V2))); +#elif defined(_XM_SSE_INTRINSICS_) + __m128i V = _mm_or_si128(_mm_castps_si128(V1), _mm_castps_si128(V2)); + return _mm_castsi128_ps(V); +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorNorInt +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTORU32 Result = { { { + ~(V1.vector4_u32[0] | V2.vector4_u32[0]), + ~(V1.vector4_u32[1] | V2.vector4_u32[1]), + ~(V1.vector4_u32[2] | V2.vector4_u32[2]), + ~(V1.vector4_u32[3] | V2.vector4_u32[3]) + } } }; + return Result.v; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x4_t Result = vorrq_u32(vreinterpretq_u32_f32(V1), vreinterpretq_u32_f32(V2)); + return vreinterpretq_f32_u32(vbicq_u32(g_XMNegOneMask, Result)); +#elif defined(_XM_SSE_INTRINSICS_) + __m128i Result; + Result = _mm_or_si128(_mm_castps_si128(V1), _mm_castps_si128(V2)); + Result = _mm_andnot_si128(Result, g_XMNegOneMask); + return _mm_castsi128_ps(Result); +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorXorInt +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTORU32 Result = { { { + V1.vector4_u32[0] ^ V2.vector4_u32[0], + V1.vector4_u32[1] ^ V2.vector4_u32[1], + V1.vector4_u32[2] ^ V2.vector4_u32[2], + V1.vector4_u32[3] ^ V2.vector4_u32[3] + } } }; + return Result.v; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vreinterpretq_f32_u32(veorq_u32(vreinterpretq_u32_f32(V1), vreinterpretq_u32_f32(V2))); +#elif defined(_XM_SSE_INTRINSICS_) + __m128i V = _mm_xor_si128(_mm_castps_si128(V1), _mm_castps_si128(V2)); + return _mm_castsi128_ps(V); +#endif +} + +//------------------------------------------------------------------------------ +// Computation operations +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorNegate(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTORF32 Result = { { { + -V.vector4_f32[0], + -V.vector4_f32[1], + -V.vector4_f32[2], + -V.vector4_f32[3] + } } }; + return Result.v; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vnegq_f32(V); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR Z; + + Z = _mm_setzero_ps(); + + return _mm_sub_ps(Z, V); +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorAdd +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTORF32 Result = { { { + V1.vector4_f32[0] + V2.vector4_f32[0], + V1.vector4_f32[1] + V2.vector4_f32[1], + V1.vector4_f32[2] + V2.vector4_f32[2], + V1.vector4_f32[3] + V2.vector4_f32[3] + } } }; + return Result.v; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vaddq_f32(V1, V2); +#elif defined(_XM_SSE_INTRINSICS_) + return _mm_add_ps(V1, V2); +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorSum(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTORF32 Result; + Result.f[0] = + Result.f[1] = + Result.f[2] = + Result.f[3] = V.vector4_f32[0] + V.vector4_f32[1] + V.vector4_f32[2] + V.vector4_f32[3]; + return Result.v; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) +#if defined(_M_ARM64) || defined(_M_HYBRID_X86_ARM64) || defined(_M_ARM64EC) || __aarch64__ + float32x4_t vTemp = vpaddq_f32(V, V); + return vpaddq_f32(vTemp, vTemp); +#else + float32x2_t v1 = vget_low_f32(V); + float32x2_t v2 = vget_high_f32(V); + v1 = vadd_f32(v1, v2); + v1 = vpadd_f32(v1, v1); + return vcombine_f32(v1, v1); +#endif +#elif defined(_XM_SSE3_INTRINSICS_) + XMVECTOR vTemp = _mm_hadd_ps(V, V); + return _mm_hadd_ps(vTemp, vTemp); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vTemp = XM_PERMUTE_PS(V, _MM_SHUFFLE(2, 3, 0, 1)); + XMVECTOR vTemp2 = _mm_add_ps(V, vTemp); + vTemp = XM_PERMUTE_PS(vTemp2, _MM_SHUFFLE(1, 0, 3, 2)); + return _mm_add_ps(vTemp, vTemp2); +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorAddAngles +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + const XMVECTOR Zero = XMVectorZero(); + + // Add the given angles together. If the range of V1 is such + // that -Pi <= V1 < Pi and the range of V2 is such that + // -2Pi <= V2 <= 2Pi, then the range of the resulting angle + // will be -Pi <= Result < Pi. + XMVECTOR Result = XMVectorAdd(V1, V2); + + XMVECTOR Mask = XMVectorLess(Result, g_XMNegativePi.v); + XMVECTOR Offset = XMVectorSelect(Zero, g_XMTwoPi.v, Mask); + + Mask = XMVectorGreaterOrEqual(Result, g_XMPi.v); + Offset = XMVectorSelect(Offset, g_XMNegativeTwoPi.v, Mask); + + Result = XMVectorAdd(Result, Offset); + + return Result; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + // Adjust the angles + float32x4_t vResult = vaddq_f32(V1, V2); + // Less than Pi? + uint32x4_t vOffset = vcltq_f32(vResult, g_XMNegativePi); + vOffset = vandq_u32(vOffset, g_XMTwoPi); + // Add 2Pi to all entries less than -Pi + vResult = vaddq_f32(vResult, vreinterpretq_f32_u32(vOffset)); + // Greater than or equal to Pi? + vOffset = vcgeq_f32(vResult, g_XMPi); + vOffset = vandq_u32(vOffset, g_XMTwoPi); + // Sub 2Pi to all entries greater than Pi + vResult = vsubq_f32(vResult, vreinterpretq_f32_u32(vOffset)); + return vResult; +#elif defined(_XM_SSE_INTRINSICS_) + // Adjust the angles + XMVECTOR vResult = _mm_add_ps(V1, V2); + // Less than Pi? + XMVECTOR vOffset = _mm_cmplt_ps(vResult, g_XMNegativePi); + vOffset = _mm_and_ps(vOffset, g_XMTwoPi); + // Add 2Pi to all entries less than -Pi + vResult = _mm_add_ps(vResult, vOffset); + // Greater than or equal to Pi? + vOffset = _mm_cmpge_ps(vResult, g_XMPi); + vOffset = _mm_and_ps(vOffset, g_XMTwoPi); + // Sub 2Pi to all entries greater than Pi + vResult = _mm_sub_ps(vResult, vOffset); + return vResult; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorSubtract +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTORF32 Result = { { { + V1.vector4_f32[0] - V2.vector4_f32[0], + V1.vector4_f32[1] - V2.vector4_f32[1], + V1.vector4_f32[2] - V2.vector4_f32[2], + V1.vector4_f32[3] - V2.vector4_f32[3] + } } }; + return Result.v; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vsubq_f32(V1, V2); +#elif defined(_XM_SSE_INTRINSICS_) + return _mm_sub_ps(V1, V2); +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorSubtractAngles +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + const XMVECTOR Zero = XMVectorZero(); + + // Subtract the given angles. If the range of V1 is such + // that -Pi <= V1 < Pi and the range of V2 is such that + // -2Pi <= V2 <= 2Pi, then the range of the resulting angle + // will be -Pi <= Result < Pi. + XMVECTOR Result = XMVectorSubtract(V1, V2); + + XMVECTOR Mask = XMVectorLess(Result, g_XMNegativePi.v); + XMVECTOR Offset = XMVectorSelect(Zero, g_XMTwoPi.v, Mask); + + Mask = XMVectorGreaterOrEqual(Result, g_XMPi.v); + Offset = XMVectorSelect(Offset, g_XMNegativeTwoPi.v, Mask); + + Result = XMVectorAdd(Result, Offset); + + return Result; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + // Adjust the angles + XMVECTOR vResult = vsubq_f32(V1, V2); + // Less than Pi? + uint32x4_t vOffset = vcltq_f32(vResult, g_XMNegativePi); + vOffset = vandq_u32(vOffset, g_XMTwoPi); + // Add 2Pi to all entries less than -Pi + vResult = vaddq_f32(vResult, vreinterpretq_f32_u32(vOffset)); + // Greater than or equal to Pi? + vOffset = vcgeq_f32(vResult, g_XMPi); + vOffset = vandq_u32(vOffset, g_XMTwoPi); + // Sub 2Pi to all entries greater than Pi + vResult = vsubq_f32(vResult, vreinterpretq_f32_u32(vOffset)); + return vResult; +#elif defined(_XM_SSE_INTRINSICS_) + // Adjust the angles + XMVECTOR vResult = _mm_sub_ps(V1, V2); + // Less than Pi? + XMVECTOR vOffset = _mm_cmplt_ps(vResult, g_XMNegativePi); + vOffset = _mm_and_ps(vOffset, g_XMTwoPi); + // Add 2Pi to all entries less than -Pi + vResult = _mm_add_ps(vResult, vOffset); + // Greater than or equal to Pi? + vOffset = _mm_cmpge_ps(vResult, g_XMPi); + vOffset = _mm_and_ps(vOffset, g_XMTwoPi); + // Sub 2Pi to all entries greater than Pi + vResult = _mm_sub_ps(vResult, vOffset); + return vResult; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorMultiply +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 Result = { { { + V1.vector4_f32[0] * V2.vector4_f32[0], + V1.vector4_f32[1] * V2.vector4_f32[1], + V1.vector4_f32[2] * V2.vector4_f32[2], + V1.vector4_f32[3] * V2.vector4_f32[3] + } } }; + return Result.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vmulq_f32(V1, V2); +#elif defined(_XM_SSE_INTRINSICS_) + return _mm_mul_ps(V1, V2); +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorMultiplyAdd +( + FXMVECTOR V1, + FXMVECTOR V2, + FXMVECTOR V3 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 Result = { { { + V1.vector4_f32[0] * V2.vector4_f32[0] + V3.vector4_f32[0], + V1.vector4_f32[1] * V2.vector4_f32[1] + V3.vector4_f32[1], + V1.vector4_f32[2] * V2.vector4_f32[2] + V3.vector4_f32[2], + V1.vector4_f32[3] * V2.vector4_f32[3] + V3.vector4_f32[3] + } } }; + return Result.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) +#if defined(_M_ARM64) || defined(_M_HYBRID_X86_ARM64) || defined(_M_ARM64EC) || __aarch64__ + return vfmaq_f32(V3, V1, V2); +#else + return vmlaq_f32(V3, V1, V2); +#endif +#elif defined(_XM_SSE_INTRINSICS_) + return XM_FMADD_PS(V1, V2, V3); +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorDivide +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 Result = { { { + V1.vector4_f32[0] / V2.vector4_f32[0], + V1.vector4_f32[1] / V2.vector4_f32[1], + V1.vector4_f32[2] / V2.vector4_f32[2], + V1.vector4_f32[3] / V2.vector4_f32[3] + } } }; + return Result.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) +#if defined(_M_ARM64) || defined(_M_HYBRID_X86_ARM64) || defined(_M_ARM64EC) || __aarch64__ + return vdivq_f32(V1, V2); +#else + // 2 iterations of Newton-Raphson refinement of reciprocal + float32x4_t Reciprocal = vrecpeq_f32(V2); + float32x4_t S = vrecpsq_f32(Reciprocal, V2); + Reciprocal = vmulq_f32(S, Reciprocal); + S = vrecpsq_f32(Reciprocal, V2); + Reciprocal = vmulq_f32(S, Reciprocal); + return vmulq_f32(V1, Reciprocal); +#endif +#elif defined(_XM_SSE_INTRINSICS_) + return _mm_div_ps(V1, V2); +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorNegativeMultiplySubtract +( + FXMVECTOR V1, + FXMVECTOR V2, + FXMVECTOR V3 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 Result = { { { + V3.vector4_f32[0] - (V1.vector4_f32[0] * V2.vector4_f32[0]), + V3.vector4_f32[1] - (V1.vector4_f32[1] * V2.vector4_f32[1]), + V3.vector4_f32[2] - (V1.vector4_f32[2] * V2.vector4_f32[2]), + V3.vector4_f32[3] - (V1.vector4_f32[3] * V2.vector4_f32[3]) + } } }; + return Result; +#elif defined(_XM_ARM_NEON_INTRINSICS_) +#if defined(_M_ARM64) || defined(_M_HYBRID_X86_ARM64) || defined(_M_ARM64EC) || __aarch64__ + return vfmsq_f32(V3, V1, V2); +#else + return vmlsq_f32(V3, V1, V2); +#endif +#elif defined(_XM_SSE_INTRINSICS_) + return XM_FNMADD_PS(V1, V2, V3); +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorScale +( + FXMVECTOR V, + float ScaleFactor +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 Result = { { { + V.vector4_f32[0] * ScaleFactor, + V.vector4_f32[1] * ScaleFactor, + V.vector4_f32[2] * ScaleFactor, + V.vector4_f32[3] * ScaleFactor + } } }; + return Result.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vmulq_n_f32(V, ScaleFactor); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vResult = _mm_set_ps1(ScaleFactor); + return _mm_mul_ps(vResult, V); +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorReciprocalEst(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 Result = { { { + 1.f / V.vector4_f32[0], + 1.f / V.vector4_f32[1], + 1.f / V.vector4_f32[2], + 1.f / V.vector4_f32[3] + } } }; + return Result.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vrecpeq_f32(V); +#elif defined(_XM_SSE_INTRINSICS_) + return _mm_rcp_ps(V); +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorReciprocal(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 Result = { { { + 1.f / V.vector4_f32[0], + 1.f / V.vector4_f32[1], + 1.f / V.vector4_f32[2], + 1.f / V.vector4_f32[3] + } } }; + return Result.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) +#if defined(_M_ARM64) || defined(_M_HYBRID_X86_ARM64) || defined(_M_ARM64EC) || __aarch64__ + float32x4_t one = vdupq_n_f32(1.0f); + return vdivq_f32(one, V); +#else + // 2 iterations of Newton-Raphson refinement + float32x4_t Reciprocal = vrecpeq_f32(V); + float32x4_t S = vrecpsq_f32(Reciprocal, V); + Reciprocal = vmulq_f32(S, Reciprocal); + S = vrecpsq_f32(Reciprocal, V); + return vmulq_f32(S, Reciprocal); +#endif +#elif defined(_XM_SSE_INTRINSICS_) + return _mm_div_ps(g_XMOne, V); +#endif +} + +//------------------------------------------------------------------------------ +// Return an estimated square root +inline XMVECTOR XM_CALLCONV XMVectorSqrtEst(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 Result = { { { + sqrtf(V.vector4_f32[0]), + sqrtf(V.vector4_f32[1]), + sqrtf(V.vector4_f32[2]), + sqrtf(V.vector4_f32[3]) + } } }; + return Result.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + // 1 iteration of Newton-Raphson refinment of sqrt + float32x4_t S0 = vrsqrteq_f32(V); + float32x4_t P0 = vmulq_f32(V, S0); + float32x4_t R0 = vrsqrtsq_f32(P0, S0); + float32x4_t S1 = vmulq_f32(S0, R0); + + XMVECTOR VEqualsInfinity = XMVectorEqualInt(V, g_XMInfinity.v); + XMVECTOR VEqualsZero = XMVectorEqual(V, vdupq_n_f32(0)); + XMVECTOR Result = vmulq_f32(V, S1); + XMVECTOR Select = XMVectorEqualInt(VEqualsInfinity, VEqualsZero); + return XMVectorSelect(V, Result, Select); +#elif defined(_XM_SSE_INTRINSICS_) + return _mm_sqrt_ps(V); +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorSqrt(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 Result = { { { + sqrtf(V.vector4_f32[0]), + sqrtf(V.vector4_f32[1]), + sqrtf(V.vector4_f32[2]), + sqrtf(V.vector4_f32[3]) + } } }; + return Result.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + // 3 iterations of Newton-Raphson refinment of sqrt + float32x4_t S0 = vrsqrteq_f32(V); + float32x4_t P0 = vmulq_f32(V, S0); + float32x4_t R0 = vrsqrtsq_f32(P0, S0); + float32x4_t S1 = vmulq_f32(S0, R0); + float32x4_t P1 = vmulq_f32(V, S1); + float32x4_t R1 = vrsqrtsq_f32(P1, S1); + float32x4_t S2 = vmulq_f32(S1, R1); + float32x4_t P2 = vmulq_f32(V, S2); + float32x4_t R2 = vrsqrtsq_f32(P2, S2); + float32x4_t S3 = vmulq_f32(S2, R2); + + XMVECTOR VEqualsInfinity = XMVectorEqualInt(V, g_XMInfinity.v); + XMVECTOR VEqualsZero = XMVectorEqual(V, vdupq_n_f32(0)); + XMVECTOR Result = vmulq_f32(V, S3); + XMVECTOR Select = XMVectorEqualInt(VEqualsInfinity, VEqualsZero); + return XMVectorSelect(V, Result, Select); +#elif defined(_XM_SSE_INTRINSICS_) + return _mm_sqrt_ps(V); +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorReciprocalSqrtEst(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 Result = { { { + 1.f / sqrtf(V.vector4_f32[0]), + 1.f / sqrtf(V.vector4_f32[1]), + 1.f / sqrtf(V.vector4_f32[2]), + 1.f / sqrtf(V.vector4_f32[3]) + } } }; + return Result.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vrsqrteq_f32(V); +#elif defined(_XM_SSE_INTRINSICS_) + return _mm_rsqrt_ps(V); +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorReciprocalSqrt(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 Result = { { { + 1.f / sqrtf(V.vector4_f32[0]), + 1.f / sqrtf(V.vector4_f32[1]), + 1.f / sqrtf(V.vector4_f32[2]), + 1.f / sqrtf(V.vector4_f32[3]) + } } }; + return Result; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + // 2 iterations of Newton-Raphson refinement of reciprocal + float32x4_t S0 = vrsqrteq_f32(V); + + float32x4_t P0 = vmulq_f32(V, S0); + float32x4_t R0 = vrsqrtsq_f32(P0, S0); + + float32x4_t S1 = vmulq_f32(S0, R0); + float32x4_t P1 = vmulq_f32(V, S1); + float32x4_t R1 = vrsqrtsq_f32(P1, S1); + + return vmulq_f32(S1, R1); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vResult = _mm_sqrt_ps(V); + vResult = _mm_div_ps(g_XMOne, vResult); + return vResult; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorExp2(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 Result = { { { + exp2f(V.vector4_f32[0]), + exp2f(V.vector4_f32[1]), + exp2f(V.vector4_f32[2]), + exp2f(V.vector4_f32[3]) + } } }; + return Result.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + int32x4_t itrunc = vcvtq_s32_f32(V); + float32x4_t ftrunc = vcvtq_f32_s32(itrunc); + float32x4_t y = vsubq_f32(V, ftrunc); + + float32x4_t poly = vmlaq_f32(g_XMExpEst6, g_XMExpEst7, y); + poly = vmlaq_f32(g_XMExpEst5, poly, y); + poly = vmlaq_f32(g_XMExpEst4, poly, y); + poly = vmlaq_f32(g_XMExpEst3, poly, y); + poly = vmlaq_f32(g_XMExpEst2, poly, y); + poly = vmlaq_f32(g_XMExpEst1, poly, y); + poly = vmlaq_f32(g_XMOne, poly, y); + + int32x4_t biased = vaddq_s32(itrunc, g_XMExponentBias); + biased = vshlq_n_s32(biased, 23); + float32x4_t result0 = XMVectorDivide(vreinterpretq_f32_s32(biased), poly); + + biased = vaddq_s32(itrunc, g_XM253); + biased = vshlq_n_s32(biased, 23); + float32x4_t result1 = XMVectorDivide(vreinterpretq_f32_s32(biased), poly); + result1 = vmulq_f32(g_XMMinNormal.v, result1); + + // Use selection to handle the cases + // if (V is NaN) -> QNaN; + // else if (V sign bit set) + // if (V > -150) + // if (V.exponent < -126) -> result1 + // else -> result0 + // else -> +0 + // else + // if (V < 128) -> result0 + // else -> +inf + + uint32x4_t comp = vcltq_s32(vreinterpretq_s32_f32(V), g_XMBin128); + float32x4_t result2 = vbslq_f32(comp, result0, g_XMInfinity); + + comp = vcltq_s32(itrunc, g_XMSubnormalExponent); + float32x4_t result3 = vbslq_f32(comp, result1, result0); + + comp = vcltq_s32(vreinterpretq_s32_f32(V), g_XMBinNeg150); + float32x4_t result4 = vbslq_f32(comp, result3, g_XMZero); + + int32x4_t sign = vandq_s32(vreinterpretq_s32_f32(V), g_XMNegativeZero); + comp = vceqq_s32(sign, g_XMNegativeZero); + float32x4_t result5 = vbslq_f32(comp, result4, result2); + + int32x4_t t0 = vandq_s32(vreinterpretq_s32_f32(V), g_XMQNaNTest); + int32x4_t t1 = vandq_s32(vreinterpretq_s32_f32(V), g_XMInfinity); + t0 = vreinterpretq_s32_u32(vceqq_s32(t0, g_XMZero)); + t1 = vreinterpretq_s32_u32(vceqq_s32(t1, g_XMInfinity)); + int32x4_t isNaN = vbicq_s32(t1, t0); + + float32x4_t vResult = vbslq_f32(vreinterpretq_u32_s32(isNaN), g_XMQNaN, result5); + return vResult; +#elif defined(_XM_SVML_INTRINSICS_) + XMVECTOR Result = _mm_exp2_ps(V); + return Result; +#elif defined(_XM_SSE_INTRINSICS_) + __m128i itrunc = _mm_cvttps_epi32(V); + __m128 ftrunc = _mm_cvtepi32_ps(itrunc); + __m128 y = _mm_sub_ps(V, ftrunc); + + __m128 poly = XM_FMADD_PS(g_XMExpEst7, y, g_XMExpEst6); + poly = XM_FMADD_PS(poly, y, g_XMExpEst5); + poly = XM_FMADD_PS(poly, y, g_XMExpEst4); + poly = XM_FMADD_PS(poly, y, g_XMExpEst3); + poly = XM_FMADD_PS(poly, y, g_XMExpEst2); + poly = XM_FMADD_PS(poly, y, g_XMExpEst1); + poly = XM_FMADD_PS(poly, y, g_XMOne); + + __m128i biased = _mm_add_epi32(itrunc, g_XMExponentBias); + biased = _mm_slli_epi32(biased, 23); + __m128 result0 = _mm_div_ps(_mm_castsi128_ps(biased), poly); + + biased = _mm_add_epi32(itrunc, g_XM253); + biased = _mm_slli_epi32(biased, 23); + __m128 result1 = _mm_div_ps(_mm_castsi128_ps(biased), poly); + result1 = _mm_mul_ps(g_XMMinNormal.v, result1); + + // Use selection to handle the cases + // if (V is NaN) -> QNaN; + // else if (V sign bit set) + // if (V > -150) + // if (V.exponent < -126) -> result1 + // else -> result0 + // else -> +0 + // else + // if (V < 128) -> result0 + // else -> +inf + + __m128i comp = _mm_cmplt_epi32(_mm_castps_si128(V), g_XMBin128); + __m128i select0 = _mm_and_si128(comp, _mm_castps_si128(result0)); + __m128i select1 = _mm_andnot_si128(comp, g_XMInfinity); + __m128i result2 = _mm_or_si128(select0, select1); + + comp = _mm_cmplt_epi32(itrunc, g_XMSubnormalExponent); + select1 = _mm_and_si128(comp, _mm_castps_si128(result1)); + select0 = _mm_andnot_si128(comp, _mm_castps_si128(result0)); + __m128i result3 = _mm_or_si128(select0, select1); + + comp = _mm_cmplt_epi32(_mm_castps_si128(V), g_XMBinNeg150); + select0 = _mm_and_si128(comp, result3); + select1 = _mm_andnot_si128(comp, g_XMZero); + __m128i result4 = _mm_or_si128(select0, select1); + + __m128i sign = _mm_and_si128(_mm_castps_si128(V), g_XMNegativeZero); + comp = _mm_cmpeq_epi32(sign, g_XMNegativeZero); + select0 = _mm_and_si128(comp, result4); + select1 = _mm_andnot_si128(comp, result2); + __m128i result5 = _mm_or_si128(select0, select1); + + __m128i t0 = _mm_and_si128(_mm_castps_si128(V), g_XMQNaNTest); + __m128i t1 = _mm_and_si128(_mm_castps_si128(V), g_XMInfinity); + t0 = _mm_cmpeq_epi32(t0, g_XMZero); + t1 = _mm_cmpeq_epi32(t1, g_XMInfinity); + __m128i isNaN = _mm_andnot_si128(t0, t1); + + select0 = _mm_and_si128(isNaN, g_XMQNaN); + select1 = _mm_andnot_si128(isNaN, result5); + __m128i vResult = _mm_or_si128(select0, select1); + + return _mm_castsi128_ps(vResult); +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorExp10(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTORF32 Result = { { { + powf(10.0f, V.vector4_f32[0]), + powf(10.0f, V.vector4_f32[1]), + powf(10.0f, V.vector4_f32[2]), + powf(10.0f, V.vector4_f32[3]) + } } }; + return Result.v; + +#elif defined(_XM_SVML_INTRINSICS_) + XMVECTOR Result = _mm_exp10_ps(V); + return Result; +#else + // exp10(V) = exp2(vin*log2(10)) + XMVECTOR Vten = XMVectorMultiply(g_XMLg10, V); + return XMVectorExp2(Vten); +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorExpE(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTORF32 Result = { { { + expf(V.vector4_f32[0]), + expf(V.vector4_f32[1]), + expf(V.vector4_f32[2]), + expf(V.vector4_f32[3]) + } } }; + return Result.v; + +#elif defined(_XM_SVML_INTRINSICS_) + XMVECTOR Result = _mm_exp_ps(V); + return Result; +#else + // expE(V) = exp2(vin*log2(e)) + XMVECTOR Ve = XMVectorMultiply(g_XMLgE, V); + return XMVectorExp2(Ve); +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorExp(FXMVECTOR V) noexcept +{ + return XMVectorExp2(V); +} + +//------------------------------------------------------------------------------ + +#if defined(_XM_SSE_INTRINSICS_) + +namespace MathInternal +{ + inline __m128i multi_sll_epi32(__m128i value, __m128i count) noexcept + { + __m128i v = _mm_shuffle_epi32(value, _MM_SHUFFLE(0, 0, 0, 0)); + __m128i c = _mm_shuffle_epi32(count, _MM_SHUFFLE(0, 0, 0, 0)); + c = _mm_and_si128(c, g_XMMaskX); + __m128i r0 = _mm_sll_epi32(v, c); + + v = _mm_shuffle_epi32(value, _MM_SHUFFLE(1, 1, 1, 1)); + c = _mm_shuffle_epi32(count, _MM_SHUFFLE(1, 1, 1, 1)); + c = _mm_and_si128(c, g_XMMaskX); + __m128i r1 = _mm_sll_epi32(v, c); + + v = _mm_shuffle_epi32(value, _MM_SHUFFLE(2, 2, 2, 2)); + c = _mm_shuffle_epi32(count, _MM_SHUFFLE(2, 2, 2, 2)); + c = _mm_and_si128(c, g_XMMaskX); + __m128i r2 = _mm_sll_epi32(v, c); + + v = _mm_shuffle_epi32(value, _MM_SHUFFLE(3, 3, 3, 3)); + c = _mm_shuffle_epi32(count, _MM_SHUFFLE(3, 3, 3, 3)); + c = _mm_and_si128(c, g_XMMaskX); + __m128i r3 = _mm_sll_epi32(v, c); + + // (r0,r0,r1,r1) + __m128 r01 = _mm_shuffle_ps(_mm_castsi128_ps(r0), _mm_castsi128_ps(r1), _MM_SHUFFLE(0, 0, 0, 0)); + // (r2,r2,r3,r3) + __m128 r23 = _mm_shuffle_ps(_mm_castsi128_ps(r2), _mm_castsi128_ps(r3), _MM_SHUFFLE(0, 0, 0, 0)); + // (r0,r1,r2,r3) + __m128 result = _mm_shuffle_ps(r01, r23, _MM_SHUFFLE(2, 0, 2, 0)); + return _mm_castps_si128(result); + } + + inline __m128i multi_srl_epi32(__m128i value, __m128i count) noexcept + { + __m128i v = _mm_shuffle_epi32(value, _MM_SHUFFLE(0, 0, 0, 0)); + __m128i c = _mm_shuffle_epi32(count, _MM_SHUFFLE(0, 0, 0, 0)); + c = _mm_and_si128(c, g_XMMaskX); + __m128i r0 = _mm_srl_epi32(v, c); + + v = _mm_shuffle_epi32(value, _MM_SHUFFLE(1, 1, 1, 1)); + c = _mm_shuffle_epi32(count, _MM_SHUFFLE(1, 1, 1, 1)); + c = _mm_and_si128(c, g_XMMaskX); + __m128i r1 = _mm_srl_epi32(v, c); + + v = _mm_shuffle_epi32(value, _MM_SHUFFLE(2, 2, 2, 2)); + c = _mm_shuffle_epi32(count, _MM_SHUFFLE(2, 2, 2, 2)); + c = _mm_and_si128(c, g_XMMaskX); + __m128i r2 = _mm_srl_epi32(v, c); + + v = _mm_shuffle_epi32(value, _MM_SHUFFLE(3, 3, 3, 3)); + c = _mm_shuffle_epi32(count, _MM_SHUFFLE(3, 3, 3, 3)); + c = _mm_and_si128(c, g_XMMaskX); + __m128i r3 = _mm_srl_epi32(v, c); + + // (r0,r0,r1,r1) + __m128 r01 = _mm_shuffle_ps(_mm_castsi128_ps(r0), _mm_castsi128_ps(r1), _MM_SHUFFLE(0, 0, 0, 0)); + // (r2,r2,r3,r3) + __m128 r23 = _mm_shuffle_ps(_mm_castsi128_ps(r2), _mm_castsi128_ps(r3), _MM_SHUFFLE(0, 0, 0, 0)); + // (r0,r1,r2,r3) + __m128 result = _mm_shuffle_ps(r01, r23, _MM_SHUFFLE(2, 0, 2, 0)); + return _mm_castps_si128(result); + } + + inline __m128i GetLeadingBit(const __m128i value) noexcept + { + static const XMVECTORI32 g_XM0000FFFF = { { { 0x0000FFFF, 0x0000FFFF, 0x0000FFFF, 0x0000FFFF } } }; + static const XMVECTORI32 g_XM000000FF = { { { 0x000000FF, 0x000000FF, 0x000000FF, 0x000000FF } } }; + static const XMVECTORI32 g_XM0000000F = { { { 0x0000000F, 0x0000000F, 0x0000000F, 0x0000000F } } }; + static const XMVECTORI32 g_XM00000003 = { { { 0x00000003, 0x00000003, 0x00000003, 0x00000003 } } }; + + __m128i v = value, r, c, b, s; + + c = _mm_cmpgt_epi32(v, g_XM0000FFFF); // c = (v > 0xFFFF) + b = _mm_srli_epi32(c, 31); // b = (c ? 1 : 0) + r = _mm_slli_epi32(b, 4); // r = (b << 4) + v = multi_srl_epi32(v, r); // v = (v >> r) + + c = _mm_cmpgt_epi32(v, g_XM000000FF); // c = (v > 0xFF) + b = _mm_srli_epi32(c, 31); // b = (c ? 1 : 0) + s = _mm_slli_epi32(b, 3); // s = (b << 3) + v = multi_srl_epi32(v, s); // v = (v >> s) + r = _mm_or_si128(r, s); // r = (r | s) + + c = _mm_cmpgt_epi32(v, g_XM0000000F); // c = (v > 0xF) + b = _mm_srli_epi32(c, 31); // b = (c ? 1 : 0) + s = _mm_slli_epi32(b, 2); // s = (b << 2) + v = multi_srl_epi32(v, s); // v = (v >> s) + r = _mm_or_si128(r, s); // r = (r | s) + + c = _mm_cmpgt_epi32(v, g_XM00000003); // c = (v > 0x3) + b = _mm_srli_epi32(c, 31); // b = (c ? 1 : 0) + s = _mm_slli_epi32(b, 1); // s = (b << 1) + v = multi_srl_epi32(v, s); // v = (v >> s) + r = _mm_or_si128(r, s); // r = (r | s) + + s = _mm_srli_epi32(v, 1); + r = _mm_or_si128(r, s); + return r; + } +} // namespace MathInternal + +#endif // _XM_SSE_INTRINSICS_ + +#if defined(_XM_ARM_NEON_INTRINSICS_) + +namespace MathInternal +{ + inline int32x4_t GetLeadingBit(const int32x4_t value) noexcept + { + static const XMVECTORI32 g_XM0000FFFF = { { { 0x0000FFFF, 0x0000FFFF, 0x0000FFFF, 0x0000FFFF } } }; + static const XMVECTORI32 g_XM000000FF = { { { 0x000000FF, 0x000000FF, 0x000000FF, 0x000000FF } } }; + static const XMVECTORI32 g_XM0000000F = { { { 0x0000000F, 0x0000000F, 0x0000000F, 0x0000000F } } }; + static const XMVECTORI32 g_XM00000003 = { { { 0x00000003, 0x00000003, 0x00000003, 0x00000003 } } }; + + uint32x4_t c = vcgtq_s32(value, g_XM0000FFFF); // c = (v > 0xFFFF) + int32x4_t b = vshrq_n_s32(vreinterpretq_s32_u32(c), 31); // b = (c ? 1 : 0) + int32x4_t r = vshlq_n_s32(b, 4); // r = (b << 4) + r = vnegq_s32(r); + int32x4_t v = vshlq_s32(value, r); // v = (v >> r) + + c = vcgtq_s32(v, g_XM000000FF); // c = (v > 0xFF) + b = vshrq_n_s32(vreinterpretq_s32_u32(c), 31); // b = (c ? 1 : 0) + int32x4_t s = vshlq_n_s32(b, 3); // s = (b << 3) + s = vnegq_s32(s); + v = vshlq_s32(v, s); // v = (v >> s) + r = vorrq_s32(r, s); // r = (r | s) + + c = vcgtq_s32(v, g_XM0000000F); // c = (v > 0xF) + b = vshrq_n_s32(vreinterpretq_s32_u32(c), 31); // b = (c ? 1 : 0) + s = vshlq_n_s32(b, 2); // s = (b << 2) + s = vnegq_s32(s); + v = vshlq_s32(v, s); // v = (v >> s) + r = vorrq_s32(r, s); // r = (r | s) + + c = vcgtq_s32(v, g_XM00000003); // c = (v > 0x3) + b = vshrq_n_s32(vreinterpretq_s32_u32(c), 31); // b = (c ? 1 : 0) + s = vshlq_n_s32(b, 1); // s = (b << 1) + s = vnegq_s32(s); + v = vshlq_s32(v, s); // v = (v >> s) + r = vorrq_s32(r, s); // r = (r | s) + + s = vshrq_n_s32(v, 1); + r = vorrq_s32(r, s); + return r; + } + +} // namespace MathInternal + +#endif + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorLog2(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 Result = { { { + log2f(V.vector4_f32[0]), + log2f(V.vector4_f32[1]), + log2f(V.vector4_f32[2]), + log2f(V.vector4_f32[3]) + } } }; + return Result.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + int32x4_t rawBiased = vandq_s32(vreinterpretq_s32_f32(V), g_XMInfinity); + int32x4_t trailing = vandq_s32(vreinterpretq_s32_f32(V), g_XMQNaNTest); + uint32x4_t isExponentZero = vceqq_s32(vreinterpretq_s32_f32(g_XMZero), rawBiased); + + // Compute exponent and significand for normals. + int32x4_t biased = vshrq_n_s32(rawBiased, 23); + int32x4_t exponentNor = vsubq_s32(biased, g_XMExponentBias); + int32x4_t trailingNor = trailing; + + // Compute exponent and significand for subnormals. + int32x4_t leading = MathInternal::GetLeadingBit(trailing); + int32x4_t shift = vsubq_s32(g_XMNumTrailing, leading); + int32x4_t exponentSub = vsubq_s32(g_XMSubnormalExponent, shift); + int32x4_t trailingSub = vshlq_s32(trailing, shift); + trailingSub = vandq_s32(trailingSub, g_XMQNaNTest); + int32x4_t e = vbslq_s32(isExponentZero, exponentSub, exponentNor); + int32x4_t t = vbslq_s32(isExponentZero, trailingSub, trailingNor); + + // Compute the approximation. + int32x4_t tmp = vorrq_s32(vreinterpretq_s32_f32(g_XMOne), t); + float32x4_t y = vsubq_f32(vreinterpretq_f32_s32(tmp), g_XMOne); + + float32x4_t log2 = vmlaq_f32(g_XMLogEst6, g_XMLogEst7, y); + log2 = vmlaq_f32(g_XMLogEst5, log2, y); + log2 = vmlaq_f32(g_XMLogEst4, log2, y); + log2 = vmlaq_f32(g_XMLogEst3, log2, y); + log2 = vmlaq_f32(g_XMLogEst2, log2, y); + log2 = vmlaq_f32(g_XMLogEst1, log2, y); + log2 = vmlaq_f32(g_XMLogEst0, log2, y); + log2 = vmlaq_f32(vcvtq_f32_s32(e), log2, y); + + // if (x is NaN) -> QNaN + // else if (V is positive) + // if (V is infinite) -> +inf + // else -> log2(V) + // else + // if (V is zero) -> -inf + // else -> -QNaN + + uint32x4_t isInfinite = vandq_u32(vreinterpretq_u32_f32(V), g_XMAbsMask); + isInfinite = vceqq_u32(isInfinite, g_XMInfinity); + + uint32x4_t isGreaterZero = vcgtq_f32(V, g_XMZero); + uint32x4_t isNotFinite = vcgtq_f32(V, g_XMInfinity); + uint32x4_t isPositive = vbicq_u32(isGreaterZero, isNotFinite); + + uint32x4_t isZero = vandq_u32(vreinterpretq_u32_f32(V), g_XMAbsMask); + isZero = vceqq_u32(isZero, g_XMZero); + + uint32x4_t t0 = vandq_u32(vreinterpretq_u32_f32(V), g_XMQNaNTest); + uint32x4_t t1 = vandq_u32(vreinterpretq_u32_f32(V), g_XMInfinity); + t0 = vceqq_u32(t0, g_XMZero); + t1 = vceqq_u32(t1, g_XMInfinity); + uint32x4_t isNaN = vbicq_u32(t1, t0); + + float32x4_t result = vbslq_f32(isInfinite, g_XMInfinity, log2); + float32x4_t tmp2 = vbslq_f32(isZero, g_XMNegInfinity, g_XMNegQNaN); + result = vbslq_f32(isPositive, result, tmp2); + result = vbslq_f32(isNaN, g_XMQNaN, result); + return result; +#elif defined(_XM_SVML_INTRINSICS_) + XMVECTOR Result = _mm_log2_ps(V); + return Result; +#elif defined(_XM_SSE_INTRINSICS_) + __m128i rawBiased = _mm_and_si128(_mm_castps_si128(V), g_XMInfinity); + __m128i trailing = _mm_and_si128(_mm_castps_si128(V), g_XMQNaNTest); + __m128i isExponentZero = _mm_cmpeq_epi32(g_XMZero, rawBiased); + + // Compute exponent and significand for normals. + __m128i biased = _mm_srli_epi32(rawBiased, 23); + __m128i exponentNor = _mm_sub_epi32(biased, g_XMExponentBias); + __m128i trailingNor = trailing; + + // Compute exponent and significand for subnormals. + __m128i leading = MathInternal::GetLeadingBit(trailing); + __m128i shift = _mm_sub_epi32(g_XMNumTrailing, leading); + __m128i exponentSub = _mm_sub_epi32(g_XMSubnormalExponent, shift); + __m128i trailingSub = MathInternal::multi_sll_epi32(trailing, shift); + trailingSub = _mm_and_si128(trailingSub, g_XMQNaNTest); + + __m128i select0 = _mm_and_si128(isExponentZero, exponentSub); + __m128i select1 = _mm_andnot_si128(isExponentZero, exponentNor); + __m128i e = _mm_or_si128(select0, select1); + + select0 = _mm_and_si128(isExponentZero, trailingSub); + select1 = _mm_andnot_si128(isExponentZero, trailingNor); + __m128i t = _mm_or_si128(select0, select1); + + // Compute the approximation. + __m128i tmp = _mm_or_si128(g_XMOne, t); + __m128 y = _mm_sub_ps(_mm_castsi128_ps(tmp), g_XMOne); + + __m128 log2 = XM_FMADD_PS(g_XMLogEst7, y, g_XMLogEst6); + log2 = XM_FMADD_PS(log2, y, g_XMLogEst5); + log2 = XM_FMADD_PS(log2, y, g_XMLogEst4); + log2 = XM_FMADD_PS(log2, y, g_XMLogEst3); + log2 = XM_FMADD_PS(log2, y, g_XMLogEst2); + log2 = XM_FMADD_PS(log2, y, g_XMLogEst1); + log2 = XM_FMADD_PS(log2, y, g_XMLogEst0); + log2 = XM_FMADD_PS(log2, y, _mm_cvtepi32_ps(e)); + + // if (x is NaN) -> QNaN + // else if (V is positive) + // if (V is infinite) -> +inf + // else -> log2(V) + // else + // if (V is zero) -> -inf + // else -> -QNaN + + __m128i isInfinite = _mm_and_si128(_mm_castps_si128(V), g_XMAbsMask); + isInfinite = _mm_cmpeq_epi32(isInfinite, g_XMInfinity); + + __m128i isGreaterZero = _mm_cmpgt_epi32(_mm_castps_si128(V), g_XMZero); + __m128i isNotFinite = _mm_cmpgt_epi32(_mm_castps_si128(V), g_XMInfinity); + __m128i isPositive = _mm_andnot_si128(isNotFinite, isGreaterZero); + + __m128i isZero = _mm_and_si128(_mm_castps_si128(V), g_XMAbsMask); + isZero = _mm_cmpeq_epi32(isZero, g_XMZero); + + __m128i t0 = _mm_and_si128(_mm_castps_si128(V), g_XMQNaNTest); + __m128i t1 = _mm_and_si128(_mm_castps_si128(V), g_XMInfinity); + t0 = _mm_cmpeq_epi32(t0, g_XMZero); + t1 = _mm_cmpeq_epi32(t1, g_XMInfinity); + __m128i isNaN = _mm_andnot_si128(t0, t1); + + select0 = _mm_and_si128(isInfinite, g_XMInfinity); + select1 = _mm_andnot_si128(isInfinite, _mm_castps_si128(log2)); + __m128i result = _mm_or_si128(select0, select1); + + select0 = _mm_and_si128(isZero, g_XMNegInfinity); + select1 = _mm_andnot_si128(isZero, g_XMNegQNaN); + tmp = _mm_or_si128(select0, select1); + + select0 = _mm_and_si128(isPositive, result); + select1 = _mm_andnot_si128(isPositive, tmp); + result = _mm_or_si128(select0, select1); + + select0 = _mm_and_si128(isNaN, g_XMQNaN); + select1 = _mm_andnot_si128(isNaN, result); + result = _mm_or_si128(select0, select1); + + return _mm_castsi128_ps(result); +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorLog10(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTORF32 Result = { { { + log10f(V.vector4_f32[0]), + log10f(V.vector4_f32[1]), + log10f(V.vector4_f32[2]), + log10f(V.vector4_f32[3]) + } } }; + return Result.v; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + int32x4_t rawBiased = vandq_s32(vreinterpretq_s32_f32(V), g_XMInfinity); + int32x4_t trailing = vandq_s32(vreinterpretq_s32_f32(V), g_XMQNaNTest); + uint32x4_t isExponentZero = vceqq_s32(g_XMZero, rawBiased); + + // Compute exponent and significand for normals. + int32x4_t biased = vshrq_n_s32(rawBiased, 23); + int32x4_t exponentNor = vsubq_s32(biased, g_XMExponentBias); + int32x4_t trailingNor = trailing; + + // Compute exponent and significand for subnormals. + int32x4_t leading = MathInternal::GetLeadingBit(trailing); + int32x4_t shift = vsubq_s32(g_XMNumTrailing, leading); + int32x4_t exponentSub = vsubq_s32(g_XMSubnormalExponent, shift); + int32x4_t trailingSub = vshlq_s32(trailing, shift); + trailingSub = vandq_s32(trailingSub, g_XMQNaNTest); + int32x4_t e = vbslq_s32(isExponentZero, exponentSub, exponentNor); + int32x4_t t = vbslq_s32(isExponentZero, trailingSub, trailingNor); + + // Compute the approximation. + int32x4_t tmp = vorrq_s32(g_XMOne, t); + float32x4_t y = vsubq_f32(vreinterpretq_f32_s32(tmp), g_XMOne); + + float32x4_t log2 = vmlaq_f32(g_XMLogEst6, g_XMLogEst7, y); + log2 = vmlaq_f32(g_XMLogEst5, log2, y); + log2 = vmlaq_f32(g_XMLogEst4, log2, y); + log2 = vmlaq_f32(g_XMLogEst3, log2, y); + log2 = vmlaq_f32(g_XMLogEst2, log2, y); + log2 = vmlaq_f32(g_XMLogEst1, log2, y); + log2 = vmlaq_f32(g_XMLogEst0, log2, y); + log2 = vmlaq_f32(vcvtq_f32_s32(e), log2, y); + + log2 = vmulq_f32(g_XMInvLg10, log2); + + // if (x is NaN) -> QNaN + // else if (V is positive) + // if (V is infinite) -> +inf + // else -> log2(V) + // else + // if (V is zero) -> -inf + // else -> -QNaN + + uint32x4_t isInfinite = vandq_u32(vreinterpretq_u32_f32(V), g_XMAbsMask); + isInfinite = vceqq_u32(isInfinite, g_XMInfinity); + + uint32x4_t isGreaterZero = vcgtq_s32(vreinterpretq_s32_f32(V), g_XMZero); + uint32x4_t isNotFinite = vcgtq_s32(vreinterpretq_s32_f32(V), g_XMInfinity); + uint32x4_t isPositive = vbicq_u32(isGreaterZero, isNotFinite); + + uint32x4_t isZero = vandq_u32(vreinterpretq_u32_f32(V), g_XMAbsMask); + isZero = vceqq_u32(isZero, g_XMZero); + + uint32x4_t t0 = vandq_u32(vreinterpretq_u32_f32(V), g_XMQNaNTest); + uint32x4_t t1 = vandq_u32(vreinterpretq_u32_f32(V), g_XMInfinity); + t0 = vceqq_u32(t0, g_XMZero); + t1 = vceqq_u32(t1, g_XMInfinity); + uint32x4_t isNaN = vbicq_u32(t1, t0); + + float32x4_t result = vbslq_f32(isInfinite, g_XMInfinity, log2); + float32x4_t tmp2 = vbslq_f32(isZero, g_XMNegInfinity, g_XMNegQNaN); + result = vbslq_f32(isPositive, result, tmp2); + result = vbslq_f32(isNaN, g_XMQNaN, result); + return result; +#elif defined(_XM_SVML_INTRINSICS_) + XMVECTOR Result = _mm_log10_ps(V); + return Result; +#elif defined(_XM_SSE_INTRINSICS_) + __m128i rawBiased = _mm_and_si128(_mm_castps_si128(V), g_XMInfinity); + __m128i trailing = _mm_and_si128(_mm_castps_si128(V), g_XMQNaNTest); + __m128i isExponentZero = _mm_cmpeq_epi32(g_XMZero, rawBiased); + + // Compute exponent and significand for normals. + __m128i biased = _mm_srli_epi32(rawBiased, 23); + __m128i exponentNor = _mm_sub_epi32(biased, g_XMExponentBias); + __m128i trailingNor = trailing; + + // Compute exponent and significand for subnormals. + __m128i leading = MathInternal::GetLeadingBit(trailing); + __m128i shift = _mm_sub_epi32(g_XMNumTrailing, leading); + __m128i exponentSub = _mm_sub_epi32(g_XMSubnormalExponent, shift); + __m128i trailingSub = MathInternal::multi_sll_epi32(trailing, shift); + trailingSub = _mm_and_si128(trailingSub, g_XMQNaNTest); + + __m128i select0 = _mm_and_si128(isExponentZero, exponentSub); + __m128i select1 = _mm_andnot_si128(isExponentZero, exponentNor); + __m128i e = _mm_or_si128(select0, select1); + + select0 = _mm_and_si128(isExponentZero, trailingSub); + select1 = _mm_andnot_si128(isExponentZero, trailingNor); + __m128i t = _mm_or_si128(select0, select1); + + // Compute the approximation. + __m128i tmp = _mm_or_si128(g_XMOne, t); + __m128 y = _mm_sub_ps(_mm_castsi128_ps(tmp), g_XMOne); + + __m128 log2 = XM_FMADD_PS(g_XMLogEst7, y, g_XMLogEst6); + log2 = XM_FMADD_PS(log2, y, g_XMLogEst5); + log2 = XM_FMADD_PS(log2, y, g_XMLogEst4); + log2 = XM_FMADD_PS(log2, y, g_XMLogEst3); + log2 = XM_FMADD_PS(log2, y, g_XMLogEst2); + log2 = XM_FMADD_PS(log2, y, g_XMLogEst1); + log2 = XM_FMADD_PS(log2, y, g_XMLogEst0); + log2 = XM_FMADD_PS(log2, y, _mm_cvtepi32_ps(e)); + + log2 = _mm_mul_ps(g_XMInvLg10, log2); + + // if (x is NaN) -> QNaN + // else if (V is positive) + // if (V is infinite) -> +inf + // else -> log2(V) + // else + // if (V is zero) -> -inf + // else -> -QNaN + + __m128i isInfinite = _mm_and_si128(_mm_castps_si128(V), g_XMAbsMask); + isInfinite = _mm_cmpeq_epi32(isInfinite, g_XMInfinity); + + __m128i isGreaterZero = _mm_cmpgt_epi32(_mm_castps_si128(V), g_XMZero); + __m128i isNotFinite = _mm_cmpgt_epi32(_mm_castps_si128(V), g_XMInfinity); + __m128i isPositive = _mm_andnot_si128(isNotFinite, isGreaterZero); + + __m128i isZero = _mm_and_si128(_mm_castps_si128(V), g_XMAbsMask); + isZero = _mm_cmpeq_epi32(isZero, g_XMZero); + + __m128i t0 = _mm_and_si128(_mm_castps_si128(V), g_XMQNaNTest); + __m128i t1 = _mm_and_si128(_mm_castps_si128(V), g_XMInfinity); + t0 = _mm_cmpeq_epi32(t0, g_XMZero); + t1 = _mm_cmpeq_epi32(t1, g_XMInfinity); + __m128i isNaN = _mm_andnot_si128(t0, t1); + + select0 = _mm_and_si128(isInfinite, g_XMInfinity); + select1 = _mm_andnot_si128(isInfinite, _mm_castps_si128(log2)); + __m128i result = _mm_or_si128(select0, select1); + + select0 = _mm_and_si128(isZero, g_XMNegInfinity); + select1 = _mm_andnot_si128(isZero, g_XMNegQNaN); + tmp = _mm_or_si128(select0, select1); + + select0 = _mm_and_si128(isPositive, result); + select1 = _mm_andnot_si128(isPositive, tmp); + result = _mm_or_si128(select0, select1); + + select0 = _mm_and_si128(isNaN, g_XMQNaN); + select1 = _mm_andnot_si128(isNaN, result); + result = _mm_or_si128(select0, select1); + + return _mm_castsi128_ps(result); +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorLogE(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTORF32 Result = { { { + logf(V.vector4_f32[0]), + logf(V.vector4_f32[1]), + logf(V.vector4_f32[2]), + logf(V.vector4_f32[3]) + } } }; + return Result.v; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + int32x4_t rawBiased = vandq_s32(vreinterpretq_s32_f32(V), g_XMInfinity); + int32x4_t trailing = vandq_s32(vreinterpretq_s32_f32(V), g_XMQNaNTest); + uint32x4_t isExponentZero = vceqq_s32(g_XMZero, rawBiased); + + // Compute exponent and significand for normals. + int32x4_t biased = vshrq_n_s32(rawBiased, 23); + int32x4_t exponentNor = vsubq_s32(biased, g_XMExponentBias); + int32x4_t trailingNor = trailing; + + // Compute exponent and significand for subnormals. + int32x4_t leading = MathInternal::GetLeadingBit(trailing); + int32x4_t shift = vsubq_s32(g_XMNumTrailing, leading); + int32x4_t exponentSub = vsubq_s32(g_XMSubnormalExponent, shift); + int32x4_t trailingSub = vshlq_s32(trailing, shift); + trailingSub = vandq_s32(trailingSub, g_XMQNaNTest); + int32x4_t e = vbslq_s32(isExponentZero, exponentSub, exponentNor); + int32x4_t t = vbslq_s32(isExponentZero, trailingSub, trailingNor); + + // Compute the approximation. + int32x4_t tmp = vorrq_s32(g_XMOne, t); + float32x4_t y = vsubq_f32(vreinterpretq_f32_s32(tmp), g_XMOne); + + float32x4_t log2 = vmlaq_f32(g_XMLogEst6, g_XMLogEst7, y); + log2 = vmlaq_f32(g_XMLogEst5, log2, y); + log2 = vmlaq_f32(g_XMLogEst4, log2, y); + log2 = vmlaq_f32(g_XMLogEst3, log2, y); + log2 = vmlaq_f32(g_XMLogEst2, log2, y); + log2 = vmlaq_f32(g_XMLogEst1, log2, y); + log2 = vmlaq_f32(g_XMLogEst0, log2, y); + log2 = vmlaq_f32(vcvtq_f32_s32(e), log2, y); + + log2 = vmulq_f32(g_XMInvLgE, log2); + + // if (x is NaN) -> QNaN + // else if (V is positive) + // if (V is infinite) -> +inf + // else -> log2(V) + // else + // if (V is zero) -> -inf + // else -> -QNaN + + uint32x4_t isInfinite = vandq_u32(vreinterpretq_u32_f32(V), g_XMAbsMask); + isInfinite = vceqq_u32(isInfinite, g_XMInfinity); + + uint32x4_t isGreaterZero = vcgtq_s32(vreinterpretq_s32_f32(V), g_XMZero); + uint32x4_t isNotFinite = vcgtq_s32(vreinterpretq_s32_f32(V), g_XMInfinity); + uint32x4_t isPositive = vbicq_u32(isGreaterZero, isNotFinite); + + uint32x4_t isZero = vandq_u32(vreinterpretq_u32_f32(V), g_XMAbsMask); + isZero = vceqq_u32(isZero, g_XMZero); + + uint32x4_t t0 = vandq_u32(vreinterpretq_u32_f32(V), g_XMQNaNTest); + uint32x4_t t1 = vandq_u32(vreinterpretq_u32_f32(V), g_XMInfinity); + t0 = vceqq_u32(t0, g_XMZero); + t1 = vceqq_u32(t1, g_XMInfinity); + uint32x4_t isNaN = vbicq_u32(t1, t0); + + float32x4_t result = vbslq_f32(isInfinite, g_XMInfinity, log2); + float32x4_t tmp2 = vbslq_f32(isZero, g_XMNegInfinity, g_XMNegQNaN); + result = vbslq_f32(isPositive, result, tmp2); + result = vbslq_f32(isNaN, g_XMQNaN, result); + return result; +#elif defined(_XM_SVML_INTRINSICS_) + XMVECTOR Result = _mm_log_ps(V); + return Result; +#elif defined(_XM_SSE_INTRINSICS_) + __m128i rawBiased = _mm_and_si128(_mm_castps_si128(V), g_XMInfinity); + __m128i trailing = _mm_and_si128(_mm_castps_si128(V), g_XMQNaNTest); + __m128i isExponentZero = _mm_cmpeq_epi32(g_XMZero, rawBiased); + + // Compute exponent and significand for normals. + __m128i biased = _mm_srli_epi32(rawBiased, 23); + __m128i exponentNor = _mm_sub_epi32(biased, g_XMExponentBias); + __m128i trailingNor = trailing; + + // Compute exponent and significand for subnormals. + __m128i leading = MathInternal::GetLeadingBit(trailing); + __m128i shift = _mm_sub_epi32(g_XMNumTrailing, leading); + __m128i exponentSub = _mm_sub_epi32(g_XMSubnormalExponent, shift); + __m128i trailingSub = MathInternal::multi_sll_epi32(trailing, shift); + trailingSub = _mm_and_si128(trailingSub, g_XMQNaNTest); + + __m128i select0 = _mm_and_si128(isExponentZero, exponentSub); + __m128i select1 = _mm_andnot_si128(isExponentZero, exponentNor); + __m128i e = _mm_or_si128(select0, select1); + + select0 = _mm_and_si128(isExponentZero, trailingSub); + select1 = _mm_andnot_si128(isExponentZero, trailingNor); + __m128i t = _mm_or_si128(select0, select1); + + // Compute the approximation. + __m128i tmp = _mm_or_si128(g_XMOne, t); + __m128 y = _mm_sub_ps(_mm_castsi128_ps(tmp), g_XMOne); + + __m128 log2 = XM_FMADD_PS(g_XMLogEst7, y, g_XMLogEst6); + log2 = XM_FMADD_PS(log2, y, g_XMLogEst5); + log2 = XM_FMADD_PS(log2, y, g_XMLogEst4); + log2 = XM_FMADD_PS(log2, y, g_XMLogEst3); + log2 = XM_FMADD_PS(log2, y, g_XMLogEst2); + log2 = XM_FMADD_PS(log2, y, g_XMLogEst1); + log2 = XM_FMADD_PS(log2, y, g_XMLogEst0); + log2 = XM_FMADD_PS(log2, y, _mm_cvtepi32_ps(e)); + + log2 = _mm_mul_ps(g_XMInvLgE, log2); + + // if (x is NaN) -> QNaN + // else if (V is positive) + // if (V is infinite) -> +inf + // else -> log2(V) + // else + // if (V is zero) -> -inf + // else -> -QNaN + + __m128i isInfinite = _mm_and_si128(_mm_castps_si128(V), g_XMAbsMask); + isInfinite = _mm_cmpeq_epi32(isInfinite, g_XMInfinity); + + __m128i isGreaterZero = _mm_cmpgt_epi32(_mm_castps_si128(V), g_XMZero); + __m128i isNotFinite = _mm_cmpgt_epi32(_mm_castps_si128(V), g_XMInfinity); + __m128i isPositive = _mm_andnot_si128(isNotFinite, isGreaterZero); + + __m128i isZero = _mm_and_si128(_mm_castps_si128(V), g_XMAbsMask); + isZero = _mm_cmpeq_epi32(isZero, g_XMZero); + + __m128i t0 = _mm_and_si128(_mm_castps_si128(V), g_XMQNaNTest); + __m128i t1 = _mm_and_si128(_mm_castps_si128(V), g_XMInfinity); + t0 = _mm_cmpeq_epi32(t0, g_XMZero); + t1 = _mm_cmpeq_epi32(t1, g_XMInfinity); + __m128i isNaN = _mm_andnot_si128(t0, t1); + + select0 = _mm_and_si128(isInfinite, g_XMInfinity); + select1 = _mm_andnot_si128(isInfinite, _mm_castps_si128(log2)); + __m128i result = _mm_or_si128(select0, select1); + + select0 = _mm_and_si128(isZero, g_XMNegInfinity); + select1 = _mm_andnot_si128(isZero, g_XMNegQNaN); + tmp = _mm_or_si128(select0, select1); + + select0 = _mm_and_si128(isPositive, result); + select1 = _mm_andnot_si128(isPositive, tmp); + result = _mm_or_si128(select0, select1); + + select0 = _mm_and_si128(isNaN, g_XMQNaN); + select1 = _mm_andnot_si128(isNaN, result); + result = _mm_or_si128(select0, select1); + + return _mm_castsi128_ps(result); +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorLog(FXMVECTOR V) noexcept +{ + return XMVectorLog2(V); +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorPow +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTORF32 Result = { { { + powf(V1.vector4_f32[0], V2.vector4_f32[0]), + powf(V1.vector4_f32[1], V2.vector4_f32[1]), + powf(V1.vector4_f32[2], V2.vector4_f32[2]), + powf(V1.vector4_f32[3], V2.vector4_f32[3]) + } } }; + return Result.v; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + XMVECTORF32 vResult = { { { + powf(vgetq_lane_f32(V1, 0), vgetq_lane_f32(V2, 0)), + powf(vgetq_lane_f32(V1, 1), vgetq_lane_f32(V2, 1)), + powf(vgetq_lane_f32(V1, 2), vgetq_lane_f32(V2, 2)), + powf(vgetq_lane_f32(V1, 3), vgetq_lane_f32(V2, 3)) + } } }; + return vResult.v; +#elif defined(_XM_SVML_INTRINSICS_) + XMVECTOR Result = _mm_pow_ps(V1, V2); + return Result; +#elif defined(_XM_SSE_INTRINSICS_) + XM_ALIGNED_DATA(16) float a[4]; + XM_ALIGNED_DATA(16) float b[4]; + _mm_store_ps(a, V1); + _mm_store_ps(b, V2); + XMVECTOR vResult = _mm_setr_ps( + powf(a[0], b[0]), + powf(a[1], b[1]), + powf(a[2], b[2]), + powf(a[3], b[3])); + return vResult; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorAbs(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 vResult = { { { + fabsf(V.vector4_f32[0]), + fabsf(V.vector4_f32[1]), + fabsf(V.vector4_f32[2]), + fabsf(V.vector4_f32[3]) + } } }; + return vResult.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + return vabsq_f32(V); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vResult = _mm_setzero_ps(); + vResult = _mm_sub_ps(vResult, V); + vResult = _mm_max_ps(vResult, V); + return vResult; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorMod +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ + // V1 % V2 = V1 - V2 * truncate(V1 / V2) + +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR Quotient = XMVectorDivide(V1, V2); + Quotient = XMVectorTruncate(Quotient); + XMVECTOR Result = XMVectorNegativeMultiplySubtract(V2, Quotient, V1); + return Result; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + XMVECTOR vResult = XMVectorDivide(V1, V2); + vResult = XMVectorTruncate(vResult); + return vmlsq_f32(V1, vResult, V2); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vResult = _mm_div_ps(V1, V2); + vResult = XMVectorTruncate(vResult); + return XM_FNMADD_PS(vResult, V2, V1); +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorModAngles(FXMVECTOR Angles) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR V; + XMVECTOR Result; + + // Modulo the range of the given angles such that -XM_PI <= Angles < XM_PI + V = XMVectorMultiply(Angles, g_XMReciprocalTwoPi.v); + V = XMVectorRound(V); + Result = XMVectorNegativeMultiplySubtract(g_XMTwoPi.v, V, Angles); + return Result; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + // Modulo the range of the given angles such that -XM_PI <= Angles < XM_PI + XMVECTOR vResult = vmulq_f32(Angles, g_XMReciprocalTwoPi); + // Use the inline function due to complexity for rounding + vResult = XMVectorRound(vResult); + return vmlsq_f32(Angles, vResult, g_XMTwoPi); +#elif defined(_XM_SSE_INTRINSICS_) + // Modulo the range of the given angles such that -XM_PI <= Angles < XM_PI + XMVECTOR vResult = _mm_mul_ps(Angles, g_XMReciprocalTwoPi); + // Use the inline function due to complexity for rounding + vResult = XMVectorRound(vResult); + return XM_FNMADD_PS(vResult, g_XMTwoPi, Angles); +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorSin(FXMVECTOR V) noexcept +{ + // 11-degree minimax approximation + +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 Result = { { { + sinf(V.vector4_f32[0]), + sinf(V.vector4_f32[1]), + sinf(V.vector4_f32[2]), + sinf(V.vector4_f32[3]) + } } }; + return Result.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + // Force the value within the bounds of pi + XMVECTOR x = XMVectorModAngles(V); + + // Map in [-pi/2,pi/2] with sin(y) = sin(x). + uint32x4_t sign = vandq_u32(vreinterpretq_u32_f32(x), g_XMNegativeZero); + uint32x4_t c = vorrq_u32(g_XMPi, sign); // pi when x >= 0, -pi when x < 0 + float32x4_t absx = vabsq_f32(x); + float32x4_t rflx = vsubq_f32(vreinterpretq_f32_u32(c), x); + uint32x4_t comp = vcleq_f32(absx, g_XMHalfPi); + x = vbslq_f32(comp, x, rflx); + + float32x4_t x2 = vmulq_f32(x, x); + + // Compute polynomial approximation + const XMVECTOR SC1 = g_XMSinCoefficients1; + const XMVECTOR SC0 = g_XMSinCoefficients0; + XMVECTOR vConstants = vdupq_lane_f32(vget_high_f32(SC0), 1); + XMVECTOR Result = vmlaq_lane_f32(vConstants, x2, vget_low_f32(SC1), 0); + + vConstants = vdupq_lane_f32(vget_high_f32(SC0), 0); + Result = vmlaq_f32(vConstants, Result, x2); + + vConstants = vdupq_lane_f32(vget_low_f32(SC0), 1); + Result = vmlaq_f32(vConstants, Result, x2); + + vConstants = vdupq_lane_f32(vget_low_f32(SC0), 0); + Result = vmlaq_f32(vConstants, Result, x2); + + Result = vmlaq_f32(g_XMOne, Result, x2); + Result = vmulq_f32(Result, x); + return Result; +#elif defined(_XM_SVML_INTRINSICS_) + XMVECTOR Result = _mm_sin_ps(V); + return Result; +#elif defined(_XM_SSE_INTRINSICS_) + // Force the value within the bounds of pi + XMVECTOR x = XMVectorModAngles(V); + + // Map in [-pi/2,pi/2] with sin(y) = sin(x). + __m128 sign = _mm_and_ps(x, g_XMNegativeZero); + __m128 c = _mm_or_ps(g_XMPi, sign); // pi when x >= 0, -pi when x < 0 + __m128 absx = _mm_andnot_ps(sign, x); // |x| + __m128 rflx = _mm_sub_ps(c, x); + __m128 comp = _mm_cmple_ps(absx, g_XMHalfPi); + __m128 select0 = _mm_and_ps(comp, x); + __m128 select1 = _mm_andnot_ps(comp, rflx); + x = _mm_or_ps(select0, select1); + + __m128 x2 = _mm_mul_ps(x, x); + + // Compute polynomial approximation + const XMVECTOR SC1 = g_XMSinCoefficients1; + __m128 vConstantsB = XM_PERMUTE_PS(SC1, _MM_SHUFFLE(0, 0, 0, 0)); + const XMVECTOR SC0 = g_XMSinCoefficients0; + __m128 vConstants = XM_PERMUTE_PS(SC0, _MM_SHUFFLE(3, 3, 3, 3)); + __m128 Result = XM_FMADD_PS(vConstantsB, x2, vConstants); + + vConstants = XM_PERMUTE_PS(SC0, _MM_SHUFFLE(2, 2, 2, 2)); + Result = XM_FMADD_PS(Result, x2, vConstants); + + vConstants = XM_PERMUTE_PS(SC0, _MM_SHUFFLE(1, 1, 1, 1)); + Result = XM_FMADD_PS(Result, x2, vConstants); + + vConstants = XM_PERMUTE_PS(SC0, _MM_SHUFFLE(0, 0, 0, 0)); + Result = XM_FMADD_PS(Result, x2, vConstants); + + Result = XM_FMADD_PS(Result, x2, g_XMOne); + Result = _mm_mul_ps(Result, x); + return Result; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorCos(FXMVECTOR V) noexcept +{ + // 10-degree minimax approximation + +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 Result = { { { + cosf(V.vector4_f32[0]), + cosf(V.vector4_f32[1]), + cosf(V.vector4_f32[2]), + cosf(V.vector4_f32[3]) + } } }; + return Result.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + // Map V to x in [-pi,pi]. + XMVECTOR x = XMVectorModAngles(V); + + // Map in [-pi/2,pi/2] with cos(y) = sign*cos(x). + uint32x4_t sign = vandq_u32(vreinterpretq_u32_f32(x), g_XMNegativeZero); + uint32x4_t c = vorrq_u32(g_XMPi, sign); // pi when x >= 0, -pi when x < 0 + float32x4_t absx = vabsq_f32(x); + float32x4_t rflx = vsubq_f32(vreinterpretq_f32_u32(c), x); + uint32x4_t comp = vcleq_f32(absx, g_XMHalfPi); + x = vbslq_f32(comp, x, rflx); + float32x4_t fsign = vbslq_f32(comp, g_XMOne, g_XMNegativeOne); + + float32x4_t x2 = vmulq_f32(x, x); + + // Compute polynomial approximation + const XMVECTOR CC1 = g_XMCosCoefficients1; + const XMVECTOR CC0 = g_XMCosCoefficients0; + XMVECTOR vConstants = vdupq_lane_f32(vget_high_f32(CC0), 1); + XMVECTOR Result = vmlaq_lane_f32(vConstants, x2, vget_low_f32(CC1), 0); + + vConstants = vdupq_lane_f32(vget_high_f32(CC0), 0); + Result = vmlaq_f32(vConstants, Result, x2); + + vConstants = vdupq_lane_f32(vget_low_f32(CC0), 1); + Result = vmlaq_f32(vConstants, Result, x2); + + vConstants = vdupq_lane_f32(vget_low_f32(CC0), 0); + Result = vmlaq_f32(vConstants, Result, x2); + + Result = vmlaq_f32(g_XMOne, Result, x2); + Result = vmulq_f32(Result, fsign); + return Result; +#elif defined(_XM_SVML_INTRINSICS_) + XMVECTOR Result = _mm_cos_ps(V); + return Result; +#elif defined(_XM_SSE_INTRINSICS_) + // Map V to x in [-pi,pi]. + XMVECTOR x = XMVectorModAngles(V); + + // Map in [-pi/2,pi/2] with cos(y) = sign*cos(x). + XMVECTOR sign = _mm_and_ps(x, g_XMNegativeZero); + __m128 c = _mm_or_ps(g_XMPi, sign); // pi when x >= 0, -pi when x < 0 + __m128 absx = _mm_andnot_ps(sign, x); // |x| + __m128 rflx = _mm_sub_ps(c, x); + __m128 comp = _mm_cmple_ps(absx, g_XMHalfPi); + __m128 select0 = _mm_and_ps(comp, x); + __m128 select1 = _mm_andnot_ps(comp, rflx); + x = _mm_or_ps(select0, select1); + select0 = _mm_and_ps(comp, g_XMOne); + select1 = _mm_andnot_ps(comp, g_XMNegativeOne); + sign = _mm_or_ps(select0, select1); + + __m128 x2 = _mm_mul_ps(x, x); + + // Compute polynomial approximation + const XMVECTOR CC1 = g_XMCosCoefficients1; + __m128 vConstantsB = XM_PERMUTE_PS(CC1, _MM_SHUFFLE(0, 0, 0, 0)); + const XMVECTOR CC0 = g_XMCosCoefficients0; + __m128 vConstants = XM_PERMUTE_PS(CC0, _MM_SHUFFLE(3, 3, 3, 3)); + __m128 Result = XM_FMADD_PS(vConstantsB, x2, vConstants); + + vConstants = XM_PERMUTE_PS(CC0, _MM_SHUFFLE(2, 2, 2, 2)); + Result = XM_FMADD_PS(Result, x2, vConstants); + + vConstants = XM_PERMUTE_PS(CC0, _MM_SHUFFLE(1, 1, 1, 1)); + Result = XM_FMADD_PS(Result, x2, vConstants); + + vConstants = XM_PERMUTE_PS(CC0, _MM_SHUFFLE(0, 0, 0, 0)); + Result = XM_FMADD_PS(Result, x2, vConstants); + + Result = XM_FMADD_PS(Result, x2, g_XMOne); + Result = _mm_mul_ps(Result, sign); + return Result; +#endif +} + +//------------------------------------------------------------------------------ + +_Use_decl_annotations_ +inline void XM_CALLCONV XMVectorSinCos +( + XMVECTOR* pSin, + XMVECTOR* pCos, + FXMVECTOR V +) noexcept +{ + assert(pSin != nullptr); + assert(pCos != nullptr); + + // 11/10-degree minimax approximation + +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 Sin = { { { + sinf(V.vector4_f32[0]), + sinf(V.vector4_f32[1]), + sinf(V.vector4_f32[2]), + sinf(V.vector4_f32[3]) + } } }; + + XMVECTORF32 Cos = { { { + cosf(V.vector4_f32[0]), + cosf(V.vector4_f32[1]), + cosf(V.vector4_f32[2]), + cosf(V.vector4_f32[3]) + } } }; + + *pSin = Sin.v; + *pCos = Cos.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + // Force the value within the bounds of pi + XMVECTOR x = XMVectorModAngles(V); + + // Map in [-pi/2,pi/2] with cos(y) = sign*cos(x). + uint32x4_t sign = vandq_u32(vreinterpretq_u32_f32(x), g_XMNegativeZero); + uint32x4_t c = vorrq_u32(g_XMPi, sign); // pi when x >= 0, -pi when x < 0 + float32x4_t absx = vabsq_f32(x); + float32x4_t rflx = vsubq_f32(vreinterpretq_f32_u32(c), x); + uint32x4_t comp = vcleq_f32(absx, g_XMHalfPi); + x = vbslq_f32(comp, x, rflx); + float32x4_t fsign = vbslq_f32(comp, g_XMOne, g_XMNegativeOne); + + float32x4_t x2 = vmulq_f32(x, x); + + // Compute polynomial approximation for sine + const XMVECTOR SC1 = g_XMSinCoefficients1; + const XMVECTOR SC0 = g_XMSinCoefficients0; + XMVECTOR vConstants = vdupq_lane_f32(vget_high_f32(SC0), 1); + XMVECTOR Result = vmlaq_lane_f32(vConstants, x2, vget_low_f32(SC1), 0); + + vConstants = vdupq_lane_f32(vget_high_f32(SC0), 0); + Result = vmlaq_f32(vConstants, Result, x2); + + vConstants = vdupq_lane_f32(vget_low_f32(SC0), 1); + Result = vmlaq_f32(vConstants, Result, x2); + + vConstants = vdupq_lane_f32(vget_low_f32(SC0), 0); + Result = vmlaq_f32(vConstants, Result, x2); + + Result = vmlaq_f32(g_XMOne, Result, x2); + *pSin = vmulq_f32(Result, x); + + // Compute polynomial approximation for cosine + const XMVECTOR CC1 = g_XMCosCoefficients1; + const XMVECTOR CC0 = g_XMCosCoefficients0; + vConstants = vdupq_lane_f32(vget_high_f32(CC0), 1); + Result = vmlaq_lane_f32(vConstants, x2, vget_low_f32(CC1), 0); + + vConstants = vdupq_lane_f32(vget_high_f32(CC0), 0); + Result = vmlaq_f32(vConstants, Result, x2); + + vConstants = vdupq_lane_f32(vget_low_f32(CC0), 1); + Result = vmlaq_f32(vConstants, Result, x2); + + vConstants = vdupq_lane_f32(vget_low_f32(CC0), 0); + Result = vmlaq_f32(vConstants, Result, x2); + + Result = vmlaq_f32(g_XMOne, Result, x2); + *pCos = vmulq_f32(Result, fsign); +#elif defined(_XM_SVML_INTRINSICS_) + *pSin = _mm_sincos_ps(pCos, V); +#elif defined(_XM_SSE_INTRINSICS_) + // Force the value within the bounds of pi + XMVECTOR x = XMVectorModAngles(V); + + // Map in [-pi/2,pi/2] with sin(y) = sin(x), cos(y) = sign*cos(x). + XMVECTOR sign = _mm_and_ps(x, g_XMNegativeZero); + __m128 c = _mm_or_ps(g_XMPi, sign); // pi when x >= 0, -pi when x < 0 + __m128 absx = _mm_andnot_ps(sign, x); // |x| + __m128 rflx = _mm_sub_ps(c, x); + __m128 comp = _mm_cmple_ps(absx, g_XMHalfPi); + __m128 select0 = _mm_and_ps(comp, x); + __m128 select1 = _mm_andnot_ps(comp, rflx); + x = _mm_or_ps(select0, select1); + select0 = _mm_and_ps(comp, g_XMOne); + select1 = _mm_andnot_ps(comp, g_XMNegativeOne); + sign = _mm_or_ps(select0, select1); + + __m128 x2 = _mm_mul_ps(x, x); + + // Compute polynomial approximation of sine + const XMVECTOR SC1 = g_XMSinCoefficients1; + __m128 vConstantsB = XM_PERMUTE_PS(SC1, _MM_SHUFFLE(0, 0, 0, 0)); + const XMVECTOR SC0 = g_XMSinCoefficients0; + __m128 vConstants = XM_PERMUTE_PS(SC0, _MM_SHUFFLE(3, 3, 3, 3)); + __m128 Result = XM_FMADD_PS(vConstantsB, x2, vConstants); + + vConstants = XM_PERMUTE_PS(SC0, _MM_SHUFFLE(2, 2, 2, 2)); + Result = XM_FMADD_PS(Result, x2, vConstants); + + vConstants = XM_PERMUTE_PS(SC0, _MM_SHUFFLE(1, 1, 1, 1)); + Result = XM_FMADD_PS(Result, x2, vConstants); + + vConstants = XM_PERMUTE_PS(SC0, _MM_SHUFFLE(0, 0, 0, 0)); + Result = XM_FMADD_PS(Result, x2, vConstants); + + Result = XM_FMADD_PS(Result, x2, g_XMOne); + Result = _mm_mul_ps(Result, x); + *pSin = Result; + + // Compute polynomial approximation of cosine + const XMVECTOR CC1 = g_XMCosCoefficients1; + vConstantsB = XM_PERMUTE_PS(CC1, _MM_SHUFFLE(0, 0, 0, 0)); + const XMVECTOR CC0 = g_XMCosCoefficients0; + vConstants = XM_PERMUTE_PS(CC0, _MM_SHUFFLE(3, 3, 3, 3)); + Result = XM_FMADD_PS(vConstantsB, x2, vConstants); + + vConstants = XM_PERMUTE_PS(CC0, _MM_SHUFFLE(2, 2, 2, 2)); + Result = XM_FMADD_PS(Result, x2, vConstants); + + vConstants = XM_PERMUTE_PS(CC0, _MM_SHUFFLE(1, 1, 1, 1)); + Result = XM_FMADD_PS(Result, x2, vConstants); + + vConstants = XM_PERMUTE_PS(CC0, _MM_SHUFFLE(0, 0, 0, 0)); + Result = XM_FMADD_PS(Result, x2, vConstants); + + Result = XM_FMADD_PS(Result, x2, g_XMOne); + Result = _mm_mul_ps(Result, sign); + *pCos = Result; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorTan(FXMVECTOR V) noexcept +{ + // Cody and Waite algorithm to compute tangent. + +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 Result = { { { + tanf(V.vector4_f32[0]), + tanf(V.vector4_f32[1]), + tanf(V.vector4_f32[2]), + tanf(V.vector4_f32[3]) + } } }; + return Result.v; +#elif defined(_XM_SVML_INTRINSICS_) + XMVECTOR Result = _mm_tan_ps(V); + return Result; +#elif defined(_XM_SSE_INTRINSICS_) || defined(_XM_ARM_NEON_INTRINSICS_) + + static const XMVECTORF32 TanCoefficients0 = { { { 1.0f, -4.667168334e-1f, 2.566383229e-2f, -3.118153191e-4f } } }; + static const XMVECTORF32 TanCoefficients1 = { { { 4.981943399e-7f, -1.333835001e-1f, 3.424887824e-3f, -1.786170734e-5f } } }; + static const XMVECTORF32 TanConstants = { { { 1.570796371f, 6.077100628e-11f, 0.000244140625f, 0.63661977228f /*2 / Pi*/ } } }; + static const XMVECTORU32 Mask = { { { 0x1, 0x1, 0x1, 0x1 } } }; + + XMVECTOR TwoDivPi = XMVectorSplatW(TanConstants.v); + + XMVECTOR Zero = XMVectorZero(); + + XMVECTOR C0 = XMVectorSplatX(TanConstants.v); + XMVECTOR C1 = XMVectorSplatY(TanConstants.v); + XMVECTOR Epsilon = XMVectorSplatZ(TanConstants.v); + + XMVECTOR VA = XMVectorMultiply(V, TwoDivPi); + + VA = XMVectorRound(VA); + + XMVECTOR VC = XMVectorNegativeMultiplySubtract(VA, C0, V); + + XMVECTOR VB = XMVectorAbs(VA); + + VC = XMVectorNegativeMultiplySubtract(VA, C1, VC); + +#if defined(_XM_ARM_NEON_INTRINSICS_) && !defined(_XM_NO_INTRINSICS_) + VB = vreinterpretq_f32_u32(vcvtq_u32_f32(VB)); +#elif defined(_XM_SSE_INTRINSICS_) && !defined(_XM_NO_INTRINSICS_) + reinterpret_cast<__m128i*>(&VB)[0] = _mm_cvttps_epi32(VB); +#else + for (size_t i = 0; i < 4; i++) + { + VB.vector4_u32[i] = static_cast(VB.vector4_f32[i]); + } +#endif + + XMVECTOR VC2 = XMVectorMultiply(VC, VC); + + XMVECTOR T7 = XMVectorSplatW(TanCoefficients1.v); + XMVECTOR T6 = XMVectorSplatZ(TanCoefficients1.v); + XMVECTOR T4 = XMVectorSplatX(TanCoefficients1.v); + XMVECTOR T3 = XMVectorSplatW(TanCoefficients0.v); + XMVECTOR T5 = XMVectorSplatY(TanCoefficients1.v); + XMVECTOR T2 = XMVectorSplatZ(TanCoefficients0.v); + XMVECTOR T1 = XMVectorSplatY(TanCoefficients0.v); + XMVECTOR T0 = XMVectorSplatX(TanCoefficients0.v); + + XMVECTOR VBIsEven = XMVectorAndInt(VB, Mask.v); + VBIsEven = XMVectorEqualInt(VBIsEven, Zero); + + XMVECTOR N = XMVectorMultiplyAdd(VC2, T7, T6); + XMVECTOR D = XMVectorMultiplyAdd(VC2, T4, T3); + N = XMVectorMultiplyAdd(VC2, N, T5); + D = XMVectorMultiplyAdd(VC2, D, T2); + N = XMVectorMultiply(VC2, N); + D = XMVectorMultiplyAdd(VC2, D, T1); + N = XMVectorMultiplyAdd(VC, N, VC); + XMVECTOR VCNearZero = XMVectorInBounds(VC, Epsilon); + D = XMVectorMultiplyAdd(VC2, D, T0); + + N = XMVectorSelect(N, VC, VCNearZero); + D = XMVectorSelect(D, g_XMOne.v, VCNearZero); + + XMVECTOR R0 = XMVectorNegate(N); + XMVECTOR R1 = XMVectorDivide(N, D); + R0 = XMVectorDivide(D, R0); + + XMVECTOR VIsZero = XMVectorEqual(V, Zero); + + XMVECTOR Result = XMVectorSelect(R0, R1, VBIsEven); + + Result = XMVectorSelect(Result, Zero, VIsZero); + + return Result; + +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorSinH(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 Result = { { { + sinhf(V.vector4_f32[0]), + sinhf(V.vector4_f32[1]), + sinhf(V.vector4_f32[2]), + sinhf(V.vector4_f32[3]) + } } }; + return Result.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + static const XMVECTORF32 Scale = { { { 1.442695040888963f, 1.442695040888963f, 1.442695040888963f, 1.442695040888963f } } }; // 1.0f / ln(2.0f) + + XMVECTOR V1 = vmlaq_f32(g_XMNegativeOne.v, V, Scale.v); + XMVECTOR V2 = vmlsq_f32(g_XMNegativeOne.v, V, Scale.v); + XMVECTOR E1 = XMVectorExp(V1); + XMVECTOR E2 = XMVectorExp(V2); + + return vsubq_f32(E1, E2); +#elif defined(_XM_SVML_INTRINSICS_) + XMVECTOR Result = _mm_sinh_ps(V); + return Result; +#elif defined(_XM_SSE_INTRINSICS_) + static const XMVECTORF32 Scale = { { { 1.442695040888963f, 1.442695040888963f, 1.442695040888963f, 1.442695040888963f } } }; // 1.0f / ln(2.0f) + + XMVECTOR V1 = XM_FMADD_PS(V, Scale, g_XMNegativeOne); + XMVECTOR V2 = XM_FNMADD_PS(V, Scale, g_XMNegativeOne); + XMVECTOR E1 = XMVectorExp(V1); + XMVECTOR E2 = XMVectorExp(V2); + + return _mm_sub_ps(E1, E2); +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorCosH(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 Result = { { { + coshf(V.vector4_f32[0]), + coshf(V.vector4_f32[1]), + coshf(V.vector4_f32[2]), + coshf(V.vector4_f32[3]) + } } }; + return Result.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + static const XMVECTORF32 Scale = { { { 1.442695040888963f, 1.442695040888963f, 1.442695040888963f, 1.442695040888963f } } }; // 1.0f / ln(2.0f) + + XMVECTOR V1 = vmlaq_f32(g_XMNegativeOne.v, V, Scale.v); + XMVECTOR V2 = vmlsq_f32(g_XMNegativeOne.v, V, Scale.v); + XMVECTOR E1 = XMVectorExp(V1); + XMVECTOR E2 = XMVectorExp(V2); + return vaddq_f32(E1, E2); +#elif defined(_XM_SVML_INTRINSICS_) + XMVECTOR Result = _mm_cosh_ps(V); + return Result; +#elif defined(_XM_SSE_INTRINSICS_) + static const XMVECTORF32 Scale = { { { 1.442695040888963f, 1.442695040888963f, 1.442695040888963f, 1.442695040888963f } } }; // 1.0f / ln(2.0f) + + XMVECTOR V1 = XM_FMADD_PS(V, Scale.v, g_XMNegativeOne.v); + XMVECTOR V2 = XM_FNMADD_PS(V, Scale.v, g_XMNegativeOne.v); + XMVECTOR E1 = XMVectorExp(V1); + XMVECTOR E2 = XMVectorExp(V2); + return _mm_add_ps(E1, E2); +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorTanH(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 Result = { { { + tanhf(V.vector4_f32[0]), + tanhf(V.vector4_f32[1]), + tanhf(V.vector4_f32[2]), + tanhf(V.vector4_f32[3]) + } } }; + return Result.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + static const XMVECTORF32 Scale = { { { 2.8853900817779268f, 2.8853900817779268f, 2.8853900817779268f, 2.8853900817779268f } } }; // 2.0f / ln(2.0f) + + XMVECTOR E = vmulq_f32(V, Scale.v); + E = XMVectorExp(E); + E = vmlaq_f32(g_XMOneHalf.v, E, g_XMOneHalf.v); + E = XMVectorReciprocal(E); + return vsubq_f32(g_XMOne.v, E); +#elif defined(_XM_SVML_INTRINSICS_) + XMVECTOR Result = _mm_tanh_ps(V); + return Result; +#elif defined(_XM_SSE_INTRINSICS_) + static const XMVECTORF32 Scale = { { { 2.8853900817779268f, 2.8853900817779268f, 2.8853900817779268f, 2.8853900817779268f } } }; // 2.0f / ln(2.0f) + + XMVECTOR E = _mm_mul_ps(V, Scale.v); + E = XMVectorExp(E); + E = XM_FMADD_PS(E, g_XMOneHalf.v, g_XMOneHalf.v); + E = _mm_div_ps(g_XMOne.v, E); + return _mm_sub_ps(g_XMOne.v, E); +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorASin(FXMVECTOR V) noexcept +{ + // 7-degree minimax approximation + +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 Result = { { { + asinf(V.vector4_f32[0]), + asinf(V.vector4_f32[1]), + asinf(V.vector4_f32[2]), + asinf(V.vector4_f32[3]) + } } }; + return Result.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x4_t nonnegative = vcgeq_f32(V, g_XMZero); + float32x4_t x = vabsq_f32(V); + + // Compute (1-|V|), clamp to zero to avoid sqrt of negative number. + float32x4_t oneMValue = vsubq_f32(g_XMOne, x); + float32x4_t clampOneMValue = vmaxq_f32(g_XMZero, oneMValue); + float32x4_t root = XMVectorSqrt(clampOneMValue); + + // Compute polynomial approximation + const XMVECTOR AC1 = g_XMArcCoefficients1; + XMVECTOR vConstants = vdupq_lane_f32(vget_high_f32(AC1), 0); + XMVECTOR t0 = vmlaq_lane_f32(vConstants, x, vget_high_f32(AC1), 1); + + vConstants = vdupq_lane_f32(vget_low_f32(AC1), 1); + t0 = vmlaq_f32(vConstants, t0, x); + + vConstants = vdupq_lane_f32(vget_low_f32(AC1), 0); + t0 = vmlaq_f32(vConstants, t0, x); + + const XMVECTOR AC0 = g_XMArcCoefficients0; + vConstants = vdupq_lane_f32(vget_high_f32(AC0), 1); + t0 = vmlaq_f32(vConstants, t0, x); + + vConstants = vdupq_lane_f32(vget_high_f32(AC0), 0); + t0 = vmlaq_f32(vConstants, t0, x); + + vConstants = vdupq_lane_f32(vget_low_f32(AC0), 1); + t0 = vmlaq_f32(vConstants, t0, x); + + vConstants = vdupq_lane_f32(vget_low_f32(AC0), 0); + t0 = vmlaq_f32(vConstants, t0, x); + t0 = vmulq_f32(t0, root); + + float32x4_t t1 = vsubq_f32(g_XMPi, t0); + t0 = vbslq_f32(nonnegative, t0, t1); + t0 = vsubq_f32(g_XMHalfPi, t0); + return t0; +#elif defined(_XM_SVML_INTRINSICS_) + XMVECTOR Result = _mm_asin_ps(V); + return Result; +#elif defined(_XM_SSE_INTRINSICS_) + __m128 nonnegative = _mm_cmpge_ps(V, g_XMZero); + __m128 mvalue = _mm_sub_ps(g_XMZero, V); + __m128 x = _mm_max_ps(V, mvalue); // |V| + + // Compute (1-|V|), clamp to zero to avoid sqrt of negative number. + __m128 oneMValue = _mm_sub_ps(g_XMOne, x); + __m128 clampOneMValue = _mm_max_ps(g_XMZero, oneMValue); + __m128 root = _mm_sqrt_ps(clampOneMValue); // sqrt(1-|V|) + + // Compute polynomial approximation + const XMVECTOR AC1 = g_XMArcCoefficients1; + __m128 vConstantsB = XM_PERMUTE_PS(AC1, _MM_SHUFFLE(3, 3, 3, 3)); + __m128 vConstants = XM_PERMUTE_PS(AC1, _MM_SHUFFLE(2, 2, 2, 2)); + __m128 t0 = XM_FMADD_PS(vConstantsB, x, vConstants); + + vConstants = XM_PERMUTE_PS(AC1, _MM_SHUFFLE(1, 1, 1, 1)); + t0 = XM_FMADD_PS(t0, x, vConstants); + + vConstants = XM_PERMUTE_PS(AC1, _MM_SHUFFLE(0, 0, 0, 0)); + t0 = XM_FMADD_PS(t0, x, vConstants); + + const XMVECTOR AC0 = g_XMArcCoefficients0; + vConstants = XM_PERMUTE_PS(AC0, _MM_SHUFFLE(3, 3, 3, 3)); + t0 = XM_FMADD_PS(t0, x, vConstants); + + vConstants = XM_PERMUTE_PS(AC0, _MM_SHUFFLE(2, 2, 2, 2)); + t0 = XM_FMADD_PS(t0, x, vConstants); + + vConstants = XM_PERMUTE_PS(AC0, _MM_SHUFFLE(1, 1, 1, 1)); + t0 = XM_FMADD_PS(t0, x, vConstants); + + vConstants = XM_PERMUTE_PS(AC0, _MM_SHUFFLE(0, 0, 0, 0)); + t0 = XM_FMADD_PS(t0, x, vConstants); + t0 = _mm_mul_ps(t0, root); + + __m128 t1 = _mm_sub_ps(g_XMPi, t0); + t0 = _mm_and_ps(nonnegative, t0); + t1 = _mm_andnot_ps(nonnegative, t1); + t0 = _mm_or_ps(t0, t1); + t0 = _mm_sub_ps(g_XMHalfPi, t0); + return t0; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorACos(FXMVECTOR V) noexcept +{ + // 7-degree minimax approximation + +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 Result = { { { + acosf(V.vector4_f32[0]), + acosf(V.vector4_f32[1]), + acosf(V.vector4_f32[2]), + acosf(V.vector4_f32[3]) + } } }; + return Result.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x4_t nonnegative = vcgeq_f32(V, g_XMZero); + float32x4_t x = vabsq_f32(V); + + // Compute (1-|V|), clamp to zero to avoid sqrt of negative number. + float32x4_t oneMValue = vsubq_f32(g_XMOne, x); + float32x4_t clampOneMValue = vmaxq_f32(g_XMZero, oneMValue); + float32x4_t root = XMVectorSqrt(clampOneMValue); + + // Compute polynomial approximation + const XMVECTOR AC1 = g_XMArcCoefficients1; + XMVECTOR vConstants = vdupq_lane_f32(vget_high_f32(AC1), 0); + XMVECTOR t0 = vmlaq_lane_f32(vConstants, x, vget_high_f32(AC1), 1); + + vConstants = vdupq_lane_f32(vget_low_f32(AC1), 1); + t0 = vmlaq_f32(vConstants, t0, x); + + vConstants = vdupq_lane_f32(vget_low_f32(AC1), 0); + t0 = vmlaq_f32(vConstants, t0, x); + + const XMVECTOR AC0 = g_XMArcCoefficients0; + vConstants = vdupq_lane_f32(vget_high_f32(AC0), 1); + t0 = vmlaq_f32(vConstants, t0, x); + + vConstants = vdupq_lane_f32(vget_high_f32(AC0), 0); + t0 = vmlaq_f32(vConstants, t0, x); + + vConstants = vdupq_lane_f32(vget_low_f32(AC0), 1); + t0 = vmlaq_f32(vConstants, t0, x); + + vConstants = vdupq_lane_f32(vget_low_f32(AC0), 0); + t0 = vmlaq_f32(vConstants, t0, x); + t0 = vmulq_f32(t0, root); + + float32x4_t t1 = vsubq_f32(g_XMPi, t0); + t0 = vbslq_f32(nonnegative, t0, t1); + return t0; +#elif defined(_XM_SVML_INTRINSICS_) + XMVECTOR Result = _mm_acos_ps(V); + return Result; +#elif defined(_XM_SSE_INTRINSICS_) + __m128 nonnegative = _mm_cmpge_ps(V, g_XMZero); + __m128 mvalue = _mm_sub_ps(g_XMZero, V); + __m128 x = _mm_max_ps(V, mvalue); // |V| + + // Compute (1-|V|), clamp to zero to avoid sqrt of negative number. + __m128 oneMValue = _mm_sub_ps(g_XMOne, x); + __m128 clampOneMValue = _mm_max_ps(g_XMZero, oneMValue); + __m128 root = _mm_sqrt_ps(clampOneMValue); // sqrt(1-|V|) + + // Compute polynomial approximation + const XMVECTOR AC1 = g_XMArcCoefficients1; + __m128 vConstantsB = XM_PERMUTE_PS(AC1, _MM_SHUFFLE(3, 3, 3, 3)); + __m128 vConstants = XM_PERMUTE_PS(AC1, _MM_SHUFFLE(2, 2, 2, 2)); + __m128 t0 = XM_FMADD_PS(vConstantsB, x, vConstants); + + vConstants = XM_PERMUTE_PS(AC1, _MM_SHUFFLE(1, 1, 1, 1)); + t0 = XM_FMADD_PS(t0, x, vConstants); + + vConstants = XM_PERMUTE_PS(AC1, _MM_SHUFFLE(0, 0, 0, 0)); + t0 = XM_FMADD_PS(t0, x, vConstants); + + const XMVECTOR AC0 = g_XMArcCoefficients0; + vConstants = XM_PERMUTE_PS(AC0, _MM_SHUFFLE(3, 3, 3, 3)); + t0 = XM_FMADD_PS(t0, x, vConstants); + + vConstants = XM_PERMUTE_PS(AC0, _MM_SHUFFLE(2, 2, 2, 2)); + t0 = XM_FMADD_PS(t0, x, vConstants); + + vConstants = XM_PERMUTE_PS(AC0, _MM_SHUFFLE(1, 1, 1, 1)); + t0 = XM_FMADD_PS(t0, x, vConstants); + + vConstants = XM_PERMUTE_PS(AC0, _MM_SHUFFLE(0, 0, 0, 0)); + t0 = XM_FMADD_PS(t0, x, vConstants); + t0 = _mm_mul_ps(t0, root); + + __m128 t1 = _mm_sub_ps(g_XMPi, t0); + t0 = _mm_and_ps(nonnegative, t0); + t1 = _mm_andnot_ps(nonnegative, t1); + t0 = _mm_or_ps(t0, t1); + return t0; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorATan(FXMVECTOR V) noexcept +{ + // 17-degree minimax approximation + +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 Result = { { { + atanf(V.vector4_f32[0]), + atanf(V.vector4_f32[1]), + atanf(V.vector4_f32[2]), + atanf(V.vector4_f32[3]) + } } }; + return Result.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x4_t absV = vabsq_f32(V); + float32x4_t invV = XMVectorReciprocal(V); + uint32x4_t comp = vcgtq_f32(V, g_XMOne); + float32x4_t sign = vbslq_f32(comp, g_XMOne, g_XMNegativeOne); + comp = vcleq_f32(absV, g_XMOne); + sign = vbslq_f32(comp, g_XMZero, sign); + float32x4_t x = vbslq_f32(comp, V, invV); + + float32x4_t x2 = vmulq_f32(x, x); + + // Compute polynomial approximation + const XMVECTOR TC1 = g_XMATanCoefficients1; + XMVECTOR vConstants = vdupq_lane_f32(vget_high_f32(TC1), 0); + XMVECTOR Result = vmlaq_lane_f32(vConstants, x2, vget_high_f32(TC1), 1); + + vConstants = vdupq_lane_f32(vget_low_f32(TC1), 1); + Result = vmlaq_f32(vConstants, Result, x2); + + vConstants = vdupq_lane_f32(vget_low_f32(TC1), 0); + Result = vmlaq_f32(vConstants, Result, x2); + + const XMVECTOR TC0 = g_XMATanCoefficients0; + vConstants = vdupq_lane_f32(vget_high_f32(TC0), 1); + Result = vmlaq_f32(vConstants, Result, x2); + + vConstants = vdupq_lane_f32(vget_high_f32(TC0), 0); + Result = vmlaq_f32(vConstants, Result, x2); + + vConstants = vdupq_lane_f32(vget_low_f32(TC0), 1); + Result = vmlaq_f32(vConstants, Result, x2); + + vConstants = vdupq_lane_f32(vget_low_f32(TC0), 0); + Result = vmlaq_f32(vConstants, Result, x2); + + Result = vmlaq_f32(g_XMOne, Result, x2); + Result = vmulq_f32(Result, x); + + float32x4_t result1 = vmulq_f32(sign, g_XMHalfPi); + result1 = vsubq_f32(result1, Result); + + comp = vceqq_f32(sign, g_XMZero); + Result = vbslq_f32(comp, Result, result1); + return Result; +#elif defined(_XM_SVML_INTRINSICS_) + XMVECTOR Result = _mm_atan_ps(V); + return Result; +#elif defined(_XM_SSE_INTRINSICS_) + __m128 absV = XMVectorAbs(V); + __m128 invV = _mm_div_ps(g_XMOne, V); + __m128 comp = _mm_cmpgt_ps(V, g_XMOne); + __m128 select0 = _mm_and_ps(comp, g_XMOne); + __m128 select1 = _mm_andnot_ps(comp, g_XMNegativeOne); + __m128 sign = _mm_or_ps(select0, select1); + comp = _mm_cmple_ps(absV, g_XMOne); + select0 = _mm_and_ps(comp, g_XMZero); + select1 = _mm_andnot_ps(comp, sign); + sign = _mm_or_ps(select0, select1); + select0 = _mm_and_ps(comp, V); + select1 = _mm_andnot_ps(comp, invV); + __m128 x = _mm_or_ps(select0, select1); + + __m128 x2 = _mm_mul_ps(x, x); + + // Compute polynomial approximation + const XMVECTOR TC1 = g_XMATanCoefficients1; + __m128 vConstantsB = XM_PERMUTE_PS(TC1, _MM_SHUFFLE(3, 3, 3, 3)); + __m128 vConstants = XM_PERMUTE_PS(TC1, _MM_SHUFFLE(2, 2, 2, 2)); + __m128 Result = XM_FMADD_PS(vConstantsB, x2, vConstants); + + vConstants = XM_PERMUTE_PS(TC1, _MM_SHUFFLE(1, 1, 1, 1)); + Result = XM_FMADD_PS(Result, x2, vConstants); + + vConstants = XM_PERMUTE_PS(TC1, _MM_SHUFFLE(0, 0, 0, 0)); + Result = XM_FMADD_PS(Result, x2, vConstants); + + const XMVECTOR TC0 = g_XMATanCoefficients0; + vConstants = XM_PERMUTE_PS(TC0, _MM_SHUFFLE(3, 3, 3, 3)); + Result = XM_FMADD_PS(Result, x2, vConstants); + + vConstants = XM_PERMUTE_PS(TC0, _MM_SHUFFLE(2, 2, 2, 2)); + Result = XM_FMADD_PS(Result, x2, vConstants); + + vConstants = XM_PERMUTE_PS(TC0, _MM_SHUFFLE(1, 1, 1, 1)); + Result = XM_FMADD_PS(Result, x2, vConstants); + + vConstants = XM_PERMUTE_PS(TC0, _MM_SHUFFLE(0, 0, 0, 0)); + Result = XM_FMADD_PS(Result, x2, vConstants); + + Result = XM_FMADD_PS(Result, x2, g_XMOne); + + Result = _mm_mul_ps(Result, x); + __m128 result1 = _mm_mul_ps(sign, g_XMHalfPi); + result1 = _mm_sub_ps(result1, Result); + + comp = _mm_cmpeq_ps(sign, g_XMZero); + select0 = _mm_and_ps(comp, Result); + select1 = _mm_andnot_ps(comp, result1); + Result = _mm_or_ps(select0, select1); + return Result; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorATan2 +( + FXMVECTOR Y, + FXMVECTOR X +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 Result = { { { + atan2f(Y.vector4_f32[0], X.vector4_f32[0]), + atan2f(Y.vector4_f32[1], X.vector4_f32[1]), + atan2f(Y.vector4_f32[2], X.vector4_f32[2]), + atan2f(Y.vector4_f32[3], X.vector4_f32[3]) + } } }; + return Result.v; +#elif defined(_XM_SVML_INTRINSICS_) + XMVECTOR Result = _mm_atan2_ps(Y, X); + return Result; +#else + + // Return the inverse tangent of Y / X in the range of -Pi to Pi with the following exceptions: + + // Y == 0 and X is Negative -> Pi with the sign of Y + // y == 0 and x is positive -> 0 with the sign of y + // Y != 0 and X == 0 -> Pi / 2 with the sign of Y + // Y != 0 and X is Negative -> atan(y/x) + (PI with the sign of Y) + // X == -Infinity and Finite Y -> Pi with the sign of Y + // X == +Infinity and Finite Y -> 0 with the sign of Y + // Y == Infinity and X is Finite -> Pi / 2 with the sign of Y + // Y == Infinity and X == -Infinity -> 3Pi / 4 with the sign of Y + // Y == Infinity and X == +Infinity -> Pi / 4 with the sign of Y + + static const XMVECTORF32 ATan2Constants = { { { XM_PI, XM_PIDIV2, XM_PIDIV4, XM_PI * 3.0f / 4.0f } } }; + + XMVECTOR Zero = XMVectorZero(); + XMVECTOR ATanResultValid = XMVectorTrueInt(); + + XMVECTOR Pi = XMVectorSplatX(ATan2Constants); + XMVECTOR PiOverTwo = XMVectorSplatY(ATan2Constants); + XMVECTOR PiOverFour = XMVectorSplatZ(ATan2Constants); + XMVECTOR ThreePiOverFour = XMVectorSplatW(ATan2Constants); + + XMVECTOR YEqualsZero = XMVectorEqual(Y, Zero); + XMVECTOR XEqualsZero = XMVectorEqual(X, Zero); + XMVECTOR XIsPositive = XMVectorAndInt(X, g_XMNegativeZero.v); + XIsPositive = XMVectorEqualInt(XIsPositive, Zero); + XMVECTOR YEqualsInfinity = XMVectorIsInfinite(Y); + XMVECTOR XEqualsInfinity = XMVectorIsInfinite(X); + + XMVECTOR YSign = XMVectorAndInt(Y, g_XMNegativeZero.v); + Pi = XMVectorOrInt(Pi, YSign); + PiOverTwo = XMVectorOrInt(PiOverTwo, YSign); + PiOverFour = XMVectorOrInt(PiOverFour, YSign); + ThreePiOverFour = XMVectorOrInt(ThreePiOverFour, YSign); + + XMVECTOR R1 = XMVectorSelect(Pi, YSign, XIsPositive); + XMVECTOR R2 = XMVectorSelect(ATanResultValid, PiOverTwo, XEqualsZero); + XMVECTOR R3 = XMVectorSelect(R2, R1, YEqualsZero); + XMVECTOR R4 = XMVectorSelect(ThreePiOverFour, PiOverFour, XIsPositive); + XMVECTOR R5 = XMVectorSelect(PiOverTwo, R4, XEqualsInfinity); + XMVECTOR Result = XMVectorSelect(R3, R5, YEqualsInfinity); + ATanResultValid = XMVectorEqualInt(Result, ATanResultValid); + + XMVECTOR V = XMVectorDivide(Y, X); + + XMVECTOR R0 = XMVectorATan(V); + + R1 = XMVectorSelect(Pi, g_XMNegativeZero, XIsPositive); + R2 = XMVectorAdd(R0, R1); + + return XMVectorSelect(Result, R2, ATanResultValid); + +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorSinEst(FXMVECTOR V) noexcept +{ + // 7-degree minimax approximation + +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 Result = { { { + sinf(V.vector4_f32[0]), + sinf(V.vector4_f32[1]), + sinf(V.vector4_f32[2]), + sinf(V.vector4_f32[3]) + } } }; + return Result.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + // Force the value within the bounds of pi + XMVECTOR x = XMVectorModAngles(V); + + // Map in [-pi/2,pi/2] with sin(y) = sin(x). + uint32x4_t sign = vandq_u32(vreinterpretq_u32_f32(x), g_XMNegativeZero); + uint32x4_t c = vorrq_u32(g_XMPi, sign); // pi when x >= 0, -pi when x < 0 + float32x4_t absx = vabsq_f32(x); + float32x4_t rflx = vsubq_f32(vreinterpretq_f32_u32(c), x); + uint32x4_t comp = vcleq_f32(absx, g_XMHalfPi); + x = vbslq_f32(comp, x, rflx); + + float32x4_t x2 = vmulq_f32(x, x); + + // Compute polynomial approximation + const XMVECTOR SEC = g_XMSinCoefficients1; + XMVECTOR vConstants = vdupq_lane_f32(vget_high_f32(SEC), 0); + XMVECTOR Result = vmlaq_lane_f32(vConstants, x2, vget_high_f32(SEC), 1); + + vConstants = vdupq_lane_f32(vget_low_f32(SEC), 1); + Result = vmlaq_f32(vConstants, Result, x2); + + Result = vmlaq_f32(g_XMOne, Result, x2); + Result = vmulq_f32(Result, x); + return Result; +#elif defined(_XM_SVML_INTRINSICS_) + XMVECTOR Result = _mm_sin_ps(V); + return Result; +#elif defined(_XM_SSE_INTRINSICS_) + // Force the value within the bounds of pi + XMVECTOR x = XMVectorModAngles(V); + + // Map in [-pi/2,pi/2] with sin(y) = sin(x). + __m128 sign = _mm_and_ps(x, g_XMNegativeZero); + __m128 c = _mm_or_ps(g_XMPi, sign); // pi when x >= 0, -pi when x < 0 + __m128 absx = _mm_andnot_ps(sign, x); // |x| + __m128 rflx = _mm_sub_ps(c, x); + __m128 comp = _mm_cmple_ps(absx, g_XMHalfPi); + __m128 select0 = _mm_and_ps(comp, x); + __m128 select1 = _mm_andnot_ps(comp, rflx); + x = _mm_or_ps(select0, select1); + + __m128 x2 = _mm_mul_ps(x, x); + + // Compute polynomial approximation + const XMVECTOR SEC = g_XMSinCoefficients1; + __m128 vConstantsB = XM_PERMUTE_PS(SEC, _MM_SHUFFLE(3, 3, 3, 3)); + __m128 vConstants = XM_PERMUTE_PS(SEC, _MM_SHUFFLE(2, 2, 2, 2)); + __m128 Result = XM_FMADD_PS(vConstantsB, x2, vConstants); + + vConstants = XM_PERMUTE_PS(SEC, _MM_SHUFFLE(1, 1, 1, 1)); + Result = XM_FMADD_PS(Result, x2, vConstants); + Result = XM_FMADD_PS(Result, x2, g_XMOne); + Result = _mm_mul_ps(Result, x); + return Result; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorCosEst(FXMVECTOR V) noexcept +{ + // 6-degree minimax approximation + +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 Result = { { { + cosf(V.vector4_f32[0]), + cosf(V.vector4_f32[1]), + cosf(V.vector4_f32[2]), + cosf(V.vector4_f32[3]) + } } }; + return Result.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + // Map V to x in [-pi,pi]. + XMVECTOR x = XMVectorModAngles(V); + + // Map in [-pi/2,pi/2] with cos(y) = sign*cos(x). + uint32x4_t sign = vandq_u32(vreinterpretq_u32_f32(x), g_XMNegativeZero); + uint32x4_t c = vorrq_u32(g_XMPi, sign); // pi when x >= 0, -pi when x < 0 + float32x4_t absx = vabsq_f32(x); + float32x4_t rflx = vsubq_f32(vreinterpretq_f32_u32(c), x); + uint32x4_t comp = vcleq_f32(absx, g_XMHalfPi); + x = vbslq_f32(comp, x, rflx); + float32x4_t fsign = vbslq_f32(comp, g_XMOne, g_XMNegativeOne); + + float32x4_t x2 = vmulq_f32(x, x); + + // Compute polynomial approximation + const XMVECTOR CEC = g_XMCosCoefficients1; + XMVECTOR vConstants = vdupq_lane_f32(vget_high_f32(CEC), 0); + XMVECTOR Result = vmlaq_lane_f32(vConstants, x2, vget_high_f32(CEC), 1); + + vConstants = vdupq_lane_f32(vget_low_f32(CEC), 1); + Result = vmlaq_f32(vConstants, Result, x2); + + Result = vmlaq_f32(g_XMOne, Result, x2); + Result = vmulq_f32(Result, fsign); + return Result; +#elif defined(_XM_SVML_INTRINSICS_) + XMVECTOR Result = _mm_cos_ps(V); + return Result; +#elif defined(_XM_SSE_INTRINSICS_) + // Map V to x in [-pi,pi]. + XMVECTOR x = XMVectorModAngles(V); + + // Map in [-pi/2,pi/2] with cos(y) = sign*cos(x). + XMVECTOR sign = _mm_and_ps(x, g_XMNegativeZero); + __m128 c = _mm_or_ps(g_XMPi, sign); // pi when x >= 0, -pi when x < 0 + __m128 absx = _mm_andnot_ps(sign, x); // |x| + __m128 rflx = _mm_sub_ps(c, x); + __m128 comp = _mm_cmple_ps(absx, g_XMHalfPi); + __m128 select0 = _mm_and_ps(comp, x); + __m128 select1 = _mm_andnot_ps(comp, rflx); + x = _mm_or_ps(select0, select1); + select0 = _mm_and_ps(comp, g_XMOne); + select1 = _mm_andnot_ps(comp, g_XMNegativeOne); + sign = _mm_or_ps(select0, select1); + + __m128 x2 = _mm_mul_ps(x, x); + + // Compute polynomial approximation + const XMVECTOR CEC = g_XMCosCoefficients1; + __m128 vConstantsB = XM_PERMUTE_PS(CEC, _MM_SHUFFLE(3, 3, 3, 3)); + __m128 vConstants = XM_PERMUTE_PS(CEC, _MM_SHUFFLE(2, 2, 2, 2)); + __m128 Result = XM_FMADD_PS(vConstantsB, x2, vConstants); + + vConstants = XM_PERMUTE_PS(CEC, _MM_SHUFFLE(1, 1, 1, 1)); + Result = XM_FMADD_PS(Result, x2, vConstants); + Result = XM_FMADD_PS(Result, x2, g_XMOne); + Result = _mm_mul_ps(Result, sign); + return Result; +#endif +} + +//------------------------------------------------------------------------------ + +_Use_decl_annotations_ +inline void XM_CALLCONV XMVectorSinCosEst +( + XMVECTOR* pSin, + XMVECTOR* pCos, + FXMVECTOR V +) noexcept +{ + assert(pSin != nullptr); + assert(pCos != nullptr); + + // 7/6-degree minimax approximation + +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 Sin = { { { + sinf(V.vector4_f32[0]), + sinf(V.vector4_f32[1]), + sinf(V.vector4_f32[2]), + sinf(V.vector4_f32[3]) + } } }; + + XMVECTORF32 Cos = { { { + cosf(V.vector4_f32[0]), + cosf(V.vector4_f32[1]), + cosf(V.vector4_f32[2]), + cosf(V.vector4_f32[3]) + } } }; + + *pSin = Sin.v; + *pCos = Cos.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + // Force the value within the bounds of pi + XMVECTOR x = XMVectorModAngles(V); + + // Map in [-pi/2,pi/2] with cos(y) = sign*cos(x). + uint32x4_t sign = vandq_u32(vreinterpretq_u32_f32(x), g_XMNegativeZero); + uint32x4_t c = vorrq_u32(g_XMPi, sign); // pi when x >= 0, -pi when x < 0 + float32x4_t absx = vabsq_f32(x); + float32x4_t rflx = vsubq_f32(vreinterpretq_f32_u32(c), x); + uint32x4_t comp = vcleq_f32(absx, g_XMHalfPi); + x = vbslq_f32(comp, x, rflx); + float32x4_t fsign = vbslq_f32(comp, g_XMOne, g_XMNegativeOne); + + float32x4_t x2 = vmulq_f32(x, x); + + // Compute polynomial approximation for sine + const XMVECTOR SEC = g_XMSinCoefficients1; + XMVECTOR vConstants = vdupq_lane_f32(vget_high_f32(SEC), 0); + XMVECTOR Result = vmlaq_lane_f32(vConstants, x2, vget_high_f32(SEC), 1); + + vConstants = vdupq_lane_f32(vget_low_f32(SEC), 1); + Result = vmlaq_f32(vConstants, Result, x2); + + Result = vmlaq_f32(g_XMOne, Result, x2); + *pSin = vmulq_f32(Result, x); + + // Compute polynomial approximation + const XMVECTOR CEC = g_XMCosCoefficients1; + vConstants = vdupq_lane_f32(vget_high_f32(CEC), 0); + Result = vmlaq_lane_f32(vConstants, x2, vget_high_f32(CEC), 1); + + vConstants = vdupq_lane_f32(vget_low_f32(CEC), 1); + Result = vmlaq_f32(vConstants, Result, x2); + + Result = vmlaq_f32(g_XMOne, Result, x2); + *pCos = vmulq_f32(Result, fsign); +#elif defined(_XM_SSE_INTRINSICS_) + // Force the value within the bounds of pi + XMVECTOR x = XMVectorModAngles(V); + + // Map in [-pi/2,pi/2] with sin(y) = sin(x), cos(y) = sign*cos(x). + XMVECTOR sign = _mm_and_ps(x, g_XMNegativeZero); + __m128 c = _mm_or_ps(g_XMPi, sign); // pi when x >= 0, -pi when x < 0 + __m128 absx = _mm_andnot_ps(sign, x); // |x| + __m128 rflx = _mm_sub_ps(c, x); + __m128 comp = _mm_cmple_ps(absx, g_XMHalfPi); + __m128 select0 = _mm_and_ps(comp, x); + __m128 select1 = _mm_andnot_ps(comp, rflx); + x = _mm_or_ps(select0, select1); + select0 = _mm_and_ps(comp, g_XMOne); + select1 = _mm_andnot_ps(comp, g_XMNegativeOne); + sign = _mm_or_ps(select0, select1); + + __m128 x2 = _mm_mul_ps(x, x); + + // Compute polynomial approximation for sine + const XMVECTOR SEC = g_XMSinCoefficients1; + __m128 vConstantsB = XM_PERMUTE_PS(SEC, _MM_SHUFFLE(3, 3, 3, 3)); + __m128 vConstants = XM_PERMUTE_PS(SEC, _MM_SHUFFLE(2, 2, 2, 2)); + __m128 Result = XM_FMADD_PS(vConstantsB, x2, vConstants); + + vConstants = XM_PERMUTE_PS(SEC, _MM_SHUFFLE(1, 1, 1, 1)); + Result = XM_FMADD_PS(Result, x2, vConstants); + Result = XM_FMADD_PS(Result, x2, g_XMOne); + Result = _mm_mul_ps(Result, x); + *pSin = Result; + + // Compute polynomial approximation for cosine + const XMVECTOR CEC = g_XMCosCoefficients1; + vConstantsB = XM_PERMUTE_PS(CEC, _MM_SHUFFLE(3, 3, 3, 3)); + vConstants = XM_PERMUTE_PS(CEC, _MM_SHUFFLE(2, 2, 2, 2)); + Result = XM_FMADD_PS(vConstantsB, x2, vConstants); + + vConstants = XM_PERMUTE_PS(CEC, _MM_SHUFFLE(1, 1, 1, 1)); + Result = XM_FMADD_PS(Result, x2, vConstants); + Result = XM_FMADD_PS(Result, x2, g_XMOne); + Result = _mm_mul_ps(Result, sign); + *pCos = Result; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorTanEst(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 Result = { { { + tanf(V.vector4_f32[0]), + tanf(V.vector4_f32[1]), + tanf(V.vector4_f32[2]), + tanf(V.vector4_f32[3]) + } } }; + return Result.v; +#elif defined(_XM_SVML_INTRINSICS_) + XMVECTOR Result = _mm_tan_ps(V); + return Result; +#else + + XMVECTOR OneOverPi = XMVectorSplatW(g_XMTanEstCoefficients.v); + + XMVECTOR V1 = XMVectorMultiply(V, OneOverPi); + V1 = XMVectorRound(V1); + + V1 = XMVectorNegativeMultiplySubtract(g_XMPi.v, V1, V); + + XMVECTOR T0 = XMVectorSplatX(g_XMTanEstCoefficients.v); + XMVECTOR T1 = XMVectorSplatY(g_XMTanEstCoefficients.v); + XMVECTOR T2 = XMVectorSplatZ(g_XMTanEstCoefficients.v); + + XMVECTOR V2T2 = XMVectorNegativeMultiplySubtract(V1, V1, T2); + XMVECTOR V2 = XMVectorMultiply(V1, V1); + XMVECTOR V1T0 = XMVectorMultiply(V1, T0); + XMVECTOR V1T1 = XMVectorMultiply(V1, T1); + + XMVECTOR D = XMVectorReciprocalEst(V2T2); + XMVECTOR N = XMVectorMultiplyAdd(V2, V1T1, V1T0); + + return XMVectorMultiply(N, D); + +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorASinEst(FXMVECTOR V) noexcept +{ + // 3-degree minimax approximation + +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 Result; + Result.f[0] = asinf(V.vector4_f32[0]); + Result.f[1] = asinf(V.vector4_f32[1]); + Result.f[2] = asinf(V.vector4_f32[2]); + Result.f[3] = asinf(V.vector4_f32[3]); + return Result.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x4_t nonnegative = vcgeq_f32(V, g_XMZero); + float32x4_t x = vabsq_f32(V); + + // Compute (1-|V|), clamp to zero to avoid sqrt of negative number. + float32x4_t oneMValue = vsubq_f32(g_XMOne, x); + float32x4_t clampOneMValue = vmaxq_f32(g_XMZero, oneMValue); + float32x4_t root = XMVectorSqrt(clampOneMValue); + + // Compute polynomial approximation + const XMVECTOR AEC = g_XMArcEstCoefficients; + XMVECTOR vConstants = vdupq_lane_f32(vget_high_f32(AEC), 0); + XMVECTOR t0 = vmlaq_lane_f32(vConstants, x, vget_high_f32(AEC), 1); + + vConstants = vdupq_lane_f32(vget_low_f32(AEC), 1); + t0 = vmlaq_f32(vConstants, t0, x); + + vConstants = vdupq_lane_f32(vget_low_f32(AEC), 0); + t0 = vmlaq_f32(vConstants, t0, x); + t0 = vmulq_f32(t0, root); + + float32x4_t t1 = vsubq_f32(g_XMPi, t0); + t0 = vbslq_f32(nonnegative, t0, t1); + t0 = vsubq_f32(g_XMHalfPi, t0); + return t0; +#elif defined(_XM_SVML_INTRINSICS_) + XMVECTOR Result = _mm_asin_ps(V); + return Result; +#elif defined(_XM_SSE_INTRINSICS_) + __m128 nonnegative = _mm_cmpge_ps(V, g_XMZero); + __m128 mvalue = _mm_sub_ps(g_XMZero, V); + __m128 x = _mm_max_ps(V, mvalue); // |V| + + // Compute (1-|V|), clamp to zero to avoid sqrt of negative number. + __m128 oneMValue = _mm_sub_ps(g_XMOne, x); + __m128 clampOneMValue = _mm_max_ps(g_XMZero, oneMValue); + __m128 root = _mm_sqrt_ps(clampOneMValue); // sqrt(1-|V|) + + // Compute polynomial approximation + const XMVECTOR AEC = g_XMArcEstCoefficients; + __m128 vConstantsB = XM_PERMUTE_PS(AEC, _MM_SHUFFLE(3, 3, 3, 3)); + __m128 vConstants = XM_PERMUTE_PS(AEC, _MM_SHUFFLE(2, 2, 2, 2)); + __m128 t0 = XM_FMADD_PS(vConstantsB, x, vConstants); + + vConstants = XM_PERMUTE_PS(AEC, _MM_SHUFFLE(1, 1, 1, 1)); + t0 = XM_FMADD_PS(t0, x, vConstants); + + vConstants = XM_PERMUTE_PS(AEC, _MM_SHUFFLE(0, 0, 0, 0)); + t0 = XM_FMADD_PS(t0, x, vConstants); + t0 = _mm_mul_ps(t0, root); + + __m128 t1 = _mm_sub_ps(g_XMPi, t0); + t0 = _mm_and_ps(nonnegative, t0); + t1 = _mm_andnot_ps(nonnegative, t1); + t0 = _mm_or_ps(t0, t1); + t0 = _mm_sub_ps(g_XMHalfPi, t0); + return t0; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorACosEst(FXMVECTOR V) noexcept +{ + // 3-degree minimax approximation + +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 Result = { { { + acosf(V.vector4_f32[0]), + acosf(V.vector4_f32[1]), + acosf(V.vector4_f32[2]), + acosf(V.vector4_f32[3]) + } } }; + return Result.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x4_t nonnegative = vcgeq_f32(V, g_XMZero); + float32x4_t x = vabsq_f32(V); + + // Compute (1-|V|), clamp to zero to avoid sqrt of negative number. + float32x4_t oneMValue = vsubq_f32(g_XMOne, x); + float32x4_t clampOneMValue = vmaxq_f32(g_XMZero, oneMValue); + float32x4_t root = XMVectorSqrt(clampOneMValue); + + // Compute polynomial approximation + const XMVECTOR AEC = g_XMArcEstCoefficients; + XMVECTOR vConstants = vdupq_lane_f32(vget_high_f32(AEC), 0); + XMVECTOR t0 = vmlaq_lane_f32(vConstants, x, vget_high_f32(AEC), 1); + + vConstants = vdupq_lane_f32(vget_low_f32(AEC), 1); + t0 = vmlaq_f32(vConstants, t0, x); + + vConstants = vdupq_lane_f32(vget_low_f32(AEC), 0); + t0 = vmlaq_f32(vConstants, t0, x); + t0 = vmulq_f32(t0, root); + + float32x4_t t1 = vsubq_f32(g_XMPi, t0); + t0 = vbslq_f32(nonnegative, t0, t1); + return t0; +#elif defined(_XM_SVML_INTRINSICS_) + XMVECTOR Result = _mm_acos_ps(V); + return Result; +#elif defined(_XM_SSE_INTRINSICS_) + __m128 nonnegative = _mm_cmpge_ps(V, g_XMZero); + __m128 mvalue = _mm_sub_ps(g_XMZero, V); + __m128 x = _mm_max_ps(V, mvalue); // |V| + + // Compute (1-|V|), clamp to zero to avoid sqrt of negative number. + __m128 oneMValue = _mm_sub_ps(g_XMOne, x); + __m128 clampOneMValue = _mm_max_ps(g_XMZero, oneMValue); + __m128 root = _mm_sqrt_ps(clampOneMValue); // sqrt(1-|V|) + + // Compute polynomial approximation + const XMVECTOR AEC = g_XMArcEstCoefficients; + __m128 vConstantsB = XM_PERMUTE_PS(AEC, _MM_SHUFFLE(3, 3, 3, 3)); + __m128 vConstants = XM_PERMUTE_PS(AEC, _MM_SHUFFLE(2, 2, 2, 2)); + __m128 t0 = XM_FMADD_PS(vConstantsB, x, vConstants); + + vConstants = XM_PERMUTE_PS(AEC, _MM_SHUFFLE(1, 1, 1, 1)); + t0 = XM_FMADD_PS(t0, x, vConstants); + + vConstants = XM_PERMUTE_PS(AEC, _MM_SHUFFLE(0, 0, 0, 0)); + t0 = XM_FMADD_PS(t0, x, vConstants); + t0 = _mm_mul_ps(t0, root); + + __m128 t1 = _mm_sub_ps(g_XMPi, t0); + t0 = _mm_and_ps(nonnegative, t0); + t1 = _mm_andnot_ps(nonnegative, t1); + t0 = _mm_or_ps(t0, t1); + return t0; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorATanEst(FXMVECTOR V) noexcept +{ + // 9-degree minimax approximation + +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 Result = { { { + atanf(V.vector4_f32[0]), + atanf(V.vector4_f32[1]), + atanf(V.vector4_f32[2]), + atanf(V.vector4_f32[3]) + } } }; + return Result.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x4_t absV = vabsq_f32(V); + float32x4_t invV = XMVectorReciprocalEst(V); + uint32x4_t comp = vcgtq_f32(V, g_XMOne); + float32x4_t sign = vbslq_f32(comp, g_XMOne, g_XMNegativeOne); + comp = vcleq_f32(absV, g_XMOne); + sign = vbslq_f32(comp, g_XMZero, sign); + float32x4_t x = vbslq_f32(comp, V, invV); + + float32x4_t x2 = vmulq_f32(x, x); + + // Compute polynomial approximation + const XMVECTOR AEC = g_XMATanEstCoefficients1; + XMVECTOR vConstants = vdupq_lane_f32(vget_high_f32(AEC), 0); + XMVECTOR Result = vmlaq_lane_f32(vConstants, x2, vget_high_f32(AEC), 1); + + vConstants = vdupq_lane_f32(vget_low_f32(AEC), 1); + Result = vmlaq_f32(vConstants, Result, x2); + + vConstants = vdupq_lane_f32(vget_low_f32(AEC), 0); + Result = vmlaq_f32(vConstants, Result, x2); + + // ATanEstCoefficients0 is already splatted + Result = vmlaq_f32(g_XMATanEstCoefficients0, Result, x2); + Result = vmulq_f32(Result, x); + + float32x4_t result1 = vmulq_f32(sign, g_XMHalfPi); + result1 = vsubq_f32(result1, Result); + + comp = vceqq_f32(sign, g_XMZero); + Result = vbslq_f32(comp, Result, result1); + return Result; +#elif defined(_XM_SVML_INTRINSICS_) + XMVECTOR Result = _mm_atan_ps(V); + return Result; +#elif defined(_XM_SSE_INTRINSICS_) + __m128 absV = XMVectorAbs(V); + __m128 invV = _mm_div_ps(g_XMOne, V); + __m128 comp = _mm_cmpgt_ps(V, g_XMOne); + __m128 select0 = _mm_and_ps(comp, g_XMOne); + __m128 select1 = _mm_andnot_ps(comp, g_XMNegativeOne); + __m128 sign = _mm_or_ps(select0, select1); + comp = _mm_cmple_ps(absV, g_XMOne); + select0 = _mm_and_ps(comp, g_XMZero); + select1 = _mm_andnot_ps(comp, sign); + sign = _mm_or_ps(select0, select1); + select0 = _mm_and_ps(comp, V); + select1 = _mm_andnot_ps(comp, invV); + __m128 x = _mm_or_ps(select0, select1); + + __m128 x2 = _mm_mul_ps(x, x); + + // Compute polynomial approximation + const XMVECTOR AEC = g_XMATanEstCoefficients1; + __m128 vConstantsB = XM_PERMUTE_PS(AEC, _MM_SHUFFLE(3, 3, 3, 3)); + __m128 vConstants = XM_PERMUTE_PS(AEC, _MM_SHUFFLE(2, 2, 2, 2)); + __m128 Result = XM_FMADD_PS(vConstantsB, x2, vConstants); + + vConstants = XM_PERMUTE_PS(AEC, _MM_SHUFFLE(1, 1, 1, 1)); + Result = XM_FMADD_PS(Result, x2, vConstants); + + vConstants = XM_PERMUTE_PS(AEC, _MM_SHUFFLE(0, 0, 0, 0)); + Result = XM_FMADD_PS(Result, x2, vConstants); + // ATanEstCoefficients0 is already splatted + Result = XM_FMADD_PS(Result, x2, g_XMATanEstCoefficients0); + Result = _mm_mul_ps(Result, x); + __m128 result1 = _mm_mul_ps(sign, g_XMHalfPi); + result1 = _mm_sub_ps(result1, Result); + + comp = _mm_cmpeq_ps(sign, g_XMZero); + select0 = _mm_and_ps(comp, Result); + select1 = _mm_andnot_ps(comp, result1); + Result = _mm_or_ps(select0, select1); + return Result; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorATan2Est +( + FXMVECTOR Y, + FXMVECTOR X +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 Result = { { { + atan2f(Y.vector4_f32[0], X.vector4_f32[0]), + atan2f(Y.vector4_f32[1], X.vector4_f32[1]), + atan2f(Y.vector4_f32[2], X.vector4_f32[2]), + atan2f(Y.vector4_f32[3], X.vector4_f32[3]), + } } }; + return Result.v; +#elif defined(_XM_SVML_INTRINSICS_) + XMVECTOR Result = _mm_atan2_ps(Y, X); + return Result; +#else + + static const XMVECTORF32 ATan2Constants = { { { XM_PI, XM_PIDIV2, XM_PIDIV4, 2.3561944905f /* Pi*3/4 */ } } }; + + const XMVECTOR Zero = XMVectorZero(); + XMVECTOR ATanResultValid = XMVectorTrueInt(); + + XMVECTOR Pi = XMVectorSplatX(ATan2Constants); + XMVECTOR PiOverTwo = XMVectorSplatY(ATan2Constants); + XMVECTOR PiOverFour = XMVectorSplatZ(ATan2Constants); + XMVECTOR ThreePiOverFour = XMVectorSplatW(ATan2Constants); + + XMVECTOR YEqualsZero = XMVectorEqual(Y, Zero); + XMVECTOR XEqualsZero = XMVectorEqual(X, Zero); + XMVECTOR XIsPositive = XMVectorAndInt(X, g_XMNegativeZero.v); + XIsPositive = XMVectorEqualInt(XIsPositive, Zero); + XMVECTOR YEqualsInfinity = XMVectorIsInfinite(Y); + XMVECTOR XEqualsInfinity = XMVectorIsInfinite(X); + + XMVECTOR YSign = XMVectorAndInt(Y, g_XMNegativeZero.v); + Pi = XMVectorOrInt(Pi, YSign); + PiOverTwo = XMVectorOrInt(PiOverTwo, YSign); + PiOverFour = XMVectorOrInt(PiOverFour, YSign); + ThreePiOverFour = XMVectorOrInt(ThreePiOverFour, YSign); + + XMVECTOR R1 = XMVectorSelect(Pi, YSign, XIsPositive); + XMVECTOR R2 = XMVectorSelect(ATanResultValid, PiOverTwo, XEqualsZero); + XMVECTOR R3 = XMVectorSelect(R2, R1, YEqualsZero); + XMVECTOR R4 = XMVectorSelect(ThreePiOverFour, PiOverFour, XIsPositive); + XMVECTOR R5 = XMVectorSelect(PiOverTwo, R4, XEqualsInfinity); + XMVECTOR Result = XMVectorSelect(R3, R5, YEqualsInfinity); + ATanResultValid = XMVectorEqualInt(Result, ATanResultValid); + + XMVECTOR Reciprocal = XMVectorReciprocalEst(X); + XMVECTOR V = XMVectorMultiply(Y, Reciprocal); + XMVECTOR R0 = XMVectorATanEst(V); + + R1 = XMVectorSelect(Pi, g_XMNegativeZero, XIsPositive); + R2 = XMVectorAdd(R0, R1); + + Result = XMVectorSelect(Result, R2, ATanResultValid); + + return Result; + +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorLerp +( + FXMVECTOR V0, + FXMVECTOR V1, + float t +) noexcept +{ + // V0 + t * (V1 - V0) + +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR Scale = XMVectorReplicate(t); + XMVECTOR Length = XMVectorSubtract(V1, V0); + return XMVectorMultiplyAdd(Length, Scale, V0); + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + XMVECTOR L = vsubq_f32(V1, V0); + return vmlaq_n_f32(V0, L, t); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR L = _mm_sub_ps(V1, V0); + XMVECTOR S = _mm_set_ps1(t); + return XM_FMADD_PS(L, S, V0); +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorLerpV +( + FXMVECTOR V0, + FXMVECTOR V1, + FXMVECTOR T +) noexcept +{ + // V0 + T * (V1 - V0) + +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR Length = XMVectorSubtract(V1, V0); + return XMVectorMultiplyAdd(Length, T, V0); + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + XMVECTOR L = vsubq_f32(V1, V0); + return vmlaq_f32(V0, L, T); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR Length = _mm_sub_ps(V1, V0); + return XM_FMADD_PS(Length, T, V0); +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorHermite +( + FXMVECTOR Position0, + FXMVECTOR Tangent0, + FXMVECTOR Position1, + GXMVECTOR Tangent1, + float t +) noexcept +{ + // Result = (2 * t^3 - 3 * t^2 + 1) * Position0 + + // (t^3 - 2 * t^2 + t) * Tangent0 + + // (-2 * t^3 + 3 * t^2) * Position1 + + // (t^3 - t^2) * Tangent1 + +#if defined(_XM_NO_INTRINSICS_) + + float t2 = t * t; + float t3 = t * t2; + + XMVECTOR P0 = XMVectorReplicate(2.0f * t3 - 3.0f * t2 + 1.0f); + XMVECTOR T0 = XMVectorReplicate(t3 - 2.0f * t2 + t); + XMVECTOR P1 = XMVectorReplicate(-2.0f * t3 + 3.0f * t2); + XMVECTOR T1 = XMVectorReplicate(t3 - t2); + + XMVECTOR Result = XMVectorMultiply(P0, Position0); + Result = XMVectorMultiplyAdd(T0, Tangent0, Result); + Result = XMVectorMultiplyAdd(P1, Position1, Result); + Result = XMVectorMultiplyAdd(T1, Tangent1, Result); + + return Result; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float t2 = t * t; + float t3 = t * t2; + + float p0 = 2.0f * t3 - 3.0f * t2 + 1.0f; + float t0 = t3 - 2.0f * t2 + t; + float p1 = -2.0f * t3 + 3.0f * t2; + float t1 = t3 - t2; + + XMVECTOR vResult = vmulq_n_f32(Position0, p0); + vResult = vmlaq_n_f32(vResult, Tangent0, t0); + vResult = vmlaq_n_f32(vResult, Position1, p1); + vResult = vmlaq_n_f32(vResult, Tangent1, t1); + return vResult; +#elif defined(_XM_SSE_INTRINSICS_) + float t2 = t * t; + float t3 = t * t2; + + XMVECTOR P0 = _mm_set_ps1(2.0f * t3 - 3.0f * t2 + 1.0f); + XMVECTOR T0 = _mm_set_ps1(t3 - 2.0f * t2 + t); + XMVECTOR P1 = _mm_set_ps1(-2.0f * t3 + 3.0f * t2); + XMVECTOR T1 = _mm_set_ps1(t3 - t2); + + XMVECTOR vResult = _mm_mul_ps(P0, Position0); + vResult = XM_FMADD_PS(Tangent0, T0, vResult); + vResult = XM_FMADD_PS(Position1, P1, vResult); + vResult = XM_FMADD_PS(Tangent1, T1, vResult); + return vResult; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorHermiteV +( + FXMVECTOR Position0, + FXMVECTOR Tangent0, + FXMVECTOR Position1, + GXMVECTOR Tangent1, + HXMVECTOR T +) noexcept +{ + // Result = (2 * t^3 - 3 * t^2 + 1) * Position0 + + // (t^3 - 2 * t^2 + t) * Tangent0 + + // (-2 * t^3 + 3 * t^2) * Position1 + + // (t^3 - t^2) * Tangent1 + +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR T2 = XMVectorMultiply(T, T); + XMVECTOR T3 = XMVectorMultiply(T, T2); + + XMVECTOR P0 = XMVectorReplicate(2.0f * T3.vector4_f32[0] - 3.0f * T2.vector4_f32[0] + 1.0f); + XMVECTOR T0 = XMVectorReplicate(T3.vector4_f32[1] - 2.0f * T2.vector4_f32[1] + T.vector4_f32[1]); + XMVECTOR P1 = XMVectorReplicate(-2.0f * T3.vector4_f32[2] + 3.0f * T2.vector4_f32[2]); + XMVECTOR T1 = XMVectorReplicate(T3.vector4_f32[3] - T2.vector4_f32[3]); + + XMVECTOR Result = XMVectorMultiply(P0, Position0); + Result = XMVectorMultiplyAdd(T0, Tangent0, Result); + Result = XMVectorMultiplyAdd(P1, Position1, Result); + Result = XMVectorMultiplyAdd(T1, Tangent1, Result); + + return Result; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + static const XMVECTORF32 CatMulT2 = { { { -3.0f, -2.0f, 3.0f, -1.0f } } }; + static const XMVECTORF32 CatMulT3 = { { { 2.0f, 1.0f, -2.0f, 1.0f } } }; + + XMVECTOR T2 = vmulq_f32(T, T); + XMVECTOR T3 = vmulq_f32(T, T2); + // Mul by the constants against t^2 + T2 = vmulq_f32(T2, CatMulT2); + // Mul by the constants against t^3 + T3 = vmlaq_f32(T2, T3, CatMulT3); + // T3 now has the pre-result. + // I need to add t.y only + T2 = vreinterpretq_f32_u32(vandq_u32(vreinterpretq_u32_f32(T), g_XMMaskY)); + T3 = vaddq_f32(T3, T2); + // Add 1.0f to x + T3 = vaddq_f32(T3, g_XMIdentityR0); + // Now, I have the constants created + // Mul the x constant to Position0 + XMVECTOR vResult = vmulq_lane_f32(Position0, vget_low_f32(T3), 0); // T3[0] + // Mul the y constant to Tangent0 + vResult = vmlaq_lane_f32(vResult, Tangent0, vget_low_f32(T3), 1); // T3[1] + // Mul the z constant to Position1 + vResult = vmlaq_lane_f32(vResult, Position1, vget_high_f32(T3), 0); // T3[2] + // Mul the w constant to Tangent1 + vResult = vmlaq_lane_f32(vResult, Tangent1, vget_high_f32(T3), 1); // T3[3] + return vResult; +#elif defined(_XM_SSE_INTRINSICS_) + static const XMVECTORF32 CatMulT2 = { { { -3.0f, -2.0f, 3.0f, -1.0f } } }; + static const XMVECTORF32 CatMulT3 = { { { 2.0f, 1.0f, -2.0f, 1.0f } } }; + + XMVECTOR T2 = _mm_mul_ps(T, T); + XMVECTOR T3 = _mm_mul_ps(T, T2); + // Mul by the constants against t^2 + T2 = _mm_mul_ps(T2, CatMulT2); + // Mul by the constants against t^3 + T3 = XM_FMADD_PS(T3, CatMulT3, T2); + // T3 now has the pre-result. + // I need to add t.y only + T2 = _mm_and_ps(T, g_XMMaskY); + T3 = _mm_add_ps(T3, T2); + // Add 1.0f to x + T3 = _mm_add_ps(T3, g_XMIdentityR0); + // Now, I have the constants created + // Mul the x constant to Position0 + XMVECTOR vResult = XM_PERMUTE_PS(T3, _MM_SHUFFLE(0, 0, 0, 0)); + vResult = _mm_mul_ps(vResult, Position0); + // Mul the y constant to Tangent0 + T2 = XM_PERMUTE_PS(T3, _MM_SHUFFLE(1, 1, 1, 1)); + vResult = XM_FMADD_PS(T2, Tangent0, vResult); + // Mul the z constant to Position1 + T2 = XM_PERMUTE_PS(T3, _MM_SHUFFLE(2, 2, 2, 2)); + vResult = XM_FMADD_PS(T2, Position1, vResult); + // Mul the w constant to Tangent1 + T3 = XM_PERMUTE_PS(T3, _MM_SHUFFLE(3, 3, 3, 3)); + vResult = XM_FMADD_PS(T3, Tangent1, vResult); + return vResult; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorCatmullRom +( + FXMVECTOR Position0, + FXMVECTOR Position1, + FXMVECTOR Position2, + GXMVECTOR Position3, + float t +) noexcept +{ + // Result = ((-t^3 + 2 * t^2 - t) * Position0 + + // (3 * t^3 - 5 * t^2 + 2) * Position1 + + // (-3 * t^3 + 4 * t^2 + t) * Position2 + + // (t^3 - t^2) * Position3) * 0.5 + +#if defined(_XM_NO_INTRINSICS_) + + float t2 = t * t; + float t3 = t * t2; + + XMVECTOR P0 = XMVectorReplicate((-t3 + 2.0f * t2 - t) * 0.5f); + XMVECTOR P1 = XMVectorReplicate((3.0f * t3 - 5.0f * t2 + 2.0f) * 0.5f); + XMVECTOR P2 = XMVectorReplicate((-3.0f * t3 + 4.0f * t2 + t) * 0.5f); + XMVECTOR P3 = XMVectorReplicate((t3 - t2) * 0.5f); + + XMVECTOR Result = XMVectorMultiply(P0, Position0); + Result = XMVectorMultiplyAdd(P1, Position1, Result); + Result = XMVectorMultiplyAdd(P2, Position2, Result); + Result = XMVectorMultiplyAdd(P3, Position3, Result); + + return Result; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float t2 = t * t; + float t3 = t * t2; + + float p0 = (-t3 + 2.0f * t2 - t) * 0.5f; + float p1 = (3.0f * t3 - 5.0f * t2 + 2.0f) * 0.5f; + float p2 = (-3.0f * t3 + 4.0f * t2 + t) * 0.5f; + float p3 = (t3 - t2) * 0.5f; + + XMVECTOR P1 = vmulq_n_f32(Position1, p1); + XMVECTOR P0 = vmlaq_n_f32(P1, Position0, p0); + XMVECTOR P3 = vmulq_n_f32(Position3, p3); + XMVECTOR P2 = vmlaq_n_f32(P3, Position2, p2); + P0 = vaddq_f32(P0, P2); + return P0; +#elif defined(_XM_SSE_INTRINSICS_) + float t2 = t * t; + float t3 = t * t2; + + XMVECTOR P0 = _mm_set_ps1((-t3 + 2.0f * t2 - t) * 0.5f); + XMVECTOR P1 = _mm_set_ps1((3.0f * t3 - 5.0f * t2 + 2.0f) * 0.5f); + XMVECTOR P2 = _mm_set_ps1((-3.0f * t3 + 4.0f * t2 + t) * 0.5f); + XMVECTOR P3 = _mm_set_ps1((t3 - t2) * 0.5f); + + P1 = _mm_mul_ps(Position1, P1); + P0 = XM_FMADD_PS(Position0, P0, P1); + P3 = _mm_mul_ps(Position3, P3); + P2 = XM_FMADD_PS(Position2, P2, P3); + P0 = _mm_add_ps(P0, P2); + return P0; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorCatmullRomV +( + FXMVECTOR Position0, + FXMVECTOR Position1, + FXMVECTOR Position2, + GXMVECTOR Position3, + HXMVECTOR T +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + float fx = T.vector4_f32[0]; + float fy = T.vector4_f32[1]; + float fz = T.vector4_f32[2]; + float fw = T.vector4_f32[3]; + XMVECTORF32 vResult = { { { + 0.5f * ((-fx * fx * fx + 2 * fx * fx - fx) * Position0.vector4_f32[0] + + (3 * fx * fx * fx - 5 * fx * fx + 2) * Position1.vector4_f32[0] + + (-3 * fx * fx * fx + 4 * fx * fx + fx) * Position2.vector4_f32[0] + + (fx * fx * fx - fx * fx) * Position3.vector4_f32[0]), + + 0.5f * ((-fy * fy * fy + 2 * fy * fy - fy) * Position0.vector4_f32[1] + + (3 * fy * fy * fy - 5 * fy * fy + 2) * Position1.vector4_f32[1] + + (-3 * fy * fy * fy + 4 * fy * fy + fy) * Position2.vector4_f32[1] + + (fy * fy * fy - fy * fy) * Position3.vector4_f32[1]), + + 0.5f * ((-fz * fz * fz + 2 * fz * fz - fz) * Position0.vector4_f32[2] + + (3 * fz * fz * fz - 5 * fz * fz + 2) * Position1.vector4_f32[2] + + (-3 * fz * fz * fz + 4 * fz * fz + fz) * Position2.vector4_f32[2] + + (fz * fz * fz - fz * fz) * Position3.vector4_f32[2]), + + 0.5f * ((-fw * fw * fw + 2 * fw * fw - fw) * Position0.vector4_f32[3] + + (3 * fw * fw * fw - 5 * fw * fw + 2) * Position1.vector4_f32[3] + + (-3 * fw * fw * fw + 4 * fw * fw + fw) * Position2.vector4_f32[3] + + (fw * fw * fw - fw * fw) * Position3.vector4_f32[3]) + } } }; + return vResult.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + static const XMVECTORF32 Catmul2 = { { { 2.0f, 2.0f, 2.0f, 2.0f } } }; + static const XMVECTORF32 Catmul3 = { { { 3.0f, 3.0f, 3.0f, 3.0f } } }; + static const XMVECTORF32 Catmul4 = { { { 4.0f, 4.0f, 4.0f, 4.0f } } }; + static const XMVECTORF32 Catmul5 = { { { 5.0f, 5.0f, 5.0f, 5.0f } } }; + // Cache T^2 and T^3 + XMVECTOR T2 = vmulq_f32(T, T); + XMVECTOR T3 = vmulq_f32(T, T2); + // Perform the Position0 term + XMVECTOR vResult = vaddq_f32(T2, T2); + vResult = vsubq_f32(vResult, T); + vResult = vsubq_f32(vResult, T3); + vResult = vmulq_f32(vResult, Position0); + // Perform the Position1 term and add + XMVECTOR vTemp = vmulq_f32(T3, Catmul3); + vTemp = vmlsq_f32(vTemp, T2, Catmul5); + vTemp = vaddq_f32(vTemp, Catmul2); + vResult = vmlaq_f32(vResult, vTemp, Position1); + // Perform the Position2 term and add + vTemp = vmulq_f32(T2, Catmul4); + vTemp = vmlsq_f32(vTemp, T3, Catmul3); + vTemp = vaddq_f32(vTemp, T); + vResult = vmlaq_f32(vResult, vTemp, Position2); + // Position3 is the last term + T3 = vsubq_f32(T3, T2); + vResult = vmlaq_f32(vResult, T3, Position3); + // Multiply by 0.5f and exit + vResult = vmulq_f32(vResult, g_XMOneHalf); + return vResult; +#elif defined(_XM_SSE_INTRINSICS_) + static const XMVECTORF32 Catmul2 = { { { 2.0f, 2.0f, 2.0f, 2.0f } } }; + static const XMVECTORF32 Catmul3 = { { { 3.0f, 3.0f, 3.0f, 3.0f } } }; + static const XMVECTORF32 Catmul4 = { { { 4.0f, 4.0f, 4.0f, 4.0f } } }; + static const XMVECTORF32 Catmul5 = { { { 5.0f, 5.0f, 5.0f, 5.0f } } }; + // Cache T^2 and T^3 + XMVECTOR T2 = _mm_mul_ps(T, T); + XMVECTOR T3 = _mm_mul_ps(T, T2); + // Perform the Position0 term + XMVECTOR vResult = _mm_add_ps(T2, T2); + vResult = _mm_sub_ps(vResult, T); + vResult = _mm_sub_ps(vResult, T3); + vResult = _mm_mul_ps(vResult, Position0); + // Perform the Position1 term and add + XMVECTOR vTemp = _mm_mul_ps(T3, Catmul3); + vTemp = XM_FNMADD_PS(T2, Catmul5, vTemp); + vTemp = _mm_add_ps(vTemp, Catmul2); + vResult = XM_FMADD_PS(vTemp, Position1, vResult); + // Perform the Position2 term and add + vTemp = _mm_mul_ps(T2, Catmul4); + vTemp = XM_FNMADD_PS(T3, Catmul3, vTemp); + vTemp = _mm_add_ps(vTemp, T); + vResult = XM_FMADD_PS(vTemp, Position2, vResult); + // Position3 is the last term + T3 = _mm_sub_ps(T3, T2); + vResult = XM_FMADD_PS(T3, Position3, vResult); + // Multiply by 0.5f and exit + vResult = _mm_mul_ps(vResult, g_XMOneHalf); + return vResult; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorBaryCentric +( + FXMVECTOR Position0, + FXMVECTOR Position1, + FXMVECTOR Position2, + float f, + float g +) noexcept +{ + // Result = Position0 + f * (Position1 - Position0) + g * (Position2 - Position0) + +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR P10 = XMVectorSubtract(Position1, Position0); + XMVECTOR ScaleF = XMVectorReplicate(f); + + XMVECTOR P20 = XMVectorSubtract(Position2, Position0); + XMVECTOR ScaleG = XMVectorReplicate(g); + + XMVECTOR Result = XMVectorMultiplyAdd(P10, ScaleF, Position0); + Result = XMVectorMultiplyAdd(P20, ScaleG, Result); + + return Result; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + XMVECTOR R1 = vsubq_f32(Position1, Position0); + XMVECTOR R2 = vsubq_f32(Position2, Position0); + R1 = vmlaq_n_f32(Position0, R1, f); + return vmlaq_n_f32(R1, R2, g); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR R1 = _mm_sub_ps(Position1, Position0); + XMVECTOR R2 = _mm_sub_ps(Position2, Position0); + XMVECTOR SF = _mm_set_ps1(f); + R1 = XM_FMADD_PS(R1, SF, Position0); + XMVECTOR SG = _mm_set_ps1(g); + return XM_FMADD_PS(R2, SG, R1); +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVectorBaryCentricV +( + FXMVECTOR Position0, + FXMVECTOR Position1, + FXMVECTOR Position2, + GXMVECTOR F, + HXMVECTOR G +) noexcept +{ + // Result = Position0 + f * (Position1 - Position0) + g * (Position2 - Position0) + +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR P10 = XMVectorSubtract(Position1, Position0); + XMVECTOR P20 = XMVectorSubtract(Position2, Position0); + + XMVECTOR Result = XMVectorMultiplyAdd(P10, F, Position0); + Result = XMVectorMultiplyAdd(P20, G, Result); + + return Result; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + XMVECTOR R1 = vsubq_f32(Position1, Position0); + XMVECTOR R2 = vsubq_f32(Position2, Position0); + R1 = vmlaq_f32(Position0, R1, F); + return vmlaq_f32(R1, R2, G); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR R1 = _mm_sub_ps(Position1, Position0); + XMVECTOR R2 = _mm_sub_ps(Position2, Position0); + R1 = XM_FMADD_PS(R1, F, Position0); + return XM_FMADD_PS(R2, G, R1); +#endif +} + +/**************************************************************************** + * + * 2D Vector + * + ****************************************************************************/ + +//------------------------------------------------------------------------------ +// Comparison operations +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ + +inline bool XM_CALLCONV XMVector2Equal +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + return (((V1.vector4_f32[0] == V2.vector4_f32[0]) && (V1.vector4_f32[1] == V2.vector4_f32[1])) != 0); +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x2_t vTemp = vceq_f32(vget_low_f32(V1), vget_low_f32(V2)); + return (vget_lane_u64(vreinterpret_u64_u32(vTemp), 0) == 0xFFFFFFFFFFFFFFFFU); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vTemp = _mm_cmpeq_ps(V1, V2); + // z and w are don't care + return (((_mm_movemask_ps(vTemp) & 3) == 3) != 0); +#endif +} + + +//------------------------------------------------------------------------------ + +inline uint32_t XM_CALLCONV XMVector2EqualR +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + uint32_t CR = 0; + if ((V1.vector4_f32[0] == V2.vector4_f32[0]) && + (V1.vector4_f32[1] == V2.vector4_f32[1])) + { + CR = XM_CRMASK_CR6TRUE; + } + else if ((V1.vector4_f32[0] != V2.vector4_f32[0]) && + (V1.vector4_f32[1] != V2.vector4_f32[1])) + { + CR = XM_CRMASK_CR6FALSE; + } + return CR; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x2_t vTemp = vceq_f32(vget_low_f32(V1), vget_low_f32(V2)); + uint64_t r = vget_lane_u64(vreinterpret_u64_u32(vTemp), 0); + uint32_t CR = 0; + if (r == 0xFFFFFFFFFFFFFFFFU) + { + CR = XM_CRMASK_CR6TRUE; + } + else if (!r) + { + CR = XM_CRMASK_CR6FALSE; + } + return CR; +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vTemp = _mm_cmpeq_ps(V1, V2); + // z and w are don't care + int iTest = _mm_movemask_ps(vTemp) & 3; + uint32_t CR = 0; + if (iTest == 3) + { + CR = XM_CRMASK_CR6TRUE; + } + else if (!iTest) + { + CR = XM_CRMASK_CR6FALSE; + } + return CR; +#endif +} + +//------------------------------------------------------------------------------ + +inline bool XM_CALLCONV XMVector2EqualInt +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + return (((V1.vector4_u32[0] == V2.vector4_u32[0]) && (V1.vector4_u32[1] == V2.vector4_u32[1])) != 0); +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x2_t vTemp = vceq_u32(vget_low_u32(vreinterpretq_u32_f32(V1)), vget_low_u32(vreinterpretq_u32_f32(V2))); + return (vget_lane_u64(vreinterpret_u64_u32(vTemp), 0) == 0xFFFFFFFFFFFFFFFFU); +#elif defined(_XM_SSE_INTRINSICS_) + __m128i vTemp = _mm_cmpeq_epi32(_mm_castps_si128(V1), _mm_castps_si128(V2)); + return (((_mm_movemask_ps(_mm_castsi128_ps(vTemp)) & 3) == 3) != 0); +#endif +} + +//------------------------------------------------------------------------------ + +inline uint32_t XM_CALLCONV XMVector2EqualIntR +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + uint32_t CR = 0; + if ((V1.vector4_u32[0] == V2.vector4_u32[0]) && + (V1.vector4_u32[1] == V2.vector4_u32[1])) + { + CR = XM_CRMASK_CR6TRUE; + } + else if ((V1.vector4_u32[0] != V2.vector4_u32[0]) && + (V1.vector4_u32[1] != V2.vector4_u32[1])) + { + CR = XM_CRMASK_CR6FALSE; + } + return CR; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x2_t vTemp = vceq_u32(vget_low_u32(vreinterpretq_u32_f32(V1)), vget_low_u32(vreinterpretq_u32_f32(V2))); + uint64_t r = vget_lane_u64(vreinterpret_u64_u32(vTemp), 0); + uint32_t CR = 0; + if (r == 0xFFFFFFFFFFFFFFFFU) + { + CR = XM_CRMASK_CR6TRUE; + } + else if (!r) + { + CR = XM_CRMASK_CR6FALSE; + } + return CR; +#elif defined(_XM_SSE_INTRINSICS_) + __m128i vTemp = _mm_cmpeq_epi32(_mm_castps_si128(V1), _mm_castps_si128(V2)); + int iTest = _mm_movemask_ps(_mm_castsi128_ps(vTemp)) & 3; + uint32_t CR = 0; + if (iTest == 3) + { + CR = XM_CRMASK_CR6TRUE; + } + else if (!iTest) + { + CR = XM_CRMASK_CR6FALSE; + } + return CR; +#endif +} + +//------------------------------------------------------------------------------ + +inline bool XM_CALLCONV XMVector2NearEqual +( + FXMVECTOR V1, + FXMVECTOR V2, + FXMVECTOR Epsilon +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + float dx = fabsf(V1.vector4_f32[0] - V2.vector4_f32[0]); + float dy = fabsf(V1.vector4_f32[1] - V2.vector4_f32[1]); + return ((dx <= Epsilon.vector4_f32[0]) && + (dy <= Epsilon.vector4_f32[1])); +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x2_t vDelta = vsub_f32(vget_low_f32(V1), vget_low_f32(V2)); +#if defined(_MSC_VER) && !defined(__clang__) && !defined(_ARM64_DISTINCT_NEON_TYPES) + uint32x2_t vTemp = vacle_f32(vDelta, vget_low_u32(Epsilon)); +#else + uint32x2_t vTemp = vcle_f32(vabs_f32(vDelta), vget_low_f32(Epsilon)); +#endif + uint64_t r = vget_lane_u64(vreinterpret_u64_u32(vTemp), 0); + return (r == 0xFFFFFFFFFFFFFFFFU); +#elif defined(_XM_SSE_INTRINSICS_) + // Get the difference + XMVECTOR vDelta = _mm_sub_ps(V1, V2); + // Get the absolute value of the difference + XMVECTOR vTemp = _mm_setzero_ps(); + vTemp = _mm_sub_ps(vTemp, vDelta); + vTemp = _mm_max_ps(vTemp, vDelta); + vTemp = _mm_cmple_ps(vTemp, Epsilon); + // z and w are don't care + return (((_mm_movemask_ps(vTemp) & 3) == 0x3) != 0); +#endif +} + +//------------------------------------------------------------------------------ + +inline bool XM_CALLCONV XMVector2NotEqual +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + return (((V1.vector4_f32[0] != V2.vector4_f32[0]) || (V1.vector4_f32[1] != V2.vector4_f32[1])) != 0); +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x2_t vTemp = vceq_f32(vget_low_f32(V1), vget_low_f32(V2)); + return (vget_lane_u64(vreinterpret_u64_u32(vTemp), 0) != 0xFFFFFFFFFFFFFFFFU); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vTemp = _mm_cmpeq_ps(V1, V2); + // z and w are don't care + return (((_mm_movemask_ps(vTemp) & 3) != 3) != 0); +#endif +} + +//------------------------------------------------------------------------------ + +inline bool XM_CALLCONV XMVector2NotEqualInt +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + return (((V1.vector4_u32[0] != V2.vector4_u32[0]) || (V1.vector4_u32[1] != V2.vector4_u32[1])) != 0); +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x2_t vTemp = vceq_u32(vget_low_u32(vreinterpretq_u32_f32(V1)), vget_low_u32(vreinterpretq_u32_f32(V2))); + return (vget_lane_u64(vreinterpret_u64_u32(vTemp), 0) != 0xFFFFFFFFFFFFFFFFU); +#elif defined(_XM_SSE_INTRINSICS_) + __m128i vTemp = _mm_cmpeq_epi32(_mm_castps_si128(V1), _mm_castps_si128(V2)); + return (((_mm_movemask_ps(_mm_castsi128_ps(vTemp)) & 3) != 3) != 0); +#endif +} + +//------------------------------------------------------------------------------ + +inline bool XM_CALLCONV XMVector2Greater +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + return (((V1.vector4_f32[0] > V2.vector4_f32[0]) && (V1.vector4_f32[1] > V2.vector4_f32[1])) != 0); +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x2_t vTemp = vcgt_f32(vget_low_f32(V1), vget_low_f32(V2)); + return (vget_lane_u64(vreinterpret_u64_u32(vTemp), 0) == 0xFFFFFFFFFFFFFFFFU); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vTemp = _mm_cmpgt_ps(V1, V2); + // z and w are don't care + return (((_mm_movemask_ps(vTemp) & 3) == 3) != 0); +#endif +} + +//------------------------------------------------------------------------------ + +inline uint32_t XM_CALLCONV XMVector2GreaterR +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + uint32_t CR = 0; + if ((V1.vector4_f32[0] > V2.vector4_f32[0]) && + (V1.vector4_f32[1] > V2.vector4_f32[1])) + { + CR = XM_CRMASK_CR6TRUE; + } + else if ((V1.vector4_f32[0] <= V2.vector4_f32[0]) && + (V1.vector4_f32[1] <= V2.vector4_f32[1])) + { + CR = XM_CRMASK_CR6FALSE; + } + return CR; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x2_t vTemp = vcgt_f32(vget_low_f32(V1), vget_low_f32(V2)); + uint64_t r = vget_lane_u64(vreinterpret_u64_u32(vTemp), 0); + uint32_t CR = 0; + if (r == 0xFFFFFFFFFFFFFFFFU) + { + CR = XM_CRMASK_CR6TRUE; + } + else if (!r) + { + CR = XM_CRMASK_CR6FALSE; + } + return CR; +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vTemp = _mm_cmpgt_ps(V1, V2); + int iTest = _mm_movemask_ps(vTemp) & 3; + uint32_t CR = 0; + if (iTest == 3) + { + CR = XM_CRMASK_CR6TRUE; + } + else if (!iTest) + { + CR = XM_CRMASK_CR6FALSE; + } + return CR; +#endif +} + +//------------------------------------------------------------------------------ + +inline bool XM_CALLCONV XMVector2GreaterOrEqual +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + return (((V1.vector4_f32[0] >= V2.vector4_f32[0]) && (V1.vector4_f32[1] >= V2.vector4_f32[1])) != 0); +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x2_t vTemp = vcge_f32(vget_low_f32(V1), vget_low_f32(V2)); + return (vget_lane_u64(vreinterpret_u64_u32(vTemp), 0) == 0xFFFFFFFFFFFFFFFFU); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vTemp = _mm_cmpge_ps(V1, V2); + return (((_mm_movemask_ps(vTemp) & 3) == 3) != 0); +#endif +} + +//------------------------------------------------------------------------------ + +inline uint32_t XM_CALLCONV XMVector2GreaterOrEqualR +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + uint32_t CR = 0; + if ((V1.vector4_f32[0] >= V2.vector4_f32[0]) && + (V1.vector4_f32[1] >= V2.vector4_f32[1])) + { + CR = XM_CRMASK_CR6TRUE; + } + else if ((V1.vector4_f32[0] < V2.vector4_f32[0]) && + (V1.vector4_f32[1] < V2.vector4_f32[1])) + { + CR = XM_CRMASK_CR6FALSE; + } + return CR; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x2_t vTemp = vcge_f32(vget_low_f32(V1), vget_low_f32(V2)); + uint64_t r = vget_lane_u64(vreinterpret_u64_u32(vTemp), 0); + uint32_t CR = 0; + if (r == 0xFFFFFFFFFFFFFFFFU) + { + CR = XM_CRMASK_CR6TRUE; + } + else if (!r) + { + CR = XM_CRMASK_CR6FALSE; + } + return CR; +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vTemp = _mm_cmpge_ps(V1, V2); + int iTest = _mm_movemask_ps(vTemp) & 3; + uint32_t CR = 0; + if (iTest == 3) + { + CR = XM_CRMASK_CR6TRUE; + } + else if (!iTest) + { + CR = XM_CRMASK_CR6FALSE; + } + return CR; +#endif +} + +//------------------------------------------------------------------------------ + +inline bool XM_CALLCONV XMVector2Less +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + return (((V1.vector4_f32[0] < V2.vector4_f32[0]) && (V1.vector4_f32[1] < V2.vector4_f32[1])) != 0); +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x2_t vTemp = vclt_f32(vget_low_f32(V1), vget_low_f32(V2)); + return (vget_lane_u64(vreinterpret_u64_u32(vTemp), 0) == 0xFFFFFFFFFFFFFFFFU); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vTemp = _mm_cmplt_ps(V1, V2); + return (((_mm_movemask_ps(vTemp) & 3) == 3) != 0); +#endif +} + +//------------------------------------------------------------------------------ + +inline bool XM_CALLCONV XMVector2LessOrEqual +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + return (((V1.vector4_f32[0] <= V2.vector4_f32[0]) && (V1.vector4_f32[1] <= V2.vector4_f32[1])) != 0); +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x2_t vTemp = vcle_f32(vget_low_f32(V1), vget_low_f32(V2)); + return (vget_lane_u64(vreinterpret_u64_u32(vTemp), 0) == 0xFFFFFFFFFFFFFFFFU); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vTemp = _mm_cmple_ps(V1, V2); + return (((_mm_movemask_ps(vTemp) & 3) == 3) != 0); +#endif +} + +//------------------------------------------------------------------------------ + +inline bool XM_CALLCONV XMVector2InBounds +( + FXMVECTOR V, + FXMVECTOR Bounds +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + return (((V.vector4_f32[0] <= Bounds.vector4_f32[0] && V.vector4_f32[0] >= -Bounds.vector4_f32[0]) && + (V.vector4_f32[1] <= Bounds.vector4_f32[1] && V.vector4_f32[1] >= -Bounds.vector4_f32[1])) != 0); +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x2_t VL = vget_low_f32(V); + float32x2_t B = vget_low_f32(Bounds); + // Test if less than or equal + uint32x2_t ivTemp1 = vcle_f32(VL, B); + // Negate the bounds + float32x2_t vTemp2 = vneg_f32(B); + // Test if greater or equal (Reversed) + uint32x2_t ivTemp2 = vcle_f32(vTemp2, VL); + // Blend answers + ivTemp1 = vand_u32(ivTemp1, ivTemp2); + // x and y in bounds? + return (vget_lane_u64(vreinterpret_u64_u32(ivTemp1), 0) == 0xFFFFFFFFFFFFFFFFU); +#elif defined(_XM_SSE_INTRINSICS_) + // Test if less than or equal + XMVECTOR vTemp1 = _mm_cmple_ps(V, Bounds); + // Negate the bounds + XMVECTOR vTemp2 = _mm_mul_ps(Bounds, g_XMNegativeOne); + // Test if greater or equal (Reversed) + vTemp2 = _mm_cmple_ps(vTemp2, V); + // Blend answers + vTemp1 = _mm_and_ps(vTemp1, vTemp2); + // x and y in bounds? (z and w are don't care) + return (((_mm_movemask_ps(vTemp1) & 0x3) == 0x3) != 0); +#endif +} + +//------------------------------------------------------------------------------ + +#if !defined(_XM_NO_INTRINSICS_) && defined(_MSC_VER) && !defined(__INTEL_COMPILER) +#pragma float_control(push) +#pragma float_control(precise, on) +#endif + +inline bool XM_CALLCONV XMVector2IsNaN(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + return (XMISNAN(V.vector4_f32[0]) || + XMISNAN(V.vector4_f32[1])); +#elif defined(_XM_ARM_NEON_INTRINSICS_) +#if defined(__clang__) && defined(__FINITE_MATH_ONLY__) + return isnan(vgetq_lane_f32(V, 0)) || isnan(vgetq_lane_f32(V, 1)); +#else + float32x2_t VL = vget_low_f32(V); + // Test against itself. NaN is always not equal + uint32x2_t vTempNan = vceq_f32(VL, VL); + // If x or y are NaN, the mask is zero + return (vget_lane_u64(vreinterpret_u64_u32(vTempNan), 0) != 0xFFFFFFFFFFFFFFFFU); +#endif +#elif defined(_XM_SSE_INTRINSICS_) +#if defined(__clang__) && defined(__FINITE_MATH_ONLY__) + XM_ALIGNED_DATA(16) float tmp[4]; + _mm_store_ps(tmp, V); + return isnan(tmp[0]) || isnan(tmp[1]); +#else +// Test against itself. NaN is always not equal + XMVECTOR vTempNan = _mm_cmpneq_ps(V, V); + // If x or y are NaN, the mask is non-zero + return ((_mm_movemask_ps(vTempNan) & 3) != 0); +#endif +#endif +} + +#if !defined(_XM_NO_INTRINSICS_) && defined(_MSC_VER) && !defined(__INTEL_COMPILER) +#pragma float_control(pop) +#endif + +//------------------------------------------------------------------------------ + +inline bool XM_CALLCONV XMVector2IsInfinite(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + return (XMISINF(V.vector4_f32[0]) || + XMISINF(V.vector4_f32[1])); +#elif defined(_XM_ARM_NEON_INTRINSICS_) + // Mask off the sign bit + uint32x2_t vTemp = vand_u32(vget_low_u32(vreinterpretq_u32_f32(V)), vget_low_u32(g_XMAbsMask)); + // Compare to infinity + vTemp = vceq_f32(vreinterpret_f32_u32(vTemp), vget_low_f32(g_XMInfinity)); + // If any are infinity, the signs are true. + return vget_lane_u64(vreinterpret_u64_u32(vTemp), 0) != 0; +#elif defined(_XM_SSE_INTRINSICS_) + // Mask off the sign bit + __m128 vTemp = _mm_and_ps(V, g_XMAbsMask); + // Compare to infinity + vTemp = _mm_cmpeq_ps(vTemp, g_XMInfinity); + // If x or z are infinity, the signs are true. + return ((_mm_movemask_ps(vTemp) & 3) != 0); +#endif +} + +//------------------------------------------------------------------------------ +// Computation operations +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector2Dot +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTORF32 Result; + Result.f[0] = + Result.f[1] = + Result.f[2] = + Result.f[3] = V1.vector4_f32[0] * V2.vector4_f32[0] + V1.vector4_f32[1] * V2.vector4_f32[1]; + return Result.v; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + // Perform the dot product on x and y + float32x2_t vTemp = vmul_f32(vget_low_f32(V1), vget_low_f32(V2)); + vTemp = vpadd_f32(vTemp, vTemp); + return vcombine_f32(vTemp, vTemp); +#elif defined(_XM_SSE4_INTRINSICS_) + return _mm_dp_ps(V1, V2, 0x3f); +#elif defined(_XM_SSE3_INTRINSICS_) + XMVECTOR vDot = _mm_mul_ps(V1, V2); + vDot = _mm_hadd_ps(vDot, vDot); + vDot = _mm_moveldup_ps(vDot); + return vDot; +#elif defined(_XM_SSE_INTRINSICS_) + // Perform the dot product on x and y + XMVECTOR vLengthSq = _mm_mul_ps(V1, V2); + // vTemp has y splatted + XMVECTOR vTemp = XM_PERMUTE_PS(vLengthSq, _MM_SHUFFLE(1, 1, 1, 1)); + // x+y + vLengthSq = _mm_add_ss(vLengthSq, vTemp); + vLengthSq = XM_PERMUTE_PS(vLengthSq, _MM_SHUFFLE(0, 0, 0, 0)); + return vLengthSq; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector2Cross +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ + // [ V1.x*V2.y - V1.y*V2.x, V1.x*V2.y - V1.y*V2.x ] + +#if defined(_XM_NO_INTRINSICS_) + float fCross = (V1.vector4_f32[0] * V2.vector4_f32[1]) - (V1.vector4_f32[1] * V2.vector4_f32[0]); + XMVECTORF32 vResult; + vResult.f[0] = + vResult.f[1] = + vResult.f[2] = + vResult.f[3] = fCross; + return vResult.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + static const XMVECTORF32 Negate = { { { 1.f, -1.f, 0, 0 } } }; + + float32x2_t vTemp = vmul_f32(vget_low_f32(V1), vrev64_f32(vget_low_f32(V2))); + vTemp = vmul_f32(vTemp, vget_low_f32(Negate)); + vTemp = vpadd_f32(vTemp, vTemp); + return vcombine_f32(vTemp, vTemp); +#elif defined(_XM_SSE_INTRINSICS_) + // Swap x and y + XMVECTOR vResult = XM_PERMUTE_PS(V2, _MM_SHUFFLE(0, 1, 0, 1)); + // Perform the muls + vResult = _mm_mul_ps(vResult, V1); + // Splat y + XMVECTOR vTemp = XM_PERMUTE_PS(vResult, _MM_SHUFFLE(1, 1, 1, 1)); + // Sub the values + vResult = _mm_sub_ss(vResult, vTemp); + // Splat the cross product + vResult = XM_PERMUTE_PS(vResult, _MM_SHUFFLE(0, 0, 0, 0)); + return vResult; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector2LengthSq(FXMVECTOR V) noexcept +{ + return XMVector2Dot(V, V); +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector2ReciprocalLengthEst(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR Result; + Result = XMVector2LengthSq(V); + Result = XMVectorReciprocalSqrtEst(Result); + return Result; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x2_t VL = vget_low_f32(V); + // Dot2 + float32x2_t vTemp = vmul_f32(VL, VL); + vTemp = vpadd_f32(vTemp, vTemp); + // Reciprocal sqrt (estimate) + vTemp = vrsqrte_f32(vTemp); + return vcombine_f32(vTemp, vTemp); +#elif defined(_XM_SSE4_INTRINSICS_) + XMVECTOR vTemp = _mm_dp_ps(V, V, 0x3f); + return _mm_rsqrt_ps(vTemp); +#elif defined(_XM_SSE3_INTRINSICS_) + XMVECTOR vLengthSq = _mm_mul_ps(V, V); + XMVECTOR vTemp = _mm_hadd_ps(vLengthSq, vLengthSq); + vLengthSq = _mm_rsqrt_ss(vTemp); + vLengthSq = XM_PERMUTE_PS(vLengthSq, _MM_SHUFFLE(0, 0, 0, 0)); + return vLengthSq; +#elif defined(_XM_SSE_INTRINSICS_) + // Perform the dot product on x and y + XMVECTOR vLengthSq = _mm_mul_ps(V, V); + // vTemp has y splatted + XMVECTOR vTemp = XM_PERMUTE_PS(vLengthSq, _MM_SHUFFLE(1, 1, 1, 1)); + // x+y + vLengthSq = _mm_add_ss(vLengthSq, vTemp); + vLengthSq = _mm_rsqrt_ss(vLengthSq); + vLengthSq = XM_PERMUTE_PS(vLengthSq, _MM_SHUFFLE(0, 0, 0, 0)); + return vLengthSq; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector2ReciprocalLength(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR Result; + Result = XMVector2LengthSq(V); + Result = XMVectorReciprocalSqrt(Result); + return Result; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x2_t VL = vget_low_f32(V); + // Dot2 + float32x2_t vTemp = vmul_f32(VL, VL); + vTemp = vpadd_f32(vTemp, vTemp); + // Reciprocal sqrt + float32x2_t S0 = vrsqrte_f32(vTemp); + float32x2_t P0 = vmul_f32(vTemp, S0); + float32x2_t R0 = vrsqrts_f32(P0, S0); + float32x2_t S1 = vmul_f32(S0, R0); + float32x2_t P1 = vmul_f32(vTemp, S1); + float32x2_t R1 = vrsqrts_f32(P1, S1); + float32x2_t Result = vmul_f32(S1, R1); + return vcombine_f32(Result, Result); +#elif defined(_XM_SSE4_INTRINSICS_) + XMVECTOR vTemp = _mm_dp_ps(V, V, 0x3f); + XMVECTOR vLengthSq = _mm_sqrt_ps(vTemp); + return _mm_div_ps(g_XMOne, vLengthSq); +#elif defined(_XM_SSE3_INTRINSICS_) + XMVECTOR vLengthSq = _mm_mul_ps(V, V); + XMVECTOR vTemp = _mm_hadd_ps(vLengthSq, vLengthSq); + vLengthSq = _mm_sqrt_ss(vTemp); + vLengthSq = _mm_div_ss(g_XMOne, vLengthSq); + vLengthSq = XM_PERMUTE_PS(vLengthSq, _MM_SHUFFLE(0, 0, 0, 0)); + return vLengthSq; +#elif defined(_XM_SSE_INTRINSICS_) + // Perform the dot product on x and y + XMVECTOR vLengthSq = _mm_mul_ps(V, V); + // vTemp has y splatted + XMVECTOR vTemp = XM_PERMUTE_PS(vLengthSq, _MM_SHUFFLE(1, 1, 1, 1)); + // x+y + vLengthSq = _mm_add_ss(vLengthSq, vTemp); + vLengthSq = _mm_sqrt_ss(vLengthSq); + vLengthSq = _mm_div_ss(g_XMOne, vLengthSq); + vLengthSq = XM_PERMUTE_PS(vLengthSq, _MM_SHUFFLE(0, 0, 0, 0)); + return vLengthSq; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector2LengthEst(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR Result; + Result = XMVector2LengthSq(V); + Result = XMVectorSqrtEst(Result); + return Result; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x2_t VL = vget_low_f32(V); + // Dot2 + float32x2_t vTemp = vmul_f32(VL, VL); + vTemp = vpadd_f32(vTemp, vTemp); + const float32x2_t zero = vdup_n_f32(0); + uint32x2_t VEqualsZero = vceq_f32(vTemp, zero); + // Sqrt (estimate) + float32x2_t Result = vrsqrte_f32(vTemp); + Result = vmul_f32(vTemp, Result); + Result = vbsl_f32(VEqualsZero, zero, Result); + return vcombine_f32(Result, Result); +#elif defined(_XM_SSE4_INTRINSICS_) + XMVECTOR vTemp = _mm_dp_ps(V, V, 0x3f); + return _mm_sqrt_ps(vTemp); +#elif defined(_XM_SSE3_INTRINSICS_) + XMVECTOR vLengthSq = _mm_mul_ps(V, V); + XMVECTOR vTemp = _mm_hadd_ps(vLengthSq, vLengthSq); + vLengthSq = _mm_sqrt_ss(vTemp); + vLengthSq = XM_PERMUTE_PS(vLengthSq, _MM_SHUFFLE(0, 0, 0, 0)); + return vLengthSq; +#elif defined(_XM_SSE_INTRINSICS_) + // Perform the dot product on x and y + XMVECTOR vLengthSq = _mm_mul_ps(V, V); + // vTemp has y splatted + XMVECTOR vTemp = XM_PERMUTE_PS(vLengthSq, _MM_SHUFFLE(1, 1, 1, 1)); + // x+y + vLengthSq = _mm_add_ss(vLengthSq, vTemp); + vLengthSq = _mm_sqrt_ss(vLengthSq); + vLengthSq = XM_PERMUTE_PS(vLengthSq, _MM_SHUFFLE(0, 0, 0, 0)); + return vLengthSq; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector2Length(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR Result; + Result = XMVector2LengthSq(V); + Result = XMVectorSqrt(Result); + return Result; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x2_t VL = vget_low_f32(V); + // Dot2 + float32x2_t vTemp = vmul_f32(VL, VL); + vTemp = vpadd_f32(vTemp, vTemp); + const float32x2_t zero = vdup_n_f32(0); + uint32x2_t VEqualsZero = vceq_f32(vTemp, zero); + // Sqrt + float32x2_t S0 = vrsqrte_f32(vTemp); + float32x2_t P0 = vmul_f32(vTemp, S0); + float32x2_t R0 = vrsqrts_f32(P0, S0); + float32x2_t S1 = vmul_f32(S0, R0); + float32x2_t P1 = vmul_f32(vTemp, S1); + float32x2_t R1 = vrsqrts_f32(P1, S1); + float32x2_t Result = vmul_f32(S1, R1); + Result = vmul_f32(vTemp, Result); + Result = vbsl_f32(VEqualsZero, zero, Result); + return vcombine_f32(Result, Result); +#elif defined(_XM_SSE4_INTRINSICS_) + XMVECTOR vTemp = _mm_dp_ps(V, V, 0x3f); + return _mm_sqrt_ps(vTemp); +#elif defined(_XM_SSE3_INTRINSICS_) + XMVECTOR vLengthSq = _mm_mul_ps(V, V); + XMVECTOR vTemp = _mm_hadd_ps(vLengthSq, vLengthSq); + vLengthSq = _mm_sqrt_ss(vTemp); + vLengthSq = XM_PERMUTE_PS(vLengthSq, _MM_SHUFFLE(0, 0, 0, 0)); + return vLengthSq; +#elif defined(_XM_SSE_INTRINSICS_) + // Perform the dot product on x and y + XMVECTOR vLengthSq = _mm_mul_ps(V, V); + // vTemp has y splatted + XMVECTOR vTemp = XM_PERMUTE_PS(vLengthSq, _MM_SHUFFLE(1, 1, 1, 1)); + // x+y + vLengthSq = _mm_add_ss(vLengthSq, vTemp); + vLengthSq = XM_PERMUTE_PS(vLengthSq, _MM_SHUFFLE(0, 0, 0, 0)); + vLengthSq = _mm_sqrt_ps(vLengthSq); + return vLengthSq; +#endif +} + +//------------------------------------------------------------------------------ +// XMVector2NormalizeEst uses a reciprocal estimate and +// returns QNaN on zero and infinite vectors. + +inline XMVECTOR XM_CALLCONV XMVector2NormalizeEst(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR Result; + Result = XMVector2ReciprocalLength(V); + Result = XMVectorMultiply(V, Result); + return Result; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x2_t VL = vget_low_f32(V); + // Dot2 + float32x2_t vTemp = vmul_f32(VL, VL); + vTemp = vpadd_f32(vTemp, vTemp); + // Reciprocal sqrt (estimate) + vTemp = vrsqrte_f32(vTemp); + // Normalize + float32x2_t Result = vmul_f32(VL, vTemp); + return vcombine_f32(Result, Result); +#elif defined(_XM_SSE4_INTRINSICS_) + XMVECTOR vTemp = _mm_dp_ps(V, V, 0x3f); + XMVECTOR vResult = _mm_rsqrt_ps(vTemp); + return _mm_mul_ps(vResult, V); +#elif defined(_XM_SSE3_INTRINSICS_) + XMVECTOR vLengthSq = _mm_mul_ps(V, V); + vLengthSq = _mm_hadd_ps(vLengthSq, vLengthSq); + vLengthSq = _mm_rsqrt_ss(vLengthSq); + vLengthSq = XM_PERMUTE_PS(vLengthSq, _MM_SHUFFLE(0, 0, 0, 0)); + vLengthSq = _mm_mul_ps(vLengthSq, V); + return vLengthSq; +#elif defined(_XM_SSE_INTRINSICS_) + // Perform the dot product on x and y + XMVECTOR vLengthSq = _mm_mul_ps(V, V); + // vTemp has y splatted + XMVECTOR vTemp = XM_PERMUTE_PS(vLengthSq, _MM_SHUFFLE(1, 1, 1, 1)); + // x+y + vLengthSq = _mm_add_ss(vLengthSq, vTemp); + vLengthSq = _mm_rsqrt_ss(vLengthSq); + vLengthSq = XM_PERMUTE_PS(vLengthSq, _MM_SHUFFLE(0, 0, 0, 0)); + vLengthSq = _mm_mul_ps(vLengthSq, V); + return vLengthSq; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector2Normalize(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR vResult = XMVector2Length(V); + float fLength = vResult.vector4_f32[0]; + + // Prevent divide by zero + if (fLength > 0) + { + fLength = 1.0f / fLength; + } + + vResult.vector4_f32[0] = V.vector4_f32[0] * fLength; + vResult.vector4_f32[1] = V.vector4_f32[1] * fLength; + vResult.vector4_f32[2] = V.vector4_f32[2] * fLength; + vResult.vector4_f32[3] = V.vector4_f32[3] * fLength; + return vResult; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x2_t VL = vget_low_f32(V); + // Dot2 + float32x2_t vTemp = vmul_f32(VL, VL); + vTemp = vpadd_f32(vTemp, vTemp); + uint32x2_t VEqualsZero = vceq_f32(vTemp, vdup_n_f32(0)); + uint32x2_t VEqualsInf = vceq_f32(vTemp, vget_low_f32(g_XMInfinity)); + // Reciprocal sqrt (2 iterations of Newton-Raphson) + float32x2_t S0 = vrsqrte_f32(vTemp); + float32x2_t P0 = vmul_f32(vTemp, S0); + float32x2_t R0 = vrsqrts_f32(P0, S0); + float32x2_t S1 = vmul_f32(S0, R0); + float32x2_t P1 = vmul_f32(vTemp, S1); + float32x2_t R1 = vrsqrts_f32(P1, S1); + vTemp = vmul_f32(S1, R1); + // Normalize + float32x2_t Result = vmul_f32(VL, vTemp); + Result = vbsl_f32(VEqualsZero, vdup_n_f32(0), Result); + Result = vbsl_f32(VEqualsInf, vget_low_f32(g_XMQNaN), Result); + return vcombine_f32(Result, Result); +#elif defined(_XM_SSE4_INTRINSICS_) + XMVECTOR vLengthSq = _mm_dp_ps(V, V, 0x3f); + // Prepare for the division + XMVECTOR vResult = _mm_sqrt_ps(vLengthSq); + // Create zero with a single instruction + XMVECTOR vZeroMask = _mm_setzero_ps(); + // Test for a divide by zero (Must be FP to detect -0.0) + vZeroMask = _mm_cmpneq_ps(vZeroMask, vResult); + // Failsafe on zero (Or epsilon) length planes + // If the length is infinity, set the elements to zero + vLengthSq = _mm_cmpneq_ps(vLengthSq, g_XMInfinity); + // Reciprocal mul to perform the normalization + vResult = _mm_div_ps(V, vResult); + // Any that are infinity, set to zero + vResult = _mm_and_ps(vResult, vZeroMask); + // Select qnan or result based on infinite length + XMVECTOR vTemp1 = _mm_andnot_ps(vLengthSq, g_XMQNaN); + XMVECTOR vTemp2 = _mm_and_ps(vResult, vLengthSq); + vResult = _mm_or_ps(vTemp1, vTemp2); + return vResult; +#elif defined(_XM_SSE3_INTRINSICS_) + // Perform the dot product on x and y only + XMVECTOR vLengthSq = _mm_mul_ps(V, V); + vLengthSq = _mm_hadd_ps(vLengthSq, vLengthSq); + vLengthSq = _mm_moveldup_ps(vLengthSq); + // Prepare for the division + XMVECTOR vResult = _mm_sqrt_ps(vLengthSq); + // Create zero with a single instruction + XMVECTOR vZeroMask = _mm_setzero_ps(); + // Test for a divide by zero (Must be FP to detect -0.0) + vZeroMask = _mm_cmpneq_ps(vZeroMask, vResult); + // Failsafe on zero (Or epsilon) length planes + // If the length is infinity, set the elements to zero + vLengthSq = _mm_cmpneq_ps(vLengthSq, g_XMInfinity); + // Reciprocal mul to perform the normalization + vResult = _mm_div_ps(V, vResult); + // Any that are infinity, set to zero + vResult = _mm_and_ps(vResult, vZeroMask); + // Select qnan or result based on infinite length + XMVECTOR vTemp1 = _mm_andnot_ps(vLengthSq, g_XMQNaN); + XMVECTOR vTemp2 = _mm_and_ps(vResult, vLengthSq); + vResult = _mm_or_ps(vTemp1, vTemp2); + return vResult; +#elif defined(_XM_SSE_INTRINSICS_) + // Perform the dot product on x and y only + XMVECTOR vLengthSq = _mm_mul_ps(V, V); + XMVECTOR vTemp = XM_PERMUTE_PS(vLengthSq, _MM_SHUFFLE(1, 1, 1, 1)); + vLengthSq = _mm_add_ss(vLengthSq, vTemp); + vLengthSq = XM_PERMUTE_PS(vLengthSq, _MM_SHUFFLE(0, 0, 0, 0)); + // Prepare for the division + XMVECTOR vResult = _mm_sqrt_ps(vLengthSq); + // Create zero with a single instruction + XMVECTOR vZeroMask = _mm_setzero_ps(); + // Test for a divide by zero (Must be FP to detect -0.0) + vZeroMask = _mm_cmpneq_ps(vZeroMask, vResult); + // Failsafe on zero (Or epsilon) length planes + // If the length is infinity, set the elements to zero + vLengthSq = _mm_cmpneq_ps(vLengthSq, g_XMInfinity); + // Reciprocal mul to perform the normalization + vResult = _mm_div_ps(V, vResult); + // Any that are infinity, set to zero + vResult = _mm_and_ps(vResult, vZeroMask); + // Select qnan or result based on infinite length + XMVECTOR vTemp1 = _mm_andnot_ps(vLengthSq, g_XMQNaN); + XMVECTOR vTemp2 = _mm_and_ps(vResult, vLengthSq); + vResult = _mm_or_ps(vTemp1, vTemp2); + return vResult; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector2ClampLength +( + FXMVECTOR V, + float LengthMin, + float LengthMax +) noexcept +{ + XMVECTOR ClampMax = XMVectorReplicate(LengthMax); + XMVECTOR ClampMin = XMVectorReplicate(LengthMin); + return XMVector2ClampLengthV(V, ClampMin, ClampMax); +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector2ClampLengthV +( + FXMVECTOR V, + FXMVECTOR LengthMin, + FXMVECTOR LengthMax +) noexcept +{ + assert((XMVectorGetY(LengthMin) == XMVectorGetX(LengthMin))); + assert((XMVectorGetY(LengthMax) == XMVectorGetX(LengthMax))); + assert(XMVector2GreaterOrEqual(LengthMin, g_XMZero)); + assert(XMVector2GreaterOrEqual(LengthMax, g_XMZero)); + assert(XMVector2GreaterOrEqual(LengthMax, LengthMin)); + + XMVECTOR LengthSq = XMVector2LengthSq(V); + + const XMVECTOR Zero = XMVectorZero(); + + XMVECTOR RcpLength = XMVectorReciprocalSqrt(LengthSq); + + XMVECTOR InfiniteLength = XMVectorEqualInt(LengthSq, g_XMInfinity.v); + XMVECTOR ZeroLength = XMVectorEqual(LengthSq, Zero); + + XMVECTOR Length = XMVectorMultiply(LengthSq, RcpLength); + + XMVECTOR Normal = XMVectorMultiply(V, RcpLength); + + XMVECTOR Select = XMVectorEqualInt(InfiniteLength, ZeroLength); + Length = XMVectorSelect(LengthSq, Length, Select); + Normal = XMVectorSelect(LengthSq, Normal, Select); + + XMVECTOR ControlMax = XMVectorGreater(Length, LengthMax); + XMVECTOR ControlMin = XMVectorLess(Length, LengthMin); + + XMVECTOR ClampLength = XMVectorSelect(Length, LengthMax, ControlMax); + ClampLength = XMVectorSelect(ClampLength, LengthMin, ControlMin); + + XMVECTOR Result = XMVectorMultiply(Normal, ClampLength); + + // Preserve the original vector (with no precision loss) if the length falls within the given range + XMVECTOR Control = XMVectorEqualInt(ControlMax, ControlMin); + Result = XMVectorSelect(Result, V, Control); + + return Result; +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector2Reflect +( + FXMVECTOR Incident, + FXMVECTOR Normal +) noexcept +{ + // Result = Incident - (2 * dot(Incident, Normal)) * Normal + + XMVECTOR Result; + Result = XMVector2Dot(Incident, Normal); + Result = XMVectorAdd(Result, Result); + Result = XMVectorNegativeMultiplySubtract(Result, Normal, Incident); + return Result; +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector2Refract +( + FXMVECTOR Incident, + FXMVECTOR Normal, + float RefractionIndex +) noexcept +{ + XMVECTOR Index = XMVectorReplicate(RefractionIndex); + return XMVector2RefractV(Incident, Normal, Index); +} + +//------------------------------------------------------------------------------ + +// Return the refraction of a 2D vector +inline XMVECTOR XM_CALLCONV XMVector2RefractV +( + FXMVECTOR Incident, + FXMVECTOR Normal, + FXMVECTOR RefractionIndex +) noexcept +{ + // Result = RefractionIndex * Incident - Normal * (RefractionIndex * dot(Incident, Normal) + + // sqrt(1 - RefractionIndex * RefractionIndex * (1 - dot(Incident, Normal) * dot(Incident, Normal)))) + +#if defined(_XM_NO_INTRINSICS_) + + float IDotN = (Incident.vector4_f32[0] * Normal.vector4_f32[0]) + (Incident.vector4_f32[1] * Normal.vector4_f32[1]); + // R = 1.0f - RefractionIndex * RefractionIndex * (1.0f - IDotN * IDotN) + float RY = 1.0f - (IDotN * IDotN); + float RX = 1.0f - (RY * RefractionIndex.vector4_f32[0] * RefractionIndex.vector4_f32[0]); + RY = 1.0f - (RY * RefractionIndex.vector4_f32[1] * RefractionIndex.vector4_f32[1]); + if (RX >= 0.0f) + { + RX = (RefractionIndex.vector4_f32[0] * Incident.vector4_f32[0]) - (Normal.vector4_f32[0] * ((RefractionIndex.vector4_f32[0] * IDotN) + sqrtf(RX))); + } + else + { + RX = 0.0f; + } + if (RY >= 0.0f) + { + RY = (RefractionIndex.vector4_f32[1] * Incident.vector4_f32[1]) - (Normal.vector4_f32[1] * ((RefractionIndex.vector4_f32[1] * IDotN) + sqrtf(RY))); + } + else + { + RY = 0.0f; + } + + XMVECTOR vResult; + vResult.vector4_f32[0] = RX; + vResult.vector4_f32[1] = RY; + vResult.vector4_f32[2] = 0.0f; + vResult.vector4_f32[3] = 0.0f; + return vResult; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x2_t IL = vget_low_f32(Incident); + float32x2_t NL = vget_low_f32(Normal); + float32x2_t RIL = vget_low_f32(RefractionIndex); + // Get the 2D Dot product of Incident-Normal + float32x2_t vTemp = vmul_f32(IL, NL); + float32x2_t IDotN = vpadd_f32(vTemp, vTemp); + // vTemp = 1.0f - RefractionIndex * RefractionIndex * (1.0f - IDotN * IDotN) + vTemp = vmls_f32(vget_low_f32(g_XMOne), IDotN, IDotN); + vTemp = vmul_f32(vTemp, RIL); + vTemp = vmls_f32(vget_low_f32(g_XMOne), vTemp, RIL); + // If any terms are <=0, sqrt() will fail, punt to zero + uint32x2_t vMask = vcgt_f32(vTemp, vget_low_f32(g_XMZero)); + // Sqrt(vTemp) + float32x2_t S0 = vrsqrte_f32(vTemp); + float32x2_t P0 = vmul_f32(vTemp, S0); + float32x2_t R0 = vrsqrts_f32(P0, S0); + float32x2_t S1 = vmul_f32(S0, R0); + float32x2_t P1 = vmul_f32(vTemp, S1); + float32x2_t R1 = vrsqrts_f32(P1, S1); + float32x2_t S2 = vmul_f32(S1, R1); + vTemp = vmul_f32(vTemp, S2); + // R = RefractionIndex * IDotN + sqrt(R) + vTemp = vmla_f32(vTemp, RIL, IDotN); + // Result = RefractionIndex * Incident - Normal * R + float32x2_t vResult = vmul_f32(RIL, IL); + vResult = vmls_f32(vResult, vTemp, NL); + vResult = vreinterpret_f32_u32(vand_u32(vreinterpret_u32_f32(vResult), vMask)); + return vcombine_f32(vResult, vResult); +#elif defined(_XM_SSE_INTRINSICS_) + // Result = RefractionIndex * Incident - Normal * (RefractionIndex * dot(Incident, Normal) + + // sqrt(1 - RefractionIndex * RefractionIndex * (1 - dot(Incident, Normal) * dot(Incident, Normal)))) + // Get the 2D Dot product of Incident-Normal + XMVECTOR IDotN = XMVector2Dot(Incident, Normal); + // vTemp = 1.0f - RefractionIndex * RefractionIndex * (1.0f - IDotN * IDotN) + XMVECTOR vTemp = XM_FNMADD_PS(IDotN, IDotN, g_XMOne); + vTemp = _mm_mul_ps(vTemp, RefractionIndex); + vTemp = XM_FNMADD_PS(vTemp, RefractionIndex, g_XMOne); + // If any terms are <=0, sqrt() will fail, punt to zero + XMVECTOR vMask = _mm_cmpgt_ps(vTemp, g_XMZero); + // R = RefractionIndex * IDotN + sqrt(R) + vTemp = _mm_sqrt_ps(vTemp); + vTemp = XM_FMADD_PS(RefractionIndex, IDotN, vTemp); + // Result = RefractionIndex * Incident - Normal * R + XMVECTOR vResult = _mm_mul_ps(RefractionIndex, Incident); + vResult = XM_FNMADD_PS(vTemp, Normal, vResult); + vResult = _mm_and_ps(vResult, vMask); + return vResult; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector2Orthogonal(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTORF32 Result = { { { + -V.vector4_f32[1], + V.vector4_f32[0], + 0.f, + 0.f + } } }; + return Result.v; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + static const XMVECTORF32 Negate = { { { -1.f, 1.f, 0, 0 } } }; + const float32x2_t zero = vdup_n_f32(0); + + float32x2_t VL = vget_low_f32(V); + float32x2_t Result = vmul_f32(vrev64_f32(VL), vget_low_f32(Negate)); + return vcombine_f32(Result, zero); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vResult = XM_PERMUTE_PS(V, _MM_SHUFFLE(3, 2, 0, 1)); + vResult = _mm_mul_ps(vResult, g_XMNegateX); + return vResult; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector2AngleBetweenNormalsEst +( + FXMVECTOR N1, + FXMVECTOR N2 +) noexcept +{ + XMVECTOR Result = XMVector2Dot(N1, N2); + Result = XMVectorClamp(Result, g_XMNegativeOne.v, g_XMOne.v); + Result = XMVectorACosEst(Result); + return Result; +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector2AngleBetweenNormals +( + FXMVECTOR N1, + FXMVECTOR N2 +) noexcept +{ + XMVECTOR Result = XMVector2Dot(N1, N2); + Result = XMVectorClamp(Result, g_XMNegativeOne, g_XMOne); + Result = XMVectorACos(Result); + return Result; +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector2AngleBetweenVectors +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ + XMVECTOR L1 = XMVector2ReciprocalLength(V1); + XMVECTOR L2 = XMVector2ReciprocalLength(V2); + + XMVECTOR Dot = XMVector2Dot(V1, V2); + + L1 = XMVectorMultiply(L1, L2); + + XMVECTOR CosAngle = XMVectorMultiply(Dot, L1); + CosAngle = XMVectorClamp(CosAngle, g_XMNegativeOne.v, g_XMOne.v); + + return XMVectorACos(CosAngle); +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector2LinePointDistance +( + FXMVECTOR LinePoint1, + FXMVECTOR LinePoint2, + FXMVECTOR Point +) noexcept +{ + // Given a vector PointVector from LinePoint1 to Point and a vector + // LineVector from LinePoint1 to LinePoint2, the scaled distance + // PointProjectionScale from LinePoint1 to the perpendicular projection + // of PointVector onto the line is defined as: + // + // PointProjectionScale = dot(PointVector, LineVector) / LengthSq(LineVector) + + XMVECTOR PointVector = XMVectorSubtract(Point, LinePoint1); + XMVECTOR LineVector = XMVectorSubtract(LinePoint2, LinePoint1); + + XMVECTOR LengthSq = XMVector2LengthSq(LineVector); + + XMVECTOR PointProjectionScale = XMVector2Dot(PointVector, LineVector); + PointProjectionScale = XMVectorDivide(PointProjectionScale, LengthSq); + + XMVECTOR DistanceVector = XMVectorMultiply(LineVector, PointProjectionScale); + DistanceVector = XMVectorSubtract(PointVector, DistanceVector); + + return XMVector2Length(DistanceVector); +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector2IntersectLine +( + FXMVECTOR Line1Point1, + FXMVECTOR Line1Point2, + FXMVECTOR Line2Point1, + GXMVECTOR Line2Point2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) || defined(_XM_ARM_NEON_INTRINSICS_) + + XMVECTOR V1 = XMVectorSubtract(Line1Point2, Line1Point1); + XMVECTOR V2 = XMVectorSubtract(Line2Point2, Line2Point1); + XMVECTOR V3 = XMVectorSubtract(Line1Point1, Line2Point1); + + XMVECTOR C1 = XMVector2Cross(V1, V2); + XMVECTOR C2 = XMVector2Cross(V2, V3); + + XMVECTOR Result; + const XMVECTOR Zero = XMVectorZero(); + if (XMVector2NearEqual(C1, Zero, g_XMEpsilon.v)) + { + if (XMVector2NearEqual(C2, Zero, g_XMEpsilon.v)) + { + // Coincident + Result = g_XMInfinity.v; + } + else + { + // Parallel + Result = g_XMQNaN.v; + } + } + else + { + // Intersection point = Line1Point1 + V1 * (C2 / C1) + XMVECTOR Scale = XMVectorReciprocal(C1); + Scale = XMVectorMultiply(C2, Scale); + Result = XMVectorMultiplyAdd(V1, Scale, Line1Point1); + } + + return Result; + +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR V1 = _mm_sub_ps(Line1Point2, Line1Point1); + XMVECTOR V2 = _mm_sub_ps(Line2Point2, Line2Point1); + XMVECTOR V3 = _mm_sub_ps(Line1Point1, Line2Point1); + // Generate the cross products + XMVECTOR C1 = XMVector2Cross(V1, V2); + XMVECTOR C2 = XMVector2Cross(V2, V3); + // If C1 is not close to epsilon, use the calculated value + XMVECTOR vResultMask = _mm_setzero_ps(); + vResultMask = _mm_sub_ps(vResultMask, C1); + vResultMask = _mm_max_ps(vResultMask, C1); + // 0xFFFFFFFF if the calculated value is to be used + vResultMask = _mm_cmpgt_ps(vResultMask, g_XMEpsilon); + // If C1 is close to epsilon, which fail type is it? INFINITY or NAN? + XMVECTOR vFailMask = _mm_setzero_ps(); + vFailMask = _mm_sub_ps(vFailMask, C2); + vFailMask = _mm_max_ps(vFailMask, C2); + vFailMask = _mm_cmple_ps(vFailMask, g_XMEpsilon); + XMVECTOR vFail = _mm_and_ps(vFailMask, g_XMInfinity); + vFailMask = _mm_andnot_ps(vFailMask, g_XMQNaN); + // vFail is NAN or INF + vFail = _mm_or_ps(vFail, vFailMask); + // Intersection point = Line1Point1 + V1 * (C2 / C1) + XMVECTOR vResult = _mm_div_ps(C2, C1); + vResult = XM_FMADD_PS(vResult, V1, Line1Point1); + // Use result, or failure value + vResult = _mm_and_ps(vResult, vResultMask); + vResultMask = _mm_andnot_ps(vResultMask, vFail); + vResult = _mm_or_ps(vResult, vResultMask); + return vResult; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector2Transform +( + FXMVECTOR V, + FXMMATRIX M +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR Y = XMVectorSplatY(V); + XMVECTOR X = XMVectorSplatX(V); + + XMVECTOR Result = XMVectorMultiplyAdd(Y, M.r[1], M.r[3]); + Result = XMVectorMultiplyAdd(X, M.r[0], Result); + + return Result; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x2_t VL = vget_low_f32(V); + float32x4_t Result = vmlaq_lane_f32(M.r[3], M.r[1], VL, 1); // Y + return vmlaq_lane_f32(Result, M.r[0], VL, 0); // X +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vResult = XM_PERMUTE_PS(V, _MM_SHUFFLE(1, 1, 1, 1)); // Y + vResult = XM_FMADD_PS(vResult, M.r[1], M.r[3]); + XMVECTOR vTemp = XM_PERMUTE_PS(V, _MM_SHUFFLE(0, 0, 0, 0)); // X + vResult = XM_FMADD_PS(vTemp, M.r[0], vResult); + return vResult; +#endif +} + +//------------------------------------------------------------------------------ + +_Use_decl_annotations_ +inline XMFLOAT4* XM_CALLCONV XMVector2TransformStream +( + XMFLOAT4* pOutputStream, + size_t OutputStride, + const XMFLOAT2* pInputStream, + size_t InputStride, + size_t VectorCount, + FXMMATRIX M +) noexcept +{ + assert(pOutputStream != nullptr); + assert(pInputStream != nullptr); + + assert(InputStride >= sizeof(XMFLOAT2)); + _Analysis_assume_(InputStride >= sizeof(XMFLOAT2)); + + assert(OutputStride >= sizeof(XMFLOAT4)); + _Analysis_assume_(OutputStride >= sizeof(XMFLOAT4)); + +#if defined(_XM_NO_INTRINSICS_) + + auto pInputVector = reinterpret_cast(pInputStream); + auto pOutputVector = reinterpret_cast(pOutputStream); + + const XMVECTOR row0 = M.r[0]; + const XMVECTOR row1 = M.r[1]; + const XMVECTOR row3 = M.r[3]; + + for (size_t i = 0; i < VectorCount; i++) + { + XMVECTOR V = XMLoadFloat2(reinterpret_cast(pInputVector)); + XMVECTOR Y = XMVectorSplatY(V); + XMVECTOR X = XMVectorSplatX(V); + + XMVECTOR Result = XMVectorMultiplyAdd(Y, row1, row3); + Result = XMVectorMultiplyAdd(X, row0, Result); + + #ifdef _PREFAST_ + #pragma prefast(push) + #pragma prefast(disable : 26015, "PREfast noise: Esp:1307" ) + #endif + + XMStoreFloat4(reinterpret_cast(pOutputVector), Result); + + #ifdef _PREFAST_ + #pragma prefast(pop) + #endif + + pInputVector += InputStride; + pOutputVector += OutputStride; + } + + return pOutputStream; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + auto pInputVector = reinterpret_cast(pInputStream); + auto pOutputVector = reinterpret_cast(pOutputStream); + + const XMVECTOR row0 = M.r[0]; + const XMVECTOR row1 = M.r[1]; + const XMVECTOR row3 = M.r[3]; + + size_t i = 0; + size_t four = VectorCount >> 2; + if (four > 0) + { + if ((InputStride == sizeof(XMFLOAT2)) && (OutputStride == sizeof(XMFLOAT4))) + { + for (size_t j = 0; j < four; ++j) + { + float32x4x2_t V = vld2q_f32(reinterpret_cast(pInputVector)); + pInputVector += sizeof(XMFLOAT2) * 4; + + float32x2_t r3 = vget_low_f32(row3); + float32x2_t r = vget_low_f32(row0); + XMVECTOR vResult0 = vmlaq_lane_f32(vdupq_lane_f32(r3, 0), V.val[0], r, 0); // Ax+M + XMVECTOR vResult1 = vmlaq_lane_f32(vdupq_lane_f32(r3, 1), V.val[0], r, 1); // Bx+N + + XM_PREFETCH(pInputVector); + + r3 = vget_high_f32(row3); + r = vget_high_f32(row0); + XMVECTOR vResult2 = vmlaq_lane_f32(vdupq_lane_f32(r3, 0), V.val[0], r, 0); // Cx+O + XMVECTOR vResult3 = vmlaq_lane_f32(vdupq_lane_f32(r3, 1), V.val[0], r, 1); // Dx+P + + XM_PREFETCH(pInputVector + XM_CACHE_LINE_SIZE); + + r = vget_low_f32(row1); + vResult0 = vmlaq_lane_f32(vResult0, V.val[1], r, 0); // Ax+Ey+M + vResult1 = vmlaq_lane_f32(vResult1, V.val[1], r, 1); // Bx+Fy+N + + XM_PREFETCH(pInputVector + (XM_CACHE_LINE_SIZE * 2)); + + r = vget_high_f32(row1); + vResult2 = vmlaq_lane_f32(vResult2, V.val[1], r, 0); // Cx+Gy+O + vResult3 = vmlaq_lane_f32(vResult3, V.val[1], r, 1); // Dx+Hy+P + + XM_PREFETCH(pInputVector + (XM_CACHE_LINE_SIZE * 3)); + + float32x4x4_t R; + R.val[0] = vResult0; + R.val[1] = vResult1; + R.val[2] = vResult2; + R.val[3] = vResult3; + + vst4q_f32(reinterpret_cast(pOutputVector), R); + pOutputVector += sizeof(XMFLOAT4) * 4; + + i += 4; + } + } + } + + for (; i < VectorCount; i++) + { + float32x2_t V = vld1_f32(reinterpret_cast(pInputVector)); + pInputVector += InputStride; + + XMVECTOR vResult = vmlaq_lane_f32(row3, row0, V, 0); // X + vResult = vmlaq_lane_f32(vResult, row1, V, 1); // Y + + vst1q_f32(reinterpret_cast(pOutputVector), vResult); + pOutputVector += OutputStride; + } + + return pOutputStream; +#elif defined(_XM_AVX2_INTRINSICS_) + auto pInputVector = reinterpret_cast(pInputStream); + auto pOutputVector = reinterpret_cast(pOutputStream); + + size_t i = 0; + size_t four = VectorCount >> 2; + if (four > 0) + { + __m256 row0 = _mm256_broadcast_ps(&M.r[0]); + __m256 row1 = _mm256_broadcast_ps(&M.r[1]); + __m256 row3 = _mm256_broadcast_ps(&M.r[3]); + + if (InputStride == sizeof(XMFLOAT2)) + { + if (OutputStride == sizeof(XMFLOAT4)) + { + if (!(reinterpret_cast(pOutputStream) & 0x1F)) + { + // Packed input, aligned & packed output + for (size_t j = 0; j < four; ++j) + { + __m256 VV = _mm256_loadu_ps(reinterpret_cast(pInputVector)); + pInputVector += sizeof(XMFLOAT2) * 4; + + __m256 Y2 = _mm256_shuffle_ps(VV, VV, _MM_SHUFFLE(3, 3, 3, 3)); + __m256 X2 = _mm256_shuffle_ps(VV, VV, _MM_SHUFFLE(2, 2, 2, 2)); + __m256 Y1 = _mm256_shuffle_ps(VV, VV, _MM_SHUFFLE(1, 1, 1, 1)); + __m256 X1 = _mm256_shuffle_ps(VV, VV, _MM_SHUFFLE(0, 0, 0, 0)); + + __m256 vTempB = _mm256_fmadd_ps(Y1, row1, row3); + __m256 vTempB2 = _mm256_fmadd_ps(Y2, row1, row3); + __m256 vTempA = _mm256_mul_ps(X1, row0); + __m256 vTempA2 = _mm256_mul_ps(X2, row0); + vTempA = _mm256_add_ps(vTempA, vTempB); + vTempA2 = _mm256_add_ps(vTempA2, vTempB2); + + X1 = _mm256_insertf128_ps(vTempA, _mm256_castps256_ps128(vTempA2), 1); + XM256_STREAM_PS(reinterpret_cast(pOutputVector), X1); + pOutputVector += sizeof(XMFLOAT4) * 2; + + X2 = _mm256_insertf128_ps(vTempA2, _mm256_extractf128_ps(vTempA, 1), 0); + XM256_STREAM_PS(reinterpret_cast(pOutputVector), X2); + pOutputVector += sizeof(XMFLOAT4) * 2; + + i += 4; + } + } + else + { + // Packed input, packed output + for (size_t j = 0; j < four; ++j) + { + __m256 VV = _mm256_loadu_ps(reinterpret_cast(pInputVector)); + pInputVector += sizeof(XMFLOAT2) * 4; + + __m256 Y2 = _mm256_shuffle_ps(VV, VV, _MM_SHUFFLE(3, 3, 3, 3)); + __m256 X2 = _mm256_shuffle_ps(VV, VV, _MM_SHUFFLE(2, 2, 2, 2)); + __m256 Y1 = _mm256_shuffle_ps(VV, VV, _MM_SHUFFLE(1, 1, 1, 1)); + __m256 X1 = _mm256_shuffle_ps(VV, VV, _MM_SHUFFLE(0, 0, 0, 0)); + + __m256 vTempB = _mm256_fmadd_ps(Y1, row1, row3); + __m256 vTempB2 = _mm256_fmadd_ps(Y2, row1, row3); + __m256 vTempA = _mm256_mul_ps(X1, row0); + __m256 vTempA2 = _mm256_mul_ps(X2, row0); + vTempA = _mm256_add_ps(vTempA, vTempB); + vTempA2 = _mm256_add_ps(vTempA2, vTempB2); + + X1 = _mm256_insertf128_ps(vTempA, _mm256_castps256_ps128(vTempA2), 1); + _mm256_storeu_ps(reinterpret_cast(pOutputVector), X1); + pOutputVector += sizeof(XMFLOAT4) * 2; + + X2 = _mm256_insertf128_ps(vTempA2, _mm256_extractf128_ps(vTempA, 1), 0); + _mm256_storeu_ps(reinterpret_cast(pOutputVector), X2); + pOutputVector += sizeof(XMFLOAT4) * 2; + + i += 4; + } + } + } + else + { + // Packed input, unpacked output + for (size_t j = 0; j < four; ++j) + { + __m256 VV = _mm256_loadu_ps(reinterpret_cast(pInputVector)); + pInputVector += sizeof(XMFLOAT2) * 4; + + __m256 Y2 = _mm256_shuffle_ps(VV, VV, _MM_SHUFFLE(3, 3, 3, 3)); + __m256 X2 = _mm256_shuffle_ps(VV, VV, _MM_SHUFFLE(2, 2, 2, 2)); + __m256 Y1 = _mm256_shuffle_ps(VV, VV, _MM_SHUFFLE(1, 1, 1, 1)); + __m256 X1 = _mm256_shuffle_ps(VV, VV, _MM_SHUFFLE(0, 0, 0, 0)); + + __m256 vTempB = _mm256_fmadd_ps(Y1, row1, row3); + __m256 vTempB2 = _mm256_fmadd_ps(Y2, row1, row3); + __m256 vTempA = _mm256_mul_ps(X1, row0); + __m256 vTempA2 = _mm256_mul_ps(X2, row0); + vTempA = _mm256_add_ps(vTempA, vTempB); + vTempA2 = _mm256_add_ps(vTempA2, vTempB2); + + _mm_storeu_ps(reinterpret_cast(pOutputVector), _mm256_castps256_ps128(vTempA)); + pOutputVector += OutputStride; + + _mm_storeu_ps(reinterpret_cast(pOutputVector), _mm256_castps256_ps128(vTempA2)); + pOutputVector += OutputStride; + + _mm_storeu_ps(reinterpret_cast(pOutputVector), _mm256_extractf128_ps(vTempA, 1)); + pOutputVector += OutputStride; + + _mm_storeu_ps(reinterpret_cast(pOutputVector), _mm256_extractf128_ps(vTempA2, 1)); + pOutputVector += OutputStride; + + i += 4; + } + } + } + } + + if (i < VectorCount) + { + const XMVECTOR row0 = M.r[0]; + const XMVECTOR row1 = M.r[1]; + const XMVECTOR row3 = M.r[3]; + + for (; i < VectorCount; i++) + { + __m128 xy = _mm_castpd_ps(_mm_load_sd(reinterpret_cast(pInputVector))); + pInputVector += InputStride; + + XMVECTOR Y = XM_PERMUTE_PS(xy, _MM_SHUFFLE(1, 1, 1, 1)); + XMVECTOR X = XM_PERMUTE_PS(xy, _MM_SHUFFLE(0, 0, 0, 0)); + + XMVECTOR vTemp = XM_FMADD_PS(Y, row1, row3); + XMVECTOR vTemp2 = _mm_mul_ps(X, row0); + vTemp = _mm_add_ps(vTemp, vTemp2); + + _mm_storeu_ps(reinterpret_cast(pOutputVector), vTemp); + pOutputVector += OutputStride; + } + } + + XM_SFENCE(); + + return pOutputStream; +#elif defined(_XM_SSE_INTRINSICS_) + auto pInputVector = reinterpret_cast(pInputStream); + auto pOutputVector = reinterpret_cast(pOutputStream); + + const XMVECTOR row0 = M.r[0]; + const XMVECTOR row1 = M.r[1]; + const XMVECTOR row3 = M.r[3]; + + size_t i = 0; + size_t two = VectorCount >> 1; + if (two > 0) + { + if (InputStride == sizeof(XMFLOAT2)) + { + if (!(reinterpret_cast(pOutputStream) & 0xF) && !(OutputStride & 0xF)) + { + // Packed input, aligned output + for (size_t j = 0; j < two; ++j) + { + XMVECTOR V = _mm_loadu_ps(reinterpret_cast(pInputVector)); + pInputVector += sizeof(XMFLOAT2) * 2; + + XMVECTOR Y = XM_PERMUTE_PS(V, _MM_SHUFFLE(1, 1, 1, 1)); + XMVECTOR X = XM_PERMUTE_PS(V, _MM_SHUFFLE(0, 0, 0, 0)); + + XMVECTOR vTemp = XM_FMADD_PS(Y, row1, row3); + XMVECTOR vTemp2 = _mm_mul_ps(X, row0); + vTemp = _mm_add_ps(vTemp, vTemp2); + + XM_STREAM_PS(reinterpret_cast(pOutputVector), vTemp); + pOutputVector += OutputStride; + + Y = XM_PERMUTE_PS(V, _MM_SHUFFLE(3, 3, 3, 3)); + X = XM_PERMUTE_PS(V, _MM_SHUFFLE(2, 2, 2, 2)); + + vTemp = XM_FMADD_PS(Y, row1, row3); + vTemp2 = _mm_mul_ps(X, row0); + vTemp = _mm_add_ps(vTemp, vTemp2); + + XM_STREAM_PS(reinterpret_cast(pOutputVector), vTemp); + pOutputVector += OutputStride; + + i += 2; + } + } + else + { + // Packed input, unaligned output + for (size_t j = 0; j < two; ++j) + { + XMVECTOR V = _mm_loadu_ps(reinterpret_cast(pInputVector)); + pInputVector += sizeof(XMFLOAT2) * 2; + + XMVECTOR Y = XM_PERMUTE_PS(V, _MM_SHUFFLE(1, 1, 1, 1)); + XMVECTOR X = XM_PERMUTE_PS(V, _MM_SHUFFLE(0, 0, 0, 0)); + + XMVECTOR vTemp = XM_FMADD_PS(Y, row1, row3); + XMVECTOR vTemp2 = _mm_mul_ps(X, row0); + vTemp = _mm_add_ps(vTemp, vTemp2); + + _mm_storeu_ps(reinterpret_cast(pOutputVector), vTemp); + pOutputVector += OutputStride; + + Y = XM_PERMUTE_PS(V, _MM_SHUFFLE(3, 3, 3, 3)); + X = XM_PERMUTE_PS(V, _MM_SHUFFLE(2, 2, 2, 2)); + + vTemp = XM_FMADD_PS(Y, row1, row3); + vTemp2 = _mm_mul_ps(X, row0); + vTemp = _mm_add_ps(vTemp, vTemp2); + + _mm_storeu_ps(reinterpret_cast(pOutputVector), vTemp); + pOutputVector += OutputStride; + + i += 2; + } + } + } + } + + if (!(reinterpret_cast(pInputVector) & 0xF) && !(InputStride & 0xF)) + { + if (!(reinterpret_cast(pOutputStream) & 0xF) && !(OutputStride & 0xF)) + { + // Aligned input, aligned output + for (; i < VectorCount; i++) + { + XMVECTOR V = _mm_castsi128_ps(_mm_loadl_epi64(reinterpret_cast(pInputVector))); + pInputVector += InputStride; + + XMVECTOR Y = XM_PERMUTE_PS(V, _MM_SHUFFLE(1, 1, 1, 1)); + XMVECTOR X = XM_PERMUTE_PS(V, _MM_SHUFFLE(0, 0, 0, 0)); + + XMVECTOR vTemp = XM_FMADD_PS(Y, row1, row3); + XMVECTOR vTemp2 = _mm_mul_ps(X, row0); + vTemp = _mm_add_ps(vTemp, vTemp2); + + XM_STREAM_PS(reinterpret_cast(pOutputVector), vTemp); + pOutputVector += OutputStride; + } + } + else + { + // Aligned input, unaligned output + for (; i < VectorCount; i++) + { + XMVECTOR V = _mm_castsi128_ps(_mm_loadl_epi64(reinterpret_cast(pInputVector))); + pInputVector += InputStride; + + XMVECTOR Y = XM_PERMUTE_PS(V, _MM_SHUFFLE(1, 1, 1, 1)); + XMVECTOR X = XM_PERMUTE_PS(V, _MM_SHUFFLE(0, 0, 0, 0)); + + XMVECTOR vTemp = XM_FMADD_PS(Y, row1, row3); + XMVECTOR vTemp2 = _mm_mul_ps(X, row0); + vTemp = _mm_add_ps(vTemp, vTemp2); + + _mm_storeu_ps(reinterpret_cast(pOutputVector), vTemp); + pOutputVector += OutputStride; + } + } + } + else + { + // Unaligned input + for (; i < VectorCount; i++) + { + __m128 xy = _mm_castpd_ps(_mm_load_sd(reinterpret_cast(pInputVector))); + pInputVector += InputStride; + + XMVECTOR Y = XM_PERMUTE_PS(xy, _MM_SHUFFLE(1, 1, 1, 1)); + XMVECTOR X = XM_PERMUTE_PS(xy, _MM_SHUFFLE(0, 0, 0, 0)); + + XMVECTOR vTemp = XM_FMADD_PS(Y, row1, row3); + XMVECTOR vTemp2 = _mm_mul_ps(X, row0); + vTemp = _mm_add_ps(vTemp, vTemp2); + + _mm_storeu_ps(reinterpret_cast(pOutputVector), vTemp); + pOutputVector += OutputStride; + } + } + + XM_SFENCE(); + + return pOutputStream; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector2TransformCoord +( + FXMVECTOR V, + FXMMATRIX M +) noexcept +{ + XMVECTOR Y = XMVectorSplatY(V); + XMVECTOR X = XMVectorSplatX(V); + + XMVECTOR Result = XMVectorMultiplyAdd(Y, M.r[1], M.r[3]); + Result = XMVectorMultiplyAdd(X, M.r[0], Result); + + XMVECTOR W = XMVectorSplatW(Result); + return XMVectorDivide(Result, W); +} + +//------------------------------------------------------------------------------ + +_Use_decl_annotations_ +inline XMFLOAT2* XM_CALLCONV XMVector2TransformCoordStream +( + XMFLOAT2* pOutputStream, + size_t OutputStride, + const XMFLOAT2* pInputStream, + size_t InputStride, + size_t VectorCount, + FXMMATRIX M +) noexcept +{ + assert(pOutputStream != nullptr); + assert(pInputStream != nullptr); + + assert(InputStride >= sizeof(XMFLOAT2)); + _Analysis_assume_(InputStride >= sizeof(XMFLOAT2)); + + assert(OutputStride >= sizeof(XMFLOAT2)); + _Analysis_assume_(OutputStride >= sizeof(XMFLOAT2)); + +#if defined(_XM_NO_INTRINSICS_) + + auto pInputVector = reinterpret_cast(pInputStream); + auto pOutputVector = reinterpret_cast(pOutputStream); + + const XMVECTOR row0 = M.r[0]; + const XMVECTOR row1 = M.r[1]; + const XMVECTOR row3 = M.r[3]; + + for (size_t i = 0; i < VectorCount; i++) + { + XMVECTOR V = XMLoadFloat2(reinterpret_cast(pInputVector)); + XMVECTOR Y = XMVectorSplatY(V); + XMVECTOR X = XMVectorSplatX(V); + + XMVECTOR Result = XMVectorMultiplyAdd(Y, row1, row3); + Result = XMVectorMultiplyAdd(X, row0, Result); + + XMVECTOR W = XMVectorSplatW(Result); + + Result = XMVectorDivide(Result, W); + + #ifdef _PREFAST_ + #pragma prefast(push) + #pragma prefast(disable : 26015, "PREfast noise: Esp:1307" ) + #endif + + XMStoreFloat2(reinterpret_cast(pOutputVector), Result); + + #ifdef _PREFAST_ + #pragma prefast(pop) + #endif + + pInputVector += InputStride; + pOutputVector += OutputStride; + } + + return pOutputStream; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + auto pInputVector = reinterpret_cast(pInputStream); + auto pOutputVector = reinterpret_cast(pOutputStream); + + const XMVECTOR row0 = M.r[0]; + const XMVECTOR row1 = M.r[1]; + const XMVECTOR row3 = M.r[3]; + + size_t i = 0; + size_t four = VectorCount >> 2; + if (four > 0) + { + if ((InputStride == sizeof(XMFLOAT2)) && (OutputStride == sizeof(XMFLOAT2))) + { + for (size_t j = 0; j < four; ++j) + { + float32x4x2_t V = vld2q_f32(reinterpret_cast(pInputVector)); + pInputVector += sizeof(XMFLOAT2) * 4; + + float32x2_t r3 = vget_low_f32(row3); + float32x2_t r = vget_low_f32(row0); + XMVECTOR vResult0 = vmlaq_lane_f32(vdupq_lane_f32(r3, 0), V.val[0], r, 0); // Ax+M + XMVECTOR vResult1 = vmlaq_lane_f32(vdupq_lane_f32(r3, 1), V.val[0], r, 1); // Bx+N + + XM_PREFETCH(pInputVector); + + r3 = vget_high_f32(row3); + r = vget_high_f32(row0); + XMVECTOR W = vmlaq_lane_f32(vdupq_lane_f32(r3, 1), V.val[0], r, 1); // Dx+P + + XM_PREFETCH(pInputVector + XM_CACHE_LINE_SIZE); + + r = vget_low_f32(row1); + vResult0 = vmlaq_lane_f32(vResult0, V.val[1], r, 0); // Ax+Ey+M + vResult1 = vmlaq_lane_f32(vResult1, V.val[1], r, 1); // Bx+Fy+N + + XM_PREFETCH(pInputVector + (XM_CACHE_LINE_SIZE * 2)); + + r = vget_high_f32(row1); + W = vmlaq_lane_f32(W, V.val[1], r, 1); // Dx+Hy+P + + XM_PREFETCH(pInputVector + (XM_CACHE_LINE_SIZE * 3)); + + #if defined(_M_ARM64) || defined(_M_HYBRID_X86_ARM64) || defined(_M_ARM64EC) || __aarch64__ + V.val[0] = vdivq_f32(vResult0, W); + V.val[1] = vdivq_f32(vResult1, W); + #else + // 2 iterations of Newton-Raphson refinement of reciprocal + float32x4_t Reciprocal = vrecpeq_f32(W); + float32x4_t S = vrecpsq_f32(Reciprocal, W); + Reciprocal = vmulq_f32(S, Reciprocal); + S = vrecpsq_f32(Reciprocal, W); + Reciprocal = vmulq_f32(S, Reciprocal); + + V.val[0] = vmulq_f32(vResult0, Reciprocal); + V.val[1] = vmulq_f32(vResult1, Reciprocal); + #endif + + vst2q_f32(reinterpret_cast(pOutputVector), V); + pOutputVector += sizeof(XMFLOAT2) * 4; + + i += 4; + } + } + } + + for (; i < VectorCount; i++) + { + float32x2_t V = vld1_f32(reinterpret_cast(pInputVector)); + pInputVector += InputStride; + + XMVECTOR vResult = vmlaq_lane_f32(row3, row0, V, 0); // X + vResult = vmlaq_lane_f32(vResult, row1, V, 1); // Y + + V = vget_high_f32(vResult); + float32x2_t W = vdup_lane_f32(V, 1); + + #if defined(_M_ARM64) || defined(_M_HYBRID_X86_ARM64) || defined(_M_ARM64EC) || __aarch64__ + V = vget_low_f32(vResult); + V = vdiv_f32(V, W); + #else + // 2 iterations of Newton-Raphson refinement of reciprocal for W + float32x2_t Reciprocal = vrecpe_f32(W); + float32x2_t S = vrecps_f32(Reciprocal, W); + Reciprocal = vmul_f32(S, Reciprocal); + S = vrecps_f32(Reciprocal, W); + Reciprocal = vmul_f32(S, Reciprocal); + + V = vget_low_f32(vResult); + V = vmul_f32(V, Reciprocal); + #endif + + vst1_f32(reinterpret_cast(pOutputVector), V); + pOutputVector += OutputStride; + } + + return pOutputStream; +#elif defined(_XM_AVX2_INTRINSICS_) + auto pInputVector = reinterpret_cast(pInputStream); + auto pOutputVector = reinterpret_cast(pOutputStream); + + size_t i = 0; + size_t four = VectorCount >> 2; + if (four > 0) + { + __m256 row0 = _mm256_broadcast_ps(&M.r[0]); + __m256 row1 = _mm256_broadcast_ps(&M.r[1]); + __m256 row3 = _mm256_broadcast_ps(&M.r[3]); + + if (InputStride == sizeof(XMFLOAT2)) + { + if (OutputStride == sizeof(XMFLOAT2)) + { + if (!(reinterpret_cast(pOutputStream) & 0x1F)) + { + // Packed input, aligned & packed output + for (size_t j = 0; j < four; ++j) + { + __m256 VV = _mm256_loadu_ps(reinterpret_cast(pInputVector)); + pInputVector += sizeof(XMFLOAT2) * 4; + + __m256 Y2 = _mm256_shuffle_ps(VV, VV, _MM_SHUFFLE(3, 3, 3, 3)); + __m256 X2 = _mm256_shuffle_ps(VV, VV, _MM_SHUFFLE(2, 2, 2, 2)); + __m256 Y1 = _mm256_shuffle_ps(VV, VV, _MM_SHUFFLE(1, 1, 1, 1)); + __m256 X1 = _mm256_shuffle_ps(VV, VV, _MM_SHUFFLE(0, 0, 0, 0)); + + __m256 vTempB = _mm256_fmadd_ps(Y1, row1, row3); + __m256 vTempB2 = _mm256_fmadd_ps(Y2, row1, row3); + __m256 vTempA = _mm256_mul_ps(X1, row0); + __m256 vTempA2 = _mm256_mul_ps(X2, row0); + vTempA = _mm256_add_ps(vTempA, vTempB); + vTempA2 = _mm256_add_ps(vTempA2, vTempB2); + + __m256 W = _mm256_shuffle_ps(vTempA, vTempA, _MM_SHUFFLE(3, 3, 3, 3)); + vTempA = _mm256_div_ps(vTempA, W); + + W = _mm256_shuffle_ps(vTempA2, vTempA2, _MM_SHUFFLE(3, 3, 3, 3)); + vTempA2 = _mm256_div_ps(vTempA2, W); + + X1 = _mm256_shuffle_ps(vTempA, vTempA2, 0x44); + XM256_STREAM_PS(reinterpret_cast(pOutputVector), X1); + pOutputVector += sizeof(XMFLOAT2) * 4; + + i += 4; + } + } + else + { + // Packed input, packed output + for (size_t j = 0; j < four; ++j) + { + __m256 VV = _mm256_loadu_ps(reinterpret_cast(pInputVector)); + pInputVector += sizeof(XMFLOAT2) * 4; + + __m256 Y2 = _mm256_shuffle_ps(VV, VV, _MM_SHUFFLE(3, 3, 3, 3)); + __m256 X2 = _mm256_shuffle_ps(VV, VV, _MM_SHUFFLE(2, 2, 2, 2)); + __m256 Y1 = _mm256_shuffle_ps(VV, VV, _MM_SHUFFLE(1, 1, 1, 1)); + __m256 X1 = _mm256_shuffle_ps(VV, VV, _MM_SHUFFLE(0, 0, 0, 0)); + + __m256 vTempB = _mm256_fmadd_ps(Y1, row1, row3); + __m256 vTempB2 = _mm256_fmadd_ps(Y2, row1, row3); + __m256 vTempA = _mm256_mul_ps(X1, row0); + __m256 vTempA2 = _mm256_mul_ps(X2, row0); + vTempA = _mm256_add_ps(vTempA, vTempB); + vTempA2 = _mm256_add_ps(vTempA2, vTempB2); + + __m256 W = _mm256_shuffle_ps(vTempA, vTempA, _MM_SHUFFLE(3, 3, 3, 3)); + vTempA = _mm256_div_ps(vTempA, W); + + W = _mm256_shuffle_ps(vTempA2, vTempA2, _MM_SHUFFLE(3, 3, 3, 3)); + vTempA2 = _mm256_div_ps(vTempA2, W); + + X1 = _mm256_shuffle_ps(vTempA, vTempA2, 0x44); + _mm256_storeu_ps(reinterpret_cast(pOutputVector), X1); + pOutputVector += sizeof(XMFLOAT2) * 4; + + i += 4; + } + } + } + else + { + // Packed input, unpacked output + for (size_t j = 0; j < four; ++j) + { + __m256 VV = _mm256_loadu_ps(reinterpret_cast(pInputVector)); + pInputVector += sizeof(XMFLOAT2) * 4; + + __m256 Y2 = _mm256_shuffle_ps(VV, VV, _MM_SHUFFLE(3, 3, 3, 3)); + __m256 X2 = _mm256_shuffle_ps(VV, VV, _MM_SHUFFLE(2, 2, 2, 2)); + __m256 Y1 = _mm256_shuffle_ps(VV, VV, _MM_SHUFFLE(1, 1, 1, 1)); + __m256 X1 = _mm256_shuffle_ps(VV, VV, _MM_SHUFFLE(0, 0, 0, 0)); + + __m256 vTempB = _mm256_fmadd_ps(Y1, row1, row3); + __m256 vTempB2 = _mm256_fmadd_ps(Y2, row1, row3); + __m256 vTempA = _mm256_mul_ps(X1, row0); + __m256 vTempA2 = _mm256_mul_ps(X2, row0); + vTempA = _mm256_add_ps(vTempA, vTempB); + vTempA2 = _mm256_add_ps(vTempA2, vTempB2); + + __m256 W = _mm256_shuffle_ps(vTempA, vTempA, _MM_SHUFFLE(3, 3, 3, 3)); + vTempA = _mm256_div_ps(vTempA, W); + + W = _mm256_shuffle_ps(vTempA2, vTempA2, _MM_SHUFFLE(3, 3, 3, 3)); + vTempA2 = _mm256_div_ps(vTempA2, W); + + _mm_store_sd(reinterpret_cast(pOutputVector), + _mm_castps_pd(_mm256_castps256_ps128(vTempA))); + pOutputVector += OutputStride; + + _mm_store_sd(reinterpret_cast(pOutputVector), + _mm_castps_pd(_mm256_castps256_ps128(vTempA2))); + pOutputVector += OutputStride; + + _mm_store_sd(reinterpret_cast(pOutputVector), + _mm_castps_pd(_mm256_extractf128_ps(vTempA, 1))); + pOutputVector += OutputStride; + + _mm_store_sd(reinterpret_cast(pOutputVector), + _mm_castps_pd(_mm256_extractf128_ps(vTempA2, 1))); + pOutputVector += OutputStride; + + i += 4; + } + } + } + } + + if (i < VectorCount) + { + const XMVECTOR row0 = M.r[0]; + const XMVECTOR row1 = M.r[1]; + const XMVECTOR row3 = M.r[3]; + + for (; i < VectorCount; i++) + { + __m128 xy = _mm_castpd_ps(_mm_load_sd(reinterpret_cast(pInputVector))); + pInputVector += InputStride; + + XMVECTOR Y = XM_PERMUTE_PS(xy, _MM_SHUFFLE(1, 1, 1, 1)); + XMVECTOR X = XM_PERMUTE_PS(xy, _MM_SHUFFLE(0, 0, 0, 0)); + + XMVECTOR vTemp = XM_FMADD_PS(Y, row1, row3); + XMVECTOR vTemp2 = _mm_mul_ps(X, row0); + vTemp = _mm_add_ps(vTemp, vTemp2); + + XMVECTOR W = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(3, 3, 3, 3)); + vTemp = _mm_div_ps(vTemp, W); + + _mm_store_sd(reinterpret_cast(pOutputVector), _mm_castps_pd(vTemp)); + pOutputVector += OutputStride; + } + } + + XM_SFENCE(); + + return pOutputStream; +#elif defined(_XM_SSE_INTRINSICS_) + auto pInputVector = reinterpret_cast(pInputStream); + auto pOutputVector = reinterpret_cast(pOutputStream); + + const XMVECTOR row0 = M.r[0]; + const XMVECTOR row1 = M.r[1]; + const XMVECTOR row3 = M.r[3]; + + size_t i = 0; + size_t two = VectorCount >> 1; + if (two > 0) + { + if (InputStride == sizeof(XMFLOAT2)) + { + if (OutputStride == sizeof(XMFLOAT2)) + { + if (!(reinterpret_cast(pOutputStream) & 0xF)) + { + // Packed input, aligned & packed output + for (size_t j = 0; j < two; ++j) + { + XMVECTOR V = _mm_loadu_ps(reinterpret_cast(pInputVector)); + pInputVector += sizeof(XMFLOAT2) * 2; + + // Result 1 + XMVECTOR Y = XM_PERMUTE_PS(V, _MM_SHUFFLE(1, 1, 1, 1)); + XMVECTOR X = XM_PERMUTE_PS(V, _MM_SHUFFLE(0, 0, 0, 0)); + + XMVECTOR vTemp = XM_FMADD_PS(Y, row1, row3); + XMVECTOR vTemp2 = _mm_mul_ps(X, row0); + vTemp = _mm_add_ps(vTemp, vTemp2); + + XMVECTOR W = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(3, 3, 3, 3)); + + XMVECTOR V1 = _mm_div_ps(vTemp, W); + + // Result 2 + Y = XM_PERMUTE_PS(V, _MM_SHUFFLE(3, 3, 3, 3)); + X = XM_PERMUTE_PS(V, _MM_SHUFFLE(2, 2, 2, 2)); + + vTemp = XM_FMADD_PS(Y, row1, row3); + vTemp2 = _mm_mul_ps(X, row0); + vTemp = _mm_add_ps(vTemp, vTemp2); + + W = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(3, 3, 3, 3)); + + XMVECTOR V2 = _mm_div_ps(vTemp, W); + + vTemp = _mm_movelh_ps(V1, V2); + + XM_STREAM_PS(reinterpret_cast(pOutputVector), vTemp); + pOutputVector += sizeof(XMFLOAT2) * 2; + + i += 2; + } + } + else + { + // Packed input, unaligned & packed output + for (size_t j = 0; j < two; ++j) + { + XMVECTOR V = _mm_loadu_ps(reinterpret_cast(pInputVector)); + pInputVector += sizeof(XMFLOAT2) * 2; + + // Result 1 + XMVECTOR Y = XM_PERMUTE_PS(V, _MM_SHUFFLE(1, 1, 1, 1)); + XMVECTOR X = XM_PERMUTE_PS(V, _MM_SHUFFLE(0, 0, 0, 0)); + + XMVECTOR vTemp = XM_FMADD_PS(Y, row1, row3); + XMVECTOR vTemp2 = _mm_mul_ps(X, row0); + vTemp = _mm_add_ps(vTemp, vTemp2); + + XMVECTOR W = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(3, 3, 3, 3)); + + XMVECTOR V1 = _mm_div_ps(vTemp, W); + + // Result 2 + Y = XM_PERMUTE_PS(V, _MM_SHUFFLE(3, 3, 3, 3)); + X = XM_PERMUTE_PS(V, _MM_SHUFFLE(2, 2, 2, 2)); + + vTemp = XM_FMADD_PS(Y, row1, row3); + vTemp2 = _mm_mul_ps(X, row0); + vTemp = _mm_add_ps(vTemp, vTemp2); + + W = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(3, 3, 3, 3)); + + XMVECTOR V2 = _mm_div_ps(vTemp, W); + + vTemp = _mm_movelh_ps(V1, V2); + + _mm_storeu_ps(reinterpret_cast(pOutputVector), vTemp); + pOutputVector += sizeof(XMFLOAT2) * 2; + + i += 2; + } + } + } + else + { + // Packed input, unpacked output + for (size_t j = 0; j < two; ++j) + { + XMVECTOR V = _mm_loadu_ps(reinterpret_cast(pInputVector)); + pInputVector += sizeof(XMFLOAT2) * 2; + + // Result 1 + XMVECTOR Y = XM_PERMUTE_PS(V, _MM_SHUFFLE(1, 1, 1, 1)); + XMVECTOR X = XM_PERMUTE_PS(V, _MM_SHUFFLE(0, 0, 0, 0)); + + XMVECTOR vTemp = XM_FMADD_PS(Y, row1, row3); + XMVECTOR vTemp2 = _mm_mul_ps(X, row0); + vTemp = _mm_add_ps(vTemp, vTemp2); + + XMVECTOR W = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(3, 3, 3, 3)); + + vTemp = _mm_div_ps(vTemp, W); + + _mm_store_sd(reinterpret_cast(pOutputVector), _mm_castps_pd(vTemp)); + pOutputVector += OutputStride; + + // Result 2 + Y = XM_PERMUTE_PS(V, _MM_SHUFFLE(3, 3, 3, 3)); + X = XM_PERMUTE_PS(V, _MM_SHUFFLE(2, 2, 2, 2)); + + vTemp = XM_FMADD_PS(Y, row1, row3); + vTemp2 = _mm_mul_ps(X, row0); + vTemp = _mm_add_ps(vTemp, vTemp2); + + W = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(3, 3, 3, 3)); + + vTemp = _mm_div_ps(vTemp, W); + + _mm_store_sd(reinterpret_cast(pOutputVector), _mm_castps_pd(vTemp)); + pOutputVector += OutputStride; + + i += 2; + } + } + } + } + + if (!(reinterpret_cast(pInputVector) & 0xF) && !(InputStride & 0xF)) + { + // Aligned input + for (; i < VectorCount; i++) + { + XMVECTOR V = _mm_castsi128_ps(_mm_loadl_epi64(reinterpret_cast(pInputVector))); + pInputVector += InputStride; + + XMVECTOR Y = XM_PERMUTE_PS(V, _MM_SHUFFLE(1, 1, 1, 1)); + XMVECTOR X = XM_PERMUTE_PS(V, _MM_SHUFFLE(0, 0, 0, 0)); + + XMVECTOR vTemp = XM_FMADD_PS(Y, row1, row3); + XMVECTOR vTemp2 = _mm_mul_ps(X, row0); + vTemp = _mm_add_ps(vTemp, vTemp2); + + XMVECTOR W = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(3, 3, 3, 3)); + + vTemp = _mm_div_ps(vTemp, W); + + _mm_store_sd(reinterpret_cast(pOutputVector), _mm_castps_pd(vTemp)); + pOutputVector += OutputStride; + } + } + else + { + // Unaligned input + for (; i < VectorCount; i++) + { + __m128 xy = _mm_castpd_ps(_mm_load_sd(reinterpret_cast(pInputVector))); + pInputVector += InputStride; + + XMVECTOR Y = XM_PERMUTE_PS(xy, _MM_SHUFFLE(1, 1, 1, 1)); + XMVECTOR X = XM_PERMUTE_PS(xy, _MM_SHUFFLE(0, 0, 0, 0)); + + XMVECTOR vTemp = XM_FMADD_PS(Y, row1, row3); + XMVECTOR vTemp2 = _mm_mul_ps(X, row0); + vTemp = _mm_add_ps(vTemp, vTemp2); + + XMVECTOR W = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(3, 3, 3, 3)); + + vTemp = _mm_div_ps(vTemp, W); + + _mm_store_sd(reinterpret_cast(pOutputVector), _mm_castps_pd(vTemp)); + pOutputVector += OutputStride; + } + } + + XM_SFENCE(); + + return pOutputStream; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector2TransformNormal +( + FXMVECTOR V, + FXMMATRIX M +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR Y = XMVectorSplatY(V); + XMVECTOR X = XMVectorSplatX(V); + + XMVECTOR Result = XMVectorMultiply(Y, M.r[1]); + Result = XMVectorMultiplyAdd(X, M.r[0], Result); + + return Result; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x2_t VL = vget_low_f32(V); + float32x4_t Result = vmulq_lane_f32(M.r[1], VL, 1); // Y + return vmlaq_lane_f32(Result, M.r[0], VL, 0); // X +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vResult = XM_PERMUTE_PS(V, _MM_SHUFFLE(1, 1, 1, 1)); // Y + vResult = _mm_mul_ps(vResult, M.r[1]); + XMVECTOR vTemp = XM_PERMUTE_PS(V, _MM_SHUFFLE(0, 0, 0, 0)); // X + vResult = XM_FMADD_PS(vTemp, M.r[0], vResult); + return vResult; +#endif +} + +//------------------------------------------------------------------------------ + +_Use_decl_annotations_ +inline XMFLOAT2* XM_CALLCONV XMVector2TransformNormalStream +( + XMFLOAT2* pOutputStream, + size_t OutputStride, + const XMFLOAT2* pInputStream, + size_t InputStride, + size_t VectorCount, + FXMMATRIX M +) noexcept +{ + assert(pOutputStream != nullptr); + assert(pInputStream != nullptr); + + assert(InputStride >= sizeof(XMFLOAT2)); + _Analysis_assume_(InputStride >= sizeof(XMFLOAT2)); + + assert(OutputStride >= sizeof(XMFLOAT2)); + _Analysis_assume_(OutputStride >= sizeof(XMFLOAT2)); + +#if defined(_XM_NO_INTRINSICS_) + + auto pInputVector = reinterpret_cast(pInputStream); + auto pOutputVector = reinterpret_cast(pOutputStream); + + const XMVECTOR row0 = M.r[0]; + const XMVECTOR row1 = M.r[1]; + + for (size_t i = 0; i < VectorCount; i++) + { + XMVECTOR V = XMLoadFloat2(reinterpret_cast(pInputVector)); + XMVECTOR Y = XMVectorSplatY(V); + XMVECTOR X = XMVectorSplatX(V); + + XMVECTOR Result = XMVectorMultiply(Y, row1); + Result = XMVectorMultiplyAdd(X, row0, Result); + + #ifdef _PREFAST_ + #pragma prefast(push) + #pragma prefast(disable : 26015, "PREfast noise: Esp:1307" ) + #endif + + XMStoreFloat2(reinterpret_cast(pOutputVector), Result); + + #ifdef _PREFAST_ + #pragma prefast(pop) + #endif + + pInputVector += InputStride; + pOutputVector += OutputStride; + } + + return pOutputStream; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + auto pInputVector = reinterpret_cast(pInputStream); + auto pOutputVector = reinterpret_cast(pOutputStream); + + const XMVECTOR row0 = M.r[0]; + const XMVECTOR row1 = M.r[1]; + + size_t i = 0; + size_t four = VectorCount >> 2; + if (four > 0) + { + if ((InputStride == sizeof(XMFLOAT2)) && (OutputStride == sizeof(XMFLOAT2))) + { + for (size_t j = 0; j < four; ++j) + { + float32x4x2_t V = vld2q_f32(reinterpret_cast(pInputVector)); + pInputVector += sizeof(XMFLOAT2) * 4; + + float32x2_t r = vget_low_f32(row0); + XMVECTOR vResult0 = vmulq_lane_f32(V.val[0], r, 0); // Ax + XMVECTOR vResult1 = vmulq_lane_f32(V.val[0], r, 1); // Bx + + XM_PREFETCH(pInputVector); + XM_PREFETCH(pInputVector + XM_CACHE_LINE_SIZE); + + r = vget_low_f32(row1); + vResult0 = vmlaq_lane_f32(vResult0, V.val[1], r, 0); // Ax+Ey + vResult1 = vmlaq_lane_f32(vResult1, V.val[1], r, 1); // Bx+Fy + + XM_PREFETCH(pInputVector + (XM_CACHE_LINE_SIZE * 2)); + XM_PREFETCH(pInputVector + (XM_CACHE_LINE_SIZE * 3)); + + V.val[0] = vResult0; + V.val[1] = vResult1; + + vst2q_f32(reinterpret_cast(pOutputVector), V); + pOutputVector += sizeof(XMFLOAT2) * 4; + + i += 4; + } + } + } + + for (; i < VectorCount; i++) + { + float32x2_t V = vld1_f32(reinterpret_cast(pInputVector)); + pInputVector += InputStride; + + XMVECTOR vResult = vmulq_lane_f32(row0, V, 0); // X + vResult = vmlaq_lane_f32(vResult, row1, V, 1); // Y + + V = vget_low_f32(vResult); + vst1_f32(reinterpret_cast(pOutputVector), V); + pOutputVector += OutputStride; + } + + return pOutputStream; +#elif defined(_XM_AVX2_INTRINSICS_) + auto pInputVector = reinterpret_cast(pInputStream); + auto pOutputVector = reinterpret_cast(pOutputStream); + + size_t i = 0; + size_t four = VectorCount >> 2; + if (four > 0) + { + __m256 row0 = _mm256_broadcast_ps(&M.r[0]); + __m256 row1 = _mm256_broadcast_ps(&M.r[1]); + + if (InputStride == sizeof(XMFLOAT2)) + { + if (OutputStride == sizeof(XMFLOAT2)) + { + if (!(reinterpret_cast(pOutputStream) & 0x1F)) + { + // Packed input, aligned & packed output + for (size_t j = 0; j < four; ++j) + { + __m256 VV = _mm256_loadu_ps(reinterpret_cast(pInputVector)); + pInputVector += sizeof(XMFLOAT2) * 4; + + __m256 Y2 = _mm256_shuffle_ps(VV, VV, _MM_SHUFFLE(3, 3, 3, 3)); + __m256 X2 = _mm256_shuffle_ps(VV, VV, _MM_SHUFFLE(2, 2, 2, 2)); + __m256 Y1 = _mm256_shuffle_ps(VV, VV, _MM_SHUFFLE(1, 1, 1, 1)); + __m256 X1 = _mm256_shuffle_ps(VV, VV, _MM_SHUFFLE(0, 0, 0, 0)); + + __m256 vTempA = _mm256_mul_ps(Y1, row1); + __m256 vTempB = _mm256_mul_ps(Y2, row1); + vTempA = _mm256_fmadd_ps(X1, row0, vTempA); + vTempB = _mm256_fmadd_ps(X2, row0, vTempB); + + X1 = _mm256_shuffle_ps(vTempA, vTempB, 0x44); + XM256_STREAM_PS(reinterpret_cast(pOutputVector), X1); + pOutputVector += sizeof(XMFLOAT2) * 4; + + i += 4; + } + } + else + { + // Packed input, packed output + for (size_t j = 0; j < four; ++j) + { + __m256 VV = _mm256_loadu_ps(reinterpret_cast(pInputVector)); + pInputVector += sizeof(XMFLOAT2) * 4; + + __m256 Y2 = _mm256_shuffle_ps(VV, VV, _MM_SHUFFLE(3, 3, 3, 3)); + __m256 X2 = _mm256_shuffle_ps(VV, VV, _MM_SHUFFLE(2, 2, 2, 2)); + __m256 Y1 = _mm256_shuffle_ps(VV, VV, _MM_SHUFFLE(1, 1, 1, 1)); + __m256 X1 = _mm256_shuffle_ps(VV, VV, _MM_SHUFFLE(0, 0, 0, 0)); + + __m256 vTempA = _mm256_mul_ps(Y1, row1); + __m256 vTempB = _mm256_mul_ps(Y2, row1); + vTempA = _mm256_fmadd_ps(X1, row0, vTempA); + vTempB = _mm256_fmadd_ps(X2, row0, vTempB); + + X1 = _mm256_shuffle_ps(vTempA, vTempB, 0x44); + _mm256_storeu_ps(reinterpret_cast(pOutputVector), X1); + pOutputVector += sizeof(XMFLOAT2) * 4; + + i += 4; + } + } + } + else + { + // Packed input, unpacked output + for (size_t j = 0; j < four; ++j) + { + __m256 VV = _mm256_loadu_ps(reinterpret_cast(pInputVector)); + pInputVector += sizeof(XMFLOAT2) * 4; + + __m256 Y2 = _mm256_shuffle_ps(VV, VV, _MM_SHUFFLE(3, 3, 3, 3)); + __m256 X2 = _mm256_shuffle_ps(VV, VV, _MM_SHUFFLE(2, 2, 2, 2)); + __m256 Y1 = _mm256_shuffle_ps(VV, VV, _MM_SHUFFLE(1, 1, 1, 1)); + __m256 X1 = _mm256_shuffle_ps(VV, VV, _MM_SHUFFLE(0, 0, 0, 0)); + + __m256 vTempA = _mm256_mul_ps(Y1, row1); + __m256 vTempB = _mm256_mul_ps(Y2, row1); + vTempA = _mm256_fmadd_ps(X1, row0, vTempA); + vTempB = _mm256_fmadd_ps(X2, row0, vTempB); + + _mm_store_sd(reinterpret_cast(pOutputVector), + _mm_castps_pd(_mm256_castps256_ps128(vTempA))); + pOutputVector += OutputStride; + + _mm_store_sd(reinterpret_cast(pOutputVector), + _mm_castps_pd(_mm256_castps256_ps128(vTempB))); + pOutputVector += OutputStride; + + _mm_store_sd(reinterpret_cast(pOutputVector), + _mm_castps_pd(_mm256_extractf128_ps(vTempA, 1))); + pOutputVector += OutputStride; + + _mm_store_sd(reinterpret_cast(pOutputVector), + _mm_castps_pd(_mm256_extractf128_ps(vTempB, 1))); + pOutputVector += OutputStride; + + i += 4; + } + } + } + } + + if (i < VectorCount) + { + const XMVECTOR row0 = M.r[0]; + const XMVECTOR row1 = M.r[1]; + + for (; i < VectorCount; i++) + { + __m128 xy = _mm_castpd_ps(_mm_load_sd(reinterpret_cast(pInputVector))); + pInputVector += InputStride; + + XMVECTOR Y = XM_PERMUTE_PS(xy, _MM_SHUFFLE(1, 1, 1, 1)); + XMVECTOR X = XM_PERMUTE_PS(xy, _MM_SHUFFLE(0, 0, 0, 0)); + + XMVECTOR vTemp = _mm_mul_ps(Y, row1); + vTemp = XM_FMADD_PS(X, row0, vTemp); + + _mm_store_sd(reinterpret_cast(pOutputVector), _mm_castps_pd(vTemp)); + pOutputVector += OutputStride; + } + } + + XM_SFENCE(); + + return pOutputStream; +#elif defined(_XM_SSE_INTRINSICS_) + auto pInputVector = reinterpret_cast(pInputStream); + auto pOutputVector = reinterpret_cast(pOutputStream); + + const XMVECTOR row0 = M.r[0]; + const XMVECTOR row1 = M.r[1]; + + size_t i = 0; + size_t two = VectorCount >> 1; + if (two > 0) + { + if (InputStride == sizeof(XMFLOAT2)) + { + if (OutputStride == sizeof(XMFLOAT2)) + { + if (!(reinterpret_cast(pOutputStream) & 0xF)) + { + // Packed input, aligned & packed output + for (size_t j = 0; j < two; ++j) + { + XMVECTOR V = _mm_loadu_ps(reinterpret_cast(pInputVector)); + pInputVector += sizeof(XMFLOAT2) * 2; + + // Result 1 + XMVECTOR Y = XM_PERMUTE_PS(V, _MM_SHUFFLE(1, 1, 1, 1)); + XMVECTOR X = XM_PERMUTE_PS(V, _MM_SHUFFLE(0, 0, 0, 0)); + + XMVECTOR vTemp = _mm_mul_ps(Y, row1); + XMVECTOR V1 = XM_FMADD_PS(X, row0, vTemp); + + // Result 2 + Y = XM_PERMUTE_PS(V, _MM_SHUFFLE(3, 3, 3, 3)); + X = XM_PERMUTE_PS(V, _MM_SHUFFLE(2, 2, 2, 2)); + + vTemp = _mm_mul_ps(Y, row1); + XMVECTOR V2 = XM_FMADD_PS(X, row0, vTemp); + + vTemp = _mm_movelh_ps(V1, V2); + + XM_STREAM_PS(reinterpret_cast(pOutputVector), vTemp); + pOutputVector += sizeof(XMFLOAT2) * 2; + + i += 2; + } + } + else + { + // Packed input, unaligned & packed output + for (size_t j = 0; j < two; ++j) + { + XMVECTOR V = _mm_loadu_ps(reinterpret_cast(pInputVector)); + pInputVector += sizeof(XMFLOAT2) * 2; + + // Result 1 + XMVECTOR Y = XM_PERMUTE_PS(V, _MM_SHUFFLE(1, 1, 1, 1)); + XMVECTOR X = XM_PERMUTE_PS(V, _MM_SHUFFLE(0, 0, 0, 0)); + + XMVECTOR vTemp = _mm_mul_ps(Y, row1); + XMVECTOR V1 = XM_FMADD_PS(X, row0, vTemp); + + // Result 2 + Y = XM_PERMUTE_PS(V, _MM_SHUFFLE(3, 3, 3, 3)); + X = XM_PERMUTE_PS(V, _MM_SHUFFLE(2, 2, 2, 2)); + + vTemp = _mm_mul_ps(Y, row1); + XMVECTOR V2 = XM_FMADD_PS(X, row0, vTemp); + + vTemp = _mm_movelh_ps(V1, V2); + + _mm_storeu_ps(reinterpret_cast(pOutputVector), vTemp); + pOutputVector += sizeof(XMFLOAT2) * 2; + + i += 2; + } + } + } + else + { + // Packed input, unpacked output + for (size_t j = 0; j < two; ++j) + { + XMVECTOR V = _mm_loadu_ps(reinterpret_cast(pInputVector)); + pInputVector += sizeof(XMFLOAT2) * 2; + + // Result 1 + XMVECTOR Y = XM_PERMUTE_PS(V, _MM_SHUFFLE(1, 1, 1, 1)); + XMVECTOR X = XM_PERMUTE_PS(V, _MM_SHUFFLE(0, 0, 0, 0)); + + XMVECTOR vTemp = _mm_mul_ps(Y, row1); + vTemp = XM_FMADD_PS(X, row0, vTemp); + + _mm_store_sd(reinterpret_cast(pOutputVector), _mm_castps_pd(vTemp)); + pOutputVector += OutputStride; + + // Result 2 + Y = XM_PERMUTE_PS(V, _MM_SHUFFLE(3, 3, 3, 3)); + X = XM_PERMUTE_PS(V, _MM_SHUFFLE(2, 2, 2, 2)); + + vTemp = _mm_mul_ps(Y, row1); + vTemp = XM_FMADD_PS(X, row0, vTemp); + + _mm_store_sd(reinterpret_cast(pOutputVector), _mm_castps_pd(vTemp)); + pOutputVector += OutputStride; + + i += 2; + } + } + } + } + + if (!(reinterpret_cast(pInputVector) & 0xF) && !(InputStride & 0xF)) + { + // Aligned input + for (; i < VectorCount; i++) + { + XMVECTOR V = _mm_castsi128_ps(_mm_loadl_epi64(reinterpret_cast(pInputVector))); + pInputVector += InputStride; + + XMVECTOR Y = XM_PERMUTE_PS(V, _MM_SHUFFLE(1, 1, 1, 1)); + XMVECTOR X = XM_PERMUTE_PS(V, _MM_SHUFFLE(0, 0, 0, 0)); + + XMVECTOR vTemp = _mm_mul_ps(Y, row1); + vTemp = XM_FMADD_PS(X, row0, vTemp); + + _mm_store_sd(reinterpret_cast(pOutputVector), _mm_castps_pd(vTemp)); + pOutputVector += OutputStride; + } + } + else + { + // Unaligned input + for (; i < VectorCount; i++) + { + __m128 xy = _mm_castpd_ps(_mm_load_sd(reinterpret_cast(pInputVector))); + pInputVector += InputStride; + + XMVECTOR Y = XM_PERMUTE_PS(xy, _MM_SHUFFLE(1, 1, 1, 1)); + XMVECTOR X = XM_PERMUTE_PS(xy, _MM_SHUFFLE(0, 0, 0, 0)); + + XMVECTOR vTemp = _mm_mul_ps(Y, row1); + vTemp = XM_FMADD_PS(X, row0, vTemp); + + _mm_store_sd(reinterpret_cast(pOutputVector), _mm_castps_pd(vTemp)); + pOutputVector += OutputStride; + } + } + + XM_SFENCE(); + + return pOutputStream; +#endif +} + +/**************************************************************************** + * + * 3D Vector + * + ****************************************************************************/ + +//------------------------------------------------------------------------------ +// Comparison operations +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ + +inline bool XM_CALLCONV XMVector3Equal +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + return (((V1.vector4_f32[0] == V2.vector4_f32[0]) && (V1.vector4_f32[1] == V2.vector4_f32[1]) && (V1.vector4_f32[2] == V2.vector4_f32[2])) != 0); +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x4_t vResult = vceqq_f32(V1, V2); + uint8x8x2_t vTemp = vzip_u8(vget_low_u8(vreinterpretq_u8_u32(vResult)), vget_high_u8(vreinterpretq_u8_u32(vResult))); + uint16x4x2_t vTemp2 = vzip_u16(vreinterpret_u16_u8(vTemp.val[0]), vreinterpret_u16_u8(vTemp.val[1])); + return ((vget_lane_u32(vreinterpret_u32_u16(vTemp2.val[1]), 1) & 0xFFFFFFU) == 0xFFFFFFU); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vTemp = _mm_cmpeq_ps(V1, V2); + return (((_mm_movemask_ps(vTemp) & 7) == 7) != 0); +#endif +} + +//------------------------------------------------------------------------------ + +inline uint32_t XM_CALLCONV XMVector3EqualR +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + uint32_t CR = 0; + if ((V1.vector4_f32[0] == V2.vector4_f32[0]) && + (V1.vector4_f32[1] == V2.vector4_f32[1]) && + (V1.vector4_f32[2] == V2.vector4_f32[2])) + { + CR = XM_CRMASK_CR6TRUE; + } + else if ((V1.vector4_f32[0] != V2.vector4_f32[0]) && + (V1.vector4_f32[1] != V2.vector4_f32[1]) && + (V1.vector4_f32[2] != V2.vector4_f32[2])) + { + CR = XM_CRMASK_CR6FALSE; + } + return CR; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x4_t vResult = vceqq_f32(V1, V2); + uint8x8x2_t vTemp = vzip_u8(vget_low_u8(vreinterpretq_u8_u32(vResult)), vget_high_u8(vreinterpretq_u8_u32(vResult))); + uint16x4x2_t vTemp2 = vzip_u16(vreinterpret_u16_u8(vTemp.val[0]), vreinterpret_u16_u8(vTemp.val[1])); + uint32_t r = vget_lane_u32(vreinterpret_u32_u16(vTemp2.val[1]), 1) & 0xFFFFFFU; + + uint32_t CR = 0; + if (r == 0xFFFFFFU) + { + CR = XM_CRMASK_CR6TRUE; + } + else if (!r) + { + CR = XM_CRMASK_CR6FALSE; + } + return CR; +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vTemp = _mm_cmpeq_ps(V1, V2); + int iTest = _mm_movemask_ps(vTemp) & 7; + uint32_t CR = 0; + if (iTest == 7) + { + CR = XM_CRMASK_CR6TRUE; + } + else if (!iTest) + { + CR = XM_CRMASK_CR6FALSE; + } + return CR; +#endif +} + +//------------------------------------------------------------------------------ + +inline bool XM_CALLCONV XMVector3EqualInt +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + return (((V1.vector4_u32[0] == V2.vector4_u32[0]) && (V1.vector4_u32[1] == V2.vector4_u32[1]) && (V1.vector4_u32[2] == V2.vector4_u32[2])) != 0); +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x4_t vResult = vceqq_u32(vreinterpretq_u32_f32(V1), vreinterpretq_u32_f32(V2)); + uint8x8x2_t vTemp = vzip_u8(vget_low_u8(vreinterpretq_u8_u32(vResult)), vget_high_u8(vreinterpretq_u8_u32(vResult))); + uint16x4x2_t vTemp2 = vzip_u16(vreinterpret_u16_u8(vTemp.val[0]), vreinterpret_u16_u8(vTemp.val[1])); + return ((vget_lane_u32(vreinterpret_u32_u16(vTemp2.val[1]), 1) & 0xFFFFFFU) == 0xFFFFFFU); +#elif defined(_XM_SSE_INTRINSICS_) + __m128i vTemp = _mm_cmpeq_epi32(_mm_castps_si128(V1), _mm_castps_si128(V2)); + return (((_mm_movemask_ps(_mm_castsi128_ps(vTemp)) & 7) == 7) != 0); +#endif +} + +//------------------------------------------------------------------------------ + +inline uint32_t XM_CALLCONV XMVector3EqualIntR +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + uint32_t CR = 0; + if ((V1.vector4_u32[0] == V2.vector4_u32[0]) && + (V1.vector4_u32[1] == V2.vector4_u32[1]) && + (V1.vector4_u32[2] == V2.vector4_u32[2])) + { + CR = XM_CRMASK_CR6TRUE; + } + else if ((V1.vector4_u32[0] != V2.vector4_u32[0]) && + (V1.vector4_u32[1] != V2.vector4_u32[1]) && + (V1.vector4_u32[2] != V2.vector4_u32[2])) + { + CR = XM_CRMASK_CR6FALSE; + } + return CR; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x4_t vResult = vceqq_u32(vreinterpretq_u32_f32(V1), vreinterpretq_u32_f32(V2)); + uint8x8x2_t vTemp = vzip_u8(vget_low_u8(vreinterpretq_u8_u32(vResult)), vget_high_u8(vreinterpretq_u8_u32(vResult))); + uint16x4x2_t vTemp2 = vzip_u16(vreinterpret_u16_u8(vTemp.val[0]), vreinterpret_u16_u8(vTemp.val[1])); + uint32_t r = vget_lane_u32(vreinterpret_u32_u16(vTemp2.val[1]), 1) & 0xFFFFFFU; + + uint32_t CR = 0; + if (r == 0xFFFFFFU) + { + CR = XM_CRMASK_CR6TRUE; + } + else if (!r) + { + CR = XM_CRMASK_CR6FALSE; + } + return CR; +#elif defined(_XM_SSE_INTRINSICS_) + __m128i vTemp = _mm_cmpeq_epi32(_mm_castps_si128(V1), _mm_castps_si128(V2)); + int iTemp = _mm_movemask_ps(_mm_castsi128_ps(vTemp)) & 7; + uint32_t CR = 0; + if (iTemp == 7) + { + CR = XM_CRMASK_CR6TRUE; + } + else if (!iTemp) + { + CR = XM_CRMASK_CR6FALSE; + } + return CR; +#endif +} + +//------------------------------------------------------------------------------ + +inline bool XM_CALLCONV XMVector3NearEqual +( + FXMVECTOR V1, + FXMVECTOR V2, + FXMVECTOR Epsilon +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + float dx, dy, dz; + + dx = fabsf(V1.vector4_f32[0] - V2.vector4_f32[0]); + dy = fabsf(V1.vector4_f32[1] - V2.vector4_f32[1]); + dz = fabsf(V1.vector4_f32[2] - V2.vector4_f32[2]); + return (((dx <= Epsilon.vector4_f32[0]) && + (dy <= Epsilon.vector4_f32[1]) && + (dz <= Epsilon.vector4_f32[2])) != 0); +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x4_t vDelta = vsubq_f32(V1, V2); +#if defined(_MSC_VER) && !defined(__clang__) && !defined(_ARM64_DISTINCT_NEON_TYPES) + uint32x4_t vResult = vacleq_f32(vDelta, Epsilon); +#else + uint32x4_t vResult = vcleq_f32(vabsq_f32(vDelta), Epsilon); +#endif + uint8x8x2_t vTemp = vzip_u8(vget_low_u8(vreinterpretq_u8_u32(vResult)), vget_high_u8(vreinterpretq_u8_u32(vResult))); + uint16x4x2_t vTemp2 = vzip_u16(vreinterpret_u16_u8(vTemp.val[0]), vreinterpret_u16_u8(vTemp.val[1])); + return ((vget_lane_u32(vreinterpret_u32_u16(vTemp2.val[1]), 1) & 0xFFFFFFU) == 0xFFFFFFU); +#elif defined(_XM_SSE_INTRINSICS_) + // Get the difference + XMVECTOR vDelta = _mm_sub_ps(V1, V2); + // Get the absolute value of the difference + XMVECTOR vTemp = _mm_setzero_ps(); + vTemp = _mm_sub_ps(vTemp, vDelta); + vTemp = _mm_max_ps(vTemp, vDelta); + vTemp = _mm_cmple_ps(vTemp, Epsilon); + // w is don't care + return (((_mm_movemask_ps(vTemp) & 7) == 0x7) != 0); +#endif +} + +//------------------------------------------------------------------------------ + +inline bool XM_CALLCONV XMVector3NotEqual +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + return (((V1.vector4_f32[0] != V2.vector4_f32[0]) || (V1.vector4_f32[1] != V2.vector4_f32[1]) || (V1.vector4_f32[2] != V2.vector4_f32[2])) != 0); +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x4_t vResult = vceqq_f32(V1, V2); + uint8x8x2_t vTemp = vzip_u8(vget_low_u8(vreinterpretq_u8_u32(vResult)), vget_high_u8(vreinterpretq_u8_u32(vResult))); + uint16x4x2_t vTemp2 = vzip_u16(vreinterpret_u16_u8(vTemp.val[0]), vreinterpret_u16_u8(vTemp.val[1])); + return ((vget_lane_u32(vreinterpret_u32_u16(vTemp2.val[1]), 1) & 0xFFFFFFU) != 0xFFFFFFU); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vTemp = _mm_cmpeq_ps(V1, V2); + return (((_mm_movemask_ps(vTemp) & 7) != 7) != 0); +#endif +} + +//------------------------------------------------------------------------------ + +inline bool XM_CALLCONV XMVector3NotEqualInt +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + return (((V1.vector4_u32[0] != V2.vector4_u32[0]) || (V1.vector4_u32[1] != V2.vector4_u32[1]) || (V1.vector4_u32[2] != V2.vector4_u32[2])) != 0); +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x4_t vResult = vceqq_u32(vreinterpretq_u32_f32(V1), vreinterpretq_u32_f32(V2)); + uint8x8x2_t vTemp = vzip_u8(vget_low_u8(vreinterpretq_u8_u32(vResult)), vget_high_u8(vreinterpretq_u8_u32(vResult))); + uint16x4x2_t vTemp2 = vzip_u16(vreinterpret_u16_u8(vTemp.val[0]), vreinterpret_u16_u8(vTemp.val[1])); + return ((vget_lane_u32(vreinterpret_u32_u16(vTemp2.val[1]), 1) & 0xFFFFFFU) != 0xFFFFFFU); +#elif defined(_XM_SSE_INTRINSICS_) + __m128i vTemp = _mm_cmpeq_epi32(_mm_castps_si128(V1), _mm_castps_si128(V2)); + return (((_mm_movemask_ps(_mm_castsi128_ps(vTemp)) & 7) != 7) != 0); +#endif +} + +//------------------------------------------------------------------------------ + +inline bool XM_CALLCONV XMVector3Greater +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + return (((V1.vector4_f32[0] > V2.vector4_f32[0]) && (V1.vector4_f32[1] > V2.vector4_f32[1]) && (V1.vector4_f32[2] > V2.vector4_f32[2])) != 0); +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x4_t vResult = vcgtq_f32(V1, V2); + uint8x8x2_t vTemp = vzip_u8(vget_low_u8(vreinterpretq_u8_u32(vResult)), vget_high_u8(vreinterpretq_u8_u32(vResult))); + uint16x4x2_t vTemp2 = vzip_u16(vreinterpret_u16_u8(vTemp.val[0]), vreinterpret_u16_u8(vTemp.val[1])); + return ((vget_lane_u32(vreinterpret_u32_u16(vTemp2.val[1]), 1) & 0xFFFFFFU) == 0xFFFFFFU); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vTemp = _mm_cmpgt_ps(V1, V2); + return (((_mm_movemask_ps(vTemp) & 7) == 7) != 0); +#endif +} + +//------------------------------------------------------------------------------ + +inline uint32_t XM_CALLCONV XMVector3GreaterR +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + uint32_t CR = 0; + if ((V1.vector4_f32[0] > V2.vector4_f32[0]) && + (V1.vector4_f32[1] > V2.vector4_f32[1]) && + (V1.vector4_f32[2] > V2.vector4_f32[2])) + { + CR = XM_CRMASK_CR6TRUE; + } + else if ((V1.vector4_f32[0] <= V2.vector4_f32[0]) && + (V1.vector4_f32[1] <= V2.vector4_f32[1]) && + (V1.vector4_f32[2] <= V2.vector4_f32[2])) + { + CR = XM_CRMASK_CR6FALSE; + } + return CR; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x4_t vResult = vcgtq_f32(V1, V2); + uint8x8x2_t vTemp = vzip_u8(vget_low_u8(vreinterpretq_u8_u32(vResult)), vget_high_u8(vreinterpretq_u8_u32(vResult))); + uint16x4x2_t vTemp2 = vzip_u16(vreinterpret_u16_u8(vTemp.val[0]), vreinterpret_u16_u8(vTemp.val[1])); + uint32_t r = vget_lane_u32(vreinterpret_u32_u16(vTemp2.val[1]), 1) & 0xFFFFFFU; + + uint32_t CR = 0; + if (r == 0xFFFFFFU) + { + CR = XM_CRMASK_CR6TRUE; + } + else if (!r) + { + CR = XM_CRMASK_CR6FALSE; + } + return CR; +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vTemp = _mm_cmpgt_ps(V1, V2); + uint32_t CR = 0; + int iTest = _mm_movemask_ps(vTemp) & 7; + if (iTest == 7) + { + CR = XM_CRMASK_CR6TRUE; + } + else if (!iTest) + { + CR = XM_CRMASK_CR6FALSE; + } + return CR; +#endif +} + +//------------------------------------------------------------------------------ + +inline bool XM_CALLCONV XMVector3GreaterOrEqual +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + return (((V1.vector4_f32[0] >= V2.vector4_f32[0]) && (V1.vector4_f32[1] >= V2.vector4_f32[1]) && (V1.vector4_f32[2] >= V2.vector4_f32[2])) != 0); +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x4_t vResult = vcgeq_f32(V1, V2); + uint8x8x2_t vTemp = vzip_u8(vget_low_u8(vreinterpretq_u8_u32(vResult)), vget_high_u8(vreinterpretq_u8_u32(vResult))); + uint16x4x2_t vTemp2 = vzip_u16(vreinterpret_u16_u8(vTemp.val[0]), vreinterpret_u16_u8(vTemp.val[1])); + return ((vget_lane_u32(vreinterpret_u32_u16(vTemp2.val[1]), 1) & 0xFFFFFFU) == 0xFFFFFFU); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vTemp = _mm_cmpge_ps(V1, V2); + return (((_mm_movemask_ps(vTemp) & 7) == 7) != 0); +#endif +} + +//------------------------------------------------------------------------------ + +inline uint32_t XM_CALLCONV XMVector3GreaterOrEqualR +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + uint32_t CR = 0; + if ((V1.vector4_f32[0] >= V2.vector4_f32[0]) && + (V1.vector4_f32[1] >= V2.vector4_f32[1]) && + (V1.vector4_f32[2] >= V2.vector4_f32[2])) + { + CR = XM_CRMASK_CR6TRUE; + } + else if ((V1.vector4_f32[0] < V2.vector4_f32[0]) && + (V1.vector4_f32[1] < V2.vector4_f32[1]) && + (V1.vector4_f32[2] < V2.vector4_f32[2])) + { + CR = XM_CRMASK_CR6FALSE; + } + return CR; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x4_t vResult = vcgeq_f32(V1, V2); + uint8x8x2_t vTemp = vzip_u8(vget_low_u8(vreinterpretq_u8_u32(vResult)), vget_high_u8(vreinterpretq_u8_u32(vResult))); + uint16x4x2_t vTemp2 = vzip_u16(vreinterpret_u16_u8(vTemp.val[0]), vreinterpret_u16_u8(vTemp.val[1])); + uint32_t r = vget_lane_u32(vreinterpret_u32_u16(vTemp2.val[1]), 1) & 0xFFFFFFU; + + uint32_t CR = 0; + if (r == 0xFFFFFFU) + { + CR = XM_CRMASK_CR6TRUE; + } + else if (!r) + { + CR = XM_CRMASK_CR6FALSE; + } + return CR; +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vTemp = _mm_cmpge_ps(V1, V2); + uint32_t CR = 0; + int iTest = _mm_movemask_ps(vTemp) & 7; + if (iTest == 7) + { + CR = XM_CRMASK_CR6TRUE; + } + else if (!iTest) + { + CR = XM_CRMASK_CR6FALSE; + } + return CR; +#endif +} + +//------------------------------------------------------------------------------ + +inline bool XM_CALLCONV XMVector3Less +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + return (((V1.vector4_f32[0] < V2.vector4_f32[0]) && (V1.vector4_f32[1] < V2.vector4_f32[1]) && (V1.vector4_f32[2] < V2.vector4_f32[2])) != 0); +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x4_t vResult = vcltq_f32(V1, V2); + uint8x8x2_t vTemp = vzip_u8(vget_low_u8(vreinterpretq_u8_u32(vResult)), vget_high_u8(vreinterpretq_u8_u32(vResult))); + uint16x4x2_t vTemp2 = vzip_u16(vreinterpret_u16_u8(vTemp.val[0]), vreinterpret_u16_u8(vTemp.val[1])); + return ((vget_lane_u32(vreinterpret_u32_u16(vTemp2.val[1]), 1) & 0xFFFFFFU) == 0xFFFFFFU); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vTemp = _mm_cmplt_ps(V1, V2); + return (((_mm_movemask_ps(vTemp) & 7) == 7) != 0); +#endif +} + +//------------------------------------------------------------------------------ + +inline bool XM_CALLCONV XMVector3LessOrEqual +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + return (((V1.vector4_f32[0] <= V2.vector4_f32[0]) && (V1.vector4_f32[1] <= V2.vector4_f32[1]) && (V1.vector4_f32[2] <= V2.vector4_f32[2])) != 0); +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x4_t vResult = vcleq_f32(V1, V2); + uint8x8x2_t vTemp = vzip_u8(vget_low_u8(vreinterpretq_u8_u32(vResult)), vget_high_u8(vreinterpretq_u8_u32(vResult))); + uint16x4x2_t vTemp2 = vzip_u16(vreinterpret_u16_u8(vTemp.val[0]), vreinterpret_u16_u8(vTemp.val[1])); + return ((vget_lane_u32(vreinterpret_u32_u16(vTemp2.val[1]), 1) & 0xFFFFFFU) == 0xFFFFFFU); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vTemp = _mm_cmple_ps(V1, V2); + return (((_mm_movemask_ps(vTemp) & 7) == 7) != 0); +#endif +} + +//------------------------------------------------------------------------------ + +inline bool XM_CALLCONV XMVector3InBounds +( + FXMVECTOR V, + FXMVECTOR Bounds +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + return (((V.vector4_f32[0] <= Bounds.vector4_f32[0] && V.vector4_f32[0] >= -Bounds.vector4_f32[0]) && + (V.vector4_f32[1] <= Bounds.vector4_f32[1] && V.vector4_f32[1] >= -Bounds.vector4_f32[1]) && + (V.vector4_f32[2] <= Bounds.vector4_f32[2] && V.vector4_f32[2] >= -Bounds.vector4_f32[2])) != 0); +#elif defined(_XM_ARM_NEON_INTRINSICS_) + // Test if less than or equal + uint32x4_t ivTemp1 = vcleq_f32(V, Bounds); + // Negate the bounds + float32x4_t vTemp2 = vnegq_f32(Bounds); + // Test if greater or equal (Reversed) + uint32x4_t ivTemp2 = vcleq_f32(vTemp2, V); + // Blend answers + ivTemp1 = vandq_u32(ivTemp1, ivTemp2); + // in bounds? + uint8x8x2_t vTemp = vzip_u8(vget_low_u8(vreinterpretq_u8_u32(ivTemp1)), vget_high_u8(vreinterpretq_u8_u32(ivTemp1))); + uint16x4x2_t vTemp3 = vzip_u16(vreinterpret_u16_u8(vTemp.val[0]), vreinterpret_u16_u8(vTemp.val[1])); + return ((vget_lane_u32(vreinterpret_u32_u16(vTemp3.val[1]), 1) & 0xFFFFFFU) == 0xFFFFFFU); +#elif defined(_XM_SSE_INTRINSICS_) + // Test if less than or equal + XMVECTOR vTemp1 = _mm_cmple_ps(V, Bounds); + // Negate the bounds + XMVECTOR vTemp2 = _mm_mul_ps(Bounds, g_XMNegativeOne); + // Test if greater or equal (Reversed) + vTemp2 = _mm_cmple_ps(vTemp2, V); + // Blend answers + vTemp1 = _mm_and_ps(vTemp1, vTemp2); + // x,y and z in bounds? (w is don't care) + return (((_mm_movemask_ps(vTemp1) & 0x7) == 0x7) != 0); +#else + return XMComparisonAllInBounds(XMVector3InBoundsR(V, Bounds)); +#endif +} + +//------------------------------------------------------------------------------ + +#if !defined(_XM_NO_INTRINSICS_) && defined(_MSC_VER) && !defined(__INTEL_COMPILER) +#pragma float_control(push) +#pragma float_control(precise, on) +#endif + +inline bool XM_CALLCONV XMVector3IsNaN(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + return (XMISNAN(V.vector4_f32[0]) || + XMISNAN(V.vector4_f32[1]) || + XMISNAN(V.vector4_f32[2])); + +#elif defined(_XM_ARM_NEON_INTRINSICS_) +#if defined(__clang__) && defined(__FINITE_MATH_ONLY__) + return isnan(vgetq_lane_f32(V, 0)) || isnan(vgetq_lane_f32(V, 1)) || isnan(vgetq_lane_f32(V, 2)); +#else +// Test against itself. NaN is always not equal + uint32x4_t vTempNan = vceqq_f32(V, V); + uint8x8x2_t vTemp = vzip_u8(vget_low_u8(vreinterpretq_u8_u32(vTempNan)), vget_high_u8(vreinterpretq_u8_u32(vTempNan))); + uint16x4x2_t vTemp2 = vzip_u16(vreinterpret_u16_u8(vTemp.val[0]), vreinterpret_u16_u8(vTemp.val[1])); + // If x or y or z are NaN, the mask is zero + return ((vget_lane_u32(vreinterpret_u32_u16(vTemp2.val[1]), 1) & 0xFFFFFFU) != 0xFFFFFFU); +#endif +#elif defined(_XM_SSE_INTRINSICS_) +#if defined(__clang__) && defined(__FINITE_MATH_ONLY__) + XM_ALIGNED_DATA(16) float tmp[4]; + _mm_store_ps(tmp, V); + return isnan(tmp[0]) || isnan(tmp[1]) || isnan(tmp[2]); +#else +// Test against itself. NaN is always not equal + XMVECTOR vTempNan = _mm_cmpneq_ps(V, V); + // If x or y or z are NaN, the mask is non-zero + return ((_mm_movemask_ps(vTempNan) & 7) != 0); +#endif +#endif +} + +#if !defined(_XM_NO_INTRINSICS_) && defined(_MSC_VER) && !defined(__INTEL_COMPILER) +#pragma float_control(pop) +#endif + +//------------------------------------------------------------------------------ + +inline bool XM_CALLCONV XMVector3IsInfinite(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + return (XMISINF(V.vector4_f32[0]) || + XMISINF(V.vector4_f32[1]) || + XMISINF(V.vector4_f32[2])); +#elif defined(_XM_ARM_NEON_INTRINSICS_) + // Mask off the sign bit + uint32x4_t vTempInf = vandq_u32(vreinterpretq_u32_f32(V), g_XMAbsMask); + // Compare to infinity + vTempInf = vceqq_f32(vreinterpretq_f32_u32(vTempInf), g_XMInfinity); + // If any are infinity, the signs are true. + uint8x8x2_t vTemp = vzip_u8(vget_low_u8(vreinterpretq_u8_u32(vTempInf)), vget_high_u8(vreinterpretq_u8_u32(vTempInf))); + uint16x4x2_t vTemp2 = vzip_u16(vreinterpret_u16_u8(vTemp.val[0]), vreinterpret_u16_u8(vTemp.val[1])); + return ((vget_lane_u32(vreinterpret_u32_u16(vTemp2.val[1]), 1) & 0xFFFFFFU) != 0); +#elif defined(_XM_SSE_INTRINSICS_) + // Mask off the sign bit + __m128 vTemp = _mm_and_ps(V, g_XMAbsMask); + // Compare to infinity + vTemp = _mm_cmpeq_ps(vTemp, g_XMInfinity); + // If x,y or z are infinity, the signs are true. + return ((_mm_movemask_ps(vTemp) & 7) != 0); +#endif +} + +//------------------------------------------------------------------------------ +// Computation operations +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector3Dot +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + float fValue = V1.vector4_f32[0] * V2.vector4_f32[0] + V1.vector4_f32[1] * V2.vector4_f32[1] + V1.vector4_f32[2] * V2.vector4_f32[2]; + XMVECTORF32 vResult; + vResult.f[0] = + vResult.f[1] = + vResult.f[2] = + vResult.f[3] = fValue; + return vResult.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x4_t vTemp = vmulq_f32(V1, V2); + float32x2_t v1 = vget_low_f32(vTemp); + float32x2_t v2 = vget_high_f32(vTemp); + v1 = vpadd_f32(v1, v1); + v2 = vdup_lane_f32(v2, 0); + v1 = vadd_f32(v1, v2); + return vcombine_f32(v1, v1); +#elif defined(_XM_SSE4_INTRINSICS_) + return _mm_dp_ps(V1, V2, 0x7f); +#elif defined(_XM_SSE3_INTRINSICS_) + XMVECTOR vTemp = _mm_mul_ps(V1, V2); + vTemp = _mm_and_ps(vTemp, g_XMMask3); + vTemp = _mm_hadd_ps(vTemp, vTemp); + return _mm_hadd_ps(vTemp, vTemp); +#elif defined(_XM_SSE_INTRINSICS_) + // Perform the dot product + XMVECTOR vDot = _mm_mul_ps(V1, V2); + // x=Dot.vector4_f32[1], y=Dot.vector4_f32[2] + XMVECTOR vTemp = XM_PERMUTE_PS(vDot, _MM_SHUFFLE(2, 1, 2, 1)); + // Result.vector4_f32[0] = x+y + vDot = _mm_add_ss(vDot, vTemp); + // x=Dot.vector4_f32[2] + vTemp = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(1, 1, 1, 1)); + // Result.vector4_f32[0] = (x+y)+z + vDot = _mm_add_ss(vDot, vTemp); + // Splat x + return XM_PERMUTE_PS(vDot, _MM_SHUFFLE(0, 0, 0, 0)); +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector3Cross +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ + // [ V1.y*V2.z - V1.z*V2.y, V1.z*V2.x - V1.x*V2.z, V1.x*V2.y - V1.y*V2.x ] + +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 vResult = { { { + (V1.vector4_f32[1] * V2.vector4_f32[2]) - (V1.vector4_f32[2] * V2.vector4_f32[1]), + (V1.vector4_f32[2] * V2.vector4_f32[0]) - (V1.vector4_f32[0] * V2.vector4_f32[2]), + (V1.vector4_f32[0] * V2.vector4_f32[1]) - (V1.vector4_f32[1] * V2.vector4_f32[0]), + 0.0f + } } }; + return vResult.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x2_t v1xy = vget_low_f32(V1); + float32x2_t v2xy = vget_low_f32(V2); + + float32x2_t v1yx = vrev64_f32(v1xy); + float32x2_t v2yx = vrev64_f32(v2xy); + + float32x2_t v1zz = vdup_lane_f32(vget_high_f32(V1), 0); + float32x2_t v2zz = vdup_lane_f32(vget_high_f32(V2), 0); + + XMVECTOR vResult = vmulq_f32(vcombine_f32(v1yx, v1xy), vcombine_f32(v2zz, v2yx)); + vResult = vmlsq_f32(vResult, vcombine_f32(v1zz, v1yx), vcombine_f32(v2yx, v2xy)); + vResult = vreinterpretq_f32_u32(veorq_u32(vreinterpretq_u32_f32(vResult), g_XMFlipY)); + return vreinterpretq_f32_u32(vandq_u32(vreinterpretq_u32_f32(vResult), g_XMMask3)); +#elif defined(_XM_SSE_INTRINSICS_) + // y1,z1,x1,w1 + XMVECTOR vTemp1 = XM_PERMUTE_PS(V1, _MM_SHUFFLE(3, 0, 2, 1)); + // z2,x2,y2,w2 + XMVECTOR vTemp2 = XM_PERMUTE_PS(V2, _MM_SHUFFLE(3, 1, 0, 2)); + // Perform the left operation + XMVECTOR vResult = _mm_mul_ps(vTemp1, vTemp2); + // z1,x1,y1,w1 + vTemp1 = XM_PERMUTE_PS(vTemp1, _MM_SHUFFLE(3, 0, 2, 1)); + // y2,z2,x2,w2 + vTemp2 = XM_PERMUTE_PS(vTemp2, _MM_SHUFFLE(3, 1, 0, 2)); + // Perform the right operation + vResult = XM_FNMADD_PS(vTemp1, vTemp2, vResult); + // Set w to zero + return _mm_and_ps(vResult, g_XMMask3); +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector3LengthSq(FXMVECTOR V) noexcept +{ + return XMVector3Dot(V, V); +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector3ReciprocalLengthEst(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR Result; + + Result = XMVector3LengthSq(V); + Result = XMVectorReciprocalSqrtEst(Result); + + return Result; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + // Dot3 + float32x4_t vTemp = vmulq_f32(V, V); + float32x2_t v1 = vget_low_f32(vTemp); + float32x2_t v2 = vget_high_f32(vTemp); + v1 = vpadd_f32(v1, v1); + v2 = vdup_lane_f32(v2, 0); + v1 = vadd_f32(v1, v2); + // Reciprocal sqrt (estimate) + v2 = vrsqrte_f32(v1); + return vcombine_f32(v2, v2); +#elif defined(_XM_SSE4_INTRINSICS_) + XMVECTOR vTemp = _mm_dp_ps(V, V, 0x7f); + return _mm_rsqrt_ps(vTemp); +#elif defined(_XM_SSE3_INTRINSICS_) + XMVECTOR vLengthSq = _mm_mul_ps(V, V); + vLengthSq = _mm_and_ps(vLengthSq, g_XMMask3); + vLengthSq = _mm_hadd_ps(vLengthSq, vLengthSq); + vLengthSq = _mm_hadd_ps(vLengthSq, vLengthSq); + vLengthSq = _mm_rsqrt_ps(vLengthSq); + return vLengthSq; +#elif defined(_XM_SSE_INTRINSICS_) + // Perform the dot product on x,y and z + XMVECTOR vLengthSq = _mm_mul_ps(V, V); + // vTemp has z and y + XMVECTOR vTemp = XM_PERMUTE_PS(vLengthSq, _MM_SHUFFLE(1, 2, 1, 2)); + // x+z, y + vLengthSq = _mm_add_ss(vLengthSq, vTemp); + // y,y,y,y + vTemp = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(1, 1, 1, 1)); + // x+z+y,??,??,?? + vLengthSq = _mm_add_ss(vLengthSq, vTemp); + // Splat the length squared + vLengthSq = XM_PERMUTE_PS(vLengthSq, _MM_SHUFFLE(0, 0, 0, 0)); + // Get the reciprocal + vLengthSq = _mm_rsqrt_ps(vLengthSq); + return vLengthSq; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector3ReciprocalLength(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR Result; + + Result = XMVector3LengthSq(V); + Result = XMVectorReciprocalSqrt(Result); + + return Result; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + // Dot3 + float32x4_t vTemp = vmulq_f32(V, V); + float32x2_t v1 = vget_low_f32(vTemp); + float32x2_t v2 = vget_high_f32(vTemp); + v1 = vpadd_f32(v1, v1); + v2 = vdup_lane_f32(v2, 0); + v1 = vadd_f32(v1, v2); + // Reciprocal sqrt + float32x2_t S0 = vrsqrte_f32(v1); + float32x2_t P0 = vmul_f32(v1, S0); + float32x2_t R0 = vrsqrts_f32(P0, S0); + float32x2_t S1 = vmul_f32(S0, R0); + float32x2_t P1 = vmul_f32(v1, S1); + float32x2_t R1 = vrsqrts_f32(P1, S1); + float32x2_t Result = vmul_f32(S1, R1); + return vcombine_f32(Result, Result); +#elif defined(_XM_SSE4_INTRINSICS_) + XMVECTOR vTemp = _mm_dp_ps(V, V, 0x7f); + XMVECTOR vLengthSq = _mm_sqrt_ps(vTemp); + return _mm_div_ps(g_XMOne, vLengthSq); +#elif defined(_XM_SSE3_INTRINSICS_) + XMVECTOR vDot = _mm_mul_ps(V, V); + vDot = _mm_and_ps(vDot, g_XMMask3); + vDot = _mm_hadd_ps(vDot, vDot); + vDot = _mm_hadd_ps(vDot, vDot); + vDot = _mm_sqrt_ps(vDot); + vDot = _mm_div_ps(g_XMOne, vDot); + return vDot; +#elif defined(_XM_SSE_INTRINSICS_) + // Perform the dot product + XMVECTOR vDot = _mm_mul_ps(V, V); + // x=Dot.y, y=Dot.z + XMVECTOR vTemp = XM_PERMUTE_PS(vDot, _MM_SHUFFLE(2, 1, 2, 1)); + // Result.x = x+y + vDot = _mm_add_ss(vDot, vTemp); + // x=Dot.z + vTemp = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(1, 1, 1, 1)); + // Result.x = (x+y)+z + vDot = _mm_add_ss(vDot, vTemp); + // Splat x + vDot = XM_PERMUTE_PS(vDot, _MM_SHUFFLE(0, 0, 0, 0)); + // Get the reciprocal + vDot = _mm_sqrt_ps(vDot); + // Get the reciprocal + vDot = _mm_div_ps(g_XMOne, vDot); + return vDot; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector3LengthEst(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR Result; + + Result = XMVector3LengthSq(V); + Result = XMVectorSqrtEst(Result); + + return Result; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + // Dot3 + float32x4_t vTemp = vmulq_f32(V, V); + float32x2_t v1 = vget_low_f32(vTemp); + float32x2_t v2 = vget_high_f32(vTemp); + v1 = vpadd_f32(v1, v1); + v2 = vdup_lane_f32(v2, 0); + v1 = vadd_f32(v1, v2); + const float32x2_t zero = vdup_n_f32(0); + uint32x2_t VEqualsZero = vceq_f32(v1, zero); + // Sqrt (estimate) + float32x2_t Result = vrsqrte_f32(v1); + Result = vmul_f32(v1, Result); + Result = vbsl_f32(VEqualsZero, zero, Result); + return vcombine_f32(Result, Result); +#elif defined(_XM_SSE4_INTRINSICS_) + XMVECTOR vTemp = _mm_dp_ps(V, V, 0x7f); + return _mm_sqrt_ps(vTemp); +#elif defined(_XM_SSE3_INTRINSICS_) + XMVECTOR vLengthSq = _mm_mul_ps(V, V); + vLengthSq = _mm_and_ps(vLengthSq, g_XMMask3); + vLengthSq = _mm_hadd_ps(vLengthSq, vLengthSq); + vLengthSq = _mm_hadd_ps(vLengthSq, vLengthSq); + vLengthSq = _mm_sqrt_ps(vLengthSq); + return vLengthSq; +#elif defined(_XM_SSE_INTRINSICS_) + // Perform the dot product on x,y and z + XMVECTOR vLengthSq = _mm_mul_ps(V, V); + // vTemp has z and y + XMVECTOR vTemp = XM_PERMUTE_PS(vLengthSq, _MM_SHUFFLE(1, 2, 1, 2)); + // x+z, y + vLengthSq = _mm_add_ss(vLengthSq, vTemp); + // y,y,y,y + vTemp = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(1, 1, 1, 1)); + // x+z+y,??,??,?? + vLengthSq = _mm_add_ss(vLengthSq, vTemp); + // Splat the length squared + vLengthSq = XM_PERMUTE_PS(vLengthSq, _MM_SHUFFLE(0, 0, 0, 0)); + // Get the length + vLengthSq = _mm_sqrt_ps(vLengthSq); + return vLengthSq; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector3Length(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR Result; + + Result = XMVector3LengthSq(V); + Result = XMVectorSqrt(Result); + + return Result; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + // Dot3 + float32x4_t vTemp = vmulq_f32(V, V); + float32x2_t v1 = vget_low_f32(vTemp); + float32x2_t v2 = vget_high_f32(vTemp); + v1 = vpadd_f32(v1, v1); + v2 = vdup_lane_f32(v2, 0); + v1 = vadd_f32(v1, v2); + const float32x2_t zero = vdup_n_f32(0); + uint32x2_t VEqualsZero = vceq_f32(v1, zero); + // Sqrt + float32x2_t S0 = vrsqrte_f32(v1); + float32x2_t P0 = vmul_f32(v1, S0); + float32x2_t R0 = vrsqrts_f32(P0, S0); + float32x2_t S1 = vmul_f32(S0, R0); + float32x2_t P1 = vmul_f32(v1, S1); + float32x2_t R1 = vrsqrts_f32(P1, S1); + float32x2_t Result = vmul_f32(S1, R1); + Result = vmul_f32(v1, Result); + Result = vbsl_f32(VEqualsZero, zero, Result); + return vcombine_f32(Result, Result); +#elif defined(_XM_SSE4_INTRINSICS_) + XMVECTOR vTemp = _mm_dp_ps(V, V, 0x7f); + return _mm_sqrt_ps(vTemp); +#elif defined(_XM_SSE3_INTRINSICS_) + XMVECTOR vLengthSq = _mm_mul_ps(V, V); + vLengthSq = _mm_and_ps(vLengthSq, g_XMMask3); + vLengthSq = _mm_hadd_ps(vLengthSq, vLengthSq); + vLengthSq = _mm_hadd_ps(vLengthSq, vLengthSq); + vLengthSq = _mm_sqrt_ps(vLengthSq); + return vLengthSq; +#elif defined(_XM_SSE_INTRINSICS_) + // Perform the dot product on x,y and z + XMVECTOR vLengthSq = _mm_mul_ps(V, V); + // vTemp has z and y + XMVECTOR vTemp = XM_PERMUTE_PS(vLengthSq, _MM_SHUFFLE(1, 2, 1, 2)); + // x+z, y + vLengthSq = _mm_add_ss(vLengthSq, vTemp); + // y,y,y,y + vTemp = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(1, 1, 1, 1)); + // x+z+y,??,??,?? + vLengthSq = _mm_add_ss(vLengthSq, vTemp); + // Splat the length squared + vLengthSq = XM_PERMUTE_PS(vLengthSq, _MM_SHUFFLE(0, 0, 0, 0)); + // Get the length + vLengthSq = _mm_sqrt_ps(vLengthSq); + return vLengthSq; +#endif +} + +//------------------------------------------------------------------------------ +// XMVector3NormalizeEst uses a reciprocal estimate and +// returns QNaN on zero and infinite vectors. + +inline XMVECTOR XM_CALLCONV XMVector3NormalizeEst(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR Result; + Result = XMVector3ReciprocalLength(V); + Result = XMVectorMultiply(V, Result); + return Result; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + // Dot3 + float32x4_t vTemp = vmulq_f32(V, V); + float32x2_t v1 = vget_low_f32(vTemp); + float32x2_t v2 = vget_high_f32(vTemp); + v1 = vpadd_f32(v1, v1); + v2 = vdup_lane_f32(v2, 0); + v1 = vadd_f32(v1, v2); + // Reciprocal sqrt (estimate) + v2 = vrsqrte_f32(v1); + // Normalize + return vmulq_f32(V, vcombine_f32(v2, v2)); +#elif defined(_XM_SSE4_INTRINSICS_) + XMVECTOR vTemp = _mm_dp_ps(V, V, 0x7f); + XMVECTOR vResult = _mm_rsqrt_ps(vTemp); + return _mm_mul_ps(vResult, V); +#elif defined(_XM_SSE3_INTRINSICS_) + XMVECTOR vDot = _mm_mul_ps(V, V); + vDot = _mm_and_ps(vDot, g_XMMask3); + vDot = _mm_hadd_ps(vDot, vDot); + vDot = _mm_hadd_ps(vDot, vDot); + vDot = _mm_rsqrt_ps(vDot); + vDot = _mm_mul_ps(vDot, V); + return vDot; +#elif defined(_XM_SSE_INTRINSICS_) + // Perform the dot product + XMVECTOR vDot = _mm_mul_ps(V, V); + // x=Dot.y, y=Dot.z + XMVECTOR vTemp = XM_PERMUTE_PS(vDot, _MM_SHUFFLE(2, 1, 2, 1)); + // Result.x = x+y + vDot = _mm_add_ss(vDot, vTemp); + // x=Dot.z + vTemp = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(1, 1, 1, 1)); + // Result.x = (x+y)+z + vDot = _mm_add_ss(vDot, vTemp); + // Splat x + vDot = XM_PERMUTE_PS(vDot, _MM_SHUFFLE(0, 0, 0, 0)); + // Get the reciprocal + vDot = _mm_rsqrt_ps(vDot); + // Perform the normalization + vDot = _mm_mul_ps(vDot, V); + return vDot; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector3Normalize(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + float fLength; + XMVECTOR vResult; + + vResult = XMVector3Length(V); + fLength = vResult.vector4_f32[0]; + + // Prevent divide by zero + if (fLength > 0) + { + fLength = 1.0f / fLength; + } + + vResult.vector4_f32[0] = V.vector4_f32[0] * fLength; + vResult.vector4_f32[1] = V.vector4_f32[1] * fLength; + vResult.vector4_f32[2] = V.vector4_f32[2] * fLength; + vResult.vector4_f32[3] = V.vector4_f32[3] * fLength; + return vResult; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + // Dot3 + float32x4_t vTemp = vmulq_f32(V, V); + float32x2_t v1 = vget_low_f32(vTemp); + float32x2_t v2 = vget_high_f32(vTemp); + v1 = vpadd_f32(v1, v1); + v2 = vdup_lane_f32(v2, 0); + v1 = vadd_f32(v1, v2); + uint32x2_t VEqualsZero = vceq_f32(v1, vdup_n_f32(0)); + uint32x2_t VEqualsInf = vceq_f32(v1, vget_low_f32(g_XMInfinity)); + // Reciprocal sqrt (2 iterations of Newton-Raphson) + float32x2_t S0 = vrsqrte_f32(v1); + float32x2_t P0 = vmul_f32(v1, S0); + float32x2_t R0 = vrsqrts_f32(P0, S0); + float32x2_t S1 = vmul_f32(S0, R0); + float32x2_t P1 = vmul_f32(v1, S1); + float32x2_t R1 = vrsqrts_f32(P1, S1); + v2 = vmul_f32(S1, R1); + // Normalize + XMVECTOR vResult = vmulq_f32(V, vcombine_f32(v2, v2)); + vResult = vbslq_f32(vcombine_u32(VEqualsZero, VEqualsZero), vdupq_n_f32(0), vResult); + return vbslq_f32(vcombine_u32(VEqualsInf, VEqualsInf), g_XMQNaN, vResult); +#elif defined(_XM_SSE4_INTRINSICS_) + XMVECTOR vLengthSq = _mm_dp_ps(V, V, 0x7f); + // Prepare for the division + XMVECTOR vResult = _mm_sqrt_ps(vLengthSq); + // Create zero with a single instruction + XMVECTOR vZeroMask = _mm_setzero_ps(); + // Test for a divide by zero (Must be FP to detect -0.0) + vZeroMask = _mm_cmpneq_ps(vZeroMask, vResult); + // Failsafe on zero (Or epsilon) length planes + // If the length is infinity, set the elements to zero + vLengthSq = _mm_cmpneq_ps(vLengthSq, g_XMInfinity); + // Divide to perform the normalization + vResult = _mm_div_ps(V, vResult); + // Any that are infinity, set to zero + vResult = _mm_and_ps(vResult, vZeroMask); + // Select qnan or result based on infinite length + XMVECTOR vTemp1 = _mm_andnot_ps(vLengthSq, g_XMQNaN); + XMVECTOR vTemp2 = _mm_and_ps(vResult, vLengthSq); + vResult = _mm_or_ps(vTemp1, vTemp2); + return vResult; +#elif defined(_XM_SSE3_INTRINSICS_) + // Perform the dot product on x,y and z only + XMVECTOR vLengthSq = _mm_mul_ps(V, V); + vLengthSq = _mm_and_ps(vLengthSq, g_XMMask3); + vLengthSq = _mm_hadd_ps(vLengthSq, vLengthSq); + vLengthSq = _mm_hadd_ps(vLengthSq, vLengthSq); + // Prepare for the division + XMVECTOR vResult = _mm_sqrt_ps(vLengthSq); + // Create zero with a single instruction + XMVECTOR vZeroMask = _mm_setzero_ps(); + // Test for a divide by zero (Must be FP to detect -0.0) + vZeroMask = _mm_cmpneq_ps(vZeroMask, vResult); + // Failsafe on zero (Or epsilon) length planes + // If the length is infinity, set the elements to zero + vLengthSq = _mm_cmpneq_ps(vLengthSq, g_XMInfinity); + // Divide to perform the normalization + vResult = _mm_div_ps(V, vResult); + // Any that are infinity, set to zero + vResult = _mm_and_ps(vResult, vZeroMask); + // Select qnan or result based on infinite length + XMVECTOR vTemp1 = _mm_andnot_ps(vLengthSq, g_XMQNaN); + XMVECTOR vTemp2 = _mm_and_ps(vResult, vLengthSq); + vResult = _mm_or_ps(vTemp1, vTemp2); + return vResult; +#elif defined(_XM_SSE_INTRINSICS_) + // Perform the dot product on x,y and z only + XMVECTOR vLengthSq = _mm_mul_ps(V, V); + XMVECTOR vTemp = XM_PERMUTE_PS(vLengthSq, _MM_SHUFFLE(2, 1, 2, 1)); + vLengthSq = _mm_add_ss(vLengthSq, vTemp); + vTemp = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(1, 1, 1, 1)); + vLengthSq = _mm_add_ss(vLengthSq, vTemp); + vLengthSq = XM_PERMUTE_PS(vLengthSq, _MM_SHUFFLE(0, 0, 0, 0)); + // Prepare for the division + XMVECTOR vResult = _mm_sqrt_ps(vLengthSq); + // Create zero with a single instruction + XMVECTOR vZeroMask = _mm_setzero_ps(); + // Test for a divide by zero (Must be FP to detect -0.0) + vZeroMask = _mm_cmpneq_ps(vZeroMask, vResult); + // Failsafe on zero (Or epsilon) length planes + // If the length is infinity, set the elements to zero + vLengthSq = _mm_cmpneq_ps(vLengthSq, g_XMInfinity); + // Divide to perform the normalization + vResult = _mm_div_ps(V, vResult); + // Any that are infinity, set to zero + vResult = _mm_and_ps(vResult, vZeroMask); + // Select qnan or result based on infinite length + XMVECTOR vTemp1 = _mm_andnot_ps(vLengthSq, g_XMQNaN); + XMVECTOR vTemp2 = _mm_and_ps(vResult, vLengthSq); + vResult = _mm_or_ps(vTemp1, vTemp2); + return vResult; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector3ClampLength +( + FXMVECTOR V, + float LengthMin, + float LengthMax +) noexcept +{ + XMVECTOR ClampMax = XMVectorReplicate(LengthMax); + XMVECTOR ClampMin = XMVectorReplicate(LengthMin); + + return XMVector3ClampLengthV(V, ClampMin, ClampMax); +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector3ClampLengthV +( + FXMVECTOR V, + FXMVECTOR LengthMin, + FXMVECTOR LengthMax +) noexcept +{ + assert((XMVectorGetY(LengthMin) == XMVectorGetX(LengthMin)) && (XMVectorGetZ(LengthMin) == XMVectorGetX(LengthMin))); + assert((XMVectorGetY(LengthMax) == XMVectorGetX(LengthMax)) && (XMVectorGetZ(LengthMax) == XMVectorGetX(LengthMax))); + assert(XMVector3GreaterOrEqual(LengthMin, XMVectorZero())); + assert(XMVector3GreaterOrEqual(LengthMax, XMVectorZero())); + assert(XMVector3GreaterOrEqual(LengthMax, LengthMin)); + + XMVECTOR LengthSq = XMVector3LengthSq(V); + + const XMVECTOR Zero = XMVectorZero(); + + XMVECTOR RcpLength = XMVectorReciprocalSqrt(LengthSq); + + XMVECTOR InfiniteLength = XMVectorEqualInt(LengthSq, g_XMInfinity.v); + XMVECTOR ZeroLength = XMVectorEqual(LengthSq, Zero); + + XMVECTOR Normal = XMVectorMultiply(V, RcpLength); + + XMVECTOR Length = XMVectorMultiply(LengthSq, RcpLength); + + XMVECTOR Select = XMVectorEqualInt(InfiniteLength, ZeroLength); + Length = XMVectorSelect(LengthSq, Length, Select); + Normal = XMVectorSelect(LengthSq, Normal, Select); + + XMVECTOR ControlMax = XMVectorGreater(Length, LengthMax); + XMVECTOR ControlMin = XMVectorLess(Length, LengthMin); + + XMVECTOR ClampLength = XMVectorSelect(Length, LengthMax, ControlMax); + ClampLength = XMVectorSelect(ClampLength, LengthMin, ControlMin); + + XMVECTOR Result = XMVectorMultiply(Normal, ClampLength); + + // Preserve the original vector (with no precision loss) if the length falls within the given range + XMVECTOR Control = XMVectorEqualInt(ControlMax, ControlMin); + Result = XMVectorSelect(Result, V, Control); + + return Result; +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector3Reflect +( + FXMVECTOR Incident, + FXMVECTOR Normal +) noexcept +{ + // Result = Incident - (2 * dot(Incident, Normal)) * Normal + + XMVECTOR Result = XMVector3Dot(Incident, Normal); + Result = XMVectorAdd(Result, Result); + Result = XMVectorNegativeMultiplySubtract(Result, Normal, Incident); + + return Result; +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector3Refract +( + FXMVECTOR Incident, + FXMVECTOR Normal, + float RefractionIndex +) noexcept +{ + XMVECTOR Index = XMVectorReplicate(RefractionIndex); + return XMVector3RefractV(Incident, Normal, Index); +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector3RefractV +( + FXMVECTOR Incident, + FXMVECTOR Normal, + FXMVECTOR RefractionIndex +) noexcept +{ + // Result = RefractionIndex * Incident - Normal * (RefractionIndex * dot(Incident, Normal) + + // sqrt(1 - RefractionIndex * RefractionIndex * (1 - dot(Incident, Normal) * dot(Incident, Normal)))) + +#if defined(_XM_NO_INTRINSICS_) + + const XMVECTOR Zero = XMVectorZero(); + + XMVECTOR IDotN = XMVector3Dot(Incident, Normal); + + // R = 1.0f - RefractionIndex * RefractionIndex * (1.0f - IDotN * IDotN) + XMVECTOR R = XMVectorNegativeMultiplySubtract(IDotN, IDotN, g_XMOne.v); + R = XMVectorMultiply(R, RefractionIndex); + R = XMVectorNegativeMultiplySubtract(R, RefractionIndex, g_XMOne.v); + + if (XMVector4LessOrEqual(R, Zero)) + { + // Total internal reflection + return Zero; + } + else + { + // R = RefractionIndex * IDotN + sqrt(R) + R = XMVectorSqrt(R); + R = XMVectorMultiplyAdd(RefractionIndex, IDotN, R); + + // Result = RefractionIndex * Incident - Normal * R + XMVECTOR Result = XMVectorMultiply(RefractionIndex, Incident); + Result = XMVectorNegativeMultiplySubtract(Normal, R, Result); + + return Result; + } + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + XMVECTOR IDotN = XMVector3Dot(Incident, Normal); + + // R = 1.0f - RefractionIndex * RefractionIndex * (1.0f - IDotN * IDotN) + float32x4_t R = vmlsq_f32(g_XMOne, IDotN, IDotN); + R = vmulq_f32(R, RefractionIndex); + R = vmlsq_f32(g_XMOne, R, RefractionIndex); + + uint32x4_t isrzero = vcleq_f32(R, g_XMZero); + uint8x8x2_t vTemp = vzip_u8(vget_low_u8(vreinterpretq_u8_u32(isrzero)), vget_high_u8(vreinterpretq_u8_u32(isrzero))); + uint16x4x2_t vTemp2 = vzip_u16(vreinterpret_u16_u8(vTemp.val[0]), vreinterpret_u16_u8(vTemp.val[1])); + + float32x4_t vResult; + if (vget_lane_u32(vreinterpret_u32_u16(vTemp2.val[1]), 1) == 0xFFFFFFFFU) + { + // Total internal reflection + vResult = g_XMZero; + } + else + { + // Sqrt(R) + float32x4_t S0 = vrsqrteq_f32(R); + float32x4_t P0 = vmulq_f32(R, S0); + float32x4_t R0 = vrsqrtsq_f32(P0, S0); + float32x4_t S1 = vmulq_f32(S0, R0); + float32x4_t P1 = vmulq_f32(R, S1); + float32x4_t R1 = vrsqrtsq_f32(P1, S1); + float32x4_t S2 = vmulq_f32(S1, R1); + R = vmulq_f32(R, S2); + // R = RefractionIndex * IDotN + sqrt(R) + R = vmlaq_f32(R, RefractionIndex, IDotN); + // Result = RefractionIndex * Incident - Normal * R + vResult = vmulq_f32(RefractionIndex, Incident); + vResult = vmlsq_f32(vResult, R, Normal); + } + return vResult; +#elif defined(_XM_SSE_INTRINSICS_) + // Result = RefractionIndex * Incident - Normal * (RefractionIndex * dot(Incident, Normal) + + // sqrt(1 - RefractionIndex * RefractionIndex * (1 - dot(Incident, Normal) * dot(Incident, Normal)))) + XMVECTOR IDotN = XMVector3Dot(Incident, Normal); + // R = 1.0f - RefractionIndex * RefractionIndex * (1.0f - IDotN * IDotN) + XMVECTOR R = XM_FNMADD_PS(IDotN, IDotN, g_XMOne); + XMVECTOR R2 = _mm_mul_ps(RefractionIndex, RefractionIndex); + R = XM_FNMADD_PS(R, R2, g_XMOne); + + XMVECTOR vResult = _mm_cmple_ps(R, g_XMZero); + if (_mm_movemask_ps(vResult) == 0x0f) + { + // Total internal reflection + vResult = g_XMZero; + } + else + { + // R = RefractionIndex * IDotN + sqrt(R) + R = _mm_sqrt_ps(R); + R = XM_FMADD_PS(RefractionIndex, IDotN, R); + // Result = RefractionIndex * Incident - Normal * R + vResult = _mm_mul_ps(RefractionIndex, Incident); + vResult = XM_FNMADD_PS(R, Normal, vResult); + } + return vResult; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector3Orthogonal(FXMVECTOR V) noexcept +{ + XMVECTOR Zero = XMVectorZero(); + XMVECTOR Z = XMVectorSplatZ(V); + XMVECTOR YZYY = XMVectorSwizzle(V); + + XMVECTOR NegativeV = XMVectorSubtract(Zero, V); + + XMVECTOR ZIsNegative = XMVectorLess(Z, Zero); + XMVECTOR YZYYIsNegative = XMVectorLess(YZYY, Zero); + + XMVECTOR S = XMVectorAdd(YZYY, Z); + XMVECTOR D = XMVectorSubtract(YZYY, Z); + + XMVECTOR Select = XMVectorEqualInt(ZIsNegative, YZYYIsNegative); + + XMVECTOR R0 = XMVectorPermute(NegativeV, S); + XMVECTOR R1 = XMVectorPermute(V, D); + + return XMVectorSelect(R1, R0, Select); +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector3AngleBetweenNormalsEst +( + FXMVECTOR N1, + FXMVECTOR N2 +) noexcept +{ + XMVECTOR Result = XMVector3Dot(N1, N2); + Result = XMVectorClamp(Result, g_XMNegativeOne.v, g_XMOne.v); + Result = XMVectorACosEst(Result); + return Result; +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector3AngleBetweenNormals +( + FXMVECTOR N1, + FXMVECTOR N2 +) noexcept +{ + XMVECTOR Result = XMVector3Dot(N1, N2); + Result = XMVectorClamp(Result, g_XMNegativeOne.v, g_XMOne.v); + Result = XMVectorACos(Result); + return Result; +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector3AngleBetweenVectors +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ + XMVECTOR L1 = XMVector3ReciprocalLength(V1); + XMVECTOR L2 = XMVector3ReciprocalLength(V2); + + XMVECTOR Dot = XMVector3Dot(V1, V2); + + L1 = XMVectorMultiply(L1, L2); + + XMVECTOR CosAngle = XMVectorMultiply(Dot, L1); + CosAngle = XMVectorClamp(CosAngle, g_XMNegativeOne.v, g_XMOne.v); + + return XMVectorACos(CosAngle); +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector3LinePointDistance +( + FXMVECTOR LinePoint1, + FXMVECTOR LinePoint2, + FXMVECTOR Point +) noexcept +{ + // Given a vector PointVector from LinePoint1 to Point and a vector + // LineVector from LinePoint1 to LinePoint2, the scaled distance + // PointProjectionScale from LinePoint1 to the perpendicular projection + // of PointVector onto the line is defined as: + // + // PointProjectionScale = dot(PointVector, LineVector) / LengthSq(LineVector) + + XMVECTOR PointVector = XMVectorSubtract(Point, LinePoint1); + XMVECTOR LineVector = XMVectorSubtract(LinePoint2, LinePoint1); + + XMVECTOR LengthSq = XMVector3LengthSq(LineVector); + + XMVECTOR PointProjectionScale = XMVector3Dot(PointVector, LineVector); + PointProjectionScale = XMVectorDivide(PointProjectionScale, LengthSq); + + XMVECTOR DistanceVector = XMVectorMultiply(LineVector, PointProjectionScale); + DistanceVector = XMVectorSubtract(PointVector, DistanceVector); + + return XMVector3Length(DistanceVector); +} + +//------------------------------------------------------------------------------ + +_Use_decl_annotations_ +inline void XM_CALLCONV XMVector3ComponentsFromNormal +( + XMVECTOR* pParallel, + XMVECTOR* pPerpendicular, + FXMVECTOR V, + FXMVECTOR Normal +) noexcept +{ + assert(pParallel != nullptr); + assert(pPerpendicular != nullptr); + + XMVECTOR Scale = XMVector3Dot(V, Normal); + + XMVECTOR Parallel = XMVectorMultiply(Normal, Scale); + + *pParallel = Parallel; + *pPerpendicular = XMVectorSubtract(V, Parallel); +} + +//------------------------------------------------------------------------------ +// Transform a vector using a rotation expressed as a unit quaternion + +inline XMVECTOR XM_CALLCONV XMVector3Rotate +( + FXMVECTOR V, + FXMVECTOR RotationQuaternion +) noexcept +{ + XMVECTOR A = XMVectorSelect(g_XMSelect1110.v, V, g_XMSelect1110.v); + XMVECTOR Q = XMQuaternionConjugate(RotationQuaternion); + XMVECTOR Result = XMQuaternionMultiply(Q, A); + return XMQuaternionMultiply(Result, RotationQuaternion); +} + +//------------------------------------------------------------------------------ +// Transform a vector using the inverse of a rotation expressed as a unit quaternion + +inline XMVECTOR XM_CALLCONV XMVector3InverseRotate +( + FXMVECTOR V, + FXMVECTOR RotationQuaternion +) noexcept +{ + XMVECTOR A = XMVectorSelect(g_XMSelect1110.v, V, g_XMSelect1110.v); + XMVECTOR Result = XMQuaternionMultiply(RotationQuaternion, A); + XMVECTOR Q = XMQuaternionConjugate(RotationQuaternion); + return XMQuaternionMultiply(Result, Q); +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector3Transform +( + FXMVECTOR V, + FXMMATRIX M +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR Z = XMVectorSplatZ(V); + XMVECTOR Y = XMVectorSplatY(V); + XMVECTOR X = XMVectorSplatX(V); + + XMVECTOR Result = XMVectorMultiplyAdd(Z, M.r[2], M.r[3]); + Result = XMVectorMultiplyAdd(Y, M.r[1], Result); + Result = XMVectorMultiplyAdd(X, M.r[0], Result); + + return Result; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x2_t VL = vget_low_f32(V); + XMVECTOR vResult = vmlaq_lane_f32(M.r[3], M.r[0], VL, 0); // X + vResult = vmlaq_lane_f32(vResult, M.r[1], VL, 1); // Y + return vmlaq_lane_f32(vResult, M.r[2], vget_high_f32(V), 0); // Z +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vResult = XM_PERMUTE_PS(V, _MM_SHUFFLE(2, 2, 2, 2)); // Z + vResult = XM_FMADD_PS(vResult, M.r[2], M.r[3]); + XMVECTOR vTemp = XM_PERMUTE_PS(V, _MM_SHUFFLE(1, 1, 1, 1)); // Y + vResult = XM_FMADD_PS(vTemp, M.r[1], vResult); + vTemp = XM_PERMUTE_PS(V, _MM_SHUFFLE(0, 0, 0, 0)); // X + vResult = XM_FMADD_PS(vTemp, M.r[0], vResult); + return vResult; +#endif +} + +//------------------------------------------------------------------------------ + +#ifdef _PREFAST_ +#pragma prefast(push) +#pragma prefast(disable : 26015 26019, "PREfast noise: Esp:1307" ) +#endif + +_Use_decl_annotations_ +inline XMFLOAT4* XM_CALLCONV XMVector3TransformStream +( + XMFLOAT4* pOutputStream, + size_t OutputStride, + const XMFLOAT3* pInputStream, + size_t InputStride, + size_t VectorCount, + FXMMATRIX M +) noexcept +{ + assert(pOutputStream != nullptr); + assert(pInputStream != nullptr); + + assert(InputStride >= sizeof(XMFLOAT3)); + _Analysis_assume_(InputStride >= sizeof(XMFLOAT3)); + + assert(OutputStride >= sizeof(XMFLOAT4)); + _Analysis_assume_(OutputStride >= sizeof(XMFLOAT4)); + +#if defined(_XM_NO_INTRINSICS_) + + auto pInputVector = reinterpret_cast(pInputStream); + auto pOutputVector = reinterpret_cast(pOutputStream); + + const XMVECTOR row0 = M.r[0]; + const XMVECTOR row1 = M.r[1]; + const XMVECTOR row2 = M.r[2]; + const XMVECTOR row3 = M.r[3]; + + for (size_t i = 0; i < VectorCount; i++) + { + XMVECTOR V = XMLoadFloat3(reinterpret_cast(pInputVector)); + XMVECTOR Z = XMVectorSplatZ(V); + XMVECTOR Y = XMVectorSplatY(V); + XMVECTOR X = XMVectorSplatX(V); + + XMVECTOR Result = XMVectorMultiplyAdd(Z, row2, row3); + Result = XMVectorMultiplyAdd(Y, row1, Result); + Result = XMVectorMultiplyAdd(X, row0, Result); + + XMStoreFloat4(reinterpret_cast(pOutputVector), Result); + + pInputVector += InputStride; + pOutputVector += OutputStride; + } + + return pOutputStream; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + auto pInputVector = reinterpret_cast(pInputStream); + auto pOutputVector = reinterpret_cast(pOutputStream); + + const XMVECTOR row0 = M.r[0]; + const XMVECTOR row1 = M.r[1]; + const XMVECTOR row2 = M.r[2]; + const XMVECTOR row3 = M.r[3]; + + size_t i = 0; + size_t four = VectorCount >> 2; + if (four > 0) + { + if ((InputStride == sizeof(XMFLOAT3)) && (OutputStride == sizeof(XMFLOAT4))) + { + for (size_t j = 0; j < four; ++j) + { + float32x4x3_t V = vld3q_f32(reinterpret_cast(pInputVector)); + pInputVector += sizeof(XMFLOAT3) * 4; + + float32x2_t r3 = vget_low_f32(row3); + float32x2_t r = vget_low_f32(row0); + XMVECTOR vResult0 = vmlaq_lane_f32(vdupq_lane_f32(r3, 0), V.val[0], r, 0); // Ax+M + XMVECTOR vResult1 = vmlaq_lane_f32(vdupq_lane_f32(r3, 1), V.val[0], r, 1); // Bx+N + + XM_PREFETCH(pInputVector); + + r3 = vget_high_f32(row3); + r = vget_high_f32(row0); + XMVECTOR vResult2 = vmlaq_lane_f32(vdupq_lane_f32(r3, 0), V.val[0], r, 0); // Cx+O + XMVECTOR vResult3 = vmlaq_lane_f32(vdupq_lane_f32(r3, 1), V.val[0], r, 1); // Dx+P + + XM_PREFETCH(pInputVector + XM_CACHE_LINE_SIZE); + + r = vget_low_f32(row1); + vResult0 = vmlaq_lane_f32(vResult0, V.val[1], r, 0); // Ax+Ey+M + vResult1 = vmlaq_lane_f32(vResult1, V.val[1], r, 1); // Bx+Fy+N + + XM_PREFETCH(pInputVector + (XM_CACHE_LINE_SIZE * 2)); + + r = vget_high_f32(row1); + vResult2 = vmlaq_lane_f32(vResult2, V.val[1], r, 0); // Cx+Gy+O + vResult3 = vmlaq_lane_f32(vResult3, V.val[1], r, 1); // Dx+Hy+P + + XM_PREFETCH(pInputVector + (XM_CACHE_LINE_SIZE * 3)); + + r = vget_low_f32(row2); + vResult0 = vmlaq_lane_f32(vResult0, V.val[2], r, 0); // Ax+Ey+Iz+M + vResult1 = vmlaq_lane_f32(vResult1, V.val[2], r, 1); // Bx+Fy+Jz+N + + XM_PREFETCH(pInputVector + (XM_CACHE_LINE_SIZE * 4)); + + r = vget_high_f32(row2); + vResult2 = vmlaq_lane_f32(vResult2, V.val[2], r, 0); // Cx+Gy+Kz+O + vResult3 = vmlaq_lane_f32(vResult3, V.val[2], r, 1); // Dx+Hy+Lz+P + + XM_PREFETCH(pInputVector + (XM_CACHE_LINE_SIZE * 5)); + + float32x4x4_t R; + R.val[0] = vResult0; + R.val[1] = vResult1; + R.val[2] = vResult2; + R.val[3] = vResult3; + + vst4q_f32(reinterpret_cast(pOutputVector), R); + pOutputVector += sizeof(XMFLOAT4) * 4; + + i += 4; + } + } + } + + for (; i < VectorCount; i++) + { + float32x2_t VL = vld1_f32(reinterpret_cast(pInputVector)); + float32x2_t zero = vdup_n_f32(0); + float32x2_t VH = vld1_lane_f32(reinterpret_cast(pInputVector) + 2, zero, 0); + pInputVector += InputStride; + + XMVECTOR vResult = vmlaq_lane_f32(row3, row0, VL, 0); // X + vResult = vmlaq_lane_f32(vResult, row1, VL, 1); // Y + vResult = vmlaq_lane_f32(vResult, row2, VH, 0); // Z + + vst1q_f32(reinterpret_cast(pOutputVector), vResult); + pOutputVector += OutputStride; + } + + return pOutputStream; +#elif defined(_XM_SSE_INTRINSICS_) + auto pInputVector = reinterpret_cast(pInputStream); + auto pOutputVector = reinterpret_cast(pOutputStream); + + const XMVECTOR row0 = M.r[0]; + const XMVECTOR row1 = M.r[1]; + const XMVECTOR row2 = M.r[2]; + const XMVECTOR row3 = M.r[3]; + + size_t i = 0; + size_t four = VectorCount >> 2; + if (four > 0) + { + if (InputStride == sizeof(XMFLOAT3)) + { + if (!(reinterpret_cast(pOutputStream) & 0xF) && !(OutputStride & 0xF)) + { + // Packed input, aligned output + for (size_t j = 0; j < four; ++j) + { + __m128 V1 = _mm_loadu_ps(reinterpret_cast(pInputVector)); + __m128 L2 = _mm_loadu_ps(reinterpret_cast(pInputVector + 16)); + __m128 L3 = _mm_loadu_ps(reinterpret_cast(pInputVector + 32)); + pInputVector += sizeof(XMFLOAT3) * 4; + + // Unpack the 4 vectors (.w components are junk) + XM3UNPACK3INTO4(V1, L2, L3); + + // Result 1 + XMVECTOR Z = XM_PERMUTE_PS(V1, _MM_SHUFFLE(2, 2, 2, 2)); + XMVECTOR Y = XM_PERMUTE_PS(V1, _MM_SHUFFLE(1, 1, 1, 1)); + XMVECTOR X = XM_PERMUTE_PS(V1, _MM_SHUFFLE(0, 0, 0, 0)); + + XMVECTOR vTemp = XM_FMADD_PS(Z, row2, row3); + XMVECTOR vTemp2 = _mm_mul_ps(Y, row1); + XMVECTOR vTemp3 = _mm_mul_ps(X, row0); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + XM_STREAM_PS(reinterpret_cast(pOutputVector), vTemp); + pOutputVector += OutputStride; + + // Result 2 + Z = XM_PERMUTE_PS(V2, _MM_SHUFFLE(2, 2, 2, 2)); + Y = XM_PERMUTE_PS(V2, _MM_SHUFFLE(1, 1, 1, 1)); + X = XM_PERMUTE_PS(V2, _MM_SHUFFLE(0, 0, 0, 0)); + + vTemp = XM_FMADD_PS(Z, row2, row3); + vTemp2 = _mm_mul_ps(Y, row1); + vTemp3 = _mm_mul_ps(X, row0); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + XM_STREAM_PS(reinterpret_cast(pOutputVector), vTemp); + pOutputVector += OutputStride; + + // Result 3 + Z = XM_PERMUTE_PS(V3, _MM_SHUFFLE(2, 2, 2, 2)); + Y = XM_PERMUTE_PS(V3, _MM_SHUFFLE(1, 1, 1, 1)); + X = XM_PERMUTE_PS(V3, _MM_SHUFFLE(0, 0, 0, 0)); + + vTemp = XM_FMADD_PS(Z, row2, row3); + vTemp2 = _mm_mul_ps(Y, row1); + vTemp3 = _mm_mul_ps(X, row0); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + XM_STREAM_PS(reinterpret_cast(pOutputVector), vTemp); + pOutputVector += OutputStride; + + // Result 4 + Z = XM_PERMUTE_PS(V4, _MM_SHUFFLE(2, 2, 2, 2)); + Y = XM_PERMUTE_PS(V4, _MM_SHUFFLE(1, 1, 1, 1)); + X = XM_PERMUTE_PS(V4, _MM_SHUFFLE(0, 0, 0, 0)); + + vTemp = XM_FMADD_PS(Z, row2, row3); + vTemp2 = _mm_mul_ps(Y, row1); + vTemp3 = _mm_mul_ps(X, row0); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + XM_STREAM_PS(reinterpret_cast(pOutputVector), vTemp); + pOutputVector += OutputStride; + + i += 4; + } + } + else + { + // Packed input, unaligned output + for (size_t j = 0; j < four; ++j) + { + __m128 V1 = _mm_loadu_ps(reinterpret_cast(pInputVector)); + __m128 L2 = _mm_loadu_ps(reinterpret_cast(pInputVector + 16)); + __m128 L3 = _mm_loadu_ps(reinterpret_cast(pInputVector + 32)); + pInputVector += sizeof(XMFLOAT3) * 4; + + // Unpack the 4 vectors (.w components are junk) + XM3UNPACK3INTO4(V1, L2, L3); + + // Result 1 + XMVECTOR Z = XM_PERMUTE_PS(V1, _MM_SHUFFLE(2, 2, 2, 2)); + XMVECTOR Y = XM_PERMUTE_PS(V1, _MM_SHUFFLE(1, 1, 1, 1)); + XMVECTOR X = XM_PERMUTE_PS(V1, _MM_SHUFFLE(0, 0, 0, 0)); + + XMVECTOR vTemp = XM_FMADD_PS(Z, row2, row3); + XMVECTOR vTemp2 = _mm_mul_ps(Y, row1); + XMVECTOR vTemp3 = _mm_mul_ps(X, row0); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + _mm_storeu_ps(reinterpret_cast(pOutputVector), vTemp); + pOutputVector += OutputStride; + + // Result 2 + Z = XM_PERMUTE_PS(V2, _MM_SHUFFLE(2, 2, 2, 2)); + Y = XM_PERMUTE_PS(V2, _MM_SHUFFLE(1, 1, 1, 1)); + X = XM_PERMUTE_PS(V2, _MM_SHUFFLE(0, 0, 0, 0)); + + vTemp = XM_FMADD_PS(Z, row2, row3); + vTemp2 = _mm_mul_ps(Y, row1); + vTemp3 = _mm_mul_ps(X, row0); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + _mm_storeu_ps(reinterpret_cast(pOutputVector), vTemp); + pOutputVector += OutputStride; + + // Result 3 + Z = XM_PERMUTE_PS(V3, _MM_SHUFFLE(2, 2, 2, 2)); + Y = XM_PERMUTE_PS(V3, _MM_SHUFFLE(1, 1, 1, 1)); + X = XM_PERMUTE_PS(V3, _MM_SHUFFLE(0, 0, 0, 0)); + + vTemp = XM_FMADD_PS(Z, row2, row3); + vTemp2 = _mm_mul_ps(Y, row1); + vTemp3 = _mm_mul_ps(X, row0); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + _mm_storeu_ps(reinterpret_cast(pOutputVector), vTemp); + pOutputVector += OutputStride; + + // Result 4 + Z = XM_PERMUTE_PS(V4, _MM_SHUFFLE(2, 2, 2, 2)); + Y = XM_PERMUTE_PS(V4, _MM_SHUFFLE(1, 1, 1, 1)); + X = XM_PERMUTE_PS(V4, _MM_SHUFFLE(0, 0, 0, 0)); + + vTemp = XM_FMADD_PS(Z, row2, row3); + vTemp2 = _mm_mul_ps(Y, row1); + vTemp3 = _mm_mul_ps(X, row0); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + _mm_storeu_ps(reinterpret_cast(pOutputVector), vTemp); + pOutputVector += OutputStride; + + i += 4; + } + } + } + } + + if (!(reinterpret_cast(pOutputStream) & 0xF) && !(OutputStride & 0xF)) + { + // Aligned output + for (; i < VectorCount; ++i) + { + XMVECTOR V = XMLoadFloat3(reinterpret_cast(pInputVector)); + pInputVector += InputStride; + + XMVECTOR Z = XM_PERMUTE_PS(V, _MM_SHUFFLE(2, 2, 2, 2)); + XMVECTOR Y = XM_PERMUTE_PS(V, _MM_SHUFFLE(1, 1, 1, 1)); + XMVECTOR X = XM_PERMUTE_PS(V, _MM_SHUFFLE(0, 0, 0, 0)); + + XMVECTOR vTemp = XM_FMADD_PS(Z, row2, row3); + XMVECTOR vTemp2 = _mm_mul_ps(Y, row1); + XMVECTOR vTemp3 = _mm_mul_ps(X, row0); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + + XM_STREAM_PS(reinterpret_cast(pOutputVector), vTemp); + pOutputVector += OutputStride; + } + } + else + { + // Unaligned output + for (; i < VectorCount; ++i) + { + XMVECTOR V = XMLoadFloat3(reinterpret_cast(pInputVector)); + pInputVector += InputStride; + + XMVECTOR Z = XM_PERMUTE_PS(V, _MM_SHUFFLE(2, 2, 2, 2)); + XMVECTOR Y = XM_PERMUTE_PS(V, _MM_SHUFFLE(1, 1, 1, 1)); + XMVECTOR X = XM_PERMUTE_PS(V, _MM_SHUFFLE(0, 0, 0, 0)); + + XMVECTOR vTemp = XM_FMADD_PS(Z, row2, row3); + XMVECTOR vTemp2 = _mm_mul_ps(Y, row1); + XMVECTOR vTemp3 = _mm_mul_ps(X, row0); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + + _mm_storeu_ps(reinterpret_cast(pOutputVector), vTemp); + pOutputVector += OutputStride; + } + } + + XM_SFENCE(); + + return pOutputStream; +#endif +} + +#ifdef _PREFAST_ +#pragma prefast(pop) +#endif + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector3TransformCoord +( + FXMVECTOR V, + FXMMATRIX M +) noexcept +{ + XMVECTOR Z = XMVectorSplatZ(V); + XMVECTOR Y = XMVectorSplatY(V); + XMVECTOR X = XMVectorSplatX(V); + + XMVECTOR Result = XMVectorMultiplyAdd(Z, M.r[2], M.r[3]); + Result = XMVectorMultiplyAdd(Y, M.r[1], Result); + Result = XMVectorMultiplyAdd(X, M.r[0], Result); + + XMVECTOR W = XMVectorSplatW(Result); + return XMVectorDivide(Result, W); +} + +//------------------------------------------------------------------------------ + +#ifdef _PREFAST_ +#pragma prefast(push) +#pragma prefast(disable : 26015 26019, "PREfast noise: Esp:1307" ) +#endif + +_Use_decl_annotations_ +inline XMFLOAT3* XM_CALLCONV XMVector3TransformCoordStream +( + XMFLOAT3* pOutputStream, + size_t OutputStride, + const XMFLOAT3* pInputStream, + size_t InputStride, + size_t VectorCount, + FXMMATRIX M +) noexcept +{ + assert(pOutputStream != nullptr); + assert(pInputStream != nullptr); + + assert(InputStride >= sizeof(XMFLOAT3)); + _Analysis_assume_(InputStride >= sizeof(XMFLOAT3)); + + assert(OutputStride >= sizeof(XMFLOAT3)); + _Analysis_assume_(OutputStride >= sizeof(XMFLOAT3)); + +#if defined(_XM_NO_INTRINSICS_) + + auto pInputVector = reinterpret_cast(pInputStream); + auto pOutputVector = reinterpret_cast(pOutputStream); + + const XMVECTOR row0 = M.r[0]; + const XMVECTOR row1 = M.r[1]; + const XMVECTOR row2 = M.r[2]; + const XMVECTOR row3 = M.r[3]; + + for (size_t i = 0; i < VectorCount; i++) + { + XMVECTOR V = XMLoadFloat3(reinterpret_cast(pInputVector)); + XMVECTOR Z = XMVectorSplatZ(V); + XMVECTOR Y = XMVectorSplatY(V); + XMVECTOR X = XMVectorSplatX(V); + + XMVECTOR Result = XMVectorMultiplyAdd(Z, row2, row3); + Result = XMVectorMultiplyAdd(Y, row1, Result); + Result = XMVectorMultiplyAdd(X, row0, Result); + + XMVECTOR W = XMVectorSplatW(Result); + + Result = XMVectorDivide(Result, W); + + XMStoreFloat3(reinterpret_cast(pOutputVector), Result); + + pInputVector += InputStride; + pOutputVector += OutputStride; + } + + return pOutputStream; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + auto pInputVector = reinterpret_cast(pInputStream); + auto pOutputVector = reinterpret_cast(pOutputStream); + + const XMVECTOR row0 = M.r[0]; + const XMVECTOR row1 = M.r[1]; + const XMVECTOR row2 = M.r[2]; + const XMVECTOR row3 = M.r[3]; + + size_t i = 0; + size_t four = VectorCount >> 2; + if (four > 0) + { + if ((InputStride == sizeof(XMFLOAT3)) && (OutputStride == sizeof(XMFLOAT3))) + { + for (size_t j = 0; j < four; ++j) + { + float32x4x3_t V = vld3q_f32(reinterpret_cast(pInputVector)); + pInputVector += sizeof(XMFLOAT3) * 4; + + float32x2_t r3 = vget_low_f32(row3); + float32x2_t r = vget_low_f32(row0); + XMVECTOR vResult0 = vmlaq_lane_f32(vdupq_lane_f32(r3, 0), V.val[0], r, 0); // Ax+M + XMVECTOR vResult1 = vmlaq_lane_f32(vdupq_lane_f32(r3, 1), V.val[0], r, 1); // Bx+N + + XM_PREFETCH(pInputVector); + + r3 = vget_high_f32(row3); + r = vget_high_f32(row0); + XMVECTOR vResult2 = vmlaq_lane_f32(vdupq_lane_f32(r3, 0), V.val[0], r, 0); // Cx+O + XMVECTOR W = vmlaq_lane_f32(vdupq_lane_f32(r3, 1), V.val[0], r, 1); // Dx+P + + XM_PREFETCH(pInputVector + XM_CACHE_LINE_SIZE); + + r = vget_low_f32(row1); + vResult0 = vmlaq_lane_f32(vResult0, V.val[1], r, 0); // Ax+Ey+M + vResult1 = vmlaq_lane_f32(vResult1, V.val[1], r, 1); // Bx+Fy+N + + XM_PREFETCH(pInputVector + (XM_CACHE_LINE_SIZE * 2)); + + r = vget_high_f32(row1); + vResult2 = vmlaq_lane_f32(vResult2, V.val[1], r, 0); // Cx+Gy+O + W = vmlaq_lane_f32(W, V.val[1], r, 1); // Dx+Hy+P + + XM_PREFETCH(pInputVector + (XM_CACHE_LINE_SIZE * 3)); + + r = vget_low_f32(row2); + vResult0 = vmlaq_lane_f32(vResult0, V.val[2], r, 0); // Ax+Ey+Iz+M + vResult1 = vmlaq_lane_f32(vResult1, V.val[2], r, 1); // Bx+Fy+Jz+N + + XM_PREFETCH(pInputVector + (XM_CACHE_LINE_SIZE * 4)); + + r = vget_high_f32(row2); + vResult2 = vmlaq_lane_f32(vResult2, V.val[2], r, 0); // Cx+Gy+Kz+O + W = vmlaq_lane_f32(W, V.val[2], r, 1); // Dx+Hy+Lz+P + + XM_PREFETCH(pInputVector + (XM_CACHE_LINE_SIZE * 5)); + + #if defined(_M_ARM64) || defined(_M_HYBRID_X86_ARM64) || defined(_M_ARM64EC) || __aarch64__ + V.val[0] = vdivq_f32(vResult0, W); + V.val[1] = vdivq_f32(vResult1, W); + V.val[2] = vdivq_f32(vResult2, W); + #else + // 2 iterations of Newton-Raphson refinement of reciprocal + float32x4_t Reciprocal = vrecpeq_f32(W); + float32x4_t S = vrecpsq_f32(Reciprocal, W); + Reciprocal = vmulq_f32(S, Reciprocal); + S = vrecpsq_f32(Reciprocal, W); + Reciprocal = vmulq_f32(S, Reciprocal); + + V.val[0] = vmulq_f32(vResult0, Reciprocal); + V.val[1] = vmulq_f32(vResult1, Reciprocal); + V.val[2] = vmulq_f32(vResult2, Reciprocal); + #endif + + vst3q_f32(reinterpret_cast(pOutputVector), V); + pOutputVector += sizeof(XMFLOAT3) * 4; + + i += 4; + } + } + } + + for (; i < VectorCount; i++) + { + float32x2_t VL = vld1_f32(reinterpret_cast(pInputVector)); + float32x2_t zero = vdup_n_f32(0); + float32x2_t VH = vld1_lane_f32(reinterpret_cast(pInputVector) + 2, zero, 0); + pInputVector += InputStride; + + XMVECTOR vResult = vmlaq_lane_f32(row3, row0, VL, 0); // X + vResult = vmlaq_lane_f32(vResult, row1, VL, 1); // Y + vResult = vmlaq_lane_f32(vResult, row2, VH, 0); // Z + + VH = vget_high_f32(vResult); + XMVECTOR W = vdupq_lane_f32(VH, 1); + + #if defined(_M_ARM64) || defined(_M_HYBRID_X86_ARM64) || defined(_M_ARM64EC) || __aarch64__ + vResult = vdivq_f32(vResult, W); + #else + // 2 iterations of Newton-Raphson refinement of reciprocal for W + float32x4_t Reciprocal = vrecpeq_f32(W); + float32x4_t S = vrecpsq_f32(Reciprocal, W); + Reciprocal = vmulq_f32(S, Reciprocal); + S = vrecpsq_f32(Reciprocal, W); + Reciprocal = vmulq_f32(S, Reciprocal); + + vResult = vmulq_f32(vResult, Reciprocal); + #endif + + VL = vget_low_f32(vResult); + vst1_f32(reinterpret_cast(pOutputVector), VL); + vst1q_lane_f32(reinterpret_cast(pOutputVector) + 2, vResult, 2); + pOutputVector += OutputStride; + } + + return pOutputStream; +#elif defined(_XM_SSE_INTRINSICS_) + auto pInputVector = reinterpret_cast(pInputStream); + auto pOutputVector = reinterpret_cast(pOutputStream); + + const XMVECTOR row0 = M.r[0]; + const XMVECTOR row1 = M.r[1]; + const XMVECTOR row2 = M.r[2]; + const XMVECTOR row3 = M.r[3]; + + size_t i = 0; + size_t four = VectorCount >> 2; + if (four > 0) + { + if (InputStride == sizeof(XMFLOAT3)) + { + if (OutputStride == sizeof(XMFLOAT3)) + { + if (!(reinterpret_cast(pOutputStream) & 0xF)) + { + // Packed input, aligned & packed output + for (size_t j = 0; j < four; ++j) + { + __m128 V1 = _mm_loadu_ps(reinterpret_cast(pInputVector)); + __m128 L2 = _mm_loadu_ps(reinterpret_cast(pInputVector + 16)); + __m128 L3 = _mm_loadu_ps(reinterpret_cast(pInputVector + 32)); + pInputVector += sizeof(XMFLOAT3) * 4; + + // Unpack the 4 vectors (.w components are junk) + XM3UNPACK3INTO4(V1, L2, L3); + + // Result 1 + XMVECTOR Z = XM_PERMUTE_PS(V1, _MM_SHUFFLE(2, 2, 2, 2)); + XMVECTOR Y = XM_PERMUTE_PS(V1, _MM_SHUFFLE(1, 1, 1, 1)); + XMVECTOR X = XM_PERMUTE_PS(V1, _MM_SHUFFLE(0, 0, 0, 0)); + + XMVECTOR vTemp = XM_FMADD_PS(Z, row2, row3); + XMVECTOR vTemp2 = _mm_mul_ps(Y, row1); + XMVECTOR vTemp3 = _mm_mul_ps(X, row0); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + + XMVECTOR W = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(3, 3, 3, 3)); + + V1 = _mm_div_ps(vTemp, W); + + // Result 2 + Z = XM_PERMUTE_PS(V2, _MM_SHUFFLE(2, 2, 2, 2)); + Y = XM_PERMUTE_PS(V2, _MM_SHUFFLE(1, 1, 1, 1)); + X = XM_PERMUTE_PS(V2, _MM_SHUFFLE(0, 0, 0, 0)); + + vTemp = XM_FMADD_PS(Z, row2, row3); + vTemp2 = _mm_mul_ps(Y, row1); + vTemp3 = _mm_mul_ps(X, row0); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + + W = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(3, 3, 3, 3)); + + V2 = _mm_div_ps(vTemp, W); + + // Result 3 + Z = XM_PERMUTE_PS(V3, _MM_SHUFFLE(2, 2, 2, 2)); + Y = XM_PERMUTE_PS(V3, _MM_SHUFFLE(1, 1, 1, 1)); + X = XM_PERMUTE_PS(V3, _MM_SHUFFLE(0, 0, 0, 0)); + + vTemp = XM_FMADD_PS(Z, row2, row3); + vTemp2 = _mm_mul_ps(Y, row1); + vTemp3 = _mm_mul_ps(X, row0); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + + W = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(3, 3, 3, 3)); + + V3 = _mm_div_ps(vTemp, W); + + // Result 4 + Z = XM_PERMUTE_PS(V4, _MM_SHUFFLE(2, 2, 2, 2)); + Y = XM_PERMUTE_PS(V4, _MM_SHUFFLE(1, 1, 1, 1)); + X = XM_PERMUTE_PS(V4, _MM_SHUFFLE(0, 0, 0, 0)); + + vTemp = XM_FMADD_PS(Z, row2, row3); + vTemp2 = _mm_mul_ps(Y, row1); + vTemp3 = _mm_mul_ps(X, row0); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + + W = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(3, 3, 3, 3)); + + V4 = _mm_div_ps(vTemp, W); + + // Pack and store the vectors + XM3PACK4INTO3(vTemp); + XM_STREAM_PS(reinterpret_cast(pOutputVector), V1); + XM_STREAM_PS(reinterpret_cast(pOutputVector + 16), vTemp); + XM_STREAM_PS(reinterpret_cast(pOutputVector + 32), V3); + pOutputVector += sizeof(XMFLOAT3) * 4; + i += 4; + } + } + else + { + // Packed input, unaligned & packed output + for (size_t j = 0; j < four; ++j) + { + __m128 V1 = _mm_loadu_ps(reinterpret_cast(pInputVector)); + __m128 L2 = _mm_loadu_ps(reinterpret_cast(pInputVector + 16)); + __m128 L3 = _mm_loadu_ps(reinterpret_cast(pInputVector + 32)); + pInputVector += sizeof(XMFLOAT3) * 4; + + // Unpack the 4 vectors (.w components are junk) + XM3UNPACK3INTO4(V1, L2, L3); + + // Result 1 + XMVECTOR Z = XM_PERMUTE_PS(V1, _MM_SHUFFLE(2, 2, 2, 2)); + XMVECTOR Y = XM_PERMUTE_PS(V1, _MM_SHUFFLE(1, 1, 1, 1)); + XMVECTOR X = XM_PERMUTE_PS(V1, _MM_SHUFFLE(0, 0, 0, 0)); + + XMVECTOR vTemp = XM_FMADD_PS(Z, row2, row3); + XMVECTOR vTemp2 = _mm_mul_ps(Y, row1); + XMVECTOR vTemp3 = _mm_mul_ps(X, row0); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + + XMVECTOR W = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(3, 3, 3, 3)); + + V1 = _mm_div_ps(vTemp, W); + + // Result 2 + Z = XM_PERMUTE_PS(V2, _MM_SHUFFLE(2, 2, 2, 2)); + Y = XM_PERMUTE_PS(V2, _MM_SHUFFLE(1, 1, 1, 1)); + X = XM_PERMUTE_PS(V2, _MM_SHUFFLE(0, 0, 0, 0)); + + vTemp = XM_FMADD_PS(Z, row2, row3); + vTemp2 = _mm_mul_ps(Y, row1); + vTemp3 = _mm_mul_ps(X, row0); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + + W = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(3, 3, 3, 3)); + + V2 = _mm_div_ps(vTemp, W); + + // Result 3 + Z = XM_PERMUTE_PS(V3, _MM_SHUFFLE(2, 2, 2, 2)); + Y = XM_PERMUTE_PS(V3, _MM_SHUFFLE(1, 1, 1, 1)); + X = XM_PERMUTE_PS(V3, _MM_SHUFFLE(0, 0, 0, 0)); + + vTemp = XM_FMADD_PS(Z, row2, row3); + vTemp2 = _mm_mul_ps(Y, row1); + vTemp3 = _mm_mul_ps(X, row0); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + + W = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(3, 3, 3, 3)); + + V3 = _mm_div_ps(vTemp, W); + + // Result 4 + Z = XM_PERMUTE_PS(V4, _MM_SHUFFLE(2, 2, 2, 2)); + Y = XM_PERMUTE_PS(V4, _MM_SHUFFLE(1, 1, 1, 1)); + X = XM_PERMUTE_PS(V4, _MM_SHUFFLE(0, 0, 0, 0)); + + vTemp = XM_FMADD_PS(Z, row2, row3); + vTemp2 = _mm_mul_ps(Y, row1); + vTemp3 = _mm_mul_ps(X, row0); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + + W = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(3, 3, 3, 3)); + + V4 = _mm_div_ps(vTemp, W); + + // Pack and store the vectors + XM3PACK4INTO3(vTemp); + _mm_storeu_ps(reinterpret_cast(pOutputVector), V1); + _mm_storeu_ps(reinterpret_cast(pOutputVector + 16), vTemp); + _mm_storeu_ps(reinterpret_cast(pOutputVector + 32), V3); + pOutputVector += sizeof(XMFLOAT3) * 4; + i += 4; + } + } + } + else + { + // Packed input, unpacked output + for (size_t j = 0; j < four; ++j) + { + __m128 V1 = _mm_loadu_ps(reinterpret_cast(pInputVector)); + __m128 L2 = _mm_loadu_ps(reinterpret_cast(pInputVector + 16)); + __m128 L3 = _mm_loadu_ps(reinterpret_cast(pInputVector + 32)); + pInputVector += sizeof(XMFLOAT3) * 4; + + // Unpack the 4 vectors (.w components are junk) + XM3UNPACK3INTO4(V1, L2, L3); + + // Result 1 + XMVECTOR Z = XM_PERMUTE_PS(V1, _MM_SHUFFLE(2, 2, 2, 2)); + XMVECTOR Y = XM_PERMUTE_PS(V1, _MM_SHUFFLE(1, 1, 1, 1)); + XMVECTOR X = XM_PERMUTE_PS(V1, _MM_SHUFFLE(0, 0, 0, 0)); + + XMVECTOR vTemp = XM_FMADD_PS(Z, row2, row3); + XMVECTOR vTemp2 = _mm_mul_ps(Y, row1); + XMVECTOR vTemp3 = _mm_mul_ps(X, row0); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + + XMVECTOR W = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(3, 3, 3, 3)); + + vTemp = _mm_div_ps(vTemp, W); + XMStoreFloat3(reinterpret_cast(pOutputVector), vTemp); + pOutputVector += OutputStride; + + // Result 2 + Z = XM_PERMUTE_PS(V2, _MM_SHUFFLE(2, 2, 2, 2)); + Y = XM_PERMUTE_PS(V2, _MM_SHUFFLE(1, 1, 1, 1)); + X = XM_PERMUTE_PS(V2, _MM_SHUFFLE(0, 0, 0, 0)); + + vTemp = XM_FMADD_PS(Z, row2, row3); + vTemp2 = _mm_mul_ps(Y, row1); + vTemp3 = _mm_mul_ps(X, row0); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + + W = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(3, 3, 3, 3)); + + vTemp = _mm_div_ps(vTemp, W); + XMStoreFloat3(reinterpret_cast(pOutputVector), vTemp); + pOutputVector += OutputStride; + + // Result 3 + Z = XM_PERMUTE_PS(V3, _MM_SHUFFLE(2, 2, 2, 2)); + Y = XM_PERMUTE_PS(V3, _MM_SHUFFLE(1, 1, 1, 1)); + X = XM_PERMUTE_PS(V3, _MM_SHUFFLE(0, 0, 0, 0)); + + vTemp = XM_FMADD_PS(Z, row2, row3); + vTemp2 = _mm_mul_ps(Y, row1); + vTemp3 = _mm_mul_ps(X, row0); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + + W = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(3, 3, 3, 3)); + + vTemp = _mm_div_ps(vTemp, W); + XMStoreFloat3(reinterpret_cast(pOutputVector), vTemp); + pOutputVector += OutputStride; + + // Result 4 + Z = XM_PERMUTE_PS(V4, _MM_SHUFFLE(2, 2, 2, 2)); + Y = XM_PERMUTE_PS(V4, _MM_SHUFFLE(1, 1, 1, 1)); + X = XM_PERMUTE_PS(V4, _MM_SHUFFLE(0, 0, 0, 0)); + + vTemp = XM_FMADD_PS(Z, row2, row3); + vTemp2 = _mm_mul_ps(Y, row1); + vTemp3 = _mm_mul_ps(X, row0); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + + W = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(3, 3, 3, 3)); + + vTemp = _mm_div_ps(vTemp, W); + XMStoreFloat3(reinterpret_cast(pOutputVector), vTemp); + pOutputVector += OutputStride; + + i += 4; + } + } + } + } + + for (; i < VectorCount; i++) + { + XMVECTOR V = XMLoadFloat3(reinterpret_cast(pInputVector)); + pInputVector += InputStride; + + XMVECTOR Z = XM_PERMUTE_PS(V, _MM_SHUFFLE(2, 2, 2, 2)); + XMVECTOR Y = XM_PERMUTE_PS(V, _MM_SHUFFLE(1, 1, 1, 1)); + XMVECTOR X = XM_PERMUTE_PS(V, _MM_SHUFFLE(0, 0, 0, 0)); + + XMVECTOR vTemp = XM_FMADD_PS(Z, row2, row3); + XMVECTOR vTemp2 = _mm_mul_ps(Y, row1); + XMVECTOR vTemp3 = _mm_mul_ps(X, row0); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + + XMVECTOR W = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(3, 3, 3, 3)); + + vTemp = _mm_div_ps(vTemp, W); + + XMStoreFloat3(reinterpret_cast(pOutputVector), vTemp); + pOutputVector += OutputStride; + } + + XM_SFENCE(); + + return pOutputStream; +#endif +} + +#ifdef _PREFAST_ +#pragma prefast(pop) +#endif + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector3TransformNormal +( + FXMVECTOR V, + FXMMATRIX M +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR Z = XMVectorSplatZ(V); + XMVECTOR Y = XMVectorSplatY(V); + XMVECTOR X = XMVectorSplatX(V); + + XMVECTOR Result = XMVectorMultiply(Z, M.r[2]); + Result = XMVectorMultiplyAdd(Y, M.r[1], Result); + Result = XMVectorMultiplyAdd(X, M.r[0], Result); + + return Result; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x2_t VL = vget_low_f32(V); + XMVECTOR vResult = vmulq_lane_f32(M.r[0], VL, 0); // X + vResult = vmlaq_lane_f32(vResult, M.r[1], VL, 1); // Y + return vmlaq_lane_f32(vResult, M.r[2], vget_high_f32(V), 0); // Z +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vResult = XM_PERMUTE_PS(V, _MM_SHUFFLE(2, 2, 2, 2)); // Z + vResult = _mm_mul_ps(vResult, M.r[2]); + XMVECTOR vTemp = XM_PERMUTE_PS(V, _MM_SHUFFLE(1, 1, 1, 1)); // Y + vResult = XM_FMADD_PS(vTemp, M.r[1], vResult); + vTemp = XM_PERMUTE_PS(V, _MM_SHUFFLE(0, 0, 0, 0)); // X + vResult = XM_FMADD_PS(vTemp, M.r[0], vResult); + return vResult; +#endif +} + +//------------------------------------------------------------------------------ + +#ifdef _PREFAST_ +#pragma prefast(push) +#pragma prefast(disable : 26015 26019, "PREfast noise: Esp:1307" ) +#endif + +_Use_decl_annotations_ +inline XMFLOAT3* XM_CALLCONV XMVector3TransformNormalStream +( + XMFLOAT3* pOutputStream, + size_t OutputStride, + const XMFLOAT3* pInputStream, + size_t InputStride, + size_t VectorCount, + FXMMATRIX M +) noexcept +{ + assert(pOutputStream != nullptr); + assert(pInputStream != nullptr); + + assert(InputStride >= sizeof(XMFLOAT3)); + _Analysis_assume_(InputStride >= sizeof(XMFLOAT3)); + + assert(OutputStride >= sizeof(XMFLOAT3)); + _Analysis_assume_(OutputStride >= sizeof(XMFLOAT3)); + +#if defined(_XM_NO_INTRINSICS_) + + auto pInputVector = reinterpret_cast(pInputStream); + auto pOutputVector = reinterpret_cast(pOutputStream); + + const XMVECTOR row0 = M.r[0]; + const XMVECTOR row1 = M.r[1]; + const XMVECTOR row2 = M.r[2]; + + for (size_t i = 0; i < VectorCount; i++) + { + XMVECTOR V = XMLoadFloat3(reinterpret_cast(pInputVector)); + XMVECTOR Z = XMVectorSplatZ(V); + XMVECTOR Y = XMVectorSplatY(V); + XMVECTOR X = XMVectorSplatX(V); + + XMVECTOR Result = XMVectorMultiply(Z, row2); + Result = XMVectorMultiplyAdd(Y, row1, Result); + Result = XMVectorMultiplyAdd(X, row0, Result); + + XMStoreFloat3(reinterpret_cast(pOutputVector), Result); + + pInputVector += InputStride; + pOutputVector += OutputStride; + } + + return pOutputStream; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + auto pInputVector = reinterpret_cast(pInputStream); + auto pOutputVector = reinterpret_cast(pOutputStream); + + const XMVECTOR row0 = M.r[0]; + const XMVECTOR row1 = M.r[1]; + const XMVECTOR row2 = M.r[2]; + + size_t i = 0; + size_t four = VectorCount >> 2; + if (four > 0) + { + if ((InputStride == sizeof(XMFLOAT3)) && (OutputStride == sizeof(XMFLOAT3))) + { + for (size_t j = 0; j < four; ++j) + { + float32x4x3_t V = vld3q_f32(reinterpret_cast(pInputVector)); + pInputVector += sizeof(XMFLOAT3) * 4; + + float32x2_t r = vget_low_f32(row0); + XMVECTOR vResult0 = vmulq_lane_f32(V.val[0], r, 0); // Ax + XMVECTOR vResult1 = vmulq_lane_f32(V.val[0], r, 1); // Bx + + XM_PREFETCH(pInputVector); + + r = vget_high_f32(row0); + XMVECTOR vResult2 = vmulq_lane_f32(V.val[0], r, 0); // Cx + + XM_PREFETCH(pInputVector + XM_CACHE_LINE_SIZE); + + r = vget_low_f32(row1); + vResult0 = vmlaq_lane_f32(vResult0, V.val[1], r, 0); // Ax+Ey + vResult1 = vmlaq_lane_f32(vResult1, V.val[1], r, 1); // Bx+Fy + + XM_PREFETCH(pInputVector + (XM_CACHE_LINE_SIZE * 2)); + + r = vget_high_f32(row1); + vResult2 = vmlaq_lane_f32(vResult2, V.val[1], r, 0); // Cx+Gy + + XM_PREFETCH(pInputVector + (XM_CACHE_LINE_SIZE * 3)); + + r = vget_low_f32(row2); + vResult0 = vmlaq_lane_f32(vResult0, V.val[2], r, 0); // Ax+Ey+Iz + vResult1 = vmlaq_lane_f32(vResult1, V.val[2], r, 1); // Bx+Fy+Jz + + XM_PREFETCH(pInputVector + (XM_CACHE_LINE_SIZE * 4)); + + r = vget_high_f32(row2); + vResult2 = vmlaq_lane_f32(vResult2, V.val[2], r, 0); // Cx+Gy+Kz + + XM_PREFETCH(pInputVector + (XM_CACHE_LINE_SIZE * 5)); + + V.val[0] = vResult0; + V.val[1] = vResult1; + V.val[2] = vResult2; + + vst3q_f32(reinterpret_cast(pOutputVector), V); + pOutputVector += sizeof(XMFLOAT3) * 4; + + i += 4; + } + } + } + + for (; i < VectorCount; i++) + { + float32x2_t VL = vld1_f32(reinterpret_cast(pInputVector)); + float32x2_t zero = vdup_n_f32(0); + float32x2_t VH = vld1_lane_f32(reinterpret_cast(pInputVector) + 2, zero, 0); + pInputVector += InputStride; + + XMVECTOR vResult = vmulq_lane_f32(row0, VL, 0); // X + vResult = vmlaq_lane_f32(vResult, row1, VL, 1); // Y + vResult = vmlaq_lane_f32(vResult, row2, VH, 0); // Z + + VL = vget_low_f32(vResult); + vst1_f32(reinterpret_cast(pOutputVector), VL); + vst1q_lane_f32(reinterpret_cast(pOutputVector) + 2, vResult, 2); + pOutputVector += OutputStride; + } + + return pOutputStream; +#elif defined(_XM_SSE_INTRINSICS_) + auto pInputVector = reinterpret_cast(pInputStream); + auto pOutputVector = reinterpret_cast(pOutputStream); + + const XMVECTOR row0 = M.r[0]; + const XMVECTOR row1 = M.r[1]; + const XMVECTOR row2 = M.r[2]; + + size_t i = 0; + size_t four = VectorCount >> 2; + if (four > 0) + { + if (InputStride == sizeof(XMFLOAT3)) + { + if (OutputStride == sizeof(XMFLOAT3)) + { + if (!(reinterpret_cast(pOutputStream) & 0xF)) + { + // Packed input, aligned & packed output + for (size_t j = 0; j < four; ++j) + { + __m128 V1 = _mm_loadu_ps(reinterpret_cast(pInputVector)); + __m128 L2 = _mm_loadu_ps(reinterpret_cast(pInputVector + 16)); + __m128 L3 = _mm_loadu_ps(reinterpret_cast(pInputVector + 32)); + pInputVector += sizeof(XMFLOAT3) * 4; + + // Unpack the 4 vectors (.w components are junk) + XM3UNPACK3INTO4(V1, L2, L3); + + // Result 1 + XMVECTOR Z = XM_PERMUTE_PS(V1, _MM_SHUFFLE(2, 2, 2, 2)); + XMVECTOR Y = XM_PERMUTE_PS(V1, _MM_SHUFFLE(1, 1, 1, 1)); + XMVECTOR X = XM_PERMUTE_PS(V1, _MM_SHUFFLE(0, 0, 0, 0)); + + XMVECTOR vTemp = _mm_mul_ps(Z, row2); + XMVECTOR vTemp2 = _mm_mul_ps(Y, row1); + XMVECTOR vTemp3 = _mm_mul_ps(X, row0); + vTemp = _mm_add_ps(vTemp, vTemp2); + V1 = _mm_add_ps(vTemp, vTemp3); + + // Result 2 + Z = XM_PERMUTE_PS(V2, _MM_SHUFFLE(2, 2, 2, 2)); + Y = XM_PERMUTE_PS(V2, _MM_SHUFFLE(1, 1, 1, 1)); + X = XM_PERMUTE_PS(V2, _MM_SHUFFLE(0, 0, 0, 0)); + + vTemp = _mm_mul_ps(Z, row2); + vTemp2 = _mm_mul_ps(Y, row1); + vTemp3 = _mm_mul_ps(X, row0); + vTemp = _mm_add_ps(vTemp, vTemp2); + V2 = _mm_add_ps(vTemp, vTemp3); + + // Result 3 + Z = XM_PERMUTE_PS(V3, _MM_SHUFFLE(2, 2, 2, 2)); + Y = XM_PERMUTE_PS(V3, _MM_SHUFFLE(1, 1, 1, 1)); + X = XM_PERMUTE_PS(V3, _MM_SHUFFLE(0, 0, 0, 0)); + + vTemp = _mm_mul_ps(Z, row2); + vTemp2 = _mm_mul_ps(Y, row1); + vTemp3 = _mm_mul_ps(X, row0); + vTemp = _mm_add_ps(vTemp, vTemp2); + V3 = _mm_add_ps(vTemp, vTemp3); + + // Result 4 + Z = XM_PERMUTE_PS(V4, _MM_SHUFFLE(2, 2, 2, 2)); + Y = XM_PERMUTE_PS(V4, _MM_SHUFFLE(1, 1, 1, 1)); + X = XM_PERMUTE_PS(V4, _MM_SHUFFLE(0, 0, 0, 0)); + + vTemp = _mm_mul_ps(Z, row2); + vTemp2 = _mm_mul_ps(Y, row1); + vTemp3 = _mm_mul_ps(X, row0); + vTemp = _mm_add_ps(vTemp, vTemp2); + V4 = _mm_add_ps(vTemp, vTemp3); + + // Pack and store the vectors + XM3PACK4INTO3(vTemp); + XM_STREAM_PS(reinterpret_cast(pOutputVector), V1); + XM_STREAM_PS(reinterpret_cast(pOutputVector + 16), vTemp); + XM_STREAM_PS(reinterpret_cast(pOutputVector + 32), V3); + pOutputVector += sizeof(XMFLOAT3) * 4; + i += 4; + } + } + else + { + // Packed input, unaligned & packed output + for (size_t j = 0; j < four; ++j) + { + __m128 V1 = _mm_loadu_ps(reinterpret_cast(pInputVector)); + __m128 L2 = _mm_loadu_ps(reinterpret_cast(pInputVector + 16)); + __m128 L3 = _mm_loadu_ps(reinterpret_cast(pInputVector + 32)); + pInputVector += sizeof(XMFLOAT3) * 4; + + // Unpack the 4 vectors (.w components are junk) + XM3UNPACK3INTO4(V1, L2, L3); + + // Result 1 + XMVECTOR Z = XM_PERMUTE_PS(V1, _MM_SHUFFLE(2, 2, 2, 2)); + XMVECTOR Y = XM_PERMUTE_PS(V1, _MM_SHUFFLE(1, 1, 1, 1)); + XMVECTOR X = XM_PERMUTE_PS(V1, _MM_SHUFFLE(0, 0, 0, 0)); + + XMVECTOR vTemp = _mm_mul_ps(Z, row2); + XMVECTOR vTemp2 = _mm_mul_ps(Y, row1); + XMVECTOR vTemp3 = _mm_mul_ps(X, row0); + vTemp = _mm_add_ps(vTemp, vTemp2); + V1 = _mm_add_ps(vTemp, vTemp3); + + // Result 2 + Z = XM_PERMUTE_PS(V2, _MM_SHUFFLE(2, 2, 2, 2)); + Y = XM_PERMUTE_PS(V2, _MM_SHUFFLE(1, 1, 1, 1)); + X = XM_PERMUTE_PS(V2, _MM_SHUFFLE(0, 0, 0, 0)); + + vTemp = _mm_mul_ps(Z, row2); + vTemp2 = _mm_mul_ps(Y, row1); + vTemp3 = _mm_mul_ps(X, row0); + vTemp = _mm_add_ps(vTemp, vTemp2); + V2 = _mm_add_ps(vTemp, vTemp3); + + // Result 3 + Z = XM_PERMUTE_PS(V3, _MM_SHUFFLE(2, 2, 2, 2)); + Y = XM_PERMUTE_PS(V3, _MM_SHUFFLE(1, 1, 1, 1)); + X = XM_PERMUTE_PS(V3, _MM_SHUFFLE(0, 0, 0, 0)); + + vTemp = _mm_mul_ps(Z, row2); + vTemp2 = _mm_mul_ps(Y, row1); + vTemp3 = _mm_mul_ps(X, row0); + vTemp = _mm_add_ps(vTemp, vTemp2); + V3 = _mm_add_ps(vTemp, vTemp3); + + // Result 4 + Z = XM_PERMUTE_PS(V4, _MM_SHUFFLE(2, 2, 2, 2)); + Y = XM_PERMUTE_PS(V4, _MM_SHUFFLE(1, 1, 1, 1)); + X = XM_PERMUTE_PS(V4, _MM_SHUFFLE(0, 0, 0, 0)); + + vTemp = _mm_mul_ps(Z, row2); + vTemp2 = _mm_mul_ps(Y, row1); + vTemp3 = _mm_mul_ps(X, row0); + vTemp = _mm_add_ps(vTemp, vTemp2); + V4 = _mm_add_ps(vTemp, vTemp3); + + // Pack and store the vectors + XM3PACK4INTO3(vTemp); + _mm_storeu_ps(reinterpret_cast(pOutputVector), V1); + _mm_storeu_ps(reinterpret_cast(pOutputVector + 16), vTemp); + _mm_storeu_ps(reinterpret_cast(pOutputVector + 32), V3); + pOutputVector += sizeof(XMFLOAT3) * 4; + i += 4; + } + } + } + else + { + // Packed input, unpacked output + for (size_t j = 0; j < four; ++j) + { + __m128 V1 = _mm_loadu_ps(reinterpret_cast(pInputVector)); + __m128 L2 = _mm_loadu_ps(reinterpret_cast(pInputVector + 16)); + __m128 L3 = _mm_loadu_ps(reinterpret_cast(pInputVector + 32)); + pInputVector += sizeof(XMFLOAT3) * 4; + + // Unpack the 4 vectors (.w components are junk) + XM3UNPACK3INTO4(V1, L2, L3); + + // Result 1 + XMVECTOR Z = XM_PERMUTE_PS(V1, _MM_SHUFFLE(2, 2, 2, 2)); + XMVECTOR Y = XM_PERMUTE_PS(V1, _MM_SHUFFLE(1, 1, 1, 1)); + XMVECTOR X = XM_PERMUTE_PS(V1, _MM_SHUFFLE(0, 0, 0, 0)); + + XMVECTOR vTemp = _mm_mul_ps(Z, row2); + XMVECTOR vTemp2 = _mm_mul_ps(Y, row1); + XMVECTOR vTemp3 = _mm_mul_ps(X, row0); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + + XMStoreFloat3(reinterpret_cast(pOutputVector), vTemp); + pOutputVector += OutputStride; + + // Result 2 + Z = XM_PERMUTE_PS(V2, _MM_SHUFFLE(2, 2, 2, 2)); + Y = XM_PERMUTE_PS(V2, _MM_SHUFFLE(1, 1, 1, 1)); + X = XM_PERMUTE_PS(V2, _MM_SHUFFLE(0, 0, 0, 0)); + + vTemp = _mm_mul_ps(Z, row2); + vTemp2 = _mm_mul_ps(Y, row1); + vTemp3 = _mm_mul_ps(X, row0); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + + XMStoreFloat3(reinterpret_cast(pOutputVector), vTemp); + pOutputVector += OutputStride; + + // Result 3 + Z = XM_PERMUTE_PS(V3, _MM_SHUFFLE(2, 2, 2, 2)); + Y = XM_PERMUTE_PS(V3, _MM_SHUFFLE(1, 1, 1, 1)); + X = XM_PERMUTE_PS(V3, _MM_SHUFFLE(0, 0, 0, 0)); + + vTemp = _mm_mul_ps(Z, row2); + vTemp2 = _mm_mul_ps(Y, row1); + vTemp3 = _mm_mul_ps(X, row0); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + + XMStoreFloat3(reinterpret_cast(pOutputVector), vTemp); + pOutputVector += OutputStride; + + // Result 4 + Z = XM_PERMUTE_PS(V4, _MM_SHUFFLE(2, 2, 2, 2)); + Y = XM_PERMUTE_PS(V4, _MM_SHUFFLE(1, 1, 1, 1)); + X = XM_PERMUTE_PS(V4, _MM_SHUFFLE(0, 0, 0, 0)); + + vTemp = _mm_mul_ps(Z, row2); + vTemp2 = _mm_mul_ps(Y, row1); + vTemp3 = _mm_mul_ps(X, row0); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + + XMStoreFloat3(reinterpret_cast(pOutputVector), vTemp); + pOutputVector += OutputStride; + + i += 4; + } + } + } + } + + for (; i < VectorCount; i++) + { + XMVECTOR V = XMLoadFloat3(reinterpret_cast(pInputVector)); + pInputVector += InputStride; + + XMVECTOR Z = XM_PERMUTE_PS(V, _MM_SHUFFLE(2, 2, 2, 2)); + XMVECTOR Y = XM_PERMUTE_PS(V, _MM_SHUFFLE(1, 1, 1, 1)); + XMVECTOR X = XM_PERMUTE_PS(V, _MM_SHUFFLE(0, 0, 0, 0)); + + XMVECTOR vTemp = _mm_mul_ps(Z, row2); + XMVECTOR vTemp2 = _mm_mul_ps(Y, row1); + XMVECTOR vTemp3 = _mm_mul_ps(X, row0); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + + XMStoreFloat3(reinterpret_cast(pOutputVector), vTemp); + pOutputVector += OutputStride; + } + + XM_SFENCE(); + + return pOutputStream; +#endif +} + +#ifdef _PREFAST_ +#pragma prefast(pop) +#endif + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector3Project +( + FXMVECTOR V, + float ViewportX, + float ViewportY, + float ViewportWidth, + float ViewportHeight, + float ViewportMinZ, + float ViewportMaxZ, + FXMMATRIX Projection, + CXMMATRIX View, + CXMMATRIX World +) noexcept +{ + const float HalfViewportWidth = ViewportWidth * 0.5f; + const float HalfViewportHeight = ViewportHeight * 0.5f; + + XMVECTOR Scale = XMVectorSet(HalfViewportWidth, -HalfViewportHeight, ViewportMaxZ - ViewportMinZ, 0.0f); + XMVECTOR Offset = XMVectorSet(ViewportX + HalfViewportWidth, ViewportY + HalfViewportHeight, ViewportMinZ, 0.0f); + + XMMATRIX Transform = XMMatrixMultiply(World, View); + Transform = XMMatrixMultiply(Transform, Projection); + + XMVECTOR Result = XMVector3TransformCoord(V, Transform); + + Result = XMVectorMultiplyAdd(Result, Scale, Offset); + + return Result; +} + +//------------------------------------------------------------------------------ + +#ifdef _PREFAST_ +#pragma prefast(push) +#pragma prefast(disable : 26015 26019, "PREfast noise: Esp:1307" ) +#endif + +_Use_decl_annotations_ +inline XMFLOAT3* XM_CALLCONV XMVector3ProjectStream +( + XMFLOAT3* pOutputStream, + size_t OutputStride, + const XMFLOAT3* pInputStream, + size_t InputStride, + size_t VectorCount, + float ViewportX, + float ViewportY, + float ViewportWidth, + float ViewportHeight, + float ViewportMinZ, + float ViewportMaxZ, + FXMMATRIX Projection, + CXMMATRIX View, + CXMMATRIX World +) noexcept +{ + assert(pOutputStream != nullptr); + assert(pInputStream != nullptr); + + assert(InputStride >= sizeof(XMFLOAT3)); + _Analysis_assume_(InputStride >= sizeof(XMFLOAT3)); + + assert(OutputStride >= sizeof(XMFLOAT3)); + _Analysis_assume_(OutputStride >= sizeof(XMFLOAT3)); + +#if defined(_XM_NO_INTRINSICS_) + + const float HalfViewportWidth = ViewportWidth * 0.5f; + const float HalfViewportHeight = ViewportHeight * 0.5f; + + XMVECTOR Scale = XMVectorSet(HalfViewportWidth, -HalfViewportHeight, ViewportMaxZ - ViewportMinZ, 1.0f); + XMVECTOR Offset = XMVectorSet(ViewportX + HalfViewportWidth, ViewportY + HalfViewportHeight, ViewportMinZ, 0.0f); + + XMMATRIX Transform = XMMatrixMultiply(World, View); + Transform = XMMatrixMultiply(Transform, Projection); + + auto pInputVector = reinterpret_cast(pInputStream); + auto pOutputVector = reinterpret_cast(pOutputStream); + + for (size_t i = 0; i < VectorCount; i++) + { + XMVECTOR V = XMLoadFloat3(reinterpret_cast(pInputVector)); + + XMVECTOR Result = XMVector3TransformCoord(V, Transform); + Result = XMVectorMultiplyAdd(Result, Scale, Offset); + + XMStoreFloat3(reinterpret_cast(pOutputVector), Result); + + pInputVector += InputStride; + pOutputVector += OutputStride; + } + + return pOutputStream; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + const float HalfViewportWidth = ViewportWidth * 0.5f; + const float HalfViewportHeight = ViewportHeight * 0.5f; + + XMMATRIX Transform = XMMatrixMultiply(World, View); + Transform = XMMatrixMultiply(Transform, Projection); + + auto pInputVector = reinterpret_cast(pInputStream); + auto pOutputVector = reinterpret_cast(pOutputStream); + + size_t i = 0; + size_t four = VectorCount >> 2; + if (four > 0) + { + if ((InputStride == sizeof(XMFLOAT3)) && (OutputStride == sizeof(XMFLOAT3))) + { + XMVECTOR ScaleX = vdupq_n_f32(HalfViewportWidth); + XMVECTOR ScaleY = vdupq_n_f32(-HalfViewportHeight); + XMVECTOR ScaleZ = vdupq_n_f32(ViewportMaxZ - ViewportMinZ); + + XMVECTOR OffsetX = vdupq_n_f32(ViewportX + HalfViewportWidth); + XMVECTOR OffsetY = vdupq_n_f32(ViewportY + HalfViewportHeight); + XMVECTOR OffsetZ = vdupq_n_f32(ViewportMinZ); + + for (size_t j = 0; j < four; ++j) + { + float32x4x3_t V = vld3q_f32(reinterpret_cast(pInputVector)); + pInputVector += sizeof(XMFLOAT3) * 4; + + float32x2_t r3 = vget_low_f32(Transform.r[3]); + float32x2_t r = vget_low_f32(Transform.r[0]); + XMVECTOR vResult0 = vmlaq_lane_f32(vdupq_lane_f32(r3, 0), V.val[0], r, 0); // Ax+M + XMVECTOR vResult1 = vmlaq_lane_f32(vdupq_lane_f32(r3, 1), V.val[0], r, 1); // Bx+N + + XM_PREFETCH(pInputVector); + + r3 = vget_high_f32(Transform.r[3]); + r = vget_high_f32(Transform.r[0]); + XMVECTOR vResult2 = vmlaq_lane_f32(vdupq_lane_f32(r3, 0), V.val[0], r, 0); // Cx+O + XMVECTOR W = vmlaq_lane_f32(vdupq_lane_f32(r3, 1), V.val[0], r, 1); // Dx+P + + XM_PREFETCH(pInputVector + XM_CACHE_LINE_SIZE); + + r = vget_low_f32(Transform.r[1]); + vResult0 = vmlaq_lane_f32(vResult0, V.val[1], r, 0); // Ax+Ey+M + vResult1 = vmlaq_lane_f32(vResult1, V.val[1], r, 1); // Bx+Fy+N + + XM_PREFETCH(pInputVector + (XM_CACHE_LINE_SIZE * 2)); + + r = vget_high_f32(Transform.r[1]); + vResult2 = vmlaq_lane_f32(vResult2, V.val[1], r, 0); // Cx+Gy+O + W = vmlaq_lane_f32(W, V.val[1], r, 1); // Dx+Hy+P + + XM_PREFETCH(pInputVector + (XM_CACHE_LINE_SIZE * 3)); + + r = vget_low_f32(Transform.r[2]); + vResult0 = vmlaq_lane_f32(vResult0, V.val[2], r, 0); // Ax+Ey+Iz+M + vResult1 = vmlaq_lane_f32(vResult1, V.val[2], r, 1); // Bx+Fy+Jz+N + + XM_PREFETCH(pInputVector + (XM_CACHE_LINE_SIZE * 4)); + + r = vget_high_f32(Transform.r[2]); + vResult2 = vmlaq_lane_f32(vResult2, V.val[2], r, 0); // Cx+Gy+Kz+O + W = vmlaq_lane_f32(W, V.val[2], r, 1); // Dx+Hy+Lz+P + + XM_PREFETCH(pInputVector + (XM_CACHE_LINE_SIZE * 5)); + + #if defined(_M_ARM64) || defined(_M_HYBRID_X86_ARM64) || defined(_M_ARM64EC) || __aarch64__ + vResult0 = vdivq_f32(vResult0, W); + vResult1 = vdivq_f32(vResult1, W); + vResult2 = vdivq_f32(vResult2, W); + #else + // 2 iterations of Newton-Raphson refinement of reciprocal + float32x4_t Reciprocal = vrecpeq_f32(W); + float32x4_t S = vrecpsq_f32(Reciprocal, W); + Reciprocal = vmulq_f32(S, Reciprocal); + S = vrecpsq_f32(Reciprocal, W); + Reciprocal = vmulq_f32(S, Reciprocal); + + vResult0 = vmulq_f32(vResult0, Reciprocal); + vResult1 = vmulq_f32(vResult1, Reciprocal); + vResult2 = vmulq_f32(vResult2, Reciprocal); + #endif + + V.val[0] = vmlaq_f32(OffsetX, vResult0, ScaleX); + V.val[1] = vmlaq_f32(OffsetY, vResult1, ScaleY); + V.val[2] = vmlaq_f32(OffsetZ, vResult2, ScaleZ); + + vst3q_f32(reinterpret_cast(pOutputVector), V); + pOutputVector += sizeof(XMFLOAT3) * 4; + + i += 4; + } + } + } + + if (i < VectorCount) + { + XMVECTOR Scale = XMVectorSet(HalfViewportWidth, -HalfViewportHeight, ViewportMaxZ - ViewportMinZ, 1.0f); + XMVECTOR Offset = XMVectorSet(ViewportX + HalfViewportWidth, ViewportY + HalfViewportHeight, ViewportMinZ, 0.0f); + + for (; i < VectorCount; i++) + { + float32x2_t VL = vld1_f32(reinterpret_cast(pInputVector)); + float32x2_t zero = vdup_n_f32(0); + float32x2_t VH = vld1_lane_f32(reinterpret_cast(pInputVector) + 2, zero, 0); + pInputVector += InputStride; + + XMVECTOR vResult = vmlaq_lane_f32(Transform.r[3], Transform.r[0], VL, 0); // X + vResult = vmlaq_lane_f32(vResult, Transform.r[1], VL, 1); // Y + vResult = vmlaq_lane_f32(vResult, Transform.r[2], VH, 0); // Z + + VH = vget_high_f32(vResult); + XMVECTOR W = vdupq_lane_f32(VH, 1); + + #if defined(_M_ARM64) || defined(_M_HYBRID_X86_ARM64) || defined(_M_ARM64EC) || __aarch64__ + vResult = vdivq_f32(vResult, W); + #else + // 2 iterations of Newton-Raphson refinement of reciprocal for W + float32x4_t Reciprocal = vrecpeq_f32(W); + float32x4_t S = vrecpsq_f32(Reciprocal, W); + Reciprocal = vmulq_f32(S, Reciprocal); + S = vrecpsq_f32(Reciprocal, W); + Reciprocal = vmulq_f32(S, Reciprocal); + + vResult = vmulq_f32(vResult, Reciprocal); + #endif + + vResult = vmlaq_f32(Offset, vResult, Scale); + + VL = vget_low_f32(vResult); + vst1_f32(reinterpret_cast(pOutputVector), VL); + vst1q_lane_f32(reinterpret_cast(pOutputVector) + 2, vResult, 2); + pOutputVector += OutputStride; + } + } + + return pOutputStream; +#elif defined(_XM_SSE_INTRINSICS_) + const float HalfViewportWidth = ViewportWidth * 0.5f; + const float HalfViewportHeight = ViewportHeight * 0.5f; + + XMVECTOR Scale = XMVectorSet(HalfViewportWidth, -HalfViewportHeight, ViewportMaxZ - ViewportMinZ, 1.0f); + XMVECTOR Offset = XMVectorSet(ViewportX + HalfViewportWidth, ViewportY + HalfViewportHeight, ViewportMinZ, 0.0f); + + XMMATRIX Transform = XMMatrixMultiply(World, View); + Transform = XMMatrixMultiply(Transform, Projection); + + auto pInputVector = reinterpret_cast(pInputStream); + auto pOutputVector = reinterpret_cast(pOutputStream); + + size_t i = 0; + size_t four = VectorCount >> 2; + if (four > 0) + { + if (InputStride == sizeof(XMFLOAT3)) + { + if (OutputStride == sizeof(XMFLOAT3)) + { + if (!(reinterpret_cast(pOutputStream) & 0xF)) + { + // Packed input, aligned & packed output + for (size_t j = 0; j < four; ++j) + { + __m128 V1 = _mm_loadu_ps(reinterpret_cast(pInputVector)); + __m128 L2 = _mm_loadu_ps(reinterpret_cast(pInputVector + 16)); + __m128 L3 = _mm_loadu_ps(reinterpret_cast(pInputVector + 32)); + pInputVector += sizeof(XMFLOAT3) * 4; + + // Unpack the 4 vectors (.w components are junk) + XM3UNPACK3INTO4(V1, L2, L3); + + // Result 1 + XMVECTOR Z = XM_PERMUTE_PS(V1, _MM_SHUFFLE(2, 2, 2, 2)); + XMVECTOR Y = XM_PERMUTE_PS(V1, _MM_SHUFFLE(1, 1, 1, 1)); + XMVECTOR X = XM_PERMUTE_PS(V1, _MM_SHUFFLE(0, 0, 0, 0)); + + XMVECTOR vTemp = XM_FMADD_PS(Z, Transform.r[2], Transform.r[3]); + XMVECTOR vTemp2 = _mm_mul_ps(Y, Transform.r[1]); + XMVECTOR vTemp3 = _mm_mul_ps(X, Transform.r[0]); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + + XMVECTOR W = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(3, 3, 3, 3)); + vTemp = _mm_div_ps(vTemp, W); + V1 = XM_FMADD_PS(vTemp, Scale, Offset); + + // Result 2 + Z = XM_PERMUTE_PS(V2, _MM_SHUFFLE(2, 2, 2, 2)); + Y = XM_PERMUTE_PS(V2, _MM_SHUFFLE(1, 1, 1, 1)); + X = XM_PERMUTE_PS(V2, _MM_SHUFFLE(0, 0, 0, 0)); + + vTemp = XM_FMADD_PS(Z, Transform.r[2], Transform.r[3]); + vTemp2 = _mm_mul_ps(Y, Transform.r[1]); + vTemp3 = _mm_mul_ps(X, Transform.r[0]); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + + W = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(3, 3, 3, 3)); + vTemp = _mm_div_ps(vTemp, W); + V2 = XM_FMADD_PS(vTemp, Scale, Offset); + + // Result 3 + Z = XM_PERMUTE_PS(V3, _MM_SHUFFLE(2, 2, 2, 2)); + Y = XM_PERMUTE_PS(V3, _MM_SHUFFLE(1, 1, 1, 1)); + X = XM_PERMUTE_PS(V3, _MM_SHUFFLE(0, 0, 0, 0)); + + vTemp = XM_FMADD_PS(Z, Transform.r[2], Transform.r[3]); + vTemp2 = _mm_mul_ps(Y, Transform.r[1]); + vTemp3 = _mm_mul_ps(X, Transform.r[0]); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + + W = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(3, 3, 3, 3)); + vTemp = _mm_div_ps(vTemp, W); + V3 = XM_FMADD_PS(vTemp, Scale, Offset); + + // Result 4 + Z = XM_PERMUTE_PS(V4, _MM_SHUFFLE(2, 2, 2, 2)); + Y = XM_PERMUTE_PS(V4, _MM_SHUFFLE(1, 1, 1, 1)); + X = XM_PERMUTE_PS(V4, _MM_SHUFFLE(0, 0, 0, 0)); + + vTemp = XM_FMADD_PS(Z, Transform.r[2], Transform.r[3]); + vTemp2 = _mm_mul_ps(Y, Transform.r[1]); + vTemp3 = _mm_mul_ps(X, Transform.r[0]); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + + W = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(3, 3, 3, 3)); + vTemp = _mm_div_ps(vTemp, W); + V4 = XM_FMADD_PS(vTemp, Scale, Offset); + + // Pack and store the vectors + XM3PACK4INTO3(vTemp); + XM_STREAM_PS(reinterpret_cast(pOutputVector), V1); + XM_STREAM_PS(reinterpret_cast(pOutputVector + 16), vTemp); + XM_STREAM_PS(reinterpret_cast(pOutputVector + 32), V3); + pOutputVector += sizeof(XMFLOAT3) * 4; + i += 4; + } + } + else + { + // Packed input, unaligned & packed output + for (size_t j = 0; j < four; ++j) + { + __m128 V1 = _mm_loadu_ps(reinterpret_cast(pInputVector)); + __m128 L2 = _mm_loadu_ps(reinterpret_cast(pInputVector + 16)); + __m128 L3 = _mm_loadu_ps(reinterpret_cast(pInputVector + 32)); + pInputVector += sizeof(XMFLOAT3) * 4; + + // Unpack the 4 vectors (.w components are junk) + XM3UNPACK3INTO4(V1, L2, L3); + + // Result 1 + XMVECTOR Z = XM_PERMUTE_PS(V1, _MM_SHUFFLE(2, 2, 2, 2)); + XMVECTOR Y = XM_PERMUTE_PS(V1, _MM_SHUFFLE(1, 1, 1, 1)); + XMVECTOR X = XM_PERMUTE_PS(V1, _MM_SHUFFLE(0, 0, 0, 0)); + + XMVECTOR vTemp = XM_FMADD_PS(Z, Transform.r[2], Transform.r[3]); + XMVECTOR vTemp2 = _mm_mul_ps(Y, Transform.r[1]); + XMVECTOR vTemp3 = _mm_mul_ps(X, Transform.r[0]); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + + XMVECTOR W = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(3, 3, 3, 3)); + vTemp = _mm_div_ps(vTemp, W); + V1 = XM_FMADD_PS(vTemp, Scale, Offset); + + // Result 2 + Z = XM_PERMUTE_PS(V2, _MM_SHUFFLE(2, 2, 2, 2)); + Y = XM_PERMUTE_PS(V2, _MM_SHUFFLE(1, 1, 1, 1)); + X = XM_PERMUTE_PS(V2, _MM_SHUFFLE(0, 0, 0, 0)); + + vTemp = XM_FMADD_PS(Z, Transform.r[2], Transform.r[3]); + vTemp2 = _mm_mul_ps(Y, Transform.r[1]); + vTemp3 = _mm_mul_ps(X, Transform.r[0]); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + + W = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(3, 3, 3, 3)); + vTemp = _mm_div_ps(vTemp, W); + V2 = XM_FMADD_PS(vTemp, Scale, Offset); + + // Result 3 + Z = XM_PERMUTE_PS(V3, _MM_SHUFFLE(2, 2, 2, 2)); + Y = XM_PERMUTE_PS(V3, _MM_SHUFFLE(1, 1, 1, 1)); + X = XM_PERMUTE_PS(V3, _MM_SHUFFLE(0, 0, 0, 0)); + + vTemp = XM_FMADD_PS(Z, Transform.r[2], Transform.r[3]); + vTemp2 = _mm_mul_ps(Y, Transform.r[1]); + vTemp3 = _mm_mul_ps(X, Transform.r[0]); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + + W = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(3, 3, 3, 3)); + vTemp = _mm_div_ps(vTemp, W); + V3 = XM_FMADD_PS(vTemp, Scale, Offset); + + // Result 4 + Z = XM_PERMUTE_PS(V4, _MM_SHUFFLE(2, 2, 2, 2)); + Y = XM_PERMUTE_PS(V4, _MM_SHUFFLE(1, 1, 1, 1)); + X = XM_PERMUTE_PS(V4, _MM_SHUFFLE(0, 0, 0, 0)); + + vTemp = XM_FMADD_PS(Z, Transform.r[2], Transform.r[3]); + vTemp2 = _mm_mul_ps(Y, Transform.r[1]); + vTemp3 = _mm_mul_ps(X, Transform.r[0]); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + + W = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(3, 3, 3, 3)); + vTemp = _mm_div_ps(vTemp, W); + V4 = XM_FMADD_PS(vTemp, Scale, Offset); + + // Pack and store the vectors + XM3PACK4INTO3(vTemp); + _mm_storeu_ps(reinterpret_cast(pOutputVector), V1); + _mm_storeu_ps(reinterpret_cast(pOutputVector + 16), vTemp); + _mm_storeu_ps(reinterpret_cast(pOutputVector + 32), V3); + pOutputVector += sizeof(XMFLOAT3) * 4; + i += 4; + } + } + } + else + { + // Packed input, unpacked output + for (size_t j = 0; j < four; ++j) + { + __m128 V1 = _mm_loadu_ps(reinterpret_cast(pInputVector)); + __m128 L2 = _mm_loadu_ps(reinterpret_cast(pInputVector + 16)); + __m128 L3 = _mm_loadu_ps(reinterpret_cast(pInputVector + 32)); + pInputVector += sizeof(XMFLOAT3) * 4; + + // Unpack the 4 vectors (.w components are junk) + XM3UNPACK3INTO4(V1, L2, L3); + + // Result 1 + XMVECTOR Z = XM_PERMUTE_PS(V1, _MM_SHUFFLE(2, 2, 2, 2)); + XMVECTOR Y = XM_PERMUTE_PS(V1, _MM_SHUFFLE(1, 1, 1, 1)); + XMVECTOR X = XM_PERMUTE_PS(V1, _MM_SHUFFLE(0, 0, 0, 0)); + + XMVECTOR vTemp = XM_FMADD_PS(Z, Transform.r[2], Transform.r[3]); + XMVECTOR vTemp2 = _mm_mul_ps(Y, Transform.r[1]); + XMVECTOR vTemp3 = _mm_mul_ps(X, Transform.r[0]); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + + XMVECTOR W = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(3, 3, 3, 3)); + vTemp = _mm_div_ps(vTemp, W); + vTemp = XM_FMADD_PS(vTemp, Scale, Offset); + + XMStoreFloat3(reinterpret_cast(pOutputVector), vTemp); + pOutputVector += OutputStride; + + // Result 2 + Z = XM_PERMUTE_PS(V2, _MM_SHUFFLE(2, 2, 2, 2)); + Y = XM_PERMUTE_PS(V2, _MM_SHUFFLE(1, 1, 1, 1)); + X = XM_PERMUTE_PS(V2, _MM_SHUFFLE(0, 0, 0, 0)); + + vTemp = XM_FMADD_PS(Z, Transform.r[2], Transform.r[3]); + vTemp2 = _mm_mul_ps(Y, Transform.r[1]); + vTemp3 = _mm_mul_ps(X, Transform.r[0]); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + + W = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(3, 3, 3, 3)); + vTemp = _mm_div_ps(vTemp, W); + vTemp = XM_FMADD_PS(vTemp, Scale, Offset); + + XMStoreFloat3(reinterpret_cast(pOutputVector), vTemp); + pOutputVector += OutputStride; + + // Result 3 + Z = XM_PERMUTE_PS(V3, _MM_SHUFFLE(2, 2, 2, 2)); + Y = XM_PERMUTE_PS(V3, _MM_SHUFFLE(1, 1, 1, 1)); + X = XM_PERMUTE_PS(V3, _MM_SHUFFLE(0, 0, 0, 0)); + + vTemp = XM_FMADD_PS(Z, Transform.r[2], Transform.r[3]); + vTemp2 = _mm_mul_ps(Y, Transform.r[1]); + vTemp3 = _mm_mul_ps(X, Transform.r[0]); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + + W = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(3, 3, 3, 3)); + vTemp = _mm_div_ps(vTemp, W); + vTemp = XM_FMADD_PS(vTemp, Scale, Offset); + + XMStoreFloat3(reinterpret_cast(pOutputVector), vTemp); + pOutputVector += OutputStride; + + // Result 4 + Z = XM_PERMUTE_PS(V4, _MM_SHUFFLE(2, 2, 2, 2)); + Y = XM_PERMUTE_PS(V4, _MM_SHUFFLE(1, 1, 1, 1)); + X = XM_PERMUTE_PS(V4, _MM_SHUFFLE(0, 0, 0, 0)); + + vTemp = XM_FMADD_PS(Z, Transform.r[2], Transform.r[3]); + vTemp2 = _mm_mul_ps(Y, Transform.r[1]); + vTemp3 = _mm_mul_ps(X, Transform.r[0]); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + + W = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(3, 3, 3, 3)); + vTemp = _mm_div_ps(vTemp, W); + vTemp = XM_FMADD_PS(vTemp, Scale, Offset); + + XMStoreFloat3(reinterpret_cast(pOutputVector), vTemp); + pOutputVector += OutputStride; + + i += 4; + } + } + } + } + + for (; i < VectorCount; i++) + { + XMVECTOR V = XMLoadFloat3(reinterpret_cast(pInputVector)); + pInputVector += InputStride; + + XMVECTOR Z = XM_PERMUTE_PS(V, _MM_SHUFFLE(2, 2, 2, 2)); + XMVECTOR Y = XM_PERMUTE_PS(V, _MM_SHUFFLE(1, 1, 1, 1)); + XMVECTOR X = XM_PERMUTE_PS(V, _MM_SHUFFLE(0, 0, 0, 0)); + + XMVECTOR vTemp = XM_FMADD_PS(Z, Transform.r[2], Transform.r[3]); + XMVECTOR vTemp2 = _mm_mul_ps(Y, Transform.r[1]); + XMVECTOR vTemp3 = _mm_mul_ps(X, Transform.r[0]); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + + XMVECTOR W = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(3, 3, 3, 3)); + vTemp = _mm_div_ps(vTemp, W); + vTemp = XM_FMADD_PS(vTemp, Scale, Offset); + + XMStoreFloat3(reinterpret_cast(pOutputVector), vTemp); + pOutputVector += OutputStride; + } + + XM_SFENCE(); + + return pOutputStream; +#endif +} + +#ifdef _PREFAST_ +#pragma prefast(pop) +#endif + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector3Unproject +( + FXMVECTOR V, + float ViewportX, + float ViewportY, + float ViewportWidth, + float ViewportHeight, + float ViewportMinZ, + float ViewportMaxZ, + FXMMATRIX Projection, + CXMMATRIX View, + CXMMATRIX World +) noexcept +{ + static const XMVECTORF32 D = { { { -1.0f, 1.0f, 0.0f, 0.0f } } }; + + XMVECTOR Scale = XMVectorSet(ViewportWidth * 0.5f, -ViewportHeight * 0.5f, ViewportMaxZ - ViewportMinZ, 1.0f); + Scale = XMVectorReciprocal(Scale); + + XMVECTOR Offset = XMVectorSet(-ViewportX, -ViewportY, -ViewportMinZ, 0.0f); + Offset = XMVectorMultiplyAdd(Scale, Offset, D.v); + + XMMATRIX Transform = XMMatrixMultiply(World, View); + Transform = XMMatrixMultiply(Transform, Projection); + Transform = XMMatrixInverse(nullptr, Transform); + + XMVECTOR Result = XMVectorMultiplyAdd(V, Scale, Offset); + + return XMVector3TransformCoord(Result, Transform); +} + +//------------------------------------------------------------------------------ + +#ifdef _PREFAST_ +#pragma prefast(push) +#pragma prefast(disable : 26015 26019, "PREfast noise: Esp:1307" ) +#endif + +_Use_decl_annotations_ +inline XMFLOAT3* XM_CALLCONV XMVector3UnprojectStream +( + XMFLOAT3* pOutputStream, + size_t OutputStride, + const XMFLOAT3* pInputStream, + size_t InputStride, + size_t VectorCount, + float ViewportX, + float ViewportY, + float ViewportWidth, + float ViewportHeight, + float ViewportMinZ, + float ViewportMaxZ, + FXMMATRIX Projection, + CXMMATRIX View, + CXMMATRIX World +) noexcept +{ + assert(pOutputStream != nullptr); + assert(pInputStream != nullptr); + + assert(InputStride >= sizeof(XMFLOAT3)); + _Analysis_assume_(InputStride >= sizeof(XMFLOAT3)); + + assert(OutputStride >= sizeof(XMFLOAT3)); + _Analysis_assume_(OutputStride >= sizeof(XMFLOAT3)); + +#if defined(_XM_NO_INTRINSICS_) + + static const XMVECTORF32 D = { { { -1.0f, 1.0f, 0.0f, 0.0f } } }; + + XMVECTOR Scale = XMVectorSet(ViewportWidth * 0.5f, -ViewportHeight * 0.5f, ViewportMaxZ - ViewportMinZ, 1.0f); + Scale = XMVectorReciprocal(Scale); + + XMVECTOR Offset = XMVectorSet(-ViewportX, -ViewportY, -ViewportMinZ, 0.0f); + Offset = XMVectorMultiplyAdd(Scale, Offset, D.v); + + XMMATRIX Transform = XMMatrixMultiply(World, View); + Transform = XMMatrixMultiply(Transform, Projection); + Transform = XMMatrixInverse(nullptr, Transform); + + auto pInputVector = reinterpret_cast(pInputStream); + auto pOutputVector = reinterpret_cast(pOutputStream); + + for (size_t i = 0; i < VectorCount; i++) + { + XMVECTOR V = XMLoadFloat3(reinterpret_cast(pInputVector)); + + XMVECTOR Result = XMVectorMultiplyAdd(V, Scale, Offset); + + Result = XMVector3TransformCoord(Result, Transform); + + XMStoreFloat3(reinterpret_cast(pOutputVector), Result); + + pInputVector += InputStride; + pOutputVector += OutputStride; + } + + return pOutputStream; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + XMMATRIX Transform = XMMatrixMultiply(World, View); + Transform = XMMatrixMultiply(Transform, Projection); + Transform = XMMatrixInverse(nullptr, Transform); + + auto pInputVector = reinterpret_cast(pInputStream); + auto pOutputVector = reinterpret_cast(pOutputStream); + + float sx = 1.f / (ViewportWidth * 0.5f); + float sy = 1.f / (-ViewportHeight * 0.5f); + float sz = 1.f / (ViewportMaxZ - ViewportMinZ); + + float ox = (-ViewportX * sx) - 1.f; + float oy = (-ViewportY * sy) + 1.f; + float oz = (-ViewportMinZ * sz); + + size_t i = 0; + size_t four = VectorCount >> 2; + if (four > 0) + { + if ((InputStride == sizeof(XMFLOAT3)) && (OutputStride == sizeof(XMFLOAT3))) + { + for (size_t j = 0; j < four; ++j) + { + float32x4x3_t V = vld3q_f32(reinterpret_cast(pInputVector)); + pInputVector += sizeof(XMFLOAT3) * 4; + + XMVECTOR ScaleX = vdupq_n_f32(sx); + XMVECTOR OffsetX = vdupq_n_f32(ox); + XMVECTOR VX = vmlaq_f32(OffsetX, ScaleX, V.val[0]); + + float32x2_t r3 = vget_low_f32(Transform.r[3]); + float32x2_t r = vget_low_f32(Transform.r[0]); + XMVECTOR vResult0 = vmlaq_lane_f32(vdupq_lane_f32(r3, 0), VX, r, 0); // Ax+M + XMVECTOR vResult1 = vmlaq_lane_f32(vdupq_lane_f32(r3, 1), VX, r, 1); // Bx+N + + XM_PREFETCH(pInputVector); + + r3 = vget_high_f32(Transform.r[3]); + r = vget_high_f32(Transform.r[0]); + XMVECTOR vResult2 = vmlaq_lane_f32(vdupq_lane_f32(r3, 0), VX, r, 0); // Cx+O + XMVECTOR W = vmlaq_lane_f32(vdupq_lane_f32(r3, 1), VX, r, 1); // Dx+P + + XM_PREFETCH(pInputVector + XM_CACHE_LINE_SIZE); + + XMVECTOR ScaleY = vdupq_n_f32(sy); + XMVECTOR OffsetY = vdupq_n_f32(oy); + XMVECTOR VY = vmlaq_f32(OffsetY, ScaleY, V.val[1]); + + r = vget_low_f32(Transform.r[1]); + vResult0 = vmlaq_lane_f32(vResult0, VY, r, 0); // Ax+Ey+M + vResult1 = vmlaq_lane_f32(vResult1, VY, r, 1); // Bx+Fy+N + + XM_PREFETCH(pInputVector + (XM_CACHE_LINE_SIZE * 2)); + + r = vget_high_f32(Transform.r[1]); + vResult2 = vmlaq_lane_f32(vResult2, VY, r, 0); // Cx+Gy+O + W = vmlaq_lane_f32(W, VY, r, 1); // Dx+Hy+P + + XM_PREFETCH(pInputVector + (XM_CACHE_LINE_SIZE * 3)); + + XMVECTOR ScaleZ = vdupq_n_f32(sz); + XMVECTOR OffsetZ = vdupq_n_f32(oz); + XMVECTOR VZ = vmlaq_f32(OffsetZ, ScaleZ, V.val[2]); + + r = vget_low_f32(Transform.r[2]); + vResult0 = vmlaq_lane_f32(vResult0, VZ, r, 0); // Ax+Ey+Iz+M + vResult1 = vmlaq_lane_f32(vResult1, VZ, r, 1); // Bx+Fy+Jz+N + + XM_PREFETCH(pInputVector + (XM_CACHE_LINE_SIZE * 4)); + + r = vget_high_f32(Transform.r[2]); + vResult2 = vmlaq_lane_f32(vResult2, VZ, r, 0); // Cx+Gy+Kz+O + W = vmlaq_lane_f32(W, VZ, r, 1); // Dx+Hy+Lz+P + + XM_PREFETCH(pInputVector + (XM_CACHE_LINE_SIZE * 5)); + + #if defined(_M_ARM64) || defined(_M_HYBRID_X86_ARM64) || defined(_M_ARM64EC) || __aarch64__ + V.val[0] = vdivq_f32(vResult0, W); + V.val[1] = vdivq_f32(vResult1, W); + V.val[2] = vdivq_f32(vResult2, W); + #else + // 2 iterations of Newton-Raphson refinement of reciprocal + float32x4_t Reciprocal = vrecpeq_f32(W); + float32x4_t S = vrecpsq_f32(Reciprocal, W); + Reciprocal = vmulq_f32(S, Reciprocal); + S = vrecpsq_f32(Reciprocal, W); + Reciprocal = vmulq_f32(S, Reciprocal); + + V.val[0] = vmulq_f32(vResult0, Reciprocal); + V.val[1] = vmulq_f32(vResult1, Reciprocal); + V.val[2] = vmulq_f32(vResult2, Reciprocal); + #endif + + vst3q_f32(reinterpret_cast(pOutputVector), V); + pOutputVector += sizeof(XMFLOAT3) * 4; + + i += 4; + } + } + } + + if (i < VectorCount) + { + float32x2_t ScaleL = vcreate_f32( + static_cast(*reinterpret_cast(&sx)) + | (static_cast(*reinterpret_cast(&sy)) << 32)); + float32x2_t ScaleH = vcreate_f32(static_cast(*reinterpret_cast(&sz))); + + float32x2_t OffsetL = vcreate_f32( + static_cast(*reinterpret_cast(&ox)) + | (static_cast(*reinterpret_cast(&oy)) << 32)); + float32x2_t OffsetH = vcreate_f32(static_cast(*reinterpret_cast(&oz))); + + for (; i < VectorCount; i++) + { + float32x2_t VL = vld1_f32(reinterpret_cast(pInputVector)); + float32x2_t zero = vdup_n_f32(0); + float32x2_t VH = vld1_lane_f32(reinterpret_cast(pInputVector) + 2, zero, 0); + pInputVector += InputStride; + + VL = vmla_f32(OffsetL, VL, ScaleL); + VH = vmla_f32(OffsetH, VH, ScaleH); + + XMVECTOR vResult = vmlaq_lane_f32(Transform.r[3], Transform.r[0], VL, 0); // X + vResult = vmlaq_lane_f32(vResult, Transform.r[1], VL, 1); // Y + vResult = vmlaq_lane_f32(vResult, Transform.r[2], VH, 0); // Z + + VH = vget_high_f32(vResult); + XMVECTOR W = vdupq_lane_f32(VH, 1); + + #if defined(_M_ARM64) || defined(_M_HYBRID_X86_ARM64) || defined(_M_ARM64EC) || __aarch64__ + vResult = vdivq_f32(vResult, W); + #else + // 2 iterations of Newton-Raphson refinement of reciprocal for W + float32x4_t Reciprocal = vrecpeq_f32(W); + float32x4_t S = vrecpsq_f32(Reciprocal, W); + Reciprocal = vmulq_f32(S, Reciprocal); + S = vrecpsq_f32(Reciprocal, W); + Reciprocal = vmulq_f32(S, Reciprocal); + + vResult = vmulq_f32(vResult, Reciprocal); + #endif + + VL = vget_low_f32(vResult); + vst1_f32(reinterpret_cast(pOutputVector), VL); + vst1q_lane_f32(reinterpret_cast(pOutputVector) + 2, vResult, 2); + pOutputVector += OutputStride; + } + } + + return pOutputStream; +#elif defined(_XM_SSE_INTRINSICS_) + static const XMVECTORF32 D = { { { -1.0f, 1.0f, 0.0f, 0.0f } } }; + + XMVECTOR Scale = XMVectorSet(ViewportWidth * 0.5f, -ViewportHeight * 0.5f, ViewportMaxZ - ViewportMinZ, 1.0f); + Scale = XMVectorReciprocal(Scale); + + XMVECTOR Offset = XMVectorSet(-ViewportX, -ViewportY, -ViewportMinZ, 0.0f); + Offset = _mm_mul_ps(Scale, Offset); + Offset = _mm_add_ps(Offset, D); + + XMMATRIX Transform = XMMatrixMultiply(World, View); + Transform = XMMatrixMultiply(Transform, Projection); + Transform = XMMatrixInverse(nullptr, Transform); + + auto pInputVector = reinterpret_cast(pInputStream); + auto pOutputVector = reinterpret_cast(pOutputStream); + + size_t i = 0; + size_t four = VectorCount >> 2; + if (four > 0) + { + if (InputStride == sizeof(XMFLOAT3)) + { + if (OutputStride == sizeof(XMFLOAT3)) + { + if (!(reinterpret_cast(pOutputStream) & 0xF)) + { + // Packed input, aligned & packed output + for (size_t j = 0; j < four; ++j) + { + __m128 V1 = _mm_loadu_ps(reinterpret_cast(pInputVector)); + __m128 L2 = _mm_loadu_ps(reinterpret_cast(pInputVector + 16)); + __m128 L3 = _mm_loadu_ps(reinterpret_cast(pInputVector + 32)); + pInputVector += sizeof(XMFLOAT3) * 4; + + // Unpack the 4 vectors (.w components are junk) + XM3UNPACK3INTO4(V1, L2, L3); + + // Result 1 + V1 = XM_FMADD_PS(V1, Scale, Offset); + + XMVECTOR Z = XM_PERMUTE_PS(V1, _MM_SHUFFLE(2, 2, 2, 2)); + XMVECTOR Y = XM_PERMUTE_PS(V1, _MM_SHUFFLE(1, 1, 1, 1)); + XMVECTOR X = XM_PERMUTE_PS(V1, _MM_SHUFFLE(0, 0, 0, 0)); + + XMVECTOR vTemp = XM_FMADD_PS(Z, Transform.r[2], Transform.r[3]); + XMVECTOR vTemp2 = _mm_mul_ps(Y, Transform.r[1]); + XMVECTOR vTemp3 = _mm_mul_ps(X, Transform.r[0]); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + + XMVECTOR W = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(3, 3, 3, 3)); + V1 = _mm_div_ps(vTemp, W); + + // Result 2 + V2 = XM_FMADD_PS(V2, Scale, Offset); + + Z = XM_PERMUTE_PS(V2, _MM_SHUFFLE(2, 2, 2, 2)); + Y = XM_PERMUTE_PS(V2, _MM_SHUFFLE(1, 1, 1, 1)); + X = XM_PERMUTE_PS(V2, _MM_SHUFFLE(0, 0, 0, 0)); + + vTemp = XM_FMADD_PS(Z, Transform.r[2], Transform.r[3]); + vTemp2 = _mm_mul_ps(Y, Transform.r[1]); + vTemp3 = _mm_mul_ps(X, Transform.r[0]); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + + W = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(3, 3, 3, 3)); + V2 = _mm_div_ps(vTemp, W); + + // Result 3 + V3 = XM_FMADD_PS(V3, Scale, Offset); + + Z = XM_PERMUTE_PS(V3, _MM_SHUFFLE(2, 2, 2, 2)); + Y = XM_PERMUTE_PS(V3, _MM_SHUFFLE(1, 1, 1, 1)); + X = XM_PERMUTE_PS(V3, _MM_SHUFFLE(0, 0, 0, 0)); + + vTemp = XM_FMADD_PS(Z, Transform.r[2], Transform.r[3]); + vTemp2 = _mm_mul_ps(Y, Transform.r[1]); + vTemp3 = _mm_mul_ps(X, Transform.r[0]); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + + W = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(3, 3, 3, 3)); + V3 = _mm_div_ps(vTemp, W); + + // Result 4 + V4 = XM_FMADD_PS(V4, Scale, Offset); + + Z = XM_PERMUTE_PS(V4, _MM_SHUFFLE(2, 2, 2, 2)); + Y = XM_PERMUTE_PS(V4, _MM_SHUFFLE(1, 1, 1, 1)); + X = XM_PERMUTE_PS(V4, _MM_SHUFFLE(0, 0, 0, 0)); + + vTemp = XM_FMADD_PS(Z, Transform.r[2], Transform.r[3]); + vTemp2 = _mm_mul_ps(Y, Transform.r[1]); + vTemp3 = _mm_mul_ps(X, Transform.r[0]); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + + W = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(3, 3, 3, 3)); + V4 = _mm_div_ps(vTemp, W); + + // Pack and store the vectors + XM3PACK4INTO3(vTemp); + XM_STREAM_PS(reinterpret_cast(pOutputVector), V1); + XM_STREAM_PS(reinterpret_cast(pOutputVector + 16), vTemp); + XM_STREAM_PS(reinterpret_cast(pOutputVector + 32), V3); + pOutputVector += sizeof(XMFLOAT3) * 4; + i += 4; + } + } + else + { + // Packed input, unaligned & packed output + for (size_t j = 0; j < four; ++j) + { + __m128 V1 = _mm_loadu_ps(reinterpret_cast(pInputVector)); + __m128 L2 = _mm_loadu_ps(reinterpret_cast(pInputVector + 16)); + __m128 L3 = _mm_loadu_ps(reinterpret_cast(pInputVector + 32)); + pInputVector += sizeof(XMFLOAT3) * 4; + + // Unpack the 4 vectors (.w components are junk) + XM3UNPACK3INTO4(V1, L2, L3); + + // Result 1 + V1 = XM_FMADD_PS(V1, Scale, Offset); + + XMVECTOR Z = XM_PERMUTE_PS(V1, _MM_SHUFFLE(2, 2, 2, 2)); + XMVECTOR Y = XM_PERMUTE_PS(V1, _MM_SHUFFLE(1, 1, 1, 1)); + XMVECTOR X = XM_PERMUTE_PS(V1, _MM_SHUFFLE(0, 0, 0, 0)); + + XMVECTOR vTemp = XM_FMADD_PS(Z, Transform.r[2], Transform.r[3]); + XMVECTOR vTemp2 = _mm_mul_ps(Y, Transform.r[1]); + XMVECTOR vTemp3 = _mm_mul_ps(X, Transform.r[0]); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + + XMVECTOR W = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(3, 3, 3, 3)); + V1 = _mm_div_ps(vTemp, W); + + // Result 2 + V2 = XM_FMADD_PS(V2, Scale, Offset); + + Z = XM_PERMUTE_PS(V2, _MM_SHUFFLE(2, 2, 2, 2)); + Y = XM_PERMUTE_PS(V2, _MM_SHUFFLE(1, 1, 1, 1)); + X = XM_PERMUTE_PS(V2, _MM_SHUFFLE(0, 0, 0, 0)); + + vTemp = XM_FMADD_PS(Z, Transform.r[2], Transform.r[3]); + vTemp2 = _mm_mul_ps(Y, Transform.r[1]); + vTemp3 = _mm_mul_ps(X, Transform.r[0]); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + + W = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(3, 3, 3, 3)); + V2 = _mm_div_ps(vTemp, W); + + // Result 3 + V3 = XM_FMADD_PS(V3, Scale, Offset); + + Z = XM_PERMUTE_PS(V3, _MM_SHUFFLE(2, 2, 2, 2)); + Y = XM_PERMUTE_PS(V3, _MM_SHUFFLE(1, 1, 1, 1)); + X = XM_PERMUTE_PS(V3, _MM_SHUFFLE(0, 0, 0, 0)); + + vTemp = XM_FMADD_PS(Z, Transform.r[2], Transform.r[3]); + vTemp2 = _mm_mul_ps(Y, Transform.r[1]); + vTemp3 = _mm_mul_ps(X, Transform.r[0]); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + + W = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(3, 3, 3, 3)); + V3 = _mm_div_ps(vTemp, W); + + // Result 4 + V4 = XM_FMADD_PS(V4, Scale, Offset); + + Z = XM_PERMUTE_PS(V4, _MM_SHUFFLE(2, 2, 2, 2)); + Y = XM_PERMUTE_PS(V4, _MM_SHUFFLE(1, 1, 1, 1)); + X = XM_PERMUTE_PS(V4, _MM_SHUFFLE(0, 0, 0, 0)); + + vTemp = XM_FMADD_PS(Z, Transform.r[2], Transform.r[3]); + vTemp2 = _mm_mul_ps(Y, Transform.r[1]); + vTemp3 = _mm_mul_ps(X, Transform.r[0]); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + + W = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(3, 3, 3, 3)); + V4 = _mm_div_ps(vTemp, W); + + // Pack and store the vectors + XM3PACK4INTO3(vTemp); + _mm_storeu_ps(reinterpret_cast(pOutputVector), V1); + _mm_storeu_ps(reinterpret_cast(pOutputVector + 16), vTemp); + _mm_storeu_ps(reinterpret_cast(pOutputVector + 32), V3); + pOutputVector += sizeof(XMFLOAT3) * 4; + i += 4; + } + } + } + else + { + // Packed input, unpacked output + for (size_t j = 0; j < four; ++j) + { + __m128 V1 = _mm_loadu_ps(reinterpret_cast(pInputVector)); + __m128 L2 = _mm_loadu_ps(reinterpret_cast(pInputVector + 16)); + __m128 L3 = _mm_loadu_ps(reinterpret_cast(pInputVector + 32)); + pInputVector += sizeof(XMFLOAT3) * 4; + + // Unpack the 4 vectors (.w components are junk) + XM3UNPACK3INTO4(V1, L2, L3); + + // Result 1 + V1 = XM_FMADD_PS(V1, Scale, Offset); + + XMVECTOR Z = XM_PERMUTE_PS(V1, _MM_SHUFFLE(2, 2, 2, 2)); + XMVECTOR Y = XM_PERMUTE_PS(V1, _MM_SHUFFLE(1, 1, 1, 1)); + XMVECTOR X = XM_PERMUTE_PS(V1, _MM_SHUFFLE(0, 0, 0, 0)); + + XMVECTOR vTemp = XM_FMADD_PS(Z, Transform.r[2], Transform.r[3]); + XMVECTOR vTemp2 = _mm_mul_ps(Y, Transform.r[1]); + XMVECTOR vTemp3 = _mm_mul_ps(X, Transform.r[0]); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + + XMVECTOR W = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(3, 3, 3, 3)); + vTemp = _mm_div_ps(vTemp, W); + + XMStoreFloat3(reinterpret_cast(pOutputVector), vTemp); + pOutputVector += OutputStride; + + // Result 2 + V2 = XM_FMADD_PS(V2, Scale, Offset); + + Z = XM_PERMUTE_PS(V2, _MM_SHUFFLE(2, 2, 2, 2)); + Y = XM_PERMUTE_PS(V2, _MM_SHUFFLE(1, 1, 1, 1)); + X = XM_PERMUTE_PS(V2, _MM_SHUFFLE(0, 0, 0, 0)); + + vTemp = XM_FMADD_PS(Z, Transform.r[2], Transform.r[3]); + vTemp2 = _mm_mul_ps(Y, Transform.r[1]); + vTemp3 = _mm_mul_ps(X, Transform.r[0]); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + + W = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(3, 3, 3, 3)); + vTemp = _mm_div_ps(vTemp, W); + + XMStoreFloat3(reinterpret_cast(pOutputVector), vTemp); + pOutputVector += OutputStride; + + // Result 3 + V3 = XM_FMADD_PS(V3, Scale, Offset); + + Z = XM_PERMUTE_PS(V3, _MM_SHUFFLE(2, 2, 2, 2)); + Y = XM_PERMUTE_PS(V3, _MM_SHUFFLE(1, 1, 1, 1)); + X = XM_PERMUTE_PS(V3, _MM_SHUFFLE(0, 0, 0, 0)); + + vTemp = XM_FMADD_PS(Z, Transform.r[2], Transform.r[3]); + vTemp2 = _mm_mul_ps(Y, Transform.r[1]); + vTemp3 = _mm_mul_ps(X, Transform.r[0]); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + + W = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(3, 3, 3, 3)); + vTemp = _mm_div_ps(vTemp, W); + + XMStoreFloat3(reinterpret_cast(pOutputVector), vTemp); + pOutputVector += OutputStride; + + // Result 4 + V4 = XM_FMADD_PS(V4, Scale, Offset); + + Z = XM_PERMUTE_PS(V4, _MM_SHUFFLE(2, 2, 2, 2)); + Y = XM_PERMUTE_PS(V4, _MM_SHUFFLE(1, 1, 1, 1)); + X = XM_PERMUTE_PS(V4, _MM_SHUFFLE(0, 0, 0, 0)); + + vTemp = XM_FMADD_PS(Z, Transform.r[2], Transform.r[3]); + vTemp2 = _mm_mul_ps(Y, Transform.r[1]); + vTemp3 = _mm_mul_ps(X, Transform.r[0]); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + + W = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(3, 3, 3, 3)); + vTemp = _mm_div_ps(vTemp, W); + + XMStoreFloat3(reinterpret_cast(pOutputVector), vTemp); + pOutputVector += OutputStride; + + i += 4; + } + } + } + } + + for (; i < VectorCount; i++) + { + XMVECTOR V = XMLoadFloat3(reinterpret_cast(pInputVector)); + pInputVector += InputStride; + + V = _mm_mul_ps(V, Scale); + V = _mm_add_ps(V, Offset); + + XMVECTOR Z = XM_PERMUTE_PS(V, _MM_SHUFFLE(2, 2, 2, 2)); + XMVECTOR Y = XM_PERMUTE_PS(V, _MM_SHUFFLE(1, 1, 1, 1)); + XMVECTOR X = XM_PERMUTE_PS(V, _MM_SHUFFLE(0, 0, 0, 0)); + + XMVECTOR vTemp = XM_FMADD_PS(Z, Transform.r[2], Transform.r[3]); + XMVECTOR vTemp2 = _mm_mul_ps(Y, Transform.r[1]); + XMVECTOR vTemp3 = _mm_mul_ps(X, Transform.r[0]); + vTemp = _mm_add_ps(vTemp, vTemp2); + vTemp = _mm_add_ps(vTemp, vTemp3); + + XMVECTOR W = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(3, 3, 3, 3)); + vTemp = _mm_div_ps(vTemp, W); + + XMStoreFloat3(reinterpret_cast(pOutputVector), vTemp); + pOutputVector += OutputStride; + } + + XM_SFENCE(); + + return pOutputStream; +#endif +} + +#ifdef _PREFAST_ +#pragma prefast(pop) +#endif + +/**************************************************************************** + * + * 4D Vector + * + ****************************************************************************/ + +//------------------------------------------------------------------------------ +// Comparison operations +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ + +inline bool XM_CALLCONV XMVector4Equal +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + return (((V1.vector4_f32[0] == V2.vector4_f32[0]) && (V1.vector4_f32[1] == V2.vector4_f32[1]) && (V1.vector4_f32[2] == V2.vector4_f32[2]) && (V1.vector4_f32[3] == V2.vector4_f32[3])) != 0); +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x4_t vResult = vceqq_f32(V1, V2); + uint8x8x2_t vTemp = vzip_u8(vget_low_u8(vreinterpretq_u8_u32(vResult)), vget_high_u8(vreinterpretq_u8_u32(vResult))); + uint16x4x2_t vTemp2 = vzip_u16(vreinterpret_u16_u8(vTemp.val[0]), vreinterpret_u16_u8(vTemp.val[1])); + return (vget_lane_u32(vreinterpret_u32_u16(vTemp2.val[1]), 1) == 0xFFFFFFFFU); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vTemp = _mm_cmpeq_ps(V1, V2); + return ((_mm_movemask_ps(vTemp) == 0x0f) != 0); +#else + return XMComparisonAllTrue(XMVector4EqualR(V1, V2)); +#endif +} + +//------------------------------------------------------------------------------ + +inline uint32_t XM_CALLCONV XMVector4EqualR +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + uint32_t CR = 0; + + if ((V1.vector4_f32[0] == V2.vector4_f32[0]) && + (V1.vector4_f32[1] == V2.vector4_f32[1]) && + (V1.vector4_f32[2] == V2.vector4_f32[2]) && + (V1.vector4_f32[3] == V2.vector4_f32[3])) + { + CR = XM_CRMASK_CR6TRUE; + } + else if ((V1.vector4_f32[0] != V2.vector4_f32[0]) && + (V1.vector4_f32[1] != V2.vector4_f32[1]) && + (V1.vector4_f32[2] != V2.vector4_f32[2]) && + (V1.vector4_f32[3] != V2.vector4_f32[3])) + { + CR = XM_CRMASK_CR6FALSE; + } + return CR; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x4_t vResult = vceqq_f32(V1, V2); + uint8x8x2_t vTemp = vzip_u8(vget_low_u8(vreinterpretq_u8_u32(vResult)), vget_high_u8(vreinterpretq_u8_u32(vResult))); + uint16x4x2_t vTemp2 = vzip_u16(vreinterpret_u16_u8(vTemp.val[0]), vreinterpret_u16_u8(vTemp.val[1])); + uint32_t r = vget_lane_u32(vreinterpret_u32_u16(vTemp2.val[1]), 1); + + uint32_t CR = 0; + if (r == 0xFFFFFFFFU) + { + CR = XM_CRMASK_CR6TRUE; + } + else if (!r) + { + CR = XM_CRMASK_CR6FALSE; + } + return CR; +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vTemp = _mm_cmpeq_ps(V1, V2); + int iTest = _mm_movemask_ps(vTemp); + uint32_t CR = 0; + if (iTest == 0xf) // All equal? + { + CR = XM_CRMASK_CR6TRUE; + } + else if (iTest == 0) // All not equal? + { + CR = XM_CRMASK_CR6FALSE; + } + return CR; +#endif +} + +//------------------------------------------------------------------------------ + +inline bool XM_CALLCONV XMVector4EqualInt +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + return (((V1.vector4_u32[0] == V2.vector4_u32[0]) && (V1.vector4_u32[1] == V2.vector4_u32[1]) && (V1.vector4_u32[2] == V2.vector4_u32[2]) && (V1.vector4_u32[3] == V2.vector4_u32[3])) != 0); +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x4_t vResult = vceqq_u32(vreinterpretq_u32_f32(V1), vreinterpretq_u32_f32(V2)); + uint8x8x2_t vTemp = vzip_u8(vget_low_u8(vreinterpretq_u8_u32(vResult)), vget_high_u8(vreinterpretq_u8_u32(vResult))); + uint16x4x2_t vTemp2 = vzip_u16(vreinterpret_u16_u8(vTemp.val[0]), vreinterpret_u16_u8(vTemp.val[1])); + return (vget_lane_u32(vreinterpret_u32_u16(vTemp2.val[1]), 1) == 0xFFFFFFFFU); +#elif defined(_XM_SSE_INTRINSICS_) + __m128i vTemp = _mm_cmpeq_epi32(_mm_castps_si128(V1), _mm_castps_si128(V2)); + return ((_mm_movemask_ps(_mm_castsi128_ps(vTemp)) == 0xf) != 0); +#else + return XMComparisonAllTrue(XMVector4EqualIntR(V1, V2)); +#endif +} + +//------------------------------------------------------------------------------ + +inline uint32_t XM_CALLCONV XMVector4EqualIntR +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + uint32_t CR = 0; + if (V1.vector4_u32[0] == V2.vector4_u32[0] && + V1.vector4_u32[1] == V2.vector4_u32[1] && + V1.vector4_u32[2] == V2.vector4_u32[2] && + V1.vector4_u32[3] == V2.vector4_u32[3]) + { + CR = XM_CRMASK_CR6TRUE; + } + else if (V1.vector4_u32[0] != V2.vector4_u32[0] && + V1.vector4_u32[1] != V2.vector4_u32[1] && + V1.vector4_u32[2] != V2.vector4_u32[2] && + V1.vector4_u32[3] != V2.vector4_u32[3]) + { + CR = XM_CRMASK_CR6FALSE; + } + return CR; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x4_t vResult = vceqq_u32(vreinterpretq_u32_f32(V1), vreinterpretq_u32_f32(V2)); + uint8x8x2_t vTemp = vzip_u8(vget_low_u8(vreinterpretq_u8_u32(vResult)), vget_high_u8(vreinterpretq_u8_u32(vResult))); + uint16x4x2_t vTemp2 = vzip_u16(vreinterpret_u16_u8(vTemp.val[0]), vreinterpret_u16_u8(vTemp.val[1])); + uint32_t r = vget_lane_u32(vreinterpret_u32_u16(vTemp2.val[1]), 1); + + uint32_t CR = 0; + if (r == 0xFFFFFFFFU) + { + CR = XM_CRMASK_CR6TRUE; + } + else if (!r) + { + CR = XM_CRMASK_CR6FALSE; + } + return CR; +#elif defined(_XM_SSE_INTRINSICS_) + __m128i vTemp = _mm_cmpeq_epi32(_mm_castps_si128(V1), _mm_castps_si128(V2)); + int iTest = _mm_movemask_ps(_mm_castsi128_ps(vTemp)); + uint32_t CR = 0; + if (iTest == 0xf) // All equal? + { + CR = XM_CRMASK_CR6TRUE; + } + else if (iTest == 0) // All not equal? + { + CR = XM_CRMASK_CR6FALSE; + } + return CR; +#endif +} + +inline bool XM_CALLCONV XMVector4NearEqual +( + FXMVECTOR V1, + FXMVECTOR V2, + FXMVECTOR Epsilon +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + float dx, dy, dz, dw; + + dx = fabsf(V1.vector4_f32[0] - V2.vector4_f32[0]); + dy = fabsf(V1.vector4_f32[1] - V2.vector4_f32[1]); + dz = fabsf(V1.vector4_f32[2] - V2.vector4_f32[2]); + dw = fabsf(V1.vector4_f32[3] - V2.vector4_f32[3]); + return (((dx <= Epsilon.vector4_f32[0]) && + (dy <= Epsilon.vector4_f32[1]) && + (dz <= Epsilon.vector4_f32[2]) && + (dw <= Epsilon.vector4_f32[3])) != 0); +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x4_t vDelta = vsubq_f32(V1, V2); +#if defined(_MSC_VER) && !defined(__clang__) && !defined(_ARM64_DISTINCT_NEON_TYPES) + uint32x4_t vResult = vacleq_f32(vDelta, Epsilon); +#else + uint32x4_t vResult = vcleq_f32(vabsq_f32(vDelta), Epsilon); +#endif + uint8x8x2_t vTemp = vzip_u8(vget_low_u8(vreinterpretq_u8_u32(vResult)), vget_high_u8(vreinterpretq_u8_u32(vResult))); + uint16x4x2_t vTemp2 = vzip_u16(vreinterpret_u16_u8(vTemp.val[0]), vreinterpret_u16_u8(vTemp.val[1])); + return (vget_lane_u32(vreinterpret_u32_u16(vTemp2.val[1]), 1) == 0xFFFFFFFFU); +#elif defined(_XM_SSE_INTRINSICS_) + // Get the difference + XMVECTOR vDelta = _mm_sub_ps(V1, V2); + // Get the absolute value of the difference + XMVECTOR vTemp = _mm_setzero_ps(); + vTemp = _mm_sub_ps(vTemp, vDelta); + vTemp = _mm_max_ps(vTemp, vDelta); + vTemp = _mm_cmple_ps(vTemp, Epsilon); + return ((_mm_movemask_ps(vTemp) == 0xf) != 0); +#endif +} + +//------------------------------------------------------------------------------ + +inline bool XM_CALLCONV XMVector4NotEqual +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + return (((V1.vector4_f32[0] != V2.vector4_f32[0]) || (V1.vector4_f32[1] != V2.vector4_f32[1]) || (V1.vector4_f32[2] != V2.vector4_f32[2]) || (V1.vector4_f32[3] != V2.vector4_f32[3])) != 0); +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x4_t vResult = vceqq_f32(V1, V2); + uint8x8x2_t vTemp = vzip_u8(vget_low_u8(vreinterpretq_u8_u32(vResult)), vget_high_u8(vreinterpretq_u8_u32(vResult))); + uint16x4x2_t vTemp2 = vzip_u16(vreinterpret_u16_u8(vTemp.val[0]), vreinterpret_u16_u8(vTemp.val[1])); + return (vget_lane_u32(vreinterpret_u32_u16(vTemp2.val[1]), 1) != 0xFFFFFFFFU); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vTemp = _mm_cmpneq_ps(V1, V2); + return ((_mm_movemask_ps(vTemp)) != 0); +#else + return XMComparisonAnyFalse(XMVector4EqualR(V1, V2)); +#endif +} + +//------------------------------------------------------------------------------ + +inline bool XM_CALLCONV XMVector4NotEqualInt +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + return (((V1.vector4_u32[0] != V2.vector4_u32[0]) || (V1.vector4_u32[1] != V2.vector4_u32[1]) || (V1.vector4_u32[2] != V2.vector4_u32[2]) || (V1.vector4_u32[3] != V2.vector4_u32[3])) != 0); +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x4_t vResult = vceqq_u32(vreinterpretq_u32_f32(V1), vreinterpretq_u32_f32(V2)); + uint8x8x2_t vTemp = vzip_u8(vget_low_u8(vreinterpretq_u8_u32(vResult)), vget_high_u8(vreinterpretq_u8_u32(vResult))); + uint16x4x2_t vTemp2 = vzip_u16(vreinterpret_u16_u8(vTemp.val[0]), vreinterpret_u16_u8(vTemp.val[1])); + return (vget_lane_u32(vreinterpret_u32_u16(vTemp2.val[1]), 1) != 0xFFFFFFFFU); +#elif defined(_XM_SSE_INTRINSICS_) + __m128i vTemp = _mm_cmpeq_epi32(_mm_castps_si128(V1), _mm_castps_si128(V2)); + return ((_mm_movemask_ps(_mm_castsi128_ps(vTemp)) != 0xF) != 0); +#else + return XMComparisonAnyFalse(XMVector4EqualIntR(V1, V2)); +#endif +} + +//------------------------------------------------------------------------------ + +inline bool XM_CALLCONV XMVector4Greater +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + return (((V1.vector4_f32[0] > V2.vector4_f32[0]) && (V1.vector4_f32[1] > V2.vector4_f32[1]) && (V1.vector4_f32[2] > V2.vector4_f32[2]) && (V1.vector4_f32[3] > V2.vector4_f32[3])) != 0); +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x4_t vResult = vcgtq_f32(V1, V2); + uint8x8x2_t vTemp = vzip_u8(vget_low_u8(vreinterpretq_u8_u32(vResult)), vget_high_u8(vreinterpretq_u8_u32(vResult))); + uint16x4x2_t vTemp2 = vzip_u16(vreinterpret_u16_u8(vTemp.val[0]), vreinterpret_u16_u8(vTemp.val[1])); + return (vget_lane_u32(vreinterpret_u32_u16(vTemp2.val[1]), 1) == 0xFFFFFFFFU); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vTemp = _mm_cmpgt_ps(V1, V2); + return ((_mm_movemask_ps(vTemp) == 0x0f) != 0); +#else + return XMComparisonAllTrue(XMVector4GreaterR(V1, V2)); +#endif +} + +//------------------------------------------------------------------------------ + +inline uint32_t XM_CALLCONV XMVector4GreaterR +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + uint32_t CR = 0; + if (V1.vector4_f32[0] > V2.vector4_f32[0] && + V1.vector4_f32[1] > V2.vector4_f32[1] && + V1.vector4_f32[2] > V2.vector4_f32[2] && + V1.vector4_f32[3] > V2.vector4_f32[3]) + { + CR = XM_CRMASK_CR6TRUE; + } + else if (V1.vector4_f32[0] <= V2.vector4_f32[0] && + V1.vector4_f32[1] <= V2.vector4_f32[1] && + V1.vector4_f32[2] <= V2.vector4_f32[2] && + V1.vector4_f32[3] <= V2.vector4_f32[3]) + { + CR = XM_CRMASK_CR6FALSE; + } + return CR; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x4_t vResult = vcgtq_f32(V1, V2); + uint8x8x2_t vTemp = vzip_u8(vget_low_u8(vreinterpretq_u8_u32(vResult)), vget_high_u8(vreinterpretq_u8_u32(vResult))); + uint16x4x2_t vTemp2 = vzip_u16(vreinterpret_u16_u8(vTemp.val[0]), vreinterpret_u16_u8(vTemp.val[1])); + uint32_t r = vget_lane_u32(vreinterpret_u32_u16(vTemp2.val[1]), 1); + + uint32_t CR = 0; + if (r == 0xFFFFFFFFU) + { + CR = XM_CRMASK_CR6TRUE; + } + else if (!r) + { + CR = XM_CRMASK_CR6FALSE; + } + return CR; +#elif defined(_XM_SSE_INTRINSICS_) + uint32_t CR = 0; + XMVECTOR vTemp = _mm_cmpgt_ps(V1, V2); + int iTest = _mm_movemask_ps(vTemp); + if (iTest == 0xf) + { + CR = XM_CRMASK_CR6TRUE; + } + else if (!iTest) + { + CR = XM_CRMASK_CR6FALSE; + } + return CR; +#endif +} + +//------------------------------------------------------------------------------ + +inline bool XM_CALLCONV XMVector4GreaterOrEqual +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + return (((V1.vector4_f32[0] >= V2.vector4_f32[0]) && (V1.vector4_f32[1] >= V2.vector4_f32[1]) && (V1.vector4_f32[2] >= V2.vector4_f32[2]) && (V1.vector4_f32[3] >= V2.vector4_f32[3])) != 0); +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x4_t vResult = vcgeq_f32(V1, V2); + uint8x8x2_t vTemp = vzip_u8(vget_low_u8(vreinterpretq_u8_u32(vResult)), vget_high_u8(vreinterpretq_u8_u32(vResult))); + uint16x4x2_t vTemp2 = vzip_u16(vreinterpret_u16_u8(vTemp.val[0]), vreinterpret_u16_u8(vTemp.val[1])); + return (vget_lane_u32(vreinterpret_u32_u16(vTemp2.val[1]), 1) == 0xFFFFFFFFU); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vTemp = _mm_cmpge_ps(V1, V2); + return ((_mm_movemask_ps(vTemp) == 0x0f) != 0); +#else + return XMComparisonAllTrue(XMVector4GreaterOrEqualR(V1, V2)); +#endif +} + +//------------------------------------------------------------------------------ + +inline uint32_t XM_CALLCONV XMVector4GreaterOrEqualR +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + uint32_t CR = 0; + if ((V1.vector4_f32[0] >= V2.vector4_f32[0]) && + (V1.vector4_f32[1] >= V2.vector4_f32[1]) && + (V1.vector4_f32[2] >= V2.vector4_f32[2]) && + (V1.vector4_f32[3] >= V2.vector4_f32[3])) + { + CR = XM_CRMASK_CR6TRUE; + } + else if ((V1.vector4_f32[0] < V2.vector4_f32[0]) && + (V1.vector4_f32[1] < V2.vector4_f32[1]) && + (V1.vector4_f32[2] < V2.vector4_f32[2]) && + (V1.vector4_f32[3] < V2.vector4_f32[3])) + { + CR = XM_CRMASK_CR6FALSE; + } + return CR; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x4_t vResult = vcgeq_f32(V1, V2); + uint8x8x2_t vTemp = vzip_u8(vget_low_u8(vreinterpretq_u8_u32(vResult)), vget_high_u8(vreinterpretq_u8_u32(vResult))); + uint16x4x2_t vTemp2 = vzip_u16(vreinterpret_u16_u8(vTemp.val[0]), vreinterpret_u16_u8(vTemp.val[1])); + uint32_t r = vget_lane_u32(vreinterpret_u32_u16(vTemp2.val[1]), 1); + + uint32_t CR = 0; + if (r == 0xFFFFFFFFU) + { + CR = XM_CRMASK_CR6TRUE; + } + else if (!r) + { + CR = XM_CRMASK_CR6FALSE; + } + return CR; +#elif defined(_XM_SSE_INTRINSICS_) + uint32_t CR = 0; + XMVECTOR vTemp = _mm_cmpge_ps(V1, V2); + int iTest = _mm_movemask_ps(vTemp); + if (iTest == 0x0f) + { + CR = XM_CRMASK_CR6TRUE; + } + else if (!iTest) + { + CR = XM_CRMASK_CR6FALSE; + } + return CR; +#endif +} + +//------------------------------------------------------------------------------ + +inline bool XM_CALLCONV XMVector4Less +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + return (((V1.vector4_f32[0] < V2.vector4_f32[0]) && (V1.vector4_f32[1] < V2.vector4_f32[1]) && (V1.vector4_f32[2] < V2.vector4_f32[2]) && (V1.vector4_f32[3] < V2.vector4_f32[3])) != 0); +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x4_t vResult = vcltq_f32(V1, V2); + uint8x8x2_t vTemp = vzip_u8(vget_low_u8(vreinterpretq_u8_u32(vResult)), vget_high_u8(vreinterpretq_u8_u32(vResult))); + uint16x4x2_t vTemp2 = vzip_u16(vreinterpret_u16_u8(vTemp.val[0]), vreinterpret_u16_u8(vTemp.val[1])); + return (vget_lane_u32(vreinterpret_u32_u16(vTemp2.val[1]), 1) == 0xFFFFFFFFU); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vTemp = _mm_cmplt_ps(V1, V2); + return ((_mm_movemask_ps(vTemp) == 0x0f) != 0); +#else + return XMComparisonAllTrue(XMVector4GreaterR(V2, V1)); +#endif +} + +//------------------------------------------------------------------------------ + +inline bool XM_CALLCONV XMVector4LessOrEqual +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + return (((V1.vector4_f32[0] <= V2.vector4_f32[0]) && (V1.vector4_f32[1] <= V2.vector4_f32[1]) && (V1.vector4_f32[2] <= V2.vector4_f32[2]) && (V1.vector4_f32[3] <= V2.vector4_f32[3])) != 0); +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x4_t vResult = vcleq_f32(V1, V2); + uint8x8x2_t vTemp = vzip_u8(vget_low_u8(vreinterpretq_u8_u32(vResult)), vget_high_u8(vreinterpretq_u8_u32(vResult))); + uint16x4x2_t vTemp2 = vzip_u16(vreinterpret_u16_u8(vTemp.val[0]), vreinterpret_u16_u8(vTemp.val[1])); + return (vget_lane_u32(vreinterpret_u32_u16(vTemp2.val[1]), 1) == 0xFFFFFFFFU); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vTemp = _mm_cmple_ps(V1, V2); + return ((_mm_movemask_ps(vTemp) == 0x0f) != 0); +#else + return XMComparisonAllTrue(XMVector4GreaterOrEqualR(V2, V1)); +#endif +} + +//------------------------------------------------------------------------------ + +inline bool XM_CALLCONV XMVector4InBounds +( + FXMVECTOR V, + FXMVECTOR Bounds +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + return (((V.vector4_f32[0] <= Bounds.vector4_f32[0] && V.vector4_f32[0] >= -Bounds.vector4_f32[0]) && + (V.vector4_f32[1] <= Bounds.vector4_f32[1] && V.vector4_f32[1] >= -Bounds.vector4_f32[1]) && + (V.vector4_f32[2] <= Bounds.vector4_f32[2] && V.vector4_f32[2] >= -Bounds.vector4_f32[2]) && + (V.vector4_f32[3] <= Bounds.vector4_f32[3] && V.vector4_f32[3] >= -Bounds.vector4_f32[3])) != 0); +#elif defined(_XM_ARM_NEON_INTRINSICS_) + // Test if less than or equal + uint32x4_t ivTemp1 = vcleq_f32(V, Bounds); + // Negate the bounds + float32x4_t vTemp2 = vnegq_f32(Bounds); + // Test if greater or equal (Reversed) + uint32x4_t ivTemp2 = vcleq_f32(vTemp2, V); + // Blend answers + ivTemp1 = vandq_u32(ivTemp1, ivTemp2); + // in bounds? + uint8x8x2_t vTemp = vzip_u8(vget_low_u8(vreinterpretq_u8_u32(ivTemp1)), vget_high_u8(vreinterpretq_u8_u32(ivTemp1))); + uint16x4x2_t vTemp3 = vzip_u16(vreinterpret_u16_u8(vTemp.val[0]), vreinterpret_u16_u8(vTemp.val[1])); + return (vget_lane_u32(vreinterpret_u32_u16(vTemp3.val[1]), 1) == 0xFFFFFFFFU); +#elif defined(_XM_SSE_INTRINSICS_) + // Test if less than or equal + XMVECTOR vTemp1 = _mm_cmple_ps(V, Bounds); + // Negate the bounds + XMVECTOR vTemp2 = _mm_mul_ps(Bounds, g_XMNegativeOne); + // Test if greater or equal (Reversed) + vTemp2 = _mm_cmple_ps(vTemp2, V); + // Blend answers + vTemp1 = _mm_and_ps(vTemp1, vTemp2); + // All in bounds? + return ((_mm_movemask_ps(vTemp1) == 0x0f) != 0); +#else + return XMComparisonAllInBounds(XMVector4InBoundsR(V, Bounds)); +#endif +} + +//------------------------------------------------------------------------------ + +#if !defined(_XM_NO_INTRINSICS_) && defined(_MSC_VER) && !defined(__INTEL_COMPILER) +#pragma float_control(push) +#pragma float_control(precise, on) +#endif + +inline bool XM_CALLCONV XMVector4IsNaN(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + return (XMISNAN(V.vector4_f32[0]) || + XMISNAN(V.vector4_f32[1]) || + XMISNAN(V.vector4_f32[2]) || + XMISNAN(V.vector4_f32[3])); +#elif defined(_XM_ARM_NEON_INTRINSICS_) +#if defined(__clang__) && defined(__FINITE_MATH_ONLY__) + return isnan(vgetq_lane_f32(V, 0)) || isnan(vgetq_lane_f32(V, 1)) || isnan(vgetq_lane_f32(V, 2)) || isnan(vgetq_lane_f32(V, 3)); +#else +// Test against itself. NaN is always not equal + uint32x4_t vTempNan = vceqq_f32(V, V); + uint8x8x2_t vTemp = vzip_u8(vget_low_u8(vreinterpretq_u8_u32(vTempNan)), vget_high_u8(vreinterpretq_u8_u32(vTempNan))); + uint16x4x2_t vTemp2 = vzip_u16(vreinterpret_u16_u8(vTemp.val[0]), vreinterpret_u16_u8(vTemp.val[1])); + // If any are NaN, the mask is zero + return (vget_lane_u32(vreinterpret_u32_u16(vTemp2.val[1]), 1) != 0xFFFFFFFFU); +#endif +#elif defined(_XM_SSE_INTRINSICS_) +#if defined(__clang__) && defined(__FINITE_MATH_ONLY__) + XM_ALIGNED_DATA(16) float tmp[4]; + _mm_store_ps(tmp, V); + return isnan(tmp[0]) || isnan(tmp[1]) || isnan(tmp[2]) || isnan(tmp[3]); +#else +// Test against itself. NaN is always not equal + XMVECTOR vTempNan = _mm_cmpneq_ps(V, V); + // If any are NaN, the mask is non-zero + return (_mm_movemask_ps(vTempNan) != 0); +#endif +#endif +} + +#if !defined(_XM_NO_INTRINSICS_) && defined(_MSC_VER) && !defined(__INTEL_COMPILER) +#pragma float_control(pop) +#endif + +//------------------------------------------------------------------------------ + +inline bool XM_CALLCONV XMVector4IsInfinite(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + return (XMISINF(V.vector4_f32[0]) || + XMISINF(V.vector4_f32[1]) || + XMISINF(V.vector4_f32[2]) || + XMISINF(V.vector4_f32[3])); + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + // Mask off the sign bit + uint32x4_t vTempInf = vandq_u32(vreinterpretq_u32_f32(V), g_XMAbsMask); + // Compare to infinity + vTempInf = vceqq_f32(vreinterpretq_f32_u32(vTempInf), g_XMInfinity); + // If any are infinity, the signs are true. + uint8x8x2_t vTemp = vzip_u8(vget_low_u8(vreinterpretq_u8_u32(vTempInf)), vget_high_u8(vreinterpretq_u8_u32(vTempInf))); + uint16x4x2_t vTemp2 = vzip_u16(vreinterpret_u16_u8(vTemp.val[0]), vreinterpret_u16_u8(vTemp.val[1])); + return (vget_lane_u32(vreinterpret_u32_u16(vTemp2.val[1]), 1) != 0); +#elif defined(_XM_SSE_INTRINSICS_) + // Mask off the sign bit + XMVECTOR vTemp = _mm_and_ps(V, g_XMAbsMask); + // Compare to infinity + vTemp = _mm_cmpeq_ps(vTemp, g_XMInfinity); + // If any are infinity, the signs are true. + return (_mm_movemask_ps(vTemp) != 0); +#endif +} + +//------------------------------------------------------------------------------ +// Computation operations +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector4Dot +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTORF32 Result; + Result.f[0] = + Result.f[1] = + Result.f[2] = + Result.f[3] = V1.vector4_f32[0] * V2.vector4_f32[0] + V1.vector4_f32[1] * V2.vector4_f32[1] + V1.vector4_f32[2] * V2.vector4_f32[2] + V1.vector4_f32[3] * V2.vector4_f32[3]; + return Result.v; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x4_t vTemp = vmulq_f32(V1, V2); + float32x2_t v1 = vget_low_f32(vTemp); + float32x2_t v2 = vget_high_f32(vTemp); + v1 = vadd_f32(v1, v2); + v1 = vpadd_f32(v1, v1); + return vcombine_f32(v1, v1); +#elif defined(_XM_SSE4_INTRINSICS_) + return _mm_dp_ps(V1, V2, 0xff); +#elif defined(_XM_SSE3_INTRINSICS_) + XMVECTOR vTemp = _mm_mul_ps(V1, V2); + vTemp = _mm_hadd_ps(vTemp, vTemp); + return _mm_hadd_ps(vTemp, vTemp); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vTemp2 = V2; + XMVECTOR vTemp = _mm_mul_ps(V1, vTemp2); + vTemp2 = _mm_shuffle_ps(vTemp2, vTemp, _MM_SHUFFLE(1, 0, 0, 0)); // Copy X to the Z position and Y to the W position + vTemp2 = _mm_add_ps(vTemp2, vTemp); // Add Z = X+Z; W = Y+W; + vTemp = _mm_shuffle_ps(vTemp, vTemp2, _MM_SHUFFLE(0, 3, 0, 0)); // Copy W to the Z position + vTemp = _mm_add_ps(vTemp, vTemp2); // Add Z and W together + return XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(2, 2, 2, 2)); // Splat Z and return +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector4Cross +( + FXMVECTOR V1, + FXMVECTOR V2, + FXMVECTOR V3 +) noexcept +{ + // [ ((v2.z*v3.w-v2.w*v3.z)*v1.y)-((v2.y*v3.w-v2.w*v3.y)*v1.z)+((v2.y*v3.z-v2.z*v3.y)*v1.w), + // ((v2.w*v3.z-v2.z*v3.w)*v1.x)-((v2.w*v3.x-v2.x*v3.w)*v1.z)+((v2.z*v3.x-v2.x*v3.z)*v1.w), + // ((v2.y*v3.w-v2.w*v3.y)*v1.x)-((v2.x*v3.w-v2.w*v3.x)*v1.y)+((v2.x*v3.y-v2.y*v3.x)*v1.w), + // ((v2.z*v3.y-v2.y*v3.z)*v1.x)-((v2.z*v3.x-v2.x*v3.z)*v1.y)+((v2.y*v3.x-v2.x*v3.y)*v1.z) ] + +#if defined(_XM_NO_INTRINSICS_) + + XMVECTORF32 Result = { { { + (((V2.vector4_f32[2] * V3.vector4_f32[3]) - (V2.vector4_f32[3] * V3.vector4_f32[2])) * V1.vector4_f32[1]) - (((V2.vector4_f32[1] * V3.vector4_f32[3]) - (V2.vector4_f32[3] * V3.vector4_f32[1])) * V1.vector4_f32[2]) + (((V2.vector4_f32[1] * V3.vector4_f32[2]) - (V2.vector4_f32[2] * V3.vector4_f32[1])) * V1.vector4_f32[3]), + (((V2.vector4_f32[3] * V3.vector4_f32[2]) - (V2.vector4_f32[2] * V3.vector4_f32[3])) * V1.vector4_f32[0]) - (((V2.vector4_f32[3] * V3.vector4_f32[0]) - (V2.vector4_f32[0] * V3.vector4_f32[3])) * V1.vector4_f32[2]) + (((V2.vector4_f32[2] * V3.vector4_f32[0]) - (V2.vector4_f32[0] * V3.vector4_f32[2])) * V1.vector4_f32[3]), + (((V2.vector4_f32[1] * V3.vector4_f32[3]) - (V2.vector4_f32[3] * V3.vector4_f32[1])) * V1.vector4_f32[0]) - (((V2.vector4_f32[0] * V3.vector4_f32[3]) - (V2.vector4_f32[3] * V3.vector4_f32[0])) * V1.vector4_f32[1]) + (((V2.vector4_f32[0] * V3.vector4_f32[1]) - (V2.vector4_f32[1] * V3.vector4_f32[0])) * V1.vector4_f32[3]), + (((V2.vector4_f32[2] * V3.vector4_f32[1]) - (V2.vector4_f32[1] * V3.vector4_f32[2])) * V1.vector4_f32[0]) - (((V2.vector4_f32[2] * V3.vector4_f32[0]) - (V2.vector4_f32[0] * V3.vector4_f32[2])) * V1.vector4_f32[1]) + (((V2.vector4_f32[1] * V3.vector4_f32[0]) - (V2.vector4_f32[0] * V3.vector4_f32[1])) * V1.vector4_f32[2]), + } } }; + return Result.v; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + const uint32x2_t select = vget_low_u32(g_XMMaskX); + + // Term1: V2zwyz * V3wzwy + const float32x2_t v2xy = vget_low_f32(V2); + const float32x2_t v2zw = vget_high_f32(V2); + const float32x2_t v2yx = vrev64_f32(v2xy); + const float32x2_t v2wz = vrev64_f32(v2zw); + const float32x2_t v2yz = vbsl_f32(select, v2yx, v2wz); + + const float32x2_t v3zw = vget_high_f32(V3); + const float32x2_t v3wz = vrev64_f32(v3zw); + const float32x2_t v3xy = vget_low_f32(V3); + const float32x2_t v3wy = vbsl_f32(select, v3wz, v3xy); + + float32x4_t vTemp1 = vcombine_f32(v2zw, v2yz); + float32x4_t vTemp2 = vcombine_f32(v3wz, v3wy); + XMVECTOR vResult = vmulq_f32(vTemp1, vTemp2); + + // - V2wzwy * V3zwyz + const float32x2_t v2wy = vbsl_f32(select, v2wz, v2xy); + + const float32x2_t v3yx = vrev64_f32(v3xy); + const float32x2_t v3yz = vbsl_f32(select, v3yx, v3wz); + + vTemp1 = vcombine_f32(v2wz, v2wy); + vTemp2 = vcombine_f32(v3zw, v3yz); + vResult = vmlsq_f32(vResult, vTemp1, vTemp2); + + // term1 * V1yxxx + const float32x2_t v1xy = vget_low_f32(V1); + const float32x2_t v1yx = vrev64_f32(v1xy); + + vTemp1 = vcombine_f32(v1yx, vdup_lane_f32(v1yx, 1)); + vResult = vmulq_f32(vResult, vTemp1); + + // Term2: V2ywxz * V3wxwx + const float32x2_t v2yw = vrev64_f32(v2wy); + const float32x2_t v2xz = vbsl_f32(select, v2xy, v2wz); + + const float32x2_t v3wx = vbsl_f32(select, v3wz, v3yx); + + vTemp1 = vcombine_f32(v2yw, v2xz); + vTemp2 = vcombine_f32(v3wx, v3wx); + float32x4_t vTerm = vmulq_f32(vTemp1, vTemp2); + + // - V2wxwx * V3ywxz + const float32x2_t v2wx = vbsl_f32(select, v2wz, v2yx); + + const float32x2_t v3yw = vrev64_f32(v3wy); + const float32x2_t v3xz = vbsl_f32(select, v3xy, v3wz); + + vTemp1 = vcombine_f32(v2wx, v2wx); + vTemp2 = vcombine_f32(v3yw, v3xz); + vTerm = vmlsq_f32(vTerm, vTemp1, vTemp2); + + // vResult - term2 * V1zzyy + const float32x2_t v1zw = vget_high_f32(V1); + + vTemp1 = vcombine_f32(vdup_lane_f32(v1zw, 0), vdup_lane_f32(v1yx, 0)); + vResult = vmlsq_f32(vResult, vTerm, vTemp1); + + // Term3: V2yzxy * V3zxyx + const float32x2_t v3zx = vrev64_f32(v3xz); + + vTemp1 = vcombine_f32(v2yz, v2xy); + vTemp2 = vcombine_f32(v3zx, v3yx); + vTerm = vmulq_f32(vTemp1, vTemp2); + + // - V2zxyx * V3yzxy + const float32x2_t v2zx = vrev64_f32(v2xz); + + vTemp1 = vcombine_f32(v2zx, v2yx); + vTemp2 = vcombine_f32(v3yz, v3xy); + vTerm = vmlsq_f32(vTerm, vTemp1, vTemp2); + + // vResult + term3 * V1wwwz + const float32x2_t v1wz = vrev64_f32(v1zw); + + vTemp1 = vcombine_f32(vdup_lane_f32(v1wz, 0), v1wz); + return vmlaq_f32(vResult, vTerm, vTemp1); +#elif defined(_XM_SSE_INTRINSICS_) + // V2zwyz * V3wzwy + XMVECTOR vResult = XM_PERMUTE_PS(V2, _MM_SHUFFLE(2, 1, 3, 2)); + XMVECTOR vTemp3 = XM_PERMUTE_PS(V3, _MM_SHUFFLE(1, 3, 2, 3)); + vResult = _mm_mul_ps(vResult, vTemp3); + // - V2wzwy * V3zwyz + XMVECTOR vTemp2 = XM_PERMUTE_PS(V2, _MM_SHUFFLE(1, 3, 2, 3)); + vTemp3 = XM_PERMUTE_PS(vTemp3, _MM_SHUFFLE(1, 3, 0, 1)); + vResult = XM_FNMADD_PS(vTemp2, vTemp3, vResult); + // term1 * V1yxxx + XMVECTOR vTemp1 = XM_PERMUTE_PS(V1, _MM_SHUFFLE(0, 0, 0, 1)); + vResult = _mm_mul_ps(vResult, vTemp1); + + // V2ywxz * V3wxwx + vTemp2 = XM_PERMUTE_PS(V2, _MM_SHUFFLE(2, 0, 3, 1)); + vTemp3 = XM_PERMUTE_PS(V3, _MM_SHUFFLE(0, 3, 0, 3)); + vTemp3 = _mm_mul_ps(vTemp3, vTemp2); + // - V2wxwx * V3ywxz + vTemp2 = XM_PERMUTE_PS(vTemp2, _MM_SHUFFLE(2, 1, 2, 1)); + vTemp1 = XM_PERMUTE_PS(V3, _MM_SHUFFLE(2, 0, 3, 1)); + vTemp3 = XM_FNMADD_PS(vTemp2, vTemp1, vTemp3); + // vResult - temp * V1zzyy + vTemp1 = XM_PERMUTE_PS(V1, _MM_SHUFFLE(1, 1, 2, 2)); + vResult = XM_FNMADD_PS(vTemp1, vTemp3, vResult); + + // V2yzxy * V3zxyx + vTemp2 = XM_PERMUTE_PS(V2, _MM_SHUFFLE(1, 0, 2, 1)); + vTemp3 = XM_PERMUTE_PS(V3, _MM_SHUFFLE(0, 1, 0, 2)); + vTemp3 = _mm_mul_ps(vTemp3, vTemp2); + // - V2zxyx * V3yzxy + vTemp2 = XM_PERMUTE_PS(vTemp2, _MM_SHUFFLE(2, 0, 2, 1)); + vTemp1 = XM_PERMUTE_PS(V3, _MM_SHUFFLE(1, 0, 2, 1)); + vTemp3 = XM_FNMADD_PS(vTemp1, vTemp2, vTemp3); + // vResult + term * V1wwwz + vTemp1 = XM_PERMUTE_PS(V1, _MM_SHUFFLE(2, 3, 3, 3)); + vResult = XM_FMADD_PS(vTemp3, vTemp1, vResult); + return vResult; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector4LengthSq(FXMVECTOR V) noexcept +{ + return XMVector4Dot(V, V); +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector4ReciprocalLengthEst(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR Result; + + Result = XMVector4LengthSq(V); + Result = XMVectorReciprocalSqrtEst(Result); + + return Result; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + // Dot4 + float32x4_t vTemp = vmulq_f32(V, V); + float32x2_t v1 = vget_low_f32(vTemp); + float32x2_t v2 = vget_high_f32(vTemp); + v1 = vadd_f32(v1, v2); + v1 = vpadd_f32(v1, v1); + // Reciprocal sqrt (estimate) + v2 = vrsqrte_f32(v1); + return vcombine_f32(v2, v2); +#elif defined(_XM_SSE4_INTRINSICS_) + XMVECTOR vTemp = _mm_dp_ps(V, V, 0xff); + return _mm_rsqrt_ps(vTemp); +#elif defined(_XM_SSE3_INTRINSICS_) + XMVECTOR vLengthSq = _mm_mul_ps(V, V); + vLengthSq = _mm_hadd_ps(vLengthSq, vLengthSq); + vLengthSq = _mm_hadd_ps(vLengthSq, vLengthSq); + vLengthSq = _mm_rsqrt_ps(vLengthSq); + return vLengthSq; +#elif defined(_XM_SSE_INTRINSICS_) + // Perform the dot product on x,y,z and w + XMVECTOR vLengthSq = _mm_mul_ps(V, V); + // vTemp has z and w + XMVECTOR vTemp = XM_PERMUTE_PS(vLengthSq, _MM_SHUFFLE(3, 2, 3, 2)); + // x+z, y+w + vLengthSq = _mm_add_ps(vLengthSq, vTemp); + // x+z,x+z,x+z,y+w + vLengthSq = XM_PERMUTE_PS(vLengthSq, _MM_SHUFFLE(1, 0, 0, 0)); + // ??,??,y+w,y+w + vTemp = _mm_shuffle_ps(vTemp, vLengthSq, _MM_SHUFFLE(3, 3, 0, 0)); + // ??,??,x+z+y+w,?? + vLengthSq = _mm_add_ps(vLengthSq, vTemp); + // Splat the length + vLengthSq = XM_PERMUTE_PS(vLengthSq, _MM_SHUFFLE(2, 2, 2, 2)); + // Get the reciprocal + vLengthSq = _mm_rsqrt_ps(vLengthSq); + return vLengthSq; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector4ReciprocalLength(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR Result; + + Result = XMVector4LengthSq(V); + Result = XMVectorReciprocalSqrt(Result); + + return Result; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + // Dot4 + float32x4_t vTemp = vmulq_f32(V, V); + float32x2_t v1 = vget_low_f32(vTemp); + float32x2_t v2 = vget_high_f32(vTemp); + v1 = vadd_f32(v1, v2); + v1 = vpadd_f32(v1, v1); + // Reciprocal sqrt + float32x2_t S0 = vrsqrte_f32(v1); + float32x2_t P0 = vmul_f32(v1, S0); + float32x2_t R0 = vrsqrts_f32(P0, S0); + float32x2_t S1 = vmul_f32(S0, R0); + float32x2_t P1 = vmul_f32(v1, S1); + float32x2_t R1 = vrsqrts_f32(P1, S1); + float32x2_t Result = vmul_f32(S1, R1); + return vcombine_f32(Result, Result); +#elif defined(_XM_SSE4_INTRINSICS_) + XMVECTOR vTemp = _mm_dp_ps(V, V, 0xff); + XMVECTOR vLengthSq = _mm_sqrt_ps(vTemp); + return _mm_div_ps(g_XMOne, vLengthSq); +#elif defined(_XM_SSE3_INTRINSICS_) + XMVECTOR vLengthSq = _mm_mul_ps(V, V); + vLengthSq = _mm_hadd_ps(vLengthSq, vLengthSq); + vLengthSq = _mm_hadd_ps(vLengthSq, vLengthSq); + vLengthSq = _mm_sqrt_ps(vLengthSq); + vLengthSq = _mm_div_ps(g_XMOne, vLengthSq); + return vLengthSq; +#elif defined(_XM_SSE_INTRINSICS_) + // Perform the dot product on x,y,z and w + XMVECTOR vLengthSq = _mm_mul_ps(V, V); + // vTemp has z and w + XMVECTOR vTemp = XM_PERMUTE_PS(vLengthSq, _MM_SHUFFLE(3, 2, 3, 2)); + // x+z, y+w + vLengthSq = _mm_add_ps(vLengthSq, vTemp); + // x+z,x+z,x+z,y+w + vLengthSq = XM_PERMUTE_PS(vLengthSq, _MM_SHUFFLE(1, 0, 0, 0)); + // ??,??,y+w,y+w + vTemp = _mm_shuffle_ps(vTemp, vLengthSq, _MM_SHUFFLE(3, 3, 0, 0)); + // ??,??,x+z+y+w,?? + vLengthSq = _mm_add_ps(vLengthSq, vTemp); + // Splat the length + vLengthSq = XM_PERMUTE_PS(vLengthSq, _MM_SHUFFLE(2, 2, 2, 2)); + // Get the reciprocal + vLengthSq = _mm_sqrt_ps(vLengthSq); + // Accurate! + vLengthSq = _mm_div_ps(g_XMOne, vLengthSq); + return vLengthSq; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector4LengthEst(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR Result; + + Result = XMVector4LengthSq(V); + Result = XMVectorSqrtEst(Result); + + return Result; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + // Dot4 + float32x4_t vTemp = vmulq_f32(V, V); + float32x2_t v1 = vget_low_f32(vTemp); + float32x2_t v2 = vget_high_f32(vTemp); + v1 = vadd_f32(v1, v2); + v1 = vpadd_f32(v1, v1); + const float32x2_t zero = vdup_n_f32(0); + uint32x2_t VEqualsZero = vceq_f32(v1, zero); + // Sqrt (estimate) + float32x2_t Result = vrsqrte_f32(v1); + Result = vmul_f32(v1, Result); + Result = vbsl_f32(VEqualsZero, zero, Result); + return vcombine_f32(Result, Result); +#elif defined(_XM_SSE4_INTRINSICS_) + XMVECTOR vTemp = _mm_dp_ps(V, V, 0xff); + return _mm_sqrt_ps(vTemp); +#elif defined(_XM_SSE3_INTRINSICS_) + XMVECTOR vLengthSq = _mm_mul_ps(V, V); + vLengthSq = _mm_hadd_ps(vLengthSq, vLengthSq); + vLengthSq = _mm_hadd_ps(vLengthSq, vLengthSq); + vLengthSq = _mm_sqrt_ps(vLengthSq); + return vLengthSq; +#elif defined(_XM_SSE_INTRINSICS_) + // Perform the dot product on x,y,z and w + XMVECTOR vLengthSq = _mm_mul_ps(V, V); + // vTemp has z and w + XMVECTOR vTemp = XM_PERMUTE_PS(vLengthSq, _MM_SHUFFLE(3, 2, 3, 2)); + // x+z, y+w + vLengthSq = _mm_add_ps(vLengthSq, vTemp); + // x+z,x+z,x+z,y+w + vLengthSq = XM_PERMUTE_PS(vLengthSq, _MM_SHUFFLE(1, 0, 0, 0)); + // ??,??,y+w,y+w + vTemp = _mm_shuffle_ps(vTemp, vLengthSq, _MM_SHUFFLE(3, 3, 0, 0)); + // ??,??,x+z+y+w,?? + vLengthSq = _mm_add_ps(vLengthSq, vTemp); + // Splat the length + vLengthSq = XM_PERMUTE_PS(vLengthSq, _MM_SHUFFLE(2, 2, 2, 2)); + // Get the length + vLengthSq = _mm_sqrt_ps(vLengthSq); + return vLengthSq; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector4Length(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR Result; + + Result = XMVector4LengthSq(V); + Result = XMVectorSqrt(Result); + + return Result; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + // Dot4 + float32x4_t vTemp = vmulq_f32(V, V); + float32x2_t v1 = vget_low_f32(vTemp); + float32x2_t v2 = vget_high_f32(vTemp); + v1 = vadd_f32(v1, v2); + v1 = vpadd_f32(v1, v1); + const float32x2_t zero = vdup_n_f32(0); + uint32x2_t VEqualsZero = vceq_f32(v1, zero); + // Sqrt + float32x2_t S0 = vrsqrte_f32(v1); + float32x2_t P0 = vmul_f32(v1, S0); + float32x2_t R0 = vrsqrts_f32(P0, S0); + float32x2_t S1 = vmul_f32(S0, R0); + float32x2_t P1 = vmul_f32(v1, S1); + float32x2_t R1 = vrsqrts_f32(P1, S1); + float32x2_t Result = vmul_f32(S1, R1); + Result = vmul_f32(v1, Result); + Result = vbsl_f32(VEqualsZero, zero, Result); + return vcombine_f32(Result, Result); +#elif defined(_XM_SSE4_INTRINSICS_) + XMVECTOR vTemp = _mm_dp_ps(V, V, 0xff); + return _mm_sqrt_ps(vTemp); +#elif defined(_XM_SSE3_INTRINSICS_) + XMVECTOR vLengthSq = _mm_mul_ps(V, V); + vLengthSq = _mm_hadd_ps(vLengthSq, vLengthSq); + vLengthSq = _mm_hadd_ps(vLengthSq, vLengthSq); + vLengthSq = _mm_sqrt_ps(vLengthSq); + return vLengthSq; +#elif defined(_XM_SSE_INTRINSICS_) + // Perform the dot product on x,y,z and w + XMVECTOR vLengthSq = _mm_mul_ps(V, V); + // vTemp has z and w + XMVECTOR vTemp = XM_PERMUTE_PS(vLengthSq, _MM_SHUFFLE(3, 2, 3, 2)); + // x+z, y+w + vLengthSq = _mm_add_ps(vLengthSq, vTemp); + // x+z,x+z,x+z,y+w + vLengthSq = XM_PERMUTE_PS(vLengthSq, _MM_SHUFFLE(1, 0, 0, 0)); + // ??,??,y+w,y+w + vTemp = _mm_shuffle_ps(vTemp, vLengthSq, _MM_SHUFFLE(3, 3, 0, 0)); + // ??,??,x+z+y+w,?? + vLengthSq = _mm_add_ps(vLengthSq, vTemp); + // Splat the length + vLengthSq = XM_PERMUTE_PS(vLengthSq, _MM_SHUFFLE(2, 2, 2, 2)); + // Get the length + vLengthSq = _mm_sqrt_ps(vLengthSq); + return vLengthSq; +#endif +} + +//------------------------------------------------------------------------------ +// XMVector4NormalizeEst uses a reciprocal estimate and +// returns QNaN on zero and infinite vectors. + +inline XMVECTOR XM_CALLCONV XMVector4NormalizeEst(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR Result; + Result = XMVector4ReciprocalLength(V); + Result = XMVectorMultiply(V, Result); + return Result; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + // Dot4 + float32x4_t vTemp = vmulq_f32(V, V); + float32x2_t v1 = vget_low_f32(vTemp); + float32x2_t v2 = vget_high_f32(vTemp); + v1 = vadd_f32(v1, v2); + v1 = vpadd_f32(v1, v1); + // Reciprocal sqrt (estimate) + v2 = vrsqrte_f32(v1); + // Normalize + return vmulq_f32(V, vcombine_f32(v2, v2)); +#elif defined(_XM_SSE4_INTRINSICS_) + XMVECTOR vTemp = _mm_dp_ps(V, V, 0xff); + XMVECTOR vResult = _mm_rsqrt_ps(vTemp); + return _mm_mul_ps(vResult, V); +#elif defined(_XM_SSE3_INTRINSICS_) + XMVECTOR vDot = _mm_mul_ps(V, V); + vDot = _mm_hadd_ps(vDot, vDot); + vDot = _mm_hadd_ps(vDot, vDot); + vDot = _mm_rsqrt_ps(vDot); + vDot = _mm_mul_ps(vDot, V); + return vDot; +#elif defined(_XM_SSE_INTRINSICS_) + // Perform the dot product on x,y,z and w + XMVECTOR vLengthSq = _mm_mul_ps(V, V); + // vTemp has z and w + XMVECTOR vTemp = XM_PERMUTE_PS(vLengthSq, _MM_SHUFFLE(3, 2, 3, 2)); + // x+z, y+w + vLengthSq = _mm_add_ps(vLengthSq, vTemp); + // x+z,x+z,x+z,y+w + vLengthSq = XM_PERMUTE_PS(vLengthSq, _MM_SHUFFLE(1, 0, 0, 0)); + // ??,??,y+w,y+w + vTemp = _mm_shuffle_ps(vTemp, vLengthSq, _MM_SHUFFLE(3, 3, 0, 0)); + // ??,??,x+z+y+w,?? + vLengthSq = _mm_add_ps(vLengthSq, vTemp); + // Splat the length + vLengthSq = XM_PERMUTE_PS(vLengthSq, _MM_SHUFFLE(2, 2, 2, 2)); + // Get the reciprocal + XMVECTOR vResult = _mm_rsqrt_ps(vLengthSq); + // Reciprocal mul to perform the normalization + vResult = _mm_mul_ps(vResult, V); + return vResult; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector4Normalize(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + float fLength; + XMVECTOR vResult; + + vResult = XMVector4Length(V); + fLength = vResult.vector4_f32[0]; + + // Prevent divide by zero + if (fLength > 0) + { + fLength = 1.0f / fLength; + } + + vResult.vector4_f32[0] = V.vector4_f32[0] * fLength; + vResult.vector4_f32[1] = V.vector4_f32[1] * fLength; + vResult.vector4_f32[2] = V.vector4_f32[2] * fLength; + vResult.vector4_f32[3] = V.vector4_f32[3] * fLength; + return vResult; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + // Dot4 + float32x4_t vTemp = vmulq_f32(V, V); + float32x2_t v1 = vget_low_f32(vTemp); + float32x2_t v2 = vget_high_f32(vTemp); + v1 = vadd_f32(v1, v2); + v1 = vpadd_f32(v1, v1); + uint32x2_t VEqualsZero = vceq_f32(v1, vdup_n_f32(0)); + uint32x2_t VEqualsInf = vceq_f32(v1, vget_low_f32(g_XMInfinity)); + // Reciprocal sqrt (2 iterations of Newton-Raphson) + float32x2_t S0 = vrsqrte_f32(v1); + float32x2_t P0 = vmul_f32(v1, S0); + float32x2_t R0 = vrsqrts_f32(P0, S0); + float32x2_t S1 = vmul_f32(S0, R0); + float32x2_t P1 = vmul_f32(v1, S1); + float32x2_t R1 = vrsqrts_f32(P1, S1); + v2 = vmul_f32(S1, R1); + // Normalize + XMVECTOR vResult = vmulq_f32(V, vcombine_f32(v2, v2)); + vResult = vbslq_f32(vcombine_u32(VEqualsZero, VEqualsZero), vdupq_n_f32(0), vResult); + return vbslq_f32(vcombine_u32(VEqualsInf, VEqualsInf), g_XMQNaN, vResult); +#elif defined(_XM_SSE4_INTRINSICS_) + XMVECTOR vLengthSq = _mm_dp_ps(V, V, 0xff); + // Prepare for the division + XMVECTOR vResult = _mm_sqrt_ps(vLengthSq); + // Create zero with a single instruction + XMVECTOR vZeroMask = _mm_setzero_ps(); + // Test for a divide by zero (Must be FP to detect -0.0) + vZeroMask = _mm_cmpneq_ps(vZeroMask, vResult); + // Failsafe on zero (Or epsilon) length planes + // If the length is infinity, set the elements to zero + vLengthSq = _mm_cmpneq_ps(vLengthSq, g_XMInfinity); + // Divide to perform the normalization + vResult = _mm_div_ps(V, vResult); + // Any that are infinity, set to zero + vResult = _mm_and_ps(vResult, vZeroMask); + // Select qnan or result based on infinite length + XMVECTOR vTemp1 = _mm_andnot_ps(vLengthSq, g_XMQNaN); + XMVECTOR vTemp2 = _mm_and_ps(vResult, vLengthSq); + vResult = _mm_or_ps(vTemp1, vTemp2); + return vResult; +#elif defined(_XM_SSE3_INTRINSICS_) + // Perform the dot product on x,y,z and w + XMVECTOR vLengthSq = _mm_mul_ps(V, V); + vLengthSq = _mm_hadd_ps(vLengthSq, vLengthSq); + vLengthSq = _mm_hadd_ps(vLengthSq, vLengthSq); + // Prepare for the division + XMVECTOR vResult = _mm_sqrt_ps(vLengthSq); + // Create zero with a single instruction + XMVECTOR vZeroMask = _mm_setzero_ps(); + // Test for a divide by zero (Must be FP to detect -0.0) + vZeroMask = _mm_cmpneq_ps(vZeroMask, vResult); + // Failsafe on zero (Or epsilon) length planes + // If the length is infinity, set the elements to zero + vLengthSq = _mm_cmpneq_ps(vLengthSq, g_XMInfinity); + // Divide to perform the normalization + vResult = _mm_div_ps(V, vResult); + // Any that are infinity, set to zero + vResult = _mm_and_ps(vResult, vZeroMask); + // Select qnan or result based on infinite length + XMVECTOR vTemp1 = _mm_andnot_ps(vLengthSq, g_XMQNaN); + XMVECTOR vTemp2 = _mm_and_ps(vResult, vLengthSq); + vResult = _mm_or_ps(vTemp1, vTemp2); + return vResult; +#elif defined(_XM_SSE_INTRINSICS_) + // Perform the dot product on x,y,z and w + XMVECTOR vLengthSq = _mm_mul_ps(V, V); + // vTemp has z and w + XMVECTOR vTemp = XM_PERMUTE_PS(vLengthSq, _MM_SHUFFLE(3, 2, 3, 2)); + // x+z, y+w + vLengthSq = _mm_add_ps(vLengthSq, vTemp); + // x+z,x+z,x+z,y+w + vLengthSq = XM_PERMUTE_PS(vLengthSq, _MM_SHUFFLE(1, 0, 0, 0)); + // ??,??,y+w,y+w + vTemp = _mm_shuffle_ps(vTemp, vLengthSq, _MM_SHUFFLE(3, 3, 0, 0)); + // ??,??,x+z+y+w,?? + vLengthSq = _mm_add_ps(vLengthSq, vTemp); + // Splat the length + vLengthSq = XM_PERMUTE_PS(vLengthSq, _MM_SHUFFLE(2, 2, 2, 2)); + // Prepare for the division + XMVECTOR vResult = _mm_sqrt_ps(vLengthSq); + // Create zero with a single instruction + XMVECTOR vZeroMask = _mm_setzero_ps(); + // Test for a divide by zero (Must be FP to detect -0.0) + vZeroMask = _mm_cmpneq_ps(vZeroMask, vResult); + // Failsafe on zero (Or epsilon) length planes + // If the length is infinity, set the elements to zero + vLengthSq = _mm_cmpneq_ps(vLengthSq, g_XMInfinity); + // Divide to perform the normalization + vResult = _mm_div_ps(V, vResult); + // Any that are infinity, set to zero + vResult = _mm_and_ps(vResult, vZeroMask); + // Select qnan or result based on infinite length + XMVECTOR vTemp1 = _mm_andnot_ps(vLengthSq, g_XMQNaN); + XMVECTOR vTemp2 = _mm_and_ps(vResult, vLengthSq); + vResult = _mm_or_ps(vTemp1, vTemp2); + return vResult; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector4ClampLength +( + FXMVECTOR V, + float LengthMin, + float LengthMax +) noexcept +{ + XMVECTOR ClampMax = XMVectorReplicate(LengthMax); + XMVECTOR ClampMin = XMVectorReplicate(LengthMin); + + return XMVector4ClampLengthV(V, ClampMin, ClampMax); +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector4ClampLengthV +( + FXMVECTOR V, + FXMVECTOR LengthMin, + FXMVECTOR LengthMax +) noexcept +{ + assert((XMVectorGetY(LengthMin) == XMVectorGetX(LengthMin)) && (XMVectorGetZ(LengthMin) == XMVectorGetX(LengthMin)) && (XMVectorGetW(LengthMin) == XMVectorGetX(LengthMin))); + assert((XMVectorGetY(LengthMax) == XMVectorGetX(LengthMax)) && (XMVectorGetZ(LengthMax) == XMVectorGetX(LengthMax)) && (XMVectorGetW(LengthMax) == XMVectorGetX(LengthMax))); + assert(XMVector4GreaterOrEqual(LengthMin, XMVectorZero())); + assert(XMVector4GreaterOrEqual(LengthMax, XMVectorZero())); + assert(XMVector4GreaterOrEqual(LengthMax, LengthMin)); + + XMVECTOR LengthSq = XMVector4LengthSq(V); + + const XMVECTOR Zero = XMVectorZero(); + + XMVECTOR RcpLength = XMVectorReciprocalSqrt(LengthSq); + + XMVECTOR InfiniteLength = XMVectorEqualInt(LengthSq, g_XMInfinity.v); + XMVECTOR ZeroLength = XMVectorEqual(LengthSq, Zero); + + XMVECTOR Normal = XMVectorMultiply(V, RcpLength); + + XMVECTOR Length = XMVectorMultiply(LengthSq, RcpLength); + + XMVECTOR Select = XMVectorEqualInt(InfiniteLength, ZeroLength); + Length = XMVectorSelect(LengthSq, Length, Select); + Normal = XMVectorSelect(LengthSq, Normal, Select); + + XMVECTOR ControlMax = XMVectorGreater(Length, LengthMax); + XMVECTOR ControlMin = XMVectorLess(Length, LengthMin); + + XMVECTOR ClampLength = XMVectorSelect(Length, LengthMax, ControlMax); + ClampLength = XMVectorSelect(ClampLength, LengthMin, ControlMin); + + XMVECTOR Result = XMVectorMultiply(Normal, ClampLength); + + // Preserve the original vector (with no precision loss) if the length falls within the given range + XMVECTOR Control = XMVectorEqualInt(ControlMax, ControlMin); + Result = XMVectorSelect(Result, V, Control); + + return Result; +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector4Reflect +( + FXMVECTOR Incident, + FXMVECTOR Normal +) noexcept +{ + // Result = Incident - (2 * dot(Incident, Normal)) * Normal + + XMVECTOR Result = XMVector4Dot(Incident, Normal); + Result = XMVectorAdd(Result, Result); + Result = XMVectorNegativeMultiplySubtract(Result, Normal, Incident); + + return Result; +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector4Refract +( + FXMVECTOR Incident, + FXMVECTOR Normal, + float RefractionIndex +) noexcept +{ + XMVECTOR Index = XMVectorReplicate(RefractionIndex); + return XMVector4RefractV(Incident, Normal, Index); +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector4RefractV +( + FXMVECTOR Incident, + FXMVECTOR Normal, + FXMVECTOR RefractionIndex +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR IDotN; + XMVECTOR R; + const XMVECTOR Zero = XMVectorZero(); + + // Result = RefractionIndex * Incident - Normal * (RefractionIndex * dot(Incident, Normal) + + // sqrt(1 - RefractionIndex * RefractionIndex * (1 - dot(Incident, Normal) * dot(Incident, Normal)))) + + IDotN = XMVector4Dot(Incident, Normal); + + // R = 1.0f - RefractionIndex * RefractionIndex * (1.0f - IDotN * IDotN) + R = XMVectorNegativeMultiplySubtract(IDotN, IDotN, g_XMOne.v); + R = XMVectorMultiply(R, RefractionIndex); + R = XMVectorNegativeMultiplySubtract(R, RefractionIndex, g_XMOne.v); + + if (XMVector4LessOrEqual(R, Zero)) + { + // Total internal reflection + return Zero; + } + else + { + XMVECTOR Result; + + // R = RefractionIndex * IDotN + sqrt(R) + R = XMVectorSqrt(R); + R = XMVectorMultiplyAdd(RefractionIndex, IDotN, R); + + // Result = RefractionIndex * Incident - Normal * R + Result = XMVectorMultiply(RefractionIndex, Incident); + Result = XMVectorNegativeMultiplySubtract(Normal, R, Result); + + return Result; + } + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + XMVECTOR IDotN = XMVector4Dot(Incident, Normal); + + // R = 1.0f - RefractionIndex * RefractionIndex * (1.0f - IDotN * IDotN) + float32x4_t R = vmlsq_f32(g_XMOne, IDotN, IDotN); + R = vmulq_f32(R, RefractionIndex); + R = vmlsq_f32(g_XMOne, R, RefractionIndex); + + uint32x4_t isrzero = vcleq_f32(R, g_XMZero); + uint8x8x2_t vTemp = vzip_u8(vget_low_u8(vreinterpretq_u8_u32(isrzero)), vget_high_u8(vreinterpretq_u8_u32(isrzero))); + uint16x4x2_t vTemp2 = vzip_u16(vreinterpret_u16_u8(vTemp.val[0]), vreinterpret_u16_u8(vTemp.val[1])); + + float32x4_t vResult; + if (vget_lane_u32(vreinterpret_u32_u16(vTemp2.val[1]), 1) == 0xFFFFFFFFU) + { + // Total internal reflection + vResult = g_XMZero; + } + else + { + // Sqrt(R) + float32x4_t S0 = vrsqrteq_f32(R); + float32x4_t P0 = vmulq_f32(R, S0); + float32x4_t R0 = vrsqrtsq_f32(P0, S0); + float32x4_t S1 = vmulq_f32(S0, R0); + float32x4_t P1 = vmulq_f32(R, S1); + float32x4_t R1 = vrsqrtsq_f32(P1, S1); + float32x4_t S2 = vmulq_f32(S1, R1); + R = vmulq_f32(R, S2); + // R = RefractionIndex * IDotN + sqrt(R) + R = vmlaq_f32(R, RefractionIndex, IDotN); + // Result = RefractionIndex * Incident - Normal * R + vResult = vmulq_f32(RefractionIndex, Incident); + vResult = vmlsq_f32(vResult, R, Normal); + } + return vResult; +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR IDotN = XMVector4Dot(Incident, Normal); + + // R = 1.0f - RefractionIndex * RefractionIndex * (1.0f - IDotN * IDotN) + XMVECTOR R = XM_FNMADD_PS(IDotN, IDotN, g_XMOne); + XMVECTOR R2 = _mm_mul_ps(RefractionIndex, RefractionIndex); + R = XM_FNMADD_PS(R, R2, g_XMOne); + + XMVECTOR vResult = _mm_cmple_ps(R, g_XMZero); + if (_mm_movemask_ps(vResult) == 0x0f) + { + // Total internal reflection + vResult = g_XMZero; + } + else + { + // R = RefractionIndex * IDotN + sqrt(R) + R = _mm_sqrt_ps(R); + R = XM_FMADD_PS(RefractionIndex, IDotN, R); + // Result = RefractionIndex * Incident - Normal * R + vResult = _mm_mul_ps(RefractionIndex, Incident); + vResult = XM_FNMADD_PS(R, Normal, vResult); + } + return vResult; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector4Orthogonal(FXMVECTOR V) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + XMVECTORF32 Result = { { { + V.vector4_f32[2], + V.vector4_f32[3], + -V.vector4_f32[0], + -V.vector4_f32[1] + } } }; + return Result.v; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + static const XMVECTORF32 Negate = { { { 1.f, 1.f, -1.f, -1.f } } }; + + float32x4_t Result = vcombine_f32(vget_high_f32(V), vget_low_f32(V)); + return vmulq_f32(Result, Negate); +#elif defined(_XM_SSE_INTRINSICS_) + static const XMVECTORF32 FlipZW = { { { 1.0f, 1.0f, -1.0f, -1.0f } } }; + XMVECTOR vResult = XM_PERMUTE_PS(V, _MM_SHUFFLE(1, 0, 3, 2)); + vResult = _mm_mul_ps(vResult, FlipZW); + return vResult; +#endif +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector4AngleBetweenNormalsEst +( + FXMVECTOR N1, + FXMVECTOR N2 +) noexcept +{ + XMVECTOR Result = XMVector4Dot(N1, N2); + Result = XMVectorClamp(Result, g_XMNegativeOne.v, g_XMOne.v); + Result = XMVectorACosEst(Result); + return Result; +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector4AngleBetweenNormals +( + FXMVECTOR N1, + FXMVECTOR N2 +) noexcept +{ + XMVECTOR Result = XMVector4Dot(N1, N2); + Result = XMVectorClamp(Result, g_XMNegativeOne.v, g_XMOne.v); + Result = XMVectorACos(Result); + return Result; +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector4AngleBetweenVectors +( + FXMVECTOR V1, + FXMVECTOR V2 +) noexcept +{ + XMVECTOR L1 = XMVector4ReciprocalLength(V1); + XMVECTOR L2 = XMVector4ReciprocalLength(V2); + + XMVECTOR Dot = XMVector4Dot(V1, V2); + + L1 = XMVectorMultiply(L1, L2); + + XMVECTOR CosAngle = XMVectorMultiply(Dot, L1); + CosAngle = XMVectorClamp(CosAngle, g_XMNegativeOne.v, g_XMOne.v); + + return XMVectorACos(CosAngle); +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV XMVector4Transform +( + FXMVECTOR V, + FXMMATRIX M +) noexcept +{ +#if defined(_XM_NO_INTRINSICS_) + + float fX = (M.m[0][0] * V.vector4_f32[0]) + (M.m[1][0] * V.vector4_f32[1]) + (M.m[2][0] * V.vector4_f32[2]) + (M.m[3][0] * V.vector4_f32[3]); + float fY = (M.m[0][1] * V.vector4_f32[0]) + (M.m[1][1] * V.vector4_f32[1]) + (M.m[2][1] * V.vector4_f32[2]) + (M.m[3][1] * V.vector4_f32[3]); + float fZ = (M.m[0][2] * V.vector4_f32[0]) + (M.m[1][2] * V.vector4_f32[1]) + (M.m[2][2] * V.vector4_f32[2]) + (M.m[3][2] * V.vector4_f32[3]); + float fW = (M.m[0][3] * V.vector4_f32[0]) + (M.m[1][3] * V.vector4_f32[1]) + (M.m[2][3] * V.vector4_f32[2]) + (M.m[3][3] * V.vector4_f32[3]); + XMVECTORF32 vResult = { { { fX, fY, fZ, fW } } }; + return vResult.v; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x2_t VL = vget_low_f32(V); + XMVECTOR vResult = vmulq_lane_f32(M.r[0], VL, 0); // X + vResult = vmlaq_lane_f32(vResult, M.r[1], VL, 1); // Y + float32x2_t VH = vget_high_f32(V); + vResult = vmlaq_lane_f32(vResult, M.r[2], VH, 0); // Z + return vmlaq_lane_f32(vResult, M.r[3], VH, 1); // W +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vResult = XM_PERMUTE_PS(V, _MM_SHUFFLE(3, 3, 3, 3)); // W + vResult = _mm_mul_ps(vResult, M.r[3]); + XMVECTOR vTemp = XM_PERMUTE_PS(V, _MM_SHUFFLE(2, 2, 2, 2)); // Z + vResult = XM_FMADD_PS(vTemp, M.r[2], vResult); + vTemp = XM_PERMUTE_PS(V, _MM_SHUFFLE(1, 1, 1, 1)); // Y + vResult = XM_FMADD_PS(vTemp, M.r[1], vResult); + vTemp = XM_PERMUTE_PS(V, _MM_SHUFFLE(0, 0, 0, 0)); // X + vResult = XM_FMADD_PS(vTemp, M.r[0], vResult); + return vResult; +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMFLOAT4* XM_CALLCONV XMVector4TransformStream +( + XMFLOAT4* pOutputStream, + size_t OutputStride, + const XMFLOAT4* pInputStream, + size_t InputStride, + size_t VectorCount, + FXMMATRIX M +) noexcept +{ + assert(pOutputStream != nullptr); + assert(pInputStream != nullptr); + + assert(InputStride >= sizeof(XMFLOAT4)); + _Analysis_assume_(InputStride >= sizeof(XMFLOAT4)); + + assert(OutputStride >= sizeof(XMFLOAT4)); + _Analysis_assume_(OutputStride >= sizeof(XMFLOAT4)); + +#if defined(_XM_NO_INTRINSICS_) + + auto pInputVector = reinterpret_cast(pInputStream); + auto pOutputVector = reinterpret_cast(pOutputStream); + + const XMVECTOR row0 = M.r[0]; + const XMVECTOR row1 = M.r[1]; + const XMVECTOR row2 = M.r[2]; + const XMVECTOR row3 = M.r[3]; + + for (size_t i = 0; i < VectorCount; i++) + { + XMVECTOR V = XMLoadFloat4(reinterpret_cast(pInputVector)); + XMVECTOR W = XMVectorSplatW(V); + XMVECTOR Z = XMVectorSplatZ(V); + XMVECTOR Y = XMVectorSplatY(V); + XMVECTOR X = XMVectorSplatX(V); + + XMVECTOR Result = XMVectorMultiply(W, row3); + Result = XMVectorMultiplyAdd(Z, row2, Result); + Result = XMVectorMultiplyAdd(Y, row1, Result); + Result = XMVectorMultiplyAdd(X, row0, Result); + + #ifdef _PREFAST_ + #pragma prefast(push) + #pragma prefast(disable : 26015, "PREfast noise: Esp:1307" ) + #endif + + XMStoreFloat4(reinterpret_cast(pOutputVector), Result); + + #ifdef _PREFAST_ + #pragma prefast(pop) + #endif + + pInputVector += InputStride; + pOutputVector += OutputStride; + } + + return pOutputStream; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + auto pInputVector = reinterpret_cast(pInputStream); + auto pOutputVector = reinterpret_cast(pOutputStream); + + const XMVECTOR row0 = M.r[0]; + const XMVECTOR row1 = M.r[1]; + const XMVECTOR row2 = M.r[2]; + const XMVECTOR row3 = M.r[3]; + + size_t i = 0; + size_t four = VectorCount >> 2; + if (four > 0) + { + if ((InputStride == sizeof(XMFLOAT4)) && (OutputStride == sizeof(XMFLOAT4))) + { + for (size_t j = 0; j < four; ++j) + { + float32x4x4_t V = vld4q_f32(reinterpret_cast(pInputVector)); + pInputVector += sizeof(XMFLOAT4) * 4; + + float32x2_t r = vget_low_f32(row0); + XMVECTOR vResult0 = vmulq_lane_f32(V.val[0], r, 0); // Ax + XMVECTOR vResult1 = vmulq_lane_f32(V.val[0], r, 1); // Bx + + XM_PREFETCH(pInputVector); + + r = vget_high_f32(row0); + XMVECTOR vResult2 = vmulq_lane_f32(V.val[0], r, 0); // Cx + XMVECTOR vResult3 = vmulq_lane_f32(V.val[0], r, 1); // Dx + + XM_PREFETCH(pInputVector + XM_CACHE_LINE_SIZE); + + r = vget_low_f32(row1); + vResult0 = vmlaq_lane_f32(vResult0, V.val[1], r, 0); // Ax+Ey + vResult1 = vmlaq_lane_f32(vResult1, V.val[1], r, 1); // Bx+Fy + + XM_PREFETCH(pInputVector + (XM_CACHE_LINE_SIZE * 2)); + + r = vget_high_f32(row1); + vResult2 = vmlaq_lane_f32(vResult2, V.val[1], r, 0); // Cx+Gy + vResult3 = vmlaq_lane_f32(vResult3, V.val[1], r, 1); // Dx+Hy + + XM_PREFETCH(pInputVector + (XM_CACHE_LINE_SIZE * 3)); + + r = vget_low_f32(row2); + vResult0 = vmlaq_lane_f32(vResult0, V.val[2], r, 0); // Ax+Ey+Iz + vResult1 = vmlaq_lane_f32(vResult1, V.val[2], r, 1); // Bx+Fy+Jz + + XM_PREFETCH(pInputVector + (XM_CACHE_LINE_SIZE * 4)); + + r = vget_high_f32(row2); + vResult2 = vmlaq_lane_f32(vResult2, V.val[2], r, 0); // Cx+Gy+Kz + vResult3 = vmlaq_lane_f32(vResult3, V.val[2], r, 1); // Dx+Hy+Lz + + XM_PREFETCH(pInputVector + (XM_CACHE_LINE_SIZE * 5)); + + r = vget_low_f32(row3); + vResult0 = vmlaq_lane_f32(vResult0, V.val[3], r, 0); // Ax+Ey+Iz+Mw + vResult1 = vmlaq_lane_f32(vResult1, V.val[3], r, 1); // Bx+Fy+Jz+Nw + + XM_PREFETCH(pInputVector + (XM_CACHE_LINE_SIZE * 6)); + + r = vget_high_f32(row3); + vResult2 = vmlaq_lane_f32(vResult2, V.val[3], r, 0); // Cx+Gy+Kz+Ow + vResult3 = vmlaq_lane_f32(vResult3, V.val[3], r, 1); // Dx+Hy+Lz+Pw + + XM_PREFETCH(pInputVector + (XM_CACHE_LINE_SIZE * 7)); + + V.val[0] = vResult0; + V.val[1] = vResult1; + V.val[2] = vResult2; + V.val[3] = vResult3; + + vst4q_f32(reinterpret_cast(pOutputVector), V); + pOutputVector += sizeof(XMFLOAT4) * 4; + + i += 4; + } + } + } + + for (; i < VectorCount; i++) + { + XMVECTOR V = vld1q_f32(reinterpret_cast(pInputVector)); + pInputVector += InputStride; + + float32x2_t VL = vget_low_f32(V); + XMVECTOR vResult = vmulq_lane_f32(row0, VL, 0); // X + vResult = vmlaq_lane_f32(vResult, row1, VL, 1); // Y + float32x2_t VH = vget_high_f32(V); + vResult = vmlaq_lane_f32(vResult, row2, VH, 0); // Z + vResult = vmlaq_lane_f32(vResult, row3, VH, 1); // W + + vst1q_f32(reinterpret_cast(pOutputVector), vResult); + pOutputVector += OutputStride; + } + + return pOutputStream; +#elif defined(_XM_AVX2_INTRINSICS_) + auto pInputVector = reinterpret_cast(pInputStream); + auto pOutputVector = reinterpret_cast(pOutputStream); + + size_t i = 0; + size_t two = VectorCount >> 1; + if (two > 0) + { + __m256 row0 = _mm256_broadcast_ps(&M.r[0]); + __m256 row1 = _mm256_broadcast_ps(&M.r[1]); + __m256 row2 = _mm256_broadcast_ps(&M.r[2]); + __m256 row3 = _mm256_broadcast_ps(&M.r[3]); + + if (InputStride == sizeof(XMFLOAT4)) + { + if (OutputStride == sizeof(XMFLOAT4)) + { + if (!(reinterpret_cast(pOutputStream) & 0x1F)) + { + // Packed input, aligned & packed output + for (size_t j = 0; j < two; ++j) + { + __m256 VV = _mm256_loadu_ps(reinterpret_cast(pInputVector)); + pInputVector += sizeof(XMFLOAT4) * 2; + + __m256 vTempX = _mm256_shuffle_ps(VV, VV, _MM_SHUFFLE(0, 0, 0, 0)); + __m256 vTempY = _mm256_shuffle_ps(VV, VV, _MM_SHUFFLE(1, 1, 1, 1)); + __m256 vTempZ = _mm256_shuffle_ps(VV, VV, _MM_SHUFFLE(2, 2, 2, 2)); + __m256 vTempW = _mm256_shuffle_ps(VV, VV, _MM_SHUFFLE(3, 3, 3, 3)); + + vTempX = _mm256_mul_ps(vTempX, row0); + vTempY = _mm256_mul_ps(vTempY, row1); + vTempZ = _mm256_fmadd_ps(vTempZ, row2, vTempX); + vTempW = _mm256_fmadd_ps(vTempW, row3, vTempY); + vTempX = _mm256_add_ps(vTempZ, vTempW); + + XM256_STREAM_PS(reinterpret_cast(pOutputVector), vTempX); + pOutputVector += sizeof(XMFLOAT4) * 2; + + i += 2; + } + } + else + { + // Packed input, packed output + for (size_t j = 0; j < two; ++j) + { + __m256 VV = _mm256_loadu_ps(reinterpret_cast(pInputVector)); + pInputVector += sizeof(XMFLOAT4) * 2; + + __m256 vTempX = _mm256_shuffle_ps(VV, VV, _MM_SHUFFLE(0, 0, 0, 0)); + __m256 vTempY = _mm256_shuffle_ps(VV, VV, _MM_SHUFFLE(1, 1, 1, 1)); + __m256 vTempZ = _mm256_shuffle_ps(VV, VV, _MM_SHUFFLE(2, 2, 2, 2)); + __m256 vTempW = _mm256_shuffle_ps(VV, VV, _MM_SHUFFLE(3, 3, 3, 3)); + + vTempX = _mm256_mul_ps(vTempX, row0); + vTempY = _mm256_mul_ps(vTempY, row1); + vTempZ = _mm256_fmadd_ps(vTempZ, row2, vTempX); + vTempW = _mm256_fmadd_ps(vTempW, row3, vTempY); + vTempX = _mm256_add_ps(vTempZ, vTempW); + + _mm256_storeu_ps(reinterpret_cast(pOutputVector), vTempX); + pOutputVector += sizeof(XMFLOAT4) * 2; + + i += 2; + } + } + } + else + { + // Packed input, unpacked output + for (size_t j = 0; j < two; ++j) + { + __m256 VV = _mm256_loadu_ps(reinterpret_cast(pInputVector)); + pInputVector += sizeof(XMFLOAT4) * 2; + + __m256 vTempX = _mm256_shuffle_ps(VV, VV, _MM_SHUFFLE(0, 0, 0, 0)); + __m256 vTempY = _mm256_shuffle_ps(VV, VV, _MM_SHUFFLE(1, 1, 1, 1)); + __m256 vTempZ = _mm256_shuffle_ps(VV, VV, _MM_SHUFFLE(2, 2, 2, 2)); + __m256 vTempW = _mm256_shuffle_ps(VV, VV, _MM_SHUFFLE(3, 3, 3, 3)); + + vTempX = _mm256_mul_ps(vTempX, row0); + vTempY = _mm256_mul_ps(vTempY, row1); + vTempZ = _mm256_fmadd_ps(vTempZ, row2, vTempX); + vTempW = _mm256_fmadd_ps(vTempW, row3, vTempY); + vTempX = _mm256_add_ps(vTempZ, vTempW); + + _mm_storeu_ps(reinterpret_cast(pOutputVector), _mm256_castps256_ps128(vTempX)); + pOutputVector += OutputStride; + + _mm_storeu_ps(reinterpret_cast(pOutputVector), _mm256_extractf128_ps(vTempX, 1)); + pOutputVector += OutputStride; + i += 2; + } + } + } + } + + if (i < VectorCount) + { + const XMVECTOR row0 = M.r[0]; + const XMVECTOR row1 = M.r[1]; + const XMVECTOR row2 = M.r[2]; + const XMVECTOR row3 = M.r[3]; + + for (; i < VectorCount; i++) + { + __m128 V = _mm_loadu_ps(reinterpret_cast(pInputVector)); + pInputVector += InputStride; + + XMVECTOR vTempX = XM_PERMUTE_PS(V, _MM_SHUFFLE(0, 0, 0, 0)); + XMVECTOR vTempY = XM_PERMUTE_PS(V, _MM_SHUFFLE(1, 1, 1, 1)); + XMVECTOR vTempZ = XM_PERMUTE_PS(V, _MM_SHUFFLE(2, 2, 2, 2)); + XMVECTOR vTempW = XM_PERMUTE_PS(V, _MM_SHUFFLE(3, 3, 3, 3)); + + vTempX = _mm_mul_ps(vTempX, row0); + vTempY = _mm_mul_ps(vTempY, row1); + vTempZ = XM_FMADD_PS(vTempZ, row2, vTempX); + vTempW = XM_FMADD_PS(vTempW, row3, vTempY); + vTempX = _mm_add_ps(vTempZ, vTempW); + + _mm_storeu_ps(reinterpret_cast(pOutputVector), vTempX); + pOutputVector += OutputStride; + } + } + + XM_SFENCE(); + + return pOutputStream; +#elif defined(_XM_SSE_INTRINSICS_) + auto pInputVector = reinterpret_cast(pInputStream); + auto pOutputVector = reinterpret_cast(pOutputStream); + + const XMVECTOR row0 = M.r[0]; + const XMVECTOR row1 = M.r[1]; + const XMVECTOR row2 = M.r[2]; + const XMVECTOR row3 = M.r[3]; + + if (!(reinterpret_cast(pOutputStream) & 0xF) && !(OutputStride & 0xF)) + { + if (!(reinterpret_cast(pInputStream) & 0xF) && !(InputStride & 0xF)) + { + // Aligned input, aligned output + for (size_t i = 0; i < VectorCount; i++) + { + __m128 V = _mm_load_ps(reinterpret_cast(pInputVector)); + pInputVector += InputStride; + + XMVECTOR vTempX = XM_PERMUTE_PS(V, _MM_SHUFFLE(0, 0, 0, 0)); + XMVECTOR vTempY = XM_PERMUTE_PS(V, _MM_SHUFFLE(1, 1, 1, 1)); + XMVECTOR vTempZ = XM_PERMUTE_PS(V, _MM_SHUFFLE(2, 2, 2, 2)); + XMVECTOR vTempW = XM_PERMUTE_PS(V, _MM_SHUFFLE(3, 3, 3, 3)); + + vTempX = _mm_mul_ps(vTempX, row0); + vTempY = _mm_mul_ps(vTempY, row1); + vTempZ = XM_FMADD_PS(vTempZ, row2, vTempX); + vTempW = XM_FMADD_PS(vTempW, row3, vTempY); + vTempX = _mm_add_ps(vTempZ, vTempW); + + XM_STREAM_PS(reinterpret_cast(pOutputVector), vTempX); + pOutputVector += OutputStride; + } + } + else + { + // Unaligned input, aligned output + for (size_t i = 0; i < VectorCount; i++) + { + __m128 V = _mm_loadu_ps(reinterpret_cast(pInputVector)); + pInputVector += InputStride; + + XMVECTOR vTempX = XM_PERMUTE_PS(V, _MM_SHUFFLE(0, 0, 0, 0)); + XMVECTOR vTempY = XM_PERMUTE_PS(V, _MM_SHUFFLE(1, 1, 1, 1)); + XMVECTOR vTempZ = XM_PERMUTE_PS(V, _MM_SHUFFLE(2, 2, 2, 2)); + XMVECTOR vTempW = XM_PERMUTE_PS(V, _MM_SHUFFLE(3, 3, 3, 3)); + + vTempX = _mm_mul_ps(vTempX, row0); + vTempY = _mm_mul_ps(vTempY, row1); + vTempZ = XM_FMADD_PS(vTempZ, row2, vTempX); + vTempW = XM_FMADD_PS(vTempW, row3, vTempY); + vTempX = _mm_add_ps(vTempZ, vTempW); + + XM_STREAM_PS(reinterpret_cast(pOutputVector), vTempX); + pOutputVector += OutputStride; + } + } + } + else + { + if (!(reinterpret_cast(pInputStream) & 0xF) && !(InputStride & 0xF)) + { + // Aligned input, unaligned output + for (size_t i = 0; i < VectorCount; i++) + { + __m128 V = _mm_load_ps(reinterpret_cast(pInputVector)); + pInputVector += InputStride; + + XMVECTOR vTempX = XM_PERMUTE_PS(V, _MM_SHUFFLE(0, 0, 0, 0)); + XMVECTOR vTempY = XM_PERMUTE_PS(V, _MM_SHUFFLE(1, 1, 1, 1)); + XMVECTOR vTempZ = XM_PERMUTE_PS(V, _MM_SHUFFLE(2, 2, 2, 2)); + XMVECTOR vTempW = XM_PERMUTE_PS(V, _MM_SHUFFLE(3, 3, 3, 3)); + + vTempX = _mm_mul_ps(vTempX, row0); + vTempY = _mm_mul_ps(vTempY, row1); + vTempZ = XM_FMADD_PS(vTempZ, row2, vTempX); + vTempW = XM_FMADD_PS(vTempW, row3, vTempY); + vTempX = _mm_add_ps(vTempZ, vTempW); + + _mm_storeu_ps(reinterpret_cast(pOutputVector), vTempX); + pOutputVector += OutputStride; + } + } + else + { + // Unaligned input, unaligned output + for (size_t i = 0; i < VectorCount; i++) + { + __m128 V = _mm_loadu_ps(reinterpret_cast(pInputVector)); + pInputVector += InputStride; + + XMVECTOR vTempX = XM_PERMUTE_PS(V, _MM_SHUFFLE(0, 0, 0, 0)); + XMVECTOR vTempY = XM_PERMUTE_PS(V, _MM_SHUFFLE(1, 1, 1, 1)); + XMVECTOR vTempZ = XM_PERMUTE_PS(V, _MM_SHUFFLE(2, 2, 2, 2)); + XMVECTOR vTempW = XM_PERMUTE_PS(V, _MM_SHUFFLE(3, 3, 3, 3)); + + vTempX = _mm_mul_ps(vTempX, row0); + vTempY = _mm_mul_ps(vTempY, row1); + vTempZ = XM_FMADD_PS(vTempZ, row2, vTempX); + vTempW = XM_FMADD_PS(vTempW, row3, vTempY); + vTempX = _mm_add_ps(vTempZ, vTempW); + + _mm_storeu_ps(reinterpret_cast(pOutputVector), vTempX); + pOutputVector += OutputStride; + } + } + } + + XM_SFENCE(); + + return pOutputStream; +#endif +} + +/**************************************************************************** + * + * XMVECTOR operators + * + ****************************************************************************/ + +#ifndef _XM_NO_XMVECTOR_OVERLOADS_ + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV operator+ (FXMVECTOR V) noexcept +{ + return V; +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV operator- (FXMVECTOR V) noexcept +{ + return XMVectorNegate(V); +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR& XM_CALLCONV operator+= +( + XMVECTOR& V1, + FXMVECTOR V2 + ) noexcept +{ + V1 = XMVectorAdd(V1, V2); + return V1; +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR& XM_CALLCONV operator-= +( + XMVECTOR& V1, + FXMVECTOR V2 + ) noexcept +{ + V1 = XMVectorSubtract(V1, V2); + return V1; +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR& XM_CALLCONV operator*= +( + XMVECTOR& V1, + FXMVECTOR V2 + ) noexcept +{ + V1 = XMVectorMultiply(V1, V2); + return V1; +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR& XM_CALLCONV operator/= +( + XMVECTOR& V1, + FXMVECTOR V2 + ) noexcept +{ + V1 = XMVectorDivide(V1, V2); + return V1; +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR& operator*= +( + XMVECTOR& V, + const float S + ) noexcept +{ + V = XMVectorScale(V, S); + return V; +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR& operator/= +( + XMVECTOR& V, + const float S + ) noexcept +{ + XMVECTOR vS = XMVectorReplicate(S); + V = XMVectorDivide(V, vS); + return V; +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV operator+ +( + FXMVECTOR V1, + FXMVECTOR V2 + ) noexcept +{ + return XMVectorAdd(V1, V2); +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV operator- +( + FXMVECTOR V1, + FXMVECTOR V2 + ) noexcept +{ + return XMVectorSubtract(V1, V2); +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV operator* +( + FXMVECTOR V1, + FXMVECTOR V2 + ) noexcept +{ + return XMVectorMultiply(V1, V2); +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV operator/ +( + FXMVECTOR V1, + FXMVECTOR V2 + ) noexcept +{ + return XMVectorDivide(V1, V2); +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV operator* +( + FXMVECTOR V, + const float S + ) noexcept +{ + return XMVectorScale(V, S); +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV operator/ +( + FXMVECTOR V, + const float S + ) noexcept +{ + XMVECTOR vS = XMVectorReplicate(S); + return XMVectorDivide(V, vS); +} + +//------------------------------------------------------------------------------ + +inline XMVECTOR XM_CALLCONV operator* +( + float S, + FXMVECTOR V + ) noexcept +{ + return XMVectorScale(V, S); +} + +#endif /* !_XM_NO_XMVECTOR_OVERLOADS_ */ + +#if defined(_XM_NO_INTRINSICS_) +#undef XMISNAN +#undef XMISINF +#endif + +#if defined(_XM_SSE_INTRINSICS_) +#undef XM3UNPACK3INTO4 +#undef XM3PACK4INTO3 +#endif + diff --git a/Minecraft.Client/Windows64/Libs/4JLibs/impls/Windows_Libs/Render/vendor/DirectXMath/DirectXPackedVector.h b/Minecraft.Client/Windows64/Libs/4JLibs/impls/Windows_Libs/Render/vendor/DirectXMath/DirectXPackedVector.h new file mode 100644 index 00000000..8af22683 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/4JLibs/impls/Windows_Libs/Render/vendor/DirectXMath/DirectXPackedVector.h @@ -0,0 +1,1252 @@ +//------------------------------------------------------------------------------------- +// DirectXPackedVector.h -- SIMD C++ Math library +// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +// +// https://go.microsoft.com/fwlink/?LinkID=615560 +//------------------------------------------------------------------------------------- + +#pragma once + +#include "DirectXMath.h" + +namespace DirectX +{ + + namespace PackedVector + { + #ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable:4201 4365 4324 4996) + // C4201: nonstandard extension used + // C4365: Off by default noise + // C4324: alignment padding warnings + // C4996: deprecation warnings + #endif + + #ifdef __clang__ + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wgnu-anonymous-struct" + #pragma clang diagnostic ignored "-Wnested-anon-types" + #endif + + //------------------------------------------------------------------------------ + // ARGB Color; 8-8-8-8 bit unsigned normalized integer components packed into + // a 32 bit integer. The normalized color is packed into 32 bits using 8 bit + // unsigned, normalized integers for the alpha, red, green, and blue components. + // The alpha component is stored in the most significant bits and the blue + // component in the least significant bits (A8R8G8B8): + // [32] aaaaaaaa rrrrrrrr gggggggg bbbbbbbb [0] + struct XMCOLOR + { + union + { + struct + { + uint8_t b; // Blue: 0/255 to 255/255 + uint8_t g; // Green: 0/255 to 255/255 + uint8_t r; // Red: 0/255 to 255/255 + uint8_t a; // Alpha: 0/255 to 255/255 + }; + uint32_t c; + }; + + XMCOLOR() = default; + + XMCOLOR(const XMCOLOR&) = default; + XMCOLOR& operator=(const XMCOLOR&) = default; + + XMCOLOR(XMCOLOR&&) = default; + XMCOLOR& operator=(XMCOLOR&&) = default; + + constexpr XMCOLOR(uint32_t Color) noexcept : c(Color) {} + XMCOLOR(float _r, float _g, float _b, float _a) noexcept; + explicit XMCOLOR(_In_reads_(4) const float* pArray) noexcept; + + operator uint32_t () const noexcept { return c; } + + XMCOLOR& operator= (const uint32_t Color) noexcept { c = Color; return *this; } + }; + + //------------------------------------------------------------------------------ + // 16 bit floating point number consisting of a sign bit, a 5 bit biased + // exponent, and a 10 bit mantissa + using HALF = uint16_t; + + //------------------------------------------------------------------------------ + // 2D Vector; 16 bit floating point components + struct XMHALF2 + { + union + { + struct + { + HALF x; + HALF y; + }; + uint32_t v; + }; + + XMHALF2() = default; + + XMHALF2(const XMHALF2&) = default; + XMHALF2& operator=(const XMHALF2&) = default; + + XMHALF2(XMHALF2&&) = default; + XMHALF2& operator=(XMHALF2&&) = default; + + explicit constexpr XMHALF2(uint32_t Packed) noexcept : v(Packed) {} + constexpr XMHALF2(HALF _x, HALF _y) noexcept : x(_x), y(_y) {} + explicit XMHALF2(_In_reads_(2) const HALF* pArray) noexcept : x(pArray[0]), y(pArray[1]) {} + XMHALF2(float _x, float _y) noexcept; + explicit XMHALF2(_In_reads_(2) const float* pArray) noexcept; + + XMHALF2& operator= (uint32_t Packed) noexcept { v = Packed; return *this; } + }; + + //------------------------------------------------------------------------------ + // 2D Vector; 16 bit signed normalized integer components + struct XMSHORTN2 + { + union + { + struct + { + int16_t x; + int16_t y; + }; + uint32_t v; + }; + + XMSHORTN2() = default; + + XMSHORTN2(const XMSHORTN2&) = default; + XMSHORTN2& operator=(const XMSHORTN2&) = default; + + XMSHORTN2(XMSHORTN2&&) = default; + XMSHORTN2& operator=(XMSHORTN2&&) = default; + + explicit constexpr XMSHORTN2(uint32_t Packed) noexcept : v(Packed) {} + constexpr XMSHORTN2(int16_t _x, int16_t _y) noexcept : x(_x), y(_y) {} + explicit XMSHORTN2(_In_reads_(2) const int16_t* pArray) noexcept : x(pArray[0]), y(pArray[1]) {} + XMSHORTN2(float _x, float _y) noexcept; + explicit XMSHORTN2(_In_reads_(2) const float* pArray) noexcept; + + XMSHORTN2& operator= (uint32_t Packed) noexcept { v = Packed; return *this; } + }; + + // 2D Vector; 16 bit signed integer components + struct XMSHORT2 + { + union + { + struct + { + int16_t x; + int16_t y; + }; + uint32_t v; + }; + + XMSHORT2() = default; + + XMSHORT2(const XMSHORT2&) = default; + XMSHORT2& operator=(const XMSHORT2&) = default; + + XMSHORT2(XMSHORT2&&) = default; + XMSHORT2& operator=(XMSHORT2&&) = default; + + explicit constexpr XMSHORT2(uint32_t Packed) noexcept : v(Packed) {} + constexpr XMSHORT2(int16_t _x, int16_t _y) noexcept : x(_x), y(_y) {} + explicit XMSHORT2(_In_reads_(2) const int16_t* pArray) noexcept : x(pArray[0]), y(pArray[1]) {} + XMSHORT2(float _x, float _y) noexcept; + explicit XMSHORT2(_In_reads_(2) const float* pArray) noexcept; + + XMSHORT2& operator= (uint32_t Packed) noexcept { v = Packed; return *this; } + }; + + // 2D Vector; 16 bit unsigned normalized integer components + struct XMUSHORTN2 + { + union + { + struct + { + uint16_t x; + uint16_t y; + }; + uint32_t v; + }; + + XMUSHORTN2() = default; + + XMUSHORTN2(const XMUSHORTN2&) = default; + XMUSHORTN2& operator=(const XMUSHORTN2&) = default; + + XMUSHORTN2(XMUSHORTN2&&) = default; + XMUSHORTN2& operator=(XMUSHORTN2&&) = default; + + explicit constexpr XMUSHORTN2(uint32_t Packed) noexcept : v(Packed) {} + constexpr XMUSHORTN2(uint16_t _x, uint16_t _y) noexcept : x(_x), y(_y) {} + explicit XMUSHORTN2(_In_reads_(2) const uint16_t* pArray) noexcept : x(pArray[0]), y(pArray[1]) {} + XMUSHORTN2(float _x, float _y) noexcept; + explicit XMUSHORTN2(_In_reads_(2) const float* pArray) noexcept; + + XMUSHORTN2& operator= (uint32_t Packed) noexcept { v = Packed; return *this; } + }; + + // 2D Vector; 16 bit unsigned integer components + struct XMUSHORT2 + { + union + { + struct + { + uint16_t x; + uint16_t y; + }; + uint32_t v; + }; + + XMUSHORT2() = default; + + XMUSHORT2(const XMUSHORT2&) = default; + XMUSHORT2& operator=(const XMUSHORT2&) = default; + + XMUSHORT2(XMUSHORT2&&) = default; + XMUSHORT2& operator=(XMUSHORT2&&) = default; + + explicit constexpr XMUSHORT2(uint32_t Packed) noexcept : v(Packed) {} + constexpr XMUSHORT2(uint16_t _x, uint16_t _y) noexcept : x(_x), y(_y) {} + explicit XMUSHORT2(_In_reads_(2) const uint16_t* pArray) noexcept : x(pArray[0]), y(pArray[1]) {} + XMUSHORT2(float _x, float _y) noexcept; + explicit XMUSHORT2(_In_reads_(2) const float* pArray) noexcept; + + XMUSHORT2& operator= (uint32_t Packed) noexcept { v = Packed; return *this; } + }; + + //------------------------------------------------------------------------------ + // 2D Vector; 8 bit signed normalized integer components + struct XMBYTEN2 + { + union + { + struct + { + int8_t x; + int8_t y; + }; + uint16_t v; + }; + + XMBYTEN2() = default; + + XMBYTEN2(const XMBYTEN2&) = default; + XMBYTEN2& operator=(const XMBYTEN2&) = default; + + XMBYTEN2(XMBYTEN2&&) = default; + XMBYTEN2& operator=(XMBYTEN2&&) = default; + + explicit constexpr XMBYTEN2(uint16_t Packed) noexcept : v(Packed) {} + constexpr XMBYTEN2(int8_t _x, int8_t _y) noexcept : x(_x), y(_y) {} + explicit XMBYTEN2(_In_reads_(2) const int8_t* pArray) noexcept : x(pArray[0]), y(pArray[1]) {} + XMBYTEN2(float _x, float _y) noexcept; + explicit XMBYTEN2(_In_reads_(2) const float* pArray) noexcept; + + XMBYTEN2& operator= (uint16_t Packed) noexcept { v = Packed; return *this; } + }; + + // 2D Vector; 8 bit signed integer components + struct XMBYTE2 + { + union + { + struct + { + int8_t x; + int8_t y; + }; + uint16_t v; + }; + + XMBYTE2() = default; + + XMBYTE2(const XMBYTE2&) = default; + XMBYTE2& operator=(const XMBYTE2&) = default; + + XMBYTE2(XMBYTE2&&) = default; + XMBYTE2& operator=(XMBYTE2&&) = default; + + explicit constexpr XMBYTE2(uint16_t Packed) noexcept : v(Packed) {} + constexpr XMBYTE2(int8_t _x, int8_t _y) noexcept : x(_x), y(_y) {} + explicit XMBYTE2(_In_reads_(2) const int8_t* pArray) noexcept : x(pArray[0]), y(pArray[1]) {} + XMBYTE2(float _x, float _y) noexcept; + explicit XMBYTE2(_In_reads_(2) const float* pArray) noexcept; + + XMBYTE2& operator= (uint16_t Packed) noexcept { v = Packed; return *this; } + }; + + // 2D Vector; 8 bit unsigned normalized integer components + struct XMUBYTEN2 + { + union + { + struct + { + uint8_t x; + uint8_t y; + }; + uint16_t v; + }; + + XMUBYTEN2() = default; + + XMUBYTEN2(const XMUBYTEN2&) = default; + XMUBYTEN2& operator=(const XMUBYTEN2&) = default; + + XMUBYTEN2(XMUBYTEN2&&) = default; + XMUBYTEN2& operator=(XMUBYTEN2&&) = default; + + explicit constexpr XMUBYTEN2(uint16_t Packed) noexcept : v(Packed) {} + constexpr XMUBYTEN2(uint8_t _x, uint8_t _y) noexcept : x(_x), y(_y) {} + explicit XMUBYTEN2(_In_reads_(2) const uint8_t* pArray) noexcept : x(pArray[0]), y(pArray[1]) {} + XMUBYTEN2(float _x, float _y) noexcept; + explicit XMUBYTEN2(_In_reads_(2) const float* pArray) noexcept; + + XMUBYTEN2& operator= (uint16_t Packed) noexcept { v = Packed; return *this; } + }; + + // 2D Vector; 8 bit unsigned integer components + struct XMUBYTE2 + { + union + { + struct + { + uint8_t x; + uint8_t y; + }; + uint16_t v; + }; + + XMUBYTE2() = default; + + XMUBYTE2(const XMUBYTE2&) = default; + XMUBYTE2& operator=(const XMUBYTE2&) = default; + + XMUBYTE2(XMUBYTE2&&) = default; + XMUBYTE2& operator=(XMUBYTE2&&) = default; + + explicit constexpr XMUBYTE2(uint16_t Packed) noexcept : v(Packed) {} + constexpr XMUBYTE2(uint8_t _x, uint8_t _y) noexcept : x(_x), y(_y) {} + explicit XMUBYTE2(_In_reads_(2) const uint8_t* pArray) noexcept : x(pArray[0]), y(pArray[1]) {} + XMUBYTE2(float _x, float _y) noexcept; + explicit XMUBYTE2(_In_reads_(2) const float* pArray) noexcept; + + XMUBYTE2& operator= (uint16_t Packed) noexcept { v = Packed; return *this; } + }; + + //------------------------------------------------------------------------------ + // 3D vector: 5/6/5 unsigned integer components + struct XMU565 + { + union + { + struct + { + uint16_t x : 5; // 0 to 31 + uint16_t y : 6; // 0 to 63 + uint16_t z : 5; // 0 to 31 + }; + uint16_t v; + }; + + XMU565() = default; + + XMU565(const XMU565&) = default; + XMU565& operator=(const XMU565&) = default; + + XMU565(XMU565&&) = default; + XMU565& operator=(XMU565&&) = default; + + explicit constexpr XMU565(uint16_t Packed) noexcept : v(Packed) {} + constexpr XMU565(uint8_t _x, uint8_t _y, uint8_t _z) noexcept : x(_x), y(_y), z(_z) {} + explicit XMU565(_In_reads_(3) const uint8_t* pArray) noexcept : x(pArray[0]), y(pArray[1]), z(pArray[2]) {} + XMU565(float _x, float _y, float _z) noexcept; + explicit XMU565(_In_reads_(3) const float* pArray) noexcept; + + operator uint16_t () const noexcept { return v; } + + XMU565& operator= (uint16_t Packed) noexcept { v = Packed; return *this; } + }; + + //------------------------------------------------------------------------------ + // 3D vector: 11/11/10 floating-point components + // The 3D vector is packed into 32 bits as follows: a 5-bit biased exponent + // and 6-bit mantissa for x component, a 5-bit biased exponent and + // 6-bit mantissa for y component, a 5-bit biased exponent and a 5-bit + // mantissa for z. The z component is stored in the most significant bits + // and the x component in the least significant bits. No sign bits so + // all partial-precision numbers are positive. + // (Z10Y11X11): [32] ZZZZZzzz zzzYYYYY yyyyyyXX XXXxxxxx [0] + struct XMFLOAT3PK + { + union + { + struct + { + uint32_t xm : 6; // x-mantissa + uint32_t xe : 5; // x-exponent + uint32_t ym : 6; // y-mantissa + uint32_t ye : 5; // y-exponent + uint32_t zm : 5; // z-mantissa + uint32_t ze : 5; // z-exponent + }; + uint32_t v; + }; + + XMFLOAT3PK() = default; + + XMFLOAT3PK(const XMFLOAT3PK&) = default; + XMFLOAT3PK& operator=(const XMFLOAT3PK&) = default; + + XMFLOAT3PK(XMFLOAT3PK&&) = default; + XMFLOAT3PK& operator=(XMFLOAT3PK&&) = default; + + explicit constexpr XMFLOAT3PK(uint32_t Packed) noexcept : v(Packed) {} + XMFLOAT3PK(float _x, float _y, float _z) noexcept; + explicit XMFLOAT3PK(_In_reads_(3) const float* pArray) noexcept; + + operator uint32_t () const noexcept { return v; } + + XMFLOAT3PK& operator= (uint32_t Packed) noexcept { v = Packed; return *this; } + }; + + //------------------------------------------------------------------------------ + // 3D vector: 9/9/9 floating-point components with shared 5-bit exponent + // The 3D vector is packed into 32 bits as follows: a 5-bit biased exponent + // with 9-bit mantissa for the x, y, and z component. The shared exponent + // is stored in the most significant bits and the x component mantissa is in + // the least significant bits. No sign bits so all partial-precision numbers + // are positive. + // (E5Z9Y9X9): [32] EEEEEzzz zzzzzzyy yyyyyyyx xxxxxxxx [0] + struct XMFLOAT3SE + { + union + { + struct + { + uint32_t xm : 9; // x-mantissa + uint32_t ym : 9; // y-mantissa + uint32_t zm : 9; // z-mantissa + uint32_t e : 5; // shared exponent + }; + uint32_t v; + }; + + XMFLOAT3SE() = default; + + XMFLOAT3SE(const XMFLOAT3SE&) = default; + XMFLOAT3SE& operator=(const XMFLOAT3SE&) = default; + + XMFLOAT3SE(XMFLOAT3SE&&) = default; + XMFLOAT3SE& operator=(XMFLOAT3SE&&) = default; + + explicit constexpr XMFLOAT3SE(uint32_t Packed) noexcept : v(Packed) {} + XMFLOAT3SE(float _x, float _y, float _z) noexcept; + explicit XMFLOAT3SE(_In_reads_(3) const float* pArray) noexcept; + + operator uint32_t () const noexcept { return v; } + + XMFLOAT3SE& operator= (uint32_t Packed) noexcept { v = Packed; return *this; } + }; + + //------------------------------------------------------------------------------ + // 4D Vector; 16 bit floating point components + struct XMHALF4 + { + union + { + struct + { + HALF x; + HALF y; + HALF z; + HALF w; + }; + uint64_t v; + }; + + XMHALF4() = default; + + XMHALF4(const XMHALF4&) = default; + XMHALF4& operator=(const XMHALF4&) = default; + + XMHALF4(XMHALF4&&) = default; + XMHALF4& operator=(XMHALF4&&) = default; + + explicit constexpr XMHALF4(uint64_t Packed) noexcept : v(Packed) {} + constexpr XMHALF4(HALF _x, HALF _y, HALF _z, HALF _w) noexcept : x(_x), y(_y), z(_z), w(_w) {} + explicit XMHALF4(_In_reads_(4) const HALF* pArray) noexcept : x(pArray[0]), y(pArray[1]), z(pArray[2]), w(pArray[3]) {} + XMHALF4(float _x, float _y, float _z, float _w) noexcept; + explicit XMHALF4(_In_reads_(4) const float* pArray) noexcept; + + XMHALF4& operator= (uint64_t Packed) noexcept { v = Packed; return *this; } + }; + + //------------------------------------------------------------------------------ + // 4D Vector; 16 bit signed normalized integer components + struct XMSHORTN4 + { + union + { + struct + { + int16_t x; + int16_t y; + int16_t z; + int16_t w; + }; + uint64_t v; + }; + + XMSHORTN4() = default; + + XMSHORTN4(const XMSHORTN4&) = default; + XMSHORTN4& operator=(const XMSHORTN4&) = default; + + XMSHORTN4(XMSHORTN4&&) = default; + XMSHORTN4& operator=(XMSHORTN4&&) = default; + + explicit constexpr XMSHORTN4(uint64_t Packed) noexcept : v(Packed) {} + constexpr XMSHORTN4(int16_t _x, int16_t _y, int16_t _z, int16_t _w) noexcept : x(_x), y(_y), z(_z), w(_w) {} + explicit XMSHORTN4(_In_reads_(4) const int16_t* pArray) noexcept : x(pArray[0]), y(pArray[1]), z(pArray[2]), w(pArray[3]) {} + XMSHORTN4(float _x, float _y, float _z, float _w) noexcept; + explicit XMSHORTN4(_In_reads_(4) const float* pArray) noexcept; + + XMSHORTN4& operator= (uint64_t Packed) noexcept { v = Packed; return *this; } + }; + + // 4D Vector; 16 bit signed integer components + struct XMSHORT4 + { + union + { + struct + { + int16_t x; + int16_t y; + int16_t z; + int16_t w; + }; + uint64_t v; + }; + + XMSHORT4() = default; + + XMSHORT4(const XMSHORT4&) = default; + XMSHORT4& operator=(const XMSHORT4&) = default; + + XMSHORT4(XMSHORT4&&) = default; + XMSHORT4& operator=(XMSHORT4&&) = default; + + explicit constexpr XMSHORT4(uint64_t Packed) noexcept : v(Packed) {} + constexpr XMSHORT4(int16_t _x, int16_t _y, int16_t _z, int16_t _w) noexcept : x(_x), y(_y), z(_z), w(_w) {} + explicit XMSHORT4(_In_reads_(4) const int16_t* pArray) noexcept : x(pArray[0]), y(pArray[1]), z(pArray[2]), w(pArray[3]) {} + XMSHORT4(float _x, float _y, float _z, float _w) noexcept; + explicit XMSHORT4(_In_reads_(4) const float* pArray) noexcept; + + XMSHORT4& operator= (uint64_t Packed) noexcept { v = Packed; return *this; } + }; + + // 4D Vector; 16 bit unsigned normalized integer components + struct XMUSHORTN4 + { + union + { + struct + { + uint16_t x; + uint16_t y; + uint16_t z; + uint16_t w; + }; + uint64_t v; + }; + + XMUSHORTN4() = default; + + XMUSHORTN4(const XMUSHORTN4&) = default; + XMUSHORTN4& operator=(const XMUSHORTN4&) = default; + + XMUSHORTN4(XMUSHORTN4&&) = default; + XMUSHORTN4& operator=(XMUSHORTN4&&) = default; + + explicit constexpr XMUSHORTN4(uint64_t Packed) noexcept : v(Packed) {} + constexpr XMUSHORTN4(uint16_t _x, uint16_t _y, uint16_t _z, uint16_t _w) noexcept : x(_x), y(_y), z(_z), w(_w) {} + explicit XMUSHORTN4(_In_reads_(4) const uint16_t* pArray) noexcept : x(pArray[0]), y(pArray[1]), z(pArray[2]), w(pArray[3]) {} + XMUSHORTN4(float _x, float _y, float _z, float _w) noexcept; + explicit XMUSHORTN4(_In_reads_(4) const float* pArray) noexcept; + + XMUSHORTN4& operator= (uint64_t Packed) noexcept { v = Packed; return *this; } + }; + + // 4D Vector; 16 bit unsigned integer components + struct XMUSHORT4 + { + union + { + struct + { + uint16_t x; + uint16_t y; + uint16_t z; + uint16_t w; + }; + uint64_t v; + }; + + XMUSHORT4() = default; + + XMUSHORT4(const XMUSHORT4&) = default; + XMUSHORT4& operator=(const XMUSHORT4&) = default; + + XMUSHORT4(XMUSHORT4&&) = default; + XMUSHORT4& operator=(XMUSHORT4&&) = default; + + explicit constexpr XMUSHORT4(uint64_t Packed) noexcept : v(Packed) {} + constexpr XMUSHORT4(uint16_t _x, uint16_t _y, uint16_t _z, uint16_t _w) noexcept : x(_x), y(_y), z(_z), w(_w) {} + explicit XMUSHORT4(_In_reads_(4) const uint16_t* pArray) noexcept : x(pArray[0]), y(pArray[1]), z(pArray[2]), w(pArray[3]) {} + XMUSHORT4(float _x, float _y, float _z, float _w) noexcept; + explicit XMUSHORT4(_In_reads_(4) const float* pArray) noexcept; + + XMUSHORT4& operator= (uint32_t Packed) noexcept { v = Packed; return *this; } + }; + + //------------------------------------------------------------------------------ + // 4D Vector; 10-10-10-2 bit normalized components packed into a 32 bit integer + // The normalized 4D Vector is packed into 32 bits as follows: a 2 bit unsigned, + // normalized integer for the w component and 10 bit signed, normalized + // integers for the z, y, and x components. The w component is stored in the + // most significant bits and the x component in the least significant bits + // (W2Z10Y10X10): [32] wwzzzzzz zzzzyyyy yyyyyyxx xxxxxxxx [0] + struct XMXDECN4 + { + union + { + struct + { + int32_t x : 10; // -511/511 to 511/511 + int32_t y : 10; // -511/511 to 511/511 + int32_t z : 10; // -511/511 to 511/511 + uint32_t w : 2; // 0/3 to 3/3 + }; + uint32_t v; + }; + + XMXDECN4() = default; + + XMXDECN4(const XMXDECN4&) = default; + XMXDECN4& operator=(const XMXDECN4&) = default; + + XMXDECN4(XMXDECN4&&) = default; + XMXDECN4& operator=(XMXDECN4&&) = default; + + explicit constexpr XMXDECN4(uint32_t Packed) : v(Packed) {} + XMXDECN4(float _x, float _y, float _z, float _w) noexcept; + explicit XMXDECN4(_In_reads_(4) const float* pArray) noexcept; + + operator uint32_t () const noexcept { return v; } + + XMXDECN4& operator= (uint32_t Packed) noexcept { v = Packed; return *this; } + }; + + // 4D Vector; 10-10-10-2 bit components packed into a 32 bit integer + // The normalized 4D Vector is packed into 32 bits as follows: a 2 bit unsigned + // integer for the w component and 10 bit signed integers for the + // z, y, and x components. The w component is stored in the + // most significant bits and the x component in the least significant bits + // (W2Z10Y10X10): [32] wwzzzzzz zzzzyyyy yyyyyyxx xxxxxxxx [0] + struct XM_DEPRECATED XMXDEC4 + { + union + { + struct + { + int32_t x : 10; // -511 to 511 + int32_t y : 10; // -511 to 511 + int32_t z : 10; // -511 to 511 + uint32_t w : 2; // 0 to 3 + }; + uint32_t v; + }; + + XMXDEC4() = default; + + XMXDEC4(const XMXDEC4&) = default; + XMXDEC4& operator=(const XMXDEC4&) = default; + + XMXDEC4(XMXDEC4&&) = default; + XMXDEC4& operator=(XMXDEC4&&) = default; + + explicit constexpr XMXDEC4(uint32_t Packed) noexcept : v(Packed) {} + XMXDEC4(float _x, float _y, float _z, float _w) noexcept; + explicit XMXDEC4(_In_reads_(4) const float* pArray) noexcept; + + operator uint32_t () const noexcept { return v; } + + XMXDEC4& operator= (uint32_t Packed) noexcept { v = Packed; return *this; } + }; + + // 4D Vector; 10-10-10-2 bit normalized components packed into a 32 bit integer + // The normalized 4D Vector is packed into 32 bits as follows: a 2 bit signed, + // normalized integer for the w component and 10 bit signed, normalized + // integers for the z, y, and x components. The w component is stored in the + // most significant bits and the x component in the least significant bits + // (W2Z10Y10X10): [32] wwzzzzzz zzzzyyyy yyyyyyxx xxxxxxxx [0] + struct XM_DEPRECATED XMDECN4 + { + union + { + struct + { + int32_t x : 10; // -511/511 to 511/511 + int32_t y : 10; // -511/511 to 511/511 + int32_t z : 10; // -511/511 to 511/511 + int32_t w : 2; // -1/1 to 1/1 + }; + uint32_t v; + }; + + XMDECN4() = default; + + XMDECN4(const XMDECN4&) = default; + XMDECN4& operator=(const XMDECN4&) = default; + + XMDECN4(XMDECN4&&) = default; + XMDECN4& operator=(XMDECN4&&) = default; + + explicit constexpr XMDECN4(uint32_t Packed) noexcept : v(Packed) {} + XMDECN4(float _x, float _y, float _z, float _w) noexcept; + explicit XMDECN4(_In_reads_(4) const float* pArray) noexcept; + + operator uint32_t () const noexcept { return v; } + + XMDECN4& operator= (uint32_t Packed) noexcept { v = Packed; return *this; } + }; + + // 4D Vector; 10-10-10-2 bit components packed into a 32 bit integer + // The 4D Vector is packed into 32 bits as follows: a 2 bit signed, + // integer for the w component and 10 bit signed integers for the + // z, y, and x components. The w component is stored in the + // most significant bits and the x component in the least significant bits + // (W2Z10Y10X10): [32] wwzzzzzz zzzzyyyy yyyyyyxx xxxxxxxx [0] + struct XM_DEPRECATED XMDEC4 + { + union + { + struct + { + int32_t x : 10; // -511 to 511 + int32_t y : 10; // -511 to 511 + int32_t z : 10; // -511 to 511 + int32_t w : 2; // -1 to 1 + }; + uint32_t v; + }; + + XMDEC4() = default; + + XMDEC4(const XMDEC4&) = default; + XMDEC4& operator=(const XMDEC4&) = default; + + XMDEC4(XMDEC4&&) = default; + XMDEC4& operator=(XMDEC4&&) = default; + + explicit constexpr XMDEC4(uint32_t Packed) noexcept : v(Packed) {} + XMDEC4(float _x, float _y, float _z, float _w) noexcept; + explicit XMDEC4(_In_reads_(4) const float* pArray) noexcept; + + operator uint32_t () const noexcept { return v; } + + XMDEC4& operator= (uint32_t Packed) noexcept { v = Packed; return *this; } + }; + + // 4D Vector; 10-10-10-2 bit normalized components packed into a 32 bit integer + // The normalized 4D Vector is packed into 32 bits as follows: a 2 bit unsigned, + // normalized integer for the w component and 10 bit unsigned, normalized + // integers for the z, y, and x components. The w component is stored in the + // most significant bits and the x component in the least significant bits + // (W2Z10Y10X10): [32] wwzzzzzz zzzzyyyy yyyyyyxx xxxxxxxx [0] + struct XMUDECN4 + { + union + { + struct + { + uint32_t x : 10; // 0/1023 to 1023/1023 + uint32_t y : 10; // 0/1023 to 1023/1023 + uint32_t z : 10; // 0/1023 to 1023/1023 + uint32_t w : 2; // 0/3 to 3/3 + }; + uint32_t v; + }; + + XMUDECN4() = default; + + XMUDECN4(const XMUDECN4&) = default; + XMUDECN4& operator=(const XMUDECN4&) = default; + + XMUDECN4(XMUDECN4&&) = default; + XMUDECN4& operator=(XMUDECN4&&) = default; + + explicit constexpr XMUDECN4(uint32_t Packed) noexcept : v(Packed) {} + XMUDECN4(float _x, float _y, float _z, float _w) noexcept; + explicit XMUDECN4(_In_reads_(4) const float* pArray) noexcept; + + operator uint32_t () const noexcept { return v; } + + XMUDECN4& operator= (uint32_t Packed) noexcept { v = Packed; return *this; } + }; + + // 4D Vector; 10-10-10-2 bit components packed into a 32 bit integer + // The 4D Vector is packed into 32 bits as follows: a 2 bit unsigned, + // integer for the w component and 10 bit unsigned integers + // for the z, y, and x components. The w component is stored in the + // most significant bits and the x component in the least significant bits + // (W2Z10Y10X10): [32] wwzzzzzz zzzzyyyy yyyyyyxx xxxxxxxx [0] + struct XMUDEC4 + { + union + { + struct + { + uint32_t x : 10; // 0 to 1023 + uint32_t y : 10; // 0 to 1023 + uint32_t z : 10; // 0 to 1023 + uint32_t w : 2; // 0 to 3 + }; + uint32_t v; + }; + + XMUDEC4() = default; + + XMUDEC4(const XMUDEC4&) = default; + XMUDEC4& operator=(const XMUDEC4&) = default; + + XMUDEC4(XMUDEC4&&) = default; + XMUDEC4& operator=(XMUDEC4&&) = default; + + explicit constexpr XMUDEC4(uint32_t Packed) noexcept : v(Packed) {} + XMUDEC4(float _x, float _y, float _z, float _w) noexcept; + explicit XMUDEC4(_In_reads_(4) const float* pArray) noexcept; + + operator uint32_t () const noexcept { return v; } + + XMUDEC4& operator= (uint32_t Packed) noexcept { v = Packed; return *this; } + }; + + //------------------------------------------------------------------------------ + // 4D Vector; 8 bit signed normalized integer components + struct XMBYTEN4 + { + union + { + struct + { + int8_t x; + int8_t y; + int8_t z; + int8_t w; + }; + uint32_t v; + }; + + XMBYTEN4() = default; + + XMBYTEN4(const XMBYTEN4&) = default; + XMBYTEN4& operator=(const XMBYTEN4&) = default; + + XMBYTEN4(XMBYTEN4&&) = default; + XMBYTEN4& operator=(XMBYTEN4&&) = default; + + constexpr XMBYTEN4(int8_t _x, int8_t _y, int8_t _z, int8_t _w) noexcept : x(_x), y(_y), z(_z), w(_w) {} + explicit constexpr XMBYTEN4(uint32_t Packed) noexcept : v(Packed) {} + explicit XMBYTEN4(_In_reads_(4) const int8_t* pArray) noexcept : x(pArray[0]), y(pArray[1]), z(pArray[2]), w(pArray[3]) {} + XMBYTEN4(float _x, float _y, float _z, float _w) noexcept; + explicit XMBYTEN4(_In_reads_(4) const float* pArray) noexcept; + + XMBYTEN4& operator= (uint32_t Packed) noexcept { v = Packed; return *this; } + }; + + // 4D Vector; 8 bit signed integer components + struct XMBYTE4 + { + union + { + struct + { + int8_t x; + int8_t y; + int8_t z; + int8_t w; + }; + uint32_t v; + }; + + XMBYTE4() = default; + + XMBYTE4(const XMBYTE4&) = default; + XMBYTE4& operator=(const XMBYTE4&) = default; + + XMBYTE4(XMBYTE4&&) = default; + XMBYTE4& operator=(XMBYTE4&&) = default; + + constexpr XMBYTE4(int8_t _x, int8_t _y, int8_t _z, int8_t _w) noexcept : x(_x), y(_y), z(_z), w(_w) {} + explicit constexpr XMBYTE4(uint32_t Packed) noexcept : v(Packed) {} + explicit XMBYTE4(_In_reads_(4) const int8_t* pArray) noexcept : x(pArray[0]), y(pArray[1]), z(pArray[2]), w(pArray[3]) {} + XMBYTE4(float _x, float _y, float _z, float _w) noexcept; + explicit XMBYTE4(_In_reads_(4) const float* pArray) noexcept; + + XMBYTE4& operator= (uint32_t Packed) noexcept { v = Packed; return *this; } + }; + + // 4D Vector; 8 bit unsigned normalized integer components + struct XMUBYTEN4 + { + union + { + struct + { + uint8_t x; + uint8_t y; + uint8_t z; + uint8_t w; + }; + uint32_t v; + }; + + XMUBYTEN4() = default; + + XMUBYTEN4(const XMUBYTEN4&) = default; + XMUBYTEN4& operator=(const XMUBYTEN4&) = default; + + XMUBYTEN4(XMUBYTEN4&&) = default; + XMUBYTEN4& operator=(XMUBYTEN4&&) = default; + + constexpr XMUBYTEN4(uint8_t _x, uint8_t _y, uint8_t _z, uint8_t _w) noexcept : x(_x), y(_y), z(_z), w(_w) {} + explicit constexpr XMUBYTEN4(uint32_t Packed) noexcept : v(Packed) {} + explicit XMUBYTEN4(_In_reads_(4) const uint8_t* pArray) noexcept : x(pArray[0]), y(pArray[1]), z(pArray[2]), w(pArray[3]) {} + XMUBYTEN4(float _x, float _y, float _z, float _w) noexcept; + explicit XMUBYTEN4(_In_reads_(4) const float* pArray) noexcept; + + XMUBYTEN4& operator= (uint32_t Packed) noexcept { v = Packed; return *this; } + }; + + // 4D Vector; 8 bit unsigned integer components + struct XMUBYTE4 + { + union + { + struct + { + uint8_t x; + uint8_t y; + uint8_t z; + uint8_t w; + }; + uint32_t v; + }; + + XMUBYTE4() = default; + + XMUBYTE4(const XMUBYTE4&) = default; + XMUBYTE4& operator=(const XMUBYTE4&) = default; + + XMUBYTE4(XMUBYTE4&&) = default; + XMUBYTE4& operator=(XMUBYTE4&&) = default; + + constexpr XMUBYTE4(uint8_t _x, uint8_t _y, uint8_t _z, uint8_t _w) noexcept : x(_x), y(_y), z(_z), w(_w) {} + explicit constexpr XMUBYTE4(uint32_t Packed) noexcept : v(Packed) {} + explicit XMUBYTE4(_In_reads_(4) const uint8_t* pArray) noexcept : x(pArray[0]), y(pArray[1]), z(pArray[2]), w(pArray[3]) {} + XMUBYTE4(float _x, float _y, float _z, float _w) noexcept; + explicit XMUBYTE4(_In_reads_(4) const float* pArray) noexcept; + + XMUBYTE4& operator= (uint32_t Packed) noexcept { v = Packed; return *this; } + }; + + //------------------------------------------------------------------------------ + // 4D vector; 4 bit unsigned integer components + struct XMUNIBBLE4 + { + union + { + struct + { + uint16_t x : 4; // 0 to 15 + uint16_t y : 4; // 0 to 15 + uint16_t z : 4; // 0 to 15 + uint16_t w : 4; // 0 to 15 + }; + uint16_t v; + }; + + XMUNIBBLE4() = default; + + XMUNIBBLE4(const XMUNIBBLE4&) = default; + XMUNIBBLE4& operator=(const XMUNIBBLE4&) = default; + + XMUNIBBLE4(XMUNIBBLE4&&) = default; + XMUNIBBLE4& operator=(XMUNIBBLE4&&) = default; + + explicit constexpr XMUNIBBLE4(uint16_t Packed) noexcept : v(Packed) {} + constexpr XMUNIBBLE4(uint8_t _x, uint8_t _y, uint8_t _z, uint8_t _w) noexcept : x(_x), y(_y), z(_z), w(_w) {} + explicit XMUNIBBLE4(_In_reads_(4) const uint8_t* pArray) noexcept : x(pArray[0]), y(pArray[1]), z(pArray[2]), w(pArray[3]) {} + XMUNIBBLE4(float _x, float _y, float _z, float _w) noexcept; + explicit XMUNIBBLE4(_In_reads_(4) const float* pArray) noexcept; + + operator uint16_t () const noexcept { return v; } + + XMUNIBBLE4& operator= (uint16_t Packed) noexcept { v = Packed; return *this; } + }; + + //------------------------------------------------------------------------------ + // 4D vector: 5/5/5/1 unsigned integer components + struct XMU555 + { + union + { + struct + { + uint16_t x : 5; // 0 to 31 + uint16_t y : 5; // 0 to 31 + uint16_t z : 5; // 0 to 31 + uint16_t w : 1; // 0 or 1 + }; + uint16_t v; + }; + + XMU555() = default; + + XMU555(const XMU555&) = default; + XMU555& operator=(const XMU555&) = default; + + XMU555(XMU555&&) = default; + XMU555& operator=(XMU555&&) = default; + + explicit constexpr XMU555(uint16_t Packed) noexcept : v(Packed) {} + constexpr XMU555(uint8_t _x, uint8_t _y, uint8_t _z, bool _w) noexcept : x(_x), y(_y), z(_z), w(_w ? 0x1 : 0) {} + XMU555(_In_reads_(3) const uint8_t* pArray, _In_ bool _w) noexcept : x(pArray[0]), y(pArray[1]), z(pArray[2]), w(_w ? 0x1 : 0) {} + XMU555(float _x, float _y, float _z, bool _w) noexcept; + XMU555(_In_reads_(3) const float* pArray, _In_ bool _w) noexcept; + + operator uint16_t () const noexcept { return v; } + + XMU555& operator= (uint16_t Packed) noexcept { v = Packed; return *this; } + }; + + #ifdef __clang__ + #pragma clang diagnostic pop + #endif + #ifdef _MSC_VER + #pragma warning(pop) + #endif + + /**************************************************************************** + * + * Data conversion operations + * + ****************************************************************************/ + + float XMConvertHalfToFloat(HALF Value) noexcept; + float* XMConvertHalfToFloatStream(_Out_writes_bytes_(sizeof(float) + OutputStride * (HalfCount - 1)) float* pOutputStream, + _In_ size_t OutputStride, + _In_reads_bytes_(sizeof(HALF) + InputStride * (HalfCount - 1)) const HALF* pInputStream, + _In_ size_t InputStride, _In_ size_t HalfCount) noexcept; + HALF XMConvertFloatToHalf(float Value) noexcept; + HALF* XMConvertFloatToHalfStream(_Out_writes_bytes_(sizeof(HALF) + OutputStride * (FloatCount - 1)) HALF* pOutputStream, + _In_ size_t OutputStride, + _In_reads_bytes_(sizeof(float) + InputStride * (FloatCount - 1)) const float* pInputStream, + _In_ size_t InputStride, _In_ size_t FloatCount) noexcept; + + /**************************************************************************** + * + * Load operations + * + ****************************************************************************/ + + XMVECTOR XM_CALLCONV XMLoadColor(_In_ const XMCOLOR* pSource) noexcept; + + XMVECTOR XM_CALLCONV XMLoadHalf2(_In_ const XMHALF2* pSource) noexcept; + XMVECTOR XM_CALLCONV XMLoadShortN2(_In_ const XMSHORTN2* pSource) noexcept; + XMVECTOR XM_CALLCONV XMLoadShort2(_In_ const XMSHORT2* pSource) noexcept; + XMVECTOR XM_CALLCONV XMLoadUShortN2(_In_ const XMUSHORTN2* pSource) noexcept; + XMVECTOR XM_CALLCONV XMLoadUShort2(_In_ const XMUSHORT2* pSource) noexcept; + XMVECTOR XM_CALLCONV XMLoadByteN2(_In_ const XMBYTEN2* pSource) noexcept; + XMVECTOR XM_CALLCONV XMLoadByte2(_In_ const XMBYTE2* pSource) noexcept; + XMVECTOR XM_CALLCONV XMLoadUByteN2(_In_ const XMUBYTEN2* pSource) noexcept; + XMVECTOR XM_CALLCONV XMLoadUByte2(_In_ const XMUBYTE2* pSource) noexcept; + + XMVECTOR XM_CALLCONV XMLoadU565(_In_ const XMU565* pSource) noexcept; + XMVECTOR XM_CALLCONV XMLoadFloat3PK(_In_ const XMFLOAT3PK* pSource) noexcept; + XMVECTOR XM_CALLCONV XMLoadFloat3SE(_In_ const XMFLOAT3SE* pSource) noexcept; + + XMVECTOR XM_CALLCONV XMLoadHalf4(_In_ const XMHALF4* pSource) noexcept; + XMVECTOR XM_CALLCONV XMLoadShortN4(_In_ const XMSHORTN4* pSource) noexcept; + XMVECTOR XM_CALLCONV XMLoadShort4(_In_ const XMSHORT4* pSource) noexcept; + XMVECTOR XM_CALLCONV XMLoadUShortN4(_In_ const XMUSHORTN4* pSource) noexcept; + XMVECTOR XM_CALLCONV XMLoadUShort4(_In_ const XMUSHORT4* pSource) noexcept; + XMVECTOR XM_CALLCONV XMLoadXDecN4(_In_ const XMXDECN4* pSource) noexcept; + XMVECTOR XM_CALLCONV XMLoadUDecN4(_In_ const XMUDECN4* pSource) noexcept; + XMVECTOR XM_CALLCONV XMLoadUDecN4_XR(_In_ const XMUDECN4* pSource) noexcept; + XMVECTOR XM_CALLCONV XMLoadUDec4(_In_ const XMUDEC4* pSource) noexcept; + XMVECTOR XM_CALLCONV XMLoadByteN4(_In_ const XMBYTEN4* pSource) noexcept; + XMVECTOR XM_CALLCONV XMLoadByte4(_In_ const XMBYTE4* pSource) noexcept; + XMVECTOR XM_CALLCONV XMLoadUByteN4(_In_ const XMUBYTEN4* pSource) noexcept; + XMVECTOR XM_CALLCONV XMLoadUByte4(_In_ const XMUBYTE4* pSource) noexcept; + XMVECTOR XM_CALLCONV XMLoadUNibble4(_In_ const XMUNIBBLE4* pSource) noexcept; + XMVECTOR XM_CALLCONV XMLoadU555(_In_ const XMU555* pSource) noexcept; + + #ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4996) + #endif + + #ifdef __clang__ + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wdeprecated-declarations" + #endif + + #ifdef __GNUC__ + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdeprecated-declarations" + #endif + + XM_DEPRECATED + XMVECTOR XM_CALLCONV XMLoadDecN4(_In_ const XMDECN4* pSource) noexcept; + + XM_DEPRECATED + XMVECTOR XM_CALLCONV XMLoadDec4(_In_ const XMDEC4* pSource) noexcept; + + XM_DEPRECATED + XMVECTOR XM_CALLCONV XMLoadXDec4(_In_ const XMXDEC4* pSource) noexcept; + + #ifdef __GNUC__ + #pragma GCC diagnostic pop + #endif + #ifdef __clang__ + #pragma clang diagnostic pop + #endif + #ifdef _MSC_VER + #pragma warning(pop) + #endif + + /**************************************************************************** + * + * Store operations + * + ****************************************************************************/ + + void XM_CALLCONV XMStoreColor(_Out_ XMCOLOR* pDestination, _In_ FXMVECTOR V) noexcept; + + void XM_CALLCONV XMStoreHalf2(_Out_ XMHALF2* pDestination, _In_ FXMVECTOR V) noexcept; + void XM_CALLCONV XMStoreShortN2(_Out_ XMSHORTN2* pDestination, _In_ FXMVECTOR V) noexcept; + void XM_CALLCONV XMStoreShort2(_Out_ XMSHORT2* pDestination, _In_ FXMVECTOR V) noexcept; + void XM_CALLCONV XMStoreUShortN2(_Out_ XMUSHORTN2* pDestination, _In_ FXMVECTOR V) noexcept; + void XM_CALLCONV XMStoreUShort2(_Out_ XMUSHORT2* pDestination, _In_ FXMVECTOR V) noexcept; + void XM_CALLCONV XMStoreByteN2(_Out_ XMBYTEN2* pDestination, _In_ FXMVECTOR V) noexcept; + void XM_CALLCONV XMStoreByte2(_Out_ XMBYTE2* pDestination, _In_ FXMVECTOR V) noexcept; + void XM_CALLCONV XMStoreUByteN2(_Out_ XMUBYTEN2* pDestination, _In_ FXMVECTOR V) noexcept; + void XM_CALLCONV XMStoreUByte2(_Out_ XMUBYTE2* pDestination, _In_ FXMVECTOR V) noexcept; + + void XM_CALLCONV XMStoreU565(_Out_ XMU565* pDestination, _In_ FXMVECTOR V) noexcept; + void XM_CALLCONV XMStoreFloat3PK(_Out_ XMFLOAT3PK* pDestination, _In_ FXMVECTOR V) noexcept; + void XM_CALLCONV XMStoreFloat3SE(_Out_ XMFLOAT3SE* pDestination, _In_ FXMVECTOR V) noexcept; + + void XM_CALLCONV XMStoreHalf4(_Out_ XMHALF4* pDestination, _In_ FXMVECTOR V) noexcept; + void XM_CALLCONV XMStoreShortN4(_Out_ XMSHORTN4* pDestination, _In_ FXMVECTOR V) noexcept; + void XM_CALLCONV XMStoreShort4(_Out_ XMSHORT4* pDestination, _In_ FXMVECTOR V) noexcept; + void XM_CALLCONV XMStoreUShortN4(_Out_ XMUSHORTN4* pDestination, _In_ FXMVECTOR V) noexcept; + void XM_CALLCONV XMStoreUShort4(_Out_ XMUSHORT4* pDestination, _In_ FXMVECTOR V) noexcept; + void XM_CALLCONV XMStoreXDecN4(_Out_ XMXDECN4* pDestination, _In_ FXMVECTOR V) noexcept; + void XM_CALLCONV XMStoreUDecN4(_Out_ XMUDECN4* pDestination, _In_ FXMVECTOR V) noexcept; + void XM_CALLCONV XMStoreUDecN4_XR(_Out_ XMUDECN4* pDestination, _In_ FXMVECTOR V) noexcept; + void XM_CALLCONV XMStoreUDec4(_Out_ XMUDEC4* pDestination, _In_ FXMVECTOR V) noexcept; + void XM_CALLCONV XMStoreByteN4(_Out_ XMBYTEN4* pDestination, _In_ FXMVECTOR V) noexcept; + void XM_CALLCONV XMStoreByte4(_Out_ XMBYTE4* pDestination, _In_ FXMVECTOR V) noexcept; + void XM_CALLCONV XMStoreUByteN4(_Out_ XMUBYTEN4* pDestination, _In_ FXMVECTOR V) noexcept; + void XM_CALLCONV XMStoreUByte4(_Out_ XMUBYTE4* pDestination, _In_ FXMVECTOR V) noexcept; + void XM_CALLCONV XMStoreUNibble4(_Out_ XMUNIBBLE4* pDestination, _In_ FXMVECTOR V) noexcept; + void XM_CALLCONV XMStoreU555(_Out_ XMU555* pDestination, _In_ FXMVECTOR V) noexcept; + + #ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4996) + #endif + + #ifdef __clang__ + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wdeprecated-declarations" + #endif + + #ifdef __GNUC__ + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdeprecated-declarations" + #endif + + XM_DEPRECATED + void XM_CALLCONV XMStoreDecN4(_Out_ XMDECN4* pDestination, _In_ FXMVECTOR V) noexcept; + + XM_DEPRECATED + void XM_CALLCONV XMStoreDec4(_Out_ XMDEC4* pDestination, _In_ FXMVECTOR V) noexcept; + + XM_DEPRECATED + void XM_CALLCONV XMStoreXDec4(_Out_ XMXDEC4* pDestination, _In_ FXMVECTOR V) noexcept; + + #ifdef __GNUC__ + #pragma GCC diagnostic pop + #endif + #ifdef __clang__ + #pragma clang diagnostic pop + #endif + #ifdef _MSC_VER + #pragma warning(pop) + #endif + + /**************************************************************************** + * + * Implementation + * + ****************************************************************************/ + #ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable:4068 4214 4204 4365 4616 6001 6101) + #endif + + #ifdef _PREFAST_ + #pragma prefast(push) + #pragma prefast(disable : 25000, "FXMVECTOR is 16 bytes") + #pragma prefast(disable : 26495, "Union initialization confuses /analyze") + #endif + + #ifdef __clang__ + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wunknown-warning-option" + #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" + #endif + + #include "DirectXPackedVector.inl" + + #ifdef __clang__ + #pragma clang diagnostic pop + #endif + #ifdef _PREFAST_ + #pragma prefast(pop) + #endif + #ifdef _MSC_VER + #pragma warning(pop) + #endif + } // namespace PackedVector + +} // namespace DirectX diff --git a/Minecraft.Client/Windows64/Libs/4JLibs/impls/Windows_Libs/Render/vendor/DirectXMath/DirectXPackedVector.inl b/Minecraft.Client/Windows64/Libs/4JLibs/impls/Windows_Libs/Render/vendor/DirectXMath/DirectXPackedVector.inl new file mode 100644 index 00000000..be99cecc --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/4JLibs/impls/Windows_Libs/Render/vendor/DirectXMath/DirectXPackedVector.inl @@ -0,0 +1,4628 @@ +//------------------------------------------------------------------------------------- +// DirectXPackedVector.inl -- SIMD C++ Math library +// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +// +// https://go.microsoft.com/fwlink/?LinkID=615560 +//------------------------------------------------------------------------------------- + +#pragma once + +/**************************************************************************** + * + * Data conversion + * + ****************************************************************************/ + +//------------------------------------------------------------------------------ + +inline float XMConvertHalfToFloat(HALF Value) noexcept +{ +#if defined(_XM_F16C_INTRINSICS_) && !defined(_XM_NO_INTRINSICS_) + __m128i V1 = _mm_cvtsi32_si128(static_cast(Value)); + __m128 V2 = _mm_cvtph_ps(V1); + return _mm_cvtss_f32(V2); +#elif defined(_XM_ARM_NEON_INTRINSICS_) && (defined(_M_ARM64) || defined(_M_HYBRID_X86_ARM64) || defined(_M_ARM64EC) || __aarch64__) && !defined(_XM_NO_INTRINSICS_) && (!defined(__GNUC__) || (__ARM_FP & 2)) + uint16x4_t vHalf = vdup_n_u16(Value); + float32x4_t vFloat = vcvt_f32_f16(vreinterpret_f16_u16(vHalf)); + return vgetq_lane_f32(vFloat, 0); +#else + auto Mantissa = static_cast(Value & 0x03FF); + + uint32_t Exponent = (Value & 0x7C00); + if (Exponent == 0x7C00) // INF/NAN + { + Exponent = 0x8f; + } + else if (Exponent != 0) // The value is normalized + { + Exponent = static_cast((static_cast(Value) >> 10) & 0x1F); + } + else if (Mantissa != 0) // The value is denormalized + { + // Normalize the value in the resulting float + Exponent = 1; + + do + { + Exponent--; + Mantissa <<= 1; + } + while ((Mantissa & 0x0400) == 0); + + Mantissa &= 0x03FF; + } + else // The value is zero + { + Exponent = static_cast(-112); + } + + uint32_t Result = + ((static_cast(Value) & 0x8000) << 16) // Sign + | ((Exponent + 112) << 23) // Exponent + | (Mantissa << 13); // Mantissa + + return reinterpret_cast(&Result)[0]; +#endif // !_XM_F16C_INTRINSICS_ +} + +//------------------------------------------------------------------------------ +#ifdef _PREFAST_ +#pragma prefast(push) +#pragma prefast(disable : 26015 26019, "PREfast noise: Esp:1307" ) +#endif + +_Use_decl_annotations_ +inline float* XMConvertHalfToFloatStream +( + float* pOutputStream, + size_t OutputStride, + const HALF* pInputStream, + size_t InputStride, + size_t HalfCount +) noexcept +{ + assert(pOutputStream); + assert(pInputStream); + + assert(InputStride >= sizeof(HALF)); + _Analysis_assume_(InputStride >= sizeof(HALF)); + + assert(OutputStride >= sizeof(float)); + _Analysis_assume_(OutputStride >= sizeof(float)); + +#if defined(_XM_F16C_INTRINSICS_) && !defined(_XM_NO_INTRINSICS_) + auto pHalf = reinterpret_cast(pInputStream); + auto pFloat = reinterpret_cast(pOutputStream); + + size_t i = 0; + size_t four = HalfCount >> 2; + if (four > 0) + { + if (InputStride == sizeof(HALF)) + { + if (OutputStride == sizeof(float)) + { + if ((reinterpret_cast(pFloat) & 0xF) == 0) + { + // Packed input, aligned & packed output + for (size_t j = 0; j < four; ++j) + { + __m128i HV = _mm_loadl_epi64(reinterpret_cast(pHalf)); + pHalf += InputStride * 4; + + __m128 FV = _mm_cvtph_ps(HV); + + XM_STREAM_PS(reinterpret_cast(pFloat), FV); + pFloat += OutputStride * 4; + i += 4; + } + } + else + { + // Packed input, packed output + for (size_t j = 0; j < four; ++j) + { + __m128i HV = _mm_loadl_epi64(reinterpret_cast(pHalf)); + pHalf += InputStride * 4; + + __m128 FV = _mm_cvtph_ps(HV); + + _mm_storeu_ps(reinterpret_cast(pFloat), FV); + pFloat += OutputStride * 4; + i += 4; + } + } + } + else + { + // Packed input, scattered output + for (size_t j = 0; j < four; ++j) + { + __m128i HV = _mm_loadl_epi64(reinterpret_cast(pHalf)); + pHalf += InputStride * 4; + + __m128 FV = _mm_cvtph_ps(HV); + + _mm_store_ss(reinterpret_cast(pFloat), FV); + pFloat += OutputStride; + *reinterpret_cast(pFloat) = _mm_extract_ps(FV, 1); + pFloat += OutputStride; + *reinterpret_cast(pFloat) = _mm_extract_ps(FV, 2); + pFloat += OutputStride; + *reinterpret_cast(pFloat) = _mm_extract_ps(FV, 3); + pFloat += OutputStride; + i += 4; + } + } + } + else if (OutputStride == sizeof(float)) + { + if ((reinterpret_cast(pFloat) & 0xF) == 0) + { + // Scattered input, aligned & packed output + for (size_t j = 0; j < four; ++j) + { + uint16_t H1 = *reinterpret_cast(pHalf); + pHalf += InputStride; + uint16_t H2 = *reinterpret_cast(pHalf); + pHalf += InputStride; + uint16_t H3 = *reinterpret_cast(pHalf); + pHalf += InputStride; + uint16_t H4 = *reinterpret_cast(pHalf); + pHalf += InputStride; + + __m128i HV = _mm_setzero_si128(); + HV = _mm_insert_epi16(HV, H1, 0); + HV = _mm_insert_epi16(HV, H2, 1); + HV = _mm_insert_epi16(HV, H3, 2); + HV = _mm_insert_epi16(HV, H4, 3); + __m128 FV = _mm_cvtph_ps(HV); + + XM_STREAM_PS(reinterpret_cast(pFloat), FV); + pFloat += OutputStride * 4; + i += 4; + } + } + else + { + // Scattered input, packed output + for (size_t j = 0; j < four; ++j) + { + uint16_t H1 = *reinterpret_cast(pHalf); + pHalf += InputStride; + uint16_t H2 = *reinterpret_cast(pHalf); + pHalf += InputStride; + uint16_t H3 = *reinterpret_cast(pHalf); + pHalf += InputStride; + uint16_t H4 = *reinterpret_cast(pHalf); + pHalf += InputStride; + + __m128i HV = _mm_setzero_si128(); + HV = _mm_insert_epi16(HV, H1, 0); + HV = _mm_insert_epi16(HV, H2, 1); + HV = _mm_insert_epi16(HV, H3, 2); + HV = _mm_insert_epi16(HV, H4, 3); + __m128 FV = _mm_cvtph_ps(HV); + + _mm_storeu_ps(reinterpret_cast(pFloat), FV); + pFloat += OutputStride * 4; + i += 4; + } + } + } + else + { + // Scattered input, scattered output + for (size_t j = 0; j < four; ++j) + { + uint16_t H1 = *reinterpret_cast(pHalf); + pHalf += InputStride; + uint16_t H2 = *reinterpret_cast(pHalf); + pHalf += InputStride; + uint16_t H3 = *reinterpret_cast(pHalf); + pHalf += InputStride; + uint16_t H4 = *reinterpret_cast(pHalf); + pHalf += InputStride; + + __m128i HV = _mm_setzero_si128(); + HV = _mm_insert_epi16(HV, H1, 0); + HV = _mm_insert_epi16(HV, H2, 1); + HV = _mm_insert_epi16(HV, H3, 2); + HV = _mm_insert_epi16(HV, H4, 3); + __m128 FV = _mm_cvtph_ps(HV); + + _mm_store_ss(reinterpret_cast(pFloat), FV); + pFloat += OutputStride; + *reinterpret_cast(pFloat) = _mm_extract_ps(FV, 1); + pFloat += OutputStride; + *reinterpret_cast(pFloat) = _mm_extract_ps(FV, 2); + pFloat += OutputStride; + *reinterpret_cast(pFloat) = _mm_extract_ps(FV, 3); + pFloat += OutputStride; + i += 4; + } + } + } + + for (; i < HalfCount; ++i) + { + *reinterpret_cast(pFloat) = XMConvertHalfToFloat(reinterpret_cast(pHalf)[0]); + pHalf += InputStride; + pFloat += OutputStride; + } + + XM_SFENCE(); + + return pOutputStream; +#elif defined(_XM_ARM_NEON_INTRINSICS_) && (defined(_M_ARM64) || defined(_M_HYBRID_X86_ARM64) || defined(_M_ARM64EC) ||__aarch64__) && !defined(_XM_NO_INTRINSICS_) && (!defined(__GNUC__) || (__ARM_FP & 2)) + auto pHalf = reinterpret_cast(pInputStream); + auto pFloat = reinterpret_cast(pOutputStream); + + size_t i = 0; + size_t four = HalfCount >> 2; + if (four > 0) + { + if (InputStride == sizeof(HALF)) + { + if (OutputStride == sizeof(float)) + { + // Packed input, packed output + for (size_t j = 0; j < four; ++j) + { + uint16x4_t vHalf = vld1_u16(reinterpret_cast(pHalf)); + pHalf += InputStride * 4; + + float32x4_t vFloat = vcvt_f32_f16(vreinterpret_f16_u16(vHalf)); + + vst1q_f32(reinterpret_cast(pFloat), vFloat); + pFloat += OutputStride * 4; + i += 4; + } + } + else + { + // Packed input, scattered output + for (size_t j = 0; j < four; ++j) + { + uint16x4_t vHalf = vld1_u16(reinterpret_cast(pHalf)); + pHalf += InputStride * 4; + + float32x4_t vFloat = vcvt_f32_f16(vreinterpret_f16_u16(vHalf)); + + vst1q_lane_f32(reinterpret_cast(pFloat), vFloat, 0); + pFloat += OutputStride; + vst1q_lane_f32(reinterpret_cast(pFloat), vFloat, 1); + pFloat += OutputStride; + vst1q_lane_f32(reinterpret_cast(pFloat), vFloat, 2); + pFloat += OutputStride; + vst1q_lane_f32(reinterpret_cast(pFloat), vFloat, 3); + pFloat += OutputStride; + i += 4; + } + } + } + else if (OutputStride == sizeof(float)) + { + // Scattered input, packed output + for (size_t j = 0; j < four; ++j) + { + uint16_t H1 = *reinterpret_cast(pHalf); + pHalf += InputStride; + uint16_t H2 = *reinterpret_cast(pHalf); + pHalf += InputStride; + uint16_t H3 = *reinterpret_cast(pHalf); + pHalf += InputStride; + uint16_t H4 = *reinterpret_cast(pHalf); + pHalf += InputStride; + + uint64_t iHalf = uint64_t(H1) | (uint64_t(H2) << 16) | (uint64_t(H3) << 32) | (uint64_t(H4) << 48); + uint16x4_t vHalf = vcreate_u16(iHalf); + + float32x4_t vFloat = vcvt_f32_f16(vreinterpret_f16_u16(vHalf)); + + vst1q_f32(reinterpret_cast(pFloat), vFloat); + pFloat += OutputStride * 4; + i += 4; + } + } + else + { + // Scattered input, scattered output + for (size_t j = 0; j < four; ++j) + { + uint16_t H1 = *reinterpret_cast(pHalf); + pHalf += InputStride; + uint16_t H2 = *reinterpret_cast(pHalf); + pHalf += InputStride; + uint16_t H3 = *reinterpret_cast(pHalf); + pHalf += InputStride; + uint16_t H4 = *reinterpret_cast(pHalf); + pHalf += InputStride; + + uint64_t iHalf = uint64_t(H1) | (uint64_t(H2) << 16) | (uint64_t(H3) << 32) | (uint64_t(H4) << 48); + uint16x4_t vHalf = vcreate_u16(iHalf); + + float32x4_t vFloat = vcvt_f32_f16(vreinterpret_f16_u16(vHalf)); + + vst1q_lane_f32(reinterpret_cast(pFloat), vFloat, 0); + pFloat += OutputStride; + vst1q_lane_f32(reinterpret_cast(pFloat), vFloat, 1); + pFloat += OutputStride; + vst1q_lane_f32(reinterpret_cast(pFloat), vFloat, 2); + pFloat += OutputStride; + vst1q_lane_f32(reinterpret_cast(pFloat), vFloat, 3); + pFloat += OutputStride; + i += 4; + } + } + } + + for (; i < HalfCount; ++i) + { + *reinterpret_cast(pFloat) = XMConvertHalfToFloat(reinterpret_cast(pHalf)[0]); + pHalf += InputStride; + pFloat += OutputStride; + } + + return pOutputStream; +#else + auto pHalf = reinterpret_cast(pInputStream); + auto pFloat = reinterpret_cast(pOutputStream); + + for (size_t i = 0; i < HalfCount; i++) + { + *reinterpret_cast(pFloat) = XMConvertHalfToFloat(reinterpret_cast(pHalf)[0]); + pHalf += InputStride; + pFloat += OutputStride; + } + + return pOutputStream; +#endif // !_XM_F16C_INTRINSICS_ +} + +//------------------------------------------------------------------------------ + +inline HALF XMConvertFloatToHalf(float Value) noexcept +{ +#if defined(_XM_F16C_INTRINSICS_) && !defined(_XM_NO_INTRINSICS_) + __m128 V1 = _mm_set_ss(Value); + __m128i V2 = _mm_cvtps_ph(V1, _MM_FROUND_TO_NEAREST_INT); + return static_cast(_mm_extract_epi16(V2, 0)); +#elif defined(_XM_ARM_NEON_INTRINSICS_) && (defined(_M_ARM64) || defined(_M_HYBRID_X86_ARM64) || defined(_M_ARM64EC) || __aarch64__) && !defined(_XM_NO_INTRINSICS_) && (!defined(__GNUC__) || (__ARM_FP & 2)) + float32x4_t vFloat = vdupq_n_f32(Value); + float16x4_t vHalf = vcvt_f16_f32(vFloat); + return vget_lane_u16(vreinterpret_u16_f16(vHalf), 0); +#else + uint32_t Result; + + auto IValue = reinterpret_cast(&Value)[0]; + uint32_t Sign = (IValue & 0x80000000U) >> 16U; + IValue = IValue & 0x7FFFFFFFU; // Hack off the sign + if (IValue >= 0x47800000 /*e+16*/) + { + // The number is too large to be represented as a half. Return infinity or NaN + Result = 0x7C00U | ((IValue > 0x7F800000) ? (0x200 | ((IValue >> 13U) & 0x3FFU)) : 0U); + } + else if (IValue <= 0x33000000U /*e-25*/) + { + Result = 0; + } + else if (IValue < 0x38800000U /*e-14*/) + { + // The number is too small to be represented as a normalized half. + // Convert it to a denormalized value. + uint32_t Shift = 125U - (IValue >> 23U); + IValue = 0x800000U | (IValue & 0x7FFFFFU); + Result = IValue >> (Shift + 1); + uint32_t s = (IValue & ((1U << Shift) - 1)) != 0; + Result += (Result | s) & ((IValue >> Shift) & 1U); + } + else + { + // Rebias the exponent to represent the value as a normalized half. + IValue += 0xC8000000U; + Result = ((IValue + 0x0FFFU + ((IValue >> 13U) & 1U)) >> 13U) & 0x7FFFU; + } + return static_cast(Result | Sign); +#endif // !_XM_F16C_INTRINSICS_ +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline HALF* XMConvertFloatToHalfStream +( + HALF* pOutputStream, + size_t OutputStride, + const float* pInputStream, + size_t InputStride, + size_t FloatCount +) noexcept +{ + assert(pOutputStream); + assert(pInputStream); + + assert(InputStride >= sizeof(float)); + _Analysis_assume_(InputStride >= sizeof(float)); + + assert(OutputStride >= sizeof(HALF)); + _Analysis_assume_(OutputStride >= sizeof(HALF)); + +#if defined(_XM_F16C_INTRINSICS_) && !defined(_XM_NO_INTRINSICS_) + auto pFloat = reinterpret_cast(pInputStream); + auto pHalf = reinterpret_cast(pOutputStream); + + size_t i = 0; + size_t four = FloatCount >> 2; + if (four > 0) + { + if (InputStride == sizeof(float)) + { + if (OutputStride == sizeof(HALF)) + { + if ((reinterpret_cast(pFloat) & 0xF) == 0) + { + // Aligned and packed input, packed output + for (size_t j = 0; j < four; ++j) + { + __m128 FV = _mm_load_ps(reinterpret_cast(pFloat)); + pFloat += InputStride * 4; + + __m128i HV = _mm_cvtps_ph(FV, _MM_FROUND_TO_NEAREST_INT); + + _mm_storel_epi64(reinterpret_cast<__m128i*>(pHalf), HV); + pHalf += OutputStride * 4; + i += 4; + } + } + else + { + // Packed input, packed output + for (size_t j = 0; j < four; ++j) + { + __m128 FV = _mm_loadu_ps(reinterpret_cast(pFloat)); + pFloat += InputStride * 4; + + __m128i HV = _mm_cvtps_ph(FV, _MM_FROUND_TO_NEAREST_INT); + + _mm_storel_epi64(reinterpret_cast<__m128i*>(pHalf), HV); + pHalf += OutputStride * 4; + i += 4; + } + } + } + else + { + if ((reinterpret_cast(pFloat) & 0xF) == 0) + { + // Aligned & packed input, scattered output + for (size_t j = 0; j < four; ++j) + { + __m128 FV = _mm_load_ps(reinterpret_cast(pFloat)); + pFloat += InputStride * 4; + + __m128i HV = _mm_cvtps_ph(FV, _MM_FROUND_TO_NEAREST_INT); + + *reinterpret_cast(pHalf) = static_cast(_mm_extract_epi16(HV, 0)); + pHalf += OutputStride; + *reinterpret_cast(pHalf) = static_cast(_mm_extract_epi16(HV, 1)); + pHalf += OutputStride; + *reinterpret_cast(pHalf) = static_cast(_mm_extract_epi16(HV, 2)); + pHalf += OutputStride; + *reinterpret_cast(pHalf) = static_cast(_mm_extract_epi16(HV, 3)); + pHalf += OutputStride; + i += 4; + } + } + else + { + // Packed input, scattered output + for (size_t j = 0; j < four; ++j) + { + __m128 FV = _mm_loadu_ps(reinterpret_cast(pFloat)); + pFloat += InputStride * 4; + + __m128i HV = _mm_cvtps_ph(FV, _MM_FROUND_TO_NEAREST_INT); + + *reinterpret_cast(pHalf) = static_cast(_mm_extract_epi16(HV, 0)); + pHalf += OutputStride; + *reinterpret_cast(pHalf) = static_cast(_mm_extract_epi16(HV, 1)); + pHalf += OutputStride; + *reinterpret_cast(pHalf) = static_cast(_mm_extract_epi16(HV, 2)); + pHalf += OutputStride; + *reinterpret_cast(pHalf) = static_cast(_mm_extract_epi16(HV, 3)); + pHalf += OutputStride; + i += 4; + } + } + } + } + else if (OutputStride == sizeof(HALF)) + { + // Scattered input, packed output + for (size_t j = 0; j < four; ++j) + { + __m128 FV1 = _mm_load_ss(reinterpret_cast(pFloat)); + pFloat += InputStride; + + __m128 FV2 = _mm_broadcast_ss(reinterpret_cast(pFloat)); + pFloat += InputStride; + + __m128 FV3 = _mm_broadcast_ss(reinterpret_cast(pFloat)); + pFloat += InputStride; + + __m128 FV4 = _mm_broadcast_ss(reinterpret_cast(pFloat)); + pFloat += InputStride; + + __m128 FV = _mm_blend_ps(FV1, FV2, 0x2); + __m128 FT = _mm_blend_ps(FV3, FV4, 0x8); + FV = _mm_blend_ps(FV, FT, 0xC); + + __m128i HV = _mm_cvtps_ph(FV, _MM_FROUND_TO_NEAREST_INT); + + _mm_storel_epi64(reinterpret_cast<__m128i*>(pHalf), HV); + pHalf += OutputStride * 4; + i += 4; + } + } + else + { + // Scattered input, scattered output + for (size_t j = 0; j < four; ++j) + { + __m128 FV1 = _mm_load_ss(reinterpret_cast(pFloat)); + pFloat += InputStride; + + __m128 FV2 = _mm_broadcast_ss(reinterpret_cast(pFloat)); + pFloat += InputStride; + + __m128 FV3 = _mm_broadcast_ss(reinterpret_cast(pFloat)); + pFloat += InputStride; + + __m128 FV4 = _mm_broadcast_ss(reinterpret_cast(pFloat)); + pFloat += InputStride; + + __m128 FV = _mm_blend_ps(FV1, FV2, 0x2); + __m128 FT = _mm_blend_ps(FV3, FV4, 0x8); + FV = _mm_blend_ps(FV, FT, 0xC); + + __m128i HV = _mm_cvtps_ph(FV, _MM_FROUND_TO_NEAREST_INT); + + *reinterpret_cast(pHalf) = static_cast(_mm_extract_epi16(HV, 0)); + pHalf += OutputStride; + *reinterpret_cast(pHalf) = static_cast(_mm_extract_epi16(HV, 1)); + pHalf += OutputStride; + *reinterpret_cast(pHalf) = static_cast(_mm_extract_epi16(HV, 2)); + pHalf += OutputStride; + *reinterpret_cast(pHalf) = static_cast(_mm_extract_epi16(HV, 3)); + pHalf += OutputStride; + i += 4; + } + } + } + + for (; i < FloatCount; ++i) + { + *reinterpret_cast(pHalf) = XMConvertFloatToHalf(reinterpret_cast(pFloat)[0]); + pFloat += InputStride; + pHalf += OutputStride; + } + + return pOutputStream; +#elif defined(_XM_ARM_NEON_INTRINSICS_) && (defined(_M_ARM64) || defined(_M_HYBRID_X86_ARM64) || defined(_M_ARM64EC) || __aarch64__) && !defined(_XM_NO_INTRINSICS_) && (!defined(__GNUC__) || (__ARM_FP & 2)) + auto pFloat = reinterpret_cast(pInputStream); + auto pHalf = reinterpret_cast(pOutputStream); + + size_t i = 0; + size_t four = FloatCount >> 2; + if (four > 0) + { + if (InputStride == sizeof(float)) + { + if (OutputStride == sizeof(HALF)) + { + // Packed input, packed output + for (size_t j = 0; j < four; ++j) + { + float32x4_t vFloat = vld1q_f32(reinterpret_cast(pFloat)); + pFloat += InputStride * 4; + + uint16x4_t vHalf = vreinterpret_u16_f16(vcvt_f16_f32(vFloat)); + + vst1_u16(reinterpret_cast(pHalf), vHalf); + pHalf += OutputStride * 4; + i += 4; + } + } + else + { + // Packed input, scattered output + for (size_t j = 0; j < four; ++j) + { + float32x4_t vFloat = vld1q_f32(reinterpret_cast(pFloat)); + pFloat += InputStride * 4; + + uint16x4_t vHalf = vreinterpret_u16_f16(vcvt_f16_f32(vFloat)); + + vst1_lane_u16(reinterpret_cast(pHalf), vHalf, 0); + pHalf += OutputStride; + vst1_lane_u16(reinterpret_cast(pHalf), vHalf, 1); + pHalf += OutputStride; + vst1_lane_u16(reinterpret_cast(pHalf), vHalf, 2); + pHalf += OutputStride; + vst1_lane_u16(reinterpret_cast(pHalf), vHalf, 3); + pHalf += OutputStride; + i += 4; + } + } + } + else if (OutputStride == sizeof(HALF)) + { + // Scattered input, packed output + for (size_t j = 0; j < four; ++j) + { + float32x4_t vFloat = vdupq_n_f32(0); + vFloat = vld1q_lane_f32(reinterpret_cast(pFloat), vFloat, 0); + pFloat += InputStride; + + vFloat = vld1q_lane_f32(reinterpret_cast(pFloat), vFloat, 1); + pFloat += InputStride; + + vFloat = vld1q_lane_f32(reinterpret_cast(pFloat), vFloat, 2); + pFloat += InputStride; + + vFloat = vld1q_lane_f32(reinterpret_cast(pFloat), vFloat, 3); + pFloat += InputStride; + + uint16x4_t vHalf = vreinterpret_u16_f16(vcvt_f16_f32(vFloat)); + + vst1_u16(reinterpret_cast(pHalf), vHalf); + pHalf += OutputStride * 4; + i += 4; + } + } + else + { + // Scattered input, scattered output + for (size_t j = 0; j < four; ++j) + { + float32x4_t vFloat = vdupq_n_f32(0); + vFloat = vld1q_lane_f32(reinterpret_cast(pFloat), vFloat, 0); + pFloat += InputStride; + + vFloat = vld1q_lane_f32(reinterpret_cast(pFloat), vFloat, 1); + pFloat += InputStride; + + vFloat = vld1q_lane_f32(reinterpret_cast(pFloat), vFloat, 2); + pFloat += InputStride; + + vFloat = vld1q_lane_f32(reinterpret_cast(pFloat), vFloat, 3); + pFloat += InputStride; + + uint16x4_t vHalf = vreinterpret_u16_f16(vcvt_f16_f32(vFloat)); + + vst1_lane_u16(reinterpret_cast(pHalf), vHalf, 0); + pHalf += OutputStride; + vst1_lane_u16(reinterpret_cast(pHalf), vHalf, 1); + pHalf += OutputStride; + vst1_lane_u16(reinterpret_cast(pHalf), vHalf, 2); + pHalf += OutputStride; + vst1_lane_u16(reinterpret_cast(pHalf), vHalf, 3); + pHalf += OutputStride; + i += 4; + } + } + } + + for (; i < FloatCount; ++i) + { + *reinterpret_cast(pHalf) = XMConvertFloatToHalf(reinterpret_cast(pFloat)[0]); + pFloat += InputStride; + pHalf += OutputStride; + } + + return pOutputStream; +#else + auto pFloat = reinterpret_cast(pInputStream); + auto pHalf = reinterpret_cast(pOutputStream); + + for (size_t i = 0; i < FloatCount; i++) + { + *reinterpret_cast(pHalf) = XMConvertFloatToHalf(reinterpret_cast(pFloat)[0]); + pFloat += InputStride; + pHalf += OutputStride; + } + return pOutputStream; +#endif // !_XM_F16C_INTRINSICS_ +} + +#ifdef _PREFAST_ +#pragma prefast(pop) +#endif + +/**************************************************************************** + * + * Vector and matrix load operations + * + ****************************************************************************/ + +#ifdef _PREFAST_ +#pragma prefast(push) +#pragma prefast(disable:28931, "PREfast noise: Esp:1266") +#endif + +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMLoadColor(const XMCOLOR* pSource) noexcept +{ + assert(pSource); +#if defined(_XM_NO_INTRINSICS_) + // int32_t -> Float conversions are done in one instruction. + // uint32_t -> Float calls a runtime function. Keep in int32_t + auto iColor = static_cast(pSource->c); + XMVECTORF32 vColor = { { { + static_cast((iColor >> 16) & 0xFF)* (1.0f / 255.0f), + static_cast((iColor >> 8) & 0xFF)* (1.0f / 255.0f), + static_cast(iColor & 0xFF)* (1.0f / 255.0f), + static_cast((iColor >> 24) & 0xFF)* (1.0f / 255.0f) + } } }; + return vColor.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32_t bgra = pSource->c; + uint32_t rgba = (bgra & 0xFF00FF00) | ((bgra >> 16) & 0xFF) | ((bgra << 16) & 0xFF0000); + uint32x2_t vInt8 = vdup_n_u32(rgba); + uint16x8_t vInt16 = vmovl_u8(vreinterpret_u8_u32(vInt8)); + uint32x4_t vInt = vmovl_u16(vget_low_u16(vInt16)); + float32x4_t R = vcvtq_f32_u32(vInt); + return vmulq_n_f32(R, 1.0f / 255.0f); +#elif defined(_XM_SSE_INTRINSICS_) + // Splat the color in all four entries + __m128i vInt = _mm_set1_epi32(static_cast(pSource->c)); + // Shift R&0xFF0000, G&0xFF00, B&0xFF, A&0xFF000000 + vInt = _mm_and_si128(vInt, g_XMMaskA8R8G8B8); + // a is unsigned! Flip the bit to convert the order to signed + vInt = _mm_xor_si128(vInt, g_XMFlipA8R8G8B8); + // Convert to floating point numbers + XMVECTOR vTemp = _mm_cvtepi32_ps(vInt); + // RGB + 0, A + 0x80000000.f to undo the signed order. + vTemp = _mm_add_ps(vTemp, g_XMFixAA8R8G8B8); + // Convert 0-255 to 0.0f-1.0f + return _mm_mul_ps(vTemp, g_XMNormalizeA8R8G8B8); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMLoadHalf2(const XMHALF2* pSource) noexcept +{ + assert(pSource); +#if defined(_XM_F16C_INTRINSICS_) && !defined(_XM_NO_INTRINSICS_) + __m128 V = _mm_load_ss(reinterpret_cast(pSource)); + return _mm_cvtph_ps(_mm_castps_si128(V)); +#else + XMVECTORF32 vResult = { { { + XMConvertHalfToFloat(pSource->x), + XMConvertHalfToFloat(pSource->y), + 0.0f, + 0.0f + } } }; + return vResult.v; +#endif // !_XM_F16C_INTRINSICS_ +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMLoadShortN2(const XMSHORTN2* pSource) noexcept +{ + assert(pSource); +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 vResult = { { { + (pSource->x == -32768) ? -1.f : (static_cast(pSource->x)* (1.0f / 32767.0f)), + (pSource->y == -32768) ? -1.f : (static_cast(pSource->y)* (1.0f / 32767.0f)), + 0.0f, + 0.0f + } } }; + return vResult.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x2_t vInt16 = vld1_dup_u32(reinterpret_cast(pSource)); + int32x4_t vInt = vmovl_s16(vreinterpret_s16_u32(vInt16)); + vInt = vandq_s32(vInt, g_XMMaskXY); + float32x4_t R = vcvtq_f32_s32(vInt); + R = vmulq_n_f32(R, 1.0f / 32767.0f); + return vmaxq_f32(R, vdupq_n_f32(-1.f)); +#elif defined(_XM_SSE_INTRINSICS_) + // Splat the two shorts in all four entries (WORD alignment okay, + // DWORD alignment preferred) + __m128 vTemp = _mm_load_ps1(reinterpret_cast(&pSource->x)); + // Mask x&0xFFFF, y&0xFFFF0000,z&0,w&0 + vTemp = _mm_and_ps(vTemp, g_XMMaskX16Y16); + // x needs to be sign extended + vTemp = _mm_xor_ps(vTemp, g_XMFlipX16Y16); + // Convert to floating point numbers + vTemp = _mm_cvtepi32_ps(_mm_castps_si128(vTemp)); + // x - 0x8000 to undo the signed order. + vTemp = _mm_add_ps(vTemp, g_XMFixX16Y16); + // Convert -1.0f - 1.0f + vTemp = _mm_mul_ps(vTemp, g_XMNormalizeX16Y16); + // Clamp result (for case of -32768) + return _mm_max_ps(vTemp, g_XMNegativeOne); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMLoadShort2(const XMSHORT2* pSource) noexcept +{ + assert(pSource); +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 vResult = { { { + static_cast(pSource->x), + static_cast(pSource->y), + 0.f, + 0.f + } } }; + return vResult.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x2_t vInt16 = vld1_dup_u32(reinterpret_cast(pSource)); + int32x4_t vInt = vmovl_s16(vreinterpret_s16_u32(vInt16)); + vInt = vandq_s32(vInt, g_XMMaskXY); + return vcvtq_f32_s32(vInt); +#elif defined(_XM_SSE_INTRINSICS_) + // Splat the two shorts in all four entries (WORD alignment okay, + // DWORD alignment preferred) + __m128 vTemp = _mm_load_ps1(reinterpret_cast(&pSource->x)); + // Mask x&0xFFFF, y&0xFFFF0000,z&0,w&0 + vTemp = _mm_and_ps(vTemp, g_XMMaskX16Y16); + // x needs to be sign extended + vTemp = _mm_xor_ps(vTemp, g_XMFlipX16Y16); + // Convert to floating point numbers + vTemp = _mm_cvtepi32_ps(_mm_castps_si128(vTemp)); + // x - 0x8000 to undo the signed order. + vTemp = _mm_add_ps(vTemp, g_XMFixX16Y16); + // Y is 65536 too large + return _mm_mul_ps(vTemp, g_XMFixupY16); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMLoadUShortN2(const XMUSHORTN2* pSource) noexcept +{ + assert(pSource); +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 vResult = { { { + static_cast(pSource->x) / 65535.0f, + static_cast(pSource->y) / 65535.0f, + 0.f, + 0.f + } } }; + return vResult.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x2_t vInt16 = vld1_dup_u32(reinterpret_cast(pSource)); + uint32x4_t vInt = vmovl_u16(vreinterpret_u16_u32(vInt16)); + vInt = vandq_u32(vInt, g_XMMaskXY); + float32x4_t R = vcvtq_f32_u32(vInt); + R = vmulq_n_f32(R, 1.0f / 65535.0f); + return vmaxq_f32(R, vdupq_n_f32(-1.f)); +#elif defined(_XM_SSE_INTRINSICS_) + static const XMVECTORF32 FixupY16 = { { { 1.0f / 65535.0f, 1.0f / (65535.0f * 65536.0f), 0.0f, 0.0f } } }; + static const XMVECTORF32 FixaddY16 = { { { 0, 32768.0f * 65536.0f, 0, 0 } } }; + // Splat the two shorts in all four entries (WORD alignment okay, + // DWORD alignment preferred) + __m128 vTemp = _mm_load_ps1(reinterpret_cast(&pSource->x)); + // Mask x&0xFFFF, y&0xFFFF0000,z&0,w&0 + vTemp = _mm_and_ps(vTemp, g_XMMaskX16Y16); + // y needs to be sign flipped + vTemp = _mm_xor_ps(vTemp, g_XMFlipY); + // Convert to floating point numbers + vTemp = _mm_cvtepi32_ps(_mm_castps_si128(vTemp)); + // y + 0x8000 to undo the signed order. + vTemp = _mm_add_ps(vTemp, FixaddY16); + // Y is 65536 times too large + vTemp = _mm_mul_ps(vTemp, FixupY16); + return vTemp; +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMLoadUShort2(const XMUSHORT2* pSource) noexcept +{ + assert(pSource); +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 vResult = { { { + static_cast(pSource->x), + static_cast(pSource->y), + 0.f, + 0.f + } } }; + return vResult.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x2_t vInt16 = vld1_dup_u32(reinterpret_cast(pSource)); + uint32x4_t vInt = vmovl_u16(vreinterpret_u16_u32(vInt16)); + vInt = vandq_u32(vInt, g_XMMaskXY); + return vcvtq_f32_u32(vInt); +#elif defined(_XM_SSE_INTRINSICS_) + static const XMVECTORF32 FixaddY16 = { { { 0, 32768.0f, 0, 0 } } }; + // Splat the two shorts in all four entries (WORD alignment okay, + // DWORD alignment preferred) + __m128 vTemp = _mm_load_ps1(reinterpret_cast(&pSource->x)); + // Mask x&0xFFFF, y&0xFFFF0000,z&0,w&0 + vTemp = _mm_and_ps(vTemp, g_XMMaskX16Y16); + // y needs to be sign flipped + vTemp = _mm_xor_ps(vTemp, g_XMFlipY); + // Convert to floating point numbers + vTemp = _mm_cvtepi32_ps(_mm_castps_si128(vTemp)); + // Y is 65536 times too large + vTemp = _mm_mul_ps(vTemp, g_XMFixupY16); + // y + 0x8000 to undo the signed order. + vTemp = _mm_add_ps(vTemp, FixaddY16); + return vTemp; +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMLoadByteN2(const XMBYTEN2* pSource) noexcept +{ + assert(pSource); +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 vResult = { { { + (pSource->x == -128) ? -1.f : (static_cast(pSource->x)* (1.0f / 127.0f)), + (pSource->y == -128) ? -1.f : (static_cast(pSource->y)* (1.0f / 127.0f)), + 0.0f, + 0.0f + } } }; + return vResult.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint16x4_t vInt8 = vld1_dup_u16(reinterpret_cast(pSource)); + int16x8_t vInt16 = vmovl_s8(vreinterpret_s8_u16(vInt8)); + int32x4_t vInt = vmovl_s16(vget_low_s16(vInt16)); + vInt = vandq_s32(vInt, g_XMMaskXY); + float32x4_t R = vcvtq_f32_s32(vInt); + R = vmulq_n_f32(R, 1.0f / 127.0f); + return vmaxq_f32(R, vdupq_n_f32(-1.f)); +#elif defined(_XM_SSE_INTRINSICS_) + static const XMVECTORF32 Scale = { { { 1.0f / 127.0f, 1.0f / (127.0f * 256.0f), 0, 0 } } }; + static const XMVECTORU32 Mask = { { { 0xFF, 0xFF00, 0, 0 } } }; + // Splat the color in all four entries (x,z,y,w) + __m128i vInt = XM_LOADU_SI16(&pSource->v); + XMVECTOR vTemp = XM_PERMUTE_PS(_mm_castsi128_ps(vInt), _MM_SHUFFLE(0, 0, 0, 0)); + // Mask + vTemp = _mm_and_ps(vTemp, Mask); + // x,y and z are unsigned! Flip the bits to convert the order to signed + vTemp = _mm_xor_ps(vTemp, g_XMXorByte4); + // Convert to floating point numbers + vTemp = _mm_cvtepi32_ps(_mm_castps_si128(vTemp)); + // x, y and z - 0x80 to complete the conversion + vTemp = _mm_add_ps(vTemp, g_XMAddByte4); + // Fix y, z and w because they are too large + vTemp = _mm_mul_ps(vTemp, Scale); + // Clamp result (for case of -128) + return _mm_max_ps(vTemp, g_XMNegativeOne); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMLoadByte2(const XMBYTE2* pSource) noexcept +{ + assert(pSource); +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 vResult = { { { + static_cast(pSource->x), + static_cast(pSource->y), + 0.0f, + 0.0f + } } }; + return vResult.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint16x4_t vInt8 = vld1_dup_u16(reinterpret_cast(pSource)); + int16x8_t vInt16 = vmovl_s8(vreinterpret_s8_u16(vInt8)); + int32x4_t vInt = vmovl_s16(vget_low_s16(vInt16)); + vInt = vandq_s32(vInt, g_XMMaskXY); + return vcvtq_f32_s32(vInt); +#elif defined(_XM_SSE_INTRINSICS_) + static const XMVECTORF32 Scale = { { { 1.0f, 1.0f / 256.0f, 1.0f / 65536.0f, 1.0f / (65536.0f * 256.0f) } } }; + static const XMVECTORU32 Mask = { { { 0xFF, 0xFF00, 0, 0 } } }; + // Splat the color in all four entries (x,z,y,w) + __m128i vInt = XM_LOADU_SI16(&pSource->v); + XMVECTOR vTemp = XM_PERMUTE_PS(_mm_castsi128_ps(vInt), _MM_SHUFFLE(0, 0, 0, 0)); + // Mask + vTemp = _mm_and_ps(vTemp, Mask); + // x,y and z are unsigned! Flip the bits to convert the order to signed + vTemp = _mm_xor_ps(vTemp, g_XMXorByte4); + // Convert to floating point numbers + vTemp = _mm_cvtepi32_ps(_mm_castps_si128(vTemp)); + // x, y and z - 0x80 to complete the conversion + vTemp = _mm_add_ps(vTemp, g_XMAddByte4); + // Fix y, z and w because they are too large + return _mm_mul_ps(vTemp, Scale); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMLoadUByteN2(const XMUBYTEN2* pSource) noexcept +{ + assert(pSource); +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 vResult = { { { + static_cast(pSource->x)* (1.0f / 255.0f), + static_cast(pSource->y)* (1.0f / 255.0f), + 0.0f, + 0.0f + } } }; + return vResult.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint16x4_t vInt8 = vld1_dup_u16(reinterpret_cast(pSource)); + uint16x8_t vInt16 = vmovl_u8(vreinterpret_u8_u16(vInt8)); + uint32x4_t vInt = vmovl_u16(vget_low_u16(vInt16)); + vInt = vandq_u32(vInt, g_XMMaskXY); + float32x4_t R = vcvtq_f32_u32(vInt); + return vmulq_n_f32(R, 1.0f / 255.0f); +#elif defined(_XM_SSE_INTRINSICS_) + static const XMVECTORF32 Scale = { { { 1.0f / 255.0f, 1.0f / (255.0f * 256.0f), 0, 0 } } }; + static const XMVECTORU32 Mask = { { { 0xFF, 0xFF00, 0, 0 } } }; + // Splat the color in all four entries (x,z,y,w) + __m128i vInt = XM_LOADU_SI16(&pSource->v); + XMVECTOR vTemp = XM_PERMUTE_PS(_mm_castsi128_ps(vInt), _MM_SHUFFLE(0, 0, 0, 0)); + // Mask + vTemp = _mm_and_ps(vTemp, Mask); + // w is signed! Flip the bits to convert the order to unsigned + vTemp = _mm_xor_ps(vTemp, g_XMFlipW); + // Convert to floating point numbers + vTemp = _mm_cvtepi32_ps(_mm_castps_si128(vTemp)); + // w + 0x80 to complete the conversion + vTemp = _mm_add_ps(vTemp, g_XMAddUDec4); + // Fix y, z and w because they are too large + return _mm_mul_ps(vTemp, Scale); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMLoadUByte2(const XMUBYTE2* pSource) noexcept +{ + assert(pSource); +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 vResult = { { { + static_cast(pSource->x), + static_cast(pSource->y), + 0.0f, + 0.0f + } } }; + return vResult.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint16x4_t vInt8 = vld1_dup_u16(reinterpret_cast(pSource)); + uint16x8_t vInt16 = vmovl_u8(vreinterpret_u8_u16(vInt8)); + uint32x4_t vInt = vmovl_u16(vget_low_u16(vInt16)); + vInt = vandq_u32(vInt, g_XMMaskXY); + return vcvtq_f32_u32(vInt); +#elif defined(_XM_SSE_INTRINSICS_) + static const XMVECTORF32 Scale = { { { 1.0f, 1.0f / 256.0f, 0, 0 } } }; + static const XMVECTORU32 Mask = { { { 0xFF, 0xFF00, 0, 0 } } }; + // Splat the color in all four entries (x,z,y,w) + __m128i vInt = XM_LOADU_SI16(&pSource->v); + XMVECTOR vTemp = XM_PERMUTE_PS(_mm_castsi128_ps(vInt), _MM_SHUFFLE(0, 0, 0, 0)); + // Mask + vTemp = _mm_and_ps(vTemp, Mask); + // w is signed! Flip the bits to convert the order to unsigned + vTemp = _mm_xor_ps(vTemp, g_XMFlipW); + // Convert to floating point numbers + vTemp = _mm_cvtepi32_ps(_mm_castps_si128(vTemp)); + // w + 0x80 to complete the conversion + vTemp = _mm_add_ps(vTemp, g_XMAddUDec4); + // Fix y, z and w because they are too large + return _mm_mul_ps(vTemp, Scale); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMLoadU565(const XMU565* pSource) noexcept +{ + assert(pSource); +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 vResult = { { { + float(pSource->v & 0x1F), + float((pSource->v >> 5) & 0x3F), + float((pSource->v >> 11) & 0x1F), + 0.f, + } } }; + return vResult.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + static const XMVECTORI32 U565And = { { { 0x1F, 0x3F << 5, 0x1F << 11, 0 } } }; + static const XMVECTORF32 U565Mul = { { { 1.0f, 1.0f / 32.0f, 1.0f / 2048.f, 0 } } }; + uint16x4_t vInt16 = vld1_dup_u16(reinterpret_cast(pSource)); + uint32x4_t vInt = vmovl_u16(vInt16); + vInt = vandq_u32(vInt, U565And); + float32x4_t R = vcvtq_f32_u32(vInt); + return vmulq_f32(R, U565Mul); +#elif defined(_XM_SSE_INTRINSICS_) + static const XMVECTORI32 U565And = { { { 0x1F, 0x3F << 5, 0x1F << 11, 0 } } }; + static const XMVECTORF32 U565Mul = { { { 1.0f, 1.0f / 32.0f, 1.0f / 2048.f, 0 } } }; + // Get the 16 bit value and splat it + __m128i vInt = XM_LOADU_SI16(&pSource->v); + XMVECTOR vResult = XM_PERMUTE_PS(_mm_castsi128_ps(vInt), _MM_SHUFFLE(0, 0, 0, 0)); + // Mask off x, y and z + vResult = _mm_and_ps(vResult, U565And); + // Convert to float + vResult = _mm_cvtepi32_ps(_mm_castps_si128(vResult)); + // Normalize x, y, and z + vResult = _mm_mul_ps(vResult, U565Mul); + return vResult; +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMLoadFloat3PK(const XMFLOAT3PK* pSource) noexcept +{ + assert(pSource); + + XM_ALIGNED_DATA(16) uint32_t Result[4]; + uint32_t Mantissa; + uint32_t Exponent; + + // X Channel (6-bit mantissa) + Mantissa = pSource->xm; + + if (pSource->xe == 0x1f) // INF or NAN + { + Result[0] = static_cast(0x7f800000 | (static_cast(pSource->xm) << 17)); + } + else + { + if (pSource->xe != 0) // The value is normalized + { + Exponent = pSource->xe; + } + else if (Mantissa != 0) // The value is denormalized + { + // Normalize the value in the resulting float + Exponent = 1; + + do + { + Exponent--; + Mantissa <<= 1; + } + while ((Mantissa & 0x40) == 0); + + Mantissa &= 0x3F; + } + else // The value is zero + { + Exponent = static_cast(-112); + } + + Result[0] = ((Exponent + 112) << 23) | (Mantissa << 17); + } + + // Y Channel (6-bit mantissa) + Mantissa = pSource->ym; + + if (pSource->ye == 0x1f) // INF or NAN + { + Result[1] = static_cast(0x7f800000 | (static_cast(pSource->ym) << 17)); + } + else + { + if (pSource->ye != 0) // The value is normalized + { + Exponent = pSource->ye; + } + else if (Mantissa != 0) // The value is denormalized + { + // Normalize the value in the resulting float + Exponent = 1; + + do + { + Exponent--; + Mantissa <<= 1; + } + while ((Mantissa & 0x40) == 0); + + Mantissa &= 0x3F; + } + else // The value is zero + { + Exponent = static_cast(-112); + } + + Result[1] = ((Exponent + 112) << 23) | (Mantissa << 17); + } + + // Z Channel (5-bit mantissa) + Mantissa = pSource->zm; + + if (pSource->ze == 0x1f) // INF or NAN + { + Result[2] = static_cast(0x7f800000 | (static_cast(pSource->zm) << 17)); + } + else + { + if (pSource->ze != 0) // The value is normalized + { + Exponent = pSource->ze; + } + else if (Mantissa != 0) // The value is denormalized + { + // Normalize the value in the resulting float + Exponent = 1; + + do + { + Exponent--; + Mantissa <<= 1; + } + while ((Mantissa & 0x20) == 0); + + Mantissa &= 0x1F; + } + else // The value is zero + { + Exponent = static_cast(-112); + } + + Result[2] = ((Exponent + 112) << 23) | (Mantissa << 18); + } + + return XMLoadFloat3A(reinterpret_cast(&Result)); +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMLoadFloat3SE(const XMFLOAT3SE* pSource) noexcept +{ + assert(pSource); + +#if defined(_XM_NO_INTRINSICS_) + + union { float f; int32_t i; } fi; + fi.i = 0x33800000 + (pSource->e << 23); + float Scale = fi.f; + + XMVECTORF32 v = { { { + Scale * float(pSource->xm), + Scale * float(pSource->ym), + Scale * float(pSource->zm), + 1.0f } } }; + return v; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + + // Build scale factor from shared exponent + union { float f; int32_t i; } fi; + fi.i = 0x33800000 + (pSource->e << 23); + + // Extract 9-bit mantissas into vector lanes + uint32x4_t mantissas = vdupq_n_u32(0); + mantissas = vsetq_lane_u32(pSource->xm, mantissas, 0); + mantissas = vsetq_lane_u32(pSource->ym, mantissas, 1); + mantissas = vsetq_lane_u32(pSource->zm, mantissas, 2); + + // Convert to float, scale, and set w = 1.0f + float32x4_t result = vmulq_n_f32(vcvtq_f32_u32(mantissas), fi.f); + return vsetq_lane_f32(1.0f, result, 3); + +#elif defined(_XM_SSE_INTRINSICS_) + + // Build scale factor from shared exponent + union { float f; int32_t i; } fi; + fi.i = 0x33800000 + (pSource->e << 23); + + // Extract 9-bit mantissas, convert to float, and scale + __m128i mantissas = _mm_set_epi32( + 0, + static_cast(pSource->zm), + static_cast(pSource->ym), + static_cast(pSource->xm)); + __m128 result = _mm_mul_ps(_mm_cvtepi32_ps(mantissas), _mm_set1_ps(fi.f)); + + // Set w = 1.0f (w lane is +0.0f so bitwise OR inserts 1.0f cleanly) + return _mm_or_ps(result, g_XMIdentityR3); + +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMLoadHalf4(const XMHALF4* pSource) noexcept +{ + assert(pSource); +#if defined(_XM_F16C_INTRINSICS_) && !defined(_XM_NO_INTRINSICS_) + __m128i V = _mm_loadl_epi64(reinterpret_cast(pSource)); + return _mm_cvtph_ps(V); +#else + XMVECTORF32 vResult = { { { + XMConvertHalfToFloat(pSource->x), + XMConvertHalfToFloat(pSource->y), + XMConvertHalfToFloat(pSource->z), + XMConvertHalfToFloat(pSource->w) + } } }; + return vResult.v; +#endif // !_XM_F16C_INTRINSICS_ +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMLoadShortN4(const XMSHORTN4* pSource) noexcept +{ + assert(pSource); +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 vResult = { { { + (pSource->x == -32768) ? -1.f : (static_cast(pSource->x)* (1.0f / 32767.0f)), + (pSource->y == -32768) ? -1.f : (static_cast(pSource->y)* (1.0f / 32767.0f)), + (pSource->z == -32768) ? -1.f : (static_cast(pSource->z)* (1.0f / 32767.0f)), + (pSource->w == -32768) ? -1.f : (static_cast(pSource->w)* (1.0f / 32767.0f)) + } } }; + return vResult.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + int16x4_t vInt = vld1_s16(reinterpret_cast(pSource)); + int32x4_t V = vmovl_s16(vInt); + float32x4_t vResult = vcvtq_f32_s32(V); + vResult = vmulq_n_f32(vResult, 1.0f / 32767.0f); + return vmaxq_f32(vResult, vdupq_n_f32(-1.f)); +#elif defined(_XM_SSE_INTRINSICS_) + // Splat the color in all four entries (x,z,y,w) + __m128d vIntd = _mm_load1_pd(reinterpret_cast(&pSource->x)); + // Shift x&0ffff,z&0xffff,y&0xffff0000,w&0xffff0000 + __m128 vTemp = _mm_and_ps(_mm_castpd_ps(vIntd), g_XMMaskX16Y16Z16W16); + // x and z are unsigned! Flip the bits to convert the order to signed + vTemp = _mm_xor_ps(vTemp, g_XMFlipX16Y16Z16W16); + // Convert to floating point numbers + vTemp = _mm_cvtepi32_ps(_mm_castps_si128(vTemp)); + // x and z - 0x8000 to complete the conversion + vTemp = _mm_add_ps(vTemp, g_XMFixX16Y16Z16W16); + // Convert to -1.0f - 1.0f + vTemp = _mm_mul_ps(vTemp, g_XMNormalizeX16Y16Z16W16); + // Very important! The entries are x,z,y,w, flip it to x,y,z,w + vTemp = XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(3, 1, 2, 0)); + // Clamp result (for case of -32768) + return _mm_max_ps(vTemp, g_XMNegativeOne); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMLoadShort4(const XMSHORT4* pSource) noexcept +{ + assert(pSource); +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 vResult = { { { + static_cast(pSource->x), + static_cast(pSource->y), + static_cast(pSource->z), + static_cast(pSource->w) + } } }; + return vResult.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + int16x4_t vInt = vld1_s16(reinterpret_cast(pSource)); + int32x4_t V = vmovl_s16(vInt); + return vcvtq_f32_s32(V); +#elif defined(_XM_SSE_INTRINSICS_) + // Splat the color in all four entries (x,z,y,w) + __m128d vIntd = _mm_load1_pd(reinterpret_cast(&pSource->x)); + // Shift x&0ffff,z&0xffff,y&0xffff0000,w&0xffff0000 + __m128 vTemp = _mm_and_ps(_mm_castpd_ps(vIntd), g_XMMaskX16Y16Z16W16); + // x and z are unsigned! Flip the bits to convert the order to signed + vTemp = _mm_xor_ps(vTemp, g_XMFlipX16Y16Z16W16); + // Convert to floating point numbers + vTemp = _mm_cvtepi32_ps(_mm_castps_si128(vTemp)); + // x and z - 0x8000 to complete the conversion + vTemp = _mm_add_ps(vTemp, g_XMFixX16Y16Z16W16); + // Fix y and w because they are 65536 too large + vTemp = _mm_mul_ps(vTemp, g_XMFixupY16W16); + // Very important! The entries are x,z,y,w, flip it to x,y,z,w + return XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(3, 1, 2, 0)); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMLoadUShortN4(const XMUSHORTN4* pSource) noexcept +{ + assert(pSource); +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 vResult = { { { + static_cast(pSource->x) / 65535.0f, + static_cast(pSource->y) / 65535.0f, + static_cast(pSource->z) / 65535.0f, + static_cast(pSource->w) / 65535.0f + } } }; + return vResult.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint16x4_t vInt = vld1_u16(reinterpret_cast(pSource)); + uint32x4_t V = vmovl_u16(vInt); + float32x4_t vResult = vcvtq_f32_u32(V); + return vmulq_n_f32(vResult, 1.0f / 65535.0f); +#elif defined(_XM_SSE_INTRINSICS_) + static const XMVECTORF32 FixupY16W16 = { { { 1.0f / 65535.0f, 1.0f / 65535.0f, 1.0f / (65535.0f * 65536.0f), 1.0f / (65535.0f * 65536.0f) } } }; + static const XMVECTORF32 FixaddY16W16 = { { { 0, 0, 32768.0f * 65536.0f, 32768.0f * 65536.0f } } }; + // Splat the color in all four entries (x,z,y,w) + __m128d vIntd = _mm_load1_pd(reinterpret_cast(&pSource->x)); + // Shift x&0ffff,z&0xffff,y&0xffff0000,w&0xffff0000 + __m128 vTemp = _mm_and_ps(_mm_castpd_ps(vIntd), g_XMMaskX16Y16Z16W16); + // y and w are signed! Flip the bits to convert the order to unsigned + vTemp = _mm_xor_ps(vTemp, g_XMFlipZW); + // Convert to floating point numbers + vTemp = _mm_cvtepi32_ps(_mm_castps_si128(vTemp)); + // y and w + 0x8000 to complete the conversion + vTemp = _mm_add_ps(vTemp, FixaddY16W16); + // Fix y and w because they are 65536 too large + vTemp = _mm_mul_ps(vTemp, FixupY16W16); + // Very important! The entries are x,z,y,w, flip it to x,y,z,w + return XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(3, 1, 2, 0)); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMLoadUShort4(const XMUSHORT4* pSource) noexcept +{ + assert(pSource); +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 vResult = { { { + static_cast(pSource->x), + static_cast(pSource->y), + static_cast(pSource->z), + static_cast(pSource->w) + } } }; + return vResult.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint16x4_t vInt = vld1_u16(reinterpret_cast(pSource)); + uint32x4_t V = vmovl_u16(vInt); + return vcvtq_f32_u32(V); +#elif defined(_XM_SSE_INTRINSICS_) + static const XMVECTORF32 FixaddY16W16 = { { { 0, 0, 32768.0f, 32768.0f } } }; + // Splat the color in all four entries (x,z,y,w) + __m128d vIntd = _mm_load1_pd(reinterpret_cast(&pSource->x)); + // Shift x&0ffff,z&0xffff,y&0xffff0000,w&0xffff0000 + __m128 vTemp = _mm_and_ps(_mm_castpd_ps(vIntd), g_XMMaskX16Y16Z16W16); + // y and w are signed! Flip the bits to convert the order to unsigned + vTemp = _mm_xor_ps(vTemp, g_XMFlipZW); + // Convert to floating point numbers + vTemp = _mm_cvtepi32_ps(_mm_castps_si128(vTemp)); + // Fix y and w because they are 65536 too large + vTemp = _mm_mul_ps(vTemp, g_XMFixupY16W16); + // y and w + 0x8000 to complete the conversion + vTemp = _mm_add_ps(vTemp, FixaddY16W16); + // Very important! The entries are x,z,y,w, flip it to x,y,z,w + return XM_PERMUTE_PS(vTemp, _MM_SHUFFLE(3, 1, 2, 0)); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMLoadXDecN4(const XMXDECN4* pSource) noexcept +{ + assert(pSource); +#if defined(_XM_NO_INTRINSICS_) + static const uint32_t SignExtend[] = { 0x00000000, 0xFFFFFC00 }; + + uint32_t ElementX = pSource->v & 0x3FF; + uint32_t ElementY = (pSource->v >> 10) & 0x3FF; + uint32_t ElementZ = (pSource->v >> 20) & 0x3FF; + + XMVECTORF32 vResult = { { { + (ElementX == 0x200) ? -1.f : (static_cast(static_cast(ElementX | SignExtend[ElementX >> 9])) / 511.0f), + (ElementY == 0x200) ? -1.f : (static_cast(static_cast(ElementY | SignExtend[ElementY >> 9])) / 511.0f), + (ElementZ == 0x200) ? -1.f : (static_cast(static_cast(ElementZ | SignExtend[ElementZ >> 9])) / 511.0f), + static_cast(pSource->v >> 30) / 3.0f + } } }; + return vResult.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x4_t vInt = vld1q_dup_u32(reinterpret_cast(pSource)); + vInt = vandq_u32(vInt, g_XMMaskA2B10G10R10); + vInt = veorq_u32(vInt, g_XMFlipA2B10G10R10); + float32x4_t R = vcvtq_f32_s32(vreinterpretq_s32_u32(vInt)); + R = vaddq_f32(R, g_XMFixAA2B10G10R10); + R = vmulq_f32(R, g_XMNormalizeA2B10G10R10); + return vmaxq_f32(R, vdupq_n_f32(-1.0f)); +#elif defined(_XM_SSE_INTRINSICS_) + // Splat the color in all four entries + __m128 vTemp = _mm_load_ps1(reinterpret_cast(&pSource->v)); + // Shift R&0xFF0000, G&0xFF00, B&0xFF, A&0xFF000000 + vTemp = _mm_and_ps(vTemp, g_XMMaskA2B10G10R10); + // a is unsigned! Flip the bit to convert the order to signed + vTemp = _mm_xor_ps(vTemp, g_XMFlipA2B10G10R10); + // Convert to floating point numbers + vTemp = _mm_cvtepi32_ps(_mm_castps_si128(vTemp)); + // RGB + 0, A + 0x80000000.f to undo the signed order. + vTemp = _mm_add_ps(vTemp, g_XMFixAA2B10G10R10); + // Convert 0-255 to 0.0f-1.0f + vTemp = _mm_mul_ps(vTemp, g_XMNormalizeA2B10G10R10); + // Clamp result (for case of -512) + return _mm_max_ps(vTemp, g_XMNegativeOne); +#endif +} + +//------------------------------------------------------------------------------ +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4996) +// C4996: ignore deprecation warning +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMLoadXDec4(const XMXDEC4* pSource) noexcept +{ + assert(pSource); +#if defined(_XM_NO_INTRINSICS_) + static const uint32_t SignExtend[] = { 0x00000000, 0xFFFFFC00 }; + + uint32_t ElementX = pSource->v & 0x3FF; + uint32_t ElementY = (pSource->v >> 10) & 0x3FF; + uint32_t ElementZ = (pSource->v >> 20) & 0x3FF; + + XMVECTORF32 vResult = { { { + static_cast(static_cast(ElementX | SignExtend[ElementX >> 9])), + static_cast(static_cast(ElementY | SignExtend[ElementY >> 9])), + static_cast(static_cast(ElementZ | SignExtend[ElementZ >> 9])), + static_cast(pSource->v >> 30) + } } }; + return vResult.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + static const XMVECTORU32 XDec4Xor = { { { 0x200, 0x200 << 10, 0x200 << 20, 0x80000000 } } }; + static const XMVECTORF32 XDec4Add = { { { -512.0f, -512.0f * 1024.0f, -512.0f * 1024.0f * 1024.0f, 32768 * 65536.0f } } }; + uint32x4_t vInt = vld1q_dup_u32(reinterpret_cast(pSource)); + vInt = vandq_u32(vInt, g_XMMaskDec4); + vInt = veorq_u32(vInt, XDec4Xor); + float32x4_t R = vcvtq_f32_s32(vreinterpretq_s32_u32(vInt)); + R = vaddq_f32(R, XDec4Add); + return vmulq_f32(R, g_XMMulDec4); +#elif defined(_XM_SSE_INTRINSICS_) + static const XMVECTORU32 XDec4Xor = { { { 0x200, 0x200 << 10, 0x200 << 20, 0x80000000 } } }; + static const XMVECTORF32 XDec4Add = { { { -512.0f, -512.0f * 1024.0f, -512.0f * 1024.0f * 1024.0f, 32768 * 65536.0f } } }; + // Splat the color in all four entries + XMVECTOR vTemp = _mm_load_ps1(reinterpret_cast(&pSource->v)); + // Shift R&0xFF0000, G&0xFF00, B&0xFF, A&0xFF000000 + vTemp = _mm_and_ps(vTemp, g_XMMaskDec4); + // a is unsigned! Flip the bit to convert the order to signed + vTemp = _mm_xor_ps(vTemp, XDec4Xor); + // Convert to floating point numbers + vTemp = _mm_cvtepi32_ps(_mm_castps_si128(vTemp)); + // RGB + 0, A + 0x80000000.f to undo the signed order. + vTemp = _mm_add_ps(vTemp, XDec4Add); + // Convert 0-255 to 0.0f-1.0f + vTemp = _mm_mul_ps(vTemp, g_XMMulDec4); + return vTemp; +#endif +} + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMLoadUDecN4(const XMUDECN4* pSource) noexcept +{ + assert(pSource); +#if defined(_XM_NO_INTRINSICS_) + + uint32_t ElementX = pSource->v & 0x3FF; + uint32_t ElementY = (pSource->v >> 10) & 0x3FF; + uint32_t ElementZ = (pSource->v >> 20) & 0x3FF; + + XMVECTORF32 vResult = { { { + static_cast(ElementX) / 1023.0f, + static_cast(ElementY) / 1023.0f, + static_cast(ElementZ) / 1023.0f, + static_cast(pSource->v >> 30) / 3.0f + } } }; + return vResult.v; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + static const XMVECTORF32 UDecN4Mul = { { { 1.0f / 1023.0f, 1.0f / (1023.0f * 1024.0f), 1.0f / (1023.0f * 1024.0f * 1024.0f), 1.0f / (3.0f * 1024.0f * 1024.0f * 1024.0f) } } }; + uint32x4_t vInt = vld1q_dup_u32(reinterpret_cast(pSource)); + vInt = vandq_u32(vInt, g_XMMaskDec4); + float32x4_t R = vcvtq_f32_u32(vInt); + return vmulq_f32(R, UDecN4Mul); +#elif defined(_XM_SSE_INTRINSICS_) + static const XMVECTORF32 UDecN4Mul = { { { 1.0f / 1023.0f, 1.0f / (1023.0f * 1024.0f), 1.0f / (1023.0f * 1024.0f * 1024.0f), 1.0f / (3.0f * 1024.0f * 1024.0f * 1024.0f) } } }; + // Splat the color in all four entries + XMVECTOR vTemp = _mm_load_ps1(reinterpret_cast(&pSource->v)); + // Shift R&0xFF0000, G&0xFF00, B&0xFF, A&0xFF000000 + vTemp = _mm_and_ps(vTemp, g_XMMaskDec4); + // a is unsigned! Flip the bit to convert the order to signed + vTemp = _mm_xor_ps(vTemp, g_XMFlipW); + // Convert to floating point numbers + vTemp = _mm_cvtepi32_ps(_mm_castps_si128(vTemp)); + // RGB + 0, A + 0x80000000.f to undo the signed order. + vTemp = _mm_add_ps(vTemp, g_XMAddUDec4); + // Convert 0-255 to 0.0f-1.0f + vTemp = _mm_mul_ps(vTemp, UDecN4Mul); + return vTemp; +#endif +} + + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMLoadUDecN4_XR(const XMUDECN4* pSource) noexcept +{ + assert(pSource); +#if defined(_XM_NO_INTRINSICS_) + + int32_t ElementX = pSource->v & 0x3FF; + int32_t ElementY = (pSource->v >> 10) & 0x3FF; + int32_t ElementZ = (pSource->v >> 20) & 0x3FF; + + XMVECTORF32 vResult = { { { + static_cast(ElementX - 0x180) / 510.0f, + static_cast(ElementY - 0x180) / 510.0f, + static_cast(ElementZ - 0x180) / 510.0f, + static_cast(pSource->v >> 30) / 3.0f + } } }; + + return vResult.v; + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + static const XMVECTORF32 XRMul = { { { 1.0f / 510.0f, 1.0f / (510.0f * 1024.0f), 1.0f / (510.0f * 1024.0f * 1024.0f), 1.0f / (3.0f * 1024.0f * 1024.0f * 1024.0f) } } }; + static const XMVECTORI32 XRBias = { { { 0x180, 0x180 * 1024, 0x180 * 1024 * 1024, 0 } } }; + uint32x4_t vInt = vld1q_dup_u32(reinterpret_cast(pSource)); + vInt = vandq_u32(vInt, g_XMMaskDec4); + int32x4_t vTemp = vsubq_s32(vreinterpretq_s32_u32(vInt), XRBias); + vTemp = veorq_s32(vTemp, g_XMFlipW); + float32x4_t R = vcvtq_f32_s32(vTemp); + R = vaddq_f32(R, g_XMAddUDec4); + return vmulq_f32(R, XRMul); +#elif defined(_XM_SSE_INTRINSICS_) + static const XMVECTORF32 XRMul = { { { 1.0f / 510.0f, 1.0f / (510.0f * 1024.0f), 1.0f / (510.0f * 1024.0f * 1024.0f), 1.0f / (3.0f * 1024.0f * 1024.0f * 1024.0f) } } }; + static const XMVECTORI32 XRBias = { { { 0x180, 0x180 * 1024, 0x180 * 1024 * 1024, 0 } } }; + // Splat the color in all four entries + XMVECTOR vTemp = _mm_load_ps1(reinterpret_cast(&pSource->v)); + // Mask channels + vTemp = _mm_and_ps(vTemp, g_XMMaskDec4); + // Subtract bias + vTemp = _mm_castsi128_ps(_mm_sub_epi32(_mm_castps_si128(vTemp), XRBias)); + // a is unsigned! Flip the bit to convert the order to signed + vTemp = _mm_xor_ps(vTemp, g_XMFlipW); + // Convert to floating point numbers + vTemp = _mm_cvtepi32_ps(_mm_castps_si128(vTemp)); + // RGB + 0, A + 0x80000000.f to undo the signed order. + vTemp = _mm_add_ps(vTemp, g_XMAddUDec4); + // Convert to 0.0f-1.0f + return _mm_mul_ps(vTemp, XRMul); +#endif +} + + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMLoadUDec4(const XMUDEC4* pSource) noexcept +{ + assert(pSource); +#if defined(_XM_NO_INTRINSICS_) + uint32_t ElementX = pSource->v & 0x3FF; + uint32_t ElementY = (pSource->v >> 10) & 0x3FF; + uint32_t ElementZ = (pSource->v >> 20) & 0x3FF; + + XMVECTORF32 vResult = { { { + static_cast(ElementX), + static_cast(ElementY), + static_cast(ElementZ), + static_cast(pSource->v >> 30) + } } }; + return vResult.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x4_t vInt = vld1q_dup_u32(reinterpret_cast(pSource)); + vInt = vandq_u32(vInt, g_XMMaskDec4); + float32x4_t R = vcvtq_f32_u32(vInt); + return vmulq_f32(R, g_XMMulDec4); +#elif defined(_XM_SSE_INTRINSICS_) + // Splat the color in all four entries + XMVECTOR vTemp = _mm_load_ps1(reinterpret_cast(&pSource->v)); + // Shift R&0xFF0000, G&0xFF00, B&0xFF, A&0xFF000000 + vTemp = _mm_and_ps(vTemp, g_XMMaskDec4); + // a is unsigned! Flip the bit to convert the order to signed + vTemp = _mm_xor_ps(vTemp, g_XMFlipW); + // Convert to floating point numbers + vTemp = _mm_cvtepi32_ps(_mm_castps_si128(vTemp)); + // RGB + 0, A + 0x80000000.f to undo the signed order. + vTemp = _mm_add_ps(vTemp, g_XMAddUDec4); + // Convert 0-255 to 0.0f-1.0f + vTemp = _mm_mul_ps(vTemp, g_XMMulDec4); + return vTemp; +#endif +} + +//------------------------------------------------------------------------------ +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4996) +// C4996: ignore deprecation warning +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMLoadDecN4(const XMDECN4* pSource) noexcept +{ + assert(pSource); +#if defined(_XM_NO_INTRINSICS_) + static const uint32_t SignExtend[] = { 0x00000000, 0xFFFFFC00 }; + static const uint32_t SignExtendW[] = { 0x00000000, 0xFFFFFFFC }; + + uint32_t ElementX = pSource->v & 0x3FF; + uint32_t ElementY = (pSource->v >> 10) & 0x3FF; + uint32_t ElementZ = (pSource->v >> 20) & 0x3FF; + uint32_t ElementW = pSource->v >> 30; + + XMVECTORF32 vResult = { { { + (ElementX == 0x200) ? -1.f : (static_cast(static_cast(ElementX | SignExtend[ElementX >> 9])) / 511.0f), + (ElementY == 0x200) ? -1.f : (static_cast(static_cast(ElementY | SignExtend[ElementY >> 9])) / 511.0f), + (ElementZ == 0x200) ? -1.f : (static_cast(static_cast(ElementZ | SignExtend[ElementZ >> 9])) / 511.0f), + (ElementW == 0x2) ? -1.f : static_cast(static_cast(ElementW | SignExtendW[(ElementW >> 1) & 1])) + } } }; + return vResult.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + static const XMVECTORF32 DecN4Mul = { { { 1.0f / 511.0f, 1.0f / (511.0f * 1024.0f), 1.0f / (511.0f * 1024.0f * 1024.0f), 1.0f / (1024.0f * 1024.0f * 1024.0f) } } }; + uint32x4_t vInt = vld1q_dup_u32(reinterpret_cast(pSource)); + vInt = vandq_u32(vInt, g_XMMaskDec4); + vInt = veorq_u32(vInt, g_XMXorDec4); + float32x4_t R = vcvtq_f32_s32(vreinterpretq_s32_u32(vInt)); + R = vaddq_f32(R, g_XMAddDec4); + R = vmulq_f32(R, DecN4Mul); + return vmaxq_f32(R, vdupq_n_f32(-1.0f)); +#elif defined(_XM_SSE_INTRINSICS_) + static const XMVECTORF32 DecN4Mul = { { { 1.0f / 511.0f, 1.0f / (511.0f * 1024.0f), 1.0f / (511.0f * 1024.0f * 1024.0f), 1.0f / (1024.0f * 1024.0f * 1024.0f) } } }; + // Splat the color in all four entries + XMVECTOR vTemp = _mm_load_ps1(reinterpret_cast(&pSource->v)); + // Shift R&0xFF0000, G&0xFF00, B&0xFF, A&0xFF000000 + vTemp = _mm_and_ps(vTemp, g_XMMaskDec4); + // a is unsigned! Flip the bit to convert the order to signed + vTemp = _mm_xor_ps(vTemp, g_XMXorDec4); + // Convert to floating point numbers + vTemp = _mm_cvtepi32_ps(_mm_castps_si128(vTemp)); + // RGB + 0, A + 0x80000000.f to undo the signed order. + vTemp = _mm_add_ps(vTemp, g_XMAddDec4); + // Convert 0-255 to 0.0f-1.0f + vTemp = _mm_mul_ps(vTemp, DecN4Mul); + // Clamp result (for case of -512/-1) + return _mm_max_ps(vTemp, g_XMNegativeOne); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMLoadDec4(const XMDEC4* pSource) noexcept +{ + assert(pSource); +#if defined(_XM_NO_INTRINSICS_) + static const uint32_t SignExtend[] = { 0x00000000, 0xFFFFFC00 }; + static const uint32_t SignExtendW[] = { 0x00000000, 0xFFFFFFFC }; + + uint32_t ElementX = pSource->v & 0x3FF; + uint32_t ElementY = (pSource->v >> 10) & 0x3FF; + uint32_t ElementZ = (pSource->v >> 20) & 0x3FF; + uint32_t ElementW = pSource->v >> 30; + + XMVECTORF32 vResult = { { { + static_cast(static_cast(ElementX | SignExtend[ElementX >> 9])), + static_cast(static_cast(ElementY | SignExtend[ElementY >> 9])), + static_cast(static_cast(ElementZ | SignExtend[ElementZ >> 9])), + static_cast(static_cast(ElementW | SignExtendW[ElementW >> 1])) + } } }; + return vResult.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x4_t vInt = vld1q_dup_u32(reinterpret_cast(pSource)); + vInt = vandq_u32(vInt, g_XMMaskDec4); + vInt = veorq_u32(vInt, g_XMXorDec4); + float32x4_t R = vcvtq_f32_s32(vreinterpretq_s32_u32(vInt)); + R = vaddq_f32(R, g_XMAddDec4); + return vmulq_f32(R, g_XMMulDec4); +#elif defined(_XM_SSE_INTRINSICS_) + // Splat the color in all four entries + XMVECTOR vTemp = _mm_load_ps1(reinterpret_cast(&pSource->v)); + // Shift R&0xFF0000, G&0xFF00, B&0xFF, A&0xFF000000 + vTemp = _mm_and_ps(vTemp, g_XMMaskDec4); + // a is unsigned! Flip the bit to convert the order to signed + vTemp = _mm_xor_ps(vTemp, g_XMXorDec4); + // Convert to floating point numbers + vTemp = _mm_cvtepi32_ps(_mm_castps_si128(vTemp)); + // RGB + 0, A + 0x80000000.f to undo the signed order. + vTemp = _mm_add_ps(vTemp, g_XMAddDec4); + // Convert 0-255 to 0.0f-1.0f + vTemp = _mm_mul_ps(vTemp, g_XMMulDec4); + return vTemp; +#endif +} + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMLoadUByteN4(const XMUBYTEN4* pSource) noexcept +{ + assert(pSource); +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 vResult = { { { + static_cast(pSource->x) / 255.0f, + static_cast(pSource->y) / 255.0f, + static_cast(pSource->z) / 255.0f, + static_cast(pSource->w) / 255.0f + } } }; + return vResult.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x2_t vInt8 = vld1_dup_u32(reinterpret_cast(pSource)); + uint16x8_t vInt16 = vmovl_u8(vreinterpret_u8_u32(vInt8)); + uint32x4_t vInt = vmovl_u16(vget_low_u16(vInt16)); + float32x4_t R = vcvtq_f32_u32(vInt); + return vmulq_n_f32(R, 1.0f / 255.0f); +#elif defined(_XM_SSE_INTRINSICS_) + static const XMVECTORF32 LoadUByteN4Mul = { { { 1.0f / 255.0f, 1.0f / (255.0f * 256.0f), 1.0f / (255.0f * 65536.0f), 1.0f / (255.0f * 65536.0f * 256.0f) } } }; + // Splat the color in all four entries (x,z,y,w) + XMVECTOR vTemp = _mm_load1_ps(reinterpret_cast(&pSource->x)); + // Mask x&0ff,y&0xff00,z&0xff0000,w&0xff000000 + vTemp = _mm_and_ps(vTemp, g_XMMaskByte4); + // w is signed! Flip the bits to convert the order to unsigned + vTemp = _mm_xor_ps(vTemp, g_XMFlipW); + // Convert to floating point numbers + vTemp = _mm_cvtepi32_ps(_mm_castps_si128(vTemp)); + // w + 0x80 to complete the conversion + vTemp = _mm_add_ps(vTemp, g_XMAddUDec4); + // Fix y, z and w because they are too large + vTemp = _mm_mul_ps(vTemp, LoadUByteN4Mul); + return vTemp; +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMLoadUByte4(const XMUBYTE4* pSource) noexcept +{ + assert(pSource); +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 vResult = { { { + static_cast(pSource->x), + static_cast(pSource->y), + static_cast(pSource->z), + static_cast(pSource->w) + } } }; + return vResult.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x2_t vInt8 = vld1_dup_u32(reinterpret_cast(pSource)); + uint16x8_t vInt16 = vmovl_u8(vreinterpret_u8_u32(vInt8)); + uint32x4_t vInt = vmovl_u16(vget_low_u16(vInt16)); + return vcvtq_f32_u32(vInt); +#elif defined(_XM_SSE_INTRINSICS_) + static const XMVECTORF32 LoadUByte4Mul = { { { 1.0f, 1.0f / 256.0f, 1.0f / 65536.0f, 1.0f / (65536.0f * 256.0f) } } }; + // Splat the color in all four entries (x,z,y,w) + XMVECTOR vTemp = _mm_load1_ps(reinterpret_cast(&pSource->x)); + // Mask x&0ff,y&0xff00,z&0xff0000,w&0xff000000 + vTemp = _mm_and_ps(vTemp, g_XMMaskByte4); + // w is signed! Flip the bits to convert the order to unsigned + vTemp = _mm_xor_ps(vTemp, g_XMFlipW); + // Convert to floating point numbers + vTemp = _mm_cvtepi32_ps(_mm_castps_si128(vTemp)); + // w + 0x80 to complete the conversion + vTemp = _mm_add_ps(vTemp, g_XMAddUDec4); + // Fix y, z and w because they are too large + vTemp = _mm_mul_ps(vTemp, LoadUByte4Mul); + return vTemp; +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMLoadByteN4(const XMBYTEN4* pSource) noexcept +{ + assert(pSource); +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 vResult = { { { + (pSource->x == -128) ? -1.f : (static_cast(pSource->x) / 127.0f), + (pSource->y == -128) ? -1.f : (static_cast(pSource->y) / 127.0f), + (pSource->z == -128) ? -1.f : (static_cast(pSource->z) / 127.0f), + (pSource->w == -128) ? -1.f : (static_cast(pSource->w) / 127.0f) + } } }; + return vResult.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x2_t vInt8 = vld1_dup_u32(reinterpret_cast(pSource)); + int16x8_t vInt16 = vmovl_s8(vreinterpret_s8_u32(vInt8)); + int32x4_t vInt = vmovl_s16(vget_low_s16(vInt16)); + float32x4_t R = vcvtq_f32_s32(vInt); + R = vmulq_n_f32(R, 1.0f / 127.0f); + return vmaxq_f32(R, vdupq_n_f32(-1.f)); +#elif defined(_XM_SSE_INTRINSICS_) + static const XMVECTORF32 LoadByteN4Mul = { { { 1.0f / 127.0f, 1.0f / (127.0f * 256.0f), 1.0f / (127.0f * 65536.0f), 1.0f / (127.0f * 65536.0f * 256.0f) } } }; + // Splat the color in all four entries (x,z,y,w) + XMVECTOR vTemp = _mm_load1_ps(reinterpret_cast(&pSource->x)); + // Mask x&0ff,y&0xff00,z&0xff0000,w&0xff000000 + vTemp = _mm_and_ps(vTemp, g_XMMaskByte4); + // x,y and z are unsigned! Flip the bits to convert the order to signed + vTemp = _mm_xor_ps(vTemp, g_XMXorByte4); + // Convert to floating point numbers + vTemp = _mm_cvtepi32_ps(_mm_castps_si128(vTemp)); + // x, y and z - 0x80 to complete the conversion + vTemp = _mm_add_ps(vTemp, g_XMAddByte4); + // Fix y, z and w because they are too large + vTemp = _mm_mul_ps(vTemp, LoadByteN4Mul); + // Clamp result (for case of -128) + return _mm_max_ps(vTemp, g_XMNegativeOne); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMLoadByte4(const XMBYTE4* pSource) noexcept +{ + assert(pSource); +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 vResult = { { { + static_cast(pSource->x), + static_cast(pSource->y), + static_cast(pSource->z), + static_cast(pSource->w) + } } }; + return vResult.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + uint32x2_t vInt8 = vld1_dup_u32(reinterpret_cast(pSource)); + int16x8_t vInt16 = vmovl_s8(vreinterpret_s8_u32(vInt8)); + int32x4_t vInt = vmovl_s16(vget_low_s16(vInt16)); + return vcvtq_f32_s32(vInt); +#elif defined(_XM_SSE_INTRINSICS_) + static const XMVECTORF32 LoadByte4Mul = { { { 1.0f, 1.0f / 256.0f, 1.0f / 65536.0f, 1.0f / (65536.0f * 256.0f) } } }; + // Splat the color in all four entries (x,z,y,w) + XMVECTOR vTemp = _mm_load1_ps(reinterpret_cast(&pSource->x)); + // Mask x&0ff,y&0xff00,z&0xff0000,w&0xff000000 + vTemp = _mm_and_ps(vTemp, g_XMMaskByte4); + // x,y and z are unsigned! Flip the bits to convert the order to signed + vTemp = _mm_xor_ps(vTemp, g_XMXorByte4); + // Convert to floating point numbers + vTemp = _mm_cvtepi32_ps(_mm_castps_si128(vTemp)); + // x, y and z - 0x80 to complete the conversion + vTemp = _mm_add_ps(vTemp, g_XMAddByte4); + // Fix y, z and w because they are too large + vTemp = _mm_mul_ps(vTemp, LoadByte4Mul); + return vTemp; +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMLoadUNibble4(const XMUNIBBLE4* pSource) noexcept +{ + assert(pSource); +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 vResult = { { { + float(pSource->v & 0xF), + float((pSource->v >> 4) & 0xF), + float((pSource->v >> 8) & 0xF), + float((pSource->v >> 12) & 0xF) + } } }; + return vResult.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + static const XMVECTORI32 UNibble4And = { { { 0xF, 0xF0, 0xF00, 0xF000 } } }; + static const XMVECTORF32 UNibble4Mul = { { { 1.0f, 1.0f / 16.f, 1.0f / 256.f, 1.0f / 4096.f } } }; + uint16x4_t vInt16 = vld1_dup_u16(reinterpret_cast(pSource)); + uint32x4_t vInt = vmovl_u16(vInt16); + vInt = vandq_u32(vInt, UNibble4And); + float32x4_t R = vcvtq_f32_u32(vInt); + return vmulq_f32(R, UNibble4Mul); +#elif defined(_XM_SSE_INTRINSICS_) + static const XMVECTORI32 UNibble4And = { { { 0xF, 0xF0, 0xF00, 0xF000 } } }; + static const XMVECTORF32 UNibble4Mul = { { { 1.0f, 1.0f / 16.f, 1.0f / 256.f, 1.0f / 4096.f } } }; + // Get the 16 bit value and splat it + __m128i vInt = XM_LOADU_SI16(&pSource->v); + XMVECTOR vResult = XM_PERMUTE_PS(_mm_castsi128_ps(vInt), _MM_SHUFFLE(0, 0, 0, 0)); + // Mask off x, y and z + vResult = _mm_and_ps(vResult, UNibble4And); + // Convert to float + vResult = _mm_cvtepi32_ps(_mm_castps_si128(vResult)); + // Normalize x, y, and z + vResult = _mm_mul_ps(vResult, UNibble4Mul); + return vResult; +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMVECTOR XM_CALLCONV XMLoadU555(const XMU555* pSource) noexcept +{ + assert(pSource); +#if defined(_XM_NO_INTRINSICS_) + XMVECTORF32 vResult = { { { + float(pSource->v & 0x1F), + float((pSource->v >> 5) & 0x1F), + float((pSource->v >> 10) & 0x1F), + float((pSource->v >> 15) & 0x1) + } } }; + return vResult.v; +#elif defined(_XM_ARM_NEON_INTRINSICS_) + static const XMVECTORI32 U555And = { { { 0x1F, 0x1F << 5, 0x1F << 10, 0x8000 } } }; + static const XMVECTORF32 U555Mul = { { { 1.0f, 1.0f / 32.f, 1.0f / 1024.f, 1.0f / 32768.f } } }; + uint16x4_t vInt16 = vld1_dup_u16(reinterpret_cast(pSource)); + uint32x4_t vInt = vmovl_u16(vInt16); + vInt = vandq_u32(vInt, U555And); + float32x4_t R = vcvtq_f32_u32(vInt); + return vmulq_f32(R, U555Mul); +#elif defined(_XM_SSE_INTRINSICS_) + static const XMVECTORI32 U555And = { { { 0x1F, 0x1F << 5, 0x1F << 10, 0x8000 } } }; + static const XMVECTORF32 U555Mul = { { { 1.0f, 1.0f / 32.f, 1.0f / 1024.f, 1.0f / 32768.f } } }; + // Get the 16bit value and splat it + __m128i vInt = XM_LOADU_SI16(&pSource->v); + XMVECTOR vResult = XM_PERMUTE_PS(_mm_castsi128_ps(vInt), _MM_SHUFFLE(0, 0, 0, 0)); + // Mask off x, y and z + vResult = _mm_and_ps(vResult, U555And); + // Convert to float + vResult = _mm_cvtepi32_ps(_mm_castps_si128(vResult)); + // Normalize x, y, and z + vResult = _mm_mul_ps(vResult, U555Mul); + return vResult; +#endif +} + +#ifdef _PREFAST_ +#pragma prefast(pop) +#endif + +/**************************************************************************** + * + * Vector and matrix store operations + * + ****************************************************************************/ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreColor +( + XMCOLOR* pDestination, + FXMVECTOR V +) noexcept +{ + assert(pDestination); +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR N = XMVectorSaturate(V); + N = XMVectorMultiply(N, g_UByteMax); + N = XMVectorRound(N); + + XMFLOAT4A tmp; + XMStoreFloat4A(&tmp, N); + + pDestination->c = (static_cast(tmp.w) << 24) | + (static_cast(tmp.x) << 16) | + (static_cast(tmp.y) << 8) | + static_cast(tmp.z); + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x4_t R = vmaxq_f32(V, vdupq_n_f32(0)); + R = vminq_f32(R, vdupq_n_f32(1.0f)); + R = vmulq_n_f32(R, 255.0f); + R = XMVectorRound(R); + uint32x4_t vInt32 = vcvtq_u32_f32(R); + uint16x4_t vInt16 = vqmovn_u32(vInt32); + uint8x8_t vInt8 = vqmovn_u16(vcombine_u16(vInt16, vInt16)); + uint32_t rgba = vget_lane_u32(vreinterpret_u32_u8(vInt8), 0); + pDestination->c = (rgba & 0xFF00FF00) | ((rgba >> 16) & 0xFF) | ((rgba << 16) & 0xFF0000); +#elif defined(_XM_SSE_INTRINSICS_) + // Set <0 to 0 + XMVECTOR vResult = _mm_max_ps(V, g_XMZero); + // Set>1 to 1 + vResult = _mm_min_ps(vResult, g_XMOne); + // Convert to 0-255 + vResult = _mm_mul_ps(vResult, g_UByteMax); + // Shuffle RGBA to ARGB + vResult = XM_PERMUTE_PS(vResult, _MM_SHUFFLE(3, 0, 1, 2)); + // Convert to int + __m128i vInt = _mm_cvtps_epi32(vResult); + // Mash to shorts + vInt = _mm_packs_epi32(vInt, vInt); + // Mash to bytes + vInt = _mm_packus_epi16(vInt, vInt); + // Store the color + _mm_store_ss(reinterpret_cast(&pDestination->c), _mm_castsi128_ps(vInt)); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreHalf2 +( + XMHALF2* pDestination, + FXMVECTOR V +) noexcept +{ + assert(pDestination); +#if defined(_XM_F16C_INTRINSICS_) && !defined(_XM_NO_INTRINSICS_) + __m128i V1 = _mm_cvtps_ph(V, _MM_FROUND_TO_NEAREST_INT); + _mm_store_ss(reinterpret_cast(pDestination), _mm_castsi128_ps(V1)); +#else + pDestination->x = XMConvertFloatToHalf(XMVectorGetX(V)); + pDestination->y = XMConvertFloatToHalf(XMVectorGetY(V)); +#endif // !_XM_F16C_INTRINSICS_ +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreShortN2 +( + XMSHORTN2* pDestination, + FXMVECTOR V +) noexcept +{ + assert(pDestination); +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR N = XMVectorClamp(V, g_XMNegativeOne.v, g_XMOne.v); + N = XMVectorMultiply(N, g_ShortMax); + N = XMVectorRound(N); + + XMFLOAT4A tmp; + XMStoreFloat4A(&tmp, N); + + pDestination->x = static_cast(tmp.x); + pDestination->y = static_cast(tmp.y); + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x4_t R = vmaxq_f32(V, vdupq_n_f32(-1.f)); + R = vminq_f32(R, vdupq_n_f32(1.0f)); + R = vmulq_n_f32(R, 32767.0f); + int32x4_t vInt32 = vcvtq_s32_f32(R); + int16x4_t vInt16 = vqmovn_s32(vInt32); + vst1_lane_u32(&pDestination->v, vreinterpret_u32_s16(vInt16), 0); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vResult = _mm_max_ps(V, g_XMNegativeOne); + vResult = _mm_min_ps(vResult, g_XMOne); + vResult = _mm_mul_ps(vResult, g_ShortMax); + __m128i vResulti = _mm_cvtps_epi32(vResult); + vResulti = _mm_packs_epi32(vResulti, vResulti); + _mm_store_ss(reinterpret_cast(&pDestination->x), _mm_castsi128_ps(vResulti)); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreShort2 +( + XMSHORT2* pDestination, + FXMVECTOR V +) noexcept +{ + assert(pDestination); +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR N = XMVectorClamp(V, g_ShortMin, g_ShortMax); + N = XMVectorRound(N); + + XMFLOAT4A tmp; + XMStoreFloat4A(&tmp, N); + + pDestination->x = static_cast(tmp.x); + pDestination->y = static_cast(tmp.y); + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x4_t R = vmaxq_f32(V, vdupq_n_f32(-32767.f)); + R = vminq_f32(R, vdupq_n_f32(32767.0f)); + int32x4_t vInt32 = vcvtq_s32_f32(R); + int16x4_t vInt16 = vqmovn_s32(vInt32); + vst1_lane_u32(&pDestination->v, vreinterpret_u32_s16(vInt16), 0); +#elif defined(_XM_SSE_INTRINSICS_) + // Bounds check + XMVECTOR vResult = _mm_max_ps(V, g_ShortMin); + vResult = _mm_min_ps(vResult, g_ShortMax); + // Convert to int with rounding + __m128i vInt = _mm_cvtps_epi32(vResult); + // Pack the ints into shorts + vInt = _mm_packs_epi32(vInt, vInt); + _mm_store_ss(reinterpret_cast(&pDestination->x), _mm_castsi128_ps(vInt)); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreUShortN2 +( + XMUSHORTN2* pDestination, + FXMVECTOR V +) noexcept +{ + assert(pDestination); +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR N = XMVectorSaturate(V); + N = XMVectorMultiplyAdd(N, g_UShortMax, g_XMOneHalf.v); + N = XMVectorTruncate(N); + + XMFLOAT4A tmp; + XMStoreFloat4A(&tmp, N); + + pDestination->x = static_cast(tmp.x); + pDestination->y = static_cast(tmp.y); + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x4_t R = vmaxq_f32(V, vdupq_n_f32(0.f)); + R = vminq_f32(R, vdupq_n_f32(1.0f)); + R = vmulq_n_f32(R, 65535.0f); + R = vaddq_f32(R, g_XMOneHalf); + uint32x4_t vInt32 = vcvtq_u32_f32(R); + uint16x4_t vInt16 = vqmovn_u32(vInt32); + vst1_lane_u32(&pDestination->v, vreinterpret_u32_u16(vInt16), 0); +#elif defined(_XM_SSE_INTRINSICS_) + // Bounds check + XMVECTOR vResult = _mm_max_ps(V, g_XMZero); + vResult = _mm_min_ps(vResult, g_XMOne); + vResult = _mm_mul_ps(vResult, g_UShortMax); + vResult = _mm_add_ps(vResult, g_XMOneHalf); + // Convert to int + __m128i vInt = _mm_cvttps_epi32(vResult); + // Since the SSE pack instruction clamps using signed rules, + // manually extract the values to store them to memory + pDestination->x = static_cast(_mm_extract_epi16(vInt, 0)); + pDestination->y = static_cast(_mm_extract_epi16(vInt, 2)); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreUShort2 +( + XMUSHORT2* pDestination, + FXMVECTOR V +) noexcept +{ + assert(pDestination); +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR N = XMVectorClamp(V, XMVectorZero(), g_UShortMax); + N = XMVectorRound(N); + + XMFLOAT4A tmp; + XMStoreFloat4A(&tmp, N); + + pDestination->x = static_cast(tmp.x); + pDestination->y = static_cast(tmp.y); + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x4_t R = vmaxq_f32(V, vdupq_n_f32(0.f)); + R = vminq_f32(R, vdupq_n_f32(65535.0f)); + uint32x4_t vInt32 = vcvtq_u32_f32(R); + uint16x4_t vInt16 = vqmovn_u32(vInt32); + vst1_lane_u32(&pDestination->v, vreinterpret_u32_u16(vInt16), 0); +#elif defined(_XM_SSE_INTRINSICS_) + // Bounds check + XMVECTOR vResult = _mm_max_ps(V, g_XMZero); + vResult = _mm_min_ps(vResult, g_UShortMax); + // Convert to int with rounding + __m128i vInt = _mm_cvtps_epi32(vResult); + // Since the SSE pack instruction clamps using signed rules, + // manually extract the values to store them to memory + pDestination->x = static_cast(_mm_extract_epi16(vInt, 0)); + pDestination->y = static_cast(_mm_extract_epi16(vInt, 2)); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreByteN2 +( + XMBYTEN2* pDestination, + FXMVECTOR V +) noexcept +{ + assert(pDestination); +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR N = XMVectorClamp(V, g_XMNegativeOne.v, g_XMOne.v); + N = XMVectorMultiply(N, g_ByteMax); + N = XMVectorRound(N); + + XMFLOAT4A tmp; + XMStoreFloat4A(&tmp, N); + + pDestination->x = static_cast(tmp.x); + pDestination->y = static_cast(tmp.y); + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x4_t R = vmaxq_f32(V, vdupq_n_f32(-1.f)); + R = vminq_f32(R, vdupq_n_f32(1.0f)); + R = vmulq_n_f32(R, 127.0f); + int32x4_t vInt32 = vcvtq_s32_f32(R); + int16x4_t vInt16 = vqmovn_s32(vInt32); + int8x8_t vInt8 = vqmovn_s16(vcombine_s16(vInt16, vInt16)); + vst1_lane_u16(reinterpret_cast(pDestination), vreinterpret_u16_s8(vInt8), 0); +#elif defined(_XM_SSE_INTRINSICS_) + // Clamp to bounds + XMVECTOR vResult = _mm_max_ps(V, g_XMNegativeOne); + vResult = _mm_min_ps(vResult, g_XMOne); + // Scale by multiplication + vResult = _mm_mul_ps(vResult, g_ByteMax); + // Convert to int by rounding + __m128i vInt = _mm_cvtps_epi32(vResult); + // No SSE operations will write to 16-bit values, so we have to extract them manually + auto x = static_cast(_mm_extract_epi16(vInt, 0)); + auto y = static_cast(_mm_extract_epi16(vInt, 2)); + pDestination->v = static_cast(((static_cast(y) & 0xFF) << 8) | (static_cast(x) & 0xFF)); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreByte2 +( + XMBYTE2* pDestination, + FXMVECTOR V +) noexcept +{ + assert(pDestination); +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR N = XMVectorClamp(V, g_ByteMin, g_ByteMax); + N = XMVectorRound(N); + + XMFLOAT4A tmp; + XMStoreFloat4A(&tmp, N); + + pDestination->x = static_cast(tmp.x); + pDestination->y = static_cast(tmp.y); + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x4_t R = vmaxq_f32(V, vdupq_n_f32(-127.f)); + R = vminq_f32(R, vdupq_n_f32(127.0f)); + int32x4_t vInt32 = vcvtq_s32_f32(R); + int16x4_t vInt16 = vqmovn_s32(vInt32); + int8x8_t vInt8 = vqmovn_s16(vcombine_s16(vInt16, vInt16)); + vst1_lane_u16(reinterpret_cast(pDestination), vreinterpret_u16_s8(vInt8), 0); +#elif defined(_XM_SSE_INTRINSICS_) + // Clamp to bounds + XMVECTOR vResult = _mm_max_ps(V, g_ByteMin); + vResult = _mm_min_ps(vResult, g_ByteMax); + // Convert to int by rounding + __m128i vInt = _mm_cvtps_epi32(vResult); + // No SSE operations will write to 16-bit values, so we have to extract them manually + auto x = static_cast(_mm_extract_epi16(vInt, 0)); + auto y = static_cast(_mm_extract_epi16(vInt, 2)); + pDestination->v = static_cast(((static_cast(y) & 0xFF) << 8) | (static_cast(x) & 0xFF)); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreUByteN2 +( + XMUBYTEN2* pDestination, + FXMVECTOR V +) noexcept +{ + assert(pDestination); +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR N = XMVectorSaturate(V); + N = XMVectorMultiplyAdd(N, g_UByteMax, g_XMOneHalf.v); + N = XMVectorTruncate(N); + + XMFLOAT4A tmp; + XMStoreFloat4A(&tmp, N); + + pDestination->x = static_cast(tmp.x); + pDestination->y = static_cast(tmp.y); + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x4_t R = vmaxq_f32(V, vdupq_n_f32(0.f)); + R = vminq_f32(R, vdupq_n_f32(1.0f)); + R = vmulq_n_f32(R, 255.0f); + R = vaddq_f32(R, g_XMOneHalf); + uint32x4_t vInt32 = vcvtq_u32_f32(R); + uint16x4_t vInt16 = vqmovn_u32(vInt32); + uint8x8_t vInt8 = vqmovn_u16(vcombine_u16(vInt16, vInt16)); + vst1_lane_u16(reinterpret_cast(pDestination), vreinterpret_u16_u8(vInt8), 0); +#elif defined(_XM_SSE_INTRINSICS_) + // Clamp to bounds + XMVECTOR vResult = _mm_max_ps(V, g_XMZero); + vResult = _mm_min_ps(vResult, g_XMOne); + // Scale by multiplication + vResult = _mm_mul_ps(vResult, g_UByteMax); + vResult = _mm_add_ps(vResult, g_XMOneHalf); + // Convert to int + __m128i vInt = _mm_cvttps_epi32(vResult); + // No SSE operations will write to 16-bit values, so we have to extract them manually + auto x = static_cast(_mm_extract_epi16(vInt, 0)); + auto y = static_cast(_mm_extract_epi16(vInt, 2)); + pDestination->v = static_cast(((static_cast(y) & 0xFF) << 8) | (static_cast(x) & 0xFF)); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreUByte2 +( + XMUBYTE2* pDestination, + FXMVECTOR V +) noexcept +{ + assert(pDestination); +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR N = XMVectorClamp(V, XMVectorZero(), g_UByteMax); + N = XMVectorRound(N); + + XMFLOAT4A tmp; + XMStoreFloat4A(&tmp, N); + + pDestination->x = static_cast(tmp.x); + pDestination->y = static_cast(tmp.y); + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x4_t R = vmaxq_f32(V, vdupq_n_f32(0.f)); + R = vminq_f32(R, vdupq_n_f32(255.0f)); + uint32x4_t vInt32 = vcvtq_u32_f32(R); + uint16x4_t vInt16 = vqmovn_u32(vInt32); + uint8x8_t vInt8 = vqmovn_u16(vcombine_u16(vInt16, vInt16)); + vst1_lane_u16(reinterpret_cast(pDestination), vreinterpret_u16_u8(vInt8), 0); +#elif defined(_XM_SSE_INTRINSICS_) + // Clamp to bounds + XMVECTOR vResult = _mm_max_ps(V, g_XMZero); + vResult = _mm_min_ps(vResult, g_UByteMax); + // Convert to int by rounding + __m128i vInt = _mm_cvtps_epi32(vResult); + // No SSE operations will write to 16-bit values, so we have to extract them manually + auto x = static_cast(_mm_extract_epi16(vInt, 0)); + auto y = static_cast(_mm_extract_epi16(vInt, 2)); + pDestination->v = static_cast(((static_cast(y) & 0xFF) << 8) | (static_cast(x) & 0xFF)); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreU565 +( + XMU565* pDestination, + FXMVECTOR V +) noexcept +{ + assert(pDestination); + static const XMVECTORF32 Max = { { { 31.0f, 63.0f, 31.0f, 0.0f } } }; + +#if defined(_XM_NO_INTRINSICS_) + XMVECTOR N = XMVectorClamp(V, XMVectorZero(), Max.v); + N = XMVectorRound(N); + + XMFLOAT4A tmp; + XMStoreFloat4A(&tmp, N); + + pDestination->v = static_cast( + ((static_cast(tmp.z) & 0x1F) << 11) + | ((static_cast(tmp.y) & 0x3F) << 5) + | ((static_cast(tmp.x) & 0x1F))); +#elif defined(_XM_ARM_NEON_INTRINSICS_) + static const XMVECTORF32 Scale = { { { 1.0f, 32.f, 32.f * 64.f, 0.f } } }; + static const XMVECTORU32 Mask = { { { 0x1F, 0x3F << 5, 0x1F << 11, 0 } } }; + float32x4_t vResult = vmaxq_f32(V, vdupq_n_f32(0)); + vResult = vminq_f32(vResult, Max); + vResult = vmulq_f32(vResult, Scale); + uint32x4_t vResulti = vcvtq_u32_f32(vResult); + vResulti = vandq_u32(vResulti, Mask); + // Do a horizontal or of 4 entries + uint32x2_t vTemp = vget_low_u32(vResulti); + uint32x2_t vhi = vget_high_u32(vResulti); + vTemp = vorr_u32(vTemp, vhi); + vTemp = vpadd_u32(vTemp, vTemp); + vst1_lane_u16(&pDestination->v, vreinterpret_u16_u32(vTemp), 0); +#elif defined(_XM_SSE_INTRINSICS_) + // Bounds check + XMVECTOR vResult = _mm_max_ps(V, g_XMZero); + vResult = _mm_min_ps(vResult, Max); + // Convert to int with rounding + __m128i vInt = _mm_cvtps_epi32(vResult); + // No SSE operations will write to 16-bit values, so we have to extract them manually + auto x = static_cast(_mm_extract_epi16(vInt, 0)); + auto y = static_cast(_mm_extract_epi16(vInt, 2)); + auto z = static_cast(_mm_extract_epi16(vInt, 4)); + pDestination->v = static_cast( + ((static_cast(z) & 0x1F) << 11) + | ((static_cast(y) & 0x3F) << 5) + | ((static_cast(x) & 0x1F))); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreFloat3PK +( + XMFLOAT3PK* pDestination, + FXMVECTOR V +) noexcept +{ + assert(pDestination); + + XM_ALIGNED_DATA(16) uint32_t IValue[4]; + XMStoreFloat3A(reinterpret_cast(&IValue), V); + + uint32_t Result[3]; + + // X & Y Channels (5-bit exponent, 6-bit mantissa) + for (uint32_t j = 0; j < 2; ++j) + { + uint32_t Sign = IValue[j] & 0x80000000; + uint32_t I = IValue[j] & 0x7FFFFFFF; + + if ((I & 0x7F800000) == 0x7F800000) + { + // INF or NAN + Result[j] = 0x7C0U; + if ((I & 0x7FFFFF) != 0) + { + Result[j] = 0x7FFU; + } + else if (Sign) + { + // -INF is clamped to 0 since 3PK is positive only + Result[j] = 0; + } + } + else if (Sign || I < 0x35800000) + { + // 3PK is positive only, so clamp to zero + Result[j] = 0; + } + else if (I > 0x477E0000U) + { + // The number is too large to be represented as a float11, set to max + Result[j] = 0x7BFU; + } + else + { + if (I < 0x38800000U) + { + // The number is too small to be represented as a normalized float11 + // Convert it to a denormalized value. + uint32_t Shift = 113U - (I >> 23U); + I = (0x800000U | (I & 0x7FFFFFU)) >> Shift; + } + else + { + // Rebias the exponent to represent the value as a normalized float11 + I += 0xC8000000U; + } + + Result[j] = ((I + 0xFFFFU + ((I >> 17U) & 1U)) >> 17U) & 0x7ffU; + } + } + + // Z Channel (5-bit exponent, 5-bit mantissa) + uint32_t Sign = IValue[2] & 0x80000000; + uint32_t I = IValue[2] & 0x7FFFFFFF; + + if ((I & 0x7F800000) == 0x7F800000) + { + // INF or NAN + Result[2] = 0x3E0U; + if (I & 0x7FFFFF) + { + Result[2] = 0x3FFU; + } + else if (Sign || I < 0x36000000) + { + // -INF is clamped to 0 since 3PK is positive only + Result[2] = 0; + } + } + else if (Sign) + { + // 3PK is positive only, so clamp to zero + Result[2] = 0; + } + else if (I > 0x477C0000U) + { + // The number is too large to be represented as a float10, set to max + Result[2] = 0x3DFU; + } + else + { + if (I < 0x38800000U) + { + // The number is too small to be represented as a normalized float10 + // Convert it to a denormalized value. + uint32_t Shift = 113U - (I >> 23U); + I = (0x800000U | (I & 0x7FFFFFU)) >> Shift; + } + else + { + // Rebias the exponent to represent the value as a normalized float10 + I += 0xC8000000U; + } + + Result[2] = ((I + 0x1FFFFU + ((I >> 18U) & 1U)) >> 18U) & 0x3ffU; + } + + // Pack Result into memory + pDestination->v = (Result[0] & 0x7ff) + | ((Result[1] & 0x7ff) << 11) + | ((Result[2] & 0x3ff) << 22); +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreFloat3SE +( + XMFLOAT3SE* pDestination, + FXMVECTOR V +) noexcept +{ + assert(pDestination); + +#if defined(_XM_NO_INTRINSICS_) + + XMFLOAT3A tmp; + XMStoreFloat3A(&tmp, V); + + static constexpr float maxf9 = float(0x1FF << 7); + static constexpr float minf9 = float(1.f / (1 << 16)); + + float x = (tmp.x >= 0.f) ? ((tmp.x > maxf9) ? maxf9 : tmp.x) : 0.f; + float y = (tmp.y >= 0.f) ? ((tmp.y > maxf9) ? maxf9 : tmp.y) : 0.f; + float z = (tmp.z >= 0.f) ? ((tmp.z > maxf9) ? maxf9 : tmp.z) : 0.f; + + const float max_xy = (x > y) ? x : y; + const float max_xyz = (max_xy > z) ? max_xy : z; + + const float maxColor = (max_xyz > minf9) ? max_xyz : minf9; + + union { float f; int32_t i; } fi; + fi.f = maxColor; + fi.i += 0x00004000; // round up leaving 9 bits in fraction (including assumed 1) + + auto exp = static_cast(fi.i) >> 23; + pDestination->e = exp - 0x6f; + + fi.i = static_cast(0x83000000 - (exp << 23)); + float ScaleR = fi.f; + + pDestination->xm = static_cast(MathInternal::round_to_nearest(x * ScaleR)); + pDestination->ym = static_cast(MathInternal::round_to_nearest(y * ScaleR)); + pDestination->zm = static_cast(MathInternal::round_to_nearest(z * ScaleR)); + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + + static const XMVECTORF32 MaxFloat9 = { { { float(0x1FF << 7), float(0x1FF << 7), float(0x1FF << 7), float(0x1FF << 7) } } }; + static constexpr float minf9 = float(1.f / (1 << 16)); + + // Clamp to [0, maxf9] then zero w lane + float32x4_t clamped = vminq_f32(vmaxq_f32(V, vdupq_n_f32(0)), MaxFloat9); + clamped = vsetq_lane_f32(0.0f, clamped, 3); + + // Horizontal max of xyz for shared exponent +#if defined(_M_ARM64) || defined(_M_HYBRID_X86_ARM64) || defined(_M_ARM64EC) || __aarch64__ + float maxVal = vmaxvq_f32(clamped); +#else + float32x2_t vlow = vget_low_f32(clamped); + float32x2_t vhigh = vget_high_f32(clamped); + float32x2_t maxPair = vpmax_f32(vlow, vhigh); + maxPair = vpmax_f32(maxPair, maxPair); + float maxVal = vget_lane_f32(maxPair, 0); +#endif + + if (maxVal < minf9) maxVal = minf9; + + // Compute shared exponent (inherently scalar) + union { float f; int32_t i; } fi; + fi.f = maxVal; + fi.i += 0x00004000; // round up leaving 9 bits in fraction (including assumed 1) + + auto exp = static_cast(fi.i) >> 23; + fi.i = static_cast(0x83000000 - (exp << 23)); + + // Scale all channels and convert to integer + float32x4_t scaled = vmulq_n_f32(clamped, fi.f); +#if defined(_M_ARM64) || defined(_M_HYBRID_X86_ARM64) || defined(_M_ARM64EC) || __aarch64__ + uint32x4_t ints = vcvtnq_u32_f32(scaled); +#else + scaled = vaddq_f32(scaled, vdupq_n_f32(0.5f)); + uint32x4_t ints = vcvtq_u32_f32(scaled); +#endif + + // Extract and pack into bitfields + pDestination->xm = vgetq_lane_u32(ints, 0) & 0x1FF; + pDestination->ym = vgetq_lane_u32(ints, 1) & 0x1FF; + pDestination->zm = vgetq_lane_u32(ints, 2) & 0x1FF; + pDestination->e = exp - 0x6f; + +#elif defined(_XM_SSE_INTRINSICS_) + + static const XMVECTORF32 MaxFloat9 = { { { float(0x1FF << 7), float(0x1FF << 7), float(0x1FF << 7), float(0x1FF << 7) } } }; + static constexpr float minf9 = float(1.f / (1 << 16)); + + // Clamp to [0, maxf9] then mask w to zero + __m128 clamped = _mm_min_ps(_mm_max_ps(V, _mm_setzero_ps()), MaxFloat9); + clamped = _mm_and_ps(clamped, g_XMMask3); + + // Horizontal max of xyz for shared exponent + __m128 maxV = clamped; + __m128 temp = XM_PERMUTE_PS(maxV, _MM_SHUFFLE(1, 1, 1, 1)); + maxV = _mm_max_ps(maxV, temp); + temp = XM_PERMUTE_PS(clamped, _MM_SHUFFLE(2, 2, 2, 2)); + maxV = _mm_max_ps(maxV, temp); + + // Ensure minimum threshold + maxV = _mm_max_ss(maxV, _mm_set_ss(minf9)); + + // Compute shared exponent (inherently scalar) + union { float f; int32_t i; } fi; + _mm_store_ss(&fi.f, maxV); + fi.i += 0x00004000; // round up leaving 9 bits in fraction (including assumed 1) + + auto exp = static_cast(fi.i) >> 23; + fi.i = static_cast(0x83000000 - (exp << 23)); + + // Scale all channels and round to nearest integer + __m128 scaled = _mm_mul_ps(clamped, _mm_set1_ps(fi.f)); + __m128i ints = _mm_cvtps_epi32(scaled); + + // Extract and pack into bitfields + XM_ALIGNED_DATA(16) uint32_t ivals[4]; + _mm_store_si128(reinterpret_cast<__m128i*>(ivals), ints); + + pDestination->xm = ivals[0] & 0x1FF; + pDestination->ym = ivals[1] & 0x1FF; + pDestination->zm = ivals[2] & 0x1FF; + pDestination->e = exp - 0x6f; +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreHalf4 +( + XMHALF4* pDestination, + FXMVECTOR V +) noexcept +{ + assert(pDestination); +#if defined(_XM_F16C_INTRINSICS_) && !defined(_XM_NO_INTRINSICS_) + __m128i V1 = _mm_cvtps_ph(V, _MM_FROUND_TO_NEAREST_INT); + _mm_storel_epi64(reinterpret_cast<__m128i*>(pDestination), V1); +#else + XMFLOAT4A t; + XMStoreFloat4A(&t, V); + + pDestination->x = XMConvertFloatToHalf(t.x); + pDestination->y = XMConvertFloatToHalf(t.y); + pDestination->z = XMConvertFloatToHalf(t.z); + pDestination->w = XMConvertFloatToHalf(t.w); +#endif // !_XM_F16C_INTRINSICS_ +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreShortN4 +( + XMSHORTN4* pDestination, + FXMVECTOR V +) noexcept +{ + assert(pDestination); +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR N = XMVectorClamp(V, g_XMNegativeOne.v, g_XMOne.v); + N = XMVectorMultiply(N, g_ShortMax); + N = XMVectorRound(N); + + XMFLOAT4A tmp; + XMStoreFloat4A(&tmp, N); + + pDestination->x = static_cast(tmp.x); + pDestination->y = static_cast(tmp.y); + pDestination->z = static_cast(tmp.z); + pDestination->w = static_cast(tmp.w); + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x4_t vResult = vmaxq_f32(V, vdupq_n_f32(-1.f)); + vResult = vminq_f32(vResult, vdupq_n_f32(1.0f)); + vResult = vmulq_n_f32(vResult, 32767.0f); + int16x4_t vInt = vmovn_s32(vcvtq_s32_f32(vResult)); + vst1_s16(reinterpret_cast(pDestination), vInt); +#elif defined(_XM_SSE_INTRINSICS_) + XMVECTOR vResult = _mm_max_ps(V, g_XMNegativeOne); + vResult = _mm_min_ps(vResult, g_XMOne); + vResult = _mm_mul_ps(vResult, g_ShortMax); + __m128i vResulti = _mm_cvtps_epi32(vResult); + vResulti = _mm_packs_epi32(vResulti, vResulti); + _mm_store_sd(reinterpret_cast(&pDestination->x), _mm_castsi128_pd(vResulti)); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreShort4 +( + XMSHORT4* pDestination, + FXMVECTOR V +) noexcept +{ + assert(pDestination); +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR N = XMVectorClamp(V, g_ShortMin, g_ShortMax); + N = XMVectorRound(N); + + XMFLOAT4A tmp; + XMStoreFloat4A(&tmp, N); + + pDestination->x = static_cast(tmp.x); + pDestination->y = static_cast(tmp.y); + pDestination->z = static_cast(tmp.z); + pDestination->w = static_cast(tmp.w); + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x4_t vResult = vmaxq_f32(V, g_ShortMin); + vResult = vminq_f32(vResult, g_ShortMax); + int16x4_t vInt = vmovn_s32(vcvtq_s32_f32(vResult)); + vst1_s16(reinterpret_cast(pDestination), vInt); +#elif defined(_XM_SSE_INTRINSICS_) + // Bounds check + XMVECTOR vResult = _mm_max_ps(V, g_ShortMin); + vResult = _mm_min_ps(vResult, g_ShortMax); + // Convert to int with rounding + __m128i vInt = _mm_cvtps_epi32(vResult); + // Pack the ints into shorts + vInt = _mm_packs_epi32(vInt, vInt); + _mm_store_sd(reinterpret_cast(&pDestination->x), _mm_castsi128_pd(vInt)); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreUShortN4 +( + XMUSHORTN4* pDestination, + FXMVECTOR V +) noexcept +{ + assert(pDestination); +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR N = XMVectorSaturate(V); + N = XMVectorMultiplyAdd(N, g_UShortMax, g_XMOneHalf.v); + N = XMVectorTruncate(N); + + XMFLOAT4A tmp; + XMStoreFloat4A(&tmp, N); + + pDestination->x = static_cast(tmp.x); + pDestination->y = static_cast(tmp.y); + pDestination->z = static_cast(tmp.z); + pDestination->w = static_cast(tmp.w); + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x4_t vResult = vmaxq_f32(V, vdupq_n_f32(0)); + vResult = vminq_f32(vResult, vdupq_n_f32(1.0f)); + vResult = vmulq_n_f32(vResult, 65535.0f); + vResult = vaddq_f32(vResult, g_XMOneHalf); + uint16x4_t vInt = vmovn_u32(vcvtq_u32_f32(vResult)); + vst1_u16(reinterpret_cast(pDestination), vInt); +#elif defined(_XM_SSE_INTRINSICS_) + // Bounds check + XMVECTOR vResult = _mm_max_ps(V, g_XMZero); + vResult = _mm_min_ps(vResult, g_XMOne); + vResult = _mm_mul_ps(vResult, g_UShortMax); + vResult = _mm_add_ps(vResult, g_XMOneHalf); + // Convert to int + __m128i vInt = _mm_cvttps_epi32(vResult); + // Since the SSE pack instruction clamps using signed rules, + // manually extract the values to store them to memory + pDestination->x = static_cast(_mm_extract_epi16(vInt, 0)); + pDestination->y = static_cast(_mm_extract_epi16(vInt, 2)); + pDestination->z = static_cast(_mm_extract_epi16(vInt, 4)); + pDestination->w = static_cast(_mm_extract_epi16(vInt, 6)); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreUShort4 +( + XMUSHORT4* pDestination, + FXMVECTOR V +) noexcept +{ + assert(pDestination); +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR N = XMVectorClamp(V, XMVectorZero(), g_UShortMax); + N = XMVectorRound(N); + + XMFLOAT4A tmp; + XMStoreFloat4A(&tmp, N); + + pDestination->x = static_cast(tmp.x); + pDestination->y = static_cast(tmp.y); + pDestination->z = static_cast(tmp.z); + pDestination->w = static_cast(tmp.w); + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x4_t vResult = vmaxq_f32(V, vdupq_n_f32(0)); + vResult = vminq_f32(vResult, g_UShortMax); + uint16x4_t vInt = vmovn_u32(vcvtq_u32_f32(vResult)); + vst1_u16(reinterpret_cast(pDestination), vInt); +#elif defined(_XM_SSE_INTRINSICS_) + // Bounds check + XMVECTOR vResult = _mm_max_ps(V, g_XMZero); + vResult = _mm_min_ps(vResult, g_UShortMax); + // Convert to int with rounding + __m128i vInt = _mm_cvtps_epi32(vResult); + // Since the SSE pack instruction clamps using signed rules, + // manually extract the values to store them to memory + pDestination->x = static_cast(_mm_extract_epi16(vInt, 0)); + pDestination->y = static_cast(_mm_extract_epi16(vInt, 2)); + pDestination->z = static_cast(_mm_extract_epi16(vInt, 4)); + pDestination->w = static_cast(_mm_extract_epi16(vInt, 6)); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreXDecN4 +( + XMXDECN4* pDestination, + FXMVECTOR V +) noexcept +{ + assert(pDestination); + static const XMVECTORF32 Min = { { { -1.0f, -1.0f, -1.0f, 0.0f } } }; + +#if defined(_XM_NO_INTRINSICS_) + + static const XMVECTORF32 Scale = { { { 511.0f, 511.0f, 511.0f, 3.0f } } }; + + XMVECTOR N = XMVectorClamp(V, Min.v, g_XMOne.v); + N = XMVectorMultiply(N, Scale.v); + N = XMVectorRound(N); + + XMFLOAT4A tmp; + XMStoreFloat4A(&tmp, N); + + pDestination->v = static_cast( + (static_cast(tmp.w) << 30) + | ((static_cast(tmp.z) & 0x3FF) << 20) + | ((static_cast(tmp.y) & 0x3FF) << 10) + | (static_cast(tmp.x) & 0x3FF)); + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + static const XMVECTORF32 Scale = { { { 511.0f, 511.0f * 1024.0f, 511.0f * 1048576.0f, 3.0f * 536870912.0f } } }; + static const XMVECTORI32 ScaleMask = { { { 0x3FF, 0x3FF << 10, 0x3FF << 20, 0x3 << 29 } } }; + float32x4_t vResult = vmaxq_f32(V, Min); + vResult = vminq_f32(vResult, vdupq_n_f32(1.0f)); + vResult = vmulq_f32(vResult, Scale); + int32x4_t vResulti = vcvtq_s32_f32(vResult); + vResulti = vandq_s32(vResulti, ScaleMask); + int32x4_t vResultw = vandq_s32(vResulti, g_XMMaskW); + vResulti = vaddq_s32(vResulti, vResultw); + // Do a horizontal or of all 4 entries + uint32x2_t vTemp = vget_low_u32(vreinterpretq_u32_s32(vResulti)); + uint32x2_t vhi = vget_high_u32(vreinterpretq_u32_s32(vResulti)); + vTemp = vorr_u32(vTemp, vhi); + vTemp = vpadd_u32(vTemp, vTemp); + vst1_lane_u32(&pDestination->v, vTemp, 0); +#elif defined(_XM_SSE_INTRINSICS_) + static const XMVECTORF32 Scale = { { { 511.0f, 511.0f * 1024.0f, 511.0f * 1048576.0f, 3.0f * 536870912.0f } } }; + static const XMVECTORI32 ScaleMask = { { { 0x3FF, 0x3FF << 10, 0x3FF << 20, 0x3 << 29 } } }; + XMVECTOR vResult = _mm_max_ps(V, Min); + vResult = _mm_min_ps(vResult, g_XMOne); + // Scale by multiplication + vResult = _mm_mul_ps(vResult, Scale); + // Convert to int (W is unsigned) + __m128i vResulti = _mm_cvtps_epi32(vResult); + // Mask off any fraction + vResulti = _mm_and_si128(vResulti, ScaleMask); + // To fix W, add itself to shift it up to <<30 instead of <<29 + __m128i vResultw = _mm_and_si128(vResulti, g_XMMaskW); + vResulti = _mm_add_epi32(vResulti, vResultw); + // Do a horizontal or of all 4 entries + vResult = XM_PERMUTE_PS(_mm_castsi128_ps(vResulti), _MM_SHUFFLE(0, 3, 2, 1)); + vResulti = _mm_or_si128(vResulti, _mm_castps_si128(vResult)); + vResult = XM_PERMUTE_PS(vResult, _MM_SHUFFLE(0, 3, 2, 1)); + vResulti = _mm_or_si128(vResulti, _mm_castps_si128(vResult)); + vResult = XM_PERMUTE_PS(vResult, _MM_SHUFFLE(0, 3, 2, 1)); + vResulti = _mm_or_si128(vResulti, _mm_castps_si128(vResult)); + _mm_store_ss(reinterpret_cast(&pDestination->v), _mm_castsi128_ps(vResulti)); +#endif +} + +//------------------------------------------------------------------------------ +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4996) +// C4996: ignore deprecation warning +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreXDec4 +( + XMXDEC4* pDestination, + FXMVECTOR V +) noexcept +{ + assert(pDestination); + static const XMVECTORF32 MinXDec4 = { { { -511.0f, -511.0f, -511.0f, 0.0f } } }; + static const XMVECTORF32 MaxXDec4 = { { { 511.0f, 511.0f, 511.0f, 3.0f } } }; + +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR N = XMVectorClamp(V, MinXDec4, MaxXDec4); + + XMFLOAT4A tmp; + XMStoreFloat4A(&tmp, N); + + pDestination->v = static_cast( + (static_cast(tmp.w) << 30) + | ((static_cast(tmp.z) & 0x3FF) << 20) + | ((static_cast(tmp.y) & 0x3FF) << 10) + | ((static_cast(tmp.x) & 0x3FF))); + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + static const XMVECTORF32 ScaleXDec4 = { { { 1.0f, 1024.0f / 2.0f, 1024.0f * 1024.0f, 1024.0f * 1024.0f * 1024.0f / 2.0f } } }; + static const XMVECTORI32 MaskXDec4 = { { { 0x3FF, 0x3FF << (10 - 1), 0x3FF << 20, 0x3 << (30 - 1) } } }; + float32x4_t vResult = vmaxq_f32(V, MinXDec4); + vResult = vminq_f32(vResult, MaxXDec4); + vResult = vmulq_f32(vResult, ScaleXDec4); + int32x4_t vResulti = vcvtq_s32_f32(vResult); + vResulti = vandq_s32(vResulti, MaskXDec4); + // Do a horizontal or of 4 entries + uint32x2_t vTemp = vget_low_u32(vreinterpretq_u32_s32(vResulti)); + uint32x2_t vTemp2 = vget_high_u32(vreinterpretq_u32_s32(vResulti)); + vTemp = vorr_u32(vTemp, vTemp2); + // Perform a single bit left shift on y|w + vTemp2 = vdup_lane_u32(vTemp, 1); + vTemp2 = vadd_u32(vTemp2, vTemp2); + vTemp = vorr_u32(vTemp, vTemp2); + vst1_lane_u32(&pDestination->v, vTemp, 0); +#elif defined(_XM_SSE_INTRINSICS_) + static const XMVECTORF32 ScaleXDec4 = { { { 1.0f, 1024.0f / 2.0f, 1024.0f * 1024.0f, 1024.0f * 1024.0f * 1024.0f / 2.0f } } }; + static const XMVECTORI32 MaskXDec4 = { { { 0x3FF, 0x3FF << (10 - 1), 0x3FF << 20, 0x3 << (30 - 1) } } }; + // Clamp to bounds + XMVECTOR vResult = _mm_max_ps(V, MinXDec4); + vResult = _mm_min_ps(vResult, MaxXDec4); + // Scale by multiplication + vResult = _mm_mul_ps(vResult, ScaleXDec4); + // Convert to int + __m128i vResulti = _mm_cvttps_epi32(vResult); + // Mask off any fraction + vResulti = _mm_and_si128(vResulti, MaskXDec4); + // Do a horizontal or of 4 entries + __m128i vResulti2 = _mm_shuffle_epi32(vResulti, _MM_SHUFFLE(3, 2, 3, 2)); + // x = x|z, y = y|w + vResulti = _mm_or_si128(vResulti, vResulti2); + // Move Z to the x position + vResulti2 = _mm_shuffle_epi32(vResulti, _MM_SHUFFLE(1, 1, 1, 1)); + // Perform a single bit left shift on y|w + vResulti2 = _mm_add_epi32(vResulti2, vResulti2); + // i = x|y|z|w + vResulti = _mm_or_si128(vResulti, vResulti2); + _mm_store_ss(reinterpret_cast(&pDestination->v), _mm_castsi128_ps(vResulti)); +#endif +} + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreUDecN4 +( + XMUDECN4* pDestination, + FXMVECTOR V +) noexcept +{ + assert(pDestination); +#if defined(_XM_NO_INTRINSICS_) + + static const XMVECTORF32 Scale = { { { 1023.0f, 1023.0f, 1023.0f, 3.0f } } }; + + XMVECTOR N = XMVectorSaturate(V); + N = XMVectorMultiply(N, Scale.v); + + XMFLOAT4A tmp; + XMStoreFloat4A(&tmp, N); + + pDestination->v = static_cast( + (static_cast(tmp.w) << 30) + | ((static_cast(tmp.z) & 0x3FF) << 20) + | ((static_cast(tmp.y) & 0x3FF) << 10) + | ((static_cast(tmp.x) & 0x3FF))); + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + static const XMVECTORF32 ScaleUDecN4 = { { { 1023.0f, 1023.0f * 1024.0f * 0.5f, 1023.0f * 1024.0f * 1024.0f, 3.0f * 1024.0f * 1024.0f * 1024.0f * 0.5f } } }; + static const XMVECTORI32 MaskUDecN4 = { { { 0x3FF, 0x3FF << (10 - 1), 0x3FF << 20, 0x3 << (30 - 1) } } }; + float32x4_t vResult = vmaxq_f32(V, vdupq_n_f32(0.f)); + vResult = vminq_f32(vResult, vdupq_n_f32(1.f)); + vResult = vmulq_f32(vResult, ScaleUDecN4); + uint32x4_t vResulti = vcvtq_u32_f32(vResult); + vResulti = vandq_u32(vResulti, MaskUDecN4); + // Do a horizontal or of 4 entries + uint32x2_t vTemp = vget_low_u32(vResulti); + uint32x2_t vTemp2 = vget_high_u32(vResulti); + vTemp = vorr_u32(vTemp, vTemp2); + // Perform a single bit left shift on y|w + vTemp2 = vdup_lane_u32(vTemp, 1); + vTemp2 = vadd_u32(vTemp2, vTemp2); + vTemp = vorr_u32(vTemp, vTemp2); + vst1_lane_u32(&pDestination->v, vTemp, 0); +#elif defined(_XM_SSE_INTRINSICS_) + static const XMVECTORF32 ScaleUDecN4 = { { { 1023.0f, 1023.0f * 1024.0f * 0.5f, 1023.0f * 1024.0f * 1024.0f, 3.0f * 1024.0f * 1024.0f * 1024.0f * 0.5f } } }; + static const XMVECTORI32 MaskUDecN4 = { { { 0x3FF, 0x3FF << (10 - 1), 0x3FF << 20, 0x3 << (30 - 1) } } }; + // Clamp to bounds + XMVECTOR vResult = _mm_max_ps(V, g_XMZero); + vResult = _mm_min_ps(vResult, g_XMOne); + // Scale by multiplication + vResult = _mm_mul_ps(vResult, ScaleUDecN4); + // Convert to int + __m128i vResulti = _mm_cvttps_epi32(vResult); + // Mask off any fraction + vResulti = _mm_and_si128(vResulti, MaskUDecN4); + // Do a horizontal or of 4 entries + __m128i vResulti2 = _mm_shuffle_epi32(vResulti, _MM_SHUFFLE(3, 2, 3, 2)); + // x = x|z, y = y|w + vResulti = _mm_or_si128(vResulti, vResulti2); + // Move Z to the x position + vResulti2 = _mm_shuffle_epi32(vResulti, _MM_SHUFFLE(1, 1, 1, 1)); + // Perform a left shift by one bit on y|w + vResulti2 = _mm_add_epi32(vResulti2, vResulti2); + // i = x|y|z|w + vResulti = _mm_or_si128(vResulti, vResulti2); + _mm_store_ss(reinterpret_cast(&pDestination->v), _mm_castsi128_ps(vResulti)); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreUDecN4_XR +( + XMUDECN4* pDestination, + FXMVECTOR V +) noexcept +{ + assert(pDestination); + static const XMVECTORF32 Scale = { { { 510.0f, 510.0f, 510.0f, 3.0f } } }; + static const XMVECTORF32 Bias = { { { 384.0f, 384.0f, 384.0f, 0.0f } } }; + static const XMVECTORF32 C = { { { 1023.f, 1023.f, 1023.f, 3.f } } }; + +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR N = XMVectorMultiplyAdd(V, Scale, Bias); + N = XMVectorClamp(N, g_XMZero, C); + + XMFLOAT4A tmp; + XMStoreFloat4A(&tmp, N); + + pDestination->v = static_cast( + (static_cast(tmp.w) << 30) + | ((static_cast(tmp.z) & 0x3FF) << 20) + | ((static_cast(tmp.y) & 0x3FF) << 10) + | ((static_cast(tmp.x) & 0x3FF))); + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + static const XMVECTORF32 Shift = { { { 1.0f, 1024.0f * 0.5f, 1024.0f * 1024.0f, 1024.0f * 1024.0f * 1024.0f * 0.5f } } }; + static const XMVECTORU32 MaskUDecN4 = { { { 0x3FF, 0x3FF << (10 - 1), 0x3FF << 20, 0x3 << (30 - 1) } } }; + float32x4_t vResult = vmlaq_f32(Bias, V, Scale); + vResult = vmaxq_f32(vResult, vdupq_n_f32(0.f)); + vResult = vminq_f32(vResult, C); + vResult = vmulq_f32(vResult, Shift); + uint32x4_t vResulti = vcvtq_u32_f32(vResult); + vResulti = vandq_u32(vResulti, MaskUDecN4); + // Do a horizontal or of 4 entries + uint32x2_t vTemp = vget_low_u32(vResulti); + uint32x2_t vTemp2 = vget_high_u32(vResulti); + vTemp = vorr_u32(vTemp, vTemp2); + // Perform a single bit left shift on y|w + vTemp2 = vdup_lane_u32(vTemp, 1); + vTemp2 = vadd_u32(vTemp2, vTemp2); + vTemp = vorr_u32(vTemp, vTemp2); + vst1_lane_u32(&pDestination->v, vTemp, 0); +#elif defined(_XM_SSE_INTRINSICS_) + static const XMVECTORF32 Shift = { { { 1.0f, 1024.0f * 0.5f, 1024.0f * 1024.0f, 1024.0f * 1024.0f * 1024.0f * 0.5f } } }; + static const XMVECTORU32 MaskUDecN4 = { { { 0x3FF, 0x3FF << (10 - 1), 0x3FF << 20, 0x3 << (30 - 1) } } }; + // Scale & bias + XMVECTOR vResult = XM_FMADD_PS(V, Scale, Bias); + // Clamp to bounds + vResult = _mm_max_ps(vResult, g_XMZero); + vResult = _mm_min_ps(vResult, C); + // Scale by shift values + vResult = _mm_mul_ps(vResult, Shift); + // Convert to int + __m128i vResulti = _mm_cvttps_epi32(vResult); + // Mask off any fraction + vResulti = _mm_and_si128(vResulti, MaskUDecN4); + // Do a horizontal or of 4 entries + __m128i vResulti2 = _mm_shuffle_epi32(vResulti, _MM_SHUFFLE(3, 2, 3, 2)); + // x = x|z, y = y|w + vResulti = _mm_or_si128(vResulti, vResulti2); + // Move Z to the x position + vResulti2 = _mm_shuffle_epi32(vResulti, _MM_SHUFFLE(1, 1, 1, 1)); + // Perform a left shift by one bit on y|w + vResulti2 = _mm_add_epi32(vResulti2, vResulti2); + // i = x|y|z|w + vResulti = _mm_or_si128(vResulti, vResulti2); + _mm_store_ss(reinterpret_cast(&pDestination->v), _mm_castsi128_ps(vResulti)); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreUDec4 +( + XMUDEC4* pDestination, + FXMVECTOR V +) noexcept +{ + assert(pDestination); + static const XMVECTORF32 MaxUDec4 = { { { 1023.0f, 1023.0f, 1023.0f, 3.0f } } }; + +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR N = XMVectorClamp(V, XMVectorZero(), MaxUDec4); + + XMFLOAT4A tmp; + XMStoreFloat4A(&tmp, N); + + pDestination->v = static_cast( + (static_cast(tmp.w) << 30) + | ((static_cast(tmp.z) & 0x3FF) << 20) + | ((static_cast(tmp.y) & 0x3FF) << 10) + | ((static_cast(tmp.x) & 0x3FF))); + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + static const XMVECTORF32 ScaleUDec4 = { { { 1.0f, 1024.0f / 2.0f, 1024.0f * 1024.0f, 1024.0f * 1024.0f * 1024.0f / 2.0f } } }; + static const XMVECTORI32 MaskUDec4 = { { { 0x3FF, 0x3FF << (10 - 1), 0x3FF << 20, 0x3 << (30 - 1) } } }; + float32x4_t vResult = vmaxq_f32(V, vdupq_n_f32(0.f)); + vResult = vminq_f32(vResult, MaxUDec4); + vResult = vmulq_f32(vResult, ScaleUDec4); + uint32x4_t vResulti = vcvtq_u32_f32(vResult); + vResulti = vandq_u32(vResulti, MaskUDec4); + // Do a horizontal or of 4 entries + uint32x2_t vTemp = vget_low_u32(vResulti); + uint32x2_t vTemp2 = vget_high_u32(vResulti); + vTemp = vorr_u32(vTemp, vTemp2); + // Perform a single bit left shift on y|w + vTemp2 = vdup_lane_u32(vTemp, 1); + vTemp2 = vadd_u32(vTemp2, vTemp2); + vTemp = vorr_u32(vTemp, vTemp2); + vst1_lane_u32(&pDestination->v, vTemp, 0); +#elif defined(_XM_SSE_INTRINSICS_) + static const XMVECTORF32 ScaleUDec4 = { { { 1.0f, 1024.0f / 2.0f, 1024.0f * 1024.0f, 1024.0f * 1024.0f * 1024.0f / 2.0f } } }; + static const XMVECTORI32 MaskUDec4 = { { { 0x3FF, 0x3FF << (10 - 1), 0x3FF << 20, 0x3 << (30 - 1) } } }; + // Clamp to bounds + XMVECTOR vResult = _mm_max_ps(V, g_XMZero); + vResult = _mm_min_ps(vResult, MaxUDec4); + // Scale by multiplication + vResult = _mm_mul_ps(vResult, ScaleUDec4); + // Convert to int + __m128i vResulti = _mm_cvttps_epi32(vResult); + // Mask off any fraction + vResulti = _mm_and_si128(vResulti, MaskUDec4); + // Do a horizontal or of 4 entries + __m128i vResulti2 = _mm_shuffle_epi32(vResulti, _MM_SHUFFLE(3, 2, 3, 2)); + // x = x|z, y = y|w + vResulti = _mm_or_si128(vResulti, vResulti2); + // Move Z to the x position + vResulti2 = _mm_shuffle_epi32(vResulti, _MM_SHUFFLE(1, 1, 1, 1)); + // Perform a left shift by one bit on y|w + vResulti2 = _mm_add_epi32(vResulti2, vResulti2); + // i = x|y|z|w + vResulti = _mm_or_si128(vResulti, vResulti2); + _mm_store_ss(reinterpret_cast(&pDestination->v), _mm_castsi128_ps(vResulti)); +#endif +} + +//------------------------------------------------------------------------------ +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4996) +// C4996: ignore deprecation warning +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreDecN4 +( + XMDECN4* pDestination, + FXMVECTOR V +) noexcept +{ + assert(pDestination); +#if defined(_XM_NO_INTRINSICS_) + + static const XMVECTORF32 Scale = { { { 511.0f, 511.0f, 511.0f, 1.0f } } }; + + XMVECTOR N = XMVectorClamp(V, g_XMNegativeOne.v, g_XMOne.v); + N = XMVectorMultiply(N, Scale.v); + + XMFLOAT4A tmp; + XMStoreFloat4A(&tmp, N); + + pDestination->v = static_cast( + (static_cast(tmp.w) << 30) + | ((static_cast(tmp.z) & 0x3FF) << 20) + | ((static_cast(tmp.y) & 0x3FF) << 10) + | ((static_cast(tmp.x) & 0x3FF))); + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + static const XMVECTORF32 ScaleDecN4 = { { { 511.0f, 511.0f * 1024.0f, 511.0f * 1024.0f * 1024.0f, 1.0f * 1024.0f * 1024.0f * 1024.0f } } }; + float32x4_t vResult = vmaxq_f32(V, vdupq_n_f32(-1.f)); + vResult = vminq_f32(vResult, vdupq_n_f32(1.f)); + vResult = vmulq_f32(vResult, ScaleDecN4); + int32x4_t vResulti = vcvtq_s32_f32(vResult); + vResulti = vandq_s32(vResulti, g_XMMaskDec4); + // Do a horizontal or of 4 entries + uint32x2_t vTemp = vget_low_u32(vreinterpretq_u32_s32(vResulti)); + uint32x2_t vhi = vget_high_u32(vreinterpretq_u32_s32(vResulti)); + vTemp = vorr_u32(vTemp, vhi); + vTemp = vpadd_u32(vTemp, vTemp); + vst1_lane_u32(&pDestination->v, vTemp, 0); +#elif defined(_XM_SSE_INTRINSICS_) + static const XMVECTORF32 ScaleDecN4 = { { { 511.0f, 511.0f * 1024.0f, 511.0f * 1024.0f * 1024.0f, 1.0f * 1024.0f * 1024.0f * 1024.0f } } }; + // Clamp to bounds + XMVECTOR vResult = _mm_max_ps(V, g_XMNegativeOne); + vResult = _mm_min_ps(vResult, g_XMOne); + // Scale by multiplication + vResult = _mm_mul_ps(vResult, ScaleDecN4); + // Convert to int + __m128i vResulti = _mm_cvttps_epi32(vResult); + // Mask off any fraction + vResulti = _mm_and_si128(vResulti, g_XMMaskDec4); + // Do a horizontal or of 4 entries + __m128i vResulti2 = _mm_shuffle_epi32(vResulti, _MM_SHUFFLE(3, 2, 3, 2)); + // x = x|z, y = y|w + vResulti = _mm_or_si128(vResulti, vResulti2); + // Move Z to the x position + vResulti2 = _mm_shuffle_epi32(vResulti, _MM_SHUFFLE(1, 1, 1, 1)); + // i = x|y|z|w + vResulti = _mm_or_si128(vResulti, vResulti2); + _mm_store_ss(reinterpret_cast(&pDestination->v), _mm_castsi128_ps(vResulti)); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreDec4 +( + XMDEC4* pDestination, + FXMVECTOR V +) noexcept +{ + assert(pDestination); + static const XMVECTORF32 MinDec4 = { { { -511.0f, -511.0f, -511.0f, -1.0f } } }; + static const XMVECTORF32 MaxDec4 = { { { 511.0f, 511.0f, 511.0f, 1.0f } } }; + +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR N = XMVectorClamp(V, MinDec4, MaxDec4); + + XMFLOAT4A tmp; + XMStoreFloat4A(&tmp, N); + + pDestination->v = static_cast( + (static_cast(tmp.w) << 30) + | ((static_cast(tmp.z) & 0x3FF) << 20) + | ((static_cast(tmp.y) & 0x3FF) << 10) + | ((static_cast(tmp.x) & 0x3FF))); + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + static const XMVECTORF32 ScaleDec4 = { { { 1.0f, 1024.0f, 1024.0f * 1024.0f, 1024.0f * 1024.0f * 1024.0f } } }; + float32x4_t vResult = vmaxq_f32(V, MinDec4); + vResult = vminq_f32(vResult, MaxDec4); + vResult = vmulq_f32(vResult, ScaleDec4); + int32x4_t vResulti = vcvtq_s32_f32(vResult); + vResulti = vandq_s32(vResulti, g_XMMaskDec4); + // Do a horizontal or of all 4 entries + uint32x2_t vTemp = vget_low_u32(vreinterpretq_u32_s32(vResulti)); + uint32x2_t vhi = vget_high_u32(vreinterpretq_u32_s32(vResulti)); + vTemp = vorr_u32(vTemp, vhi); + vTemp = vpadd_u32(vTemp, vTemp); + vst1_lane_u32(&pDestination->v, vTemp, 0); +#elif defined(_XM_SSE_INTRINSICS_) + static const XMVECTORF32 ScaleDec4 = { { { 1.0f, 1024.0f, 1024.0f * 1024.0f, 1024.0f * 1024.0f * 1024.0f } } }; + // Clamp to bounds + XMVECTOR vResult = _mm_max_ps(V, MinDec4); + vResult = _mm_min_ps(vResult, MaxDec4); + // Scale by multiplication + vResult = _mm_mul_ps(vResult, ScaleDec4); + // Convert to int + __m128i vResulti = _mm_cvttps_epi32(vResult); + // Mask off any fraction + vResulti = _mm_and_si128(vResulti, g_XMMaskDec4); + // Do a horizontal or of 4 entries + __m128i vResulti2 = _mm_shuffle_epi32(vResulti, _MM_SHUFFLE(3, 2, 3, 2)); + // x = x|z, y = y|w + vResulti = _mm_or_si128(vResulti, vResulti2); + // Move Z to the x position + vResulti2 = _mm_shuffle_epi32(vResulti, _MM_SHUFFLE(1, 1, 1, 1)); + // i = x|y|z|w + vResulti = _mm_or_si128(vResulti, vResulti2); + _mm_store_ss(reinterpret_cast(&pDestination->v), _mm_castsi128_ps(vResulti)); +#endif +} + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreUByteN4 +( + XMUBYTEN4* pDestination, + FXMVECTOR V +) noexcept +{ + assert(pDestination); +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR N = XMVectorSaturate(V); + N = XMVectorMultiply(N, g_UByteMax); + N = XMVectorTruncate(N); + + XMFLOAT4A tmp; + XMStoreFloat4A(&tmp, N); + + pDestination->x = static_cast(tmp.x); + pDestination->y = static_cast(tmp.y); + pDestination->z = static_cast(tmp.z); + pDestination->w = static_cast(tmp.w); + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x4_t R = vmaxq_f32(V, vdupq_n_f32(0)); + R = vminq_f32(R, vdupq_n_f32(1.0f)); + R = vmulq_n_f32(R, 255.0f); + uint32x4_t vInt32 = vcvtq_u32_f32(R); + uint16x4_t vInt16 = vqmovn_u32(vInt32); + uint8x8_t vInt8 = vqmovn_u16(vcombine_u16(vInt16, vInt16)); + vst1_lane_u32(&pDestination->v, vreinterpret_u32_u8(vInt8), 0); +#elif defined(_XM_SSE_INTRINSICS_) + static const XMVECTORF32 ScaleUByteN4 = { { { 255.0f, 255.0f * 256.0f * 0.5f, 255.0f * 256.0f * 256.0f, 255.0f * 256.0f * 256.0f * 256.0f * 0.5f } } }; + static const XMVECTORI32 MaskUByteN4 = { { { 0xFF, 0xFF << (8 - 1), 0xFF << 16, 0xFF << (24 - 1) } } }; + // Clamp to bounds + XMVECTOR vResult = _mm_max_ps(V, g_XMZero); + vResult = _mm_min_ps(vResult, g_XMOne); + // Scale by multiplication + vResult = _mm_mul_ps(vResult, ScaleUByteN4); + // Convert to int + __m128i vResulti = _mm_cvttps_epi32(vResult); + // Mask off any fraction + vResulti = _mm_and_si128(vResulti, MaskUByteN4); + // Do a horizontal or of 4 entries + __m128i vResulti2 = _mm_shuffle_epi32(vResulti, _MM_SHUFFLE(3, 2, 3, 2)); + // x = x|z, y = y|w + vResulti = _mm_or_si128(vResulti, vResulti2); + // Move Z to the x position + vResulti2 = _mm_shuffle_epi32(vResulti, _MM_SHUFFLE(1, 1, 1, 1)); + // Perform a single bit left shift to fix y|w + vResulti2 = _mm_add_epi32(vResulti2, vResulti2); + // i = x|y|z|w + vResulti = _mm_or_si128(vResulti, vResulti2); + _mm_store_ss(reinterpret_cast(&pDestination->v), _mm_castsi128_ps(vResulti)); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreUByte4 +( + XMUBYTE4* pDestination, + FXMVECTOR V +) noexcept +{ + assert(pDestination); +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR N = XMVectorClamp(V, XMVectorZero(), g_UByteMax); + N = XMVectorRound(N); + + XMFLOAT4A tmp; + XMStoreFloat4A(&tmp, N); + + pDestination->x = static_cast(tmp.x); + pDestination->y = static_cast(tmp.y); + pDestination->z = static_cast(tmp.z); + pDestination->w = static_cast(tmp.w); + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x4_t R = vmaxq_f32(V, vdupq_n_f32(0)); + R = vminq_f32(R, vdupq_n_f32(255.0f)); + uint32x4_t vInt32 = vcvtq_u32_f32(R); + uint16x4_t vInt16 = vqmovn_u32(vInt32); + uint8x8_t vInt8 = vqmovn_u16(vcombine_u16(vInt16, vInt16)); + vst1_lane_u32(&pDestination->v, vreinterpret_u32_u8(vInt8), 0); +#elif defined(_XM_SSE_INTRINSICS_) + static const XMVECTORF32 ScaleUByte4 = { { { 1.0f, 256.0f * 0.5f, 256.0f * 256.0f, 256.0f * 256.0f * 256.0f * 0.5f } } }; + static const XMVECTORI32 MaskUByte4 = { { { 0xFF, 0xFF << (8 - 1), 0xFF << 16, 0xFF << (24 - 1) } } }; + // Clamp to bounds + XMVECTOR vResult = _mm_max_ps(V, g_XMZero); + vResult = _mm_min_ps(vResult, g_UByteMax); + // Scale by multiplication + vResult = _mm_mul_ps(vResult, ScaleUByte4); + // Convert to int by rounding + __m128i vResulti = _mm_cvtps_epi32(vResult); + // Mask off any fraction + vResulti = _mm_and_si128(vResulti, MaskUByte4); + // Do a horizontal or of 4 entries + __m128i vResulti2 = _mm_shuffle_epi32(vResulti, _MM_SHUFFLE(3, 2, 3, 2)); + // x = x|z, y = y|w + vResulti = _mm_or_si128(vResulti, vResulti2); + // Move Z to the x position + vResulti2 = _mm_shuffle_epi32(vResulti, _MM_SHUFFLE(1, 1, 1, 1)); + // Perform a single bit left shift to fix y|w + vResulti2 = _mm_add_epi32(vResulti2, vResulti2); + // i = x|y|z|w + vResulti = _mm_or_si128(vResulti, vResulti2); + _mm_store_ss(reinterpret_cast(&pDestination->v), _mm_castsi128_ps(vResulti)); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreByteN4 +( + XMBYTEN4* pDestination, + FXMVECTOR V +) noexcept +{ + assert(pDestination); +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR N = XMVectorClamp(V, g_XMNegativeOne.v, g_XMOne.v); + N = XMVectorMultiply(N, g_ByteMax); + N = XMVectorTruncate(N); + + XMFLOAT4A tmp; + XMStoreFloat4A(&tmp, N); + + pDestination->x = static_cast(tmp.x); + pDestination->y = static_cast(tmp.y); + pDestination->z = static_cast(tmp.z); + pDestination->w = static_cast(tmp.w); + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x4_t R = vmaxq_f32(V, vdupq_n_f32(-1.f)); + R = vminq_f32(R, vdupq_n_f32(1.0f)); + R = vmulq_n_f32(R, 127.0f); + int32x4_t vInt32 = vcvtq_s32_f32(R); + int16x4_t vInt16 = vqmovn_s32(vInt32); + int8x8_t vInt8 = vqmovn_s16(vcombine_s16(vInt16, vInt16)); + vst1_lane_u32(&pDestination->v, vreinterpret_u32_s8(vInt8), 0); +#elif defined(_XM_SSE_INTRINSICS_) + static const XMVECTORF32 ScaleByteN4 = { { { 127.0f, 127.0f * 256.0f, 127.0f * 256.0f * 256.0f, 127.0f * 256.0f * 256.0f * 256.0f } } }; + static const XMVECTORI32 MaskByteN4 = { { { 0xFF, 0xFF << 8, 0xFF << 16, static_cast(0xFF000000) } } }; + // Clamp to bounds + XMVECTOR vResult = _mm_max_ps(V, g_XMNegativeOne); + vResult = _mm_min_ps(vResult, g_XMOne); + // Scale by multiplication + vResult = _mm_mul_ps(vResult, ScaleByteN4); + // Convert to int + __m128i vResulti = _mm_cvttps_epi32(vResult); + // Mask off any fraction + vResulti = _mm_and_si128(vResulti, MaskByteN4); + // Do a horizontal or of 4 entries + __m128i vResulti2 = _mm_shuffle_epi32(vResulti, _MM_SHUFFLE(3, 2, 3, 2)); + // x = x|z, y = y|w + vResulti = _mm_or_si128(vResulti, vResulti2); + // Move Z to the x position + vResulti2 = _mm_shuffle_epi32(vResulti, _MM_SHUFFLE(1, 1, 1, 1)); + // i = x|y|z|w + vResulti = _mm_or_si128(vResulti, vResulti2); + _mm_store_ss(reinterpret_cast(&pDestination->v), _mm_castsi128_ps(vResulti)); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreByte4 +( + XMBYTE4* pDestination, + FXMVECTOR V +) noexcept +{ + assert(pDestination); +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR N = XMVectorClamp(V, g_ByteMin, g_ByteMax); + N = XMVectorRound(N); + + XMFLOAT4A tmp; + XMStoreFloat4A(&tmp, N); + + pDestination->x = static_cast(tmp.x); + pDestination->y = static_cast(tmp.y); + pDestination->z = static_cast(tmp.z); + pDestination->w = static_cast(tmp.w); + +#elif defined(_XM_ARM_NEON_INTRINSICS_) + float32x4_t R = vmaxq_f32(V, vdupq_n_f32(-127.f)); + R = vminq_f32(R, vdupq_n_f32(127.f)); + int32x4_t vInt32 = vcvtq_s32_f32(R); + int16x4_t vInt16 = vqmovn_s32(vInt32); + int8x8_t vInt8 = vqmovn_s16(vcombine_s16(vInt16, vInt16)); + vst1_lane_u32(&pDestination->v, vreinterpret_u32_s8(vInt8), 0); +#elif defined(_XM_SSE_INTRINSICS_) + static const XMVECTORF32 ScaleByte4 = { { { 1.0f, 256.0f, 256.0f * 256.0f, 256.0f * 256.0f * 256.0f } } }; + static const XMVECTORI32 MaskByte4 = { { { 0xFF, 0xFF << 8, 0xFF << 16, static_cast(0xFF000000) } } }; + // Clamp to bounds + XMVECTOR vResult = _mm_max_ps(V, g_ByteMin); + vResult = _mm_min_ps(vResult, g_ByteMax); + // Scale by multiplication + vResult = _mm_mul_ps(vResult, ScaleByte4); + // Convert to int by rounding + __m128i vResulti = _mm_cvtps_epi32(vResult); + // Mask off any fraction + vResulti = _mm_and_si128(vResulti, MaskByte4); + // Do a horizontal or of 4 entries + __m128i vResulti2 = _mm_shuffle_epi32(vResulti, _MM_SHUFFLE(3, 2, 3, 2)); + // x = x|z, y = y|w + vResulti = _mm_or_si128(vResulti, vResulti2); + // Move Z to the x position + vResulti2 = _mm_shuffle_epi32(vResulti, _MM_SHUFFLE(1, 1, 1, 1)); + // i = x|y|z|w + vResulti = _mm_or_si128(vResulti, vResulti2); + _mm_store_ss(reinterpret_cast(&pDestination->v), _mm_castsi128_ps(vResulti)); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreUNibble4 +( + XMUNIBBLE4* pDestination, + FXMVECTOR V +) noexcept +{ + assert(pDestination); + static const XMVECTORF32 Max = { { { 15.0f, 15.0f, 15.0f, 15.0f } } }; +#if defined(_XM_NO_INTRINSICS_) + + XMVECTOR N = XMVectorClamp(V, XMVectorZero(), Max.v); + N = XMVectorRound(N); + + XMFLOAT4A tmp; + XMStoreFloat4A(&tmp, N); + + pDestination->v = static_cast( + ((static_cast(tmp.w) & 0xF) << 12) + | ((static_cast(tmp.z) & 0xF) << 8) + | ((static_cast(tmp.y) & 0xF) << 4) + | (static_cast(tmp.x) & 0xF)); +#elif defined(_XM_ARM_NEON_INTRINSICS_) + static const XMVECTORF32 Scale = { { { 1.0f, 16.f, 16.f * 16.f, 16.f * 16.f * 16.f } } }; + static const XMVECTORU32 Mask = { { { 0xF, 0xF << 4, 0xF << 8, 0xF << 12 } } }; + float32x4_t vResult = vmaxq_f32(V, vdupq_n_f32(0)); + vResult = vminq_f32(vResult, Max); + vResult = vmulq_f32(vResult, Scale); + uint32x4_t vResulti = vcvtq_u32_f32(vResult); + vResulti = vandq_u32(vResulti, Mask); + // Do a horizontal or of 4 entries + uint32x2_t vTemp = vget_low_u32(vResulti); + uint32x2_t vhi = vget_high_u32(vResulti); + vTemp = vorr_u32(vTemp, vhi); + vTemp = vpadd_u32(vTemp, vTemp); + vst1_lane_u16(&pDestination->v, vreinterpret_u16_u32(vTemp), 0); +#elif defined(_XM_SSE_INTRINSICS_) + // Bounds check + XMVECTOR vResult = _mm_max_ps(V, g_XMZero); + vResult = _mm_min_ps(vResult, Max); + // Convert to int with rounding + __m128i vInt = _mm_cvtps_epi32(vResult); + // No SSE operations will write to 16-bit values, so we have to extract them manually + auto x = static_cast(_mm_extract_epi16(vInt, 0)); + auto y = static_cast(_mm_extract_epi16(vInt, 2)); + auto z = static_cast(_mm_extract_epi16(vInt, 4)); + auto w = static_cast(_mm_extract_epi16(vInt, 6)); + pDestination->v = static_cast( + ((static_cast(w) & 0xF) << 12) + | ((static_cast(z) & 0xF) << 8) + | ((static_cast(y) & 0xF) << 4) + | ((static_cast(x) & 0xF))); +#endif +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline void XM_CALLCONV XMStoreU555 +( + XMU555* pDestination, + FXMVECTOR V +) noexcept +{ + assert(pDestination); + static const XMVECTORF32 Max = { { { 31.0f, 31.0f, 31.0f, 1.0f } } }; + +#if defined(_XM_NO_INTRINSICS_) + XMVECTOR N = XMVectorClamp(V, XMVectorZero(), Max.v); + N = XMVectorRound(N); + + XMFLOAT4A tmp; + XMStoreFloat4A(&tmp, N); + + pDestination->v = static_cast( + ((tmp.w > 0.f) ? 0x8000 : 0) + | ((static_cast(tmp.z) & 0x1F) << 10) + | ((static_cast(tmp.y) & 0x1F) << 5) + | (static_cast(tmp.x) & 0x1F)); +#elif defined(_XM_ARM_NEON_INTRINSICS_) + static const XMVECTORF32 Scale = { { { 1.0f, 32.f / 2.f, 32.f * 32.f, 32.f * 32.f * 32.f / 2.f } } }; + static const XMVECTORU32 Mask = { { { 0x1F, 0x1F << (5 - 1), 0x1F << 10, 0x1 << (15 - 1) } } }; + float32x4_t vResult = vmaxq_f32(V, vdupq_n_f32(0)); + vResult = vminq_f32(vResult, Max); + vResult = vmulq_f32(vResult, Scale); + uint32x4_t vResulti = vcvtq_u32_f32(vResult); + vResulti = vandq_u32(vResulti, Mask); + // Do a horizontal or of 4 entries + uint32x2_t vTemp = vget_low_u32(vResulti); + uint32x2_t vTemp2 = vget_high_u32(vResulti); + vTemp = vorr_u32(vTemp, vTemp2); + // Perform a single bit left shift on y|w + vTemp2 = vdup_lane_u32(vTemp, 1); + vTemp2 = vadd_u32(vTemp2, vTemp2); + vTemp = vorr_u32(vTemp, vTemp2); + vst1_lane_u16(&pDestination->v, vreinterpret_u16_u32(vTemp), 0); +#elif defined(_XM_SSE_INTRINSICS_) + // Bounds check + XMVECTOR vResult = _mm_max_ps(V, g_XMZero); + vResult = _mm_min_ps(vResult, Max); + // Convert to int with rounding + __m128i vInt = _mm_cvtps_epi32(vResult); + // No SSE operations will write to 16-bit values, so we have to extract them manually + auto x = static_cast(_mm_extract_epi16(vInt, 0)); + auto y = static_cast(_mm_extract_epi16(vInt, 2)); + auto z = static_cast(_mm_extract_epi16(vInt, 4)); + auto w = static_cast(_mm_extract_epi16(vInt, 6)); + pDestination->v = static_cast( + (static_cast(w) ? 0x8000 : 0) + | ((static_cast(z) & 0x1F) << 10) + | ((static_cast(y) & 0x1F) << 5) + | ((static_cast(x) & 0x1F))); +#endif +} + + +/**************************************************************************** + * + * XMCOLOR operators + * + ****************************************************************************/ + +//------------------------------------------------------------------------------ + +inline XMCOLOR::XMCOLOR +( + float _r, + float _g, + float _b, + float _a +) noexcept +{ + XMStoreColor(this, XMVectorSet(_r, _g, _b, _a)); +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMCOLOR::XMCOLOR(const float* pArray) noexcept +{ + XMStoreColor(this, XMLoadFloat4(reinterpret_cast(pArray))); +} + +/**************************************************************************** + * + * XMHALF2 operators + * + ****************************************************************************/ + +//------------------------------------------------------------------------------ + +inline XMHALF2::XMHALF2 +( + float _x, + float _y +) noexcept +{ + x = XMConvertFloatToHalf(_x); + y = XMConvertFloatToHalf(_y); +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMHALF2::XMHALF2(const float* pArray) noexcept +{ + assert(pArray != nullptr); + x = XMConvertFloatToHalf(pArray[0]); + y = XMConvertFloatToHalf(pArray[1]); +} + +/**************************************************************************** + * + * XMSHORTN2 operators + * + ****************************************************************************/ + +//------------------------------------------------------------------------------ + +inline XMSHORTN2::XMSHORTN2 +( + float _x, + float _y +) noexcept +{ + XMStoreShortN2(this, XMVectorSet(_x, _y, 0.0f, 0.0f)); +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMSHORTN2::XMSHORTN2(const float* pArray) noexcept +{ + XMStoreShortN2(this, XMLoadFloat2(reinterpret_cast(pArray))); +} + +/**************************************************************************** + * + * XMSHORT2 operators + * + ****************************************************************************/ + +//------------------------------------------------------------------------------ + +inline XMSHORT2::XMSHORT2 +( + float _x, + float _y +) noexcept +{ + XMStoreShort2(this, XMVectorSet(_x, _y, 0.0f, 0.0f)); +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMSHORT2::XMSHORT2(const float* pArray) noexcept +{ + XMStoreShort2(this, XMLoadFloat2(reinterpret_cast(pArray))); +} + +/**************************************************************************** + * + * XMUSHORTN2 operators + * + ****************************************************************************/ + +//------------------------------------------------------------------------------ + +inline XMUSHORTN2::XMUSHORTN2 +( + float _x, + float _y +) noexcept +{ + XMStoreUShortN2(this, XMVectorSet(_x, _y, 0.0f, 0.0f)); +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMUSHORTN2::XMUSHORTN2(const float* pArray) noexcept +{ + XMStoreUShortN2(this, XMLoadFloat2(reinterpret_cast(pArray))); +} + +/**************************************************************************** + * + * XMUSHORT2 operators + * + ****************************************************************************/ + +//------------------------------------------------------------------------------ + +inline XMUSHORT2::XMUSHORT2 +( + float _x, + float _y +) noexcept +{ + XMStoreUShort2(this, XMVectorSet(_x, _y, 0.0f, 0.0f)); +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMUSHORT2::XMUSHORT2(const float* pArray) noexcept +{ + XMStoreUShort2(this, XMLoadFloat2(reinterpret_cast(pArray))); +} + +/**************************************************************************** + * + * XMBYTEN2 operators + * + ****************************************************************************/ + +//------------------------------------------------------------------------------ + +inline XMBYTEN2::XMBYTEN2 +( + float _x, + float _y +) noexcept +{ + XMStoreByteN2(this, XMVectorSet(_x, _y, 0.0f, 0.0f)); +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMBYTEN2::XMBYTEN2(const float* pArray) noexcept +{ + XMStoreByteN2(this, XMLoadFloat2(reinterpret_cast(pArray))); +} + +/**************************************************************************** + * + * XMBYTE2 operators + * + ****************************************************************************/ + +//------------------------------------------------------------------------------ + +inline XMBYTE2::XMBYTE2 +( + float _x, + float _y +) noexcept +{ + XMStoreByte2(this, XMVectorSet(_x, _y, 0.0f, 0.0f)); +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMBYTE2::XMBYTE2(const float* pArray) noexcept +{ + XMStoreByte2(this, XMLoadFloat2(reinterpret_cast(pArray))); +} + +/**************************************************************************** + * + * XMUBYTEN2 operators + * + ****************************************************************************/ + +//------------------------------------------------------------------------------ + +inline XMUBYTEN2::XMUBYTEN2 +( + float _x, + float _y +) noexcept +{ + XMStoreUByteN2(this, XMVectorSet(_x, _y, 0.0f, 0.0f)); +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMUBYTEN2::XMUBYTEN2(const float* pArray) noexcept +{ + XMStoreUByteN2(this, XMLoadFloat2(reinterpret_cast(pArray))); +} + +/**************************************************************************** + * + * XMUBYTE2 operators + * + ****************************************************************************/ + +//------------------------------------------------------------------------------ + +inline XMUBYTE2::XMUBYTE2 +( + float _x, + float _y +) noexcept +{ + XMStoreUByte2(this, XMVectorSet(_x, _y, 0.0f, 0.0f)); +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMUBYTE2::XMUBYTE2(const float* pArray) noexcept +{ + XMStoreUByte2(this, XMLoadFloat2(reinterpret_cast(pArray))); +} + +/**************************************************************************** + * + * XMU565 operators + * + ****************************************************************************/ + +inline XMU565::XMU565 +( + float _x, + float _y, + float _z +) noexcept +{ + XMStoreU565(this, XMVectorSet(_x, _y, _z, 0.0f)); +} + +_Use_decl_annotations_ +inline XMU565::XMU565(const float* pArray) noexcept +{ + XMStoreU565(this, XMLoadFloat3(reinterpret_cast(pArray))); +} + +/**************************************************************************** + * + * XMFLOAT3PK operators + * + ****************************************************************************/ + +inline XMFLOAT3PK::XMFLOAT3PK +( + float _x, + float _y, + float _z +) noexcept +{ + XMStoreFloat3PK(this, XMVectorSet(_x, _y, _z, 0.0f)); +} + +_Use_decl_annotations_ +inline XMFLOAT3PK::XMFLOAT3PK(const float* pArray) noexcept +{ + XMStoreFloat3PK(this, XMLoadFloat3(reinterpret_cast(pArray))); +} + +/**************************************************************************** + * + * XMFLOAT3SE operators + * + ****************************************************************************/ + +inline XMFLOAT3SE::XMFLOAT3SE +( + float _x, + float _y, + float _z +) noexcept +{ + XMStoreFloat3SE(this, XMVectorSet(_x, _y, _z, 0.0f)); +} + +_Use_decl_annotations_ +inline XMFLOAT3SE::XMFLOAT3SE(const float* pArray) noexcept +{ + XMStoreFloat3SE(this, XMLoadFloat3(reinterpret_cast(pArray))); +} + +/**************************************************************************** + * + * XMHALF4 operators + * + ****************************************************************************/ + +//------------------------------------------------------------------------------ + +inline XMHALF4::XMHALF4 +( + float _x, + float _y, + float _z, + float _w +) noexcept +{ + x = XMConvertFloatToHalf(_x); + y = XMConvertFloatToHalf(_y); + z = XMConvertFloatToHalf(_z); + w = XMConvertFloatToHalf(_w); +} + +//------------------------------------------------------------------------------ + +_Use_decl_annotations_ +inline XMHALF4::XMHALF4(const float* pArray) noexcept +{ + XMConvertFloatToHalfStream(&x, sizeof(HALF), pArray, sizeof(float), 4); +} + +/**************************************************************************** + * + * XMSHORTN4 operators + * + ****************************************************************************/ + +//------------------------------------------------------------------------------ + +inline XMSHORTN4::XMSHORTN4 +( + float _x, + float _y, + float _z, + float _w +) noexcept +{ + XMStoreShortN4(this, XMVectorSet(_x, _y, _z, _w)); +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMSHORTN4::XMSHORTN4(const float* pArray) noexcept +{ + XMStoreShortN4(this, XMLoadFloat4(reinterpret_cast(pArray))); +} + +/**************************************************************************** + * + * XMSHORT4 operators + * + ****************************************************************************/ + +//------------------------------------------------------------------------------ + +inline XMSHORT4::XMSHORT4 +( + float _x, + float _y, + float _z, + float _w +) noexcept +{ + XMStoreShort4(this, XMVectorSet(_x, _y, _z, _w)); +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMSHORT4::XMSHORT4(const float* pArray) noexcept +{ + XMStoreShort4(this, XMLoadFloat4(reinterpret_cast(pArray))); +} + +/**************************************************************************** + * + * XMUSHORTN4 operators + * + ****************************************************************************/ + +//------------------------------------------------------------------------------ + +inline XMUSHORTN4::XMUSHORTN4 +( + float _x, + float _y, + float _z, + float _w +) noexcept +{ + XMStoreUShortN4(this, XMVectorSet(_x, _y, _z, _w)); +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMUSHORTN4::XMUSHORTN4(const float* pArray) noexcept +{ + XMStoreUShortN4(this, XMLoadFloat4(reinterpret_cast(pArray))); +} + +/**************************************************************************** + * + * XMUSHORT4 operators + * + ****************************************************************************/ + +//------------------------------------------------------------------------------ + +inline XMUSHORT4::XMUSHORT4 +( + float _x, + float _y, + float _z, + float _w +) noexcept +{ + XMStoreUShort4(this, XMVectorSet(_x, _y, _z, _w)); +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMUSHORT4::XMUSHORT4(const float* pArray) noexcept +{ + XMStoreUShort4(this, XMLoadFloat4(reinterpret_cast(pArray))); +} + +/**************************************************************************** + * + * XMXDECN4 operators + * + ****************************************************************************/ + +//------------------------------------------------------------------------------ + +inline XMXDECN4::XMXDECN4 +( + float _x, + float _y, + float _z, + float _w +) noexcept +{ + XMStoreXDecN4(this, XMVectorSet(_x, _y, _z, _w)); +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMXDECN4::XMXDECN4(const float* pArray) noexcept +{ + XMStoreXDecN4(this, XMLoadFloat4(reinterpret_cast(pArray))); +} + +/**************************************************************************** + * + * XMXDEC4 operators + * + ****************************************************************************/ +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4996) +// C4996: ignore deprecation warning +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +//------------------------------------------------------------------------------ + +inline XMXDEC4::XMXDEC4 +( + float _x, + float _y, + float _z, + float _w +) noexcept +{ + XMStoreXDec4(this, XMVectorSet(_x, _y, _z, _w)); +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMXDEC4::XMXDEC4(const float* pArray) noexcept +{ + XMStoreXDec4(this, XMLoadFloat4(reinterpret_cast(pArray))); +} + +/**************************************************************************** + * + * XMDECN4 operators + * + ****************************************************************************/ + +//------------------------------------------------------------------------------ + +inline XMDECN4::XMDECN4 +( + float _x, + float _y, + float _z, + float _w +) noexcept +{ + XMStoreDecN4(this, XMVectorSet(_x, _y, _z, _w)); +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMDECN4::XMDECN4(const float* pArray) noexcept +{ + XMStoreDecN4(this, XMLoadFloat4(reinterpret_cast(pArray))); +} + +/**************************************************************************** + * + * XMDEC4 operators + * + ****************************************************************************/ + +//------------------------------------------------------------------------------ + +inline XMDEC4::XMDEC4 +( + float _x, + float _y, + float _z, + float _w +) noexcept +{ + XMStoreDec4(this, XMVectorSet(_x, _y, _z, _w)); +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMDEC4::XMDEC4(const float* pArray) noexcept +{ + XMStoreDec4(this, XMLoadFloat4(reinterpret_cast(pArray))); +} + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +/**************************************************************************** + * + * XMUDECN4 operators + * + ****************************************************************************/ + +//------------------------------------------------------------------------------ + +inline XMUDECN4::XMUDECN4 +( + float _x, + float _y, + float _z, + float _w +) noexcept +{ + XMStoreUDecN4(this, XMVectorSet(_x, _y, _z, _w)); +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMUDECN4::XMUDECN4(const float* pArray) noexcept +{ + XMStoreUDecN4(this, XMLoadFloat4(reinterpret_cast(pArray))); +} + +/**************************************************************************** + * + * XMUDEC4 operators + * + ****************************************************************************/ + +//------------------------------------------------------------------------------ + +inline XMUDEC4::XMUDEC4 +( + float _x, + float _y, + float _z, + float _w +) noexcept +{ + XMStoreUDec4(this, XMVectorSet(_x, _y, _z, _w)); +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMUDEC4::XMUDEC4(const float* pArray) noexcept +{ + XMStoreUDec4(this, XMLoadFloat4(reinterpret_cast(pArray))); +} + +/**************************************************************************** + * + * XMBYTEN4 operators + * + ****************************************************************************/ + +//------------------------------------------------------------------------------ + +inline XMBYTEN4::XMBYTEN4 +( + float _x, + float _y, + float _z, + float _w +) noexcept +{ + XMStoreByteN4(this, XMVectorSet(_x, _y, _z, _w)); +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMBYTEN4::XMBYTEN4(const float* pArray) noexcept +{ + XMStoreByteN4(this, XMLoadFloat4(reinterpret_cast(pArray))); +} + +/**************************************************************************** + * + * XMBYTE4 operators + * + ****************************************************************************/ + +//------------------------------------------------------------------------------ + +inline XMBYTE4::XMBYTE4 +( + float _x, + float _y, + float _z, + float _w +) noexcept +{ + XMStoreByte4(this, XMVectorSet(_x, _y, _z, _w)); +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMBYTE4::XMBYTE4(const float* pArray) noexcept +{ + XMStoreByte4(this, XMLoadFloat4(reinterpret_cast(pArray))); +} + +/**************************************************************************** + * + * XMUBYTEN4 operators + * + ****************************************************************************/ + +//------------------------------------------------------------------------------ + +inline XMUBYTEN4::XMUBYTEN4 +( + float _x, + float _y, + float _z, + float _w +) noexcept +{ + XMStoreUByteN4(this, XMVectorSet(_x, _y, _z, _w)); +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMUBYTEN4::XMUBYTEN4(const float* pArray) noexcept +{ + XMStoreUByteN4(this, XMLoadFloat4(reinterpret_cast(pArray))); +} + +/**************************************************************************** + * + * XMUBYTE4 operators + * + ****************************************************************************/ + +//------------------------------------------------------------------------------ + +inline XMUBYTE4::XMUBYTE4 +( + float _x, + float _y, + float _z, + float _w +) noexcept +{ + XMStoreUByte4(this, XMVectorSet(_x, _y, _z, _w)); +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMUBYTE4::XMUBYTE4(const float* pArray) noexcept +{ + XMStoreUByte4(this, XMLoadFloat4(reinterpret_cast(pArray))); +} + +/**************************************************************************** + * + * XMUNIBBLE4 operators + * + ****************************************************************************/ + +//------------------------------------------------------------------------------ + +inline XMUNIBBLE4::XMUNIBBLE4 +( + float _x, + float _y, + float _z, + float _w +) noexcept +{ + XMStoreUNibble4(this, XMVectorSet(_x, _y, _z, _w)); +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMUNIBBLE4::XMUNIBBLE4(const float* pArray) noexcept +{ + XMStoreUNibble4(this, XMLoadFloat4(reinterpret_cast(pArray))); +} + +/**************************************************************************** + * + * XMU555 operators + * + ****************************************************************************/ + +//------------------------------------------------------------------------------ + +inline XMU555::XMU555 +( + float _x, + float _y, + float _z, + bool _w +) noexcept +{ + XMStoreU555(this, XMVectorSet(_x, _y, _z, ((_w) ? 1.0f : 0.0f))); +} + +//------------------------------------------------------------------------------ +_Use_decl_annotations_ +inline XMU555::XMU555 +( + const float* pArray, + bool _w +) noexcept +{ + XMVECTOR V = XMLoadFloat3(reinterpret_cast(pArray)); + XMStoreU555(this, XMVectorSetW(V, ((_w) ? 1.0f : 0.0f))); +} + diff --git a/Minecraft.Client/Windows64/Libs/4JLibs/inc/4J_Render.h b/Minecraft.Client/Windows64/Libs/4JLibs/inc/4J_Render.h index 7fe70415..f17edd12 100644 --- a/Minecraft.Client/Windows64/Libs/4JLibs/inc/4J_Render.h +++ b/Minecraft.Client/Windows64/Libs/4JLibs/inc/4J_Render.h @@ -40,11 +40,11 @@ public: } }; -using D3DXIMAGE_INFO = struct +typedef struct { int Width; int Height; -}; +} D3DXIMAGE_INFO; typedef struct _XSOCIAL_PREVIEWIMAGE { @@ -93,7 +93,7 @@ public: void EndConditionalRendering(); // Vertex data handling - using eVertexType = enum + enum eVertexType { VERTEX_TYPE_PF3_TF2_CB4_NB4_XW1, // Position 3 x float, texture 2 x float, colour 4 x byte, normal 4 x byte, padding 1 DWORD @@ -105,7 +105,7 @@ public: }; // Pixel shader - using ePixelShaderType = enum + enum ePixelShaderType { PIXEL_SHADER_TYPE_STANDARD, PIXEL_SHADER_TYPE_PROJECTION, @@ -113,7 +113,7 @@ public: PIXEL_SHADER_COUNT }; - using eViewportType = enum + enum eViewportType { VIEWPORT_TYPE_FULLSCREEN, VIEWPORT_TYPE_SPLIT_TOP, @@ -126,7 +126,7 @@ public: VIEWPORT_TYPE_QUADRANT_BOTTOM_RIGHT, }; - using ePrimitiveType = enum + enum ePrimitiveType { PRIMITIVE_TYPE_TRIANGLE_LIST, PRIMITIVE_TYPE_TRIANGLE_STRIP, @@ -155,7 +155,7 @@ public: void CBuffDeferredModeStart(); void CBuffDeferredModeEnd(); - using eTextureFormat = enum + enum eTextureFormat { TEXTURE_FORMAT_RxGyBzAw, // Normal 32-bit RGBA texture, 8 bits per component /* Don't think these are all directly available on D3D 11 - leaving for now diff --git a/Minecraft.Client/Windows64/Libs/Iggy/inc/rrCore.h b/Minecraft.Client/Windows64/Libs/Iggy/inc/rrCore.h index 17ebee3a..a500775e 100644 --- a/Minecraft.Client/Windows64/Libs/Iggy/inc/rrCore.h +++ b/Minecraft.Client/Windows64/Libs/Iggy/inc/rrCore.h @@ -1641,13 +1641,17 @@ RADDEFSTART void RR_BREAK( void ); #pragma aux RR_BREAK = "int 0x3"; + #elif defined(__RADWIN__) && defined(__GNUC__) + + #define RR_BREAK() __asm__ volatile("int3") + #elif defined(__RADWIN__) && defined(_MSC_VER) && _MSC_VER >= 1300 #define RR_BREAK __debugbreak #else - #define RR_BREAK() RAD_STATEMENT_WRAPPER( __asm {int 3} ) + #define RR_BREAK() __builtin_trap() #endif diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/.appveyor.yml b/Minecraft.Client/Windows64/Libs/RmlUi/.appveyor.yml new file mode 100644 index 00000000..5b2afbb8 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/.appveyor.yml @@ -0,0 +1,27 @@ +version: build.{build} + +image: Visual Studio 2017 + +environment: + FREETYPE_VER: 2.11.1 + VS_GENERATOR: Visual Studio 15 2017 Win64 + +install: +- cmd: |- + cd Dependencies + appveyor DownloadFile https://github.com/ubawurinna/freetype-windows-binaries/archive/refs/tags/v%FREETYPE_VER%.zip + unzip -o v%FREETYPE_VER%.zip + mv "freetype-windows-binaries-%FREETYPE_VER%/include" include + mv "freetype-windows-binaries-%FREETYPE_VER%/release dll/win64" lib + cd .. + + cmake -B Build -G "%VS_GENERATOR%" -DBUILD_SHARED_LIBS=ON -DRMLUI_WARNINGS_AS_ERRORS=ON + +build_script: +- cmd: |- + cmake --build Build --config Debug -- /verbosity:minimal /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" + cmake --build Build --config Release -- /verbosity:minimal /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" + +after_build: + - cmd: |- + ls Build/*/*.dll -s -h -X diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/.clang-format b/Minecraft.Client/Windows64/Libs/RmlUi/.clang-format new file mode 100644 index 00000000..a696f5ea --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/.clang-format @@ -0,0 +1,69 @@ +AccessModifierOffset: -4 +AllowAllArgumentsOnNextLine: false +AllowAllParametersOfDeclarationOnNextLine: false +AlignAfterOpenBracket: DontAlign +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Left +AlignOperands: false +AlignTrailingComments: + Kind: Always + OverEmptyLines: 1 +AllowShortBlocksOnASingleLine: Empty +AllowShortCaseLabelsOnASingleLine: true +AllowShortFunctionsOnASingleLine: Inline +AlwaysBreakTemplateDeclarations: Yes +BinPackArguments: true +BinPackParameters: true +BreakBeforeBraces: Custom +BraceWrapping: + AfterCaseLabel: true + AfterControlStatement: true + AfterFunction: true + BeforeElse: true + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: false +BreakConstructorInitializers: AfterColon +ColumnLimit: 150 +CompactNamespaces: false +Cpp11BracedListStyle: true +FixNamespaceComments: true +IncludeBlocks: Merge +IncludeCategories: + - Regex: '^

(val)), true }; + } + + std::pair insert(const value_type& val) + { + auto i = lower_bound(val.first); + if (i != end() && !cmp()(val.first, *i)) + { + return { i, false }; + } + + return{ m_container.emplace(i, val), true }; + } + + template + std::pair emplace(Args&&... args) + { + value_type val(std::forward(args)...); + return insert(std::move(val)); + } + + iterator erase(const_iterator pos) + { + return m_container.erase(pos); + } + + iterator erase(iterator pos) + { + return m_container.erase(const_iterator(pos)); + } + + template + size_type erase(const K& k) + { + auto i = find(k); + if (i == end()) + { + return 0; + } + + erase(i); + return 1; + } + + template + typename std::enable_if::value, + mapped_type&>::type operator[](K&& k) + { + auto i = lower_bound(k); + if (i != end() && !cmp()(k, *i)) + { + return i->second; + } + + i = m_container.emplace(i, std::forward(k), mapped_type()); + return i->second; + } + + mapped_type& at(const key_type& k) + { + auto i = lower_bound(k); + if (i == end() || cmp()(*i, k)) + { + I_ITLIB_THROW_FLAT_MAP_OUT_OF_RANGE(); + } + + return i->second; + } + + const mapped_type& at(const key_type& k) const + { + auto i = lower_bound(k); + if (i == end() || cmp()(*i, k)) + { + I_ITLIB_THROW_FLAT_MAP_OUT_OF_RANGE(); + } + + return i->second; + } + + void swap(flat_map& x) + { + std::swap(cmp(), x.cmp()); + m_container.swap(x.m_container); + } + + const container_type& container() const noexcept + { + return m_container; + } + + // DANGER! If you're not careful with this function, you may irreversably break the map + container_type& modify_container() noexcept + { + return m_container; + } +}; + +template +bool operator==(const flat_map& a, const flat_map& b) +{ + return a.container() == b.container(); +} + +template +bool operator!=(const flat_map& a, const flat_map& b) +{ + return a.container() != b.container(); +} + +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Containers/itlib/flat_set.hpp b/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Containers/itlib/flat_set.hpp new file mode 100644 index 00000000..08fbafe1 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Containers/itlib/flat_set.hpp @@ -0,0 +1,325 @@ +// itlib-flat-set v1.06 +// +// std::set-like class with an underlying vector +// +// SPDX-License-Identifier: MIT +// MIT License: +// Copyright(c) 2021-2023 Borislav Stanimirov +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files(the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and / or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions : +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// +// VERSION HISTORY +// +// 1.06 (2023-01-14) Fixed initialization with custom Compare when equivalence +// is not the same as `==`. +// Inherit from Compare to enable empty base optimization +// 1.05 (2022-09-17) upper_bound and equal_range +// 1.04 (2022-06-23) Transparent lookups (C++14 style) +// 1.03 (2022-04-14) Noxcept move construct and assign +// 1.02 (2021-09-28) Fixed construction from std::initializer_list which +// allowed duplicate elements to find their wey in the set +// 1.01 (2021-09-15) Constructors from std::initializer_list +// 1.00 (2021-08-10) Initial-release +// +// +// DOCUMENTATION +// +// Simply include this file wherever you need. +// It defines the class itlib::flat_set, which is an almsot drop-in replacement +// of std::set. Flat set has an optional underlying container which by default +// is std::vector. Thus the items in the set are in a continuous block of +// memory. Thus iterating over the set is cache friendly, at the cost of +// O(n) for insert and erase. +// +// The elements inside (like in std::set) are kept in an order sorted by key. +// Getting a value by key is O(log2 n) +// +// It generally performs much faster than std::set for smaller sets of elements +// +// The difference with std::set, which makes flat_set an not-exactly-drop-in +// replacement is the last template argument: +// * std::set has +// * itlib::flat_set has +// The container must be an std::vector compatible type (itlib::static_vector +// is, for example, viable). The container value type must be `key` +// +// Changing the allocator. +// +// If you want to change the allocator of flat set, you'll have to provide a +// container with the appropriate one. Example: +// +// itlib::flat_set< +// string, +// less, +// std::vector> +// > myset +// +// +// TESTS +// +// You can find unit tests for static_vector in its official repo: +// https://github.com/iboB/itlib/blob/master/test/ +// +#pragma once + +#include +#include +#include + +namespace itlib +{ + +namespace fsimpl +{ +struct less // so as not to clash with map_less +{ + template + auto operator()(const T& t, const U& u) const -> decltype(t < u) + { + return t < u; + } +}; +} + +template > +class flat_set : private /*EBO*/ Compare +{ + Container m_container; + Compare& cmp() { return *this; } + const Compare& cmp() const { return *this; } +public: + using key_type = Key; + using value_type = Key; + using container_type = Container; + using key_compare = Compare; + using reference = value_type&; + using const_reference = const value_type& ; + using allocator_type = typename container_type::allocator_type; + using pointer = typename std::allocator_traits::pointer; + using const_pointer = typename std::allocator_traits::pointer; + using iterator = typename container_type::iterator; + using const_iterator = typename container_type::const_iterator; + using reverse_iterator = typename container_type::reverse_iterator; + using const_reverse_iterator = typename container_type::const_reverse_iterator; + using difference_type = typename container_type::difference_type; + using size_type = typename container_type::size_type; + + flat_set() + {} + + explicit flat_set(const key_compare& comp, const allocator_type& alloc = allocator_type()) + : Compare(comp) + , m_container(alloc) + {} + + explicit flat_set(container_type container, const key_compare& comp = key_compare()) + : Compare(comp) + , m_container(std::move(container)) + { + std::sort(m_container.begin(), m_container.end(), cmp()); + auto new_end = std::unique(m_container.begin(), m_container.end(), [this](const value_type& a, const value_type& b) -> bool { + return !cmp()(a, b) && !cmp()(b, a); + }); + m_container.erase(new_end, m_container.end()); + } + + flat_set(std::initializer_list init, const key_compare& comp = key_compare(), const allocator_type& alloc = allocator_type()) + : flat_set(container_type(std::move(init), alloc), comp) + {} + + flat_set(std::initializer_list init, const allocator_type& alloc) + : flat_set(std::move(init), key_compare(), alloc) + {} + + flat_set(const flat_set& x) = default; + flat_set& operator=(const flat_set& x) = default; + + flat_set(flat_set&& x) noexcept = default; + flat_set& operator=(flat_set&& x) noexcept = default; + + key_compare key_comp() const { return *this; } + + iterator begin() noexcept { return m_container.begin(); } + const_iterator begin() const noexcept { return m_container.begin(); } + iterator end() noexcept { return m_container.end(); } + const_iterator end() const noexcept { return m_container.end(); } + reverse_iterator rbegin() noexcept { return m_container.rbegin(); } + const_reverse_iterator rbegin() const noexcept { return m_container.rbegin(); } + reverse_iterator rend() noexcept { return m_container.rend(); } + const_reverse_iterator rend() const noexcept { return m_container.rend(); } + const_iterator cbegin() const noexcept { return m_container.cbegin(); } + const_iterator cend() const noexcept { return m_container.cend(); } + + bool empty() const noexcept { return m_container.empty(); } + size_type size() const noexcept { return m_container.size(); } + size_type max_size() const noexcept { return m_container.max_size(); } + + void reserve(size_type count) { return m_container.reserve(count); } + size_type capacity() const noexcept { return m_container.capacity(); } + + void clear() noexcept { m_container.clear(); } + + template + iterator lower_bound(const F& k) + { + return std::lower_bound(m_container.begin(), m_container.end(), k, cmp()); + } + + template + const_iterator lower_bound(const F& k) const + { + return std::lower_bound(m_container.begin(), m_container.end(), k, cmp()); + } + + template + iterator upper_bound(const K& k) + { + return std::upper_bound(m_container.begin(), m_container.end(), k, cmp()); + } + + template + const_iterator upper_bound(const K& k) const + { + return std::upper_bound(m_container.begin(), m_container.end(), k, cmp()); + } + + template + std::pair equal_range(const K& k) + { + return std::equal_range(m_container.begin(), m_container.end(), k, cmp()); + } + + template + std::pair equal_range(const K& k) const + { + return std::equal_range(m_container.begin(), m_container.end(), k, cmp()); + } + + template + iterator find(const F& k) + { + auto i = lower_bound(k); + if (i != end() && !cmp()(k, *i)) + return i; + + return end(); + } + + template + const_iterator find(const F& k) const + { + auto i = lower_bound(k); + if (i != end() && !cmp()(k, *i)) + return i; + + return end(); + } + + template + size_t count(const F& k) const + { + return find(k) == end() ? 0 : 1; + } + + template + std::pair insert(P&& val) + { + auto i = lower_bound(val); + if (i != end() && !cmp()(val, *i)) + { + return { i, false }; + } + + return{ m_container.emplace(i, std::forward

(val)), true }; + } + + std::pair insert(const value_type& val) + { + auto i = lower_bound(val); + if (i != end() && !cmp()(val, *i)) + { + return { i, false }; + } + + return{ m_container.emplace(i, val), true }; + } + + template + std::pair emplace(Args&&... args) + { + value_type val(std::forward(args)...); + return insert(std::move(val)); + } + + iterator erase(const_iterator pos) + { + return m_container.erase(pos); + } + + iterator erase(iterator pos) + { + return erase(const_iterator(pos)); + } + + template + size_type erase(const F& k) + { + auto i = find(k); + if (i == end()) + { + return 0; + } + + erase(i); + return 1; + } + + void swap(flat_set& x) + { + std::swap(cmp(), x.cmp()); + m_container.swap(x.m_container); + } + + const container_type& container() const noexcept + { + return m_container; + } + + // DANGER! If you're not careful with this function, you may irreversably break the set + container_type& modify_container() noexcept + { + return m_container; + } +}; + +template +bool operator==(const flat_set& a, const flat_set& b) +{ + return a.container() == b.container(); +} + +template +bool operator!=(const flat_set& a, const flat_set& b) +{ + return a.container() != b.container(); +} + +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Containers/robin_hood.h b/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Containers/robin_hood.h new file mode 100644 index 00000000..a5a97843 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Containers/robin_hood.h @@ -0,0 +1,2401 @@ +// ______ _____ ______ _________ +// ______________ ___ /_ ___(_)_______ ___ /_ ______ ______ ______ / +// __ ___/_ __ \__ __ \__ / __ __ \ __ __ \_ __ \_ __ \_ __ / +// _ / / /_/ /_ /_/ /_ / _ / / / _ / / // /_/ // /_/ // /_/ / +// /_/ \____/ /_.___/ /_/ /_/ /_/ ________/_/ /_/ \____/ \____/ \__,_/ +// _/_____/ +// +// Fast & memory efficient hashtable based on robin hood hashing for C++11/14/17/20 +// https://github.com/martinus/robin-hood-hashing +// +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// Copyright (c) 2018-2020 Martin Ankerl +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#ifndef ROBIN_HOOD_H_INCLUDED +#define ROBIN_HOOD_H_INCLUDED + +// see https://semver.org/ +#define ROBIN_HOOD_VERSION_MAJOR 3 // for incompatible API changes +#define ROBIN_HOOD_VERSION_MINOR 9 // for adding functionality in a backwards-compatible manner +#define ROBIN_HOOD_VERSION_PATCH 0 // for backwards-compatible bug fixes + +#include +#include +#include +#include +#include +#include +#include // only to support hash of smart pointers +#include +#include +#include +#include +#if __cplusplus >= 201703L +# include +#endif + +// #define ROBIN_HOOD_LOG_ENABLED +#ifdef ROBIN_HOOD_LOG_ENABLED +# include +# define ROBIN_HOOD_LOG(x) std::cout << __FUNCTION__ << "@" << __LINE__ << ": " << x << std::endl +#else +# define ROBIN_HOOD_LOG(x) +#endif + +// #define ROBIN_HOOD_TRACE_ENABLED +#ifdef ROBIN_HOOD_TRACE_ENABLED +# include +# define ROBIN_HOOD_TRACE(x) \ + std::cout << __FUNCTION__ << "@" << __LINE__ << ": " << x << std::endl +#else +# define ROBIN_HOOD_TRACE(x) +#endif + +// #define ROBIN_HOOD_COUNT_ENABLED +#ifdef ROBIN_HOOD_COUNT_ENABLED +# include +# define ROBIN_HOOD_COUNT(x) ++counts().x; +namespace robin_hood { +struct Counts { + uint64_t shiftUp{}; + uint64_t shiftDown{}; +}; +inline std::ostream& operator<<(std::ostream& os, Counts const& c) { + return os << c.shiftUp << " shiftUp" << std::endl << c.shiftDown << " shiftDown" << std::endl; +} + +static Counts& counts() { + static Counts counts{}; + return counts; +} +} // namespace robin_hood +#else +# define ROBIN_HOOD_COUNT(x) +#endif + +// all non-argument macros should use this facility. See +// https://www.fluentcpp.com/2019/05/28/better-macros-better-flags/ +#define ROBIN_HOOD(x) ROBIN_HOOD_PRIVATE_DEFINITION_##x() + +// mark unused members with this macro +#define ROBIN_HOOD_UNUSED(identifier) + +// bitness +#if SIZE_MAX == UINT32_MAX +# define ROBIN_HOOD_PRIVATE_DEFINITION_BITNESS() 32 +#elif SIZE_MAX == UINT64_MAX +# define ROBIN_HOOD_PRIVATE_DEFINITION_BITNESS() 64 +#else +# error Unsupported bitness +#endif + +// endianess +#ifdef _MSC_VER +# define ROBIN_HOOD_PRIVATE_DEFINITION_LITTLE_ENDIAN() 1 +# define ROBIN_HOOD_PRIVATE_DEFINITION_BIG_ENDIAN() 0 +#else +# define ROBIN_HOOD_PRIVATE_DEFINITION_LITTLE_ENDIAN() \ + (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) +# define ROBIN_HOOD_PRIVATE_DEFINITION_BIG_ENDIAN() (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) +#endif + +// inline +#ifdef _MSC_VER +# define ROBIN_HOOD_PRIVATE_DEFINITION_NOINLINE() __declspec(noinline) +#else +# define ROBIN_HOOD_PRIVATE_DEFINITION_NOINLINE() __attribute__((noinline)) +#endif + +// exceptions +#if !defined(__cpp_exceptions) && !defined(__EXCEPTIONS) && !defined(_CPPUNWIND) +# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_EXCEPTIONS() 0 +#else +# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_EXCEPTIONS() 1 +#endif + +// count leading/trailing bits +#if !defined(ROBIN_HOOD_DISABLE_INTRINSICS) +# ifdef _MSC_VER +# if ROBIN_HOOD(BITNESS) == 32 +# define ROBIN_HOOD_PRIVATE_DEFINITION_BITSCANFORWARD() _BitScanForward +# else +# define ROBIN_HOOD_PRIVATE_DEFINITION_BITSCANFORWARD() _BitScanForward64 +# endif +# include +# pragma intrinsic(ROBIN_HOOD(BITSCANFORWARD)) +# define ROBIN_HOOD_COUNT_TRAILING_ZEROES(x) \ + [](size_t mask) noexcept -> int { \ + unsigned long index; \ + return ROBIN_HOOD(BITSCANFORWARD)(&index, mask) ? static_cast(index) \ + : ROBIN_HOOD(BITNESS); \ + }(x) +# else +# if ROBIN_HOOD(BITNESS) == 32 +# define ROBIN_HOOD_PRIVATE_DEFINITION_CTZ() __builtin_ctzl +# define ROBIN_HOOD_PRIVATE_DEFINITION_CLZ() __builtin_clzl +# else +# define ROBIN_HOOD_PRIVATE_DEFINITION_CTZ() __builtin_ctzll +# define ROBIN_HOOD_PRIVATE_DEFINITION_CLZ() __builtin_clzll +# endif +# define ROBIN_HOOD_COUNT_LEADING_ZEROES(x) ((x) ? ROBIN_HOOD(CLZ)(x) : ROBIN_HOOD(BITNESS)) +# define ROBIN_HOOD_COUNT_TRAILING_ZEROES(x) ((x) ? ROBIN_HOOD(CTZ)(x) : ROBIN_HOOD(BITNESS)) +# endif +#endif + +// fallthrough +#ifndef __has_cpp_attribute // For backwards compatibility +# define __has_cpp_attribute(x) 0 +#endif +#if __has_cpp_attribute(clang::fallthrough) +# define ROBIN_HOOD_PRIVATE_DEFINITION_FALLTHROUGH() [[clang::fallthrough]] +#elif __has_cpp_attribute(gnu::fallthrough) +# define ROBIN_HOOD_PRIVATE_DEFINITION_FALLTHROUGH() [[gnu::fallthrough]] +#else +# define ROBIN_HOOD_PRIVATE_DEFINITION_FALLTHROUGH() +#endif + +// likely/unlikely +#ifdef _MSC_VER +# define ROBIN_HOOD_LIKELY(condition) condition +# define ROBIN_HOOD_UNLIKELY(condition) condition +#else +# define ROBIN_HOOD_LIKELY(condition) __builtin_expect(condition, 1) +# define ROBIN_HOOD_UNLIKELY(condition) __builtin_expect(condition, 0) +#endif + +// detect if native wchar_t type is availiable in MSVC +#ifdef _MSC_VER +# ifdef _NATIVE_WCHAR_T_DEFINED +# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_NATIVE_WCHART() 1 +# else +# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_NATIVE_WCHART() 0 +# endif +#else +# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_NATIVE_WCHART() 1 +#endif + +// workaround missing "is_trivially_copyable" in g++ < 5.0 +// See https://stackoverflow.com/a/31798726/48181 +#if defined(__GNUC__) && __GNUC__ < 5 +# define ROBIN_HOOD_IS_TRIVIALLY_COPYABLE(...) __is_trivially_constructible(__VA_ARGS__) +#else +# define ROBIN_HOOD_IS_TRIVIALLY_COPYABLE(...) std::is_trivially_copyable<__VA_ARGS__>::value +#endif + +// helpers for C++ versions, see https://gcc.gnu.org/onlinedocs/cpp/Standard-Predefined-Macros.html +#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX() __cplusplus +#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX98() 199711L +#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX11() 201103L +#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX14() 201402L +#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX17() 201703L + +#if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX17) +# define ROBIN_HOOD_PRIVATE_DEFINITION_NODISCARD() [[nodiscard]] +#else +# define ROBIN_HOOD_PRIVATE_DEFINITION_NODISCARD() +#endif + +namespace robin_hood { + +#if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX14) +# define ROBIN_HOOD_STD std +#else + +// c++11 compatibility layer +namespace ROBIN_HOOD_STD { +template +struct alignment_of + : std::integral_constant::type)> {}; + +template +class integer_sequence { +public: + using value_type = T; + static_assert(std::is_integral::value, "not integral type"); + static constexpr std::size_t size() noexcept { + return sizeof...(Ints); + } +}; +template +using index_sequence = integer_sequence; + +namespace detail_ { +template +struct IntSeqImpl { + using TValue = T; + static_assert(std::is_integral::value, "not integral type"); + static_assert(Begin >= 0 && Begin < End, "unexpected argument (Begin<0 || Begin<=End)"); + + template + struct IntSeqCombiner; + + template + struct IntSeqCombiner, integer_sequence> { + using TResult = integer_sequence; + }; + + using TResult = + typename IntSeqCombiner::TResult, + typename IntSeqImpl::TResult>::TResult; +}; + +template +struct IntSeqImpl { + using TValue = T; + static_assert(std::is_integral::value, "not integral type"); + static_assert(Begin >= 0, "unexpected argument (Begin<0)"); + using TResult = integer_sequence; +}; + +template +struct IntSeqImpl { + using TValue = T; + static_assert(std::is_integral::value, "not integral type"); + static_assert(Begin >= 0, "unexpected argument (Begin<0)"); + using TResult = integer_sequence; +}; +} // namespace detail_ + +template +using make_integer_sequence = typename detail_::IntSeqImpl::TResult; + +template +using make_index_sequence = make_integer_sequence; + +template +using index_sequence_for = make_index_sequence; + +} // namespace ROBIN_HOOD_STD + +#endif + +namespace detail { + +// make sure we static_cast to the correct type for hash_int +#if ROBIN_HOOD(BITNESS) == 64 +using SizeT = uint64_t; +#else +using SizeT = uint32_t; +#endif + +template +T rotr(T x, unsigned k) { + return (x >> k) | (x << (8U * sizeof(T) - k)); +} + +// This cast gets rid of warnings like "cast from 'uint8_t*' {aka 'unsigned char*'} to +// 'uint64_t*' {aka 'long unsigned int*'} increases required alignment of target type". Use with +// care! +template +inline T reinterpret_cast_no_cast_align_warning(void* ptr) noexcept { + return reinterpret_cast(ptr); +} + +template +inline T reinterpret_cast_no_cast_align_warning(void const* ptr) noexcept { + return reinterpret_cast(ptr); +} + +// make sure this is not inlined as it is slow and dramatically enlarges code, thus making other +// inlinings more difficult. Throws are also generally the slow path. +template +[[noreturn]] ROBIN_HOOD(NOINLINE) +#if ROBIN_HOOD(HAS_EXCEPTIONS) + void doThrow(Args&&... args) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay) + throw E(std::forward(args)...); +} +#else + void doThrow(Args&&... ROBIN_HOOD_UNUSED(args) /*unused*/) { + abort(); +} +#endif + +template +T* assertNotNull(T* t, Args&&... args) { + if (ROBIN_HOOD_UNLIKELY(nullptr == t)) { + doThrow(std::forward(args)...); + } + return t; +} + +template +inline T unaligned_load(void const* ptr) noexcept { + // using memcpy so we don't get into unaligned load problems. + // compiler should optimize this very well anyways. + T t; + std::memcpy(&t, ptr, sizeof(T)); + return t; +} + +// Allocates bulks of memory for objects of type T. This deallocates the memory in the destructor, +// and keeps a linked list of the allocated memory around. Overhead per allocation is the size of a +// pointer. +template +class BulkPoolAllocator { +public: + BulkPoolAllocator() noexcept = default; + + // does not copy anything, just creates a new allocator. + BulkPoolAllocator(const BulkPoolAllocator& ROBIN_HOOD_UNUSED(o) /*unused*/) noexcept + : mHead(nullptr) + , mListForFree(nullptr) {} + + BulkPoolAllocator(BulkPoolAllocator&& o) noexcept + : mHead(o.mHead) + , mListForFree(o.mListForFree) { + o.mListForFree = nullptr; + o.mHead = nullptr; + } + + BulkPoolAllocator& operator=(BulkPoolAllocator&& o) noexcept { + reset(); + mHead = o.mHead; + mListForFree = o.mListForFree; + o.mListForFree = nullptr; + o.mHead = nullptr; + return *this; + } + + BulkPoolAllocator& + // NOLINTNEXTLINE(bugprone-unhandled-self-assignment,cert-oop54-cpp) + operator=(const BulkPoolAllocator& ROBIN_HOOD_UNUSED(o) /*unused*/) noexcept { + // does not do anything + return *this; + } + + ~BulkPoolAllocator() noexcept { + reset(); + } + + // Deallocates all allocated memory. + void reset() noexcept { + while (mListForFree) { + T* tmp = *mListForFree; + std::free(mListForFree); + mListForFree = reinterpret_cast_no_cast_align_warning(tmp); + } + mHead = nullptr; + } + + // allocates, but does NOT initialize. Use in-place new constructor, e.g. + // T* obj = pool.allocate(); + // ::new (static_cast(obj)) T(); + T* allocate() { + T* tmp = mHead; + if (!tmp) { + tmp = performAllocation(); + } + + mHead = *reinterpret_cast_no_cast_align_warning(tmp); + return tmp; + } + + // does not actually deallocate but puts it in store. + // make sure you have already called the destructor! e.g. with + // obj->~T(); + // pool.deallocate(obj); + void deallocate(T* obj) noexcept { + *reinterpret_cast_no_cast_align_warning(obj) = mHead; + mHead = obj; + } + + // Adds an already allocated block of memory to the allocator. This allocator is from now on + // responsible for freeing the data (with free()). If the provided data is not large enough to + // make use of, it is immediately freed. Otherwise it is reused and freed in the destructor. + void addOrFree(void* ptr, const size_t numBytes) noexcept { + // calculate number of available elements in ptr + if (numBytes < ALIGNMENT + ALIGNED_SIZE) { + // not enough data for at least one element. Free and return. + std::free(ptr); + } else { + add(ptr, numBytes); + } + } + + void swap(BulkPoolAllocator& other) noexcept { + using std::swap; + swap(mHead, other.mHead); + swap(mListForFree, other.mListForFree); + } + +private: + // iterates the list of allocated memory to calculate how many to alloc next. + // Recalculating this each time saves us a size_t member. + // This ignores the fact that memory blocks might have been added manually with addOrFree. In + // practice, this should not matter much. + ROBIN_HOOD(NODISCARD) size_t calcNumElementsToAlloc() const noexcept { + auto tmp = mListForFree; + size_t numAllocs = MinNumAllocs; + + while (numAllocs * 2 <= MaxNumAllocs && tmp) { + auto x = reinterpret_cast(tmp); + tmp = *x; + numAllocs *= 2; + } + + return numAllocs; + } + + // WARNING: Underflow if numBytes < ALIGNMENT! This is guarded in addOrFree(). + void add(void* ptr, const size_t numBytes) noexcept { + const size_t numElements = (numBytes - ALIGNMENT) / ALIGNED_SIZE; + + auto data = reinterpret_cast(ptr); + + // link free list + auto x = reinterpret_cast(data); + *x = mListForFree; + mListForFree = data; + + // create linked list for newly allocated data + auto* const headT = + reinterpret_cast_no_cast_align_warning(reinterpret_cast(ptr) + ALIGNMENT); + + auto* const head = reinterpret_cast(headT); + + // Visual Studio compiler automatically unrolls this loop, which is pretty cool + for (size_t i = 0; i < numElements; ++i) { + *reinterpret_cast_no_cast_align_warning(head + i * ALIGNED_SIZE) = + head + (i + 1) * ALIGNED_SIZE; + } + + // last one points to 0 + *reinterpret_cast_no_cast_align_warning(head + (numElements - 1) * ALIGNED_SIZE) = + mHead; + mHead = headT; + } + + // Called when no memory is available (mHead == 0). + // Don't inline this slow path. + ROBIN_HOOD(NOINLINE) T* performAllocation() { + size_t const numElementsToAlloc = calcNumElementsToAlloc(); + + // alloc new memory: [prev |T, T, ... T] + // std::cout << (sizeof(T*) + ALIGNED_SIZE * numElementsToAlloc) << " bytes" << std::endl; + size_t const bytes = ALIGNMENT + ALIGNED_SIZE * numElementsToAlloc; + add(assertNotNull(std::malloc(bytes)), bytes); + return mHead; + } + + // enforce byte alignment of the T's +#if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX14) + static constexpr size_t ALIGNMENT = + (std::max)(std::alignment_of::value, std::alignment_of::value); +#else + static const size_t ALIGNMENT = + (ROBIN_HOOD_STD::alignment_of::value > ROBIN_HOOD_STD::alignment_of::value) + ? ROBIN_HOOD_STD::alignment_of::value + : +ROBIN_HOOD_STD::alignment_of::value; // the + is for walkarround +#endif + + static constexpr size_t ALIGNED_SIZE = ((sizeof(T) - 1) / ALIGNMENT + 1) * ALIGNMENT; + + static_assert(MinNumAllocs >= 1, "MinNumAllocs"); + static_assert(MaxNumAllocs >= MinNumAllocs, "MaxNumAllocs"); + static_assert(ALIGNED_SIZE >= sizeof(T*), "ALIGNED_SIZE"); + static_assert(0 == (ALIGNED_SIZE % sizeof(T*)), "ALIGNED_SIZE mod"); + static_assert(ALIGNMENT >= sizeof(T*), "ALIGNMENT"); + + T* mHead{nullptr}; + T** mListForFree{nullptr}; +}; + +template +struct NodeAllocator; + +// dummy allocator that does nothing +template +struct NodeAllocator { + + // we are not using the data, so just free it. + void addOrFree(void* ptr, size_t ROBIN_HOOD_UNUSED(numBytes) /*unused*/) noexcept { + std::free(ptr); + } +}; + +template +struct NodeAllocator : public BulkPoolAllocator {}; + +// dummy hash, unsed as mixer when robin_hood::hash is already used +template +struct identity_hash { + constexpr size_t operator()(T const& obj) const noexcept { + return static_cast(obj); + } +}; + +// c++14 doesn't have is_nothrow_swappable, and clang++ 6.0.1 doesn't like it either, so I'm making +// my own here. +namespace swappable { +#if ROBIN_HOOD(CXX) < ROBIN_HOOD(CXX17) +using std::swap; +template +struct nothrow { + static const bool value = noexcept(swap(std::declval(), std::declval())); +}; +#else +template +struct nothrow { + static const bool value = std::is_nothrow_swappable::value; +}; +#endif +} // namespace swappable + +} // namespace detail + +struct is_transparent_tag {}; + +// A custom pair implementation is used in the map because std::pair is not is_trivially_copyable, +// which means it would not be allowed to be used in std::memcpy. This struct is copyable, which is +// also tested. +template +struct pair { + using first_type = T1; + using second_type = T2; + + template ::value && + std::is_default_constructible::value>::type> + constexpr pair() noexcept(noexcept(U1()) && noexcept(U2())) + : first() + , second() {} + + // pair constructors are explicit so we don't accidentally call this ctor when we don't have to. + explicit constexpr pair(std::pair const& o) noexcept( + noexcept(T1(std::declval())) && noexcept(T2(std::declval()))) + : first(o.first) + , second(o.second) {} + + // pair constructors are explicit so we don't accidentally call this ctor when we don't have to. + explicit constexpr pair(std::pair&& o) noexcept(noexcept( + T1(std::move(std::declval()))) && noexcept(T2(std::move(std::declval())))) + : first(std::move(o.first)) + , second(std::move(o.second)) {} + + constexpr pair(T1&& a, T2&& b) noexcept(noexcept( + T1(std::move(std::declval()))) && noexcept(T2(std::move(std::declval())))) + : first(std::move(a)) + , second(std::move(b)) {} + + template + constexpr pair(U1&& a, U2&& b) noexcept(noexcept(T1(std::forward( + std::declval()))) && noexcept(T2(std::forward(std::declval())))) + : first(std::forward(a)) + , second(std::forward(b)) {} + + template + constexpr pair( + std::piecewise_construct_t /*unused*/, std::tuple a, + std::tuple b) noexcept(noexcept(pair(std::declval&>(), + std::declval&>(), + ROBIN_HOOD_STD::index_sequence_for(), + ROBIN_HOOD_STD::index_sequence_for()))) + : pair(a, b, ROBIN_HOOD_STD::index_sequence_for(), + ROBIN_HOOD_STD::index_sequence_for()) {} + + // constructor called from the std::piecewise_construct_t ctor + template + pair(std::tuple& a, std::tuple& b, ROBIN_HOOD_STD::index_sequence /*unused*/, ROBIN_HOOD_STD::index_sequence /*unused*/) noexcept( + noexcept(T1(std::forward(std::get( + std::declval&>()))...)) && noexcept(T2(std:: + forward(std::get( + std::declval&>()))...))) + : first(std::forward(std::get(a))...) + , second(std::forward(std::get(b))...) { + // make visual studio compiler happy about warning about unused a & b. + // Visual studio's pair implementation disables warning 4100. + (void)a; + (void)b; + } + + void swap(pair& o) noexcept((detail::swappable::nothrow::value) && + (detail::swappable::nothrow::value)) { + using std::swap; + swap(first, o.first); + swap(second, o.second); + } + + T1 first; // NOLINT(misc-non-private-member-variables-in-classes) + T2 second; // NOLINT(misc-non-private-member-variables-in-classes) +}; + +template +inline void swap(pair& a, pair& b) noexcept( + noexcept(std::declval&>().swap(std::declval&>()))) { + a.swap(b); +} + +template +inline constexpr bool operator==(pair const& x, pair const& y) { + return (x.first == y.first) && (x.second == y.second); +} +template +inline constexpr bool operator!=(pair const& x, pair const& y) { + return !(x == y); +} +template +inline constexpr bool operator<(pair const& x, pair const& y) noexcept(noexcept( + std::declval() < std::declval()) && noexcept(std::declval() < + std::declval())) { + return x.first < y.first || (!(y.first < x.first) && x.second < y.second); +} +template +inline constexpr bool operator>(pair const& x, pair const& y) { + return y < x; +} +template +inline constexpr bool operator<=(pair const& x, pair const& y) { + return !(x > y); +} +template +inline constexpr bool operator>=(pair const& x, pair const& y) { + return !(x < y); +} + +inline size_t hash_bytes(void const* ptr, size_t len) noexcept { + static constexpr uint64_t m = UINT64_C(0xc6a4a7935bd1e995); + static constexpr uint64_t seed = UINT64_C(0xe17a1465); + static constexpr unsigned int r = 47; + + auto const* const data64 = static_cast(ptr); + uint64_t h = seed ^ (len * m); + + size_t const n_blocks = len / 8; + for (size_t i = 0; i < n_blocks; ++i) { + auto k = detail::unaligned_load(data64 + i); + + k *= m; + k ^= k >> r; + k *= m; + + h ^= k; + h *= m; + } + + auto const* const data8 = reinterpret_cast(data64 + n_blocks); + switch (len & 7U) { + case 7: + h ^= static_cast(data8[6]) << 48U; + ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH + case 6: + h ^= static_cast(data8[5]) << 40U; + ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH + case 5: + h ^= static_cast(data8[4]) << 32U; + ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH + case 4: + h ^= static_cast(data8[3]) << 24U; + ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH + case 3: + h ^= static_cast(data8[2]) << 16U; + ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH + case 2: + h ^= static_cast(data8[1]) << 8U; + ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH + case 1: + h ^= static_cast(data8[0]); + h *= m; + ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH + default: + break; + } + + h ^= h >> r; + h *= m; + h ^= h >> r; + return static_cast(h); +} + +inline size_t hash_int(uint64_t x) noexcept { + // inspired by lemire's strongly universal hashing + // https://lemire.me/blog/2018/08/15/fast-strongly-universal-64-bit-hashing-everywhere/ + // + // Instead of shifts, we use rotations so we don't lose any bits. + // + // Added a final multiplcation with a constant for more mixing. It is most important that + // the lower bits are well mixed. + auto h1 = x * UINT64_C(0xA24BAED4963EE407); + auto h2 = detail::rotr(x, 32U) * UINT64_C(0x9FB21C651E98DF25); + auto h = detail::rotr(h1 + h2, 32U); + return static_cast(h); +} + +// A thin wrapper around std::hash, performing an additional simple mixing step of the result. +template +struct hash : public std::hash { + size_t operator()(T const& obj) const + noexcept(noexcept(std::declval>().operator()(std::declval()))) { + // call base hash + auto result = std::hash::operator()(obj); + // return mixed of that, to be save against identity has + return hash_int(static_cast(result)); + } +}; + +template +struct hash> { + size_t operator()(std::basic_string const& str) const noexcept { + return hash_bytes(str.data(), sizeof(CharT) * str.size()); + } +}; + +#if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX17) +template +struct hash> { + size_t operator()(std::basic_string_view const& sv) const noexcept { + return hash_bytes(sv.data(), sizeof(CharT) * sv.size()); + } +}; +#endif + +template +struct hash { + size_t operator()(T* ptr) const noexcept { + return hash_int(reinterpret_cast(ptr)); + } +}; + +template +struct hash> { + size_t operator()(std::unique_ptr const& ptr) const noexcept { + return hash_int(reinterpret_cast(ptr.get())); + } +}; + +template +struct hash> { + size_t operator()(std::shared_ptr const& ptr) const noexcept { + return hash_int(reinterpret_cast(ptr.get())); + } +}; + +template +struct hash::value>::type> { + size_t operator()(Enum e) const noexcept { + using Underlying = typename std::underlying_type::type; + return hash{}(static_cast(e)); + } +}; + +#define ROBIN_HOOD_HASH_INT(T) \ + template <> \ + struct hash { \ + size_t operator()(T const& obj) const noexcept { \ + return hash_int(static_cast(obj)); \ + } \ + } + +#if defined(__GNUC__) && !defined(__clang__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wuseless-cast" +#endif +// see https://en.cppreference.com/w/cpp/utility/hash +ROBIN_HOOD_HASH_INT(bool); +ROBIN_HOOD_HASH_INT(char); +ROBIN_HOOD_HASH_INT(signed char); +ROBIN_HOOD_HASH_INT(unsigned char); +ROBIN_HOOD_HASH_INT(char16_t); +ROBIN_HOOD_HASH_INT(char32_t); +#if ROBIN_HOOD(HAS_NATIVE_WCHART) +ROBIN_HOOD_HASH_INT(wchar_t); +#endif +ROBIN_HOOD_HASH_INT(short); +ROBIN_HOOD_HASH_INT(unsigned short); +ROBIN_HOOD_HASH_INT(int); +ROBIN_HOOD_HASH_INT(unsigned int); +ROBIN_HOOD_HASH_INT(long); +ROBIN_HOOD_HASH_INT(long long); +ROBIN_HOOD_HASH_INT(unsigned long); +ROBIN_HOOD_HASH_INT(unsigned long long); +#if defined(__GNUC__) && !defined(__clang__) +# pragma GCC diagnostic pop +#endif +namespace detail { + +template +struct void_type { + using type = void; +}; + +template +struct has_is_transparent : public std::false_type {}; + +template +struct has_is_transparent::type> + : public std::true_type {}; + +// using wrapper classes for hash and key_equal prevents the diamond problem when the same type +// is used. see https://stackoverflow.com/a/28771920/48181 +template +struct WrapHash : public T { + WrapHash() = default; + explicit WrapHash(T const& o) noexcept(noexcept(T(std::declval()))) + : T(o) {} +}; + +template +struct WrapKeyEqual : public T { + WrapKeyEqual() = default; + explicit WrapKeyEqual(T const& o) noexcept(noexcept(T(std::declval()))) + : T(o) {} +}; + +// A highly optimized hashmap implementation, using the Robin Hood algorithm. +// +// In most cases, this map should be usable as a drop-in replacement for std::unordered_map, but +// be about 2x faster in most cases and require much less allocations. +// +// This implementation uses the following memory layout: +// +// [Node, Node, ... Node | info, info, ... infoSentinel ] +// +// * Node: either a DataNode that directly has the std::pair as member, +// or a DataNode with a pointer to std::pair. Which DataNode representation to use +// depends on how fast the swap() operation is. Heuristically, this is automatically choosen +// based on sizeof(). there are always 2^n Nodes. +// +// * info: Each Node in the map has a corresponding info byte, so there are 2^n info bytes. +// Each byte is initialized to 0, meaning the corresponding Node is empty. Set to 1 means the +// corresponding node contains data. Set to 2 means the corresponding Node is filled, but it +// actually belongs to the previous position and was pushed out because that place is already +// taken. +// +// * infoSentinel: Sentinel byte set to 1, so that iterator's ++ can stop at end() without the +// need for a idx variable. +// +// According to STL, order of templates has effect on throughput. That's why I've moved the +// boolean to the front. +// https://www.reddit.com/r/cpp/comments/ahp6iu/compile_time_binary_size_reductions_and_cs_future/eeguck4/ +template +class Table + : public WrapHash, + public WrapKeyEqual, + detail::NodeAllocator< + typename std::conditional< + std::is_void::value, Key, + robin_hood::pair::type, T>>::type, + 4, 16384, IsFlat> { +public: + static constexpr bool is_flat = IsFlat; + static constexpr bool is_map = !std::is_void::value; + static constexpr bool is_set = !is_map; + static constexpr bool is_transparent = + has_is_transparent::value && has_is_transparent::value; + + using key_type = Key; + using mapped_type = T; + using value_type = typename std::conditional< + is_set, Key, + robin_hood::pair::type, T>>::type; + using size_type = size_t; + using hasher = Hash; + using key_equal = KeyEqual; + using Self = Table; + +private: + static_assert(MaxLoadFactor100 > 10 && MaxLoadFactor100 < 100, + "MaxLoadFactor100 needs to be >10 && < 100"); + + using WHash = WrapHash; + using WKeyEqual = WrapKeyEqual; + + // configuration defaults + + // make sure we have 8 elements, needed to quickly rehash mInfo + static constexpr size_t InitialNumElements = sizeof(uint64_t); + static constexpr uint32_t InitialInfoNumBits = 5; + static constexpr uint8_t InitialInfoInc = 1U << InitialInfoNumBits; + static constexpr size_t InfoMask = InitialInfoInc - 1U; + static constexpr uint8_t InitialInfoHashShift = 0; + using DataPool = detail::NodeAllocator; + + // type needs to be wider than uint8_t. + using InfoType = uint32_t; + + // DataNode //////////////////////////////////////////////////////// + + // Primary template for the data node. We have special implementations for small and big + // objects. For large objects it is assumed that swap() is fairly slow, so we allocate these + // on the heap so swap merely swaps a pointer. + template + class DataNode {}; + + // Small: just allocate on the stack. + template + class DataNode final { + public: + template + explicit DataNode(M& ROBIN_HOOD_UNUSED(map) /*unused*/, Args&&... args) noexcept( + noexcept(value_type(std::forward(args)...))) + : mData(std::forward(args)...) {} + + DataNode(M& ROBIN_HOOD_UNUSED(map) /*unused*/, DataNode&& n) noexcept( + std::is_nothrow_move_constructible::value) + : mData(std::move(n.mData)) {} + + // doesn't do anything + void destroy(M& ROBIN_HOOD_UNUSED(map) /*unused*/) noexcept {} + void destroyDoNotDeallocate() noexcept {} + + value_type const* operator->() const noexcept { + return &mData; + } + value_type* operator->() noexcept { + return &mData; + } + + const value_type& operator*() const noexcept { + return mData; + } + + value_type& operator*() noexcept { + return mData; + } + + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type getFirst() noexcept { + return mData.first; + } + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type getFirst() noexcept { + return mData; + } + + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type + getFirst() const noexcept { + return mData.first; + } + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type getFirst() const noexcept { + return mData; + } + + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type getSecond() noexcept { + return mData.second; + } + + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type getSecond() const noexcept { + return mData.second; + } + + void swap(DataNode& o) noexcept( + noexcept(std::declval().swap(std::declval()))) { + mData.swap(o.mData); + } + + private: + value_type mData; + }; + + // big object: allocate on heap. + template + class DataNode { + public: + template + explicit DataNode(M& map, Args&&... args) + : mData(map.allocate()) { + ::new (static_cast(mData)) value_type(std::forward(args)...); + } + + DataNode(M& ROBIN_HOOD_UNUSED(map) /*unused*/, DataNode&& n) noexcept + : mData(std::move(n.mData)) {} + + void destroy(M& map) noexcept { + // don't deallocate, just put it into list of datapool. + mData->~value_type(); + map.deallocate(mData); + } + + void destroyDoNotDeallocate() noexcept { + mData->~value_type(); + } + + value_type const* operator->() const noexcept { + return mData; + } + + value_type* operator->() noexcept { + return mData; + } + + const value_type& operator*() const { + return *mData; + } + + value_type& operator*() { + return *mData; + } + + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type getFirst() noexcept { + return mData->first; + } + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type getFirst() noexcept { + return *mData; + } + + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type + getFirst() const noexcept { + return mData->first; + } + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type getFirst() const noexcept { + return *mData; + } + + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type getSecond() noexcept { + return mData->second; + } + + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type getSecond() const noexcept { + return mData->second; + } + + void swap(DataNode& o) noexcept { + using std::swap; + swap(mData, o.mData); + } + + private: + value_type* mData; + }; + + using Node = DataNode; + + // helpers for doInsert: extract first entry (only const required) + ROBIN_HOOD(NODISCARD) key_type const& getFirstConst(Node const& n) const noexcept { + return n.getFirst(); + } + + // in case we have void mapped_type, we are not using a pair, thus we just route k through. + // No need to disable this because it's just not used if not applicable. + ROBIN_HOOD(NODISCARD) key_type const& getFirstConst(key_type const& k) const noexcept { + return k; + } + + // in case we have non-void mapped_type, we have a standard robin_hood::pair + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::value, key_type const&>::type + getFirstConst(value_type const& vt) const noexcept { + return vt.first; + } + + // Cloner ////////////////////////////////////////////////////////// + + template + struct Cloner; + + // fast path: Just copy data, without allocating anything. + template + struct Cloner { + void operator()(M const& source, M& target) const { + auto const* const src = reinterpret_cast(source.mKeyVals); + auto* tgt = reinterpret_cast(target.mKeyVals); + auto const numElementsWithBuffer = target.calcNumElementsWithBuffer(target.mMask + 1); + std::copy(src, src + target.calcNumBytesTotal(numElementsWithBuffer), tgt); + } + }; + + template + struct Cloner { + void operator()(M const& s, M& t) const { + auto const numElementsWithBuffer = t.calcNumElementsWithBuffer(t.mMask + 1); + std::copy(s.mInfo, s.mInfo + t.calcNumBytesInfo(numElementsWithBuffer), t.mInfo); + + for (size_t i = 0; i < numElementsWithBuffer; ++i) { + if (t.mInfo[i]) { + ::new (static_cast(t.mKeyVals + i)) Node(t, *s.mKeyVals[i]); + } + } + } + }; + + // Destroyer /////////////////////////////////////////////////////// + + template + struct Destroyer {}; + + template + struct Destroyer { + void nodes(M& m) const noexcept { + m.mNumElements = 0; + } + + void nodesDoNotDeallocate(M& m) const noexcept { + m.mNumElements = 0; + } + }; + + template + struct Destroyer { + void nodes(M& m) const noexcept { + m.mNumElements = 0; + // clear also resets mInfo to 0, that's sometimes not necessary. + auto const numElementsWithBuffer = m.calcNumElementsWithBuffer(m.mMask + 1); + + for (size_t idx = 0; idx < numElementsWithBuffer; ++idx) { + if (0 != m.mInfo[idx]) { + Node& n = m.mKeyVals[idx]; + n.destroy(m); + n.~Node(); + } + } + } + + void nodesDoNotDeallocate(M& m) const noexcept { + m.mNumElements = 0; + // clear also resets mInfo to 0, that's sometimes not necessary. + auto const numElementsWithBuffer = m.calcNumElementsWithBuffer(m.mMask + 1); + for (size_t idx = 0; idx < numElementsWithBuffer; ++idx) { + if (0 != m.mInfo[idx]) { + Node& n = m.mKeyVals[idx]; + n.destroyDoNotDeallocate(); + n.~Node(); + } + } + } + }; + + // Iter //////////////////////////////////////////////////////////// + + struct fast_forward_tag {}; + + // generic iterator for both const_iterator and iterator. + template + // NOLINTNEXTLINE(hicpp-special-member-functions,cppcoreguidelines-special-member-functions) + class Iter { + private: + using NodePtr = typename std::conditional::type; + + public: + using difference_type = std::ptrdiff_t; + using value_type = typename Self::value_type; + using reference = typename std::conditional::type; + using pointer = typename std::conditional::type; + using iterator_category = std::forward_iterator_tag; + + // default constructed iterator can be compared to itself, but WON'T return true when + // compared to end(). + Iter() = default; + + // Rule of zero: nothing specified. The conversion constructor is only enabled for + // iterator to const_iterator, so it doesn't accidentally work as a copy ctor. + + // Conversion constructor from iterator to const_iterator. + template ::type> + // NOLINTNEXTLINE(hicpp-explicit-conversions) + Iter(Iter const& other) noexcept + : mKeyVals(other.mKeyVals) + , mInfo(other.mInfo) {} + + Iter(NodePtr valPtr, uint8_t const* infoPtr) noexcept + : mKeyVals(valPtr) + , mInfo(infoPtr) {} + + Iter(NodePtr valPtr, uint8_t const* infoPtr, + fast_forward_tag ROBIN_HOOD_UNUSED(tag) /*unused*/) noexcept + : mKeyVals(valPtr) + , mInfo(infoPtr) { + fastForward(); + } + + template ::type> + Iter& operator=(Iter const& other) noexcept { + mKeyVals = other.mKeyVals; + mInfo = other.mInfo; + return *this; + } + + // prefix increment. Undefined behavior if we are at end()! + Iter& operator++() noexcept { + mInfo++; + mKeyVals++; + fastForward(); + return *this; + } + + Iter operator++(int) noexcept { + Iter tmp = *this; + ++(*this); + return tmp; + } + + reference operator*() const { + return **mKeyVals; + } + + pointer operator->() const { + return &**mKeyVals; + } + + template + bool operator==(Iter const& o) const noexcept { + return mKeyVals == o.mKeyVals; + } + + template + bool operator!=(Iter const& o) const noexcept { + return mKeyVals != o.mKeyVals; + } + + private: + // fast forward to the next non-free info byte + // I've tried a few variants that don't depend on intrinsics, but unfortunately they are + // quite a bit slower than this one. So I've reverted that change again. See map_benchmark. + void fastForward() noexcept { + size_t n = 0; + while (0U == (n = detail::unaligned_load(mInfo))) { + mInfo += sizeof(size_t); + mKeyVals += sizeof(size_t); + } +#if defined(ROBIN_HOOD_DISABLE_INTRINSICS) + // we know for certain that within the next 8 bytes we'll find a non-zero one. + if (ROBIN_HOOD_UNLIKELY(0U == detail::unaligned_load(mInfo))) { + mInfo += 4; + mKeyVals += 4; + } + if (ROBIN_HOOD_UNLIKELY(0U == detail::unaligned_load(mInfo))) { + mInfo += 2; + mKeyVals += 2; + } + if (ROBIN_HOOD_UNLIKELY(0U == *mInfo)) { + mInfo += 1; + mKeyVals += 1; + } +#else +# if ROBIN_HOOD(LITTLE_ENDIAN) + auto inc = ROBIN_HOOD_COUNT_TRAILING_ZEROES(n) / 8; +# else + auto inc = ROBIN_HOOD_COUNT_LEADING_ZEROES(n) / 8; +# endif + mInfo += inc; + mKeyVals += inc; +#endif + } + + friend class Table; + NodePtr mKeyVals{nullptr}; + uint8_t const* mInfo{nullptr}; + }; + + //////////////////////////////////////////////////////////////////// + + // highly performance relevant code. + // Lower bits are used for indexing into the array (2^n size) + // The upper 1-5 bits need to be a reasonable good hash, to save comparisons. + template + void keyToIdx(HashKey&& key, size_t* idx, InfoType* info) const { + // for a user-specified hash that is *not* robin_hood::hash, apply robin_hood::hash as + // an additional mixing step. This serves as a bad hash prevention, if the given data is + // badly mixed. + using Mix = + typename std::conditional, hasher>::value, + ::robin_hood::detail::identity_hash, + ::robin_hood::hash>::type; + + // the lower InitialInfoNumBits are reserved for info. + auto h = Mix{}(WHash::operator()(key)); + *info = mInfoInc + static_cast((h & InfoMask) >> mInfoHashShift); + *idx = (h >> InitialInfoNumBits) & mMask; + } + + // forwards the index by one, wrapping around at the end + void next(InfoType* info, size_t* idx) const noexcept { + *idx = *idx + 1; + *info += mInfoInc; + } + + void nextWhileLess(InfoType* info, size_t* idx) const noexcept { + // unrolling this by hand did not bring any speedups. + while (*info < mInfo[*idx]) { + next(info, idx); + } + } + + // Shift everything up by one element. Tries to move stuff around. + void + shiftUp(size_t startIdx, + size_t const insertion_idx) noexcept(std::is_nothrow_move_assignable::value) { + auto idx = startIdx; + ::new (static_cast(mKeyVals + idx)) Node(std::move(mKeyVals[idx - 1])); + while (--idx != insertion_idx) { + mKeyVals[idx] = std::move(mKeyVals[idx - 1]); + } + + idx = startIdx; + while (idx != insertion_idx) { + ROBIN_HOOD_COUNT(shiftUp) + mInfo[idx] = static_cast(mInfo[idx - 1] + mInfoInc); + if (ROBIN_HOOD_UNLIKELY(mInfo[idx] + mInfoInc > 0xFF)) { + mMaxNumElementsAllowed = 0; + } + --idx; + } + } + + void shiftDown(size_t idx) noexcept(std::is_nothrow_move_assignable::value) { + // until we find one that is either empty or has zero offset. + // TODO(martinus) we don't need to move everything, just the last one for the same + // bucket. + mKeyVals[idx].destroy(*this); + + // until we find one that is either empty or has zero offset. + while (mInfo[idx + 1] >= 2 * mInfoInc) { + ROBIN_HOOD_COUNT(shiftDown) + mInfo[idx] = static_cast(mInfo[idx + 1] - mInfoInc); + mKeyVals[idx] = std::move(mKeyVals[idx + 1]); + ++idx; + } + + mInfo[idx] = 0; + // don't destroy, we've moved it + // mKeyVals[idx].destroy(*this); + mKeyVals[idx].~Node(); + } + + // copy of find(), except that it returns iterator instead of const_iterator. + template + ROBIN_HOOD(NODISCARD) + size_t findIdx(Other const& key) const { + size_t idx{}; + InfoType info{}; + keyToIdx(key, &idx, &info); + + do { + // unrolling this twice gives a bit of a speedup. More unrolling did not help. + if (info == mInfo[idx] && + ROBIN_HOOD_LIKELY(WKeyEqual::operator()(key, mKeyVals[idx].getFirst()))) { + return idx; + } + next(&info, &idx); + if (info == mInfo[idx] && + ROBIN_HOOD_LIKELY(WKeyEqual::operator()(key, mKeyVals[idx].getFirst()))) { + return idx; + } + next(&info, &idx); + } while (info <= mInfo[idx]); + + // nothing found! + return mMask == 0 ? 0 + : static_cast(std::distance( + mKeyVals, reinterpret_cast_no_cast_align_warning(mInfo))); + } + + void cloneData(const Table& o) { + Cloner()(o, *this); + } + + // inserts a keyval that is guaranteed to be new, e.g. when the hashmap is resized. + // @return index where the element was created + size_t insert_move(Node&& keyval) { + // we don't retry, fail if overflowing + // don't need to check max num elements + if (0 == mMaxNumElementsAllowed && !try_increase_info()) { + throwOverflowError(); // impossible to reach LCOV_EXCL_LINE + } + + size_t idx{}; + InfoType info{}; + keyToIdx(keyval.getFirst(), &idx, &info); + + // skip forward. Use <= because we are certain that the element is not there. + while (info <= mInfo[idx]) { + idx = idx + 1; + info += mInfoInc; + } + + // key not found, so we are now exactly where we want to insert it. + auto const insertion_idx = idx; + auto const insertion_info = static_cast(info); + if (ROBIN_HOOD_UNLIKELY(insertion_info + mInfoInc > 0xFF)) { + mMaxNumElementsAllowed = 0; + } + + // find an empty spot + while (0 != mInfo[idx]) { + next(&info, &idx); + } + + auto& l = mKeyVals[insertion_idx]; + if (idx == insertion_idx) { + ::new (static_cast(&l)) Node(std::move(keyval)); + } else { + shiftUp(idx, insertion_idx); + l = std::move(keyval); + } + + // put at empty spot + mInfo[insertion_idx] = insertion_info; + + ++mNumElements; + return insertion_idx; + } + +public: + using iterator = Iter; + using const_iterator = Iter; + + Table() noexcept(noexcept(Hash()) && noexcept(KeyEqual())) + : WHash() + , WKeyEqual() { + ROBIN_HOOD_TRACE(this) + } + + // Creates an empty hash map. Nothing is allocated yet, this happens at the first insert. + // This tremendously speeds up ctor & dtor of a map that never receives an element. The + // penalty is payed at the first insert, and not before. Lookup of this empty map works + // because everybody points to DummyInfoByte::b. parameter bucket_count is dictated by the + // standard, but we can ignore it. + explicit Table( + size_t ROBIN_HOOD_UNUSED(bucket_count) /*unused*/, const Hash& h = Hash{}, + const KeyEqual& equal = KeyEqual{}) noexcept(noexcept(Hash(h)) && noexcept(KeyEqual(equal))) + : WHash(h) + , WKeyEqual(equal) { + ROBIN_HOOD_TRACE(this) + } + + template + Table(Iter first, Iter last, size_t ROBIN_HOOD_UNUSED(bucket_count) /*unused*/ = 0, + const Hash& h = Hash{}, const KeyEqual& equal = KeyEqual{}) + : WHash(h) + , WKeyEqual(equal) { + ROBIN_HOOD_TRACE(this) + insert(first, last); + } + + Table(std::initializer_list initlist, + size_t ROBIN_HOOD_UNUSED(bucket_count) /*unused*/ = 0, const Hash& h = Hash{}, + const KeyEqual& equal = KeyEqual{}) + : WHash(h) + , WKeyEqual(equal) { + ROBIN_HOOD_TRACE(this) + insert(initlist.begin(), initlist.end()); + } + + Table(Table&& o) noexcept + : WHash(std::move(static_cast(o))) + , WKeyEqual(std::move(static_cast(o))) + , DataPool(std::move(static_cast(o))) { + ROBIN_HOOD_TRACE(this) + if (o.mMask) { + mKeyVals = std::move(o.mKeyVals); + mInfo = std::move(o.mInfo); + mNumElements = std::move(o.mNumElements); + mMask = std::move(o.mMask); + mMaxNumElementsAllowed = std::move(o.mMaxNumElementsAllowed); + mInfoInc = std::move(o.mInfoInc); + mInfoHashShift = std::move(o.mInfoHashShift); + // set other's mask to 0 so its destructor won't do anything + o.init(); + } + } + + Table& operator=(Table&& o) noexcept { + ROBIN_HOOD_TRACE(this) + if (&o != this) { + if (o.mMask) { + // only move stuff if the other map actually has some data + destroy(); + mKeyVals = std::move(o.mKeyVals); + mInfo = std::move(o.mInfo); + mNumElements = std::move(o.mNumElements); + mMask = std::move(o.mMask); + mMaxNumElementsAllowed = std::move(o.mMaxNumElementsAllowed); + mInfoInc = std::move(o.mInfoInc); + mInfoHashShift = std::move(o.mInfoHashShift); + WHash::operator=(std::move(static_cast(o))); + WKeyEqual::operator=(std::move(static_cast(o))); + DataPool::operator=(std::move(static_cast(o))); + + o.init(); + + } else { + // nothing in the other map => just clear us. + clear(); + } + } + return *this; + } + + Table(const Table& o) + : WHash(static_cast(o)) + , WKeyEqual(static_cast(o)) + , DataPool(static_cast(o)) { + ROBIN_HOOD_TRACE(this) + if (!o.empty()) { + // not empty: create an exact copy. it is also possible to just iterate through all + // elements and insert them, but copying is probably faster. + + auto const numElementsWithBuffer = calcNumElementsWithBuffer(o.mMask + 1); + mKeyVals = static_cast(detail::assertNotNull( + std::malloc(calcNumBytesTotal(numElementsWithBuffer)))); + // no need for calloc because clonData does memcpy + mInfo = reinterpret_cast(mKeyVals + numElementsWithBuffer); + mNumElements = o.mNumElements; + mMask = o.mMask; + mMaxNumElementsAllowed = o.mMaxNumElementsAllowed; + mInfoInc = o.mInfoInc; + mInfoHashShift = o.mInfoHashShift; + cloneData(o); + } + } + + // Creates a copy of the given map. Copy constructor of each entry is used. + // Not sure why clang-tidy thinks this doesn't handle self assignment, it does + // NOLINTNEXTLINE(bugprone-unhandled-self-assignment,cert-oop54-cpp) + Table& operator=(Table const& o) { + ROBIN_HOOD_TRACE(this) + if (&o == this) { + // prevent assigning of itself + return *this; + } + + // we keep using the old allocator and not assign the new one, because we want to keep + // the memory available. when it is the same size. + if (o.empty()) { + if (0 == mMask) { + // nothing to do, we are empty too + return *this; + } + + // not empty: destroy what we have there + // clear also resets mInfo to 0, that's sometimes not necessary. + destroy(); + init(); + WHash::operator=(static_cast(o)); + WKeyEqual::operator=(static_cast(o)); + DataPool::operator=(static_cast(o)); + + return *this; + } + + // clean up old stuff + Destroyer::value>{}.nodes(*this); + + if (mMask != o.mMask) { + // no luck: we don't have the same array size allocated, so we need to realloc. + if (0 != mMask) { + // only deallocate if we actually have data! + std::free(mKeyVals); + } + + auto const numElementsWithBuffer = calcNumElementsWithBuffer(o.mMask + 1); + mKeyVals = static_cast(detail::assertNotNull( + std::malloc(calcNumBytesTotal(numElementsWithBuffer)))); + + // no need for calloc here because cloneData performs a memcpy. + mInfo = reinterpret_cast(mKeyVals + numElementsWithBuffer); + // sentinel is set in cloneData + } + WHash::operator=(static_cast(o)); + WKeyEqual::operator=(static_cast(o)); + DataPool::operator=(static_cast(o)); + mNumElements = o.mNumElements; + mMask = o.mMask; + mMaxNumElementsAllowed = o.mMaxNumElementsAllowed; + mInfoInc = o.mInfoInc; + mInfoHashShift = o.mInfoHashShift; + cloneData(o); + + return *this; + } + + // Swaps everything between the two maps. + void swap(Table& o) { + ROBIN_HOOD_TRACE(this) + using std::swap; + swap(o, *this); + } + + // Clears all data, without resizing. + void clear() { + ROBIN_HOOD_TRACE(this) + if (empty()) { + // don't do anything! also important because we don't want to write to + // DummyInfoByte::b, even though we would just write 0 to it. + return; + } + + Destroyer::value>{}.nodes(*this); + + auto const numElementsWithBuffer = calcNumElementsWithBuffer(mMask + 1); + // clear everything, then set the sentinel again + uint8_t const z = 0; + std::fill(mInfo, mInfo + calcNumBytesInfo(numElementsWithBuffer), z); + mInfo[numElementsWithBuffer] = 1; + + mInfoInc = InitialInfoInc; + mInfoHashShift = InitialInfoHashShift; + } + + // Destroys the map and all it's contents. + ~Table() { + ROBIN_HOOD_TRACE(this) + destroy(); + } + + // Checks if both tables contain the same entries. Order is irrelevant. + bool operator==(const Table& other) const { + ROBIN_HOOD_TRACE(this) + if (other.size() != size()) { + return false; + } + for (auto const& otherEntry : other) { + if (!has(otherEntry)) { + return false; + } + } + + return true; + } + + bool operator!=(const Table& other) const { + ROBIN_HOOD_TRACE(this) + return !operator==(other); + } + + template + typename std::enable_if::value, Q&>::type operator[](const key_type& key) { + ROBIN_HOOD_TRACE(this) + return doCreateByKey(key); + } + + template + typename std::enable_if::value, Q&>::type operator[](key_type&& key) { + ROBIN_HOOD_TRACE(this) + return doCreateByKey(std::move(key)); + } + + template + void insert(Iter first, Iter last) { + for (; first != last; ++first) { + // value_type ctor needed because this might be called with std::pair's + insert(value_type(*first)); + } + } + + template + std::pair emplace(Args&&... args) { + ROBIN_HOOD_TRACE(this) + Node n{*this, std::forward(args)...}; + auto r = doInsert(std::move(n)); + if (!r.second) { + // insertion not possible: destroy node + // NOLINTNEXTLINE(bugprone-use-after-move) + n.destroy(*this); + } + return r; + } + + template + std::pair try_emplace(const key_type& key, Args&&... args) { + return try_emplace_impl(key, std::forward(args)...); + } + + template + std::pair try_emplace(key_type&& key, Args&&... args) { + return try_emplace_impl(std::move(key), std::forward(args)...); + } + + template + std::pair try_emplace(const_iterator hint, const key_type& key, + Args&&... args) { + (void)hint; + return try_emplace_impl(key, std::forward(args)...); + } + + template + std::pair try_emplace(const_iterator hint, key_type&& key, Args&&... args) { + (void)hint; + return try_emplace_impl(std::move(key), std::forward(args)...); + } + + template + std::pair insert_or_assign(const key_type& key, Mapped&& obj) { + return insert_or_assign_impl(key, std::forward(obj)); + } + + template + std::pair insert_or_assign(key_type&& key, Mapped&& obj) { + return insert_or_assign_impl(std::move(key), std::forward(obj)); + } + + template + std::pair insert_or_assign(const_iterator hint, const key_type& key, + Mapped&& obj) { + (void)hint; + return insert_or_assign_impl(key, std::forward(obj)); + } + + template + std::pair insert_or_assign(const_iterator hint, key_type&& key, Mapped&& obj) { + (void)hint; + return insert_or_assign_impl(std::move(key), std::forward(obj)); + } + + std::pair insert(const value_type& keyval) { + ROBIN_HOOD_TRACE(this) + return doInsert(keyval); + } + + std::pair insert(value_type&& keyval) { + return doInsert(std::move(keyval)); + } + + // Returns 1 if key is found, 0 otherwise. + size_t count(const key_type& key) const { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this) + auto kv = mKeyVals + findIdx(key); + if (kv != reinterpret_cast_no_cast_align_warning(mInfo)) { + return 1; + } + return 0; + } + + template + // NOLINTNEXTLINE(modernize-use-nodiscard) + typename std::enable_if::type count(const OtherKey& key) const { + ROBIN_HOOD_TRACE(this) + auto kv = mKeyVals + findIdx(key); + if (kv != reinterpret_cast_no_cast_align_warning(mInfo)) { + return 1; + } + return 0; + } + + bool contains(const key_type& key) const { // NOLINT(modernize-use-nodiscard) + return 1U == count(key); + } + + template + // NOLINTNEXTLINE(modernize-use-nodiscard) + typename std::enable_if::type contains(const OtherKey& key) const { + return 1U == count(key); + } + + // Returns a reference to the value found for key. + // Throws std::out_of_range if element cannot be found + template + // NOLINTNEXTLINE(modernize-use-nodiscard) + typename std::enable_if::value, Q&>::type at(key_type const& key) { + ROBIN_HOOD_TRACE(this) + auto kv = mKeyVals + findIdx(key); + if (kv == reinterpret_cast_no_cast_align_warning(mInfo)) { + doThrow("key not found"); + } + return kv->getSecond(); + } + + // Returns a reference to the value found for key. + // Throws std::out_of_range if element cannot be found + template + // NOLINTNEXTLINE(modernize-use-nodiscard) + typename std::enable_if::value, Q const&>::type at(key_type const& key) const { + ROBIN_HOOD_TRACE(this) + auto kv = mKeyVals + findIdx(key); + if (kv == reinterpret_cast_no_cast_align_warning(mInfo)) { + doThrow("key not found"); + } + return kv->getSecond(); + } + + const_iterator find(const key_type& key) const { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this) + const size_t idx = findIdx(key); + return const_iterator{mKeyVals + idx, mInfo + idx}; + } + + template + const_iterator find(const OtherKey& key, is_transparent_tag /*unused*/) const { + ROBIN_HOOD_TRACE(this) + const size_t idx = findIdx(key); + return const_iterator{mKeyVals + idx, mInfo + idx}; + } + + template + typename std::enable_if::type // NOLINT(modernize-use-nodiscard) + find(const OtherKey& key) const { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this) + const size_t idx = findIdx(key); + return const_iterator{mKeyVals + idx, mInfo + idx}; + } + + iterator find(const key_type& key) { + ROBIN_HOOD_TRACE(this) + const size_t idx = findIdx(key); + return iterator{mKeyVals + idx, mInfo + idx}; + } + + template + iterator find(const OtherKey& key, is_transparent_tag /*unused*/) { + ROBIN_HOOD_TRACE(this) + const size_t idx = findIdx(key); + return iterator{mKeyVals + idx, mInfo + idx}; + } + + template + typename std::enable_if::type find(const OtherKey& key) { + ROBIN_HOOD_TRACE(this) + const size_t idx = findIdx(key); + return iterator{mKeyVals + idx, mInfo + idx}; + } + + iterator begin() { + ROBIN_HOOD_TRACE(this) + if (empty()) { + return end(); + } + return iterator(mKeyVals, mInfo, fast_forward_tag{}); + } + const_iterator begin() const { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this) + return cbegin(); + } + const_iterator cbegin() const { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this) + if (empty()) { + return cend(); + } + return const_iterator(mKeyVals, mInfo, fast_forward_tag{}); + } + + iterator end() { + ROBIN_HOOD_TRACE(this) + // no need to supply valid info pointer: end() must not be dereferenced, and only node + // pointer is compared. + return iterator{reinterpret_cast_no_cast_align_warning(mInfo), nullptr}; + } + const_iterator end() const { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this) + return cend(); + } + const_iterator cend() const { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this) + return const_iterator{reinterpret_cast_no_cast_align_warning(mInfo), nullptr}; + } + + iterator erase(const_iterator pos) { + ROBIN_HOOD_TRACE(this) + // its safe to perform const cast here + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) + return erase(iterator{const_cast(pos.mKeyVals), const_cast(pos.mInfo)}); + } + + // Erases element at pos, returns iterator to the next element. + iterator erase(iterator pos) { + ROBIN_HOOD_TRACE(this) + // we assume that pos always points to a valid entry, and not end(). + auto const idx = static_cast(pos.mKeyVals - mKeyVals); + + shiftDown(idx); + --mNumElements; + + if (*pos.mInfo) { + // we've backward shifted, return this again + return pos; + } + + // no backward shift, return next element + return ++pos; + } + + size_t erase(const key_type& key) { + ROBIN_HOOD_TRACE(this) + size_t idx{}; + InfoType info{}; + keyToIdx(key, &idx, &info); + + // check while info matches with the source idx + do { + if (info == mInfo[idx] && WKeyEqual::operator()(key, mKeyVals[idx].getFirst())) { + shiftDown(idx); + --mNumElements; + return 1; + } + next(&info, &idx); + } while (info <= mInfo[idx]); + + // nothing found to delete + return 0; + } + + // reserves space for the specified number of elements. Makes sure the old data fits. + // exactly the same as reserve(c). + void rehash(size_t c) { + reserve(c); + } + + // reserves space for the specified number of elements. Makes sure the old data fits. + // Exactly the same as resize(c). Use resize(0) to shrink to fit. + void reserve(size_t c) { + ROBIN_HOOD_TRACE(this) + auto const minElementsAllowed = (std::max)(c, mNumElements); + auto newSize = InitialNumElements; + while (calcMaxNumElementsAllowed(newSize) < minElementsAllowed && newSize != 0) { + newSize *= 2; + } + if (ROBIN_HOOD_UNLIKELY(newSize == 0)) { + throwOverflowError(); + } + + rehashPowerOfTwo(newSize); + } + + size_type size() const noexcept { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this) + return mNumElements; + } + + size_type max_size() const noexcept { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this) + return static_cast(-1); + } + + ROBIN_HOOD(NODISCARD) bool empty() const noexcept { + ROBIN_HOOD_TRACE(this) + return 0 == mNumElements; + } + + float max_load_factor() const noexcept { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this) + return MaxLoadFactor100 / 100.0F; + } + + // Average number of elements per bucket. Since we allow only 1 per bucket + float load_factor() const noexcept { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this) + return static_cast(size()) / static_cast(mMask + 1); + } + + ROBIN_HOOD(NODISCARD) size_t mask() const noexcept { + ROBIN_HOOD_TRACE(this) + return mMask; + } + + ROBIN_HOOD(NODISCARD) size_t calcMaxNumElementsAllowed(size_t maxElements) const noexcept { + if (ROBIN_HOOD_LIKELY(maxElements <= (std::numeric_limits::max)() / 100)) { + return maxElements * MaxLoadFactor100 / 100; + } + + // we might be a bit inprecise, but since maxElements is quite large that doesn't matter + return (maxElements / 100) * MaxLoadFactor100; + } + + ROBIN_HOOD(NODISCARD) size_t calcNumBytesInfo(size_t numElements) const noexcept { + // we add a uint64_t, which houses the sentinel (first byte) and padding so we can load + // 64bit types. + return numElements + sizeof(uint64_t); + } + + ROBIN_HOOD(NODISCARD) + size_t calcNumElementsWithBuffer(size_t numElements) const noexcept { + auto maxNumElementsAllowed = calcMaxNumElementsAllowed(numElements); + return numElements + (std::min)(maxNumElementsAllowed, (static_cast(0xFF))); + } + + // calculation only allowed for 2^n values + ROBIN_HOOD(NODISCARD) size_t calcNumBytesTotal(size_t numElements) const { +#if ROBIN_HOOD(BITNESS) == 64 + return numElements * sizeof(Node) + calcNumBytesInfo(numElements); +#else + // make sure we're doing 64bit operations, so we are at least safe against 32bit overflows. + auto const ne = static_cast(numElements); + auto const s = static_cast(sizeof(Node)); + auto const infos = static_cast(calcNumBytesInfo(numElements)); + + auto const total64 = ne * s + infos; + auto const total = static_cast(total64); + + if (ROBIN_HOOD_UNLIKELY(static_cast(total) != total64)) { + throwOverflowError(); + } + return total; +#endif + } + +private: + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::value, bool>::type has(const value_type& e) const { + ROBIN_HOOD_TRACE(this) + auto it = find(e.first); + return it != end() && it->second == e.second; + } + + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::value, bool>::type has(const value_type& e) const { + ROBIN_HOOD_TRACE(this) + return find(e) != end(); + } + + // reserves space for at least the specified number of elements. + // only works if numBuckets if power of two + void rehashPowerOfTwo(size_t numBuckets) { + ROBIN_HOOD_TRACE(this) + + Node* const oldKeyVals = mKeyVals; + uint8_t const* const oldInfo = mInfo; + + const size_t oldMaxElementsWithBuffer = calcNumElementsWithBuffer(mMask + 1); + + // resize operation: move stuff + init_data(numBuckets); + if (oldMaxElementsWithBuffer > 1) { + for (size_t i = 0; i < oldMaxElementsWithBuffer; ++i) { + if (oldInfo[i] != 0) { + insert_move(std::move(oldKeyVals[i])); + // destroy the node but DON'T destroy the data. + oldKeyVals[i].~Node(); + } + } + + // this check is not necessary as it's guarded by the previous if, but it helps silence + // g++'s overeager "attempt to free a non-heap object 'map' + // [-Werror=free-nonheap-object]" warning. + if (oldKeyVals != reinterpret_cast_no_cast_align_warning(&mMask)) { + // don't destroy old data: put it into the pool instead + DataPool::addOrFree(oldKeyVals, calcNumBytesTotal(oldMaxElementsWithBuffer)); + } + } + } + + ROBIN_HOOD(NOINLINE) void throwOverflowError() const { +#if ROBIN_HOOD(HAS_EXCEPTIONS) + throw std::overflow_error("robin_hood::map overflow"); +#else + abort(); +#endif + } + + template + std::pair try_emplace_impl(OtherKey&& key, Args&&... args) { + ROBIN_HOOD_TRACE(this) + auto it = find(key); + if (it == end()) { + return emplace(std::piecewise_construct, + std::forward_as_tuple(std::forward(key)), + std::forward_as_tuple(std::forward(args)...)); + } + return {it, false}; + } + + template + std::pair insert_or_assign_impl(OtherKey&& key, Mapped&& obj) { + ROBIN_HOOD_TRACE(this) + auto it = find(key); + if (it == end()) { + return emplace(std::forward(key), std::forward(obj)); + } + it->second = std::forward(obj); + return {it, false}; + } + + void init_data(size_t max_elements) { + mNumElements = 0; + mMask = max_elements - 1; + mMaxNumElementsAllowed = calcMaxNumElementsAllowed(max_elements); + + auto const numElementsWithBuffer = calcNumElementsWithBuffer(max_elements); + + // calloc also zeroes everything + mKeyVals = reinterpret_cast(detail::assertNotNull( + std::calloc(1, calcNumBytesTotal(numElementsWithBuffer)))); + mInfo = reinterpret_cast(mKeyVals + numElementsWithBuffer); + + // set sentinel + mInfo[numElementsWithBuffer] = 1; + + mInfoInc = InitialInfoInc; + mInfoHashShift = InitialInfoHashShift; + } + + template + typename std::enable_if::value, Q&>::type doCreateByKey(Arg&& key) { + while (true) { + size_t idx{}; + InfoType info{}; + keyToIdx(key, &idx, &info); + nextWhileLess(&info, &idx); + + // while we potentially have a match. Can't do a do-while here because when mInfo is + // 0 we don't want to skip forward + while (info == mInfo[idx]) { + if (WKeyEqual::operator()(key, mKeyVals[idx].getFirst())) { + // key already exists, do not insert. + return mKeyVals[idx].getSecond(); + } + next(&info, &idx); + } + + // unlikely that this evaluates to true + if (ROBIN_HOOD_UNLIKELY(mNumElements >= mMaxNumElementsAllowed)) { + increase_size(); + continue; + } + + // key not found, so we are now exactly where we want to insert it. + auto const insertion_idx = idx; + auto const insertion_info = info; + if (ROBIN_HOOD_UNLIKELY(insertion_info + mInfoInc > 0xFF)) { + mMaxNumElementsAllowed = 0; + } + + // find an empty spot + while (0 != mInfo[idx]) { + next(&info, &idx); + } + + auto& l = mKeyVals[insertion_idx]; + if (idx == insertion_idx) { + // put at empty spot. This forwards all arguments into the node where the object + // is constructed exactly where it is needed. + ::new (static_cast(&l)) + Node(*this, std::piecewise_construct, + std::forward_as_tuple(std::forward(key)), std::forward_as_tuple()); + } else { + shiftUp(idx, insertion_idx); + l = Node(*this, std::piecewise_construct, + std::forward_as_tuple(std::forward(key)), std::forward_as_tuple()); + } + + // mKeyVals[idx].getFirst() = std::move(key); + mInfo[insertion_idx] = static_cast(insertion_info); + + ++mNumElements; + return mKeyVals[insertion_idx].getSecond(); + } + } + + // This is exactly the same code as operator[], except for the return values + template + std::pair doInsert(Arg&& keyval) { + while (true) { + size_t idx{}; + InfoType info{}; + keyToIdx(getFirstConst(keyval), &idx, &info); + nextWhileLess(&info, &idx); + + // while we potentially have a match + while (info == mInfo[idx]) { + if (WKeyEqual::operator()(getFirstConst(keyval), mKeyVals[idx].getFirst())) { + // key already exists, do NOT insert. + // see http://en.cppreference.com/w/cpp/container/unordered_map/insert + return std::make_pair(iterator(mKeyVals + idx, mInfo + idx), + false); + } + next(&info, &idx); + } + + // unlikely that this evaluates to true + if (ROBIN_HOOD_UNLIKELY(mNumElements >= mMaxNumElementsAllowed)) { + increase_size(); + continue; + } + + // key not found, so we are now exactly where we want to insert it. + auto const insertion_idx = idx; + auto const insertion_info = info; + if (ROBIN_HOOD_UNLIKELY(insertion_info + mInfoInc > 0xFF)) { + mMaxNumElementsAllowed = 0; + } + + // find an empty spot + while (0 != mInfo[idx]) { + next(&info, &idx); + } + + auto& l = mKeyVals[insertion_idx]; + if (idx == insertion_idx) { + ::new (static_cast(&l)) Node(*this, std::forward(keyval)); + } else { + shiftUp(idx, insertion_idx); + l = Node(*this, std::forward(keyval)); + } + + // put at empty spot + mInfo[insertion_idx] = static_cast(insertion_info); + + ++mNumElements; + return std::make_pair(iterator(mKeyVals + insertion_idx, mInfo + insertion_idx), true); + } + } + + bool try_increase_info() { + ROBIN_HOOD_LOG("mInfoInc=" << mInfoInc << ", numElements=" << mNumElements + << ", maxNumElementsAllowed=" + << calcMaxNumElementsAllowed(mMask + 1)) + if (mInfoInc <= 2) { + // need to be > 2 so that shift works (otherwise undefined behavior!) + return false; + } + // we got space left, try to make info smaller + mInfoInc = static_cast(mInfoInc >> 1U); + + // remove one bit of the hash, leaving more space for the distance info. + // This is extremely fast because we can operate on 8 bytes at once. + ++mInfoHashShift; + auto const numElementsWithBuffer = calcNumElementsWithBuffer(mMask + 1); + + for (size_t i = 0; i < numElementsWithBuffer; i += 8) { + auto val = unaligned_load(mInfo + i); + val = (val >> 1U) & UINT64_C(0x7f7f7f7f7f7f7f7f); + std::memcpy(mInfo + i, &val, sizeof(val)); + } + // update sentinel, which might have been cleared out! + mInfo[numElementsWithBuffer] = 1; + + mMaxNumElementsAllowed = calcMaxNumElementsAllowed(mMask + 1); + return true; + } + + void increase_size() { + // nothing allocated yet? just allocate InitialNumElements + if (0 == mMask) { + init_data(InitialNumElements); + return; + } + + auto const maxNumElementsAllowed = calcMaxNumElementsAllowed(mMask + 1); + if (mNumElements < maxNumElementsAllowed && try_increase_info()) { + return; + } + + ROBIN_HOOD_LOG("mNumElements=" << mNumElements << ", maxNumElementsAllowed=" + << maxNumElementsAllowed << ", load=" + << (static_cast(mNumElements) * 100.0 / + (static_cast(mMask) + 1))) + // it seems we have a really bad hash function! don't try to resize again + if (mNumElements * 2 < calcMaxNumElementsAllowed(mMask + 1)) { + throwOverflowError(); + } + + rehashPowerOfTwo((mMask + 1) * 2); + } + + void destroy() { + if (0 == mMask) { + // don't deallocate! + return; + } + + Destroyer::value>{} + .nodesDoNotDeallocate(*this); + + // This protection against not deleting mMask shouldn't be needed as it's sufficiently + // protected with the 0==mMask check, but I have this anyways because g++ 7 otherwise + // reports a compile error: attempt to free a non-heap object 'fm' + // [-Werror=free-nonheap-object] + if (mKeyVals != reinterpret_cast_no_cast_align_warning(&mMask)) { + std::free(mKeyVals); + } + } + + void init() noexcept { + mKeyVals = reinterpret_cast_no_cast_align_warning(&mMask); + mInfo = reinterpret_cast(&mMask); + mNumElements = 0; + mMask = 0; + mMaxNumElementsAllowed = 0; + mInfoInc = InitialInfoInc; + mInfoHashShift = InitialInfoHashShift; + } + + // members are sorted so no padding occurs + Node* mKeyVals = reinterpret_cast_no_cast_align_warning(&mMask); // 8 byte 8 + uint8_t* mInfo = reinterpret_cast(&mMask); // 8 byte 16 + size_t mNumElements = 0; // 8 byte 24 + size_t mMask = 0; // 8 byte 32 + size_t mMaxNumElementsAllowed = 0; // 8 byte 40 + InfoType mInfoInc = InitialInfoInc; // 4 byte 44 + InfoType mInfoHashShift = InitialInfoHashShift; // 4 byte 48 + // 16 byte 56 if NodeAllocator +}; + +} // namespace detail + +// map + +template , + typename KeyEqual = std::equal_to, size_t MaxLoadFactor100 = 80> +using unordered_flat_map = detail::Table; + +template , + typename KeyEqual = std::equal_to, size_t MaxLoadFactor100 = 80> +using unordered_node_map = detail::Table; + +template , + typename KeyEqual = std::equal_to, size_t MaxLoadFactor100 = 80> +using unordered_map = + detail::Table) <= sizeof(size_t) * 6 && + std::is_nothrow_move_constructible>::value && + std::is_nothrow_move_assignable>::value, + MaxLoadFactor100, Key, T, Hash, KeyEqual>; + +// set + +template , typename KeyEqual = std::equal_to, + size_t MaxLoadFactor100 = 80> +using unordered_flat_set = detail::Table; + +template , typename KeyEqual = std::equal_to, + size_t MaxLoadFactor100 = 80> +using unordered_node_set = detail::Table; + +template , typename KeyEqual = std::equal_to, + size_t MaxLoadFactor100 = 80> +using unordered_set = detail::Table::value && + std::is_nothrow_move_assignable::value, + MaxLoadFactor100, Key, void, Hash, KeyEqual>; + +} // namespace robin_hood + +#endif diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Context.h b/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Context.h new file mode 100644 index 00000000..bf73811a --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Context.h @@ -0,0 +1,447 @@ +#pragma once + +#include "Header.h" +#include "Input.h" +#include "ScriptInterface.h" +#include "ScrollTypes.h" +#include "Traits.h" +#include "Types.h" + +namespace Rml { + +class Stream; +class ContextInstancer; +class ElementDocument; +class EventListener; +class DataModel; +class DataModelConstructor; +class DataTypeRegister; +class ScrollController; +class RenderManager; +class TextInputHandler; +enum class EventId : uint16_t; + +/** + A context for storing, rendering, and processing RML documents. Multiple contexts can exist simultaneously. + */ + +class RMLUICORE_API Context : public ScriptInterface { +public: + /// Constructs a new, uninitialised context. This should not be called directly, use CreateContext() instead. + /// @param[in] name The name of the context. + /// @param[in] render_manager The render manager used for this context. + /// @param[in] text_input_handler The text input handler used for this context. + Context(const String& name, RenderManager* render_manager, TextInputHandler* text_input_handler); + /// Destroys a context. + virtual ~Context(); + + /// Returns the name of the context. + /// @return The context's name. + const String& GetName() const; + + /// Changes the dimensions of the context. + /// @param[in] dimensions The new dimensions of the context. + void SetDimensions(Vector2i dimensions); + /// Returns the dimensions of the context. + /// @return The current dimensions of the context. + Vector2i GetDimensions() const; + + /// Changes the ratio of the 'dp' unit to the 'px' unit. + /// @param[in] dp_ratio The new density-independent pixel ratio of the context. + void SetDensityIndependentPixelRatio(float dp_ratio); + /// Returns the ratio of the 'dp' unit to the 'px' unit. + /// @return The current density-independent pixel ratio of the context. + float GetDensityIndependentPixelRatio() const; + + /// Updates all elements in the context's documents. + /// This must be called before Context::Render, but after any elements have been changed, added, or removed. + bool Update(); + /// Renders all visible elements in the context's documents. + bool Render(); + + /// Creates a new, empty document and places it into this context. + /// @param[in] instancer_name The name of the instancer used to create the document. + /// @return The new document, or nullptr if no document could be created. + ElementDocument* CreateDocument(const String& instancer_name = "body"); + /// Load a document into the context. + /// @param[in] document_path The path to the document to load. The path is passed directly to the file interface which is used to load the file. + /// The default file interface accepts both absolute paths and paths relative to the working directory. + /// @return The loaded document, or nullptr if no document was loaded. + ElementDocument* LoadDocument(const String& document_path); + /// Load a document into the context. + /// @param[in] document_stream The opened stream, ready to read. + /// @return The loaded document, or nullptr if no document was loaded. + ElementDocument* LoadDocument(Stream* document_stream); + /// Load a document into the context. + /// @param[in] document_rml The string containing the document RML. + /// @param[in] source_url Optional string used to set the document's source URL, or naming the document for log messages. + /// @return The loaded document, or nullptr if no document was loaded. + ElementDocument* LoadDocumentFromMemory(const String& document_rml, const String& source_url = "[document from memory]"); + /// Unload the given document. + /// @param[in] document The document to unload. + /// @note The destruction of the document is deferred until the next call to Context::Update(). + void UnloadDocument(ElementDocument* document); + /// Unloads all loaded documents. + /// @note The destruction of the documents is deferred until the next call to Context::Update(). + void UnloadAllDocuments(); + + /// Enable or disable handling of the mouse cursor from this context. + /// When enabled, changes to the cursor name are passed to the system interface. + /// @param[in] enable True to enable mouse cursor handling, false to disable. + void EnableMouseCursor(bool enable); + + /// Activate or deactivate a media theme. Themes can be used in RCSS media queries. + /// @param theme_name[in] The name of the theme to (de)activate. + /// @param activate True to activate the given theme, false to deactivate. + void ActivateTheme(const String& theme_name, bool activate); + /// Check if a given media theme has been activated. + /// @param theme_name The name of the theme. + /// @return True if the theme is activated. + bool IsThemeActive(const String& theme_name) const; + + /// Returns the first document in the context with the given id. + /// @param[in] id The id of the desired document. + /// @return The document (if it was found), or nullptr if no document exists with the ID. + ElementDocument* GetDocument(const String& id); + /// Returns a document in the context by index. + /// @param[in] index The index of the desired document. + /// @return The document (if one exists with this index), or nullptr if the index was invalid. + ElementDocument* GetDocument(int index); + /// Returns the number of documents in the context. + int GetNumDocuments() const; + + /// Returns the hover element. + /// @return The element the mouse cursor is hovering over. + Element* GetHoverElement(); + /// Returns the focus element. + /// @return The element with input focus. + Element* GetFocusElement(); + /// Returns the root element that holds all the documents + /// @return The root element. + Element* GetRootElement(); + + /// Returns the youngest descendent of the given element which is under the given point in screen coordinates. + /// @param[in] point The point to test. + /// @param[in] ignore_element If set, this element and its descendents will be ignored. + /// @param[in] element Used internally. + /// @return The element under the point, or nullptr if nothing is. + Element* GetElementAtPoint(Vector2f point, const Element* ignore_element = nullptr, Element* element = nullptr) const; + + /// Brings the document to the front of the document stack. + /// @param[in] document The document to pull to the front of the stack. + void PullDocumentToFront(ElementDocument* document); + /// Sends the document to the back of the document stack. + /// @param[in] document The document to push to the bottom of the stack. + void PushDocumentToBack(ElementDocument* document); + /// Remove the document from the focus history and focus the previous document. + /// @param[in] document The document to unfocus. + void UnfocusDocument(ElementDocument* document); + + /// Adds an event listener to the context's root element. + /// @param[in] event The name of the event to attach to. + /// @param[in] listener Listener object to be attached. + /// @param[in] in_capture_phase True if the listener is to be attached to the capture phase, false for the bubble phase. + void AddEventListener(const String& event, EventListener* listener, bool in_capture_phase = false); + /// Removes an event listener from the context's root element. + /// @param[in] event The name of the event to detach from. + /// @param[in] listener Listener object to be detached. + /// @param[in] in_capture_phase True to detach from the capture phase, false from the bubble phase. + void RemoveEventListener(const String& event, EventListener* listener, bool in_capture_phase = false); + + /// Sends a key down event into this context. + /// @param[in] key_identifier The key pressed. + /// @param[in] key_modifier_state The state of key modifiers (shift, control, caps-lock, etc.) keys; this should be generated by ORing together + /// members of the Input::KeyModifier enumeration. + /// @return True if the event was not consumed (ie, was prevented from propagating by an element), false if it was. + bool ProcessKeyDown(Input::KeyIdentifier key_identifier, int key_modifier_state); + /// Sends a key up event into this context. + /// @param[in] key_identifier The key released. + /// @param[in] key_modifier_state The state of key modifiers (shift, control, caps-lock, etc.) keys; this should be generated by ORing together + /// members of the Input::KeyModifier enumeration. + /// @return True if the event was not consumed (ie, was prevented from propagating by an element), false if it was. + bool ProcessKeyUp(Input::KeyIdentifier key_identifier, int key_modifier_state); + + /// Sends a single Unicode character as text input into this context. + /// @param[in] character The Unicode code point to send into this context. + /// @return True if the event was not consumed (ie, was prevented from propagating by an element), false if it was. + bool ProcessTextInput(Character character); + /// Sends a single ascii character as text input into this context. + bool ProcessTextInput(char character); + /// Sends a string of text as text input into this context. + /// @param[in] string The UTF-8 string to send into this context. + /// @return True if the event was not consumed (ie, was prevented from propagating by an element), false if it was. + bool ProcessTextInput(const String& string); + + /// Sends a mouse movement event into this context. + /// @param[in] x The x-coordinate of the mouse cursor, in window-coordinates (ie, 0 should be the left of the client area). + /// @param[in] y The y-coordinate of the mouse cursor, in window-coordinates (ie, 0 should be the top of the client area). + /// @param[in] key_modifier_state The state of key modifiers (shift, control, caps-lock, etc.) keys; this should be generated by ORing together + /// members of the Input::KeyModifier enumeration. + /// @return True if the mouse is not interacting with any elements in the context (see 'IsMouseInteracting'), otherwise false. + bool ProcessMouseMove(int x, int y, int key_modifier_state); + /// Sends a mouse-button down event into this context. + /// @param[in] button_index The index of the button that was pressed. Left: 0, Right: 1, Middle: 2. + /// @param[in] key_modifier_state The state of key modifiers (shift, control, caps-lock, etc.) keys; this should be generated by ORing together + /// members of the Input::KeyModifier enumeration. + /// @return True if the mouse is not interacting with any elements in the context (see 'IsMouseInteracting'), otherwise false. + bool ProcessMouseButtonDown(int button_index, int key_modifier_state); + /// Sends a mouse-button up event into this context. + /// @param[in] button_index The index of the button that was release. Left: 0, Right: 1, Middle: 2. + /// @param[in] key_modifier_state The state of key modifiers (shift, control, caps-lock, etc.) keys; this should be generated by ORing together + /// members of the Input::KeyModifier enumeration. + /// @return True if the mouse is not interacting with any elements in the context (see 'IsMouseInteracting'), otherwise false. + bool ProcessMouseButtonUp(int button_index, int key_modifier_state); + /// Sends a mousescroll event into this context. + /// @deprecated Please use the Vector2f version of this function. + bool ProcessMouseWheel(float wheel_delta, int key_modifier_state); + /// Sends a mousescroll event into this context, and scrolls the document unless the event was stopped from propagating. + /// @param[in] wheel_delta The mouse-wheel movement this frame, with positive values being directed right and down. + /// @param[in] key_modifier_state The state of key modifiers (shift, control, caps-lock, etc.) keys; this should be generated by ORing together + /// members of the Input::KeyModifier enumeration. + /// @return True if the event was not consumed (ie, was prevented from propagating by an element), false if it was. + bool ProcessMouseWheel(Vector2f wheel_delta, int key_modifier_state); + /// Tells the context the mouse has left the window. This removes any hover state from all elements and prevents 'Update()' from setting the hover + /// state for elements under the mouse. + /// @return True if the mouse is not interacting with any elements in the context (see 'IsMouseInteracting'), otherwise false. + /// @note The mouse is considered activated again after the next call to 'ProcessMouseMove()'. + bool ProcessMouseLeave(); + + /// Process touch movements for this context. + /// @param[in] touches List of touches. + /// @param[in] key_modifier_state The state of key modifiers (shift, control, caps-lock, etc.) keys; this should be generated by ORing together + /// members of the Input::KeyModifier enumeration. + /// @return True if no touch points are interacting with any elements in the context, otherwise false. + bool ProcessTouchMove(const TouchList& touches, int key_modifier_state); + /// Process touch start (press) for this context. + /// @param[in] touches List of touches. + /// @param[in] key_modifier_state The state of key modifiers (shift, control, caps-lock, etc.) keys; this should be generated by ORing together + /// members of the Input::KeyModifier enumeration. + /// @return True if no touch points are interacting with any elements in the context, otherwise false. + bool ProcessTouchStart(const TouchList& touches, int key_modifier_state); + /// Process touch end (release) for this context. + /// @param[in] touches List of touches. + /// @param[in] key_modifier_state The state of key modifiers (shift, control, caps-lock, etc.) keys; this should be generated by ORing together + /// members of the Input::KeyModifier enumeration. + /// @return True if no touch points are interacting with any elements in the context, otherwise false. + bool ProcessTouchEnd(const TouchList& touches, int key_modifier_state); + /// Process touch cancel for this context. + /// @param[in] touches List of touches. + /// @return True if no touch points are interacting with any elements in the context, otherwise false. + bool ProcessTouchCancel(const TouchList& touches); + + /// Returns a hint on whether the mouse is currently interacting with any elements in this context, based on previously submitted + /// 'ProcessMouse...()' commands. + /// @note Interaction is determined irrespective of background and opacity. See the RCSS property 'pointer-events' to disable interaction for + /// specific elements. + /// @return True if the mouse hovers over or has activated an element in this context, otherwise false. + bool IsMouseInteracting() const; + + /// Sets the default scroll behavior, such as for mouse wheel processing and scrollbar interaction. + /// @param[in] scroll_behavior The default smooth scroll behavior, set to instant to disable smooth scrolling. + /// @param[in] speed_factor A factor for adjusting the final smooth scrolling speed, must be strictly positive, defaults to 1.0. + void SetDefaultScrollBehavior(ScrollBehavior scroll_behavior, float speed_factor); + + /// Retrieves the render manager which can be used to submit changes to the render state. + RenderManager& GetRenderManager(); + + /// Retrieves the text input handler. + TextInputHandler* GetTextInputHandler() const; + + /// Sets the instancer to use for releasing this object. + /// @param[in] instancer The context's instancer. + void SetInstancer(ContextInstancer* instancer); + + /// Creates a data model. + /// The returned constructor can be used to bind data variables. Elements can bind to the model using the attribute 'data-model="name"'. + /// @param[in] name The name of the data model. + /// @param[in] data_type_register The data type register to use for the data model, or null to use the default register. + /// @param[in] allow_missing_variables If true, allows variables to be bound after document load. Views referencing + /// not-yet-bound variables will silently produce default values until the variable is bound and dirtied. + /// @return A constructor for the data model, or empty if it could not be created. + DataModelConstructor CreateDataModel(const String& name, DataTypeRegister* data_type_register = nullptr, bool allow_missing_variables = false); + /// Retrieves the constructor for an existing data model. + /// The returned constructor can be used to add additional bindings to an existing model. + /// @param[in] name The name of the data model. + /// @return A constructor for the data model, or empty if it could not be found. + DataModelConstructor GetDataModel(const String& name); + /// Retrieves all data models in this context. + /// @return A map of all data models in this context, keyed by their name. + UnorderedMap GetDataModels() const; + /// Removes the given data model. + /// This also removes all data views, controllers, and bindings contained by the data model. + /// @warning Invalidates all handles and constructors pointing to the data model. + /// @param[in] name The name of the data model. + /// @return True if successfully removed, false if no data model was found. + bool RemoveDataModel(const String& name); + + /// Sets the base tag name of documents before creation. Default: "body". + /// @param[in] tag The name of the base tag. Example: "html" + void SetDocumentsBaseTag(const String& tag); + /// Gets the base tag name of documents. + /// @return The current base tag name of documents. + const String& GetDocumentsBaseTag(); + + /// Updates the time until Update() should get called again. This can be used by elements and the application to + /// implement on-demand rendering and thus drastically save CPU/GPU and reduce power consumption during inactivity. + /// The context stores the lowest requested timestamp, which can later be retrieved using GetNextUpdateDelay(). + /// @param[in] delay Maximum time until next update. + void RequestNextUpdate(double delay); + /// Get the max delay until Update() and Render() should get called again. An application can choose to only call + /// update and render once the time has elapsed, but there's no harm in doing so more often. The returned value can + /// be infinity, in which case Update() should be invoked after user input was received. A value of 0 means "render + /// as fast as possible", for example if an animation is playing. + /// @return Time until the next update is expected. + double GetNextUpdateDelay() const; + +protected: + void Release() override; + +private: + String name; + Vector2i dimensions; + float density_independent_pixel_ratio = 1.f; + String documents_base_tag = "body"; + + // Wrapper around the render interface for tracking the render state. + RenderManager* render_manager; + + SmallUnorderedSet active_themes; + + ContextInstancer* instancer; + + using ElementSet = SmallOrderedSet; + using ElementList = Vector; + // Set of elements that are currently in hover state. + ElementSet hover_chain; + // List of elements that are currently in active state. + ElementList active_chain; + // History of windows that have had focus + ElementList document_focus_history; + + // Documents that have been unloaded from the context but not yet released. + OwnedElementList unloaded_documents; + + // Root of the element tree. + ElementPtr root; + // The element that currently has input focus. + Element* focus; + // The top-most element being hovered over. + Element* hover; + // The element that was being hovered over when the primary mouse button was pressed most recently. + Element* active; + + // The element that was clicked on last. + Element* last_click_element; + // The time the last click occurred. + double last_click_time; + // Mouse position during the last mouse_down event. + Vector2i last_click_mouse_position; + + // Input state; stored from the most recent input events we receive from the application. + Vector2i mouse_position; + bool mouse_active; + + // The current state of Touches, required to implement proper inertia scrolling. + struct TouchState { + bool scrolling_right = false; + bool scrolling_down = false; + Vector2f start_position; + Vector2f inertia_position; + Vector2f last_position; + Element* scroll_container = nullptr; + double scrolling_last_time = 0; + double scrolling_start_time_x = 0; + double scrolling_start_time_y = 0; + }; + SmallUnorderedMap touch_states; + + // Controller for various scroll behavior modes. + UniquePtr scroll_controller; // [not-null] + + // Enables cursor handling. + bool enable_cursor; + String cursor_name; + // Document attached to cursor (e.g. while dragging). + ElementPtr cursor_proxy; + + // The element that is currently being dragged (or about to be dragged). + Element* drag; + // True if a drag has begun (ie, the ondragstart event has been fired for the drag element), false otherwise. + bool drag_started; + // True if the current drag is a verbose drag (ie, sends ondragover, ondragout, ondragdrop, etc, events). + bool drag_verbose; + // Used when dragging a cloned object. + Element* drag_clone; + + // The element currently being dragged over. This is equivalent to hover, but only set while an element is being + // dragged, and excludes the dragged element. + Element* drag_hover; + // Set of elements that are currently being dragged over; this differs from the hover state as the dragged element + // itself can't be part of it. + ElementSet drag_hover_chain; + + UnorderedMap> data_models; + + UniquePtr default_data_type_register; + + TextInputHandler* text_input_handler; + + // Time in seconds until Update and Render should be called again. This allows applications to only redraw the ui if needed. + // See RequestNextUpdate() and NextUpdateRequested() for details. + double next_update_timeout = 0; + + // Internal callback for when an element is detached or removed from the hierarchy. + void OnElementDetach(Element* element); + // Internal callback for when a new element gains focus. + bool OnFocusChange(Element* element, bool focus_visible); + + // Generates an event for faking clicks on an element. + void GenerateClickEvent(Element* element); + + // Updates the current hover elements, sending required events. + void UpdateHoverChain(Vector2i old_mouse_position, int key_modifier_state = 0, Dictionary* out_parameters = nullptr, + Dictionary* out_drag_parameters = nullptr); + // Resets the current active element and its chain. + void ResetActiveChain(); + + // Creates the drag clone from the given element. The old drag clone will be released if necessary. + void CreateDragClone(Element* element); + // Releases the drag clone if one exists. + void ReleaseDragClone(); + + // Scroll the target by the given amount, using smooth scrolling. + void PerformSmoothscrollOnTarget(Element* target, Vector2f delta_offset, ScrollBehavior scroll_behavior); + + // Returns the data model with the provided name, or nullptr if it does not exist. + DataModel* GetDataModelPtr(const String& name) const; + + // Builds the parameters for a generic key event. + void GenerateKeyEventParameters(Dictionary& parameters, Input::KeyIdentifier key_identifier); + // Builds the parameters for a generic mouse event. + void GenerateMouseEventParameters(Dictionary& parameters, int button_index = -1); + // Builds the parameters for the key modifier state. + void GenerateKeyModifierEventParameters(Dictionary& parameters, int key_modifier_state); + // Builds the parameters for a drag event. + void GenerateDragEventParameters(Dictionary& parameters); + + // Releases all unloaded documents pending destruction. + void ReleaseUnloadedDocuments(); + + // Helper method to lookup TouchState by touch id. + TouchState* LookupTouch(TouchId identifier); + // Process a single touch movement for this context. + bool ProcessTouchMove(const Touch& touch, int key_modifier_state); + // Process a single touch press for this context. + bool ProcessTouchStart(const Touch& touch, int key_modifier_state); + // Process a single touch release for this context. + bool ProcessTouchEnd(const Touch& touch, int key_modifier_state); + // Cancel processing a touch for this context. + bool ProcessTouchCancel(const Touch& touch); + + // Sends the specified event to all elements in new_items that don't appear in old_items. + static void SendEvents(const ElementSet& old_items, const ElementSet& new_items, EventId id, const Dictionary& parameters); + + friend class Rml::Element; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/ContextInstancer.h b/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/ContextInstancer.h new file mode 100644 index 00000000..ba7cd177 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/ContextInstancer.h @@ -0,0 +1,34 @@ +#pragma once + +#include "Header.h" +#include "Traits.h" +#include "Types.h" + +namespace Rml { + +class TextInputHandler; +class RenderManager; +class Context; +class Event; + +/** + Abstract instancer interface for instancing contexts. + */ + +class RMLUICORE_API ContextInstancer : public Releasable { +public: + virtual ~ContextInstancer(); + + /// Instances a context. + /// @param[in] name Name of this context. + /// @param[in] render_manager The render manager used for this context. + /// @param[in] text_input_handler The text input handler used for this context. + /// @return The instanced context. + virtual ContextPtr InstanceContext(const String& name, RenderManager* render_manager, TextInputHandler* text_input_handler) = 0; + + /// Releases a context previously created by this context. + /// @param[in] context The context to release. + virtual void ReleaseContext(Context* context) = 0; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/ConvolutionFilter.h b/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/ConvolutionFilter.h new file mode 100644 index 00000000..f1c61f13 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/ConvolutionFilter.h @@ -0,0 +1,61 @@ +#pragma once + +#include "Header.h" +#include "Types.h" + +namespace Rml { + +enum class FilterOperation { + // The result is the sum of all the filtered pixels. + Sum, + // The result is the largest value of all filtered pixels. + Dilation, +}; + +/** + A programmable convolution filter, designed to aid in the generation of texture data by custom + FontEffect types. + */ + +class RMLUICORE_API ConvolutionFilter { +public: + ConvolutionFilter(); + ~ConvolutionFilter(); + + /// Initialises a square kernel filter with the given radius. + bool Initialise(int kernel_radius, FilterOperation operation); + + /// Initialises the filter. A filter must be initialised and populated with values before use. + /// @param[in] kernel_radii The size of the filter's kernel on each side of the origin along both axes. So, for example, a filter initialised with + /// radii (1,1) will store 9 values. + /// @param[in] operation The operation the filter conducts to determine the result. + bool Initialise(Vector2i kernel_radii, FilterOperation operation); + + /// Returns a reference to one of the rows of the filter kernel. + /// @param[in] kernel_y_index The index of the desired row. + /// @return Pointer to the first value in the kernel row. + float* operator[](int kernel_y_index); + + /// Runs the convolution filter. The filter will operate on each pixel in the destination + /// surface, setting its opacity to the result the filter on the source opacity values. The + /// colour values will remain unchanged. + /// @param[in] destination The RGBA-encoded destination buffer. + /// @param[in] destination_dimensions The size of the destination region (in pixels). + /// @param[in] destination_stride The stride (in bytes) of the destination region. + /// @param[in] destination_color_format Determines the representation of the bytes in the destination texture, only the alpha channel will be + /// written to. + /// @param[in] source The opacity information for the source buffer. + /// @param[in] source_dimensions The size of the source region (in pixels). The stride is assumed to be equivalent to the horizontal width. + /// @param[in] source_offset The offset of the source region from the destination region. This is usually the same as the kernel size. + /// @param[in] source_color_format Determines the representation of the bytes in the source texture, only the alpha channel will be used. + void Run(byte* destination, Vector2i destination_dimensions, int destination_stride, ColorFormat destination_color_format, const byte* source, + Vector2i source_dimensions, Vector2i source_offset, ColorFormat source_color_format) const; + +private: + Vector2i kernel_size; + UniquePtr kernel; + + FilterOperation operation = FilterOperation::Sum; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Core.h b/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Core.h new file mode 100644 index 00000000..e73f724d --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Core.h @@ -0,0 +1,173 @@ +#pragma once + +#include "Event.h" +#include "Header.h" +#include "StyleTypes.h" +#include "Types.h" + +namespace Rml { + +class Plugin; +class Context; +class FileInterface; +class FontEngineInterface; +class RenderInterface; +class SystemInterface; +class TextInputHandler; +enum class DefaultActionPhase; + +/** + RmlUi library core API. + */ + +/// Initialises RmlUi. +RMLUICORE_API bool Initialise(); +/// Shutdown RmlUi. +RMLUICORE_API void Shutdown(); + +/// Returns the version of this RmlUi library. +/// @return The version number. +RMLUICORE_API String GetVersion(); + +/// Sets the interface through which all system requests are made. This is not required to be called, but if it is, it +/// must be called before Initialise(). +/// @param[in] system_interface A non-owning pointer to the application-specified logging interface. +/// @lifetime The interface must be kept alive until after the call to Rml::Shutdown. +RMLUICORE_API void SetSystemInterface(SystemInterface* system_interface); +/// Returns RmlUi's system interface. +RMLUICORE_API SystemInterface* GetSystemInterface(); + +/// Sets the interface through which all rendering requests are made. This is not required to be called, but if it is, +/// it must be called before Initialise(). If no render interface is specified, then all contexts must specify a render +/// interface when created. +/// @param[in] render_interface A non-owning pointer to the render interface implementation. +/// @lifetime The interface must be kept alive until after the call to Rml::Shutdown. +RMLUICORE_API void SetRenderInterface(RenderInterface* render_interface); +/// Returns RmlUi's default's render interface. +RMLUICORE_API RenderInterface* GetRenderInterface(); + +/// Sets the interface through which all file I/O requests are made. This is not required to be called, but if it is, it +/// must be called before Initialise(). +/// @param[in] file_interface A non-owning pointer to the application-specified file interface. +/// @lifetime The interface must be kept alive until after the call to Rml::Shutdown. +RMLUICORE_API void SetFileInterface(FileInterface* file_interface); +/// Returns RmlUi's file interface. +RMLUICORE_API FileInterface* GetFileInterface(); + +/// Sets the interface through which all font requests are made. This is not required to be called, but if it is, +/// it must be called before Initialise(). +/// @param[in] font_interface A non-owning pointer to the application-specified font engine interface. +/// @lifetime The interface must be kept alive until after the call to Rml::Shutdown. +RMLUICORE_API void SetFontEngineInterface(FontEngineInterface* font_interface); +/// Returns RmlUi's font interface. +RMLUICORE_API FontEngineInterface* GetFontEngineInterface(); + +/// Sets the implementation for handling text input events. This is not required to be called. +/// @param[in] text_input_handler A non-owning pointer to the application-specified implementation of a text input handler. +/// @lifetime The instance must be kept alive until after the call to Rml::Shutdown. +/// @note Be aware that you might be overriding a custom backend implementation. +RMLUICORE_API void SetTextInputHandler(TextInputHandler* text_input_handler); +/// Returns RmlUi's default implementation of a text input handler. +RMLUICORE_API TextInputHandler* GetTextInputHandler(); + +/// Creates a new element context. +/// @param[in] name The new name of the context. This must be unique. +/// @param[in] dimensions The initial dimensions of the new context. +/// @param[in] render_interface The custom render interface to use, or nullptr to use the default. +/// @param[in] text_input_handler The custom text input handler to use, or nullptr to use the default. +/// @lifetime If specified, the render interface and the text input handler must be kept alive until after the call to +/// Rml::Shutdown. Alternatively, the render interface can be destroyed after all contexts it belongs to have been +/// destroyed, and a subsequent call has been made to Rml::ReleaseRenderManagers. +/// @return A non-owning pointer to the new context, or nullptr if the context could not be created. +RMLUICORE_API Context* CreateContext(const String& name, Vector2i dimensions, RenderInterface* render_interface = nullptr, + TextInputHandler* text_input_handler = nullptr); +/// Removes and destroys a context. +/// @param[in] name The name of the context to remove. +/// @return True if the name is a valid context, false otherwise. +RMLUICORE_API bool RemoveContext(const String& name); +/// Fetches a previously constructed context by name. +/// @param[in] name The name of the desired context. +/// @return The desired context, or nullptr if no context exists with the given name. +RMLUICORE_API Context* GetContext(const String& name); +/// Fetches a context by index. +/// @param[in] index The index of the desired context. If this is outside the valid range of contexts, it will be clamped. +/// @return The requested context, or nullptr if no contexts exist. +RMLUICORE_API Context* GetContext(int index); +/// Returns the number of active contexts. +/// @return The total number of active RmlUi contexts. +RMLUICORE_API int GetNumContexts(); + +/// Adds a new font face from file to the font engine. The face's family, style, and weight will be determined from the face itself. +/// @param[in] file_path The path to the file to load the face from. The path is passed directly to the file interface which is used to load the file. +/// The default file interface accepts both absolute paths and paths relative to the working directory. +/// @param[in] fallback_face True to use this font face for unknown characters in other font faces. +/// @param[in] weight The weight to load when the font face contains multiple weights, otherwise the weight to register the font as. By default, it +/// loads all found font weights. +/// @param[in] face_index The index of the font face within a font collection. +/// @return True if the face was loaded successfully, false otherwise. +RMLUICORE_API bool LoadFontFace(const String& file_path, bool fallback_face = false, Style::FontWeight weight = Style::FontWeight::Auto, + int face_index = 0); + +/// Adds a new font face from file to the font engine. The face's family, style, and weight are given by the parameters. +/// @param[in] data The font data. +/// @param[in] family The family to register the font as. +/// @param[in] style The style to register the font as. +/// @param[in] weight The weight to load when the font face contains multiple weights, otherwise the weight to register the font as. By default, it +/// loads all found font weights. +/// @param[in] fallback_face True to use this font face for unknown characters in other font faces. +/// @param[in] face_index The index of the font face within a font collection. +/// @return True if the face was loaded successfully, false otherwise. +/// @lifetime The pointed to 'data' must remain available until after the call to Rml::Shutdown. +RMLUICORE_API bool LoadFontFace(const String& file_path, const String& family, Style::FontStyle style, + Style::FontWeight weight = Style::FontWeight::Auto, bool fallback_face = false, int face_index = 0); + +/// Adds a new font face from memory to the font engine. The face's family, style, and weight are given by the parameters. +/// @param[in] data The font data. +/// @param[in] family The family to register the font as. +/// @param[in] style The style to register the font as. +/// @param[in] weight The weight to load when the font face contains multiple weights, otherwise the weight to register the font as. By default, it +/// loads all found font weights. +/// @param[in] fallback_face True to use this font face for unknown characters in other font faces. +/// @param[in] face_index The index of the font face within a font collection. +/// @return True if the face was loaded successfully, false otherwise. +/// @lifetime The pointed to 'data' must remain available until after the call to Rml::Shutdown. +RMLUICORE_API bool LoadFontFace(Span data, const String& family, Style::FontStyle style, + Style::FontWeight weight = Style::FontWeight::Auto, bool fallback_face = false, int face_index = 0); + +/// Registers a generic RmlUi plugin. +RMLUICORE_API void RegisterPlugin(Plugin* plugin); + +/// Unregisters a generic RmlUi plugin. +RMLUICORE_API void UnregisterPlugin(Plugin* plugin); + +/// Registers a new event type. If the type already exists, it will replace custom event types, but not internal types. +/// @param[in] type The new event type. +/// @param[in] interruptible Whether the event can be interrupted during dispatch. +/// @param[in] bubbles Whether the event executes the bubble phase. If false, only capture and target phase is executed. +/// @param[in] default_action_phase Defines during which phase(s) the 'Element::ProcessDefaultAction' method is called. +/// @return The EventId of the newly created type, or existing type if 'type' is an internal type. +RMLUICORE_API EventId RegisterEventType(const String& type, bool interruptible, bool bubbles, + DefaultActionPhase default_action_phase = DefaultActionPhase::None); + +/// Returns a list of source URLs to textures in all loaded documents. +RMLUICORE_API StringList GetTextureSourceList(); +/// Forces all texture handles loaded and generated by RmlUi to be released. +/// @param[in] render_interface Release all textures belonging to the given interface, or nullptr to release all textures in all interfaces. +RMLUICORE_API void ReleaseTextures(RenderInterface* render_interface = nullptr); +/// Releases a specified texture by name from memory, returning 'true' if successful and 'false' if not found. +/// @param[in] source The texture source to match. +/// @param[in] render_interface Release any matching texture belonging to the given interface, or nullptr to look in all interfaces. +/// @return True if any texture was released. +RMLUICORE_API bool ReleaseTexture(const String& source, RenderInterface* render_interface = nullptr); +/// Forces all compiled geometry handles generated by RmlUi to be released. +/// @param[in] render_interface Release all geometry belonging to the given interface, or nullptr to release all geometry in all interfaces. +RMLUICORE_API void ReleaseCompiledGeometry(RenderInterface* render_interface = nullptr); +/// Releases unused font textures and rendered glyphs to free up memory, and regenerates actively used fonts. +/// @note Invalidates all existing FontFaceHandles returned from the font engine. +RMLUICORE_API void ReleaseFontResources(); +/// Releases render managers that are not used by any contexts. +/// @note Any resources referring to the render manager in user space must be cleared first, including callback textures and compiled geometry. +/// @note Also releases font resources, which invalidates all existing FontFaceHandles returned from the font engine. +RMLUICORE_API void ReleaseRenderManagers(); + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/DataModelHandle.h b/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/DataModelHandle.h new file mode 100644 index 00000000..7bfd634b --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/DataModelHandle.h @@ -0,0 +1,197 @@ +#pragma once + +#include "DataStructHandle.h" +#include "DataTypeRegister.h" +#include "DataTypes.h" +#include "Header.h" +#include "Traits.h" +#include "Types.h" + +namespace Rml { + +class DataModel; + +class RMLUICORE_API DataModelHandle { +public: + DataModelHandle(DataModel* model = nullptr); + + bool IsVariableDirty(const String& variable_name); + void DirtyVariable(const String& variable_name); + void DirtyAllVariables(); + + explicit operator bool() { return model; } + +private: + DataModel* model; +}; + +class RMLUICORE_API DataModelConstructor { +public: + template + using DataEventMemberFunc = void (T::*)(DataModelHandle, Event&, const VariantList&); + + DataModelConstructor(); + explicit DataModelConstructor(DataModel* model); + + // Return a handle to the data model being constructed, which can later be used to synchronize variables and update the model. + DataModelHandle GetModelHandle() const; + + // Bind a data variable. + // @note For non-builtin types, make sure they first have been registered with the appropriate 'Register...()' functions. + template + bool Bind(const String& name, T* ptr) + { + RMLUI_ASSERTMSG(ptr, "Invalid pointer to data variable"); + return BindVariable(name, DataVariable(type_register->GetDefinition(), static_cast(ptr))); + } + + // Bind a get/set function pair. + bool BindFunc(const String& name, DataGetFunc get_func, DataSetFunc set_func = {}); + + // Bind an event callback. + bool BindEventCallback(const String& name, DataEventFunc event_func); + + // Convenience wrapper around BindEventCallback for member functions. + template + bool BindEventCallback(const String& name, DataEventMemberFunc member_func, T* object_pointer) + { + return BindEventCallback(name, [member_func, object_pointer](DataModelHandle handle, Event& event, const VariantList& arguments) { + (object_pointer->*member_func)(handle, event, arguments); + }); + } + + // Bind a user-declared DataVariable. + // For advanced use cases, such as binding variables to a custom 'VariableDefinition'. + bool BindCustomDataVariable(const String& name, DataVariable data_variable) { return BindVariable(name, data_variable); } + + // Register a scalar type with associated get and set functions. + // @note This registers a type which can later be used as a normal data variable, while 'BindFunc' registers a named data variable with a specific + // getter and setter. + // @note The type applies to every data model associated with the current Context. + template + bool RegisterScalar(DataTypeGetFunc get_func, DataTypeSetFunc set_func = {}); + + // Register a struct type. + // @note The type applies to every data model associated with the current Context. + // @return A handle which can be used to register struct members. + template + StructHandle RegisterStruct(); + + // Register a user-declared VariableDefinition to describe a custom type behavior. + template + bool RegisterCustomDataVariableDefinition(UniquePtr definition); + + // Register an array type. + // @note The type applies to every data model associated with the current Context. + // @note If 'Container::value_type' represents a non-scalar type, that type must already have been registered with the appropriate 'Register...()' + // functions. + // @note Container requires the following functions to be implemented: size() and begin(). This is satisfied by several containers including + // std::vector and std::array. + template + bool RegisterArray(); + + // Register a transform function. + // A transform function modifies a variant with optional arguments. It can be called in data expressions using the pipe '|' operator. + // @note The transform function applies to every data model associated with the current Context. + void RegisterTransformFunc(const String& name, DataTransformFunc transform_func) + { + type_register->GetTransformFuncRegister()->Register(name, std::move(transform_func)); + } + + // Returns the type register. + // The type register contains VariableDefinitions of all the data types registered to this data model's owning context. + DataTypeRegister* GetDataTypeRegister() const { return type_register; } + + explicit operator bool() { return model && type_register; } + +private: + bool BindVariable(const String& name, DataVariable data_variable); + friend class Detail::DataModelConstructorAccessor; + + DataModel* model; + DataTypeRegister* type_register; +}; + +template +inline bool DataModelConstructor::RegisterScalar(DataTypeGetFunc get_func, DataTypeSetFunc set_func) +{ + // We allow custom getters/setters for enums, even though they are considered builtin data scalars. + static_assert(!is_builtin_data_scalar_v || std::is_enum_v, + "Cannot register scalar data type function. Arithmetic types and String are handled internally and does not need to be registered."); + const FamilyId id = Family::Id(); + + auto scalar_func_definition = Rml::MakeUnique>(get_func, set_func); + + const bool inserted = type_register->RegisterDefinition(id, std::move(scalar_func_definition)); + if (!inserted) + { + RMLUI_LOG_TYPE_ERROR(T, "Scalar function type already registered."); + return false; + } + + return true; +} + +template +inline bool DataModelConstructor::RegisterCustomDataVariableDefinition(UniquePtr definition) +{ + const FamilyId id = Family::Id(); + + const bool inserted = type_register->RegisterDefinition(id, std::move(definition)); + if (!inserted) + { + RMLUI_LOG_TYPE_ERROR(T, "Custom data type already registered."); + return false; + } + + return true; +} + +template +inline StructHandle DataModelConstructor::RegisterStruct() +{ + static_assert(std::is_class_v, "Type must be a struct or class type."); + const FamilyId id = Family::Id(); + + auto struct_definition = Rml::MakeUnique(); + StructDefinition* struct_variable_raw = struct_definition.get(); + + const bool inserted = type_register->RegisterDefinition(id, std::move(struct_definition)); + if (!inserted) + { + RMLUI_LOG_TYPE_ERROR(T, "Struct type already declared"); + return StructHandle(nullptr, nullptr); + } + + return StructHandle(type_register, struct_variable_raw); +} + +template +inline bool DataModelConstructor::RegisterArray() +{ + using value_type = typename Container::value_type; + VariableDefinition* value_variable = type_register->GetDefinition(); + RMLUI_LOG_TYPE_ERROR_ASSERT(value_type, value_variable, "Underlying value type of array has not been registered."); + if (!value_variable) + return false; + + const FamilyId container_id = Family::Id(); + auto array_definition = Rml::MakeUnique>(value_variable); + + const bool inserted = type_register->RegisterDefinition(container_id, std::move(array_definition)); + if (!inserted) + { + RMLUI_LOG_TYPE_ERROR(Container, "Array type already declared."); + return false; + } + + return true; +} + +namespace Detail { + class DataModelConstructorAccessor { + public: + RMLUICORE_API static const UnorderedMap& GetAllVariables(const DataModelConstructor& data_model_constructor); + }; +} // namespace Detail +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/DataStructHandle.h b/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/DataStructHandle.h new file mode 100644 index 00000000..c2c95b4e --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/DataStructHandle.h @@ -0,0 +1,183 @@ +#pragma once + +#include "DataTypeRegister.h" +#include "DataTypes.h" +#include "DataVariable.h" +#include "Header.h" +#include "Traits.h" +#include "Types.h" + +namespace Rml { + +template +class StructHandle { +public: + StructHandle(DataTypeRegister* type_register, StructDefinition* struct_definition) : + type_register(type_register), struct_definition(struct_definition) + {} + + /// Register a member object. + /// @note Underlying type must be registered before it is used as a member. + /// @note Getter functions can return by reference, raw pointer, or by value. If returned by value, + /// the returned type must be a scalar data type, otherwise any data type can be used. + /// @example + /// struct Invader { + /// int health; + /// }; + /// struct_handle.RegisterMember("health", &Invader::health); + template + bool RegisterMember(const String& name, MemberType Object::*member_object_ptr) + { + return CreateMemberObjectDefinition(name, member_object_ptr); + } + + /// Register a member getter function. + /// @note Underlying type must be registered before it is used as a member. + /// @note Getter functions can return by reference, raw pointer, or by value. If returned by value, + /// the returned type must be a scalar data type, otherwise any data type can be used. + /// @example + /// struct Invader { + /// std::vector& GetWeapons(); + /// /* ... */ + /// }; + /// struct_handle.RegisterMember("weapons", &Invader::GetWeapons); + template + bool RegisterMember(const String& name, ReturnType (Object::*member_get_func_ptr)()) + { + return RegisterMemberGetter(name, member_get_func_ptr); + } + + /// Register member getter and setter functions. The getter and setter functions must return and assign scalar value types. + /// @note Underlying type must be registered before it is used as a member. + /// @note Getter and setter functions can return by reference, raw pointer, or by value. + /// @example + /// struct Invader { + /// MyColorType GetColor(); + /// void SetColor(MyColorType color); + /// /* ... */ + /// }; + /// struct_handle.RegisterMember("color", &Invader::GetColor, &Invader::SetColor); + template + bool RegisterMember(const String& name, ReturnType (Object::*member_get_func_ptr)(), void (Object::*member_set_func_ptr)(AssignType)) + { + using BasicReturnType = typename std::remove_reference::type; + using BasicAssignType = typename std::remove_const::type>::type; + using UnderlyingType = typename std::conditional, BasicAssignType, BasicReturnType>::type; + + static_assert(IsVoidMemberFunc || IsVoidMemberFunc || + std::is_same::value, + "Provided getter and setter functions must get and set the same type."); + + return CreateMemberScalarGetSetFuncDefinition(name, member_get_func_ptr, member_set_func_ptr); + } + + explicit operator bool() const { return type_register && struct_definition; } + +private: + // Member getter with reference return type. + template + bool RegisterMemberGetter(const String& name, ReturnType& (Object::*member_get_func_ptr)()) + { + return CreateMemberGetFuncDefinition(name, member_get_func_ptr); + } + + // Member getter with pointer return type. + template + bool RegisterMemberGetter(const String& name, ReturnType* (Object::*member_get_func_ptr)()) + { + return CreateMemberGetFuncDefinition(name, member_get_func_ptr); + } + + // Member getter with value return type, only valid for scalar return types. + template + bool RegisterMemberGetter(const String& name, ReturnType (Object::*member_get_func_ptr)()) + { + using SetType = VoidMemberFunc Object::*; + return CreateMemberScalarGetSetFuncDefinition(name, member_get_func_ptr, SetType{}); + } + + template + bool CreateMemberObjectDefinition(const String& name, MemberType Object::*member_ptr); + + template + bool CreateMemberGetFuncDefinition(const String& name, MemberType Object::*member_get_func_ptr); + + template + bool CreateMemberScalarGetSetFuncDefinition(const String& name, MemberGetType Object::*member_get_func_ptr, + MemberSetType Object::*member_set_func_ptr); + + DataTypeRegister* type_register; + StructDefinition* struct_definition; +}; + +template +template +bool StructHandle::CreateMemberObjectDefinition(const String& name, MemberType Object::*member_ptr) +{ + using MemberObjectPtr = MemberType Object::*; + + // If the member function signature doesn't match the getter function signature, it will end up calling this function. Emit a compile error in + // that case. + static_assert(!std::is_member_function_pointer::value, + "Illegal data member getter function signature. Make sure it takes no arguments and is not const qualified."); + static_assert(!std::is_const::value, "Data member objects cannot be const qualified."); + + VariableDefinition* underlying_definition = type_register->GetDefinition(); + if (!underlying_definition) + return false; + struct_definition->AddMember(name, Rml::MakeUnique>(underlying_definition, member_ptr)); + return true; +} + +template +template +bool StructHandle::CreateMemberGetFuncDefinition(const String& name, MemberType Object::*member_get_func_ptr) +{ + static_assert(!std::is_const::value, "Returned type from data member function cannot be const qualified."); + + VariableDefinition* underlying_definition = type_register->GetDefinition(); + if (!underlying_definition) + return false; + + struct_definition->AddMember(name, + Rml::MakeUnique>(underlying_definition, member_get_func_ptr)); + return true; +} + +template +template +bool StructHandle::CreateMemberScalarGetSetFuncDefinition(const String& name, MemberGetType Object::*member_get_func_ptr, + MemberSetType Object::*member_set_func_ptr) +{ + static_assert(std::is_default_constructible::value, + "Struct member getter/setter functions must return/assign a type that is default constructible."); + static_assert(!std::is_const::value, "Const qualified type illegal in data member getter functions."); + + if (!IsVoidMemberFunc) + { + RMLUI_ASSERTMSG(member_get_func_ptr, "Expected member getter function, but none provided."); + } + if (!IsVoidMemberFunc) + { + RMLUI_ASSERTMSG(member_get_func_ptr, "Expected member setter function, but none provided."); + } + + VariableDefinition* underlying_definition = type_register->GetDefinition(); + if (!underlying_definition) + return false; + + if (underlying_definition->Type() != DataVariableType::Scalar) + { + RMLUI_LOG_TYPE_ERROR(UnderlyingType, + "Only scalar data variables are allowed here. Data member functions require scalar types when returning by value, or using getter/setter " + "function pairs."); + return false; + } + + struct_definition->AddMember(name, + Rml::MakeUnique>(underlying_definition, + member_get_func_ptr, member_set_func_ptr)); + return true; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/DataTypeRegister.h b/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/DataTypeRegister.h new file mode 100644 index 00000000..6c939d7a --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/DataTypeRegister.h @@ -0,0 +1,116 @@ +#pragma once + +#include "DataTypes.h" +#include "DataVariable.h" +#include "Header.h" +#include "Traits.h" +#include "Types.h" +#include "Utilities.h" + +namespace Rml { + +template +constexpr bool is_builtin_data_scalar_v = std::is_arithmetic_v || std::is_enum_v || std::is_same_v, String>; + +class RMLUICORE_API TransformFuncRegister { +public: + void Register(const String& name, DataTransformFunc transform_func); + bool Call(const String& name, const VariantList& arguments, Variant& out_result) const; + +private: + UnorderedMap transform_functions; +}; + +class RMLUICORE_API DataTypeRegister final : NonCopyMoveable { +public: + DataTypeRegister(); + ~DataTypeRegister(); + + inline bool RegisterDefinition(FamilyId id, UniquePtr definition) + { + const bool inserted = type_register.emplace(id, std::move(definition)).second; + return inserted; + } + + template + VariableDefinition* GetDefinition() + { + if constexpr (!PointerTraits::is_pointer_v && is_builtin_data_scalar_v) + { + // Get definition for scalar types that can be assigned to and from Rml::Variant. + // We automatically register these when needed, so users don't have to register trivial types manually. + static_assert(!std::is_const_v, "Data binding variables cannot point to constant variables."); + FamilyId id = Family::Id(); + + auto result = type_register.emplace(id, nullptr); + bool inserted = result.second; + UniquePtr& definition = result.first->second; + + if (inserted) + definition = Rml::MakeUnique>(); + + return definition.get(); + } + else if constexpr (!PointerTraits::is_pointer_v && !is_builtin_data_scalar_v) + { + // Get definition for types that are not a built-in scalar. + // These must already have been registered by the user. + FamilyId id = Family::Id(); + auto it = type_register.find(id); + if (it == type_register.end()) + { + RMLUI_LOG_TYPE_ERROR(T, + "Desired data type T not registered with the type register, please use the 'Register...()' functions before binding values, " + "adding " + "members, or registering arrays of non-scalar types."); + return nullptr; + } + + return it->second.get(); + } + else if constexpr (PointerTraits::is_pointer_v) + { + // Get definition for pointer types, or create one as needed. + // This will create a wrapper definition that forwards the call to the definition of the underlying type. + static_assert(PointerTraits::is_pointer_v, "Invalid pointer type provided."); + + using UnderlyingType = typename PointerTraits::element_type; + static_assert(!PointerTraits::is_pointer_v, + "Recursive pointer types (pointer to pointer) to data variables are disallowed."); + static_assert(!std::is_const_v, "Pointer to a const data variable is not supported."); + + // Get the underlying definition. + VariableDefinition* underlying_definition = GetDefinition(); + if (!underlying_definition) + { + RMLUI_LOG_TYPE_ERROR(T, "Underlying type of pointer not registered."); + return nullptr; + } + + // Get or create the pointer wrapper definition. + FamilyId id = Family::Id(); + + auto result = type_register.emplace(id, nullptr); + bool inserted = result.second; + UniquePtr& definition = result.first->second; + + if (inserted) + definition = Rml::MakeUnique>(underlying_definition); + + return definition.get(); + } + else + { + static_assert(Utilities::DependentFalse); + return nullptr; + } + } + + inline TransformFuncRegister* GetTransformFuncRegister() { return &transform_register; } + +private: + UnorderedMap> type_register; + TransformFuncRegister transform_register; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/DataTypes.h b/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/DataTypes.h new file mode 100644 index 00000000..411432e6 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/DataTypes.h @@ -0,0 +1,82 @@ +#pragma once + +#include "Header.h" +#include "Types.h" +#include + +namespace Rml { + +class VariableDefinition; +class DataTypeRegister; +class TransformFuncRegister; +class DataModelHandle; +class DataVariable; + +using DataGetFunc = Function; +using DataSetFunc = Function; +using DataTransformFunc = Function; +using DataEventFunc = Function; + +template +using MemberGetFunc = void (T::*)(Variant&); +template +using MemberSetFunc = void (T::*)(const Variant&); + +template +using DataTypeGetFunc = void (*)(const T&, Variant&); +template +using DataTypeSetFunc = void (*)(T&, const Variant&); + +template +using MemberGetterFunc = ReturnType (Object::*)(); +template +using MemberSetterFunc = void (Object::*)(AssignType); + +using DirtyVariables = SmallUnorderedSet; + +struct DataAddressEntry { + DataAddressEntry(String name) : name(std::move(name)), index(-1) {} + DataAddressEntry(int index) : index(index) {} + String name; + int index; +}; +using DataAddress = Vector; + +template +struct PointerTraits { + static constexpr bool is_pointer_v = std::false_type::value; + using element_type = T; + static void* Dereference(void* ptr) { return ptr; } +}; +template +struct PointerTraits { + static constexpr bool is_pointer_v = std::true_type::value; + using element_type = T; + static void* Dereference(void* ptr) { return static_cast(*static_cast(ptr)); } +}; +template +struct PointerTraits> { + static constexpr bool is_pointer_v = std::true_type::value; + using element_type = T; + static void* Dereference(void* ptr) { return static_cast(static_cast*>(ptr)->get()); } +}; +template +struct PointerTraits> { + static constexpr bool is_pointer_v = std::true_type::value; + using element_type = T; + static void* Dereference(void* ptr) { return static_cast(static_cast*>(ptr)->get()); } +}; + +struct VoidMemberFunc {}; +template +constexpr bool IsVoidMemberFunc = std::is_same_v; + +#define RMLUI_LOG_TYPE_ERROR(T, msg) RMLUI_ERRORMSG((String(msg) + String("\nT: ") + String(rmlui_type_name())).c_str()) +#define RMLUI_LOG_TYPE_ERROR_ASSERT(T, val, msg) RMLUI_ASSERTMSG((val), (String(msg) + String("\nT: ") + String(rmlui_type_name())).c_str()) + +namespace Detail { + class DataVariableAccessor; + class DataModelConstructorAccessor; +} // namespace Detail + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/DataVariable.h b/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/DataVariable.h new file mode 100644 index 00000000..61cf244f --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/DataVariable.h @@ -0,0 +1,286 @@ +#pragma once + +#include "DataTypes.h" +#include "Header.h" +#include "Traits.h" +#include "Types.h" +#include "Variant.h" +#include + +namespace Rml { + +enum class DataVariableType { Scalar, Array, Struct }; + +/* + * A 'DataVariable' wraps a user handle (pointer) and a VariableDefinition. + * + * Together they can be used to get and set variables between the user side and data model side. + */ + +class RMLUICORE_API DataVariable { +public: + DataVariable() = default; + DataVariable(VariableDefinition* definition, void* ptr) : definition(definition), ptr(ptr) {} + + explicit operator bool() const { return definition; } + + bool Get(Variant& variant) const; + bool Set(const Variant& variant); + int Size() const; + DataVariable Child(const DataAddressEntry& address) const; + DataVariableType Type() const; + +private: + friend class Detail::DataVariableAccessor; + + VariableDefinition* definition = nullptr; + void* ptr = nullptr; +}; + +/* + * A 'VariableDefinition' specifies how a user handle (pointer) is translated to and from a value in the data model. + * + * Generally, Scalar types can set and get values, while Array and Struct types can retrieve children based on data addresses. + */ + +class RMLUICORE_API VariableDefinition : public NonCopyMoveable { +public: + virtual ~VariableDefinition() = default; + DataVariableType Type() const { return type; } + + virtual bool Get(void* ptr, Variant& variant); + virtual bool Set(void* ptr, const Variant& variant); + + virtual int Size(void* ptr); + virtual DataVariable Child(void* ptr, const DataAddressEntry& address); + + virtual StringList ReflectMemberNames(); + +protected: + VariableDefinition(DataVariableType type) : type(type) {} + +private: + DataVariableType type; +}; + +// Literal data variable constructor +RMLUICORE_API DataVariable MakeLiteralIntVariable(int value); + +template +class ScalarDefinition final : public VariableDefinition { +public: + ScalarDefinition() : VariableDefinition(DataVariableType::Scalar) {} + + bool Get(void* ptr, Variant& variant) override + { + variant = *static_cast(ptr); + return true; + } + bool Set(void* ptr, const Variant& variant) override { return variant.GetInto(*static_cast(ptr)); } +}; + +class RMLUICORE_API FuncDefinition final : public VariableDefinition { +public: + FuncDefinition(DataGetFunc get, DataSetFunc set); + + bool Get(void* ptr, Variant& variant) override; + bool Set(void* ptr, const Variant& variant) override; + +private: + DataGetFunc get; + DataSetFunc set; +}; + +template +class ScalarFuncDefinition final : public VariableDefinition { +public: + ScalarFuncDefinition(DataTypeGetFunc get, DataTypeSetFunc set) : VariableDefinition(DataVariableType::Scalar), get(get), set(set) {} + + bool Get(void* ptr, Variant& variant) override + { + if (!get) + return false; + get(*static_cast(ptr), variant); + return true; + } + bool Set(void* ptr, const Variant& variant) override + { + if (!set) + return false; + set(*static_cast(ptr), variant); + return true; + } + +private: + DataTypeGetFunc get; + DataTypeSetFunc set; +}; + +class RMLUICORE_API StructDefinition final : public VariableDefinition { +public: + StructDefinition(); + + DataVariable Child(void* ptr, const DataAddressEntry& address) override; + + StringList ReflectMemberNames() override; + + void AddMember(const String& name, UniquePtr member); + +private: + SmallOrderedMap> members; +}; + +template +class ArrayDefinition final : public VariableDefinition { +public: + ArrayDefinition(VariableDefinition* underlying_definition) : + VariableDefinition(DataVariableType::Array), underlying_definition(underlying_definition) + {} + + int Size(void* ptr) override { return int(static_cast(ptr)->size()); } + +protected: + DataVariable Child(void* void_ptr, const DataAddressEntry& address) override + { + Container* ptr = static_cast(void_ptr); + const int index = address.index; + + const int container_size = int(ptr->size()); + if (index < 0 || index >= container_size) + { + if (address.name == "size") + return MakeLiteralIntVariable(container_size); + + Log::Message(Log::LT_WARNING, "Data array index out of bounds."); + return DataVariable(); + } + + auto it = ptr->begin(); + std::advance(it, index); + + void* next_ptr = &(*it); + return DataVariable(underlying_definition, next_ptr); + } + +private: + VariableDefinition* underlying_definition; +}; + +class RMLUICORE_API BasePointerDefinition : public VariableDefinition { +public: + BasePointerDefinition(VariableDefinition* underlying_definition); + + bool Get(void* ptr, Variant& variant) override; + bool Set(void* ptr, const Variant& variant) override; + int Size(void* ptr) override; + DataVariable Child(void* ptr, const DataAddressEntry& address) override; + + StringList ReflectMemberNames() override; + +protected: + virtual void* DereferencePointer(void* ptr) = 0; + +private: + VariableDefinition* underlying_definition; +}; + +template +class PointerDefinition final : public BasePointerDefinition { +public: + PointerDefinition(VariableDefinition* underlying_definition) : BasePointerDefinition(underlying_definition) {} + +protected: + void* DereferencePointer(void* ptr) override { return PointerTraits::Dereference(ptr); } +}; + +template +class MemberObjectDefinition final : public BasePointerDefinition { +public: + MemberObjectDefinition(VariableDefinition* underlying_definition, MemberType Object::* member_ptr) : + BasePointerDefinition(underlying_definition), member_ptr(member_ptr) + {} + +protected: + void* DereferencePointer(void* base_ptr) override { return &(static_cast(base_ptr)->*member_ptr); } + +private: + MemberType Object::* member_ptr; +}; + +template +class MemberGetFuncDefinition final : public BasePointerDefinition { +public: + MemberGetFuncDefinition(VariableDefinition* underlying_definition, MemberType Object::* member_get_func_ptr) : + BasePointerDefinition(underlying_definition), member_get_func_ptr(member_get_func_ptr) + {} + +protected: + void* DereferencePointer(void* base_ptr) override + { + return static_cast(Extract((static_cast(base_ptr)->*member_get_func_ptr)())); + } + +private: + BasicReturnType* Extract(BasicReturnType* value) { return value; } + BasicReturnType* Extract(BasicReturnType& value) { return &value; } + + MemberType Object::* member_get_func_ptr; +}; + +template +class MemberScalarGetSetFuncDefinition final : public VariableDefinition { +public: + MemberScalarGetSetFuncDefinition(VariableDefinition* underlying_definition, MemberGetType Object::* member_get_func_ptr, + MemberSetType Object::* member_set_func_ptr) : + VariableDefinition(underlying_definition->Type()), underlying_definition(underlying_definition), member_get_func_ptr(member_get_func_ptr), + member_set_func_ptr(member_set_func_ptr) + {} + + bool Get(void* ptr, Variant& variant) override + { + if constexpr (IsVoidMemberFunc) + { + return false; + } + else + { + RMLUI_ASSERT(member_get_func_ptr); + auto&& value = (static_cast(ptr)->*member_get_func_ptr)(); + bool result = underlying_definition->Get(static_cast(&value), variant); + return result; + } + } + + bool Set(void* ptr, const Variant& variant) override + { + if constexpr (IsVoidMemberFunc) + { + return false; + } + else + { + RMLUI_ASSERT(member_set_func_ptr); + + UnderlyingType result; + if (!underlying_definition->Set(static_cast(&result), variant)) + return false; + + (static_cast(ptr)->*member_set_func_ptr)(result); + + return true; + } + } + +private: + VariableDefinition* underlying_definition; + MemberGetType Object::* member_get_func_ptr; + MemberSetType Object::* member_set_func_ptr; +}; + +namespace Detail { + class DataVariableAccessor { + public: + RMLUICORE_API_INLINE static VariableDefinition* GetDefinition(const DataVariable& variable) { return variable.definition; } + }; +} // namespace Detail +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Debug.h b/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Debug.h new file mode 100644 index 00000000..7228d3b6 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Debug.h @@ -0,0 +1,98 @@ +#pragma once + +#include "Header.h" + +// Define for breakpointing. +#if defined(RMLUI_PLATFORM_WIN32) + #if defined(__MINGW32__) + #define RMLUI_BREAK \ + { \ + asm("int $0x03"); \ + } + #elif defined(_MSC_VER) + #define RMLUI_BREAK \ + { \ + __debugbreak(); \ + } + #else + #define RMLUI_BREAK + #endif +#elif defined(RMLUI_PLATFORM_LINUX) + #if defined __GNUC__ + #define RMLUI_BREAK \ + { \ + __builtin_trap(); \ + } + #else + #define RMLUI_BREAK + #endif +#elif defined(RMLUI_PLATFORM_MACOSX) + #define RMLUI_BREAK \ + { \ + __builtin_trap(); \ + } +#else + #define RMLUI_BREAK +#endif + +namespace Rml { + +bool RMLUICORE_API Assert(const char* message, const char* file, int line); + +} + +// Define the RmlUi assertion macros. +#if !defined RMLUI_DEBUG + + #define RMLUI_ASSERT(x) + #define RMLUI_ASSERTMSG(x, m) + #define RMLUI_ERROR + #define RMLUI_ERRORMSG(m) + #define RMLUI_VERIFY(x) x + #define RMLUI_ASSERT_NONRECURSIVE + +#else + + #define RMLUI_ASSERT(x) \ + if (!(x)) \ + { \ + if (!(::Rml::Assert("RMLUI_ASSERT(" #x ")", __FILE__, __LINE__))) \ + { \ + RMLUI_BREAK; \ + } \ + } + #define RMLUI_ASSERTMSG(x, m) \ + if (!(x)) \ + { \ + if (!(::Rml::Assert(m, __FILE__, __LINE__))) \ + { \ + RMLUI_BREAK; \ + } \ + } + #define RMLUI_ERROR \ + if (!(::Rml::Assert("RMLUI_ERROR", __FILE__, __LINE__))) \ + { \ + RMLUI_BREAK; \ + } + #define RMLUI_ERRORMSG(m) \ + if (!(::Rml::Assert(m, __FILE__, __LINE__))) \ + { \ + RMLUI_BREAK; \ + } + #define RMLUI_VERIFY(x) RMLUI_ASSERT(x) + +struct RmlUiAssertNonrecursive { + bool& entered; + RmlUiAssertNonrecursive(bool& entered) : entered(entered) + { + RMLUI_ASSERTMSG(!entered, "A method defined as non-recursive was entered twice!"); + entered = true; + } + ~RmlUiAssertNonrecursive() { entered = false; } +}; + + #define RMLUI_ASSERT_NONRECURSIVE \ + static bool rmlui_nonrecursive_entered = false; \ + RmlUiAssertNonrecursive rmlui_nonrecursive(rmlui_nonrecursive_entered) + +#endif // RMLUI_DEBUG diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/DecorationTypes.h b/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/DecorationTypes.h new file mode 100644 index 00000000..3b0e0e56 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/DecorationTypes.h @@ -0,0 +1,38 @@ +#pragma once + +#include "NumericValue.h" +#include "Types.h" + +namespace Rml { + +struct ColorStop { + ColourbPremultiplied color; + NumericValue position; +}; +inline bool operator==(const ColorStop& a, const ColorStop& b) +{ + return a.color == b.color && a.position == b.position; +} +inline bool operator!=(const ColorStop& a, const ColorStop& b) +{ + return !(a == b); +} + +struct BoxShadow { + ColourbPremultiplied color; + NumericValue offset_x, offset_y; + NumericValue blur_radius; + NumericValue spread_distance; + bool inset = false; +}; +inline bool operator==(const BoxShadow& a, const BoxShadow& b) +{ + return a.color == b.color && a.offset_x == b.offset_x && a.offset_y == b.offset_y && a.blur_radius == b.blur_radius && + a.spread_distance == b.spread_distance && a.inset == b.inset; +} +inline bool operator!=(const BoxShadow& a, const BoxShadow& b) +{ + return !(a == b); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Decorator.h b/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Decorator.h new file mode 100644 index 00000000..fe3431d2 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Decorator.h @@ -0,0 +1,104 @@ +#pragma once + +#include "EffectSpecification.h" +#include "Header.h" +#include "PropertyDictionary.h" +#include "PropertySpecification.h" +#include "Texture.h" +#include "Types.h" + +namespace Rml { + +class Element; +class PropertyDictionary; +struct Sprite; +class Texture; +class StyleSheet; +class RenderManager; +class DecoratorInstancerInterface; + +/** + The abstract base class for any visual object that can be attached to any element. + */ +class RMLUICORE_API Decorator { +public: + Decorator(); + virtual ~Decorator(); + + /// Called on a decorator to generate any required per-element data for a newly decorated element. + /// @param[in] element The newly decorated element. + /// @param[in] paint_area Determines the element's area to be painted by the decorator. + /// @return A handle to a decorator-defined data handle, or zero if no data is needed for the element. + virtual DecoratorDataHandle GenerateElementData(Element* element, BoxArea paint_area) const = 0; + /// Called to release element data generated by this decorator. + /// @param[in] element_data The element data handle to release. + virtual void ReleaseElementData(DecoratorDataHandle element_data) const = 0; + + /// Called to render the decorator on an element. + /// @param[in] element The element to render the decorator on. + /// @param[in] element_data The handle to the data generated by the decorator for the element. + virtual void RenderElement(Element* element, DecoratorDataHandle element_data) const = 0; + + /// Value specifying an invalid or non-existent Decorator data handle. + /// @note This value will prevent the decorator from being rendered on the given element. + static const DecoratorDataHandle INVALID_DECORATORDATAHANDLE = 0; + +protected: + /// Adds a texture if it is valid into the list of textures in use by the decorator. + /// @param[in] texture The texture to add. + /// @return The index of the texture if it is successful, or -1 if it is invalid. + int AddTexture(Texture texture); + /// Get number of textures in use by the decorator. + int GetNumTextures() const; + /// Returns one of the decorator's previously loaded textures. + /// @param[in] index The index of the desired texture. + /// @return The texture at the appropriate index, or default-inititalized if the index was invalid. + Texture GetTexture(int index = 0) const; + +private: + // Stores a list of textures in use by this decorator. + // Optimized for the common case of a single texture. + Texture first_texture; + Vector additional_textures; +}; + +/** + A decorator instancer, which can be inherited from to instance new decorators when encountered in the style sheet. + */ +class RMLUICORE_API DecoratorInstancer : public EffectSpecification { +public: + DecoratorInstancer(); + virtual ~DecoratorInstancer(); + + /// Instances a decorator given the property tag and attributes from the RCSS file. + /// @param[in] name The type of decorator desired. For example, "decorator: simple(...);" is declared as type "simple". + /// @param[in] properties All RCSS properties associated with the decorator. + /// @param[in] instancer_interface An interface for querying the active style sheet. + /// @return A shared_ptr to the decorator if it was instanced successfully. + virtual SharedPtr InstanceDecorator(const String& name, const PropertyDictionary& properties, + const DecoratorInstancerInterface& instancer_interface) = 0; +}; + +class RMLUICORE_API DecoratorInstancerInterface { +public: + DecoratorInstancerInterface(RenderManager& render_manager, const StyleSheet& style_sheet, const PropertySource* property_source) : + render_manager(render_manager), style_sheet(style_sheet), property_source(property_source) + {} + + /// Get a sprite from any @spritesheet in the style sheet the decorator is being instanced on. + const Sprite* GetSprite(const String& name) const; + + /// Get a texture using the given filename. + /// This will use the document path where the 'decorator' property was declared to locate relative files, if available. + Texture GetTexture(const String& filename) const; + + /// Get the render manager for the decorator being instanced. + RenderManager& GetRenderManager() const; + +private: + RenderManager& render_manager; + const StyleSheet& style_sheet; + const PropertySource* property_source; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Dictionary.h b/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Dictionary.h new file mode 100644 index 00000000..3d3e79fb --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Dictionary.h @@ -0,0 +1,32 @@ +#pragma once + +#include "Header.h" +#include "Variant.h" + +namespace Rml { + +inline Variant* GetIf(Dictionary& dictionary, const String& key) +{ + auto it = dictionary.find(key); + if (it != dictionary.end()) + return &(it->second); + return nullptr; +} +inline const Variant* GetIf(const Dictionary& dictionary, const String& key) +{ + auto it = dictionary.find(key); + if (it != dictionary.end()) + return &(it->second); + return nullptr; +} +template +inline T Get(const Dictionary& dictionary, const String& key, const T& default_value) +{ + T result = default_value; + auto it = dictionary.find(key); + if (it != dictionary.end()) + it->second.GetInto(result); + return result; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/EffectSpecification.h b/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/EffectSpecification.h new file mode 100644 index 00000000..accca51e --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/EffectSpecification.h @@ -0,0 +1,43 @@ +#pragma once + +#include "Header.h" +#include "PropertySpecification.h" +#include "Types.h" + +namespace Rml { + +class PropertyDefinition; + +/** + Specifies properties and shorthands for effects (decorators and filters). + */ +class RMLUICORE_API EffectSpecification { +public: + EffectSpecification(); + + /// Returns the property specification associated with the instancer. + const PropertySpecification& GetPropertySpecification() const; + +protected: + ~EffectSpecification(); + + /// Registers a property for the effect. + /// @param[in] property_name The name of the new property (how it is specified through RCSS). + /// @param[in] default_value The default value to be used. + /// @return The new property definition, ready to have parsers attached. + PropertyDefinition& RegisterProperty(const String& property_name, const String& default_value); + + /// Registers a shorthand property definition. Specify a shorthand name of 'decorator' or 'filter' to parse + /// anonymous decorators or filters, respectively. + /// @param[in] shorthand_name The name to register the new shorthand property under. + /// @param[in] property_names A comma-separated list of the properties this definition is a shorthand for. The order + /// in which they are specified here is the order in which the values will be processed. + /// @param[in] type The type of shorthand to declare. + /// @return An ID for the new shorthand, or 'Invalid' if the shorthand declaration is invalid. + ShorthandId RegisterShorthand(const String& shorthand_name, const String& property_names, ShorthandType type); + +private: + PropertySpecification properties; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Element.h b/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Element.h new file mode 100644 index 00000000..424fb8aa --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Element.h @@ -0,0 +1,809 @@ +#pragma once + +// Guard against Windows macros that conflict with RmlUi method names. +// PCH-based undef in stdafx.h doesn't survive GCC PCH serialization, so we +// undef here and restore at end-of-file. +#pragma push_macro("GetNextSibling") +#undef GetNextSibling +#pragma push_macro("GetFirstChild") +#undef GetFirstChild + +#include "Box.h" +#include "Core.h" +#include "Event.h" +#include "Header.h" +#include "ObserverPtr.h" +#include "Property.h" +#include "RenderBox.h" +#include "ScriptInterface.h" +#include "ScrollTypes.h" +#include "StyleTypes.h" +#include "Transform.h" +#include "Tween.h" +#include "Types.h" + +namespace Rml { + +class Context; +class DataModel; +class Decorator; +class ElementInstancer; +class EventDispatcher; +class EventListener; +class ElementBackgroundBorder; +class ElementDefinition; +class ElementDocument; +class ElementScroll; +class ElementStyle; +class LayoutEngine; +class ContainerBox; +class InlineLevelBox; +class ReplacedBox; +class PropertiesIteratorView; +class PropertyDictionary; +class RenderManager; +class StyleSheet; +class StyleSheetContainer; +class TransformState; +struct ElementMeta; +struct StackingContextChild; + +/** + A generic element in the DOM tree. + */ + +class RMLUICORE_API Element : public ScriptInterface, public EnableObserverPtr { +public: + RMLUI_RTTI_DefineWithParent(Element, ScriptInterface) + + /// Constructs a new RmlUi element. This should not be called directly; use the Factory instead. + /// @param[in] tag The tag the element was declared as in RML. + Element(const String& tag); + virtual ~Element(); + + /// Clones this element, returning a new, unparented element. + ElementPtr Clone() const; + + /** @name Classes + */ + //@{ + /// Sets or removes a class on the element. + /// @param[in] class_name The name of the class to add or remove from the class list. + /// @param[in] activate True if the class is to be added, false to be removed. + void SetClass(const String& class_name, bool activate); + /// Checks if a class is set on the element. + /// @param[in] class_name The name of the class to check for. + /// @return True if the class is set on the element, false otherwise. + bool IsClassSet(const String& class_name) const; + /// Specifies the entire list of classes for this element. This will replace any others specified. + /// @param[in] class_names The list of class names to set on the style, separated by spaces. + void SetClassNames(const String& class_names); + /// Return the active class list. + /// @return The space-separated list of classes active on the element. + String GetClassNames() const; + //@} + + /// Returns the active style sheet for this element. This may be nullptr. + /// @return The element's style sheet. + virtual const StyleSheet* GetStyleSheet() const; + + /// Fills a string with the full address of this element. + /// @param[in] include_pseudo_classes True to include the pseudo-classes of the leaf element. + /// @param[in] include_parents True to include the address of all parent elements. + /// @return The address of the element. + String GetAddress(bool include_pseudo_classes = false, bool include_parents = true) const; + + /// Sets the position of this element, as a two-dimensional offset from another element. + /// @param[in] offset The offset (in pixels) of our primary box's top-left border corner from our offset parent's top-left border corner. + /// @param[in] offset_parent The element this element is being positioned relative to. + /// @param[in] offset_fixed True if the element is fixed in place (and will not scroll), false if not. + void SetOffset(Vector2f offset, Element* offset_parent, bool offset_fixed = false); + /// Returns the position of the top-left corner of one of the areas of this element's primary box, relative to its + /// offset parent's top-left border corner. + /// @param[in] area The desired area position. + /// @return The relative offset. + Vector2f GetRelativeOffset(BoxArea area = BoxArea::Content); + /// Returns the position of the top-left corner of one of the areas of this element's primary box, relative to + /// the element root. + /// @param[in] area The desired area position. + /// @return The absolute offset. + Vector2f GetAbsoluteOffset(BoxArea area = BoxArea::Content); + + /// Sets an alternate area to use as the clip area. + /// @param[in] clip_area The box area to use as the element's clip area. + void SetClipArea(BoxArea clip_area); + /// Returns the area the element uses as its clip area. + /// @return The box area used as the element's clip area. + BoxArea GetClipArea() const; + + /// Sets the dimensions of the element's scrollable overflow rectangle. This is the tightest fitting box surrounding + /// all of this element's logical children, and the element's padding box. + /// @param[in] scrollable_overflow_rectangle The dimensions of the box's scrollable content. + /// @param[in] clamp_scroll_offset Clamp scroll offset to the new rectangle, should only be set when the final overflow size has been determined. + void SetScrollableOverflowRectangle(Vector2f scrollable_overflow_rectangle, bool clamp_scroll_offset); + /// Sets the box describing the size of the element, and removes all others. + /// @param[in] box The new dimensions box for the element. + void SetBox(const Box& box); + /// Adds a box to the end of the list describing this element's geometry. + /// @param[in] box The auxiliary box for the element. + /// @param[in] offset The offset of the box relative to the top left border corner of the element. + void AddBox(const Box& box, Vector2f offset); + /// Returns the main box describing the size of the element. + /// @return The box. + const Box& GetBox(); + /// Returns one of the boxes describing the size of the element. + /// @param[in] index The index of the desired box, with 0 being the main box. If outside of bounds, the main box will be returned. + /// @param[out] offset The offset of the box relative to the element's border box. + /// @return The requested box. + const Box& GetBox(int index, Vector2f& offset); + /// Returns one of the render boxes describing how to generate the geometry for the corresponding element's box. + /// @param[in] fill_area The box area that acts as the background, or fill, of the render box. + /// @param[in] index The index of the desired box, with 0 being the main box. If outside of bounds, the main render box will be returned. + /// @return The requested render box. + RenderBox GetRenderBox(BoxArea fill_area = BoxArea::Padding, int index = 0); + /// Returns the number of boxes making up this element's geometry. + /// @return the number of boxes making up this element's geometry. + int GetNumBoxes(); + + /// Returns the baseline of the element, in pixel offset from the element's bottom margin edge (positive up). + /// @return The element's baseline. The default element will return 0. + virtual float GetBaseline() const; + /// Gets the intrinsic dimensions of this element, if it is a replaced element with an inherent size. This size will + /// only be overridden by a styled width or height. + /// @param[out] dimensions The dimensions to size, if appropriate. + /// @param[out] ratio The intrinsic ratio (width/height), if appropriate. + /// @return True if the element is a replaced element with intrinsic dimensions, false otherwise. + virtual bool GetIntrinsicDimensions(Vector2f& dimensions, float& ratio); + /// Returns true if the element is replaced, thereby handling its own rendering. + /// @return True if the element is a replaced element. + bool IsReplaced(); + + /// Checks if a given point in screen coordinates lies within the bordered area of this element. + /// @param[in] point The point to test. + /// @return True if the element is within this element, false otherwise. + virtual bool IsPointWithinElement(Vector2f point); + + /// Returns the visibility of the element. + /// @param[in] include_ancestors Check parent elements for visibility + /// @return True if the element is visible, false otherwise. + bool IsVisible(bool include_ancestors = false) const; + /// Returns the z-index of the element. + /// @return The element's z-index. + float GetZIndex() const; + + /// Returns the element's font face handle. + /// @return The element's font face handle. + FontFaceHandle GetFontFaceHandle() const; + + /** @name Properties + */ + //@{ + /// Sets a local property override on the element. + /// @param[in] name The name of the new property. + /// @param[in] value The new property to set. + /// @return True if the property parsed successfully, false otherwise. + bool SetProperty(const String& name, const String& value); + /// Sets a local property override on the element to a pre-parsed value. + /// @param[in] id The id of the new property. + /// @param[in] property The parsed property to set. + /// @return True if the property was set successfully, false otherwise. + bool SetProperty(PropertyId id, const Property& property); + /// Removes a local property override on the element; its value will revert to that defined in the style sheet. + /// @param[in] name The name of the local property definition to remove. + void RemoveProperty(const String& name); + void RemoveProperty(PropertyId id); + /// Returns one of this element's properties. If the property is not defined for this element and not inherited + /// from an ancestor, the default value will be returned. + /// @param[in] name The name of the property to fetch the value for. + /// @return The value of this property for this element, or nullptr if no property exists with the given name. + const Property* GetProperty(const String& name); + const Property* GetProperty(PropertyId id); + /// Returns the values of one of this element's properties. + /// @param[in] name The name of the property to get. + /// @return The value of this property. + template + T GetProperty(const String& name); + /// Returns one of this element's properties. If this element is not defined this property, nullptr will be + /// returned. + /// @param[in] name The name of the property to fetch the value for. + /// @return The value of this property for this element, or nullptr if this property has not been explicitly defined for this element. + const Property* GetLocalProperty(const String& name); + const Property* GetLocalProperty(PropertyId id); + /// Returns the local style properties. Does not include properties applied from any locally set classes. + /// @return The local properties for this element. + const PropertyMap& GetLocalStyleProperties(); + + /// Resolves a length to its canonical unit ('px'). + /// @param[in] value The numeric value. + /// @return The resolved value in their canonical unit, or zero if it could not be resolved. + /// @note Font-relative and context-relative units will be resolved against this element's computed values and its context. + float ResolveLength(NumericValue value); + /// Resolves a numeric value with units of number, percentage, length, or angle to their canonical unit (unit-less, 'px', or 'rad'). + /// Numbers and percentages are scaled by the base value and returned. + /// @param[in] value The value to be resolved. + /// @param[in] base_value The value that is scaled by the number or percentage value, if applicable. + /// @return The resolved value in their canonical unit, or zero if it could not be resolved. + float ResolveNumericValue(NumericValue value, float base_value); + + /// Returns the size of the containing block. Often percentages are scaled relative to this. + Vector2f GetContainingBlock(); + /// Returns 'position' property value from the element's computed values. + Style::Position GetPosition(); + /// Returns 'float' property value from the element's computed values. + Style::Float GetFloat(); + /// Returns 'display' property value from the element's computed values. + Style::Display GetDisplay(); + /// Returns 'line-height' property value from the element's computed values. + float GetLineHeight(); + + /// Project a 2D point in pixel coordinates onto the element's plane. + /// @param[in-out] point The point to project in, and the resulting projected point out. + /// @return True on success, false if transformation matrix is singular. + bool Project(Vector2f& point) const noexcept; + + /// Start an animation of the given property on this element. + /// If an animation of the same property name exists, it will be replaced. + /// If start_value is null, the current property value on this element is used. + /// @return True if a new animation was added. + bool Animate(const String& property_name, const Property& target_value, float duration, Tween tween = Tween{}, int num_iterations = 1, + bool alternate_direction = true, float delay = 0.0f, const Property* start_value = nullptr); + bool Animate(PropertyId id, const Property& target_value, float duration, Tween tween = Tween{}, int num_iterations = 1, + bool alternate_direction = true, float delay = 0.0f, const Property* start_value = nullptr); + + /// Add a key to an animation, extending its duration. + /// If no animation exists for the given property name, the call will be ignored. + /// @return True if a new animation key was added. + bool AddAnimationKey(const String& property_name, const Property& target_value, float duration, Tween tween = Tween{}); + bool AddAnimationKey(PropertyId id, const Property& target_value, float duration, Tween tween = Tween{}); + + /// Iterator for the local (non-inherited) properties defined on this element. + /// @warning Modifying the element's properties or classes invalidates the iterator. + /// @return Iterator to the first property defined on this element. + PropertiesIteratorView IterateLocalProperties() const; + ///@} + + /** @name Pseudo-classes + */ + //@{ + /// Sets or removes a pseudo-class on the element. + /// @param[in] pseudo_class The pseudo class to activate or deactivate. + /// @param[in] activate True if the pseudo-class is to be activated, false to be deactivated. + void SetPseudoClass(const String& pseudo_class, bool activate); + /// Checks if a specific pseudo-class has been set on the element. + /// @param[in] pseudo_class The name of the pseudo-class to check for. + /// @return True if the pseudo-class is set on the element, false if not. + bool IsPseudoClassSet(const String& pseudo_class) const; + /// Checks if a complete set of pseudo-classes are set on the element. + /// @param[in] pseudo_classes The list of pseudo-classes to check for. + /// @return True if all the pseudo-classes are set, false if not. + bool ArePseudoClassesSet(const StringList& pseudo_classes) const; + /// Gets a list of the current active pseudo-classes. + /// @return The list of active pseudo-classes. + StringList GetActivePseudoClasses() const; + //@} + + /** @name Attributes + */ + //@{ + /// Sets an attribute on the element. + /// @param[in] name Name of the attribute. + /// @param[in] value Value of the attribute. + template + void SetAttribute(const String& name, const T& value); + /// Gets the specified attribute. + /// @param[in] name Name of the attribute to retrieve. + /// @return A variant representing the attribute, or nullptr if the attribute doesn't exist. + Variant* GetAttribute(const String& name); + /// Gets the specified attribute. + const Variant* GetAttribute(const String& name) const; + /// Gets the specified attribute, or the default value. + /// @param[in] name Name of the attribute to retrieve. + /// @param[in] default_value Value to return if the attribute doesn't exist. + template + T GetAttribute(const String& name, const T& default_value) const; + /// Checks if the element has a certain attribute. + /// @param[in] name The name of the attribute to check for. + /// @return True if the element has the given attribute, false if not. + bool HasAttribute(const String& name) const; + /// Removes the attribute from the element. + /// @param[in] name Name of the attribute. + void RemoveAttribute(const String& name); + /// Set a group of attributes. + /// @param[in] attributes Attributes to set. + void SetAttributes(const ElementAttributes& attributes); + /// Get the attributes of the element. + /// @return The attributes + const ElementAttributes& GetAttributes() const { return attributes; } + /// Returns the number of attributes on the element. + /// @return The number of attributes on the element. + int GetNumAttributes() const; + //@} + + /// Gets the outermost focus element down the tree from this node. + /// @return Outermost focus element. + Element* GetFocusLeafNode(); + + /// Returns the element's context. + /// @return The context this element's document exists within. + Context* GetContext() const; + /// Returns the element's render manager. + /// @return The render manager responsible for this element. + RenderManager* GetRenderManager() const; + + /** @name DOM Properties + */ + //@{ + + /// Gets the name of the element. + /// @return The name of the element. + const String& GetTagName() const; + + /// Gets the id of the element. + /// @return The element's id. + const String& GetId() const; + /// Sets the id of the element. + /// @param[in] id The new id of the element. + void SetId(const String& id); + + /// Gets the horizontal offset from the context's left edge to the element's left border edge. + /// @return The horizontal offset of the element within its context, in pixels. + float GetAbsoluteLeft(); + /// Gets the vertical offset from the context's top edge to the element's top border edge. + /// @return The vertical offset of the element within its context, in pixels. + float GetAbsoluteTop(); + + /// Gets the horizontal offset from the element's left border edge to the left edge of its client area. This is + /// effectively equivalent to the left border width. + /// @return The horizontal offset of the element's client area, in pixels. + float GetClientLeft(); + /// Gets the vertical offset from the element's top border edge to the top edge of its client area. This is + /// effectively equivalent to the top border width. + /// @return The vertical offset of the element's client area, in pixels. + float GetClientTop(); + /// Gets the width of the element's client area. This is the padding area subtracted by the vertical scrollbar width if present. + /// @return The element's client width, in pixels. + float GetClientWidth(); + /// Gets the height of the element's client area. This is the padding area subtracted by the horizontal scrollbar height if present. + /// @return The element's client height, in pixels. + float GetClientHeight(); + + /// Returns the element from which all offset calculations are currently computed. + /// @return This element's offset parent. + Element* GetOffsetParent(); + /// Gets the distance from this element's left border to its offset parent's left border. + /// @return The horizontal distance (in pixels) from this element's offset parent to itself. + float GetOffsetLeft(); + /// Gets the distance from this element's top border to its offset parent's top border. + /// @return The vertical distance (in pixels) from this element's offset parent to itself. + float GetOffsetTop(); + /// Gets the width of the element, including the content area, padding, borders and scrollbars, but not margins. + /// @return The width of the rendered element, in pixels. + float GetOffsetWidth(); + /// Gets the height of the element, including the content area, padding, borders and scrollbars, but not margins. + /// @return The height of the rendered element, in pixels. + float GetOffsetHeight(); + + /// Gets the left scroll offset of the element. + /// @return The element's left scroll offset. + float GetScrollLeft(); + /// Sets the left scroll offset of the element. + /// @param[in] scroll_left The element's new left scroll offset. + void SetScrollLeft(float scroll_left); + /// Gets the top scroll offset of the element. + /// @return The element's top scroll offset. + float GetScrollTop(); + /// Sets the top scroll offset of the element. + /// @param[in] scroll_top The element's new top scroll offset. + void SetScrollTop(float scroll_top); + /// Gets the width of the scrollable content of the element; it includes the element padding but not its margin. + /// @return The width (in pixels) of the scrollable content of the element. + float GetScrollWidth(); + /// Gets the height of the scrollable content of the element; it includes the element padding but not its margin. + /// @return The height (in pixels) of the scrollable content of the element. + float GetScrollHeight(); + + /// Gets the object representing the declarations of an element's style attributes. + /// @return The element's style. + ElementStyle* GetStyle() const; + + /// Gets the document this element belongs to. + /// @return This element's document. + ElementDocument* GetOwnerDocument() const; + + /// Gets this element's parent node. + /// @return This element's parent. + Element* GetParentNode() const; + /// Recursively search for the first ancestor of this node matching the given selector. + /// @param[in] selectors The selector or comma-separated selectors to match against. + /// @return The ancestor if found, or nullptr if no ancestor could be matched. + /// @performance Prefer GetElementById/TagName/ClassName whenever possible. + Element* Closest(const String& selectors) const; + + /// Gets the element immediately following this one in the tree. + /// @return This element's next sibling element, or nullptr if there is no sibling element. + Element* GetNextSibling() const; + /// Gets the element immediately preceding this one in the tree. + /// @return This element's previous sibling element, or nullptr if there is no sibling element. + Element* GetPreviousSibling() const; + + /// Returns the first child of this element. + /// @return This element's first child, or nullptr if it contains no children. + Element* GetFirstChild() const; + /// Gets the last child of this element. + /// @return This element's last child, or nullptr if it contains no children. + Element* GetLastChild() const; + /// Get the child element at the given index. + /// @param[in] index Index of child to get. + /// @return The child element at the given index. + Element* GetChild(int index) const; + /// Get the current number of children in this element + /// @param[in] include_non_dom_elements True if the caller wants to include the non-DOM children. Only set this to true if you know what you're + /// doing! + /// @return The number of children. + int GetNumChildren(bool include_non_dom_elements = false) const; + + /// Gets the markup and content of the element. + /// @param[out] content The content of the element. + virtual void GetInnerRML(String& content) const; + /// Gets the markup and content of the element. + /// @return The content of the element. + String GetInnerRML() const; + /// Sets the markup and content of the element. All existing children will be replaced. + /// @param[in] rml The new content of the element. + virtual void SetInnerRML(const String& rml); + + //@} + + /** @name DOM Methods + */ + //@{ + + /// Gives focus to the current element. + /// @param[in] focus_visible True to indicate that the focus should be visually indicated by setting the ':focus-visible' pseudo class. + /// @return True if the change focus request was successful + bool Focus(bool focus_visible = false); + /// Removes focus from this element. + void Blur(); + /// Fakes a mouse click on this element. + void Click(); + + /// Adds an event listener to this element. + /// @param[in] event Event to attach to. + /// @param[in] listener The listener object to be attached. + /// @param[in] in_capture_phase True to attach in the capture phase, false in the bubble phase. + /// @lifetime The added listener must stay alive until after the dispatched call from EventListener::OnDetach(). This occurs + /// e.g. when the element is destroyed or when RemoveEventListener() is called with the same parameters passed here. + void AddEventListener(const String& event, EventListener* listener, bool in_capture_phase = false); + /// Adds an event listener to this element by id. + /// @lifetime The added listener must stay alive until after the dispatched call from EventListener::OnDetach(). This occurs + /// e.g. when the element is destroyed or when RemoveEventListener() is called with the same parameters passed here. + void AddEventListener(EventId id, EventListener* listener, bool in_capture_phase = false); + /// Removes an event listener from this element. + /// @param[in] event Event to detach from. + /// @param[in] listener The listener object to be detached. + /// @param[in] in_capture_phase True to detach from the capture phase, false from the bubble phase. + void RemoveEventListener(const String& event, EventListener* listener, bool in_capture_phase = false); + /// Removes an event listener from this element by id. + void RemoveEventListener(EventId id, EventListener* listener, bool in_capture_phase = false); + /// Sends an event to this element. + /// @param[in] type Event type in string form. + /// @param[in] parameters The event parameters. + /// @return True if the event was not consumed (ie, was prevented from propagating by an element), false if it was. + bool DispatchEvent(const String& type, const Dictionary& parameters); + /// Sends an event to this element, overriding the default behavior for the given event type. + bool DispatchEvent(const String& type, const Dictionary& parameters, bool interruptible, bool bubbles = true); + /// Sends an event to this element by event id. + bool DispatchEvent(EventId id, const Dictionary& parameters); + + /// Scrolls the parent element's contents so that this element is visible. + /// @param[in] options Scroll parameters that control the desired element alignment relative to the parent. + void ScrollIntoView(ScrollIntoViewOptions options); + /// Scrolls the parent element's contents so that this element is visible. + /// @param[in] align_with_top If true, the element will align itself to the top of the parent element's window. If false, the element will be + /// aligned to the bottom of the parent element's window. + void ScrollIntoView(bool align_with_top = true); + /// Sets the scroll offset of this element to the given coordinates. + /// @param[in] offset The scroll destination coordinates. + /// @param[in] behavior Smooth scrolling behavior. + /// @note Smooth scrolling can only be applied to a single element at a time, any active smooth scrolls will be canceled. + void ScrollTo(Vector2f offset, ScrollBehavior behavior = ScrollBehavior::Instant); + + /// Append a child to this element. + /// @param[in] element The element to append as a child. + /// @param[in] dom_element True if the element is to be part of the DOM, false otherwise. Only set this to false if you know what you're doing! + Element* AppendChild(ElementPtr element, bool dom_element = true); + /// Adds a child to this element directly before the adjacent element. The new element inherits the DOM/non-DOM + /// status from the adjacent element. + /// @param[in] element Element to be inserted. + /// @param[in] adjacent_element The reference element which the new element will be inserted before. + Element* InsertBefore(ElementPtr element, Element* adjacent_element); + /// Replaces the second node with the first node. + /// @param[in] inserted_element The element that will be inserted and replace the other element. + /// @param[in] replaced_element The existing element that will be replaced. If this doesn't exist, inserted_element will be appended. + /// @return A unique pointer to the replaced element if found, discard the result to immediately destroy. + ElementPtr ReplaceChild(ElementPtr inserted_element, Element* replaced_element); + /// Remove a child element from this element. + /// @param[in] element The element to remove. + /// @returns A unique pointer to the element if found, discard the result to immediately destroy. + ElementPtr RemoveChild(Element* element); + /// Returns whether or not this element has any DOM children. + /// @return True if the element has at least one DOM child, false otherwise. + bool HasChildNodes() const; + + /// Get a child element by its ID. + /// @param[in] id The ID of the child element. + /// @return The child of this element with the given ID, or nullptr if no such child exists. + Element* GetElementById(const String& id); + /// Get all descendant elements with the given tag. + /// @param[out] elements Resulting elements. + /// @param[in] tag Tag to search for. + void GetElementsByTagName(ElementList& elements, const String& tag); + /// Get all descendant elements with the given class set on them. + /// @param[out] elements Resulting elements. + /// @param[in] class_name Class name to search for. + void GetElementsByClassName(ElementList& elements, const String& class_name); + /// Returns the first descendent element matching the RCSS selector query. + /// @param[in] selector The selector or comma-separated selectors to match against. + /// @return The first matching element during a depth-first traversal. + /// @performance Prefer GetElementById/TagName/ClassName whenever possible. + Element* QuerySelector(const String& selector); + /// Returns all descendent elements matching the RCSS selector query. + /// @param[out] elements The list of matching elements. + /// @param[in] selector The selector or comma-separated selectors to match against. + /// @performance Prefer GetElementById/TagName/ClassName whenever possible. + void QuerySelectorAll(ElementList& elements, const String& selector); + /// Checks if the element matches the given RCSS selector query. + /// @param[in] selector The selector or comma-separated selectors to match against. + /// @return True if the element matches the given RCSS selector query, false otherwise. + bool Matches(const String& selector); + /// Checks if the provided element is a descendant of the current element. + /// @param[in] element The element to test with. + /// @return True if the provided element is a descendant of this element, false otherwise. + bool Contains(Element* element) const; + + //@} + + /** + @name Internal Functions + */ + //@{ + /// Access the event dispatcher for this element. + EventDispatcher* GetEventDispatcher() const; + /// Returns event types with the number of listeners for debugging. + String GetEventDispatcherSummary() const; + /// Access the element background and border. + ElementBackgroundBorder* GetElementBackgroundBorder() const; + /// Returns the element's scrollbar functionality. + ElementScroll* GetElementScroll() const; + /// Returns the element's nearest scroll container that can be scrolled, if any. + Element* GetClosestScrollableContainer(); + /// Returns the element's transform state. + const TransformState* GetTransformState() const noexcept; + /// Returns the data model of this element. + DataModel* GetDataModel() const; + //@} + + /// Sets the instancer to use for releasing this element. + /// @param[in] instancer Instancer to set on this element. + void SetInstancer(ElementInstancer* instancer); + + /// Called when an emitted event propagates to this element, for event types with default actions. + /// Note: See 'EventSpecification' for the events that call this function and during which phase. + /// @param[in] event The event to process. + virtual void ProcessDefaultAction(Event& event); + + /// Return the computed values of the element's properties. These values are updated as appropriate on every Context::Update. + const ComputedValues& GetComputedValues() const; + +protected: + void Update(float dp_ratio, Vector2f vp_dimensions); + void Render(); + + /// Updates definition, computed values, and runs OnPropertyChange on this element. + void UpdateProperties(float dp_ratio, Vector2f vp_dimensions); + + /// Forces the element to generate a local stacking context, regardless of the value of its z-index property. + void ForceLocalStackingContext(); + + /// Called during the update loop after children are updated. + virtual void OnUpdate(); + /// Called during render after backgrounds, borders, decorators, but before children, are rendered. + virtual void OnRender(); + /// Called during update if the element size has been changed. + virtual void OnResize(); + /// Called during a layout operation, when the element is being positioned and sized. + virtual void OnLayout(); + /// Called when the 'dp'-ratio has been changed. + virtual void OnDpRatioChange(); + /// Called when the current document's compiled style sheet has been changed. This may result in changed sprites. + virtual void OnStyleSheetChange(); + + /// Called when attributes on the element are changed. + /// @param[in] changed_attributes Dictionary of attributes changed on the element. Attribute value will be empty if it was unset. + virtual void OnAttributeChange(const ElementAttributes& changed_attributes); + /// Called when properties on the element are changed. + /// @param[in] changed_properties The properties changed on the element. + virtual void OnPropertyChange(const PropertyIdSet& changed_properties); + /// Called when a pseudo class on the element is changed. + /// @param[in] pseudo_class The pseudo class changed on the element. + /// @param[in] activate True if the pseudo class was activated. + virtual void OnPseudoClassChange(const String& pseudo_class, bool activate); + + /// Called when a child node has been added up to two levels below us in the hierarchy. + /// @param[in] child The element that has been added. This may be this element. + virtual void OnChildAdd(Element* child); + /// Called when a child node has been removed up to two levels below us in the hierarchy. + /// @param[in] child The element that has been removed. This may be this element. + virtual void OnChildRemove(Element* child); + + /// Forces a re-layout of this element, and any other elements required. + virtual void DirtyLayout(); + /// Returns true if the element has been marked as needing a re-layout. + virtual bool IsLayoutDirty(); + + /// Returns the RML of this element and all children. + /// @param[out] content The content of this element and those under it, in XML form. + virtual void GetRML(String& content); + + /// Sets or removes an overriding pseudo-class on the element. + /// @param[in] target_element The element to set or remove the pseudo class on. + /// @param[in] pseudo_class The pseudo class to activate or deactivate. + /// @param[in] activate True if the pseudo-class is to be activated, false to be deactivated. + static void OverridePseudoClass(Element* target_element, const String& pseudo_class, bool activate); + + enum class DirtyNodes { Self, SelfAndSiblings }; + // Dirty the element style definition, including all descendants of the specified nodes. + void DirtyDefinition(DirtyNodes dirty_nodes); + + void SetOwnerDocument(ElementDocument* document); + + void OnStyleSheetChangeRecursive(); + + void Release() override; + +private: + void SetParent(Element* parent); + + void SetDataModel(DataModel* new_data_model); + + void DirtyAbsoluteOffset(); + void DirtyAbsoluteOffsetRecursive(); + void UpdateAbsoluteOffsetAndRenderBoxData(); + void UpdateOffset(); + void SetBaseline(float baseline); + + void BuildLocalStackingContext(); + void AddChildrenToStackingContext(Vector& stacking_children); + void AddToStackingContext(Vector& stacking_children, bool is_flex_item, bool is_non_dom_element); + void DirtyStackingContext(); + Element* ClosestStackingContextContainer(); + + void UpdateDefinition(); + + void DirtyTransformState(bool perspective_dirty, bool transform_dirty); + void UpdateTransformState(); + + void OnDpRatioChangeRecursive(); + void DirtyFontFaceRecursive(); + + void ClampScrollOffset(); + void ClampScrollOffsetRecursive(); + + /// Start an animation, replacing any existing animations of the same property name. If start_value is null, the element's current value is used. + ElementAnimationList::iterator StartAnimation(PropertyId property_id, const Property* start_value, int num_iterations, bool alternate_direction, + float delay, bool initiated_by_animation_property); + + /// Add a key to an animation, extending its duration. If target_value is null, the element's current value is used. + bool AddAnimationKeyTime(PropertyId property_id, const Property* target_value, float time, Tween tween); + + /// Start a transition of the given property on this element. + /// If an animation exists for the property, the call will be ignored. If a transition exists for this property, it will be replaced. + /// @return True if the transition was added or replaced. + bool StartTransition(const Transition& transition, const Property& start_value, const Property& target_value); + + /// Removes all transitions that are no longer part of the element's 'transition' property. + void HandleTransitionProperty(); + + /// Starts new animations and removes animations no longer part of the element's 'animation' property. + void HandleAnimationProperty(); + + /// Advances the animations (including transitions) forward in time. + void AdvanceAnimations(); + + // State flags are packed together for compact data layout. + bool local_stacking_context; + bool local_stacking_context_forced; + bool stacking_context_dirty; + bool computed_values_are_default_initialized; + + bool visible; // True if the element is visible and active. + + bool offset_fixed; + bool absolute_offset_dirty; + bool rounded_main_padding_size_dirty : 1; + + bool dirty_definition : 1; // Implies dirty child definitions as well. + bool dirty_child_definitions : 1; + + bool dirty_animation : 1; + bool dirty_transition : 1; + bool dirty_transform : 1; + bool dirty_perspective : 1; + + OwnedElementList children; + int num_non_dom_children; + + // Defines which box area to use for clipping; this is usually padding, but may be content. + BoxArea clip_area; + + // Original tag this element came from. + String tag; + + // The optional, unique ID of this object. + String id; + + // Instancer that created us, used for destruction. + ElementInstancer* instancer; + + // Parent element. + Element* parent; + // Currently focused child object + Element* focus; + // The owning document + ElementDocument* owner_document; + + // Active data model for this element. + DataModel* data_model; + // Attributes on this element. + ElementAttributes attributes; + + // The offset of the element, and the element it is offset from. + Element* offset_parent; + Vector2f relative_offset_base; // the base offset from the parent + Vector2f relative_offset_position; // the offset of a relatively positioned element + + Vector2f absolute_offset; + Vector2f rounded_main_padding_size; + + // The offset this element adds to its logical children due to scrolling content. + Vector2f scroll_offset; + + // The size of the element. + struct PositionedBox { + Box box; + Vector2f offset; + }; + using PositionedBoxList = Vector; + Box main_box; + PositionedBoxList additional_boxes; + + // And of the element's scrollable content. + Vector2f scrollable_overflow_rectangle; + + float baseline; + float z_index; + + ElementList stacking_context; + + UniquePtr transform_state; + + ElementAnimationList animations; + + ElementMeta* meta; + + friend class Rml::Context; + friend class Rml::ElementStyle; + friend class Rml::ContainerBox; + friend class Rml::InlineLevelBox; + friend class Rml::ReplacedBox; + friend class Rml::LayoutEngine; + friend class Rml::ElementScroll; + friend RMLUICORE_API void Rml::ReleaseFontResources(); +}; + +} // namespace Rml + +#include "Element.inl" + +#pragma pop_macro("GetFirstChild") +#pragma pop_macro("GetNextSibling") diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Element.inl b/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Element.inl new file mode 100644 index 00000000..17ebf457 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/Element.inl @@ -0,0 +1,31 @@ +namespace Rml { + +template +T Element::GetProperty(const String& name) +{ + const Property* property = GetProperty(name); + if (!property) + { + Log::Message(Log::LT_WARNING, "Invalid property name %s.", name.c_str()); + return T{}; + } + return property->Get(); +} + +template +void Element::SetAttribute(const String& name, const T& value) +{ + Variant variant(value); + attributes[name] = variant; + ElementAttributes changed_attributes; + changed_attributes.emplace(name, std::move(variant)); + OnAttributeChange(changed_attributes); +} + +template +T Element::GetAttribute(const String& name, const T& default_value) const +{ + return Get(attributes, name, default_value); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/ElementDocument.h b/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/ElementDocument.h new file mode 100644 index 00000000..d89ccc29 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Include/RmlUi/Core/ElementDocument.h @@ -0,0 +1,179 @@ +#pragma once + +#include "Element.h" + +namespace Rml { + +class Context; +class Stream; +class DocumentHeader; +class ElementText; +class StyleSheet; +class StyleSheetContainer; +enum class NavigationSearchDirection; + +/** ModalFlag controls the modal state of the document. */ +enum class ModalFlag { + None, // Remove modal state. + Modal, // Set modal state, other documents cannot receive focus. + Keep, // Modal state unchanged. +}; +/** FocusFlag controls the focus when showing the document. */ +enum class FocusFlag { + None, // No focus. + Document, // Focus the document. + Keep, // Focus the element in the document which last had focus. + Auto, // Focus the first tab element with the 'autofocus' attribute or else the document. +}; +/** ScrollFlag controls whether an element is scrolled into view when showing the document. */ +enum class ScrollFlag { + None, // Never scroll. + Auto, // Scroll the focused element into view, if applicable. +}; + +/** + Represents a document in the dom tree. + */ + +class RMLUICORE_API ElementDocument : public Element { +public: + RMLUI_RTTI_DefineWithParent(ElementDocument, Element) + + ElementDocument(const String& tag); + virtual ~ElementDocument(); + + /// Process given document header + void ProcessHeader(const DocumentHeader* header); + + /// Returns the document's context. + Context* GetContext(); + + /// Sets the document's title. + void SetTitle(const String& title); + /// Returns the title of this document. + const String& GetTitle() const; + + /// Returns the source address of this document. + const String& GetSourceURL() const; + + /// Returns the document's compiled style sheet. + /// @note The style sheet may be regenerated when media query parameters change, invalidating the pointer. + const StyleSheet* GetStyleSheet() const override; + /// Reload the document's style sheet from source files. + /// Styles will be reloaded from + + + This is a sample. + + diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/assets/hello_world.png b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/assets/hello_world.png new file mode 100644 index 0000000000000000000000000000000000000000..bec0f8235b0305e8a56a088ea183436c3bd52e5d GIT binary patch literal 10463 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 literal 0 HcmV?d00001 diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/assets/high_scores_alien_1.tga b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/assets/high_scores_alien_1.tga new file mode 100644 index 0000000000000000000000000000000000000000..722557738ec6db0728f9b7932098800726a8d939 GIT binary patch literal 16402 zcmeI2KZ_JW5XE=b-~=%c6pciT42_JGo5;iT0`VJ&iGqTGC>%zD9A*llk(wwd7#Q3I z`_abtTc(!2W_PN4d*`^j?Se-#k6JRfQ2h45QQi64H9frr9f!CNoTK^L1;|8Rj5 z+;Z0&ne+;|4Cgz!p2e;*mER}nJ`)}btOb(-rN7|aB zb)?h%sPdigRq)mebkM~nCw}9UyWXgoUSYm6X6d&BC)`###>2|L`t6JGgStd}bg{wK z{LIP(d-4lcIK#bEF3Z8TuqUMQ4>|vo-7jo2?Xkf&^p)EFp6uWZ_fjd1e--{mY!tx! zKB527-4nKjb;Msf{^#7qC$;~M*kET!h`)6F$Ip9v9zPL>Z^pw)k5620b}HKlvlwH2 z?>_KnIf5fxZNHJbABdm#_<1C}6g~^qPAT73zCx7xo7s09;Rvt$0Kf-iP@#71h__lV!vgH33a@i~KdcgjGJrZMrcjXwIw>}Ww2g+xO|ds~5Mso+msLj?k9buBs&)m2C|w1;RYuGycq zyzk^hjyJL6c(a?6#?n(9+nJd+_Dme_&Sp1fRawrKv!{2eJG+_(%md~D^MHB4JYXI$ z510qc1LlDY58Tk$Tf%MOzVJYJBs>A04Qt#v>Ei=s?%lr7fNxN-1cIU)D17`B_+R5`{6sZ->6) zk&O;?;e#-Lg!ZvchUSa5cs`aq&=$Yg^+C80`tqsQN5WkjWIEaCKo>a!KLG03gl&L& zX}-XBVLY$Y2HN76xw+!5>sb6NAE2u(f2`CFu&*><#NxRvezC1a&-u(<|FwMSiscXN zE6o?gIO_;sUWR_w&vUQ`}T%)p8erKJ?wAX573VfupJI?$t~=w+l3SwuLw!@b7g>B`{B8bi{agGG$$!!J z-_-J5XphT(CGWrJ{wLHowR^|EWd9T0|4xl>Xpi`p?tjDYpL(x>Z$o|I{d_*(_z3$` z_lw11<0CcR&9`r3>Gx0g{hRfD>vN}`TYT=+Q^n(dp*psfe*cE&AG}9V={8?lx6WepEK|I(w4Qj`x;{>tr_WqzzvFOByG*_Io8 z$)2D(-_FVhx%^V&Wk`&eIcpjbT#s+NJcj32(>Yc#vIE^#Ri2dx#ehQpT_@J#m z2a7z9f6;}`D%WGnzRM8)*!@X(Ct$k|+UR4<`Wo;bvi`q_+Z%zs3E!kXXrqrYDgLa1 zL$Ckn_YoWdq~6b2`wPh?R!)SW=lgoJsPJ3kv9J2h0QJ0rP-) I;QD#sKUNipPyhe` literal 0 HcmV?d00001 diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/assets/high_scores_alien_3.tga b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/assets/high_scores_alien_3.tga new file mode 100644 index 0000000000000000000000000000000000000000..9f832c3b74288da9c6c2bbb919df653f022166be GIT binary patch literal 16402 zcmeI2zm5|z5XSST2m(C<}GW6ykJ&t&6dGMP=HG?QlX3crUUT#0U?g$Tsr@~8N89q+19 zdG=EKZGIkUf;YmkaK163XT*SgKrT=ZJV6It#+;eyHhM;Ev4u@+r`8t@$r78N`0-Kr zC2Zr5ziVMBP!~Nx2i>)>Yd&mY6WgivMMJV=q0!xZXDu$c>$=O|wfg$xPfW3i?bQ0B zAz5Pc6JJ_v*{>z`e``;+Q|n6>c@5qtKfC_@(3tna3*m7X|3Fvv(4zp_JY#Nj&}9r` zyK`;ZU#aoclv%P*eny{v%hG3oJ^D(37qzVsUJK+epxp@cp@S}C7|R@SzHR*JTMJ8J zpZX%TEJ@}6MbGn`xG-Z}h|DFl&gztjqAN{Q1#X4I5?R}VUr@pnPdwjqbe8RWVa$5$D1oHNi zu+RQ%V`TZ-d(GGC(suf>jSu*OPxw|^{&)V@+5hdD+V!;eTD(@5w%0G6q4)ol_0RfS z`d<8p{`}`Xr4jhN`fGhk^R@HiL-3{172iq=?`piS@R>ap{`i7V_*PnYXQker3S;4q zFZhISr6q~~bUNLD*=%+JyMOKdd_LcFVf*d9$g=;3p|du$b9$s7p(_@|CsXKi$_*H2z+C%a~)g0 zd;GKNL5KfX^WEd0RSyFHG3UF-KdT-D{x>P#y?EGq;Qjt_)AHTpZ|lKI1NgprB5-a2 zX!DFde)~%Kzl_vJEw&zbzkd$>e}>Y*v-N=U1I{V<4#`;>pv^P-$d^OD&&zz38i5*t Y8i5*t8i5*t8i5*t8i5*te=!1o0Dv&GtN;K2 literal 0 HcmV?d00001 diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/assets/high_scores_defender.tga b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/assets/high_scores_defender.tga new file mode 100644 index 0000000000000000000000000000000000000000..e10f9463b76a37c9387878a77659913703eebc79 GIT binary patch literal 32786 zcmeI3y>1gh6onTkjD#qVK%qpbs1Ow`TR~6DJETcV5z!*V6YvUofKcq4Ih=1ZBkhiz zm|cfRGxuEU%3j;Ep7Y(EpS?5gy2o9%i>~k9Jk3XbHUSeb0TVC*6EFc2FaZ-V0TVC* z6EFc2FaZ-V0TVC*6EJ~$An-`%cp~4(pUYp!U&&A8uN!r=p%JZUMmr2(5zWy3Ob(N` z@(=P}{z?A1QAb-ZM=P4q4g*+3Gp>E~zn6cJpUJ<=f69L~>S#kFTG5Pl7{DT$(f`o~ z;P69!B_Dz~TG5Pl7{DT$F$U5P(iX0+7ut5ay~b-bG$-vafJHQ89@NX}4~JUgxVGMC zPTG4t!y=k7?lB)?OpF?Tw8H=v(TsaM=K0L^qQ)QXFn~ogbNp|veaGMJ-|c_xM5@wq z>g<{Sx&Qb0@BZKYzx)46`|-8acK<)#^Z5SH_kX_s_x=B{wjZsvocsSV=jr@!-2AWC ze{R;kdi4bn*ME*$|E;y(&-GT!qp$y#`#)xTKQgCQp3`_g)fj#M$3{%1`@g>F_%d^9 zCGOg#GCs1dq!F#r_kWf9Kgk80Cq6@?@#p$RBU+>H|19@^pC&G}jsG71+%6yLIzy8j zZNwg}jDPfjQTKmK`pnAtz`gDVE#rJB{;Jt@tUmU&_j}Glz4w5&Fr5#uFO@2LB;>x%YnZC9-?6!B;5 z@8uEm^SeardgrM11xbybpRX(0yR~J$HT{3->>j?H|LME4&vhI*(w84TpY|R?Y{=54=q7 s{Cw-tw>}dv0TVC*6EFc2FaZ-V0TVC*6EFc2FaZ-V0TVC*69`1$FASy}dH?_b literal 0 HcmV?d00001 diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/assets/invader.rcss b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/assets/invader.rcss new file mode 100644 index 00000000..4ab9a71b --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/assets/invader.rcss @@ -0,0 +1,769 @@ +@spritesheet theme +{ + src: invader.tga; + + /* For high dpi screens, designates the scaling it is intended to be shown at. */ + resolution: 1x; + + /** + The following specifies a list of sprite names and associated rectangles into the image given above. + Any sprite given here can be specified in a decorator. Their names must be globally unique. + Rectangles are specified as: x y width height. With the origin assumed to be at the top left corner. + */ + title-bar-l: 147px 0px 82px 85px; + title-bar-c: 229px 0px 1px 85px; + title-bar-r: 231px 0px 15px 85px; + + /* huditems are vertically flipped titlebars */ + huditem-l: 147px 55px 82px -55px; + huditem-c: 229px 55px 1px -55px; + huditem-r: 231px 55px 15px -55px; + + icon-help: 128px 152px 51px 39px; + icon-invader: 179px 152px 51px 39px; + icon-game: 230px 152px 51px 39px; + icon-hiscore: 281px 152px 51px 39px; + icon-waves: 332px 152px 51px 39px; + icon-flag: 336px 191px 51px 39px; + icon-lives: 383px 152px 51px 39px; + icon-score: 434px 152px 51px 39px; + + window-tl: 0px 0px 133px 140px; + window-t: 134px 0px 1px 140px; + window-tr: 136px 0px 10px 140px; + window-l: 0px 139px 10px 1px; + window-c: 11px 139px 1px 1px; + window-r: 10px 139px -10px 1px; /* mirrored left */ + window-bl: 0px 140px 11px 11px; + window-b: 11px 140px 1px 11px; + window-br: 136px 140px 10px 11px; + + button: 247px 0px 159px 45px; + button-hover: 247px 45px 159px 45px; + button-active: 247px 90px 159px 45px; + + button-inner: 259px 19px 135px 1px; + button-inner-hover: 259px 64px 135px 1px; + button-inner-active: 259px 109px 135px 1px; + + text-l: 162px 192px 14px 31px; + text-c: 176px 192px 1px 31px; + text-focus-l: 162px 230px 14px 31px; + text-focus-c: 176px 230px 1px 31px; + textarea: 162px 193px 145px 31px; + textarea-inner: 173px 206px 127px 10px; + textarea-focus: 162px 231px 145px 31px; + textarea-focus-inner: 173px 244px 127px 10px; + + selectbox-tl: 281px 275px 11px 9px; + selectbox-t: 292px 275px 1px 9px; + selectbox-tr: 294px 275px 11px 9px; + selectbox-l: 281px 283px 11px 1px; + selectbox-c: 292px 283px 1px 1px; + selectbox-bl: 281px 285px 11px 11px; + selectbox-b: 292px 285px 1px 11px; + selectbox-br: 294px 285px 11px 11px; + + selectvalue: 162px 192px 145px 37px; + selectvalue-hover: 162px 230px 145px 37px; + selectarrow: 307px 192px 30px 37px; + selectarrow-hover: 307px 230px 30px 37px; + selectarrow-active: 307px 268px 30px 37px; + + radio: 407px 0px 30px 30px; + radio-hover: 437px 0px 30px 30px; + radio-active: 467px 0px 30px 30px; + radio-checked: 407px 30px 30px 30px; + radio-checked-hover: 437px 30px 30px 30px; + radio-checked-active: 467px 30px 30px 30px; + + checkbox: 407px 60px 30px 30px; + checkbox-hover: 437px 60px 30px 30px; + checkbox-active: 467px 60px 30px 30px; + checkbox-checked: 407px 90px 30px 30px; + checkbox-checked-hover: 437px 90px 30px 30px; + checkbox-checked-active: 467px 90px 30px 30px; + + tableheader-l: 127px 192px 16px 31px; + tableheader-c: 143px 192px 2px 31px; + tableheader-r: 145px 192px 15px 31px; + + expand: 3px 232px 17px 17px; + expand-hover: 21px 232px 17px 17px; + expand-active: 39px 232px 17px 17px; + expand-collapsed: 3px 250px 17px 17px; + expand-collapsed-hover: 21px 250px 17px 17px; + expand-collapsed-active: 39px 250px 17px 17px; + + slidertrack-t: 70px 199px 27px 2px; + slidertrack-c: 70px 201px 27px 1px; + slidertrack-b: 70px 202px 27px 2px; + + sliderbar-t: 56px 152px 23px 23px; + sliderbar-c: 56px 175px 23px 1px; + sliderbar-b: 56px 176px 23px 22px; + sliderbar-hover-t: 80px 152px 23px 23px; + sliderbar-hover-c: 80px 175px 23px 1px; + sliderbar-hover-b: 80px 176px 23px 22px; + sliderbar-active-t: 104px 152px 23px 23px; + sliderbar-active-c: 104px 175px 23px 1px; + sliderbar-active-b: 104px 176px 23px 22px; + + sliderarrowdec: 0px 152px 27px 24px; + sliderarrowdec-hover: 0px 177px 27px 24px; + sliderarrowdec-active: 0px 202px 27px 24px; + + sliderarrowinc: 28px 152px 27px 24px; + sliderarrowinc-hover: 28px 177px 27px 24px; + sliderarrowinc-active: 28px 202px 27px 24px; + + range-track: 219px 194px 3px 32px; + range-track-inner: 220px 204px 1px 14px; + range-track-focus: 219px 232px 3px 32px; + range-track-focus-inner: 220px 242px 1px 14px; + + range-bar: 127px 191px 34px 32px; + range-dec: 3px 232px 17px 17px; + range-dec-hover: 21px 232px 17px 17px; + range-dec-active: 39px 232px 17px 17px; + range-inc: 3px 250px 17px 17px; + range-inc-hover: 21px 250px 17px 17px; + range-inc-active: 39px 250px 17px 17px; + + progress-l: 103px 267px 13px 34px; + progress-c: 116px 267px 54px 34px; + progress-r: 170px 267px 13px 34px; + progress-fill-l: 110px 302px 6px 34px; + progress-fill-c: 140px 302px 6px 34px; + progress-fill-r: 170px 302px 6px 34px; + gauge: 0px 271px 100px 86px; + gauge-fill: 0px 356px 100px 86px; +} + +body +{ + font-family: LatoLatin; + font-weight: normal; + font-style: normal; + font-size: 15dp; + color: white; + nav: auto; +} + +body.window +{ + padding-top: 43dp; + padding-bottom: 20dp; + + min-width: 250dp; + max-width: 800dp; + + min-height: 135dp; + max-height: 700dp; +} + +div#title_bar +{ + z-index: 1; + + position: absolute; + top: 7dp; + left: 0; + + text-align: left; + vertical-align: bottom; +} + +div#title_bar div#icon +{ + position: absolute; + left: 15dp; + top: -4dp; + + width: 51dp; + height: 39dp; +} + + +div#title_bar span +{ + padding-left: 85dp; + padding-right: 25dp; + padding-top: 18dp; + padding-bottom: 43dp; + + vertical-align: top; + + line-height: 24dp; + font-size: 20dp; + font-weight: bold; + + font-effect: glow(1dp black); + decorator: tiled-horizontal( title-bar-l, title-bar-c, title-bar-r ); +} + +div#window +{ + width: auto; + height: 100%; + padding: 10dp 15dp; + + decorator: tiled-box( + window-tl, window-t, window-tr, + window-l, window-c, window-r, + window-bl, window-b, window-br + ); +} + +div#content +{ + z-index: 2; + + width: auto; + height: 100%; + + overflow: hidden auto; + + text-align: center; +} + + + +p +{ + text-align: left; + margin-bottom: 1em; +} + +h1 +{ + margin-left: 0.4em; + margin-bottom: 0.4em; + + text-align: left; + font-size: 16dp; + font-weight: bold; + + font-effect: glow(1dp 1dp 1dp 1dp #1117); +} + + + +input, +select +{ + margin-left: 20dp; +} + +input.submit +{ + margin-left: 0; +} + +input, button, select { + nav: auto; +} + +button, +input.submit +{ + display: inline-block; + + width: 159dp; + height: 33dp; + padding-top: 12dp; + + font-size: 16dp; + text-align: center; + tab-index: auto; + + decorator: image(button); +} + +button:focus, +input.submit:focus +{ + font-effect: blur(3dp #fff); +} + +button:hover, button:focus-visible, +input.submit:hover, input.submit:focus-visible +{ + decorator: image(button-hover); +} + +button:active, +input.submit:active +{ + decorator: image(button-active); +} + +input.submit:disabled +{ + decorator: image(button); + image-color: rgba(50, 150, 150, 120); + cursor: unavailable; +} + +input.text, input.password +{ + box-sizing: border-box; + height: 31dp; + padding: 11dp 10dp 0; + decorator: tiled-horizontal( text-l, text-c, auto ); /* Right becomes mirrored left */ + cursor: text; + text-align: left; +} +input.text:focus-visible, input.password:focus-visible +{ + decorator: tiled-horizontal( text-focus-l, text-focus-c, auto ); +} +input.text:placeholder-shown, input.password:placeholder-shown +{ + color: #888; +} + +textarea +{ + padding: 5dp 8dp; + decorator: ninepatch(textarea, textarea-inner, 1.0) border-box; + cursor: text; + text-align: left; + line-height: 1.3; + border-width: 14dp 12dp 10dp; + border-color: transparent; +} +textarea:focus-visible +{ + decorator: ninepatch(textarea-focus, textarea-focus-inner, 1.0) border-box; +} + +input.text, +input.password, +select, +textarea +{ + color: #333; + font-size: 13dp; +} + +table input.text +{ + box-sizing: border-box; + width: 100%; + margin: 0; + padding: 1dp 5dp; + height: auto; + line-height: 16dp; + text-align: center; + + border-width: 1dp; + border-color: black; + background-color: white; + + font-size: 15dp; +} +table input.text, table input.text:focus-visible +{ + decorator: none; +} + + +select +{ + width: 175dp; + height: 37dp; +} + +select selectvalue +{ + width: auto; + margin-right: 30dp; + + height: 25dp; + padding: 12dp 10dp 0dp 10dp; + + decorator: image( selectvalue ); +} +select:focus-visible selectvalue { + decorator: image( selectvalue-hover ); +} + +select selectarrow +{ + width: 30dp; + height: 37dp; + + decorator: image( selectarrow ); +} + +select:hover selectarrow, select:focus-visible selectarrow +{ + decorator: image( selectarrow-hover ); +} + +select:active selectarrow, +select selectarrow:checked +{ + decorator: image( selectarrow-active ); +} + +select selectbox +{ + margin-left: 1dp; + margin-top: -7dp; + margin-bottom: -10dp; + width: 162dp; + padding: 1dp 4dp 4dp 4dp; +} + +select selectbox, +tbody +{ + decorator: tiled-box( + selectbox-tl, selectbox-t, selectbox-tr, + selectbox-l, selectbox-c, auto, /* auto mirrors left */ + selectbox-bl, selectbox-b, selectbox-br + ); +} + +select selectbox option +{ + width: auto; + padding: 3dp 0 3dp 6dp; + background: #DDDD; +} + +select selectbox option:nth-child(even), +tr:nth-child(even) +{ + background: #FFFFFFA0; +} +select selectbox option:checked +{ + font-weight: bold; +} +select selectbox option:hover +{ + background: #FF5D5D; +} + + + +input.radio, +input.checkbox +{ + width: 30dp; + height: 30dp; + + vertical-align: -11dp; +} + +input.radio +{ + decorator: image(radio); +} + +input.radio:hover, input.radio:focus-visible +{ + decorator: image(radio-hover); +} + +input.radio:active +{ + decorator: image(radio-active); +} + +input.radio:checked +{ + decorator: image(radio-checked); +} + +input.radio:checked:hover, input.radio:checked:focus-visible +{ + decorator: image(radio-checked-hover); +} + +input.radio:checked:active +{ + decorator: image(radio-checked-active); +} + +input.checkbox +{ + decorator: image(checkbox); +} + +input.checkbox:hover, input.checkbox:focus-visible +{ + decorator: image(checkbox-hover); +} + +input.checkbox:active +{ + decorator: image(checkbox-active); +} + +input.checkbox:checked +{ + decorator: image(checkbox-checked); +} + +input.checkbox:checked:hover, input.checkbox:checked:focus-visible +{ + decorator: image(checkbox-checked-hover); +} + +input.checkbox:checked:active +{ + decorator: image(checkbox-checked-active); +} + +input.range { + width: 200dp; + height: 32dp; + vertical-align: -12dp; +} +input.range slidertrack { + margin-top: 3dp; + height: 22dp; + image-color: #ecc; + decorator: ninepatch( range-track, range-track-inner, 1.0 ); +} +input.range:focus-visible slidertrack { + decorator: ninepatch( range-track-focus, range-track-focus-inner, 1.0 ); +} +input.range sliderprogress { + background: rgba(100, 0, 0, 80); + margin-top: 8dp; + height: 7dp; +} +input.range sliderbar { + margin-left: -8dp; + margin-right: -7dp; + margin-top: -3dp; + width: 34dp; + height: 23dp; + decorator: image( range-bar ); +} +input.range sliderbar:hover, input.range slidertrack:hover + sliderbar { + image-color: #cc0; +} +input.range sliderbar:active, input.range slidertrack:active + sliderbar { + image-color: #c80; +} +input.range sliderarrowdec, input.range sliderarrowinc { + width: 17dp; + height: 17dp; + margin-top: 6dp; +} +input.range sliderarrowdec { decorator: image( range-dec ); } +input.range sliderarrowinc { decorator: image( range-inc ); } +input.range sliderarrowdec:hover { decorator: image( range-dec-hover ); } +input.range sliderarrowinc:hover { decorator: image( range-inc-hover ); } +input.range sliderarrowdec:active { decorator: image( range-dec-active ); } +input.range sliderarrowinc:active { decorator: image( range-inc-active ); } + +thead tr { + height: 35dp; + decorator: tiled-horizontal( tableheader-l, tableheader-c, tableheader-r ); +} +thead td { + padding-top: 11dp; +} + +tbody { + /* Margin left/right only affects the background positioning for the decorator, not the cell placement */ + margin-left: 5dp; + margin-right: 4dp; + /* Padding top/bottom adds extra spacing between the header row and the body, and between the body and table bottom */ + padding-top: 4dp; + padding-bottom: 4dp; +} +tbody tr { + margin-left: 9dp; + margin-right: 8dp; + color: black; +} + + +expand +{ + display: block; + + margin: 1dp 0 1dp 5dp; + height: 17dp; + width: 17dp; + + decorator: image( expand ); +} + +expand:hover +{ + decorator: image( expand-hover ); +} + +expand:active +{ + decorator: image( expand-active ); +} + +expand.collapsed +{ + decorator: image( expand-collapsed ); +} + +expand.collapsed:hover +{ + decorator: image( expand-collapsed-hover ); +} + +expand.collapsed:active +{ + decorator: image( expand-collapsed-active ); +} + + +scrollbarvertical +{ + margin-top: -6dp; + margin-bottom: -6dp; + margin-right: -11dp; + width: 27dp; +} + +scrollbarvertical slidertrack +{ + decorator: tiled-vertical( slidertrack-t, slidertrack-c, slidertrack-b ); +} +scrollbarvertical slidertrack:active +{ + image-color: #aaa; +} + +scrollbarvertical sliderbar +{ + margin-left: 4dp; + width: 23dp; + min-height: 46dp; + + decorator: tiled-vertical( sliderbar-t, sliderbar-c, sliderbar-b ); +} +scrollbarvertical sliderbar:hover +{ + decorator: tiled-vertical( sliderbar-hover-t, sliderbar-hover-c, sliderbar-hover-b ); +} +scrollbarvertical sliderbar:active +{ + decorator: tiled-vertical( sliderbar-active-t, sliderbar-active-c, sliderbar-active-b ); +} + +scrollbarvertical sliderarrowdec, +scrollbarvertical sliderarrowinc +{ + width: 27dp; + height: 24dp; +} +scrollbarvertical sliderarrowdec +{ + decorator: image( sliderarrowdec ); +} +scrollbarvertical sliderarrowdec:hover +{ + decorator: image( sliderarrowdec-hover ); +} +scrollbarvertical sliderarrowdec:active +{ + decorator: image( sliderarrowdec-active ); +} + +scrollbarvertical sliderarrowinc +{ + decorator: image( sliderarrowinc ); +} +scrollbarvertical sliderarrowinc:hover +{ + decorator: image( sliderarrowinc-hover ); +} +scrollbarvertical sliderarrowinc:active +{ + decorator: image( sliderarrowinc-active ); +} + +scrollbarhorizontal +{ + width: 0; + height: 0; +} + +textarea scrollbarvertical +{ + cursor: arrow; + width: 12dp; + margin: -3dp -7dp -3dp 7dp; +} +textarea scrollbarvertical slidertrack +{ + decorator: none; + background-color: #eee; +} +textarea scrollbarvertical sliderbar +{ + decorator: none; + background-color: #bc0000; + border-left: 5dp #cc7272; + width: 7dp; + min-height: 16dp; + margin: 0; +} +textarea scrollbarvertical sliderbar:hover +{ + decorator: none; + background-color: #ca3535; +} + +textarea scrollbarvertical sliderbar:active +{ + decorator: none; + background-color: #880000; +} +textarea scrollbarvertical sliderarrowdec, +textarea scrollbarvertical sliderarrowinc +{ + width: 0; + height: 0; +} + +textarea scrollbarhorizontal +{ + cursor: arrow; + height: 10dp; + margin: 3dp -7dp -3dp -3dp; +} +textarea scrollbarhorizontal sliderbar +{ + background-color: #bc0000; + border-top: 4dp #cc7272; + height: 6dp; + min-width: 10dp; +} +textarea scrollbarhorizontal sliderbar:hover +{ + background-color: #ca3535; +} +textarea scrollbarhorizontal sliderbar:active +{ + background-color: #880000; +} +textarea scrollbarhorizontal slidertrack +{ + background-color: #eee; +} + +textarea scrollbarcorner +{ + cursor: arrow; + background-color: #ccc; + margin-top: 3dp; + margin-left: 7dp; +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/assets/invader.tga b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/assets/invader.tga new file mode 100644 index 0000000000000000000000000000000000000000..3223e72f34e33f988e0962a35422f8a24cd4d3a1 GIT binary patch literal 1049115 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�l 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 E4?lBWX#fBK literal 0 HcmV?d00001 diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/assets/rml.rcss b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/assets/rml.rcss new file mode 100644 index 00000000..0905b24b --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/assets/rml.rcss @@ -0,0 +1,61 @@ +/* +* Default styles for all the basic elements. +*/ + +div +{ + display: block; +} + +p +{ + display: block; +} + +h1 +{ + display: block; +} + +em +{ + font-style: italic; +} + +strong +{ + font-weight: bold; +} + +select +{ + text-align: left; +} + +tabset tabs +{ + display: block; +} + +table { + box-sizing: border-box; + display: table; +} +tr { + box-sizing: border-box; + display: table-row; +} +td { + box-sizing: border-box; + display: table-cell; +} +col { + box-sizing: border-box; + display: table-column; +} +colgroup { + display: table-column-group; +} +thead, tbody, tfoot { + display: table-row-group; +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/assets/window.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/assets/window.rml new file mode 100644 index 00000000..e368b36f --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/assets/window.rml @@ -0,0 +1,19 @@ + diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/CMakeLists.txt b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/CMakeLists.txt new file mode 100644 index 00000000..223aaaa5 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/CMakeLists.txt @@ -0,0 +1,41 @@ +# The following sample does not require any default font engine. +add_subdirectory("bitmap_font") + +# Only enable the remaining samples if a default font engine is selected. +if(RMLUI_FONT_ENGINE_ENABLED) + add_subdirectory("animation") + add_subdirectory("benchmark") + add_subdirectory("custom_log") + add_subdirectory("data_binding") + add_subdirectory("demo") + add_subdirectory("drag") + add_subdirectory("effects") + add_subdirectory("load_document") + add_subdirectory("transform") + add_subdirectory("tree_view") + + if(RMLUI_HARFBUZZ_SAMPLE) + add_subdirectory("harfbuzz") + else() + message(STATUS "HarfBuzz sample disabled due to RMLUI_HARFBUZZ_SAMPLE=OFF") + endif() + if(RMLUI_LOTTIE_PLUGIN) + add_subdirectory("lottie") + else() + message(STATUS "Lottie sample disabled due to RMLUI_LOTTIE_PLUGIN=OFF") + endif() + if(RMLUI_SVG_PLUGIN) + add_subdirectory("svg") + else() + message(STATUS "SVG sample disabled due to RMLUI_SVG_PLUGIN=OFF") + endif() + + # Enable the IME sample only for SDL or Windows backends; no other platform backend is currently supported. + if(RMLUI_BACKEND MATCHES "^Win32" OR (RMLUI_IME_SAMPLE_USE_NOTO_FONTS AND RMLUI_BACKEND MATCHES "^SDL")) + add_subdirectory("ime") + elseif (RMLUI_BACKEND MATCHES "^SDL") + message(STATUS "IME sample disabled - no Asian language fonts available, see RMLUI_IME_SAMPLE_USE_NOTO_FONTS") + else() + message(STATUS "IME sample disabled - only available with SDL or Win32 backends, see RMLUI_BACKEND") + endif() +endif() diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/animation/CMakeLists.txt b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/animation/CMakeLists.txt new file mode 100644 index 00000000..d455841e --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/animation/CMakeLists.txt @@ -0,0 +1,12 @@ +set(SAMPLE_NAME "animation") +set(TARGET_NAME "${RMLUI_SAMPLE_PREFIX}${SAMPLE_NAME}") + +add_executable(${TARGET_NAME} WIN32 + src/main.cpp +) + +set_common_target_options(${TARGET_NAME}) + +target_link_libraries(${TARGET_NAME} PRIVATE rmlui_shell) + +install_sample_target(${TARGET_NAME}) diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/animation/data/animation.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/animation/data/animation.rml new file mode 100644 index 00000000..d4776582 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/animation/data/animation.rml @@ -0,0 +1,273 @@ + + + + Animation Sample + + + + +

+
+
+
+
+
+ +
+ +
+ +
+

Test transform animations

+
Generic form conversion.
+
Match different transform primitive sizes
+
Force full matrix decomposition
+
+ +
+

Mixed units tests

+
Pixel vs percentage.
+
Pixel vs percentage transform.
+
Animation event
Events
(press arrow keys)
+
+ +
+

Transition tests

+
Transition test (hover)
+
Transition class (click)
+
Keyframes test
+
+ + diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/animation/src/main.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/animation/src/main.cpp new file mode 100644 index 00000000..022c7ca8 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/animation/src/main.cpp @@ -0,0 +1,338 @@ +#include +#include +#include +#include +#include +#include + +class DemoWindow { +public: + DemoWindow(const Rml::String& title, Rml::Context* context) + { + using namespace Rml; + document = context->LoadDocument("basic/animation/data/animation.rml"); + if (document != nullptr) + { + document->GetElementById("title")->SetInnerRML(title); + + // Button fun + { + auto el = document->GetElementById("start_game"); + auto p1 = Transform::MakeProperty({Transforms::Rotate2D{10.f}, Transforms::TranslateX{100.f}}); + auto p2 = Transform::MakeProperty({Transforms::Scale2D{3.f}}); + el->Animate("transform", p1, 1.8f, Tween{Tween::Elastic, Tween::InOut}, -1, true); + el->AddAnimationKey("transform", p2, 1.3f, Tween{Tween::Elastic, Tween::InOut}); + } + { + auto el = document->GetElementById("high_scores"); + el->Animate("margin-left", Property(0.f, Unit::PX), 0.3f, Tween{Tween::Sine, Tween::In}, 10, true, 1.f); + el->AddAnimationKey("margin-left", Property(100.f, Unit::PX), 3.0f, Tween{Tween::Circular, Tween::Out}); + } + { + auto el = document->GetElementById("options"); + el->Animate("image-color", Property(Colourb(128, 255, 255, 255), Unit::COLOUR), 0.3f, Tween{}, -1, false); + el->AddAnimationKey("image-color", Property(Colourb(128, 128, 255, 255), Unit::COLOUR), 0.3f); + el->AddAnimationKey("image-color", Property(Colourb(0, 128, 128, 255), Unit::COLOUR), 0.3f); + el->AddAnimationKey("image-color", Property(Colourb(64, 128, 255, 0), Unit::COLOUR), 0.9f); + el->AddAnimationKey("image-color", Property(Colourb(255, 255, 255, 255), Unit::COLOUR), 0.3f); + } + { + auto el = document->GetElementById("exit"); + PropertyDictionary pd; + StyleSheetSpecification::ParsePropertyDeclaration(pd, "transform", "translate(200px, 200px) rotate(1215deg)"); + el->Animate("transform", *pd.GetProperty(PropertyId::Transform), 3.f, Tween{Tween::Bounce, Tween::Out}, -1); + } + + // Transform tests + { + auto el = document->GetElementById("generic"); + auto p = Transform::MakeProperty( + {Transforms::TranslateY{50, Unit::PX}, Transforms::Rotate3D{0, 0, 1, -90, Unit::DEG}, Transforms::ScaleY{0.8f}}); + el->Animate("transform", p, 1.5f, Tween{Tween::Sine, Tween::InOut}, -1, true); + } + { + auto el = document->GetElementById("combine"); + auto p = Transform::MakeProperty({Transforms::Translate2D{50, 50, Unit::PX}, Transforms::Rotate2D(1215)}); + el->Animate("transform", p, 8.0f, Tween{}, -1, true); + } + { + auto el = document->GetElementById("decomposition"); + auto p = Transform::MakeProperty({Transforms::TranslateY{50, Unit::PX}, Transforms::Rotate3D{0.8f, 0, 1, 110, Unit::DEG}}); + el->Animate("transform", p, 1.3f, Tween{Tween::Quadratic, Tween::InOut}, -1, true); + } + + // Mixed units tests + { + auto el = document->GetElementById("abs_rel"); + el->Animate("margin-left", Property(50.f, Unit::PERCENT), 1.5f, Tween{}, -1, true); + } + { + auto el = document->GetElementById("abs_rel_transform"); + auto p = Transform::MakeProperty({Transforms::TranslateX{0, Unit::PX}}); + el->Animate("transform", p, 1.5f, Tween{}, -1, true); + } + { + auto el = document->GetElementById("animation_event"); + el->Animate("top", Property(Math::RandomReal(250.f), Unit::PX), 1.5f, Tween{Tween::Cubic, Tween::InOut}); + el->Animate("left", Property(Math::RandomReal(250.f), Unit::PX), 1.5f, Tween{Tween::Cubic, Tween::InOut}); + } + + document->Show(); + } + } + + void Update(double t) + { + if (document) + { + if (t - t_prev_fade >= 1.4) + { + auto el = document->GetElementById("help"); + if (el->IsClassSet("fadeout")) + { + el->SetClass("fadeout", false); + el->SetClass("fadein", true); + } + else if (el->IsClassSet("fadein")) + { + el->SetClass("fadein", false); + el->SetClass("textalign", true); + } + else + { + el->SetClass("textalign", false); + el->SetClass("fadeout", true); + } + + t_prev_fade = t; + } + } + } + + ~DemoWindow() + { + if (document) + { + document->Close(); + } + } + + Rml::ElementDocument* GetDocument() { return document; } + +private: + Rml::ElementDocument* document; + double t_prev_fade = 0; +}; + +Rml::Context* context = nullptr; +bool run_loop = true; +bool single_loop = false; +int nudge = 0; + +class Event : public Rml::EventListener { +public: + Event(const Rml::String& value) : value(value) {} + + void ProcessEvent(Rml::Event& event) override + { + using namespace Rml; + + if (value == "exit") + Backend::RequestExit(); + + switch (event.GetId()) + { + case EventId::Keydown: + { + Rml::Input::KeyIdentifier key_identifier = (Rml::Input::KeyIdentifier)event.GetParameter("key_identifier", 0); + + if (key_identifier == Rml::Input::KI_SPACE) + { + run_loop = !run_loop; + } + else if (key_identifier == Rml::Input::KI_RETURN) + { + run_loop = false; + single_loop = true; + } + else if (key_identifier == Rml::Input::KI_OEM_PLUS) + { + nudge = 1; + } + else if (key_identifier == Rml::Input::KI_OEM_MINUS) + { + nudge = -1; + } + else if (key_identifier == Rml::Input::KI_ESCAPE) + { + Backend::RequestExit(); + } + else if (key_identifier == Rml::Input::KI_LEFT) + { + auto el = context->GetRootElement()->GetElementById("keyevent_response"); + if (el) + el->Animate("left", Property{-200.f, Unit::DP}, 0.5, Tween{Tween::Cubic}); + } + else if (key_identifier == Rml::Input::KI_RIGHT) + { + auto el = context->GetRootElement()->GetElementById("keyevent_response"); + if (el) + el->Animate("left", Property{200.f, Unit::DP}, 0.5, Tween{Tween::Cubic}); + } + else if (key_identifier == Rml::Input::KI_UP) + { + auto el = context->GetRootElement()->GetElementById("keyevent_response"); + auto offset_right = Property{200.f, Unit::DP}; + if (el) + el->Animate("left", Property{0.f, Unit::PX}, 0.5, Tween{Tween::Cubic}, 1, true, 0, &offset_right); + } + else if (key_identifier == Rml::Input::KI_DOWN) + { + auto el = context->GetRootElement()->GetElementById("keyevent_response"); + if (el) + el->Animate("left", Property{0.f, Unit::PX}, 0.5, Tween{Tween::Cubic}); + } + } + break; + + case EventId::Click: + { + auto el = event.GetTargetElement(); + if (el->GetId() == "transition_class") + { + el->SetClass("move_me", !el->IsClassSet("move_me")); + } + } + break; + + case EventId::Animationend: + { + auto el = event.GetTargetElement(); + if (el->GetId() == "animation_event") + { + el->Animate("top", Property(Math::RandomReal(200.f), Unit::PX), 1.2f, Tween{Tween::Cubic, Tween::InOut}); + el->Animate("left", Property(Math::RandomReal(100.f), Unit::PERCENT), 0.8f, Tween{Tween::Cubic, Tween::InOut}); + } + } + break; + + default: break; + } + } + + void OnDetach(Rml::Element* /*element*/) override { delete this; } + +private: + Rml::String value; +}; + +class EventInstancer : public Rml::EventListenerInstancer { +public: + Rml::EventListener* InstanceEventListener(const Rml::String& value, Rml::Element* /*element*/) override { return new Event(value); } +}; + +#if defined RMLUI_PLATFORM_WIN32 + #include +int APIENTRY WinMain(HINSTANCE /*instance_handle*/, HINSTANCE /*previous_instance_handle*/, char* /*command_line*/, int /*command_show*/) +#else +int main(int /*argc*/, char** /*argv*/) +#endif +{ + const int window_width = 1700; + const int window_height = 900; + + // Initializes the shell which provides common functionality used by the included samples. + if (!Shell::Initialize()) + return -1; + + // Constructs the system and render interfaces, creates a window, and attaches the renderer. + if (!Backend::Initialize("Animation Sample", window_width, window_height, true)) + { + Shell::Shutdown(); + return -1; + } + + // Install the custom interfaces constructed by the backend before initializing RmlUi. + Rml::SetSystemInterface(Backend::GetSystemInterface()); + Rml::SetRenderInterface(Backend::GetRenderInterface()); + + // RmlUi initialisation. + Rml::Initialise(); + + // Create the main RmlUi context. + context = Rml::CreateContext("main", Rml::Vector2i(window_width, window_height)); + if (!context) + { + Rml::Shutdown(); + Backend::Shutdown(); + Shell::Shutdown(); + return -1; + } + + Rml::Debugger::Initialise(context); + + EventInstancer event_listener_instancer; + Rml::Factory::RegisterEventListenerInstancer(&event_listener_instancer); + + Shell::LoadFonts(); + + Rml::UniquePtr window = Rml::MakeUnique("Animation sample", context); + window->GetDocument()->AddEventListener(Rml::EventId::Keydown, new Event("hello")); + window->GetDocument()->AddEventListener(Rml::EventId::Keyup, new Event("hello")); + window->GetDocument()->AddEventListener(Rml::EventId::Animationend, new Event("hello")); + + bool running = true; + while (running) + { + running = Backend::ProcessEvents(context, &Shell::ProcessKeyDownShortcuts); + + double t = Rml::GetSystemInterface()->GetElapsedTime(); + + if (run_loop || single_loop) + { + window->Update(t); + context->Update(); + + Backend::BeginFrame(); + context->Render(); + Backend::PresentFrame(); + + single_loop = false; + } + + static double t_prev = 0.0f; + float dt = float(t - t_prev); + static int count_frames = 0; + count_frames += 1; + + if (nudge) + { + t_prev = t; + static float ff = 0.0f; + ff += float(nudge) * 0.3f; + auto el = window->GetDocument()->GetElementById("exit"); + el->SetProperty(Rml::PropertyId::MarginLeft, Rml::Property(ff, Rml::Unit::PX)); + float f_left = el->GetAbsoluteLeft(); + Rml::Log::Message(Rml::Log::LT_INFO, "margin-left: '%f' abs: %f.", ff, f_left); + nudge = 0; + } + + if (dt > 0.2f) + { + t_prev = t; + auto el = window->GetDocument()->GetElementById("fps"); + float fps = float(count_frames) / dt; + count_frames = 0; + el->SetInnerRML(Rml::CreateString("FPS: %f", fps)); + } + } + + window.reset(); + + // Shutdown RmlUi. + Rml::Shutdown(); + Backend::Shutdown(); + Shell::Shutdown(); + + return 0; +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/benchmark/CMakeLists.txt b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/benchmark/CMakeLists.txt new file mode 100644 index 00000000..42874624 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/benchmark/CMakeLists.txt @@ -0,0 +1,12 @@ +set(SAMPLE_NAME "benchmark") +set(TARGET_NAME "${RMLUI_SAMPLE_PREFIX}${SAMPLE_NAME}") + +add_executable(${TARGET_NAME} WIN32 + src/main.cpp +) + +set_common_target_options(${TARGET_NAME}) + +target_link_libraries(${TARGET_NAME} PRIVATE rmlui_shell) + +install_sample_target(${TARGET_NAME}) diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/benchmark/data/benchmark.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/benchmark/data/benchmark.rml new file mode 100644 index 00000000..31a2922a --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/benchmark/data/benchmark.rml @@ -0,0 +1,36 @@ + + + + Benchmark Sample + + + + +
+
+
+ + diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/benchmark/src/main.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/benchmark/src/main.cpp new file mode 100644 index 00000000..7605b6db --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/benchmark/src/main.cpp @@ -0,0 +1,250 @@ +#include +#include +#include +#include + +class DemoWindow { +public: + DemoWindow(const Rml::String& title, Rml::Context* context) + { + using namespace Rml; + document = context->LoadDocument("basic/benchmark/data/benchmark.rml"); + if (document != nullptr) + { + { + document->GetElementById("title")->SetInnerRML(title); + } + + document->Show(); + } + } + + void performance_test() + { + RMLUI_ZoneScoped; + + if (!document) + return; + + Rml::String rml; + + for (int i = 0; i < 50; i++) + { + int index = rand() % 1000; + int route = rand() % 50; + int max = (rand() % 40) + 10; + int value = rand() % max; + Rml::String rml_row = Rml::CreateString(R"( +
+ +
+
Assigned
+ +
+
Assign to route
+
+ Confirm +
+
+
)", + index, route, max, value); + rml += rml_row; + } + + if (auto el = document->GetElementById("performance")) + el->SetInnerRML(rml); + } + + class SimpleEventListener : public Rml::EventListener { + public: + void ProcessEvent(Rml::Event& event) override + { + static int i = 0; + event.GetTargetElement()->SetProperty("background-color", i++ % 2 == 0 ? "green" : "orange"); + } + } simple_event_listener; + + ~DemoWindow() + { + if (document) + { + document->Close(); + } + } + + Rml::ElementDocument* GetDocument() { return document; } + +private: + Rml::ElementDocument* document; +}; + +bool run_loop = true; +bool single_loop = true; +bool run_update = true; +bool single_update = true; + +class Event : public Rml::EventListener { +public: + Event(const Rml::String& value) : value(value) {} + + void ProcessEvent(Rml::Event& event) override + { + using namespace Rml; + + if (value == "exit") + Backend::RequestExit(); + + if (event == "keydown") + { + auto key_identifier = (Rml::Input::KeyIdentifier)event.GetParameter("key_identifier", 0); + + if (key_identifier == Rml::Input::KI_SPACE) + { + run_loop = !run_loop; + } + else if (key_identifier == Rml::Input::KI_DOWN) + { + run_loop = false; + single_loop = true; + } + else if (key_identifier == Rml::Input::KI_RIGHT) + { + run_update = false; + single_update = true; + } + else if (key_identifier == Rml::Input::KI_RETURN) + { + run_update = !run_update; + } + else if (key_identifier == Rml::Input::KI_ESCAPE) + { + Backend::RequestExit(); + } + } + } + + void OnDetach(Rml::Element* /*element*/) override { delete this; } + +private: + Rml::String value; +}; + +class EventInstancer : public Rml::EventListenerInstancer { +public: + /// Instances a new event handle for Invaders. + Rml::EventListener* InstanceEventListener(const Rml::String& value, Rml::Element* /*element*/) override { return new Event(value); } +}; + +#if defined RMLUI_PLATFORM_WIN32 + #include +int APIENTRY WinMain(HINSTANCE /*instance_handle*/, HINSTANCE /*previous_instance_handle*/, char* /*command_line*/, int /*command_show*/) +#else +int main(int /*argc*/, char** /*argv*/) +#endif +{ + const int window_width = 1800; + const int window_height = 1000; + + // Initializes the shell which provides common functionality used by the included samples. + if (!Shell::Initialize()) + return -1; + + // Constructs the system and render interfaces, creates a window, and attaches the renderer. + if (!Backend::Initialize("Benchmark Sample", window_width, window_height, true)) + { + Shell::Shutdown(); + return -1; + } + + // Install the custom interfaces constructed by the backend before initializing RmlUi. + Rml::SetSystemInterface(Backend::GetSystemInterface()); + Rml::SetRenderInterface(Backend::GetRenderInterface()); + + // RmlUi initialisation. + Rml::Initialise(); + + // Create the main RmlUi context. + Rml::Context* context = Rml::CreateContext("main", Rml::Vector2i(window_width, window_height)); + if (!context) + { + Rml::Shutdown(); + Backend::Shutdown(); + Shell::Shutdown(); + return -1; + } + + Rml::Debugger::Initialise(context); + + EventInstancer event_listener_instancer; + Rml::Factory::RegisterEventListenerInstancer(&event_listener_instancer); + + Shell::LoadFonts(); + + Rml::UniquePtr window = Rml::MakeUnique("Benchmark sample", context); + window->GetDocument()->AddEventListener(Rml::EventId::Keydown, new Event("hello")); + window->GetDocument()->AddEventListener(Rml::EventId::Keyup, new Event("hello")); + window->GetDocument()->AddEventListener(Rml::EventId::Animationend, new Event("hello")); + + bool running = true; + while (running) + { + running = Backend::ProcessEvents(context, &Shell::ProcessKeyDownShortcuts); + + if (run_update || single_update) + { + single_update = false; + + window->performance_test(); + } + + if (run_loop || single_loop) + { + single_loop = false; + + context->Update(); + + Backend::BeginFrame(); + context->Render(); + Backend::PresentFrame(); + } + + static constexpr int buffer_size = 200; + static float fps_buffer[buffer_size] = {}; + static int buffer_index = 0; + + static double t_prev = 0.0f; + double t = Rml::GetSystemInterface()->GetElapsedTime(); + float dt = float(t - t_prev); + t_prev = t; + static int count_frames = 0; + count_frames += 1; + + float fps = 1.0f / dt; + fps_buffer[buffer_index] = fps; + buffer_index = ((buffer_index + 1) % buffer_size); + + if (window && count_frames > buffer_size / 8) + { + float fps_mean = 0; + for (int i = 0; i < buffer_size; i++) + fps_mean += fps_buffer[(buffer_index + i) % buffer_size]; + fps_mean = fps_mean / (float)buffer_size; + + auto el = window->GetDocument()->GetElementById("fps"); + count_frames = 0; + el->SetInnerRML(Rml::CreateString("FPS: %f", fps_mean)); + } + } + + window.reset(); + + // Shutdown RmlUi. + Rml::Shutdown(); + + Backend::Shutdown(); + Shell::Shutdown(); + + return 0; +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/bitmap_font/CMakeLists.txt b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/bitmap_font/CMakeLists.txt new file mode 100644 index 00000000..e3ea4b92 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/bitmap_font/CMakeLists.txt @@ -0,0 +1,16 @@ +set(SAMPLE_NAME "bitmap_font") +set(TARGET_NAME "${RMLUI_SAMPLE_PREFIX}${SAMPLE_NAME}") + +add_executable(${TARGET_NAME} WIN32 + src/FontEngineBitmap.cpp + src/FontEngineBitmap.h + src/FontEngineInterfaceBitmap.cpp + src/FontEngineInterfaceBitmap.h + src/main.cpp +) + +set_common_target_options(${TARGET_NAME}) + +target_link_libraries(${TARGET_NAME} PRIVATE rmlui_shell) + +install_sample_target(${TARGET_NAME}) diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/bitmap_font/data/Comfortaa_Regular_22.fnt b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/bitmap_font/data/Comfortaa_Regular_22.fnt new file mode 100644 index 00000000..f569e156 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/bitmap_font/data/Comfortaa_Regular_22.fnt @@ -0,0 +1,758 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/bitmap_font/data/Comfortaa_Regular_22.tga b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/bitmap_font/data/Comfortaa_Regular_22.tga new file mode 100644 index 0000000000000000000000000000000000000000..4c081dce1dab7912ce6657b0bcb6df0faa42bd16 GIT binary patch literal 131116 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 + +Demo + + + + +

This is a bitmap font sample!

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Urna neque viverra justo nec ultrices dui sapien eget mi. Risus quis varius quam quisque id. Amet est placerat in egestas erat imperdiet. Velit egestas dui id ornare arcu odio ut sem. Aliquet porttitor lacus luctus accumsan tortor posuere. Et malesuada fames ac turpis egestas integer eget. Enim nunc faucibus a pellentesque sit amet porttitor eget. Nunc pulvinar sapien et ligula. Sit amet mattis vulputate enim nulla aliquet porttitor lacus luctus. Dolor sit amet consectetur adipiscing. Congue eu consequat ac felis donec et odio pellentesque. Nunc non blandit massa enim nec dui nunc mattis.

+

This block uses an RCSS-loaded font family. Posuere sollicitudin aliquam ultrices sagittis orci a scelerisque. Quam pellentesque nec nam aliquam sem et. Et tortor consequat id porta nibh venenatis. Facilisi cras fermentum odio eu feugiat pretium nibh ipsum consequat. Magna ac placerat vestibulum lectus mauris ultrices eros. Amet purus gravida quis blandit turpis cursus. Elit duis tristique sollicitudin nibh sit amet commodo nulla. Suscipit adipiscing bibendum est ultricies integer quis auctor elit sed. Iaculis at erat pellentesque adipiscing commodo elit at imperdiet. Pellentesque elit ullamcorper dignissim cras. Nunc eget lorem dolor sed viverra ipsum nunc. Ac felis donec et odio pellentesque diam volutpat commodo.

+ + diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/bitmap_font/src/FontEngineBitmap.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/bitmap_font/src/FontEngineBitmap.cpp new file mode 100644 index 00000000..dd025e4e --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/bitmap_font/src/FontEngineBitmap.cpp @@ -0,0 +1,264 @@ +#include "FontEngineBitmap.h" +#include +#include +#include +#include +#include + +namespace FontProviderBitmap { +static Rml::Vector> fonts; + +void Initialise() {} + +void Shutdown() +{ + fonts.clear(); +} + +bool LoadFontFace(const String& file_name, const String& family) +{ + // Load the xml meta file into memory + Rml::UniquePtr data; + size_t length = 0; + + { + auto file_interface = Rml::GetFileInterface(); + auto handle = file_interface->Open(file_name); + if (!handle) + return false; + + length = file_interface->Length(handle); + + data.reset(new byte[length]); + + size_t read_length = file_interface->Read(data.get(), length, handle); + + file_interface->Close(handle); + + if (read_length != length || !data) + return false; + } + + // Parse the xml font description + FontParserBitmap parser; + + { + auto stream = Rml::MakeUnique(data.get(), length); + stream->SetSourceURL(file_name); + + parser.Parse(stream.get()); + + if (parser.family.empty() || parser.glyphs.empty() || parser.texture_name.empty() || parser.metrics.size == 0) + return false; + + // Fill the remaining metrics + parser.metrics.underline_position = 3.f; + parser.metrics.underline_thickness = 1.f; + } + + // Check inputs from RCSS + if (!family.empty()) + { + parser.family = family; + } + + // Construct and add the font face + fonts.push_back(Rml::MakeUnique(parser.family, parser.style, parser.weight, parser.metrics, parser.texture_name, file_name, + parser.texture_dimensions, std::move(parser.glyphs), std::move(parser.kerning))); + + return true; +} + +FontFaceBitmap* GetFontFaceHandle(const String& family, FontStyle style, FontWeight weight, int size) +{ + FontFaceBitmap* best_match = nullptr; + int best_score = 0; + + // Normally, we'd want to only match the font family exactly, but for this demo we create a very lenient heuristic. + for (const auto& font : fonts) + { + int score = 1; + if (font->GetFamily() == family) + score += 100; + + score += 10 - std::min(10, std::abs(font->GetMetrics().size - size)); + + if (font->GetStyle() == style) + score += 2; + if (font->GetWeight() == weight) + score += 1; + + if (score > best_score) + { + best_match = font.get(); + best_score = score; + } + } + + return best_match; +} + +} // namespace FontProviderBitmap + +FontFaceBitmap::FontFaceBitmap(String family, FontStyle style, FontWeight weight, FontMetrics metrics, String texture_name, String texture_path, + Vector2f texture_dimensions, FontGlyphs&& glyphs, FontKerning&& kerning) : + family(family), style(style), weight(weight), metrics(metrics), texture_source(texture_name, texture_path), + texture_dimensions(texture_dimensions), glyphs(std::move(glyphs)), kerning(std::move(kerning)) +{} + +int FontFaceBitmap::GetStringWidth(StringView string, Character previous_character) +{ + int width = 0; + + for (auto it_char = Rml::StringIteratorU8(string); it_char; ++it_char) + { + Character character = *it_char; + + auto it_glyph = glyphs.find(character); + if (it_glyph == glyphs.end()) + continue; + + const BitmapGlyph& glyph = it_glyph->second; + + int kerning = GetKerning(previous_character, character); + + width += glyph.advance + kerning; + previous_character = character; + } + + return width; +} + +int FontFaceBitmap::GenerateString(RenderManager& render_manager, StringView string, Vector2f string_position, ColourbPremultiplied colour, + TexturedMeshList& mesh_list) +{ + int width = 0; + + mesh_list.resize(1); + + mesh_list[0].texture = texture_source.GetTexture(render_manager); + + Rml::Mesh& mesh = mesh_list[0].mesh; + auto& vertices = mesh.vertices; + auto& indices = mesh.indices; + + vertices.reserve(string.size() * 4); + indices.reserve(string.size() * 6); + + Vector2f position = string_position.Round(); + Character previous_character = Character::Null; + + for (auto it_char = Rml::StringIteratorU8(string); it_char; ++it_char) + { + Character character = *it_char; + + auto it_glyph = glyphs.find(character); + if (it_glyph == glyphs.end()) + continue; + + int kerning = GetKerning(previous_character, character); + + width += kerning; + position.x += kerning; + + const BitmapGlyph& glyph = it_glyph->second; + + // Generate the geometry for the character. + vertices.resize(vertices.size() + 4); + indices.resize(indices.size() + 6); + + Vector2f uv_top_left = glyph.position / texture_dimensions; + Vector2f uv_bottom_right = (glyph.position + glyph.dimension) / texture_dimensions; + + Rml::MeshUtilities::GenerateQuad(mesh, Vector2f(position + glyph.offset).Round(), glyph.dimension, colour, uv_top_left, uv_bottom_right); + + width += glyph.advance; + position.x += glyph.advance; + + previous_character = character; + } + + return width; +} + +int FontFaceBitmap::GetKerning(Character left, Character right) const +{ + uint64_t key = (((uint64_t)left << 32) | (uint64_t)right); + + auto it = kerning.find(key); + if (it != kerning.end()) + return it->second; + + return 0; +} + +FontParserBitmap::~FontParserBitmap() {} + +void FontParserBitmap::HandleElementStart(const String& name, const Rml::XMLAttributes& attributes) +{ + if (name == "info") + { + family = Rml::StringUtilities::ToLower(Get(attributes, "face", String())); + metrics.size = Get(attributes, "size", 0); + + style = Get(attributes, "italic", 0) == 1 ? FontStyle::Italic : FontStyle::Normal; + weight = Get(attributes, "bold", 0) == 1 ? FontWeight::Bold : FontWeight::Normal; + } + else if (name == "common") + { + metrics.line_spacing = Get(attributes, "lineHeight", 0.f); + metrics.ascent = Get(attributes, "base", 0.f); + metrics.descent = metrics.line_spacing - metrics.ascent; + + texture_dimensions.x = Get(attributes, "scaleW", 0.f); + texture_dimensions.y = Get(attributes, "scaleH", 0.f); + } + else if (name == "page") + { + int id = Get(attributes, "id", -1); + if (id != 0) + { + Rml::Log::Message(Rml::Log::LT_WARNING, "Only single font textures are supported in Bitmap Font Engine"); + return; + } + texture_name = Get(attributes, "file", String()); + } + else if (name == "char") + { + Character character = (Character)Get(attributes, "id", 0); + if (character == Character::Null) + return; + + BitmapGlyph& glyph = glyphs[character]; + + glyph.offset.x = Get(attributes, "xoffset", 0.f); + glyph.offset.y = Get(attributes, "yoffset", 0.f) - metrics.ascent; // Shift y-origin from top to baseline + + glyph.advance = Get(attributes, "xadvance", 0); + glyph.position.x = Get(attributes, "x", 0.f); + glyph.position.y = Get(attributes, "y", 0.f); + glyph.dimension.x = Get(attributes, "width", 0.f); + glyph.dimension.y = Get(attributes, "height", 0.f); + + if (character == (Character)'x') + metrics.x_height = glyph.dimension.y; + if (character == (Character)0x2026) + metrics.has_ellipsis = true; + } + else if (name == "kerning") + { + uint64_t first = (uint64_t)Get(attributes, "first", 0); + uint64_t second = (uint64_t)Get(attributes, "second", 0); + int amount = Get(attributes, "amount", 0); + + if (first != 0 && second != 0 && amount != 0) + { + uint64_t key = ((first << 32) | second); + kerning[key] = amount; + } + } +} + +void FontParserBitmap::HandleElementEnd(const String& /*name*/) {} + +void FontParserBitmap::HandleData(const String& /*data*/, Rml::XMLDataType /*type*/) {} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/bitmap_font/src/FontEngineBitmap.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/bitmap_font/src/FontEngineBitmap.h new file mode 100644 index 00000000..d532f70e --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/bitmap_font/src/FontEngineBitmap.h @@ -0,0 +1,90 @@ +#pragma once + +#include "FontEngineInterfaceBitmap.h" +#include +#include +#include + +class FontFaceBitmap; +using Rml::TextureSource; + +namespace FontProviderBitmap { +void Initialise(); +void Shutdown(); +bool LoadFontFace(const String& file_name, const String& family = ""); +FontFaceBitmap* GetFontFaceHandle(const String& family, FontStyle style, FontWeight weight, int size); +} // namespace FontProviderBitmap + +struct BitmapGlyph { + int advance = 0; + Vector2f offset = {0, 0}; + Vector2f position = {0, 0}; + Vector2f dimension = {0, 0}; +}; + +// A mapping of characters to their glyphs. +using FontGlyphs = Rml::UnorderedMap; + +// Mapping of combined (left, right) character to kerning in pixels. +using FontKerning = Rml::UnorderedMap; + +class FontFaceBitmap { +public: + FontFaceBitmap(String family, FontStyle style, FontWeight weight, FontMetrics metrics, String texture_name, String texture_path, + Vector2f texture_dimensions, FontGlyphs&& glyphs, FontKerning&& kerning); + + // Get width of string. + int GetStringWidth(StringView string, Character prior_character); + + // Generate the string geometry, returning its width. + int GenerateString(RenderManager& render_manager, StringView string, Vector2f position, ColourbPremultiplied colour, TexturedMeshList& mesh_list); + + const FontMetrics& GetMetrics() const { return metrics; } + + const String& GetFamily() const { return family; } + FontStyle GetStyle() const { return style; } + FontWeight GetWeight() const { return weight; } + +private: + int GetKerning(Character left, Character right) const; + + String family; + FontStyle style; + FontWeight weight; + + FontMetrics metrics; + + TextureSource texture_source; + Vector2f texture_dimensions; + + FontGlyphs glyphs; + FontKerning kerning; +}; + +/* + Parses the font meta data from an xml file. +*/ + +class FontParserBitmap : public Rml::BaseXMLParser { +public: + FontParserBitmap() {} + virtual ~FontParserBitmap(); + + /// Called when the parser finds the beginning of an element tag. + void HandleElementStart(const String& name, const Rml::XMLAttributes& attributes) override; + /// Called when the parser finds the end of an element tag. + void HandleElementEnd(const String& name) override; + /// Called when the parser encounters data. + void HandleData(const String& data, Rml::XMLDataType type) override; + + String family; + FontStyle style = FontStyle::Normal; + FontWeight weight = FontWeight::Normal; + + String texture_name; + Vector2f texture_dimensions = {0, 0}; + + FontMetrics metrics = {}; + FontGlyphs glyphs; + FontKerning kerning; +}; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/bitmap_font/src/FontEngineInterfaceBitmap.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/bitmap_font/src/FontEngineInterfaceBitmap.cpp new file mode 100644 index 00000000..ddb61dd2 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/bitmap_font/src/FontEngineInterfaceBitmap.cpp @@ -0,0 +1,73 @@ +#include "FontEngineInterfaceBitmap.h" +#include "FontEngineBitmap.h" +#include + +void FontEngineInterfaceBitmap::Initialize() +{ + FontProviderBitmap::Initialise(); +} + +void FontEngineInterfaceBitmap::Shutdown() +{ + FontProviderBitmap::Shutdown(); +} + +bool FontEngineInterfaceBitmap::LoadFontFace(const String& file_name, int /*face_index*/, bool /*fallback_face*/, FontWeight /*weight*/) +{ + return FontProviderBitmap::LoadFontFace(file_name); +} + +bool FontEngineInterfaceBitmap::LoadFontFace(const String& file_name, int /*face_index*/, const String& font_family, FontStyle /*style*/, + FontWeight /*weight*/, bool /*fallback_face*/) +{ + return FontProviderBitmap::LoadFontFace(file_name, font_family); +} + +bool FontEngineInterfaceBitmap::LoadFontFace(Span /*data*/, int /*face_index*/, const String& font_family, FontStyle /*style*/, + FontWeight /*weight*/, bool /*fallback_face*/) +{ + // We return 'true' here to allow the debugger to continue loading, but we will use our own fonts when it asks for a handle. + // The debugger might look a bit off with our own fonts, but hey it works. + if (font_family == "rmlui-debugger-font") + return true; + + return false; +} + +FontFaceHandle FontEngineInterfaceBitmap::GetFontFaceHandle(const String& family, FontStyle style, FontWeight weight, int size) +{ + auto handle = FontProviderBitmap::GetFontFaceHandle(family, style, weight, size); + return reinterpret_cast(handle); +} + +FontEffectsHandle FontEngineInterfaceBitmap::PrepareFontEffects(FontFaceHandle /*handle*/, const FontEffectList& /*font_effects*/) +{ + // Font effects are not rendered in this implementation. + return 0; +} + +const FontMetrics& FontEngineInterfaceBitmap::GetFontMetrics(FontFaceHandle handle) +{ + auto handle_bitmap = reinterpret_cast(handle); + return handle_bitmap->GetMetrics(); +} + +int FontEngineInterfaceBitmap::GetStringWidth(FontFaceHandle handle, StringView string, const TextShapingContext& /*text_shaping_context*/, + Character prior_character) +{ + auto handle_bitmap = reinterpret_cast(handle); + return handle_bitmap->GetStringWidth(string, prior_character); +} + +int FontEngineInterfaceBitmap::GenerateString(RenderManager& render_manager, FontFaceHandle handle, FontEffectsHandle /*font_effects_handle*/, + StringView string, Vector2f position, ColourbPremultiplied colour, float /*opacity*/, const TextShapingContext& /*text_shaping_context*/, + TexturedMeshList& mesh_list) +{ + auto handle_bitmap = reinterpret_cast(handle); + return handle_bitmap->GenerateString(render_manager, string, position, colour, mesh_list); +} + +int FontEngineInterfaceBitmap::GetVersion(FontFaceHandle /*handle*/) +{ + return 0; +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/bitmap_font/src/FontEngineInterfaceBitmap.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/bitmap_font/src/FontEngineInterfaceBitmap.h new file mode 100644 index 00000000..4771b5a2 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/bitmap_font/src/FontEngineInterfaceBitmap.h @@ -0,0 +1,67 @@ +#pragma once + +#include +#include +#include + +using Rml::FontEffectsHandle; +using Rml::FontFaceHandle; + +using Rml::byte; +using Rml::Character; +using Rml::ColourbPremultiplied; +using Rml::Span; +using Rml::String; +using Rml::StringView; +using Rml::Texture; +using Rml::Vector2f; +using Rml::Vector2i; +using Rml::Style::FontStyle; +using Rml::Style::FontWeight; + +using Rml::FontEffectList; +using Rml::FontMetrics; +using Rml::RenderManager; +using Rml::TextShapingContext; +using Rml::TexturedMeshList; + +class FontEngineInterfaceBitmap : public Rml::FontEngineInterface { +public: + /// Called when RmlUi is being initialized. + void Initialize() override; + + /// Called when RmlUi is being shut down. + void Shutdown() override; + + /// Called by RmlUi when it wants to load a font face from file. + bool LoadFontFace(const String& file_name, int face_index, bool fallback_face, FontWeight weight) override; + + /// Called by RmlUi when it wants to load a font face from file, registered using the provided family, style, and weight. + bool LoadFontFace(const String& file_name, int face_index, const String& family, FontStyle style, FontWeight weight, bool fallback_face) override; + + /// Called by RmlUi when it wants to load a font face from memory, registered using the provided family, style, and weight. + /// @param[in] data A pointer to the data. + bool LoadFontFace(Span data, int face_index, const String& family, FontStyle style, FontWeight weight, bool fallback_face) override; + + /// Called by RmlUi when a font configuration is resolved for an element. Should return a handle that + /// can later be used to resolve properties of the face, and generate string geometry to be rendered. + FontFaceHandle GetFontFaceHandle(const String& family, FontStyle style, FontWeight weight, int size) override; + + /// Called by RmlUi when a list of font effects is resolved for an element with a given font face. + FontEffectsHandle PrepareFontEffects(FontFaceHandle handle, const FontEffectList& font_effects) override; + + /// Should return the font metrics of the given font face. + const FontMetrics& GetFontMetrics(FontFaceHandle handle) override; + + /// Called by RmlUi when it wants to retrieve the width of a string when rendered with this handle. + int GetStringWidth(FontFaceHandle handle, StringView string, const TextShapingContext& text_shaping_context, + Character prior_character = Character::Null) override; + + /// Called by RmlUi when it wants to retrieve the geometry required to render a single line of text. + int GenerateString(RenderManager& render_manager, FontFaceHandle face_handle, FontEffectsHandle font_effects_handle, StringView string, + Vector2f position, ColourbPremultiplied colour, float opacity, const TextShapingContext& text_shaping_context, + TexturedMeshList& mesh_list) override; + + /// Called by RmlUi to determine if the text geometry is required to be re-generated.eometry. + int GetVersion(FontFaceHandle handle) override; +}; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/bitmap_font/src/main.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/bitmap_font/src/main.cpp new file mode 100644 index 00000000..13b83311 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/bitmap_font/src/main.cpp @@ -0,0 +1,97 @@ +#include "FontEngineInterfaceBitmap.h" +#include +#include +#include +#include + +/* + + This demo shows how to create a custom bitmap font engine implementation. + + It should work even when RmlUi is compiled without the default font engine (see CMake flag 'NO_FONT_INTERFACE_DEFAULT'). + See the interface in 'FontEngineInterfaceBitmap.h' and the implementation in 'FontEngineBitmap.h'. + +*/ + +#if defined RMLUI_PLATFORM_WIN32 + #include +int APIENTRY WinMain(HINSTANCE /*instance_handle*/, HINSTANCE /*previous_instance_handle*/, char* /*command_line*/, int /*command_show*/) +#else +int main(int /*argc*/, char** /*argv*/) +#endif +{ + int window_width = 1024; + int window_height = 768; + + // Initializes the shell which provides common functionality used by the included samples. + if (!Shell::Initialize()) + return -1; + + // Constructs the system and render interfaces, creates a window, and attaches the renderer. + if (!Backend::Initialize("Bitmap Font Sample", window_width, window_height, true)) + { + Shell::Shutdown(); + return -1; + } + + // Install the custom interfaces constructed by the backend before initializing RmlUi. + Rml::SetSystemInterface(Backend::GetSystemInterface()); + Rml::SetRenderInterface(Backend::GetRenderInterface()); + + // Construct and load the font interface. + auto font_interface = Rml::MakeUnique(); + Rml::SetFontEngineInterface(font_interface.get()); + + // RmlUi initialisation. + Rml::Initialise(); + + // Create the main RmlUi context. + Rml::Context* context = Rml::CreateContext("main", Rml::Vector2i(window_width, window_height)); + if (!context) + { + Rml::Shutdown(); + Backend::Shutdown(); + Shell::Shutdown(); + return -1; + } + + Rml::Debugger::Initialise(context); + + // Load bitmap font + if (!Rml::LoadFontFace("basic/bitmap_font/data/Comfortaa_Regular_22.fnt")) + { + Rml::Shutdown(); + Backend::Shutdown(); + Shell::Shutdown(); + return -1; + } + + // Load and show the demo document. + if (Rml::ElementDocument* document = context->LoadDocument("basic/bitmap_font/data/bitmap_font.rml")) + { + if (auto el = document->GetElementById("title")) + el->SetInnerRML("Bitmap font"); + + document->Show(); + } + + bool running = true; + while (running) + { + running = Backend::ProcessEvents(context, &Shell::ProcessKeyDownShortcuts, true); + + context->Update(); + + Backend::BeginFrame(); + context->Render(); + Backend::PresentFrame(); + } + + // Shutdown RmlUi. + Rml::Shutdown(); + + Backend::Shutdown(); + Shell::Shutdown(); + + return 0; +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/custom_log/CMakeLists.txt b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/custom_log/CMakeLists.txt new file mode 100644 index 00000000..38eb7666 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/custom_log/CMakeLists.txt @@ -0,0 +1,14 @@ +set(SAMPLE_NAME "custom_log") +set(TARGET_NAME "${RMLUI_SAMPLE_PREFIX}${SAMPLE_NAME}") + +add_executable(${TARGET_NAME} WIN32 + src/main.cpp + src/SystemInterface.cpp + src/SystemInterface.h +) + +set_common_target_options(${TARGET_NAME}) + +target_link_libraries(${TARGET_NAME} PRIVATE rmlui_shell) + +install_sample_target(${TARGET_NAME}) diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/custom_log/src/SystemInterface.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/custom_log/src/SystemInterface.cpp new file mode 100644 index 00000000..f35a5ce3 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/custom_log/src/SystemInterface.cpp @@ -0,0 +1,53 @@ +#include "SystemInterface.h" +#include +#include +#include +#include +#ifdef RMLUI_PLATFORM_WIN32 + #include +#endif + +SystemInterface::SystemInterface() +{ + fp = fopen("log.txt", "wt"); +} + +SystemInterface::~SystemInterface() +{ + if (fp) + fclose(fp); +} + +bool SystemInterface::LogMessage(Rml::Log::Type type, const Rml::String& message) +{ + if (fp) + { + // Select a prefix appropriate for the severity of the message. + const char* prefix; + switch (type) + { + case Rml::Log::LT_ERROR: + case Rml::Log::LT_ASSERT: prefix = "-!-"; break; + + case Rml::Log::LT_WARNING: prefix = "-*-"; break; + + default: prefix = "---"; break; + } + + // Print the message and timestamp to file, and force a write in case of a crash. + fprintf(fp, "%s (%.2f): %s\n", prefix, GetElapsedTime(), message.c_str()); + fflush(fp); + +#ifdef RMLUI_PLATFORM_WIN32 + if (type == Rml::Log::LT_ASSERT) + { + Rml::String assert_message = Rml::CreateString("%s\nWould you like to interrupt execution?", message.c_str()); + + // Return TRUE if the user presses NO (continue execution) + return MessageBoxA(nullptr, assert_message.c_str(), "Assertion Failure", MB_YESNO | MB_ICONSTOP | MB_DEFBUTTON2 | MB_SYSTEMMODAL) == IDNO; + } +#endif + } + + return true; +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/custom_log/src/SystemInterface.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/custom_log/src/SystemInterface.h new file mode 100644 index 00000000..4c2ffc25 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/custom_log/src/SystemInterface.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +/** + Sample custom RmlUi system interface for writing log messages to file. + */ + +class SystemInterface : public Rml::SystemInterface { +public: + SystemInterface(); + virtual ~SystemInterface(); + + /// Log the specified message. + /// @param[in] type Type of log message, ERROR, WARNING, etc. + /// @param[in] message Message to log. + /// @return True to continue execution, false to break into the debugger. + bool LogMessage(Rml::Log::Type type, const Rml::String& message) override; + +private: + FILE* fp; +}; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/custom_log/src/main.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/custom_log/src/main.cpp new file mode 100644 index 00000000..1eeeed14 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/custom_log/src/main.cpp @@ -0,0 +1,86 @@ +#include "SystemInterface.h" +#include +#include +#include +#include + +#if defined RMLUI_PLATFORM_WIN32 + #include +int APIENTRY WinMain(HINSTANCE /*instance_handle*/, HINSTANCE /*previous_instance_handle*/, char* /*command_line*/, int /*command_show*/) +#else +int main(int /*argc*/, char** /*argv*/) +#endif +{ + int window_width = 1024; + int window_height = 768; + + // Initializes the shell which provides common functionality used by the included samples. + if (!Shell::Initialize()) + return -1; + + // Constructs the system and render interfaces, creates a window, and attaches the renderer. + if (!Backend::Initialize("Custom File Handler Sample", window_width, window_height, true)) + { + Shell::Shutdown(); + return -1; + } + + // Install the custom interfaces constructed by the backend before initializing RmlUi. + Rml::SetSystemInterface(Backend::GetSystemInterface()); + Rml::SetRenderInterface(Backend::GetRenderInterface()); + + // Initialize our custom system interface to write the log messages to file. + SystemInterface system_interface; + Rml::SetSystemInterface(&system_interface); + + // RmlUi initialisation. + Rml::Initialise(); + + // Create the main RmlUi context. + Rml::Context* context = Rml::CreateContext("main", Rml::Vector2i(window_width, window_height)); + if (!context) + { + Rml::Shutdown(); + Backend::Shutdown(); + Shell::Shutdown(); + return -1; + } + + Rml::Debugger::Initialise(context); + Shell::LoadFonts(); + + // Load a non-existent document to spawn an error message. + Rml::ElementDocument* invalid_document = context->LoadDocument("assets/invalid.rml"); + RMLUI_ASSERTMSG(invalid_document, "Testing ASSERT logging."); + if (invalid_document) + { + invalid_document->Close(); + } + + // Load and show the demo document. + Rml::ElementDocument* document = context->LoadDocument("assets/demo.rml"); + if (document != nullptr) + { + document->Show(); + } + + bool running = true; + while (running) + { + running = Backend::ProcessEvents(context, &Shell::ProcessKeyDownShortcuts); + + context->Update(); + + Backend::BeginFrame(); + context->Render(); + Backend::PresentFrame(); + } + + // Shutdown RmlUi. + Rml::Shutdown(); + + Backend::Shutdown(); + Shell::Shutdown(); + + return 0; +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/data_binding/CMakeLists.txt b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/data_binding/CMakeLists.txt new file mode 100644 index 00000000..068749fb --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/data_binding/CMakeLists.txt @@ -0,0 +1,12 @@ +set(SAMPLE_NAME "data_binding") +set(TARGET_NAME "${RMLUI_SAMPLE_PREFIX}${SAMPLE_NAME}") + +add_executable(${TARGET_NAME} WIN32 + src/main.cpp +) + +set_common_target_options(${TARGET_NAME}) + +target_link_libraries(${TARGET_NAME} PRIVATE rmlui_shell) + +install_sample_target(${TARGET_NAME}) diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/data_binding/data/data_binding.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/data_binding/data/data_binding.rml new file mode 100644 index 00000000..3650ba5a --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/data_binding/data/data_binding.rml @@ -0,0 +1,404 @@ + + + +Data Binding Sample + + + + + +Basics + +

{{title}}

+

The quick brown fox jumps over the lazy {{animal}}.

+ +
+Events + +

{{hello_world}} Rated: {{rating}}

+

Data binding demo. We rate this a good old {{rating}}!

+ +
Thanks for the goodawesome rating!
+
+
+
+ Recorded mouse positions.
+ x: {{ pos.x }}, y: {{ pos.y }}
+
+ +

+ For loop with data expressions:
+ {{ i * 2 + (!(i < 10) ? ' wow!' | to_upper : '') }} +

+
+Invaders + + + + + +
+ + + + + {{ invader.name }} + +
+
+ Do you dare to take on the fight against the alien invaders?
+ +
+
+ Game Over!
+ +
+
+Forms + +
+

Rating

+
+ {{rating}} +
+

Favorite animal: {{ animal }}

+
+ + + + +
+

Favorite meals

+
+ + + +
  • • You can only have pizza if the rating is satisfactory.
  • +
  • • Pizza is life!
  • +
  • • Pasta is squiggly good!
  • +
  • • Lasagne is delicious!
  • +
    +

    Subject

    +
    + +
    +

    + Select subject by index
    + {{selected_subject + 1}} +

    +
    +

    + {{it_index + 1}}: {{ s }}  + [-] +

    +
    +
    +
    + +
    +
    +
    +
    +
    + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/data_binding/src/main.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/data_binding/src/main.cpp new file mode 100644 index 00000000..2af4a0c6 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/data_binding/src/main.cpp @@ -0,0 +1,503 @@ +#include +#include +#include +#include +#include + +namespace { +namespace BasicExample { + + Rml::DataModelHandle model_handle; + + struct MyData { + Rml::String title = "Simple data binding example"; + Rml::String animal = "dog"; + bool show_text = true; + } my_data; + + bool Initialize(Rml::Context* context) + { + Rml::DataModelConstructor constructor = context->CreateDataModel("basics"); + if (!constructor) + return false; + + constructor.Bind("title", &my_data.title); + constructor.Bind("animal", &my_data.animal); + constructor.Bind("show_text", &my_data.show_text); + + model_handle = constructor.GetModelHandle(); + + return true; + } +} // namespace BasicExample + +namespace EventsExample { + + Rml::DataModelHandle model_handle; + + struct MyData { + Rml::String hello_world = "Hello World!"; + Rml::String mouse_detector = "Mouse-move Detector."; + int rating = 99; + + Rml::Vector list = {1, 2, 3, 4, 5}; + + Rml::Vector positions; + + void AddMousePos(Rml::DataModelHandle model, Rml::Event& ev, const Rml::VariantList& /*arguments*/) + { + positions.emplace_back(ev.GetParameter("mouse_x", 0.f), ev.GetParameter("mouse_y", 0.f)); + model.DirtyVariable("positions"); + } + + } my_data; + + void ClearPositions(Rml::DataModelHandle model, Rml::Event& /*ev*/, const Rml::VariantList& /*arguments*/) + { + my_data.positions.clear(); + model.DirtyVariable("positions"); + } + + void HasGoodRating(Rml::Variant& variant) + { + variant = int(my_data.rating > 50); + } + + bool Initialize(Rml::Context* context) + { + using namespace Rml; + DataModelConstructor constructor = context->CreateDataModel("events"); + if (!constructor) + return false; + + // Register all the types first + constructor.RegisterArray>(); + + if (auto vec2_handle = constructor.RegisterStruct()) + { + vec2_handle.RegisterMember("x", &Vector2f::x); + vec2_handle.RegisterMember("y", &Vector2f::y); + } + constructor.RegisterArray>(); + + // Bind the variables to the data model + constructor.Bind("hello_world", &my_data.hello_world); + constructor.Bind("mouse_detector", &my_data.mouse_detector); + constructor.Bind("rating", &my_data.rating); + constructor.BindFunc("good_rating", &HasGoodRating); + constructor.BindFunc("great_rating", [](Variant& variant) { variant = int(my_data.rating > 80); }); + + constructor.Bind("list", &my_data.list); + + constructor.Bind("positions", &my_data.positions); + + constructor.BindEventCallback("clear_positions", &ClearPositions); + constructor.BindEventCallback("add_mouse_pos", &MyData::AddMousePos, &my_data); + + model_handle = constructor.GetModelHandle(); + + return true; + } + + void Update() + { + if (model_handle.IsVariableDirty("rating")) + { + model_handle.DirtyVariable("good_rating"); + model_handle.DirtyVariable("great_rating"); + + size_t new_size = my_data.rating / 10 + 1; + if (new_size != my_data.list.size()) + { + my_data.list.resize(new_size); + std::iota(my_data.list.begin(), my_data.list.end(), float(new_size)); + model_handle.DirtyVariable("list"); + } + } + } +} // namespace EventsExample + +namespace InvadersExample { + + Rml::DataModelHandle model_handle; + + static constexpr int num_invaders = 12; + static constexpr double incoming_invaders_rate = 50; // Per minute + + struct Invader { + Rml::String name; + Rml::String sprite; + Rml::Colourb color{255, 255, 255}; + float max_health = 0; + float charge_rate = 0; + float health = 0; + float charge = 0; + }; + + struct InvadersData { + float health = 0; + float charge = 0; + int score = 0; + + double elapsed_time = 0; + double next_invader_spawn_time = 0; + + int num_games_played = 0; + + Rml::Array invaders; + + // Start a new game. + void StartGame(Rml::DataModelHandle model, Rml::Event& /*ev*/, const Rml::VariantList& /*arguments*/) + { + health = 100; + charge = 30; + score = 0; + elapsed_time = 0; + next_invader_spawn_time = 0; + num_games_played += 1; + + for (Invader& invader : invaders) + invader.health = 0; + + model.DirtyVariable("health"); + model.DirtyVariable("charge"); + model.DirtyVariable("score"); + model.DirtyVariable("elapsed_time"); + model.DirtyVariable("num_games_played"); + model.DirtyVariable("invaders"); + } + + // Fire on the invader of the given index (first argument). + void Fire(Rml::DataModelHandle model, Rml::Event& /*ev*/, const Rml::VariantList& arguments) + { + if (arguments.size() != 1) + return; + const size_t index = arguments[0].Get(); + if (index >= invaders.size()) + return; + + Invader& invader = invaders[index]; + if (health <= 0 || invader.health <= 0) + return; + + const float new_health = Rml::Math::Max(invader.health - charge * Rml::Math::SquareRoot(charge), 0.0f); + + charge = 30.f; + score += int(invader.health - new_health) + 1000 * (new_health == 0); + + invader.health = new_health; + + model.DirtyVariable("invaders"); + model.DirtyVariable("charge"); + model.DirtyVariable("score"); + } + + } data; + + bool Initialize(Rml::Context* context) + { + Rml::DataModelConstructor constructor = context->CreateDataModel("invaders"); + if (!constructor) + return false; + + // Register a custom getter for the Colourb type. + constructor.RegisterScalar([](const Rml::Colourb& color, Rml::Variant& variant) { variant = Rml::ToString(color); }); + // Register a transform function for formatting time + constructor.RegisterTransformFunc("format_time", [](const Rml::VariantList& arguments) -> Rml::Variant { + if (arguments.empty()) + return {}; + const double t = arguments[0].Get(); + const int minutes = int(t) / 60; + const double seconds = t - 60.0 * double(minutes); + return Rml::Variant(Rml::CreateString("%02d:%05.2f", minutes, seconds)); + }); + + // Structs are registered by adding all their members through the returned handle. + if (auto invader_handle = constructor.RegisterStruct()) + { + invader_handle.RegisterMember("name", &Invader::name); + invader_handle.RegisterMember("sprite", &Invader::sprite); + invader_handle.RegisterMember("color", &Invader::color); + invader_handle.RegisterMember("max_health", &Invader::max_health); + invader_handle.RegisterMember("charge_rate", &Invader::charge_rate); + invader_handle.RegisterMember("health", &Invader::health); + invader_handle.RegisterMember("charge", &Invader::charge); + } + + // We can even have an Array of Structs, infinitely nested if we so desire. + // Make sure the underlying type (here Invader) is registered before the array. + constructor.RegisterArray(); + + // Now we can bind the variables to the model. + constructor.Bind("invaders", &data.invaders); + constructor.Bind("health", &data.health); + constructor.Bind("charge", &data.charge); + constructor.Bind("score", &data.score); + constructor.Bind("elapsed_time", &data.elapsed_time); + constructor.Bind("num_games_played", &data.num_games_played); + + // This function will be called when the user clicks on the (re)start game button. + constructor.BindEventCallback("start_game", &InvadersData::StartGame, &data); + // This function will be called when the user clicks on any of the invaders. + constructor.BindEventCallback("fire", &InvadersData::Fire, &data); + + model_handle = constructor.GetModelHandle(); + + return true; + } + + void Update(const double dt) + { + using namespace Rml; + + if (data.health == 0) + return; + + data.elapsed_time += dt; + model_handle.DirtyVariable("elapsed_time"); + + // Steadily increase the player charge. + data.charge = Math::Min(data.charge + float(40.0 * dt), 100.f); + model_handle.DirtyVariable("charge"); + + // Add new invaders at the scheduled time. + if (data.elapsed_time >= data.next_invader_spawn_time) + { + constexpr int num_items = 4; + static Array names = {"Angry invader", "Harmless invader", "Deceitful invader", "Cute invader"}; + static Array sprites = {"icon-invader", "icon-flag", "icon-game", "icon-waves"}; + static Array colors = {{{255, 40, 30}, {20, 40, 255}, {255, 255, 30}, {230, 230, 230}}}; + + Invader new_invader; + new_invader.name = names[Math::RandomInteger(num_items)]; + new_invader.sprite = sprites[Math::RandomInteger(num_items)]; + new_invader.color = colors[Math::RandomInteger(num_items)]; + + new_invader.max_health = 300.f + float(30.0 * data.elapsed_time) + Math::RandomReal(300.f); + new_invader.charge_rate = 10.f + Math::RandomReal(50.f); + + new_invader.health = new_invader.max_health; + + // Find an available slot to spawn the new invader in. + const int i_begin = Math::RandomInteger(num_invaders); + for (int i = 0; i < num_invaders; i++) + { + Invader& invader = data.invaders[(i + i_begin) % num_invaders]; + if (invader.health <= 0) + { + invader = std::move(new_invader); + model_handle.DirtyVariable("invaders"); + break; + } + } + + // Add new invaders at steadily decreasing time intervals. + data.next_invader_spawn_time = data.elapsed_time + 60.0 / (incoming_invaders_rate + 0.1 * data.elapsed_time); + } + + // Iterate through the invaders and fire at the player. + for (Invader& invader : data.invaders) + { + if (invader.health > 0) + { + invader.charge = invader.charge + invader.charge_rate * float(dt); + + if (invader.charge >= 100) + { + data.health = Math::Max(data.health - float(10.0 * dt), 0.0f); + model_handle.DirtyVariable("health"); + } + + if (invader.charge >= 120) + invader.charge = 0; + + model_handle.DirtyVariable("invaders"); + } + } + } +} // namespace InvadersExample + +namespace FormsExample { + + Rml::DataModelHandle model_handle; + + struct MyData { + int rating = 50; + bool pizza = true; + bool pasta = false; + bool lasagne = false; + Rml::String animal = "dog"; + Rml::Vector subjects = {"Choose your subject", "Feature request", "Bug report", "Praise", "Criticism"}; + int selected_subject = 0; + Rml::String new_subject = "New subject"; + } my_data; + + bool Initialize(Rml::Context* context) + { + Rml::DataModelConstructor constructor = context->CreateDataModel("forms"); + if (!constructor) + return false; + + constructor.RegisterArray>(); + + constructor.Bind("rating", &my_data.rating); + constructor.Bind("pizza", &my_data.pizza); + constructor.Bind("pasta", &my_data.pasta); + constructor.Bind("lasagne", &my_data.lasagne); + constructor.Bind("animal", &my_data.animal); + constructor.Bind("subjects", &my_data.subjects); + constructor.Bind("selected_subject", &my_data.selected_subject); + constructor.Bind("new_subject", &my_data.new_subject); + + constructor.BindEventCallback("add_subject", [](Rml::DataModelHandle model, Rml::Event& /*ev*/, const Rml::VariantList& arguments) { + Rml::String name = (arguments.size() == 1 ? arguments[0].Get() : ""); + if (!name.empty()) + { + my_data.subjects.push_back(std::move(name)); + model.DirtyVariable("subjects"); + } + }); + constructor.BindEventCallback("erase_subject", [](Rml::DataModelHandle model, Rml::Event& /*ev*/, const Rml::VariantList& arguments) { + const int i = (arguments.size() == 1 ? arguments[0].Get(-1) : -1); + if (i >= 0 && i < (int)my_data.subjects.size()) + { + my_data.subjects.erase(my_data.subjects.begin() + i); + my_data.selected_subject = 0; + model.DirtyVariable("subjects"); + model.DirtyVariable("selected_subject"); + } + }); + + model_handle = constructor.GetModelHandle(); + + return true; + } +} // namespace FormsExample +} // namespace + +class DemoWindow : public Rml::EventListener { +public: + DemoWindow(const Rml::String& title, Rml::Context* context) + { + document = context->LoadDocument("basic/data_binding/data/data_binding.rml"); + if (document) + { + document->GetElementById("title")->SetInnerRML(title); + document->Show(); + } + } + + void Shutdown() + { + if (document) + { + document->Close(); + document = nullptr; + } + } + + void ProcessEvent(Rml::Event& event) override + { + switch (event.GetId()) + { + case Rml::EventId::Keydown: + { + Rml::Input::KeyIdentifier key_identifier = (Rml::Input::KeyIdentifier)event.GetParameter("key_identifier", 0); + + if (key_identifier == Rml::Input::KI_ESCAPE) + Backend::RequestExit(); + } + break; + default: break; + } + } + + Rml::ElementDocument* GetDocument() { return document; } + +private: + Rml::ElementDocument* document = nullptr; +}; + +#if defined RMLUI_PLATFORM_WIN32 + #include +int APIENTRY WinMain(HINSTANCE /*instance_handle*/, HINSTANCE /*previous_instance_handle*/, char* /*command_line*/, int /*command_show*/) +#else +int main(int /*argc*/, char** /*argv*/) +#endif +{ + const int width = 1600; + const int height = 900; + + // Initializes the shell which provides common functionality used by the included samples. + if (!Shell::Initialize()) + return -1; + + // Constructs the system and render interfaces, creates a window, and attaches the renderer. + if (!Backend::Initialize("Data Binding Sample", width, height, true)) + { + Shell::Shutdown(); + return -1; + } + + // Install the custom interfaces constructed by the backend before initializing RmlUi. + Rml::SetSystemInterface(Backend::GetSystemInterface()); + Rml::SetRenderInterface(Backend::GetRenderInterface()); + + // RmlUi initialisation. + Rml::Initialise(); + + // Create the main RmlUi context. + Rml::Context* context = Rml::CreateContext("main", Rml::Vector2i(width, height)); + + if (!context // + || !BasicExample::Initialize(context) // + || !EventsExample::Initialize(context) // + || !InvadersExample::Initialize(context) // + || !FormsExample::Initialize(context) // + ) + { + Rml::Shutdown(); + Backend::Shutdown(); + Shell::Shutdown(); + return -1; + } + + Rml::Debugger::Initialise(context); + Shell::LoadFonts(); + + auto demo_window = Rml::MakeUnique("Data binding", context); + demo_window->GetDocument()->AddEventListener(Rml::EventId::Keydown, demo_window.get()); + demo_window->GetDocument()->AddEventListener(Rml::EventId::Keyup, demo_window.get()); + + double t_prev = 0; + bool running = true; + while (running) + { + running = Backend::ProcessEvents(context, &Shell::ProcessKeyDownShortcuts); + + const double t = Rml::GetSystemInterface()->GetElapsedTime(); + const double dt = Rml::Math::Min(t - t_prev, 0.1); + t_prev = t; + + EventsExample::Update(); + InvadersExample::Update(dt); + + context->Update(); + + Backend::BeginFrame(); + context->Render(); + Backend::PresentFrame(); + } + + demo_window->Shutdown(); + + // Shutdown RmlUi. + Rml::Shutdown(); + + Backend::Shutdown(); + Shell::Shutdown(); + + demo_window.reset(); + + return 0; +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/demo/CMakeLists.txt b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/demo/CMakeLists.txt new file mode 100644 index 00000000..3ea227bb --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/demo/CMakeLists.txt @@ -0,0 +1,21 @@ +set(SAMPLE_NAME "demo") +set(TARGET_NAME "${RMLUI_SAMPLE_PREFIX}${SAMPLE_NAME}") + +add_executable(${TARGET_NAME} WIN32 + src/main.cpp + src/DemoEventListener.cpp + src/DemoEventListener.h + src/DemoWindow.cpp + src/DemoWindow.h +) + +set_common_target_options(${TARGET_NAME}) + +target_link_libraries(${TARGET_NAME} PRIVATE rmlui_shell) + +install_sample_target(${TARGET_NAME}) + +if(MSVC) + # Set UTF-8 on MSVC to properly encode emoji characters. + target_compile_options(${TARGET_NAME} PRIVATE /utf-8) +endif() diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/demo/data/demo.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/demo/data/demo.rml new file mode 100644 index 00000000..598c225b --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/demo/data/demo.rml @@ -0,0 +1,937 @@ + + + +Demo Sample + + + + + + +Welcome + +

    RmlUi 😍

    +

    Have fun fiddling about in this demo.

    +

    Press 'F8' to open up the debugger.

    +

    🎉

    +
    +Decorators + +

    Gradient decorator

    +

    The 'gradient' decorator renders a color gradient in the vertical or horizontal direction.

    +
    + + +
    + +

    Image decorator

    +

    The 'image' decorator applies an image to the background of an element. By default it stretches the image to the size of the element, but this behavior can be controlled as seen later on.

    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    + +

    Tiled-horizontal decorator

    +

    The 'tiled-horizontal' decorator separates an image into three parts, with the edges displayed at their native size while the middle part is stretched to cover the remaining width of the element. In each of the following examples an image is displayed at its native size first, and then the decorator is applied to it with its element stretched horizontally.

    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    In the third example we show the same title bar used to style the sample windows.

    + + +

    Tiled-vertical decorator

    +

    The 'tiled-vertical' decorator behaves like tiled-horizontal, but this time it is stretched vertically.

    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    Clearly, not all images are designed to stretch like this. However, one can certainly envision a usage for the first two examples.

    + +

    Tiled-box decorator

    +

    The 'tiled-box' decorator lays out a 3x3 grid within the element, each of the nine parts being filled with a separate sprite or image. The corners stay fixed in size, while the center and edges are stretched to fill the remaining size of the element.

    +

    The following image displays the region used to define the background of the window in this sample.

    + +

    The decorator's element can then gracefully be sized in any direction, keeping the corners and edges at a proper size.

    +
    + +

    Ninepatch decorator

    +

    The 'ninepatch' decorator is similar to the tiled-box decorator in that it splits the image into nine parts. However, only two sprites are used to define all nine tiles, an outer sprite and an inner sprite.

    + +

    Let us define the outer sprite by the following image, and the inner sprite as the part within the displayed border.

    +
    + +
    + +

    When the ninepatch decorator is applied and the element is stretched, the following is rendered.

    +
    +

    Notice that the corners stay fixed, and the inner sprite is stretched. We can also control the rendered size of the edges, here the left edge is displayed at its native size and the others at half their size.

    +
    + +

    If we instead compare this to the image decorator, we see the following.

    +
    +

    With the image decorator it looks blurry and its borders are stretched.

    + +
    +

    Image decorator fit modes

    +
    +

    fill

    +

    contain

    +

    cover

    +

    scale-none

    +

    scale-down

    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    repeat

    +

    repeat-x

    +

    repeat-y

    +
    +

    Image decorator alignment modes

    +
    +

    default

    +

    right

    +

    left bottom

    +

    70% 30%

    +

    100px -15px

    +

    Using fit mode 'scale-none'.

    +
    +

    Tiled decorators orientation

    +

    Orientation can be applied to every tile individually in the 'image', 'tiled-horizontal', 'tiled-vertical', and 'tiled-box' decorators.

    +
    +

    default

    +

    flip-vertical

    +

    flip-horizontal

    +

    rotate-180

    +
    +
    + +

    Image elements

    +
    + + + + +
    +

    The 'img' elements are separate from decorators, but we describe them here for completeness. They can be declared as in html. Additionally, a 'rect' attribute can be used to crop the image. The element can also display sprites declared in a sprite sheet.

    + +

    Sprite sheets

    +

    Sprite sheets are defined by an image source and a collection of sprites, and can be declared in RCSS. Sprites are essentially rectangles into the given image. For example, the following image

    + +

    is used to render most sprites in this demo. Sprites can be used in decorators and image elements as if they were separate images.

    + +Font effects + +

    None

    +
    RmlUi 😍
    +

    Glow

    +
    RmlUi 😍
    +
    RmlUi 😍
    +
    RmlUi 😍
    +
    RmlUi 😍
    +

    Outline

    +
    RmlUi 😍
    +
    RmlUi 😍
    +

    Shadow

    +
    RmlUi 😍
    +
    RmlUi 😍
    +

    Blur

    +
    RmlUi 😍
    +
    RmlUi 😍
    +
    +Animations + + +

    Transitions

    +

    Hover over the following to see an animation performed by the transition property in RCSS.

    +
    + + + +
    +
    +
    + +

    Tweening functions

    +

    Set animation parameters below and click on the background or the flag.

    +
    + +
    + + +
    + + Duration 0.50 s +
    + +

    Cube

    +

    The cube is transformed and animated entirely in RCSS using the @keyframes at-rule.

    +
    +
    +
    6
    +
    5
    +
    4
    +
    3
    +
    2
    +
    1
    +
    +
    + +Forms + +
    +

    Full name

    +
    + +
    +

    Email and password

    +
    + + +
    +

    Favorite animal

    +
    + + + + +
    +

    Favorite meals

    +
    + + + +
    +

    Rating

    +
    +   +
    +

    Subject

    +
    + +
    +

    Message

    +
    + +
    +
    + Submit +
    + +
    +
    +Sandbox + + + + + + + + + + + + + + + + +
    +
    + + + diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/demo/src/DemoEventListener.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/demo/src/DemoEventListener.cpp new file mode 100644 index 00000000..1082c0cd --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/demo/src/DemoEventListener.cpp @@ -0,0 +1,179 @@ +#include "DemoEventListener.h" +#include "DemoWindow.h" +#include +#include +#include +#include + +DemoEventListener::DemoEventListener(const Rml::String& value, Rml::Element* element, DemoWindow* demo_window) : + value(value), element(element), demo_window(demo_window) +{} + +void DemoEventListener::ProcessEvent(Rml::Event& event) +{ + using namespace Rml; + + if (value == "change_color") + { + const TweeningParameters tweening_parameters = demo_window->GetTweeningParameters(); + const Colourb color((byte)Math::RandomInteger(255), (byte)Math::RandomInteger(255), (byte)Math::RandomInteger(255)); + + element->Animate("image-color", Property(color, Unit::COLOUR), tweening_parameters.duration, + Tween(tweening_parameters.type, tweening_parameters.direction)); + + event.StopPropagation(); + } + else if (value == "move_child") + { + const Vector2f mouse_pos = {event.GetParameter("mouse_x", 0.0f), event.GetParameter("mouse_y", 0.0f)}; + if (Element* child = element->GetFirstChild()) + { + Vector2f new_pos = mouse_pos - element->GetAbsoluteOffset() - Vector2f(0.35f * child->GetClientWidth(), 0.9f * child->GetClientHeight()); + Property destination = Transform::MakeProperty({Transforms::Translate2D(new_pos.x, new_pos.y)}); + + const TweeningParameters tweening_parameters = demo_window->GetTweeningParameters(); + if (tweening_parameters.duration <= 0) + child->SetProperty(PropertyId::Transform, destination); + else + child->Animate("transform", destination, tweening_parameters.duration, + Tween(tweening_parameters.type, tweening_parameters.direction)); + } + } + else if (value == "tween_function") + { + static const SmallUnorderedMap tweening_functions = { + {"back", Tween::Back}, + {"bounce", Tween::Bounce}, + {"circular", Tween::Circular}, + {"cubic", Tween::Cubic}, + {"elastic", Tween::Elastic}, + {"exponential", Tween::Exponential}, + {"linear", Tween::Linear}, + {"quadratic", Tween::Quadratic}, + {"quartic", Tween::Quartic}, + {"quintic", Tween::Quintic}, + {"sine", Tween::Sine}, + }; + + const String value = event.GetParameter("value", String()); + auto it = tweening_functions.find(value); + if (it != tweening_functions.end()) + { + TweeningParameters tweening_parameters = demo_window->GetTweeningParameters(); + tweening_parameters.type = it->second; + demo_window->SetTweeningParameters(tweening_parameters); + } + else + { + RMLUI_ERROR; + } + } + else if (value == "tween_direction") + { + const String value = event.GetParameter("value", String()); + TweeningParameters tweening_parameters = demo_window->GetTweeningParameters(); + if (value == "in") + tweening_parameters.direction = Tween::In; + else if (value == "out") + tweening_parameters.direction = Tween::Out; + else if (value == "in-out") + tweening_parameters.direction = Tween::InOut; + else + { + RMLUI_ERROR; + } + demo_window->SetTweeningParameters(tweening_parameters); + } + else if (value == "tween_duration") + { + const float value = (float)std::atof(rmlui_static_cast(element)->GetValue().c_str()); + + TweeningParameters tweening_parameters = demo_window->GetTweeningParameters(); + tweening_parameters.duration = value; + demo_window->SetTweeningParameters(tweening_parameters); + + if (auto el_duration = element->GetElementById("duration")) + el_duration->SetInnerRML(CreateString("%2.2f", value)); + } + else if (value == "rating") + { + auto el_rating = element->GetElementById("rating"); + auto el_rating_emoji = element->GetElementById("rating_emoji"); + if (el_rating && el_rating_emoji) + { + enum { Sad, Mediocre, Exciting, Celebrate, Champion, CountEmojis }; + static const char* emojis[CountEmojis] = {"😢", "😐", "😮", "😎", "🏆"}; + int value = event.GetParameter("value", 50); + + Rml::String emoji; + if (value <= 0) + emoji = emojis[Sad]; + else if (value < 50) + emoji = emojis[Mediocre]; + else if (value < 75) + emoji = emojis[Exciting]; + else if (value < 100) + emoji = emojis[Celebrate]; + else + emoji = emojis[Champion]; + + el_rating->SetInnerRML(Rml::CreateString("%d%%", value)); + el_rating_emoji->SetInnerRML(emoji); + } + } + else if (value == "submit_form") + { + const auto& p = event.GetParameters(); + Rml::String output = "

    "; + for (auto& entry : p) + { + auto value = Rml::StringUtilities::EncodeRml(entry.second.Get()); + if (entry.first == "message") + value = "
    " + value; + output += "" + entry.first + ": " + value + "
    "; + } + output += "

    "; + + demo_window->SubmitForm(output); + } + else if (value == "set_sandbox_body") + { + if (auto source = rmlui_dynamic_cast(element->GetElementById("sandbox_rml_source"))) + { + auto value = source->GetValue(); + demo_window->SetSandboxBody(value); + } + } + else if (value == "set_sandbox_style") + { + if (auto source = rmlui_dynamic_cast(element->GetElementById("sandbox_rcss_source"))) + { + auto value = source->GetValue(); + demo_window->SetSandboxStylesheet(value); + } + } + else if (value == "cancel_selection_on_escape") + { + if (event.GetParameter("key_identifier", 0) == Input::KeyIdentifier ::KI_ESCAPE) + { + auto select_control = rmlui_dynamic_cast(element); + if (select_control && select_control->IsSelectBoxVisible()) + { + select_control->CancelSelectBox(); + event.StopPropagation(); + } + } + } +} + +void DemoEventListener::OnDetach(Rml::Element* /*element*/) +{ + delete this; +} + +DemoEventListenerInstancer::DemoEventListenerInstancer(DemoWindow* demo_window) : demo_window(demo_window) {} + +Rml::EventListener* DemoEventListenerInstancer::InstanceEventListener(const Rml::String& value, Rml::Element* element) +{ + return new DemoEventListener(value, element, demo_window); +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/demo/src/DemoEventListener.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/demo/src/DemoEventListener.h new file mode 100644 index 00000000..42fc70ff --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/demo/src/DemoEventListener.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include + +class DemoWindow; + +class DemoEventListener : public Rml::EventListener { +public: + DemoEventListener(const Rml::String& value, Rml::Element* element, DemoWindow* demo_window); + + void ProcessEvent(Rml::Event& event) override; + + void OnDetach(Rml::Element* element) override; + +private: + Rml::String value; + Rml::Element* element; + DemoWindow* demo_window; +}; + +class DemoEventListenerInstancer : public Rml::EventListenerInstancer { +public: + DemoEventListenerInstancer(DemoWindow* demo_window); + + Rml::EventListener* InstanceEventListener(const Rml::String& value, Rml::Element* element) override; + +private: + DemoWindow* demo_window; +}; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/demo/src/DemoWindow.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/demo/src/DemoWindow.cpp new file mode 100644 index 00000000..28042025 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/demo/src/DemoWindow.cpp @@ -0,0 +1,220 @@ +#include "DemoWindow.h" +#include "RmlUi/Core/StreamMemory.h" +#include +#include +#include +#include +#include +#include + +static const Rml::String g_sandbox_default_rcss = R"( +body { top: 0; left: 0; right: 0; bottom: 0; overflow: hidden auto; } +scrollbarvertical { width: 15px; } +scrollbarvertical slidertrack { background: #eee; } +scrollbarvertical slidertrack:active { background: #ddd; } +scrollbarvertical sliderbar { width: 15px; min-height: 30px; background: #aaa; } +scrollbarvertical sliderbar:hover { background: #888; } +scrollbarvertical sliderbar:active { background: #666; } +scrollbarhorizontal { height: 15px; } +scrollbarhorizontal slidertrack { background: #eee; } +scrollbarhorizontal slidertrack:active { background: #ddd; } +scrollbarhorizontal sliderbar { height: 15px; min-width: 30px; background: #aaa; } +scrollbarhorizontal sliderbar:hover { background: #888; } +scrollbarhorizontal sliderbar:active { background: #666; } +)"; + +bool DemoWindow::Initialize(const Rml::String& title, Rml::Context* context) +{ + using namespace Rml; + + document = context->LoadDocument("basic/demo/data/demo.rml"); + if (!document) + return false; + + document->GetElementById("title")->SetInnerRML(title); + + // Add sandbox default text. + if (auto source = rmlui_dynamic_cast(document->GetElementById("sandbox_rml_source"))) + { + auto value = source->GetValue(); + value += "

    Write your RML here

    \n\n"; + source->SetValue(value); + } + + // Prepare sandbox document. + if (auto target = document->GetElementById("sandbox_target")) + { + iframe = context->CreateDocument(); + auto iframe_ptr = iframe->GetParentNode()->RemoveChild(iframe); + target->AppendChild(std::move(iframe_ptr)); + iframe->SetProperty(PropertyId::Position, Property(Style::Position::Absolute)); + iframe->SetProperty(PropertyId::Display, Property(Style::Display::Block)); + iframe->SetInnerRML("

    Rendered output goes here.

    "); + + // Load basic RML style sheet + Rml::String style_sheet_content; + { + // Load file into string + auto file_interface = Rml::GetFileInterface(); + Rml::FileHandle handle = file_interface->Open("assets/rml.rcss"); + + size_t length = file_interface->Length(handle); + style_sheet_content.resize(length); + file_interface->Read((void*)style_sheet_content.data(), length, handle); + file_interface->Close(handle); + + style_sheet_content += g_sandbox_default_rcss; + } + + Rml::StreamMemory stream((Rml::byte*)style_sheet_content.data(), style_sheet_content.size()); + stream.SetSourceURL("sandbox://default_rcss"); + + rml_basic_style_sheet = MakeShared(); + rml_basic_style_sheet->LoadStyleSheetContainer(&stream); + } + + // Add sandbox style sheet text. + if (auto source = rmlui_dynamic_cast(document->GetElementById("sandbox_rcss_source"))) + { + Rml::String value = "/* Write your RCSS here */\n\n/* body { color: #fea; background: #224; }\nimg { image-color: red; } */"; + source->SetValue(value); + SetSandboxStylesheet(value); + } + + gauge = document->GetElementById("gauge"); + progress_horizontal = document->GetElementById("progress_horizontal"); + + document->Show(); + + return true; +} + +void DemoWindow::Shutdown() +{ + if (document) + { + document->Close(); + document = nullptr; + } +} + +void DemoWindow::Update() +{ + if (iframe) + iframe->UpdateDocument(); + + if (submitting && gauge && progress_horizontal) + { + using namespace Rml; + constexpr float progressbars_time = 2.f; + const float progress = Math::Min(float(GetSystemInterface()->GetElapsedTime() - submitting_start_time) / progressbars_time, 2.f); + + float value_gauge = 1.0f; + float value_horizontal = 0.0f; + if (progress < 1.0f) + value_gauge = 0.5f - 0.5f * Math::Cos(Math::RMLUI_PI * progress); + else + value_horizontal = 0.5f - 0.5f * Math::Cos(Math::RMLUI_PI * (progress - 1.0f)); + + progress_horizontal->SetAttribute("value", value_horizontal); + + const float value_begin = 0.09f; + const float value_end = 1.f - value_begin; + float value_mapped = value_begin + value_gauge * (value_end - value_begin); + gauge->SetAttribute("value", value_mapped); + + auto value_gauge_str = CreateString("%d %%", Math::RoundToInteger(value_gauge * 100.f)); + auto value_horizontal_str = CreateString("%d %%", Math::RoundToInteger(value_horizontal * 100.f)); + + if (auto el_value = document->GetElementById("gauge_value")) + el_value->SetInnerRML(value_gauge_str); + if (auto el_value = document->GetElementById("progress_value")) + el_value->SetInnerRML(value_horizontal_str); + + String label = "Placing tubes"; + size_t num_dots = (size_t(progress * 10.f) % 4); + if (progress > 1.0f) + label += "... Placed! Assembling message"; + if (progress < 2.0f) + label += String(num_dots, '.'); + else + label += "... Done!"; + + if (auto el_label = document->GetElementById("progress_label")) + el_label->SetInnerRML(label); + + if (progress >= 2.0f) + { + submitting = false; + if (auto el_output = document->GetElementById("form_output")) + el_output->SetInnerRML(submit_message); + } + + document->GetContext()->RequestNextUpdate(.0); + } +} + +void DemoWindow::ProcessEvent(Rml::Event& event) +{ + using namespace Rml; + + switch (event.GetId()) + { + case EventId::Keydown: + { + Rml::Input::KeyIdentifier key_identifier = (Rml::Input::KeyIdentifier)event.GetParameter("key_identifier", 0); + + if (key_identifier == Rml::Input::KI_ESCAPE) + Backend::RequestExit(); + } + break; + + default: break; + } +} + +Rml::ElementDocument* DemoWindow::GetDocument() +{ + return document; +} + +void DemoWindow::SubmitForm(Rml::String in_submit_message) +{ + submitting = true; + submitting_start_time = Rml::GetSystemInterface()->GetElapsedTime(); + submit_message = in_submit_message; + if (auto el_output = document->GetElementById("form_output")) + el_output->SetInnerRML(""); + if (auto el_progress = document->GetElementById("submit_progress")) + el_progress->SetProperty("display", "block"); +} + +void DemoWindow::SetSandboxStylesheet(const Rml::String& string) +{ + if (iframe && rml_basic_style_sheet) + { + auto style = Rml::MakeShared(); + Rml::StreamMemory stream((const Rml::byte*)string.data(), string.size()); + stream.SetSourceURL("sandbox://rcss"); + + style->LoadStyleSheetContainer(&stream); + style = rml_basic_style_sheet->CombineStyleSheetContainer(*style); + iframe->SetStyleSheetContainer(style); + } +} + +void DemoWindow::SetSandboxBody(const Rml::String& string) +{ + if (iframe) + iframe->SetInnerRML(string); +} + +TweeningParameters DemoWindow::GetTweeningParameters() const +{ + return tweening_parameters; +} + +void DemoWindow::SetTweeningParameters(TweeningParameters in_tweening_parameters) +{ + tweening_parameters = in_tweening_parameters; +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/demo/src/DemoWindow.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/demo/src/DemoWindow.h new file mode 100644 index 00000000..0f29f5a4 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/demo/src/DemoWindow.h @@ -0,0 +1,42 @@ +#pragma once + +#include +#include + +struct TweeningParameters { + Rml::Tween::Type type = Rml::Tween::Linear; + Rml::Tween::Direction direction = Rml::Tween::Out; + float duration = 0.5f; +}; + +class DemoWindow : public Rml::EventListener { +public: + bool Initialize(const Rml::String& title, Rml::Context* context); + void Shutdown(); + + void Update(); + + void ProcessEvent(Rml::Event& event) override; + + Rml::ElementDocument* GetDocument(); + + void SubmitForm(Rml::String in_submit_message); + void SetSandboxStylesheet(const Rml::String& string); + void SetSandboxBody(const Rml::String& string); + + TweeningParameters GetTweeningParameters() const; + void SetTweeningParameters(TweeningParameters tweening_parameters); + +private: + Rml::ElementDocument* document = nullptr; + Rml::ElementDocument* iframe = nullptr; + Rml::Element* gauge = nullptr; + Rml::Element* progress_horizontal = nullptr; + Rml::SharedPtr rml_basic_style_sheet; + + bool submitting = false; + double submitting_start_time = 0; + Rml::String submit_message; + + TweeningParameters tweening_parameters; +}; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/demo/src/main.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/demo/src/main.cpp new file mode 100644 index 00000000..b9ee669b --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/demo/src/main.cpp @@ -0,0 +1,89 @@ +#include "DemoEventListener.h" +#include "DemoWindow.h" +#include +#include +#include +#include +#include + +#if defined RMLUI_PLATFORM_WIN32 + #include +int APIENTRY WinMain(HINSTANCE /*instance_handle*/, HINSTANCE /*previous_instance_handle*/, char* /*command_line*/, int /*command_show*/) +#else +int main(int /*argc*/, char** /*argv*/) +#endif +{ + const int width = 1600; + const int height = 890; + + // Initializes the shell which provides common functionality used by the included samples. + if (!Shell::Initialize()) + return -1; + + // Constructs the system and render interfaces, creates a window, and attaches the renderer. + if (!Backend::Initialize("Demo Sample", width, height, true)) + { + Shell::Shutdown(); + return -1; + } + + // Install the custom interfaces constructed by the backend before initializing RmlUi. + Rml::SetSystemInterface(Backend::GetSystemInterface()); + Rml::SetRenderInterface(Backend::GetRenderInterface()); + + // RmlUi initialisation. + Rml::Initialise(); + + // Create the main RmlUi context. + Rml::Context* context = Rml::CreateContext("main", Rml::Vector2i(width, height)); + if (!context) + { + Rml::Shutdown(); + Backend::Shutdown(); + Shell::Shutdown(); + return -1; + } + + Rml::Debugger::Initialise(context); + + Shell::LoadFonts(); + + DemoWindow demo_window; + + DemoEventListenerInstancer event_listener_instancer{&demo_window}; + Rml::Factory::RegisterEventListenerInstancer(&event_listener_instancer); + + if (!demo_window.Initialize("Demo sample", context)) + { + Rml::Shutdown(); + Backend::Shutdown(); + Shell::Shutdown(); + return -1; + } + + demo_window.GetDocument()->AddEventListener(Rml::EventId::Keydown, &demo_window); + demo_window.GetDocument()->AddEventListener(Rml::EventId::Keyup, &demo_window); + demo_window.GetDocument()->AddEventListener(Rml::EventId::Animationend, &demo_window); + + bool running = true; + while (running) + { + demo_window.Update(); + + running = Backend::ProcessEvents(context, &Shell::ProcessKeyDownShortcuts, true); + context->Update(); + + Backend::BeginFrame(); + context->Render(); + Backend::PresentFrame(); + } + + demo_window.Shutdown(); + + Rml::Shutdown(); + + Backend::Shutdown(); + Shell::Shutdown(); + + return 0; +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/drag/CMakeLists.txt b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/drag/CMakeLists.txt new file mode 100644 index 00000000..530291ba --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/drag/CMakeLists.txt @@ -0,0 +1,16 @@ +set(SAMPLE_NAME "drag") +set(TARGET_NAME "${RMLUI_SAMPLE_PREFIX}${SAMPLE_NAME}") + +add_executable(${TARGET_NAME} WIN32 + src/DragListener.cpp + src/DragListener.h + src/Inventory.cpp + src/Inventory.h + src/main.cpp +) + +set_common_target_options(${TARGET_NAME}) + +target_link_libraries(${TARGET_NAME} PRIVATE rmlui_shell) + +install_sample_target(${TARGET_NAME}) diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/drag/data/icon.rcss b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/drag/data/icon.rcss new file mode 100644 index 00000000..c8bbbb48 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/drag/data/icon.rcss @@ -0,0 +1,22 @@ +icon +{ + /* The icons are floated left so they appear left-to-right. */ + float: left; + + /* The padding is added to push the text down to the bottom of the element. */ + box-sizing: border-box; + width: 100dp; + height: 100dp; + padding: 60dp 10dp 0 10dp; + margin: 10dp; + + decorator: image( ../../../assets/present.tga ); + + font-size: 12dp; + text-align: center; + font-effect: shadow(1dp 1dp black); + + cursor: move; + + drag: clone; +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/drag/data/inventory.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/drag/data/inventory.rml new file mode 100644 index 00000000..3f4aa3f5 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/drag/data/inventory.rml @@ -0,0 +1,22 @@ + + + + + Inventory + + + + + diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/drag/src/DragListener.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/drag/src/DragListener.cpp new file mode 100644 index 00000000..ae7f691e --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/drag/src/DragListener.cpp @@ -0,0 +1,55 @@ +#include "DragListener.h" +#include + +static DragListener drag_listener; + +void DragListener::RegisterDraggableContainer(Rml::Element* element) +{ + element->AddEventListener(Rml::EventId::Dragdrop, &drag_listener); +} + +void DragListener::ProcessEvent(Rml::Event& event) +{ + if (event == Rml::EventId::Dragdrop) + { + Rml::Element* dest_container = event.GetCurrentElement(); + Rml::Element* dest_element = event.GetTargetElement(); + Rml::Element* drag_element = static_cast(event.GetParameter("drag_element", nullptr)); + + if (dest_container == dest_element) + { + // The dragged element was dragged directly onto a container. + Rml::ElementPtr element = drag_element->GetParentNode()->RemoveChild(drag_element); + dest_container->AppendChild(std::move(element)); + } + else + { + // The dragged element was dragged onto an item inside a container. In order to get the + // element in the right place, it will be inserted into the container before the item + // it was dragged on top of. + Rml::Element* insert_before = dest_element; + + // Unless of course if it was dragged on top of an item in its own container; we need + // to check then if it was moved up or down with the container. + if (drag_element->GetParentNode() == dest_container) + { + // Check whether we're moving this icon from the left or the right. + + Rml::Element* previous_icon = insert_before->GetPreviousSibling(); + while (previous_icon != nullptr) + { + if (previous_icon == drag_element) + { + insert_before = insert_before->GetNextSibling(); + break; + } + + previous_icon = previous_icon->GetPreviousSibling(); + } + } + + Rml::ElementPtr element = drag_element->GetParentNode()->RemoveChild(drag_element); + dest_container->InsertBefore(std::move(element), insert_before); + } + } +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/drag/src/DragListener.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/drag/src/DragListener.h new file mode 100644 index 00000000..3fa289dd --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/drag/src/DragListener.h @@ -0,0 +1,13 @@ +#pragma once + +#include +#include + +class DragListener : public Rml::EventListener { +public: + /// Registers an elemenet as being a container of draggable elements. + static void RegisterDraggableContainer(Rml::Element* element); + +protected: + void ProcessEvent(Rml::Event& event) override; +}; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/drag/src/Inventory.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/drag/src/Inventory.cpp new file mode 100644 index 00000000..2eda7b39 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/drag/src/Inventory.cpp @@ -0,0 +1,40 @@ +#include "Inventory.h" +#include "DragListener.h" +#include + +Inventory::Inventory(const Rml::String& title, const Rml::Vector2f& position, Rml::Context* context) +{ + document = context->LoadDocument("basic/drag/data/inventory.rml"); + if (document != nullptr) + { + using Rml::PropertyId; + document->GetElementById("title")->SetInnerRML(title); + document->SetProperty(PropertyId::Left, Rml::Property(position.x, Rml::Unit::DP)); + document->SetProperty(PropertyId::Top, Rml::Property(position.y, Rml::Unit::DP)); + document->Show(); + } + + DragListener::RegisterDraggableContainer(document->GetElementById("content")); +} + +Inventory::~Inventory() +{ + if (document != nullptr) + { + document->Close(); + } +} + +void Inventory::AddItem(const Rml::String& name) +{ + if (document == nullptr) + return; + + Rml::Element* content = document->GetElementById("content"); + if (content == nullptr) + return; + + // Create the new 'icon' element. + Rml::Element* icon = content->AppendChild(Rml::Factory::InstanceElement(content, "icon", "icon", Rml::XMLAttributes())); + icon->SetInnerRML(name); +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/drag/src/Inventory.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/drag/src/Inventory.h new file mode 100644 index 00000000..a9b52ff7 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/drag/src/Inventory.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include +#include + +class Inventory { +public: + /// Constructs a new inventory and opens its window. + /// @param[in] title The title of the new inventory. + /// @param[in] position The position of the inventory window. + /// @param[in] context The context to open the inventory window in. + Inventory(const Rml::String& title, const Rml::Vector2f& position, Rml::Context* context); + /// Destroys the inventory and closes its window. + ~Inventory(); + + /// Adds a brand-new item into this inventory. + /// @param[in] name The name of this item. + void AddItem(const Rml::String& name); + +private: + Rml::ElementDocument* document; +}; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/drag/src/main.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/drag/src/main.cpp new file mode 100644 index 00000000..524b0907 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/drag/src/main.cpp @@ -0,0 +1,80 @@ +#include "Inventory.h" +#include +#include +#include +#include + +#if defined RMLUI_PLATFORM_WIN32 + #include +int APIENTRY WinMain(HINSTANCE /*instance_handle*/, HINSTANCE /*previous_instance_handle*/, char* /*command_line*/, int /*command_show*/) +#else +int main(int /*argc*/, char** /*argv*/) +#endif +{ + int window_width = 1024; + int window_height = 768; + + // Initializes the shell which provides common functionality used by the included samples. + if (!Shell::Initialize()) + return -1; + + // Constructs the system and render interfaces, creates a window, and attaches the renderer. + if (!Backend::Initialize("Drag Sample", window_width, window_height, true)) + { + Shell::Shutdown(); + return -1; + } + + // Install the custom interfaces constructed by the backend before initializing RmlUi. + Rml::SetSystemInterface(Backend::GetSystemInterface()); + Rml::SetRenderInterface(Backend::GetRenderInterface()); + + // RmlUi initialisation. + Rml::Initialise(); + + // Create the main RmlUi context. + Rml::Context* context = Rml::CreateContext("main", Rml::Vector2i(window_width, window_height)); + if (!context) + { + Rml::Shutdown(); + Backend::Shutdown(); + Shell::Shutdown(); + return -1; + } + + Rml::Debugger::Initialise(context); + Shell::LoadFonts(); + + // Load and show the inventory document. + Inventory* inventory_1 = new Inventory("Inventory 1", Rml::Vector2f(50, 200), context); + Inventory* inventory_2 = new Inventory("Inventory 2", Rml::Vector2f(540, 240), context); + + // Add items into the inventory. + inventory_1->AddItem("Mk III L.A.S.E.R."); + inventory_1->AddItem("Gravity Descender"); + inventory_1->AddItem("Closed-Loop Ion Beam"); + inventory_1->AddItem("5kT Mega-Bomb"); + + bool running = true; + while (running) + { + running = Backend::ProcessEvents(context, &Shell::ProcessKeyDownShortcuts, true); + + context->Update(); + + Backend::BeginFrame(); + context->Render(); + Backend::PresentFrame(); + } + + delete inventory_1; + delete inventory_2; + + // Shutdown RmlUi. + Rml::Shutdown(); + + Backend::Shutdown(); + Shell::Shutdown(); + + return 0; +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/effects/CMakeLists.txt b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/effects/CMakeLists.txt new file mode 100644 index 00000000..4785cc3f --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/effects/CMakeLists.txt @@ -0,0 +1,17 @@ +if(NOT RMLUI_BACKEND MATCHES "(GL3|DX12)$") + message(STATUS "Effects sample disabled - only available on GL3 backends") + return() +endif() + +set(SAMPLE_NAME "effects") +set(TARGET_NAME "${RMLUI_SAMPLE_PREFIX}${SAMPLE_NAME}") + +add_executable(${TARGET_NAME} WIN32 + src/main.cpp +) + +set_common_target_options(${TARGET_NAME}) + +target_link_libraries(${TARGET_NAME} PRIVATE rmlui_shell) + +install_sample_target(${TARGET_NAME}) diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/effects/data/effects.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/effects/data/effects.rml new file mode 100644 index 00000000..1137caa1 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/effects/data/effects.rml @@ -0,0 +1,194 @@ + + + + +Effects Sample + + + +

    Effects Sample

    + + + +
    +
    Hello, do you feel the funk?
    +
    Hello, do you feel the funk?
    +
    Hello, do you feel the funk?
    + +
    Hello, do you feel the funk?
    +
    "Creation" (Danilo Guanabara)
    +
    Hello, do you feel the funk?
    +
    Hello, do you feel the funk?
    + +
    Hello, do you feel the funk?
    + +
    Hello, do you feel the funk?
    +
    Hello, do you feel the funk?
    +
    Hello, do you feel the funk?
    +
    Hello, do you feel the funk?
    + +
    Hello, do you feel the funk?
    +
    Hello, do you feel the funk?
    +
    Hello, do you feel the funk?
    + +
    Hello, do you feel the funk?
    +
    Hello, do you feel the funk?
    +
    + +
    Hello, do you feel the funk?
    +
    Hello, do you feel the funk? +
    + + diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/effects/data/effects_style.rcss b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/effects/data/effects_style.rcss new file mode 100644 index 00000000..106faab4 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/effects/data/effects_style.rcss @@ -0,0 +1,199 @@ +body { + font-family: LatoLatin; + font-weight: normal; + font-style: normal; + font-size: 15dp; + + left: 80dp; + right: 80dp; + top: 50dp; + bottom: 50dp; + min-width: 400dp; + min-height: 60dp; + background-color: #a4b6b7; + border: 3dp #d3e9ea; + border-radius: 30dp 8dp; + padding-top: 75dp; + overflow: hidden auto; +} +h1 { + margin: 0em 0 0.7em; + font-size: 22dp; + font-effect: glow(2dp #354c2e); + color: #fed; + padding: 1em 0 1em 40dp; + border-bottom: 3dp #d3e9ea; + background-color: #619158; + z-index: 1; + position: fixed; + top: 0; + right: 0; + left: 0; +} +handle.size { + position: fixed; + z-index: 100; + bottom: 0; + right: 0; + width: 18dp; + height: 18dp; + background-color: #d3e9ea66; + border-top-left-radius: 5dp; + cursor: resize; +} +handle.size:hover, handle.size:active { + background-color: #d3e9ea; +} + +#menu_button { + position: fixed; + z-index: 2; + top: 15dp; + right: 25dp; + box-sizing: border-box; + width: 36dp; + height: 36dp; + + background: #fffc; + border: 2dp #555a; + border-radius: 5dp; + color: #333; + padding-top: 5dp; + text-align: center; + line-height: 7dp; + font-size: 28dp; + cursor: pointer; +} +#menu_button.open { + background-color: #4bdc; + border-color: transparent; + border-top-right-radius: 15dp; +} +#menu_button:hover { background: #bcbc; } +#menu_button:active { background: #abac; } +#menu_button.open:hover { background: #5cec; } +#menu_button.open:active { background: #4bdc; } + +#menu { + position: fixed; + z-index: 1; + top: 15dp; + right: 25dp; + box-sizing: border-box; + width: 400dp; + height: 480dp; + overflow: auto; + overscroll-behavior: contain; + + background: #fffc; + border: 2dp #555a; + border-radius: 15dp; + color: #222; + padding: 20dp 40dp 0dp; +} +#menu table { + margin-bottom: 10dp; +} +#menu td { + vertical-align: middle; + height: 36dp; + line-height: 16dp; +} +#menu td:nth-child(3) { + text-align: right; + white-space: nowrap; + font-size: 0.92em; +} + +#submenu { + display: flex; + text-align: center; + margin-bottom: 20dp; + justify-content: space-around; +} +#submenu div { + flex: 0.35; + height: 25dp; + cursor: pointer; + border-bottom: 1dp #aaa; + box-sizing: border-box; +} +#submenu div:hover { + color: #000; + border-bottom-color: #555; +} +#submenu div.selected { + font-weight: bold; + color: #37a; + border-bottom-color: #4bd; + border-bottom: 2dp #37a; +} + +scrollbarvertical { + z-index: 100; + margin-top: 75dp; + margin-bottom: 20dp; + margin-right: 0dp; + width: 0dp; +} +scrollbarvertical sliderbar { + margin-left: -14dp; + width: 12dp; + min-height: 25dp; + background: #d3e9ea66; + border-radius: 4dp; +} +scrollbarvertical sliderbar:hover, scrollbarvertical sliderbar:active { + background: #d3e9eaaa; +} + +input.range { + width: 100%; + height: 15dp; + transition: opacity 0.2s cubic-out; +} +input.range:disabled { opacity: 0.3; } +input.range slidertrack { + background-color: #fff; +} +input.range sliderbar { + width: 15dp; + border-radius: 3dp; +} +input.range:hover sliderbar { background-color: #333; } +input.range sliderbar:active { background-color: #111; } +input.range sliderbar, input.range sliderbar:disabled { background-color: #555; } +input.range sliderarrowdec, input.range sliderarrowinc { + width: 12dp; + height: 15dp; +} +input.range sliderarrowdec { border-radius: 2dp 0 0 2dp; } +input.range sliderarrowinc { border-radius: 0 2dp 2dp 0; } +input.range sliderarrowdec:hover, input.range sliderarrowinc:hover { background-color: #ddd; } +input.range sliderarrowdec:active, input.range sliderarrowinc:active { background-color: #eee; } +input.range sliderarrowdec, input.range sliderarrowinc, +input.range sliderarrowdec:disabled, input.range sliderarrowinc:disabled { background-color: #ccc; } + +input.radio, input.checkbox { + width: 15dp; + height: 15dp; + border: 1dp #ccc; + background: #fff; + border-radius: 2dp; +} +input.radio { + border-radius: 8dp; +} +input.radio:hover, input.checkbox:hover { background-color: #ff3; } +input.radio:active, input.checkbox:active { background-color: #ddd; } +input.radio:checked, input.checkbox:checked { background-color: #555; } + +button { + border: 1dp #555; + border-radius: 7dp; + padding: 6dp 13dp; + background-color: #fffa; + cursor: pointer; +} +button:hover { background-color: #ccca; } +button:active { background-color: #bbba; } diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/effects/src/main.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/effects/src/main.cpp new file mode 100644 index 00000000..2581cb43 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/effects/src/main.cpp @@ -0,0 +1,130 @@ +#include +#include +#include +#include + +#if defined RMLUI_PLATFORM_WIN32 + #include +int APIENTRY WinMain(HINSTANCE /*instance_handle*/, HINSTANCE /*previous_instance_handle*/, char* /*command_line*/, int /*command_show*/) +#else +int main(int /*argc*/, char** /*argv*/) +#endif +{ + const int window_width = 1024; + const int window_height = 768; + + // Initializes the shell which provides common functionality used by the included samples. + if (!Shell::Initialize()) + return -1; + + // Constructs the system and render interfaces, creates a window, and attaches the renderer. + if (!Backend::Initialize("Effects Sample", window_width, window_height, true)) + { + Shell::Shutdown(); + return -1; + } + + // Install the custom interfaces constructed by the backend before initializing RmlUi. + Rml::SetSystemInterface(Backend::GetSystemInterface()); + Rml::SetRenderInterface(Backend::GetRenderInterface()); + + // RmlUi initialisation. + Rml::Initialise(); + + // Create the main RmlUi context. + Rml::Context* context = Rml::CreateContext("main", Rml::Vector2i(window_width, window_height)); + if (!context) + { + Rml::Shutdown(); + Backend::Shutdown(); + Shell::Shutdown(); + return -1; + } + + Rml::Debugger::Initialise(context); + Shell::LoadFonts(); + + static constexpr float perspective_max = 3000.f; + + struct EffectsData { + bool show_menu = false; + Rml::String submenu = "filter"; + + struct Filter { + float opacity = 1.0f; + float sepia = 0.0f; + float grayscale = 0.0f; + float saturate = 1.0f; + float brightness = 1.0f; + float contrast = 1.0f; + float hue_rotate = 0.0f; + float invert = 0.0f; + float blur = 0.0f; + bool drop_shadow = false; + } filter; + + struct Transform { + float scale = 1.0f; + Rml::Vector3f rotate; + float perspective = perspective_max; + Rml::Vector2f perspective_origin = Rml::Vector2f(50.f); + bool transform_all = false; + } transform; + } data; + + if (Rml::DataModelConstructor constructor = context->CreateDataModel("effects")) + { + constructor.Bind("show_menu", &data.show_menu); + constructor.Bind("submenu", &data.submenu); + + constructor.Bind("opacity", &data.filter.opacity); + constructor.Bind("sepia", &data.filter.sepia); + constructor.Bind("grayscale", &data.filter.grayscale); + constructor.Bind("saturate", &data.filter.saturate); + constructor.Bind("brightness", &data.filter.brightness); + constructor.Bind("contrast", &data.filter.contrast); + constructor.Bind("hue_rotate", &data.filter.hue_rotate); + constructor.Bind("invert", &data.filter.invert); + constructor.Bind("blur", &data.filter.blur); + constructor.Bind("drop_shadow", &data.filter.drop_shadow); + + constructor.Bind("scale", &data.transform.scale); + constructor.Bind("rotate_x", &data.transform.rotate.x); + constructor.Bind("rotate_y", &data.transform.rotate.y); + constructor.Bind("rotate_z", &data.transform.rotate.z); + constructor.Bind("perspective", &data.transform.perspective); + constructor.Bind("perspective_origin_x", &data.transform.perspective_origin.x); + constructor.Bind("perspective_origin_y", &data.transform.perspective_origin.y); + constructor.Bind("transform_all", &data.transform.transform_all); + + constructor.BindEventCallback("reset", [&data](Rml::DataModelHandle handle, Rml::Event& /*ev*/, const Rml::VariantList& /*arguments*/) { + if (data.submenu == "transform") + data.transform = EffectsData::Transform{}; + else if (data.submenu == "filter") + data.filter = EffectsData::Filter{}; + handle.DirtyAllVariables(); + }); + } + + if (Rml::ElementDocument* document = context->LoadDocument("basic/effects/data/effects.rml")) + document->Show(); + + bool running = true; + while (running) + { + running = Backend::ProcessEvents(context, &Shell::ProcessKeyDownShortcuts, false); + + context->Update(); + + Backend::BeginFrame(); + context->Render(); + Backend::PresentFrame(); + } + + Rml::Shutdown(); + + Backend::Shutdown(); + Shell::Shutdown(); + + return 0; +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/CMakeLists.txt b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/CMakeLists.txt new file mode 100644 index 00000000..94364479 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/CMakeLists.txt @@ -0,0 +1,43 @@ +set(SAMPLE_NAME "harfbuzz") +set(TARGET_NAME "${RMLUI_SAMPLE_PREFIX}${SAMPLE_NAME}") + +add_executable(${TARGET_NAME} WIN32 + src/FontEngineInterfaceHarfBuzz.cpp + src/FontEngineInterfaceHarfBuzz.h + src/FontFace.cpp + src/FontFace.h + src/FontFaceHandleHarfBuzz.cpp + src/FontFaceHandleHarfBuzz.h + src/FontFaceLayer.cpp + src/FontFaceLayer.h + src/FontFamily.cpp + src/FontFamily.h + src/FontGlyph.h + src/FontProvider.cpp + src/FontProvider.h + src/FreeTypeInterface.cpp + src/FreeTypeInterface.h + src/LanguageData.h + src/TextureLayout.cpp + src/TextureLayout.h + src/TextureLayoutRectangle.cpp + src/TextureLayoutRectangle.h + src/TextureLayoutRow.cpp + src/TextureLayoutRow.h + src/TextureLayoutTexture.cpp + src/TextureLayoutTexture.h + src/main.cpp +) + +set_common_target_options(${TARGET_NAME}) + +target_link_libraries(${TARGET_NAME} PRIVATE rmlui_shell Freetype::Freetype harfbuzz::harfbuzz) + +# Use private Core headers as a workaround for now, until we merge the HarfBuzz font engine into Core. +target_include_directories(${TARGET_NAME} PRIVATE ${PROJECT_SOURCE_DIR}/Source/Core) +# Remove this check once the HarfBuzz font engine has been merged into Core. +if(WIN32 AND BUILD_SHARED_LIBS) + message(FATAL_ERROR "The HarfBuzz sample cannot be built when using shared libraries on Windows. Please disable either BUILD_SHARED_LIBS or RMLUI_HARFBUZZ_SAMPLE.") +endif() + +install_sample_target(${TARGET_NAME}) diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/data/Cairo-Regular.ttf b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/data/Cairo-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..fb9e6cc6ba56b5af11c5360d62d5557eb245ae61 GIT binary patch literal 94484 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 )qVx=Uj+evOH^HOAo97>-+G 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 vFX+!q_dH&0KU7o-0J*eDu{ literal 0 HcmV?d00001 diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/data/LICENSE.txt b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/data/LICENSE.txt new file mode 100644 index 00000000..9a8ed6a1 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/data/LICENSE.txt @@ -0,0 +1,209 @@ +The RmlUi text shaper sample includes fonts that are separately licensed. +They are listed below along with their licenses. + +----------------------------------------------------------- +----------------------------------------------------------- +--- Cairo --- +--- https://fonts.google.com/specimen/Cairo --- +--- --- +--- Cairo-Regular.ttf --- +----------------------------------------------------------- +----------------------------------------------------------- + +Copyright 2009 The Cairo Project Authors (https://github.com/Gue3bara/Cairo) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. + + +----------------------------------------------------------- +----------------------------------------------------------- +--- Poppins --- +--- https://fonts.google.com/specimen/Poppins --- +--- --- +--- Poppins-Regular.ttf --- +----------------------------------------------------------- +----------------------------------------------------------- + +Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/data/Poppins-Regular.ttf b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/data/Poppins-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..9f0c71b70a49664ced448c63edc9c4ff2bf8cf4a GIT binary patch literal 158240 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 + +Text Shaping Demo + + + + +

    + + + +
    +

    Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Urna neque viverra justo nec ultrices dui sapien eget mi. Risus quis varius quam quisque id. Amet est placerat in egestas erat imperdiet. Velit egestas dui id ornare arcu odio ut sem. Aliquet porttitor lacus luctus accumsan tortor posuere. Et malesuada fames ac turpis egestas integer eget. Enim nunc faucibus a pellentesque sit amet porttitor eget. Nunc pulvinar sapien et ligula. Sit amet mattis vulputate enim nulla aliquet porttitor lacus luctus. Dolor sit amet consectetur adipiscing. Congue eu consequat ac felis donec et odio pellentesque. Nunc non blandit massa enim nec dui nunc mattis.

    +

    هناك حقيقة مثبتة منذ زمن طويل وهي أن المحتوى المقروء لصفحة ما سيلهي القارئ عن التركيز على الشكل الخارجي للنص أو شكل توضع الفقرات في الصفحة التي يقرأها. ولذلك يتم استخدام طريقة لوريم إيبسوم لأنها تعطي توزيعاَ طبيعياَ -إلى حد ما- للأحرف عوضاً عن استخدام "هنا يوجد محتوى نصي، هنا يوجد محتوى نصي" فتجعلها تبدو (أي الأحرف) وكأنها نص مقروء. العديد من برامح النشر المكتبي وبرامح تحرير صفحات الويب تستخدم لوريم إيبسوم بشكل إفتراضي كنموذج عن النص، وإذا قمت بإدخال "muspi merol" في أي محرك بحث ستظهر العديد من المواقع الحديثة العهد في نتائج البحث. على مدى السنين ظهرت نسخ جديدة ومختلفة من نص لوريم إيبسوم، أحياناً عن طريق الصدفة، وأحياناً عن عمد كإدخال بعض العبارات الفكاهية إليها.

    +

    यह एक लंबा स्थापित तथ्य है कि जब एक पाठक एक पृष्ठ के खाखे को देखेगा तो पठनीय सामग्री से विचलित हो जाएगा. Lorem Ipsum का उपयोग करने का मुद्दा यह है कि इसमें एक और अधिक या कम अक्षरों का सामान्य वितरण किया गया है, 'Content here, content here' प्रयोग करने की जगह इसे पठनीय English के रूप में प्रयोग किया जाये. अब कई डेस्कटॉप प्रकाशन संकुल और वेब पेज संपादक उनके डिफ़ॉल्ट मॉडल पाठ के रूप में Lorem Ipsum उपयोग करते हैं, और अब "Lorem Ipsum" के लिए खोज अपने शैशव में कई वेब साइटों को उजागर करती है. इसके विभिन्न संस्करणों का वर्षों में विकास हुआ है, कभी दुर्घटना से, तो कभी प्रयोजन पर (हास्य और लगाव डालने के लिए).

    + + diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FontEngineInterfaceHarfBuzz.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FontEngineInterfaceHarfBuzz.cpp new file mode 100644 index 00000000..b7db66e7 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FontEngineInterfaceHarfBuzz.cpp @@ -0,0 +1,75 @@ +#include "FontEngineInterfaceHarfBuzz.h" +#include "FontFaceHandleHarfBuzz.h" +#include "FontProvider.h" +#include + +void FontEngineInterfaceHarfBuzz::Initialize() +{ + FontProvider::Initialise(); +} +void FontEngineInterfaceHarfBuzz::Shutdown() +{ + FontProvider::Shutdown(); +} + +bool FontEngineInterfaceHarfBuzz::LoadFontFace(const String& file_name, int face_index, bool fallback_face, Style::FontWeight weight) +{ + return FontProvider::LoadFontFace(file_name, face_index, fallback_face, weight); +} + +bool FontEngineInterfaceHarfBuzz::LoadFontFace(Span data, int face_index, const String& font_family, Style::FontStyle style, + Style::FontWeight weight, bool fallback_face) +{ + return FontProvider::LoadFontFace(data, face_index, font_family, style, weight, fallback_face); +} + +FontFaceHandle FontEngineInterfaceHarfBuzz::GetFontFaceHandle(const String& family, Style::FontStyle style, Style::FontWeight weight, int size) +{ + auto handle = FontProvider::GetFontFaceHandle(family, style, weight, size); + return reinterpret_cast(handle); +} + +FontEffectsHandle FontEngineInterfaceHarfBuzz::PrepareFontEffects(FontFaceHandle handle, const FontEffectList& font_effects) +{ + auto handle_harfbuzz = reinterpret_cast(handle); + return (FontEffectsHandle)handle_harfbuzz->GenerateLayerConfiguration(font_effects); +} + +const FontMetrics& FontEngineInterfaceHarfBuzz::GetFontMetrics(FontFaceHandle handle) +{ + auto handle_harfbuzz = reinterpret_cast(handle); + return handle_harfbuzz->GetFontMetrics(); +} + +int FontEngineInterfaceHarfBuzz::GetStringWidth(FontFaceHandle handle, StringView string, const TextShapingContext& text_shaping_context, + Character prior_character) +{ + auto handle_harfbuzz = reinterpret_cast(handle); + return handle_harfbuzz->GetStringWidth(string, text_shaping_context, registered_languages, prior_character); +} + +int FontEngineInterfaceHarfBuzz::GenerateString(RenderManager& render_manager, FontFaceHandle handle, FontEffectsHandle font_effects_handle, + StringView string, Vector2f position, ColourbPremultiplied colour, float opacity, const TextShapingContext& text_shaping_context, + TexturedMeshList& mesh_list) +{ + auto handle_harfbuzz = reinterpret_cast(handle); + return handle_harfbuzz->GenerateString(render_manager, mesh_list, string, position, colour, opacity, text_shaping_context, registered_languages, + (int)font_effects_handle); +} + +int FontEngineInterfaceHarfBuzz::GetVersion(FontFaceHandle handle) +{ + auto handle_harfbuzz = reinterpret_cast(handle); + return handle_harfbuzz->GetVersion(); +} + +void FontEngineInterfaceHarfBuzz::ReleaseFontResources() +{ + FontProvider::ReleaseFontResources(); +} + +void FontEngineInterfaceHarfBuzz::RegisterLanguage(const String& language_bcp47_code, const String& script_iso15924_code, + const TextFlowDirection text_flow_direction) +{ + registered_languages[language_bcp47_code] = LanguageData{script_iso15924_code, text_flow_direction}; +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FontEngineInterfaceHarfBuzz.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FontEngineInterfaceHarfBuzz.h new file mode 100644 index 00000000..137e63c1 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FontEngineInterfaceHarfBuzz.h @@ -0,0 +1,64 @@ +#pragma once + +#include "LanguageData.h" +#include + +using Rml::byte; +using Rml::Character; +using Rml::ColourbPremultiplied; +using Rml::FontEffectList; +using Rml::FontEffectsHandle; +using Rml::FontFaceHandle; +using Rml::FontMetrics; +using Rml::RenderManager; +using Rml::Span; +using Rml::String; +using Rml::StringView; +using Rml::TextShapingContext; +using Rml::TexturedMeshList; +using Rml::Vector2f; +namespace Style = Rml::Style; + +class FontEngineInterfaceHarfBuzz : public Rml::FontEngineInterface { +public: + void Initialize() override; + void Shutdown() override; + + /// Adds a new font face to the database. The face's family, style and weight will be determined from the face itself. + bool LoadFontFace(const String& file_name, int face_index, bool fallback_face, Style::FontWeight weight) override; + + /// Adds a new font face to the database using the provided family, style and weight. + bool LoadFontFace(Span data, int face_index, const String& font_family, Style::FontStyle style, Style::FontWeight weight, + bool fallback_face) override; + + /// Returns a handle to a font face that can be used to position and render text. This will return the closest match + /// it can find, but in the event a font family is requested that does not exist, NULL will be returned instead of a + /// valid handle. + FontFaceHandle GetFontFaceHandle(const String& family, Style::FontStyle style, Style::FontWeight weight, int size) override; + + /// Prepares for font effects by configuring a new, or returning an existing, layer configuration. + FontEffectsHandle PrepareFontEffects(FontFaceHandle handle, const FontEffectList& font_effects) override; + + /// Returns the font metrics of the given font face. + const FontMetrics& GetFontMetrics(FontFaceHandle handle) override; + + /// Returns the width a string will take up if rendered with this handle. + int GetStringWidth(FontFaceHandle handle, StringView string, const TextShapingContext& text_shaping_context, Character prior_character) override; + + /// Generates the geometry required to render a single line of text. + int GenerateString(RenderManager& render_manager, FontFaceHandle face_handle, FontEffectsHandle effects_handle, StringView string, + Vector2f position, ColourbPremultiplied colour, float opacity, const TextShapingContext& text_shaping_context, + TexturedMeshList& mesh_list) override; + + /// Returns the current version of the font face. + int GetVersion(FontFaceHandle handle) override; + + /// Releases resources owned by sized font faces, including their textures and rendered glyphs. + void ReleaseFontResources() override; + + /// Registers a new language to assist with text shaping. + void RegisterLanguage(const String& language_bcp47_code, const String& script_iso15924_code, const TextFlowDirection text_flow_direction); + +private: + LanguageDataMap registered_languages; +}; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FontFace.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FontFace.cpp new file mode 100644 index 00000000..7f33ea53 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FontFace.cpp @@ -0,0 +1,59 @@ +#include "FontFace.h" +#include "FontEngineDefault/FreeTypeInterface.h" + +FontFace::FontFace(FontFaceHandleFreetype _face, Style::FontStyle _style, Style::FontWeight _weight) +{ + style = _style; + weight = _weight; + face = _face; +} + +FontFace::~FontFace() +{ + if (face) + Rml::FreeType::ReleaseFace(face); +} + +Style::FontStyle FontFace::GetStyle() const +{ + return style; +} + +Style::FontWeight FontFace::GetWeight() const +{ + return weight; +} + +FontFaceHandleHarfBuzz* FontFace::GetHandle(int size, bool load_default_glyphs) +{ + auto it = handles.find(size); + if (it != handles.end()) + return it->second.get(); + + // See if this face has been released. + if (!face) + { + Rml::Log::Message(Rml::Log::LT_WARNING, "Font face has been released, unable to generate new handle."); + return nullptr; + } + + // Construct and initialise the new handle. + auto handle = Rml::MakeUnique(); + if (!handle->Initialize(face, size, load_default_glyphs)) + { + handles[size] = nullptr; + return nullptr; + } + + FontFaceHandleHarfBuzz* result = handle.get(); + + // Save the new handle to the font face + handles[size] = std::move(handle); + + return result; +} + +void FontFace::ReleaseFontResources() +{ + HandleMap().swap(handles); +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FontFace.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FontFace.h new file mode 100644 index 00000000..adb7ab0b --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FontFace.h @@ -0,0 +1,42 @@ +#pragma once + +#include "FontEngineDefault/FontTypes.h" +#include "FontFaceHandleHarfBuzz.h" +#include + +using Rml::FontFaceHandleFreetype; +using Rml::UniquePtr; +using Rml::UnorderedMap; +namespace Style = Rml::Style; + +/** + Modified to support HarfBuzz text shaping. + */ + +class FontFace { +public: + FontFace(FontFaceHandleFreetype face, Style::FontStyle style, Style::FontWeight weight); + ~FontFace(); + + Style::FontStyle GetStyle() const; + Style::FontWeight GetWeight() const; + + /// Returns a handle for positioning and rendering this face at the given size. + /// @param[in] size The size of the desired handle, in points. + /// @param[in] load_default_glyphs True to load the default set of glyph (ASCII range). + /// @return The font handle. + FontFaceHandleHarfBuzz* GetHandle(int size, bool load_default_glyphs); + + /// Releases resources owned by sized font faces, including their textures and rendered glyphs. + void ReleaseFontResources(); + +private: + Style::FontStyle style; + Style::FontWeight weight; + + // Key is font size + using HandleMap = UnorderedMap>; + HandleMap handles; + + FontFaceHandleFreetype face; +}; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FontFaceHandleHarfBuzz.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FontFaceHandleHarfBuzz.cpp new file mode 100644 index 00000000..7525e1fb --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FontFaceHandleHarfBuzz.cpp @@ -0,0 +1,803 @@ +#include "FontFaceHandleHarfBuzz.h" +#include "FontEngineDefault/FreeTypeInterface.h" +#include "FontFaceLayer.h" +#include "FontProvider.h" +#include "FreeTypeInterface.h" +#include +#include FT_FREETYPE_H +#include +#include +#include +#include +#include + +static bool IsControlCharacter(Character c) +{ + return (char32_t)c < U' ' || ((char32_t)c >= U'\x7F' && (char32_t)c <= U'\x9F'); +} + +static bool IsKerningEnabled(const TextShapingContext& text_shaping_context, int font_size) +{ + static constexpr int minimum_font_size_to_enable_kerning = 14; + + switch (text_shaping_context.font_kerning) + { + case Style::FontKerning::Normal: return true; + case Style::FontKerning::None: return false; + default: return font_size >= minimum_font_size_to_enable_kerning; + } +} + +static Vector GetTextShapingFeatures(const TextShapingContext& text_shaping_context, int font_size) +{ + Vector shaping_features; + + if (!IsKerningEnabled(text_shaping_context, font_size)) + { + // Kerning is enabled by default in HarfBuzz, so we need to explicitly disable it. + shaping_features.emplace_back(); + hb_feature_from_string("kern=0", -1, &shaping_features.back()); + } + + return shaping_features; +} + +FontFaceHandleHarfBuzz::FontFaceHandleHarfBuzz() +{ + base_layer = nullptr; + metrics = {}; + ft_face = 0; + hb_font = nullptr; +} + +FontFaceHandleHarfBuzz::~FontFaceHandleHarfBuzz() +{ + hb_font_destroy(hb_font); + + glyphs.clear(); + layers.clear(); +} + +bool FontFaceHandleHarfBuzz::Initialize(FontFaceHandleFreetype face, int font_size, bool load_default_glyphs) +{ + ft_face = face; + + RMLUI_ASSERTMSG(layer_configurations.empty(), "Initialize must only be called once."); + + if (!FreeType::InitialiseFaceHandle(ft_face, font_size, glyphs, metrics, load_default_glyphs)) + return false; + + hb_font = hb_ft_font_create_referenced((FT_Face)ft_face); + RMLUI_ASSERT(hb_font != nullptr); + hb_font_set_ptem(hb_font, (float)font_size); + hb_ft_font_set_funcs(hb_font); + + // Generate the default layer and layer configuration. + base_layer = GetOrCreateLayer(nullptr); + layer_configurations.push_back(LayerConfiguration{base_layer}); + + return true; +} + +const FontMetrics& FontFaceHandleHarfBuzz::GetFontMetrics() const +{ + return metrics; +} + +const FontGlyphMap& FontFaceHandleHarfBuzz::GetGlyphs() const +{ + return glyphs; +} + +const FallbackFontGlyphMap& FontFaceHandleHarfBuzz::GetFallbackGlyphs() const +{ + return fallback_glyphs; +} + +const FallbackFontClusterGlyphsMap& FontFaceHandleHarfBuzz::GetFallbackClusterGlyphs() const +{ + return fallback_cluster_glyphs; +} + +int FontFaceHandleHarfBuzz::GetStringWidth(StringView string, const TextShapingContext& text_shaping_context, + const LanguageDataMap& registered_languages, Character /*prior_character*/) +{ + int width = 0; + + // Apply text shaping. + hb_buffer_t* shaping_buffer = hb_buffer_create(); + RMLUI_ASSERT(shaping_buffer != nullptr); + ConfigureTextShapingBuffer(shaping_buffer, string, text_shaping_context, registered_languages, nullptr); + hb_buffer_add_utf8(shaping_buffer, string.begin(), (int)string.size(), 0, (int)string.size()); + + Vector shaping_features = GetTextShapingFeatures(text_shaping_context, metrics.size); + const hb_feature_t* shaping_features_pointer = !shaping_features.empty() ? shaping_features.data() : nullptr; + hb_shape(hb_font, shaping_buffer, shaping_features_pointer, (unsigned int)shaping_features.size()); + + unsigned int glyph_count = 0; + hb_glyph_info_t* glyph_info = hb_buffer_get_glyph_infos(shaping_buffer, &glyph_count); + hb_glyph_position_t* glyph_positions = hb_buffer_get_glyph_positions(shaping_buffer, &glyph_count); + + Queue> glyph_queue; + + for (int g = 0; g < (int)glyph_count; ++g) + { + Character character = Rml::StringUtilities::ToCharacter(string.begin() + glyph_info[g].cluster, string.end()); + + // Don't render control characters. + if (IsControlCharacter(character)) + continue; + + const FontGlyphIndex glyph_index = glyph_info[g].codepoint; + int extra_glyph_index_offset = 0; + + if (glyph_index == 0) + { + // Check to see if the glyph is the start of an unsupported multi-character cluster. + int cluster_codepoint_count = 0; + StringView cluster_string = GetCurrentClusterString(glyph_info, glyph_count, g, character, string, cluster_codepoint_count); + + if (cluster_codepoint_count > 1) + { + // Unsupported cluster detected; use fallback cluster glyph if one is available. + const Vector* cluster_glyphs = + GetOrAppendFallbackClusterGlyphs(cluster_string, text_shaping_context, registered_languages, shaping_features); + + if (cluster_glyphs) + { + extra_glyph_index_offset = cluster_codepoint_count - 1; + for (const auto& cluster_glyph : *cluster_glyphs) + glyph_queue.emplace(cluster_glyph.glyph_index, &cluster_glyph.glyph_data.bitmap); + } + } + } + + if (glyph_queue.empty()) + { + const FontGlyph* glyph = GetOrAppendGlyph(glyph_index, character); + if (glyph) + glyph_queue.emplace(glyph_index, glyph); + } + + while (!glyph_queue.empty()) + { + auto& glyph_pair = glyph_queue.front(); + + // Adjust the cursor for this character's advance. + if (glyph_info[g].codepoint != 0) + width += glyph_positions[g].x_advance >> 6; + else + // Use the unshaped advance for unsupported characters. + width += glyph_pair.second->advance; + + width += (int)text_shaping_context.letter_spacing; + + glyph_queue.pop(); + } + + g += extra_glyph_index_offset; + } + + hb_buffer_destroy(shaping_buffer); + + return Rml::Math::Max(width, 0); +} + +int FontFaceHandleHarfBuzz::GenerateLayerConfiguration(const FontEffectList& font_effects) +{ + if (font_effects.empty()) + return 0; + + // Check each existing configuration for a match with this arrangement of effects. + int configuration_index = 1; + for (; configuration_index < (int)layer_configurations.size(); ++configuration_index) + { + const LayerConfiguration& configuration = layer_configurations[configuration_index]; + + // Check the size is correct. For a match, there should be one layer in the configuration + // plus an extra for the base layer. + if (configuration.size() != font_effects.size() + 1) + continue; + + // Check through each layer, checking it was created by the same effect as the one we're + // checking. + size_t effect_index = 0; + for (size_t i = 0; i < configuration.size(); ++i) + { + // Skip the base layer ... + if (configuration[i]->GetFontEffect() == nullptr) + continue; + + // If the ith layer's effect doesn't match the equivalent effect, then this + // configuration can't match. + if (configuration[i]->GetFontEffect() != font_effects[effect_index].get()) + break; + + // Check the next one ... + ++effect_index; + } + + if (effect_index == font_effects.size()) + return configuration_index; + } + + // No match, so we have to generate a new layer configuration. + layer_configurations.push_back(LayerConfiguration()); + LayerConfiguration& layer_configuration = layer_configurations.back(); + + bool added_base_layer = false; + + for (size_t i = 0; i < font_effects.size(); ++i) + { + if (!added_base_layer && font_effects[i]->GetLayer() == FontEffect::Layer::Front) + { + layer_configuration.push_back(base_layer); + added_base_layer = true; + } + + FontFaceLayer* new_layer = GetOrCreateLayer(font_effects[i]); + layer_configuration.push_back(new_layer); + } + + // Add the base layer now if we still haven't added it. + if (!added_base_layer) + layer_configuration.push_back(base_layer); + + return (int)(layer_configurations.size() - 1); +} + +bool FontFaceHandleHarfBuzz::GenerateLayerTexture(Vector& texture_data, Vector2i& texture_dimensions, const FontEffect* font_effect, + int texture_id, int handle_version) const +{ + if (handle_version != version) + { + RMLUI_ERRORMSG("While generating font layer texture: Handle version mismatch in texture vs font-face."); + return false; + } + + auto it = std::find_if(layers.begin(), layers.end(), [font_effect](const EffectLayerPair& pair) { return pair.font_effect == font_effect; }); + + if (it == layers.end()) + { + RMLUI_ERRORMSG("While generating font layer texture: Layer id not found."); + return false; + } + + return it->layer->GenerateTexture(texture_data, texture_dimensions, texture_id, + FontGlyphMaps{&glyphs, &fallback_glyphs, &fallback_cluster_glyphs_lookup}); +} + +int FontFaceHandleHarfBuzz::GenerateString(RenderManager& render_manager, TexturedMeshList& mesh_list, StringView string, const Vector2f position, + const ColourbPremultiplied colour, const float opacity, const TextShapingContext& text_shaping_context, + const LanguageDataMap& registered_languages, const int layer_configuration_index) +{ + int geometry_index = 0; + int line_width = 0; + + RMLUI_ASSERT(layer_configuration_index >= 0); + RMLUI_ASSERT(layer_configuration_index < (int)layer_configurations.size()); + + UpdateLayersOnDirty(); + + // Fetch the requested configuration and generate the geometry for each one. + const LayerConfiguration& layer_configuration = layer_configurations[layer_configuration_index]; + + // Each texture represents one geometry. + const int num_geometries = std::accumulate(layer_configuration.begin(), layer_configuration.end(), 0, + [](int sum, const FontFaceLayer* layer) { return sum + layer->GetNumTextures(); }); + + mesh_list.resize(num_geometries); + + hb_buffer_t* shaping_buffer = hb_buffer_create(); + RMLUI_ASSERT(shaping_buffer != nullptr); + Vector shaping_features = GetTextShapingFeatures(text_shaping_context, metrics.size); + const hb_feature_t* shaping_features_pointer = !shaping_features.empty() ? shaping_features.data() : nullptr; + + for (size_t layer_index = 0; layer_index < layer_configuration.size(); ++layer_index) + { + FontFaceLayer* layer = layer_configuration[layer_index]; + + ColourbPremultiplied layer_colour; + if (layer == base_layer) + layer_colour = colour; + else + layer_colour = layer->GetColour(opacity); + + const int num_textures = layer->GetNumTextures(); + if (num_textures == 0) + continue; + + RMLUI_ASSERT(geometry_index + num_textures <= (int)mesh_list.size()); + + line_width = 0; + + // Set the mesh and textures to the geometries. + for (int tex_index = 0; tex_index < num_textures; ++tex_index) + mesh_list[geometry_index + tex_index].texture = layer->GetTexture(render_manager, tex_index); + + // Set up and apply text shaping. + hb_buffer_clear_contents(shaping_buffer); + ConfigureTextShapingBuffer(shaping_buffer, string, text_shaping_context, registered_languages, nullptr); + hb_buffer_add_utf8(shaping_buffer, string.begin(), (int)string.size(), 0, (int)string.size()); + hb_shape(hb_font, shaping_buffer, shaping_features_pointer, (unsigned int)shaping_features.size()); + + unsigned int glyph_count = 0; + hb_glyph_info_t* glyph_info = hb_buffer_get_glyph_infos(shaping_buffer, &glyph_count); + hb_glyph_position_t* glyph_positions = hb_buffer_get_glyph_positions(shaping_buffer, &glyph_count); + + mesh_list[geometry_index].mesh.indices.reserve(string.size() * 6); + mesh_list[geometry_index].mesh.vertices.reserve(string.size() * 4); + + Queue> glyph_queue; + + for (int g = 0; g < (int)glyph_count; ++g) + { + Character character = Rml::StringUtilities::ToCharacter(string.begin() + glyph_info[g].cluster, string.end()); + + // Don't render control characters. + if (IsControlCharacter(character)) + continue; + + const FontGlyphIndex glyph_index = glyph_info[g].codepoint; + int extra_glyph_index_offset = 0; + bool is_cluster = false; + + if (glyph_index == 0) + { + // Check to see if the glyph is the start of an unsupported multi-character cluster. + int cluster_codepoint_count = 0; + StringView cluster_string = GetCurrentClusterString(glyph_info, glyph_count, g, character, string, cluster_codepoint_count); + + if (cluster_codepoint_count > 1) + { + // Unsupported cluster detected; use fallback cluster glyph if one is available. + const Vector* cluster_glyphs = + GetOrAppendFallbackClusterGlyphs(cluster_string, text_shaping_context, registered_languages, shaping_features); + + if (cluster_glyphs) + { + extra_glyph_index_offset = cluster_codepoint_count - 1; + is_cluster = true; + + for (const auto& cluster_glyph : *cluster_glyphs) + glyph_queue.emplace(cluster_glyph.glyph_index, FontGlyphReference{&cluster_glyph.glyph_data.bitmap, cluster_glyph.glyph_data.character}); + } + } + } + + if (glyph_queue.empty()) + { + const FontGlyph* glyph = GetOrAppendGlyph(glyph_index, character); + if (glyph) + glyph_queue.emplace(glyph_index, FontGlyphReference{glyph, character}); + } + + while (!glyph_queue.empty()) + { + auto& glyph_pair = glyph_queue.front(); + + ColourbPremultiplied glyph_color = layer_colour; + // Use white vertex colors on RGB glyphs. + if (layer == base_layer && glyph_pair.second.bitmap->color_format == ColorFormat::RGBA8) + glyph_color = ColourbPremultiplied(layer_colour.alpha, layer_colour.alpha); + + Vector2f glyph_offset = {0.0f, 0.0f}; + glyph_offset += Vector2f{float(glyph_positions[g].x_offset >> 6), float(glyph_positions[g].y_offset >> 6)}; + Vector2f glyph_geometry_position = Vector2f{position.x + line_width, position.y} + glyph_offset; + layer->GenerateGeometry(&mesh_list[geometry_index], glyph_pair.first, glyph_pair.second.character, is_cluster, glyph_geometry_position, glyph_color); + + // Adjust the cursor for this character's advance. + if (glyph_info[g].codepoint != 0) + line_width += glyph_positions[g].x_advance >> 6; + else + // Use the unshaped advance for unsupported characters. + line_width += glyph_pair.second.bitmap->advance; + + line_width += (int)text_shaping_context.letter_spacing; + + glyph_queue.pop(); + } + + g += extra_glyph_index_offset; + } + + geometry_index += num_textures; + } + + hb_buffer_destroy(shaping_buffer); + + return Rml::Math::Max(line_width, 0); +} + +bool FontFaceHandleHarfBuzz::UpdateLayersOnDirty() +{ + bool result = false; + + // If we are dirty, regenerate all the layers and increment the version + if (is_layers_dirty && base_layer) + { + is_layers_dirty = false; + ++version; + + // Regenerate all the layers. + // Note: The layer regeneration needs to happen in the order in which the layers were created, + // otherwise we may end up cloning a layer which has not yet been regenerated. This means trouble! + for (auto& pair : layers) + { + GenerateLayer(pair.layer.get()); + } + + result = true; + } + + return result; +} + +int FontFaceHandleHarfBuzz::GetVersion() const +{ + return version; +} + +bool FontFaceHandleHarfBuzz::AppendGlyph(FontGlyphIndex glyph_index, Character character) +{ + bool result = FreeType::AppendGlyph(ft_face, metrics.size, glyph_index, character, glyphs); + return result; +} + +bool FontFaceHandleHarfBuzz::AppendFallbackGlyph(Character& character) +{ + const int num_fallback_faces = FontProvider::CountFallbackFontFaces(); + for (int i = 0; i < num_fallback_faces; i++) + { + FontFaceHandleHarfBuzz* fallback_face = FontProvider::GetFallbackFontFace(i, metrics.size); + if (!fallback_face || fallback_face == this) + continue; + + const FontGlyphIndex character_index = FreeType::GetGlyphIndexFromCharacter(fallback_face->ft_face, character); + if (character_index == 0) + continue; + + const FontGlyph* glyph = fallback_face->GetOrAppendGlyph(character_index, character, false); + if (glyph) + { + // Insert the new glyph into our own set of fallback glyphs + auto pair = fallback_glyphs.emplace(character, glyph->WeakCopy()); + if (pair.second) + is_layers_dirty = true; + + return true; + } + } + + return false; +} + +const FontGlyph* FontFaceHandleHarfBuzz::GetOrAppendGlyph(FontGlyphIndex glyph_index, Character& character, bool look_in_fallback_fonts) +{ + if (glyph_index == 0 && look_in_fallback_fonts && character != Character::Replacement) + { + auto fallback_glyph = GetOrAppendFallbackGlyph(character); + if (fallback_glyph != nullptr) + return fallback_glyph; + } + + auto glyph_location = glyphs.find(glyph_index); + if (glyph_location == glyphs.cend()) + { + bool result = false; + if (glyph_index != 0) + result = AppendGlyph(glyph_index, character); + + if (result) + { + glyph_location = glyphs.find(glyph_index); + if (glyph_location == glyphs.cend()) + { + RMLUI_ERROR; + return nullptr; + } + + is_layers_dirty = true; + } + else + return nullptr; + } + + if (glyph_index == 0) + character = Character::Replacement; + else if (character != glyph_location->second.character) + character = glyph_location->second.character; + + const FontGlyph* glyph = &glyph_location->second.bitmap; + return glyph; +} + +const FontGlyph* FontFaceHandleHarfBuzz::GetOrAppendFallbackGlyph(Character& character) +{ + auto fallback_glyph_location = fallback_glyphs.find(character); + if (fallback_glyph_location != fallback_glyphs.cend()) + return &fallback_glyph_location->second; + + bool result = AppendFallbackGlyph(character); + + if (result) + { + fallback_glyph_location = fallback_glyphs.find(character); + if (fallback_glyph_location == fallback_glyphs.cend()) + { + RMLUI_ERROR; + return nullptr; + } + + is_layers_dirty = true; + } + else + return nullptr; + + const FontGlyph* fallback_glyph = &fallback_glyph_location->second; + return fallback_glyph; +} + +bool FontFaceHandleHarfBuzz::AppendFallbackClusterGlyphs(StringView cluster, const TextShapingContext& text_shaping_context, + const LanguageDataMap& registered_languages, Span text_shaping_features) +{ + const hb_feature_t* shaping_features_pointer = !text_shaping_features.empty() ? text_shaping_features.data() : nullptr; + hb_buffer_t* shaping_buffer = hb_buffer_create(); + RMLUI_ASSERT(shaping_buffer != nullptr); + + TextFlowDirection text_direction = TextFlowDirection::LeftToRight; + + // Iterate through all available fallback font faces. + const int num_fallback_faces = FontProvider::CountFallbackFontFaces(); + for (int i = 0; i < num_fallback_faces; i++) + { + FontFaceHandleHarfBuzz* fallback_face = FontProvider::GetFallbackFontFace(i, metrics.size); + if (!fallback_face || fallback_face == this) + continue; + + // Insert the cluster into a shaping buffer and perform text shaping. + hb_buffer_clear_contents(shaping_buffer); + ConfigureTextShapingBuffer(shaping_buffer, cluster, text_shaping_context, registered_languages, &text_direction); + hb_buffer_add_utf8(shaping_buffer, cluster.begin(), (int)cluster.size(), 0, (int)cluster.size()); + hb_shape(fallback_face->hb_font, shaping_buffer, shaping_features_pointer, (unsigned int)text_shaping_features.size()); + + unsigned int glyph_count = 0; + hb_glyph_info_t* glyph_info = hb_buffer_get_glyph_infos(shaping_buffer, &glyph_count); + if (glyph_count == 0) + continue; + + Vector cluster_glyphs; + cluster_glyphs.reserve((size_t)glyph_count); + + int glyph_info_index_offset = text_direction == TextFlowDirection::RightToLeft ? (int)glyph_count - 1 : 0; + int cluster_string_offset = 0; + bool has_supported_glyph = false; + + // Create the cluster glyphs. + for (int g = 0; g < (int)glyph_count; ++g) + { + int glyph_info_index = g + glyph_info_index_offset; + RMLUI_ASSERT(glyph_info_index < (int)glyph_count); + + // Reverse the order of the glyphs in right-to-left text. + if (text_direction == TextFlowDirection::RightToLeft) + glyph_info_index_offset -= 2; + + Character character = Rml::StringUtilities::ToCharacter(cluster.begin() + cluster_string_offset, cluster.end()); + const FontGlyph* glyph = fallback_face->GetOrAppendGlyph(glyph_info[glyph_info_index].codepoint, character, false); + if (glyph && glyph->bitmap_data && glyph->bitmap_dimensions.x > 0 && glyph->bitmap_dimensions.y > 0) + { + cluster_glyphs.push_back(FontClusterGlyphData{glyph_info[glyph_info_index].codepoint, FontGlyphData{glyph->WeakCopy(), character}}); + if (!has_supported_glyph && glyph_info[glyph_info_index].codepoint != 0) + has_supported_glyph = true; + } + + cluster_string_offset += (int)Rml::StringUtilities::BytesUTF8(character); + RMLUI_ASSERT(cluster_string_offset <= (int)cluster.size()); + } + + if (cluster_glyphs.empty() || !has_supported_glyph) + continue; + + // Insert the cluster glyphs into our own set of fallback cluster glyphs. + auto pair = fallback_cluster_glyphs.emplace(cluster, std::move(cluster_glyphs)); + if (pair.second) + { + is_layers_dirty = true; + + // Populate quick-lookup glyph map to glyph search times during rendering. + for (const auto& cluster_glyph : pair.first->second) + { + uint64_t cluster_glyph_id = GetFallbackFontClusterGlyphLookupID(cluster_glyph.glyph_index, cluster_glyph.glyph_data.character); + fallback_cluster_glyphs_lookup.emplace(cluster_glyph_id, &cluster_glyph.glyph_data.bitmap); + } + } + + return true; + } + + return false; +} + +const Vector* FontFaceHandleHarfBuzz::GetOrAppendFallbackClusterGlyphs(StringView cluster, + const TextShapingContext& text_shaping_context, const LanguageDataMap& registered_languages, Span text_shaping_features) +{ + String cluster_string(cluster); + auto fallback_cluster_glyphs_location = fallback_cluster_glyphs.find(cluster_string); + if (fallback_cluster_glyphs_location != fallback_cluster_glyphs.cend()) + { + return &fallback_cluster_glyphs_location->second; + } + + bool result = AppendFallbackClusterGlyphs(cluster, text_shaping_context, registered_languages, text_shaping_features); + + if (result) + { + fallback_cluster_glyphs_location = fallback_cluster_glyphs.find(cluster_string); + if (fallback_cluster_glyphs_location == fallback_cluster_glyphs.cend()) + { + RMLUI_ERROR; + return nullptr; + } + + is_layers_dirty = true; + } + else + return nullptr; + + const Vector* fallback_cluster_glyphs = &fallback_cluster_glyphs_location->second; + return fallback_cluster_glyphs; +} + +FontFaceLayer* FontFaceHandleHarfBuzz::GetOrCreateLayer(const SharedPtr& font_effect) +{ + // Search for the font effect layer first, it may have been instanced before as part of a different configuration. + const FontEffect* font_effect_ptr = font_effect.get(); + auto it = + std::find_if(layers.begin(), layers.end(), [font_effect_ptr](const EffectLayerPair& pair) { return pair.font_effect == font_effect_ptr; }); + + if (it != layers.end()) + return it->layer.get(); + + // No existing effect matches, generate a new layer for the effect. + layers.push_back(EffectLayerPair{font_effect_ptr, nullptr}); + auto& layer = layers.back().layer; + + layer = Rml::MakeUnique(font_effect); + GenerateLayer(layer.get()); + + return layer.get(); +} + +bool FontFaceHandleHarfBuzz::GenerateLayer(FontFaceLayer* layer) +{ + RMLUI_ASSERT(layer); + const FontEffect* font_effect = layer->GetFontEffect(); + bool result = false; + + if (!font_effect) + { + result = layer->Generate(this); + } + else + { + // Determine which, if any, layer the new layer should copy its geometry and textures from. + FontFaceLayer* clone = nullptr; + bool clone_glyph_origins = true; + String generation_key; + size_t fingerprint = font_effect->GetFingerprint(); + + if (!font_effect->HasUniqueTexture()) + { + clone = base_layer; + clone_glyph_origins = false; + } + else + { + auto cache_iterator = layer_cache.find(fingerprint); + if (cache_iterator != layer_cache.end() && cache_iterator->second != layer) + clone = cache_iterator->second; + } + + // Create a new layer. + result = layer->Generate(this, clone, clone_glyph_origins); + + // Cache the layer in the layer cache if it generated its own textures (ie, didn't clone). + if (!clone) + layer_cache[fingerprint] = layer; + } + + return result; +} + +void FontFaceHandleHarfBuzz::ConfigureTextShapingBuffer(hb_buffer_t* shaping_buffer, StringView string, + const TextShapingContext& text_shaping_context, const LanguageDataMap& registered_languages, TextFlowDirection* determined_text_direction) const +{ + // Set the buffer's language based on the value of the element's 'lang' attribute. + hb_buffer_set_language(shaping_buffer, hb_language_from_string(text_shaping_context.language.c_str(), -1)); + + // Set the buffer's script. + hb_script_t script = HB_SCRIPT_UNKNOWN; + auto registered_language_location = registered_languages.find(text_shaping_context.language); + if (registered_language_location != registered_languages.cend()) + // Get script from registered language data. + script = hb_script_from_string(registered_language_location->second.script_code.c_str(), -1); + else + { + // Try to guess script from the first character of the string. + hb_unicode_funcs_t* unicode_functions = hb_unicode_funcs_get_default(); + if (unicode_functions != nullptr && !string.empty()) + { + Character first_character = Rml::StringUtilities::ToCharacter(string.begin(), string.end()); + script = hb_unicode_script(unicode_functions, (hb_codepoint_t)first_character); + } + } + + hb_buffer_set_script(shaping_buffer, script); + + // Set the buffer's text-flow direction based on the value of the element's 'dir' attribute. + hb_direction_t text_direction = HB_DIRECTION_LTR; + switch (text_shaping_context.text_direction) + { + case Rml::Style::Direction::Auto: + if (registered_language_location != registered_languages.cend()) + // Automatically determine the text-flow direction from the registered language. + switch (registered_language_location->second.text_flow_direction) + { + case TextFlowDirection::LeftToRight: text_direction = HB_DIRECTION_LTR; break; + case TextFlowDirection::RightToLeft: text_direction = HB_DIRECTION_RTL; break; + } + else + { + // Language not registered; determine best text-flow direction based on script. + text_direction = hb_script_get_horizontal_direction(script); + if (text_direction == HB_DIRECTION_INVALID) + // Some scripts support both horizontal directions of text flow; default to left-to-right. + text_direction = HB_DIRECTION_LTR; + } + break; + + case Rml::Style::Direction::Ltr: text_direction = HB_DIRECTION_LTR; break; + case Rml::Style::Direction::Rtl: text_direction = HB_DIRECTION_RTL; break; + } + + RMLUI_ASSERT(text_direction == HB_DIRECTION_LTR || text_direction == HB_DIRECTION_RTL); + hb_buffer_set_direction(shaping_buffer, text_direction); + if (determined_text_direction) + *determined_text_direction = text_direction == HB_DIRECTION_LTR ? TextFlowDirection::LeftToRight : TextFlowDirection::RightToLeft; + + // Set buffer flags for additional text-shaping configuration. + int buffer_flags = HB_BUFFER_FLAG_DEFAULT | HB_BUFFER_FLAG_BOT | HB_BUFFER_FLAG_EOT; + +#if HB_VERSION_ATLEAST(5, 1, 0) + if (script == HB_SCRIPT_ARABIC) + buffer_flags |= HB_BUFFER_FLAG_PRODUCE_SAFE_TO_INSERT_TATWEEL; +#endif +#if defined(RMLUI_DEBUG) && HB_VERSION_ATLEAST(3, 4, 0) + buffer_flags |= HB_BUFFER_FLAG_VERIFY; +#endif + + hb_buffer_set_flags(shaping_buffer, (hb_buffer_flags_t)buffer_flags); +} + +StringView FontFaceHandleHarfBuzz::GetCurrentClusterString(const hb_glyph_info_t* glyph_info, int glyph_count, int glyph_index, + Character first_character, StringView string, int& cluster_codepoint_count) const +{ + unsigned int cluster_index = glyph_info[glyph_index].cluster; + cluster_codepoint_count = 1; + int cluster_offset = glyph_index + 1; + int cluster_string_size = (int)Rml::StringUtilities::BytesUTF8(first_character); + + // Continue counting characters that are part of the same cluster. + while (cluster_offset < (int)glyph_count && glyph_info[cluster_offset].cluster == cluster_index) + { + Character current_cluster_character = + Rml::StringUtilities::ToCharacter(string.begin() + glyph_info[glyph_index].cluster + cluster_string_size, string.end()); + cluster_string_size += (int)Rml::StringUtilities::BytesUTF8(current_cluster_character); + + ++cluster_codepoint_count; + ++cluster_offset; + } + + return StringView(string.begin() + glyph_info[glyph_index].cluster, string.begin() + glyph_info[glyph_index].cluster + cluster_string_size); +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FontFaceHandleHarfBuzz.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FontFaceHandleHarfBuzz.h new file mode 100644 index 00000000..0168b26c --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FontFaceHandleHarfBuzz.h @@ -0,0 +1,179 @@ +#pragma once + +#include "FontEngineDefault/FontTypes.h" +#include "FontFaceLayer.h" +#include "FontGlyph.h" +#include "LanguageData.h" +#include + +using Rml::byte; +using Rml::Character; +using Rml::Colourb; +using Rml::FontEffect; +using Rml::FontEffectList; +using Rml::FontFaceHandleFreetype; +using Rml::FontGlyph; +using Rml::FontMetrics; +using Rml::Pair; +using Rml::Queue; +using Rml::RenderManager; +using Rml::SharedPtr; +using Rml::SmallUnorderedMap; +using Rml::Span; +using Rml::String; +using Rml::StringView; +using Rml::TextShapingContext; +using Rml::TexturedMeshList; +using Rml::UniquePtr; +using Rml::UnorderedMap; +using Rml::Vector; +using Rml::Vector2f; +using Rml::Vector2i; + +class FontFaceHandleHarfBuzz : public Rml::NonCopyMoveable { +public: + FontFaceHandleHarfBuzz(); + ~FontFaceHandleHarfBuzz(); + + bool Initialize(FontFaceHandleFreetype face, int font_size, bool load_default_glyphs); + + const FontMetrics& GetFontMetrics() const; + + const FontGlyphMap& GetGlyphs() const; + const FallbackFontGlyphMap& GetFallbackGlyphs() const; + const FallbackFontClusterGlyphsMap& GetFallbackClusterGlyphs() const; + + /// Returns the width a string will take up if rendered with this handle. + /// @param[in] string The string to measure. + /// @param[in] text_shaping_context Extra parameters that provide context for text shaping. + /// @param[in] registered_languages A list of languages registered in the font engine interface. + /// @param[in] prior_character The optionally-specified character that immediately precedes the string. This may have an impact on the string + /// width due to kerning. + /// @return The width, in pixels, this string will occupy if rendered with this handle. + int GetStringWidth(StringView string, const TextShapingContext& text_shaping_context, const LanguageDataMap& registered_languages, + Character prior_character = Character::Null); + + /// Generates, if required, the layer configuration for a given list of font effects. + /// @param[in] font_effects The list of font effects to generate the configuration for. + /// @return The index to use when generating geometry using this configuration. + int GenerateLayerConfiguration(const FontEffectList& font_effects); + /// Generates the texture data for a layer (for the texture database). + /// @param[out] texture_data The generated texture data. + /// @param[out] texture_dimensions The dimensions of the texture. + /// @param[in] font_effect The font effect used for the layer. + /// @param[in] texture_id The index of the texture within the layer to generate. + /// @param[in] handle_version The version of the handle data. Function returns false if out of date. + bool GenerateLayerTexture(Vector& texture_data, Vector2i& texture_dimensions, const FontEffect* font_effect, int texture_id, + int handle_version) const; + + /// Generates the geometry required to render a single line of text. + /// @param[in] render_manager The render manager responsible for rendering the string. + /// @param[out] mesh_list A list to place the new meshes into. + /// @param[in] string The string to render. + /// @param[in] position The position of the baseline of the first character to render. + /// @param[in] colour The colour to render the text. + /// @param[in] opacity The opacity of the text, should be applied to font effects. + /// @param[in] text_shaping_context Extra parameters that provide context for text shaping. + /// @param[in] registered_languages A list of languages registered in the font engine interface. + /// @param[in] layer_configuration Face configuration index to use for generating string. + /// @return The width, in pixels, of the string geometry. + int GenerateString(RenderManager& render_manager, TexturedMeshList& mesh_list, StringView string, Vector2f position, ColourbPremultiplied colour, + float opacity, const TextShapingContext& text_shaping_context, const LanguageDataMap& registered_languages, int layer_configuration = 0); + + /// Version is changed whenever the layers are dirtied, requiring regeneration of string geometry. + int GetVersion() const; + +private: + // Build and append glyph to 'glyphs'. + bool AppendGlyph(FontGlyphIndex glyph_index, Character character); + + // Build and append fallback glyph to 'fallback_glyphs'. + bool AppendFallbackGlyph(Character& character); + + /// Retrieve a glyph from the given code index, building and appending a new glyph if not already built. + /// @param[in] glyph_index The glyph index. + /// @param[in-out] character The character codepoint, can be changed e.g. to the replacement character if no glyph is found. + /// @param[in] look_in_fallback_fonts Look for the glyph in fallback fonts if not found locally, adding it to our fallback glyph map. + /// @return The font glyph for the returned glyph index. + const FontGlyph* GetOrAppendGlyph(FontGlyphIndex glyph_index, Character& character, bool look_in_fallback_fonts = true); + + /// Retrieve a fallback glyph from the given character, building and appending a new fallback glyph if not already built. + /// @param[in-out] character The character codepoint, can be changed e.g. to the replacement character if no glyph is found. + /// @return The fallback font glyph for character. + const FontGlyph* GetOrAppendFallbackGlyph(Character& character); + + // Build and append fallback cluster glyph to 'fallback_cluster_glyphs'. + bool AppendFallbackClusterGlyphs(StringView cluster, const TextShapingContext& text_shaping_context, const LanguageDataMap& registered_languages, + Span text_shaping_features); + + /// Retrieve a fallback cluster glyph from the given cluster and text-shaping/language data, building and appending a new fallback cluster glyph + /// if not already built. + /// @param[in] cluster The cluster. + /// @param[in] text_shaping_context Extra parameters that provide context for text shaping. + /// @param[in] registered_languages A list of languages registered in the font engine interface. + /// @param[in] text_shaping_features A list of OpenType feature settings used for text shaping. + /// @return The fallback glyphs of the cluster. + const Vector* GetOrAppendFallbackClusterGlyphs(StringView cluster, const TextShapingContext& text_shaping_context, + const LanguageDataMap& registered_languages, Span text_shaping_features); + + // Regenerate layers if dirty, such as after adding new glyphs. + bool UpdateLayersOnDirty(); + + // Create a new layer from the given font effect if it does not already exist. + FontFaceLayer* GetOrCreateLayer(const SharedPtr& font_effect); + + // (Re-)generate a layer in this font face handle. + bool GenerateLayer(FontFaceLayer* layer); + + /// Configure internal text shaping buffer values with context. + /// @param[in] shaping_buffer The shaping buffer to be configured. + /// @param[in] string The string currently being measured/rendered. + /// @param[in] text_shaping_context Extra parameters that provide context for text shaping. + /// @param[in] registered_languages A list of languages registered in the font engine interface. + /// @param[out] determined_text_direction The text direction that was used to shape the buffer. + void ConfigureTextShapingBuffer(struct hb_buffer_t* shaping_buffer, StringView string, const TextShapingContext& text_shaping_context, + const LanguageDataMap& registered_languages, TextFlowDirection* determined_text_direction) const; + + /// Creates a cluster string from shaped glyph info and index. + /// @param[in] glyph_info The shaped glyph info list (supplied by HarfBuzz). + /// @param[in] glyph_count The number of shaped glyphs in glyph_info. + /// @param[in] glyph_index The current glyph index. + /// @param[in] first_character The first character of the cluster. + /// @param[in] string The string currently being measured/rendered. + /// @param[out] cluster_codepoint_count The number of codepoints in the cluster (which may differ from the length of the returned string). + /// @return A UTF8 string built from all codepoints in the current glyph cluster. + StringView GetCurrentClusterString(const struct hb_glyph_info_t* glyph_info, int glyph_count, int glyph_index, Character first_character, + StringView string, int& cluster_codepoint_count) const; + + FontGlyphMap glyphs; + FallbackFontGlyphMap fallback_glyphs; + + FallbackFontClusterGlyphsMap fallback_cluster_glyphs; + FallbackFontClusterGlyphLookupMap fallback_cluster_glyphs_lookup; + + struct EffectLayerPair { + const FontEffect* font_effect; + UniquePtr layer; + }; + using FontLayerMap = Vector; + using FontLayerCache = SmallUnorderedMap; + using LayerConfiguration = Vector; + using LayerConfigurationList = Vector; + + // The list of all font layers, index by the effect that instanced them. + FontFaceLayer* base_layer; + FontLayerMap layers; + // Each font layer that generated geometry or textures, indexed by the font-effect's fingerprint key. + FontLayerCache layer_cache; + + bool is_layers_dirty = false; + int version = 0; + + // All configurations currently in use on this handle. New configurations will be generated as required. + LayerConfigurationList layer_configurations; + + FontMetrics metrics; + + FontFaceHandleFreetype ft_face; + struct hb_font_t* hb_font; +}; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FontFaceLayer.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FontFaceLayer.cpp new file mode 100644 index 00000000..c2986a83 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FontFaceLayer.cpp @@ -0,0 +1,366 @@ +#include "FontFaceLayer.h" +#include "FontFaceHandleHarfBuzz.h" +#include +#include + +FontFaceLayer::FontFaceLayer(const SharedPtr& _effect) : colour(255, 255, 255) +{ + effect = _effect; + if (effect) + colour = effect->GetColour(); +} + +FontFaceLayer::~FontFaceLayer() {} + +bool FontFaceLayer::Generate(const FontFaceHandleHarfBuzz* handle, const FontFaceLayer* clone, bool clone_glyph_origins) +{ + // Clear the old layout if it exists. + { + // @performance: We could be much smarter about this, e.g. such as adding new glyphs to the existing texture layout and textures. + // Right now we re-generate the whole thing, including textures. + texture_layout = TextureLayout{}; + character_boxes.clear(); + textures_owned.clear(); + textures_ptr = &textures_owned; + } + + const FontGlyphMap& glyphs = handle->GetGlyphs(); + const FallbackFontGlyphMap& fallback_glyphs = handle->GetFallbackGlyphs(); + const FallbackFontClusterGlyphsMap& fallback_cluster_glyphs = handle->GetFallbackClusterGlyphs(); + + // Generate the new layout. + if (clone) + { + // Clone the geometry and textures from the clone layer. + character_boxes = clone->character_boxes; + + // Point our textures to the cloned layer's textures. + textures_ptr = clone->textures_ptr; + + // Request the effect (if we have one) and adjust the origins as appropriate. + if (effect && !clone_glyph_origins) + { + for (auto& pair : glyphs) + { + FontGlyphIndex glyph_index = pair.first; + const FontGlyph& glyph = pair.second.bitmap; + const Character glyph_character = pair.second.character; + + CloneTextureBox(glyph, glyph_index, glyph_character, false); + } + + for (auto& pair : fallback_glyphs) + { + const Character glyph_character = pair.first; + const FontGlyph& glyph = pair.second; + + CloneTextureBox(glyph, 0, glyph_character, false); + } + + for (auto& pair : fallback_cluster_glyphs) + for (auto& cluster_glyph : pair.second) + { + const Character glyph_character = cluster_glyph.glyph_data.character; + const FontGlyph& glyph = cluster_glyph.glyph_data.bitmap; + + CloneTextureBox(glyph, cluster_glyph.glyph_index, glyph_character, true); + } + } + } + else + { + // Initialise the texture layout for the glyphs. + character_boxes.reserve(glyphs.size() + fallback_glyphs.size() + fallback_cluster_glyphs.size()); + for (auto& pair : glyphs) + { + FontGlyphIndex glyph_index = pair.first; + const FontGlyph& glyph = pair.second.bitmap; + Character glyph_character = pair.second.character; + + CreateTextureLayout(glyph, glyph_index, glyph_character, false); + } + + for (auto& pair : fallback_glyphs) + { + Character glyph_character = pair.first; + const FontGlyph& glyph = pair.second; + + CreateTextureLayout(glyph, 0, glyph_character, false); + } + + for (auto& pair : fallback_cluster_glyphs) + for (auto& cluster_glyph : pair.second) + { + const Character glyph_character = cluster_glyph.glyph_data.character; + const FontGlyph& glyph = cluster_glyph.glyph_data.bitmap; + + CreateTextureLayout(glyph, cluster_glyph.glyph_index, glyph_character, true); + } + + constexpr int max_texture_dimensions = 1024; + + // Generate the texture layout; this will position the glyph rectangles efficiently and + // allocate the texture data ready for writing. + if (!texture_layout.GenerateLayout(max_texture_dimensions)) + return false; + + // Iterate over each rectangle in the layout, copying the glyph data into the rectangle as + // appropriate and generating geometry. + for (int i = 0; i < texture_layout.GetNumRectangles(); ++i) + { + TextureLayoutRectangle& rectangle = texture_layout.GetRectangle(i); + const TextureLayoutTexture& texture = texture_layout.GetTexture(rectangle.GetTextureIndex()); + uint64_t font_glyph_id = rectangle.GetId(); + RMLUI_ASSERT(character_boxes.find(font_glyph_id) != character_boxes.end()); + TextureBox& box = character_boxes[font_glyph_id]; + + // Set the character's texture index. + box.texture_index = rectangle.GetTextureIndex(); + + // Generate the character's texture coordinates. + box.texcoords[0].x = float(rectangle.GetPosition().x) / float(texture.GetDimensions().x); + box.texcoords[0].y = float(rectangle.GetPosition().y) / float(texture.GetDimensions().y); + box.texcoords[1].x = float(rectangle.GetPosition().x + rectangle.GetDimensions().x) / float(texture.GetDimensions().x); + box.texcoords[1].y = float(rectangle.GetPosition().y + rectangle.GetDimensions().y) / float(texture.GetDimensions().y); + } + + const FontEffect* effect_ptr = effect.get(); + const int handle_version = handle->GetVersion(); + + // Generate the textures. + for (int i = 0; i < texture_layout.GetNumTextures(); ++i) + { + const int texture_id = i; + + CallbackTextureFunction texture_callback = [handle, effect_ptr, texture_id, handle_version]( + const CallbackTextureInterface& texture_interface) -> bool { + Vector2i dimensions; + Vector data; + if (!handle->GenerateLayerTexture(data, dimensions, effect_ptr, texture_id, handle_version) || data.empty()) + return false; + if (!texture_interface.GenerateTexture(data, dimensions)) + return false; + return true; + }; + + static_assert(std::is_nothrow_move_constructible::value, + "CallbackTextureSource must be nothrow move constructible so that it can be placed in the vector below."); + + textures_owned.emplace_back(std::move(texture_callback)); + } + } + + return true; +} + +bool FontFaceLayer::GenerateTexture(Vector& texture_data, Vector2i& texture_dimensions, int texture_id, const FontGlyphMaps& glyph_maps) +{ + if (texture_id < 0 || texture_id > texture_layout.GetNumTextures()) + return false; + + // Generate the texture data. + texture_data = texture_layout.GetTexture(texture_id).AllocateTexture(); + texture_dimensions = texture_layout.GetTexture(texture_id).GetDimensions(); + + for (int i = 0; i < texture_layout.GetNumRectangles(); ++i) + { + TextureLayoutRectangle& rectangle = texture_layout.GetRectangle(i); + uint64_t font_glyph_id = rectangle.GetId(); + RMLUI_ASSERT(character_boxes.find(font_glyph_id) != character_boxes.end()); + + TextureBox& box = character_boxes[font_glyph_id]; + + if (box.texture_index != texture_id) + continue; + + const FontGlyph* glyph = nullptr; + FontGlyphIndex glyph_index = GetFontGlyphIndexFromID(font_glyph_id); + Rml::Character glyph_character = GetCharacterCodepointFromID(font_glyph_id); + bool is_cluster = IsFontGlyphIDPartOfCluster(font_glyph_id); + + // Get the glyph bitmap by looking it up with the glyph index. + RMLUI_ASSERT(glyph_maps.glyphs != nullptr); + auto it = glyph_maps.glyphs->find(is_cluster ? 0 : glyph_index); + if (it == glyph_maps.glyphs->end() || glyph_index == 0 || is_cluster) + { + // Glyph was not found; attempt to find it in the fallback cluster glyphs. + if (is_cluster && glyph_maps.fallback_cluster_glyphs) + { + uint64_t cluster_glyph_lookup_id = GetFallbackFontClusterGlyphLookupID(glyph_index, glyph_character); + auto cluster_glyph_it = glyph_maps.fallback_cluster_glyphs->find(cluster_glyph_lookup_id); + if (cluster_glyph_it != glyph_maps.fallback_cluster_glyphs->end()) + glyph = cluster_glyph_it->second; + } + + // Glyph was still not found; attempt to find it in the fallback glyphs. + if (!glyph && !is_cluster && glyph_maps.fallback_glyphs) + { + auto fallback_it = glyph_maps.fallback_glyphs->find(glyph_character); + if (fallback_it != glyph_maps.fallback_glyphs->end()) + // Fallback glyph was found. + glyph = &fallback_it->second; + } + + if (!glyph) + { + if (it != glyph_maps.glyphs->end()) + // Fallback glyph was not found, but replacement glyph bitmap exists, so use it instead. + glyph = &it->second.bitmap; + else + // No fallback glyph nor replacement glyph bitmap was found; ignore this glyph. + continue; + } + } + else + // Glyph was found. + glyph = &it->second.bitmap; + + if (effect == nullptr) + { + // Copy the glyph's bitmap data into its allocated texture. + if (glyph->bitmap_data) + { + byte* destination = rectangle.GetTextureData(); + const byte* source = glyph->bitmap_data; + const int num_bytes_per_line = glyph->bitmap_dimensions.x * (glyph->color_format == ColorFormat::RGBA8 ? 4 : 1); + + for (int j = 0; j < glyph->bitmap_dimensions.y; ++j) + { + switch (glyph->color_format) + { + case ColorFormat::A8: + { + // We use premultiplied alpha, so copy the alpha into all four channels. + for (int k = 0; k < num_bytes_per_line; ++k) + for (int c = 0; c < 4; ++c) + destination[k * 4 + c] = source[k]; + } + break; + case ColorFormat::RGBA8: + { + memcpy(destination, source, num_bytes_per_line); + } + break; + } + + destination += rectangle.GetTextureStride(); + source += num_bytes_per_line; + } + } + } + else + effect->GenerateGlyphTexture(rectangle.GetTextureData(), Vector2i(box.dimensions), rectangle.GetTextureStride(), *glyph); + } + + return true; +} + +const FontEffect* FontFaceLayer::GetFontEffect() const +{ + return effect.get(); +} + +Texture FontFaceLayer::GetTexture(RenderManager& render_manager, int index) +{ + RMLUI_ASSERT(index >= 0); + RMLUI_ASSERT(index < GetNumTextures()); + + return (*textures_ptr)[index].GetTexture(render_manager); +} + +int FontFaceLayer::GetNumTextures() const +{ + return (int)textures_ptr->size(); +} + +ColourbPremultiplied FontFaceLayer::GetColour(float opacity) const +{ + return colour.ToPremultiplied(opacity); +} + +uint64_t FontFaceLayer::CreateFontGlyphID(const FontGlyphIndex glyph_index, const Character character_code, bool is_cluster) const +{ + // Font glyph ID details: + // 64 48 32 16 + // 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 + // | <---------- glyph_index ----------> | ^| <-------- character_code --------> | + // | + // is_cluster + // + // The maximum valid Unicode codepoint is U+10FFFF (slightly larger than 2^20), + // so dedicating the 32nd bit of 'character_code' to 'is_cluster' shouldn't cause any issues. + + uint64_t font_glyph_id = (static_cast(glyph_index) << (sizeof(Character) * 8)) | static_cast(character_code); + + if (is_cluster) + font_glyph_id |= font_glyph_id_cluster_bit_mask; + else + font_glyph_id &= ~font_glyph_id_cluster_bit_mask; + + return font_glyph_id; +} + +FontGlyphIndex FontFaceLayer::GetFontGlyphIndexFromID(const uint64_t glyph_id) const +{ + return static_cast(glyph_id >> (sizeof(Character) * 8)); +} + +Character FontFaceLayer::GetCharacterCodepointFromID(const uint64_t glyph_id) const +{ + uint64_t character_codepoint = glyph_id & static_cast>(-1); + character_codepoint &= ~font_glyph_id_cluster_bit_mask; + + return static_cast(character_codepoint); +} + +bool FontFaceLayer::IsFontGlyphIDPartOfCluster(const uint64_t glyph_id) const +{ + return glyph_id & font_glyph_id_cluster_bit_mask; +} + +void FontFaceLayer::CreateTextureLayout(const FontGlyph& glyph, FontGlyphIndex glyph_index, Character glyph_character, bool is_cluster) +{ + Vector2i glyph_origin(0, 0); + Vector2i glyph_dimensions = glyph.bitmap_dimensions; + + // Adjust glyph origin / dimensions for the font effect. + if (effect) + { + if (!effect->GetGlyphMetrics(glyph_origin, glyph_dimensions, glyph)) + return; + } + + TextureBox box; + box.origin = Vector2f(float(glyph_origin.x + glyph.bearing.x), float(glyph_origin.y - glyph.bearing.y)); + box.dimensions = Vector2f(glyph_dimensions); + + RMLUI_ASSERT(box.dimensions.x >= 0 && box.dimensions.y >= 0); + + uint64_t font_glyph_id = CreateFontGlyphID(glyph_index, glyph_character, is_cluster); + character_boxes[font_glyph_id] = box; + + // Add the character's dimensions into the texture layout engine. + texture_layout.AddRectangle(font_glyph_id, glyph_dimensions); +} + +void FontFaceLayer::CloneTextureBox(const FontGlyph& glyph, FontGlyphIndex glyph_index, Character glyph_character, bool is_cluster) +{ + auto it = character_boxes.find(CreateFontGlyphID(glyph_index, glyph_character, is_cluster)); + if (it == character_boxes.end()) + { + // This can happen if the layers have been dirtied in FontHandleDefault. We will + // probably be regenerated soon, just skip the character for now. + return; + } + + TextureBox& box = it->second; + + Vector2i glyph_origin = Vector2i(box.origin); + Vector2i glyph_dimensions = Vector2i(box.dimensions); + + if (effect->GetGlyphMetrics(glyph_origin, glyph_dimensions, glyph)) + box.origin = Vector2f(glyph_origin); + else + box.texture_index = -1; + +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FontFaceLayer.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FontFaceLayer.h new file mode 100644 index 00000000..8ff2d148 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FontFaceLayer.h @@ -0,0 +1,136 @@ +#pragma once + +#include "FontGlyph.h" +#include "TextureLayout.h" +#include "TextureLayoutRectangle.h" +#include + +using Rml::byte; +using Rml::CallbackTextureFunction; +using Rml::CallbackTextureInterface; +using Rml::CallbackTextureSource; +using Rml::Character; +using Rml::ColorFormat; +using Rml::Colourb; +using Rml::ColourbPremultiplied; +using Rml::FontEffect; +using Rml::FontGlyph; +using Rml::Geometry; +using Rml::Mesh; +using Rml::RenderManager; +using Rml::SharedPtr; +using Rml::Texture; +using Rml::TexturedMesh; +using Rml::UniquePtr; +using Rml::UnorderedMap; +using Rml::Vector; +using Rml::Vector2f; +using Rml::Vector2i; + +class FontFaceHandleHarfBuzz; + +/** + A textured layer stored as part of a font face handle. Each handle will have at least a base + layer for the standard font. Further layers can be added to allow rendering of text effects. + + Modified to support HarfBuzz text shaping. + */ + +class FontFaceLayer { +public: + FontFaceLayer(const SharedPtr& _effect); + ~FontFaceLayer(); + + /// Generates or re-generates the character and texture data for the layer. + /// @param[in] handle The handle generating this layer. + /// @param[in] clone The layer to optionally clone geometry and texture data from. + /// @param[in] clone_glyph_origins True to keep the character origins from the cloned layer, false to generate new ones. + /// @return True if the layer was generated successfully, false if not. + bool Generate(const FontFaceHandleHarfBuzz* handle, const FontFaceLayer* clone = nullptr, bool clone_glyph_origins = false); + + /// Generates the texture data for a layer (for the texture database). + /// @param[out] texture_data The generated texture data. + /// @param[out] texture_dimensions The dimensions of the texture. + /// @param[in] texture_id The index of the texture within the layer to generate. + /// @param[in] glyph_maps The glyph maps required by the font face handle. + bool GenerateTexture(Vector& texture_data, Vector2i& texture_dimensions, int texture_id, const FontGlyphMaps& glyph_maps); + + /// Generates the geometry required to render a single character. + /// @param[out] mesh_list An array of meshes this layer will write to. It must be at least as big as the number of textures in this layer. + /// @param[in] character_code The character to generate geometry for. + /// @param[in] is_cluster Whether the glyph is part of a cluster or not. + /// @param[in] position The position of the baseline. + /// @param[in] colour The colour of the string. + inline void GenerateGeometry(TexturedMesh* mesh_list, const FontGlyphIndex glyph_index, const Character character_code, bool is_cluster, + const Vector2f position, const ColourbPremultiplied colour) const + { + auto it = character_boxes.find(CreateFontGlyphID(glyph_index, character_code, is_cluster)); + if (it == character_boxes.end()) + return; + + const TextureBox& box = it->second; + + if (box.texture_index < 0) + return; + + // Generate the geometry for the character. + Mesh& mesh = mesh_list[box.texture_index].mesh; + Rml::MeshUtilities::GenerateQuad(mesh, (position + box.origin).Round(), box.dimensions, colour, box.texcoords[0], box.texcoords[1]); + } + + /// Returns the effect used to generate the layer. + const FontEffect* GetFontEffect() const; + + /// Returns one of the layer's textures. + Texture GetTexture(RenderManager& render_manager, int index); + /// Returns the number of textures employed by this layer. + int GetNumTextures() const; + + /// Returns the layer's colour after applying the given opacity. + ColourbPremultiplied GetColour(float opacity) const; + +private: + /// Creates an ID for a font glyph from a glyph index and character codepoint. + uint64_t CreateFontGlyphID(const FontGlyphIndex glyph_index, const Character character_code, bool is_cluster) const; + + /// Retrieves the font glyph index from a font glyph ID. + FontGlyphIndex GetFontGlyphIndexFromID(const uint64_t glyph_id) const; + + /// Retrieves the character from a font glyph ID. + Character GetCharacterCodepointFromID(const uint64_t glyph_id) const; + + /// Determines if a font glyph ID is part of a cluster instead of a single glyph. + bool IsFontGlyphIDPartOfCluster(const uint64_t glyph_id) const; + + /// Creates a texture layout from the given glyph bitmap and data. + void CreateTextureLayout(const FontGlyph& glyph, FontGlyphIndex glyph_index, Character glyph_character, bool is_cluster); + + /// Clones the given glyph bitmap and data into a texture box. + void CloneTextureBox(const FontGlyph& glyph, FontGlyphIndex glyph_index, Character glyph_character, bool is_cluster); + + struct TextureBox { + // The offset, in pixels, of the baseline from the start of this character's geometry. + Vector2f origin; + // The width and height, in pixels, of this character's geometry. + Vector2f dimensions; + // The texture coordinates for the character's geometry. + Vector2f texcoords[2]; + + // The texture this character renders from. + int texture_index = -1; + }; + + using CharacterMap = UnorderedMap; + using TextureList = Vector; + + static constexpr uint64_t font_glyph_id_cluster_bit_mask = 1ull << 31ull; + + SharedPtr effect; + + TextureList textures_owned; + TextureList* textures_ptr = &textures_owned; + + TextureLayout texture_layout; + CharacterMap character_boxes; + Colourb colour; +}; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FontFamily.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FontFamily.cpp new file mode 100644 index 00000000..78106412 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FontFamily.cpp @@ -0,0 +1,62 @@ +#include "FontFamily.h" +#include "FontFace.h" +#include "FontFaceHandleHarfBuzz.h" +#include + +FontFamily::FontFamily(const String& name) : name(name) {} + +FontFamily::~FontFamily() +{ + // Multiple face entries may share memory within a single font family, although only one of them owns it. Here we make sure that all the face + // destructors are run before all the memory is released. This way we don't leave any hanging references to invalidated memory. + for (FontFaceEntry& entry : font_faces) + entry.face.reset(); +} + +FontFaceHandleHarfBuzz* FontFamily::GetFaceHandle(Style::FontStyle style, Style::FontWeight weight, int size) +{ + int best_dist = INT_MAX; + FontFace* matching_face = nullptr; + for (size_t i = 0; i < font_faces.size(); i++) + { + FontFace* face = font_faces[i].face.get(); + + if (face->GetStyle() == style) + { + const int dist = Rml::Math::Absolute((int)face->GetWeight() - (int)weight); + if (dist == 0) + { + // Direct match for weight, break the loop early. + matching_face = face; + break; + } + else if (dist < best_dist) + { + // Best match so far for weight, store the face and dist. + matching_face = face; + best_dist = dist; + } + } + } + + if (!matching_face) + return nullptr; + + return matching_face->GetHandle(size, true); +} + +FontFace* FontFamily::AddFace(FontFaceHandleFreetype ft_face, Style::FontStyle style, Style::FontWeight weight, UniquePtr face_memory) +{ + auto face = Rml::MakeUnique(ft_face, style, weight); + FontFace* result = face.get(); + + font_faces.push_back(FontFaceEntry{std::move(face), std::move(face_memory)}); + + return result; +} + +void FontFamily::ReleaseFontResources() +{ + for (auto& entry : font_faces) + entry.face->ReleaseFontResources(); +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FontFamily.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FontFamily.h new file mode 100644 index 00000000..e31456eb --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FontFamily.h @@ -0,0 +1,54 @@ +#pragma once + +#include "FontEngineDefault/FontTypes.h" +#include + +using Rml::byte; +using Rml::FontFaceHandleFreetype; +using Rml::String; +using Rml::UniquePtr; +using Rml::Vector; +namespace Style = Rml::Style; + +class FontFace; +class FontFaceHandleHarfBuzz; + +/** + Modified to support HarfBuzz text shaping. + */ + +class FontFamily { +public: + FontFamily(const String& name); + ~FontFamily(); + + /// Returns a handle to the most appropriate font in the family, at the correct size. + /// @param[in] style The style of the desired handle. + /// @param[in] weight The weight of the desired handle. + /// @param[in] size The size of desired handle, in points. + /// @return A valid handle if a matching (or closely matching) font face was found, nullptr otherwise. + FontFaceHandleHarfBuzz* GetFaceHandle(Style::FontStyle style, Style::FontWeight weight, int size); + + /// Adds a new face to the family. + /// @param[in] ft_face The previously loaded FreeType face. + /// @param[in] style The style of the new face. + /// @param[in] weight The weight of the new face. + /// @param[in] face_memory Optionally pass ownership of the face's memory to the face itself, automatically releasing it on destruction. + /// @return True if the face was loaded successfully, false otherwise. + FontFace* AddFace(FontFaceHandleFreetype ft_face, Style::FontStyle style, Style::FontWeight weight, UniquePtr face_memory); + + /// Releases resources owned by sized font faces, including their textures and rendered glyphs. + void ReleaseFontResources(); + +protected: + String name; + + struct FontFaceEntry { + UniquePtr face; + // Only filled if we own the memory used by the face's FreeType handle. May be shared with other faces in this family. + UniquePtr face_memory; + }; + + using FontFaceList = Vector; + FontFaceList font_faces; +}; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FontGlyph.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FontGlyph.h new file mode 100644 index 00000000..79f90551 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FontGlyph.h @@ -0,0 +1,40 @@ +#pragma once + +#include + +using FontGlyphIndex = uint32_t; + +struct FontGlyphData +{ + Rml::FontGlyph bitmap; + Rml::Character character; +}; + +struct FontGlyphReference +{ + const Rml::FontGlyph* bitmap; + Rml::Character character; +}; + +struct FontClusterGlyphData +{ + FontGlyphIndex glyph_index; + FontGlyphData glyph_data; +}; + +using FontGlyphMap = Rml::UnorderedMap; +using FallbackFontGlyphMap = Rml::UnorderedMap; +using FallbackFontClusterGlyphsMap = Rml::UnorderedMap>; +using FallbackFontClusterGlyphLookupMap = Rml::UnorderedMap; + +struct FontGlyphMaps { + const FontGlyphMap* glyphs; + const FallbackFontGlyphMap* fallback_glyphs; + const FallbackFontClusterGlyphLookupMap* fallback_cluster_glyphs; +}; + +inline uint64_t GetFallbackFontClusterGlyphLookupID(FontGlyphIndex glyph_index, Rml::Character character) +{ + // Combine 32-bit glyph index and 32-bit character into a single 64-bit integer. + return (static_cast(glyph_index) << (sizeof(Rml::Character) * 8)) | static_cast(character); +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FontProvider.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FontProvider.cpp new file mode 100644 index 00000000..58673703 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FontProvider.cpp @@ -0,0 +1,231 @@ +#include "FontProvider.h" +#include "FontEngineDefault/../ComputeProperty.h" +#include "FontEngineDefault/FreeTypeInterface.h" +#include "FontFace.h" +#include "FontFaceHandleHarfBuzz.h" +#include "FontFamily.h" +#include + +static FontProvider* g_font_provider = nullptr; + +FontProvider::FontProvider() +{ + RMLUI_ASSERT(!g_font_provider); +} + +FontProvider::~FontProvider() +{ + RMLUI_ASSERT(g_font_provider == this); +} + +bool FontProvider::Initialise() +{ + RMLUI_ASSERT(!g_font_provider); + if (!Rml::FreeType::Initialise()) + return false; + g_font_provider = new FontProvider; + return true; +} + +void FontProvider::Shutdown() +{ + RMLUI_ASSERT(g_font_provider); + delete g_font_provider; + g_font_provider = nullptr; + Rml::FreeType::Shutdown(); +} + +FontProvider& FontProvider::Get() +{ + RMLUI_ASSERT(g_font_provider); + return *g_font_provider; +} + +FontFaceHandleHarfBuzz* FontProvider::GetFontFaceHandle(const String& family, Style::FontStyle style, Style::FontWeight weight, int size) +{ + RMLUI_ASSERTMSG(family == Rml::StringUtilities::ToLower(family), "Font family name must be converted to lowercase before entering here."); + + FontFamilyMap& families = Get().font_families; + + auto it = families.find(family); + if (it == families.end()) + return nullptr; + + return it->second->GetFaceHandle(style, weight, size); +} + +int FontProvider::CountFallbackFontFaces() +{ + return (int)Get().fallback_font_faces.size(); +} + +FontFaceHandleHarfBuzz* FontProvider::GetFallbackFontFace(int index, int font_size) +{ + auto& faces = FontProvider::Get().fallback_font_faces; + + if (index >= 0 && index < (int)faces.size()) + return faces[index]->GetHandle(font_size, false); + + return nullptr; +} + +void FontProvider::ReleaseFontResources() +{ + RMLUI_ASSERT(g_font_provider); + for (auto& name_family : g_font_provider->font_families) + name_family.second->ReleaseFontResources(); +} + +bool FontProvider::LoadFontFace(const String& file_name, int face_index, bool fallback_face, Style::FontWeight weight) +{ + Rml::FileInterface* file_interface = Rml::GetFileInterface(); + Rml::FileHandle handle = file_interface->Open(file_name); + + if (!handle) + { + Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to load font face from %s, could not open file.", file_name.c_str()); + return false; + } + + size_t length = file_interface->Length(handle); + + auto buffer_ptr = UniquePtr(new byte[length]); + byte* buffer = buffer_ptr.get(); + file_interface->Read(buffer, length, handle); + file_interface->Close(handle); + + bool result = Get().LoadFontFace({buffer, length}, face_index, fallback_face, std::move(buffer_ptr), file_name, {}, Style::FontStyle::Normal, weight); + + return result; +} + +bool FontProvider::LoadFontFace(Span data, int face_index, const String& font_family, Style::FontStyle style, Style::FontWeight weight, + bool fallback_face) +{ + const String source = "memory"; + + bool result = Get().LoadFontFace(data, face_index, fallback_face, nullptr, source, font_family, style, weight); + + return result; +} + +bool FontProvider::LoadFontFace(Span data, int face_index, bool fallback_face, UniquePtr face_memory, const String& source, String font_family, + Style::FontStyle style, Style::FontWeight weight) +{ + using Style::FontWeight; + + Vector face_variations; + if (!Rml::FreeType::GetFaceVariations(data, face_variations, face_index)) + { + Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to load font face from '%s': Invalid or unsupported font face file format.", source.c_str()); + return false; + } + + Vector load_variations; + if (face_variations.empty()) + { + load_variations.push_back(Rml::FaceVariation{Style::FontWeight::Auto, 0, 0}); + } + else + { + // Iterate through all the face variations and pick the ones to load. The list is already sorted by (weight, width). When weight is set to + // 'auto' we load all the weights of the face. However, we only want to load one width for each weight. + for (auto it = face_variations.begin(); it != face_variations.end();) + { + if (weight != FontWeight::Auto && it->weight != weight) + { + ++it; + continue; + } + + // We don't currently have any way for users to select widths, so we search for a regular (medium) value here. + constexpr int search_width = 100; + const FontWeight current_weight = it->weight; + + int best_width_distance = Rml::Math::Absolute((int)it->width - search_width); + auto it_best_width = it; + + // Search forward to find the best 'width' with the same weight. + for (++it; it != face_variations.end(); ++it) + { + if (it->weight != current_weight) + break; + + const int width_distance = Rml::Math::Absolute((int)it->width - search_width); + if (width_distance < best_width_distance) + { + best_width_distance = width_distance; + it_best_width = it; + } + } + + load_variations.push_back(*it_best_width); + } + } + + if (load_variations.empty()) + { + Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to load font face from '%s': Could not locate face with weight %d.", source.c_str(), + (int)weight); + return false; + } + + for (const Rml::FaceVariation& variation : load_variations) + { + FontFaceHandleFreetype ft_face = Rml::FreeType::LoadFace(data, source, face_index, variation.named_instance_index); + if (!ft_face) + return false; + + if (font_family.empty()) + Rml::FreeType::GetFaceStyle(ft_face, &font_family, &style, nullptr); + if (weight == FontWeight::Auto) + Rml::FreeType::GetFaceStyle(ft_face, nullptr, nullptr, &weight); + + const FontWeight variation_weight = (variation.weight == FontWeight::Auto ? weight : variation.weight); + const String font_face_description = Rml::GetFontFaceDescription(font_family, style, variation_weight); + + if (!AddFace(ft_face, font_family, style, variation_weight, fallback_face, std::move(face_memory))) + { + Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to load font face %s from '%s'.", font_face_description.c_str(), source.c_str()); + return false; + } + + Rml::Log::Message(Rml::Log::LT_INFO, "Loaded font face %s from '%s'.", font_face_description.c_str(), source.c_str()); + } + + return true; +} + +bool FontProvider::AddFace(FontFaceHandleFreetype face, const String& family, Style::FontStyle style, Style::FontWeight weight, + bool fallback_face, UniquePtr face_memory) +{ + if (family.empty() || weight == Style::FontWeight::Auto) + return false; + + String family_lower = Rml::StringUtilities::ToLower(family); + FontFamily* font_family = nullptr; + auto it = font_families.find(family_lower); + if (it != font_families.end()) + { + font_family = (FontFamily*)it->second.get(); + } + else + { + auto font_family_ptr = Rml::MakeUnique(family_lower); + font_family = font_family_ptr.get(); + font_families[family_lower] = std::move(font_family_ptr); + } + + FontFace* font_face_result = font_family->AddFace(face, style, weight, std::move(face_memory)); + + if (font_face_result && fallback_face) + { + auto it_fallback_face = std::find(fallback_font_faces.begin(), fallback_font_faces.end(), font_face_result); + if (it_fallback_face == fallback_font_faces.end()) + { + fallback_font_faces.push_back(font_face_result); + } + } + + return static_cast(font_face_result); +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FontProvider.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FontProvider.h new file mode 100644 index 00000000..49f41e88 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FontProvider.h @@ -0,0 +1,74 @@ +#pragma once + +#include "FontEngineDefault/FontTypes.h" +#include + +using Rml::byte; +using Rml::FontFaceHandleFreetype; +using Rml::Span; +using Rml::String; +using Rml::UniquePtr; +using Rml::UnorderedMap; +using Rml::Vector; +namespace Style = Rml::Style; + +class FontFace; +class FontFamily; +class FontFaceHandleHarfBuzz; + +/** + The font provider contains all font families currently in use by RmlUi. + Modified to support HarfBuzz text shaping. + */ + +class FontProvider { +public: + static bool Initialise(); + static void Shutdown(); + + /// Returns a handle to a font face that can be used to position and render text. This will return the closest match + /// it can find, but in the event a font family is requested that does not exist, nullptr will be returned instead of a + /// valid handle. + /// @param[in] family The family of the desired font handle. + /// @param[in] style The style of the desired font handle. + /// @param[in] weight The weight of the desired font handle. + /// @param[in] size The size of desired handle, in points. + /// @return A valid handle if a matching (or closely matching) font face was found, nullptr otherwise. + static FontFaceHandleHarfBuzz* GetFontFaceHandle(const String& family, Style::FontStyle style, Style::FontWeight weight, int size); + + /// Adds a new font face to the database. The face's family, style and weight will be determined from the face itself. + static bool LoadFontFace(const String& file_name, int face_index, bool fallback_face, Style::FontWeight weight = Style::FontWeight::Auto); + + /// Adds a new font face from memory. + static bool LoadFontFace(Span data, int face_index, const String& font_family, Style::FontStyle style, Style::FontWeight weight, bool fallback_face); + + /// Return the number of fallback font faces. + static int CountFallbackFontFaces(); + + /// Return a font face handle with the given index, at the given font size. + static FontFaceHandleHarfBuzz* GetFallbackFontFace(int index, int font_size); + + /// Releases resources owned by sized font faces, including their textures and rendered glyphs. + static void ReleaseFontResources(); + +private: + FontProvider(); + ~FontProvider(); + + static FontProvider& Get(); + + bool LoadFontFace(Span data, int face_index, bool fallback_face, UniquePtr face_memory, const String& source, String font_family, + Style::FontStyle style, + Style::FontWeight weight); + + bool AddFace(FontFaceHandleFreetype face, const String& family, Style::FontStyle style, Style::FontWeight weight, + bool fallback_face, UniquePtr face_memory); + + using FontFaceList = Vector; + using FontFamilyMap = UnorderedMap>; + + FontFamilyMap font_families; + FontFaceList fallback_font_faces; + + static const String debugger_font_family_name; +}; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FreeTypeInterface.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FreeTypeInterface.cpp new file mode 100644 index 00000000..165907fd --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FreeTypeInterface.cpp @@ -0,0 +1,408 @@ +#include "FreeTypeInterface.h" +#include +#include +#include +#include FT_FREETYPE_H +#include FT_MULTIPLE_MASTERS_H +#include FT_TRUETYPE_TABLES_H + +namespace FreeType { +static bool BuildGlyph(FT_Face ft_face, const FontGlyphIndex glyph_index, Character character, FontGlyphMap& glyphs, + const float bitmap_scaling_factor); +static void BuildGlyphMap(FT_Face ft_face, int size, FontGlyphMap& glyphs, const float bitmap_scaling_factor, const bool load_default_glyphs); +static void GenerateMetrics(FT_Face ft_face, FontMetrics& metrics, float bitmap_scaling_factor); +static bool SetFontSize(FT_Face ft_face, int font_size, float& out_bitmap_scaling_factor); +static void BitmapDownscale(Rml::byte* bitmap_new, const int new_width, const int new_height, const Rml::byte* bitmap_source, const int width, + const int height, const int pitch, const Rml::ColorFormat color_format); + +#ifdef RMLUI_DEBUG +#define FT_ERROR_START_LIST switch ( error_code ) { +#define FT_ERRORDEF( e, v, s ) case v: return s; +#define FT_ERROR_END_LIST } +// https://freetype.org/freetype2/docs/reference/ft2-error_enumerations.html +static const char* GetFreeTypeErrorString(FT_Error error_code) +{ +#undef FTERRORS_H_ +#include "freetype/fterrors.h" + return ""; +} +#endif + +bool InitialiseFaceHandle(FontFaceHandleFreetype face, int font_size, FontGlyphMap& glyphs, FontMetrics& metrics, bool load_default_glyphs) +{ + FT_Face ft_face = (FT_Face)face; + + metrics.size = font_size; + + float bitmap_scaling_factor = 1.0f; + if (!SetFontSize(ft_face, font_size, bitmap_scaling_factor)) + return false; + + // Construct the initial list of glyphs. + BuildGlyphMap(ft_face, font_size, glyphs, bitmap_scaling_factor, load_default_glyphs); + + // Generate the metrics for the handle. + GenerateMetrics(ft_face, metrics, bitmap_scaling_factor); + + return true; +} + +bool AppendGlyph(FontFaceHandleFreetype face, int font_size, FontGlyphIndex glyph_index, Character character, FontGlyphMap& glyphs) +{ + FT_Face ft_face = (FT_Face)face; + + RMLUI_ASSERT(glyphs.find(glyph_index) == glyphs.end()); + RMLUI_ASSERT(ft_face); + + // Set face size again in case it was used at another size in another font face handle. + float bitmap_scaling_factor = 1.0f; + if (!SetFontSize(ft_face, font_size, bitmap_scaling_factor)) + return false; + + if (!BuildGlyph(ft_face, glyph_index, character, glyphs, bitmap_scaling_factor)) + return false; + + return true; +} + +FontGlyphIndex GetGlyphIndexFromCharacter(FontFaceHandleFreetype face, Character character) +{ + return FT_Get_Char_Index((FT_Face)face, (FT_ULong)character); +} + +static bool BuildGlyph(FT_Face ft_face, const FontGlyphIndex glyph_index, Character character, FontGlyphMap& glyphs, + const float bitmap_scaling_factor) +{ + if (glyph_index == 0) + return false; + + FT_Error error = FT_Load_Glyph(ft_face, glyph_index, FT_LOAD_COLOR); + if (error != 0) + { +#ifdef RMLUI_DEBUG + auto error_message = GetFreeTypeErrorString(error); + Rml::Log::Message(Rml::Log::LT_WARNING, "Unable to load glyph at index '%u' in font face '%s %s'; FreeType error 0x%x: %s.", + (unsigned int)glyph_index, ft_face->family_name, ft_face->style_name, error, error_message); +#else + Rml::Log::Message(Rml::Log::LT_WARNING, "Unable to load glyph at index '%u' in font face '%s %s'; FreeType error 0x%x.", + (unsigned int)glyph_index, ft_face->family_name, ft_face->style_name, error); +#endif + return false; + } + + error = FT_Render_Glyph(ft_face->glyph, FT_RENDER_MODE_NORMAL); + if (error != 0) + { +#ifdef RMLUI_DEBUG + auto error_message = GetFreeTypeErrorString(error); + Rml::Log::Message(Rml::Log::LT_WARNING, "Unable to render glyph at index '%u' in font face '%s %s'; FreeType error 0x%x: %s.", + (unsigned int)glyph_index, ft_face->family_name, ft_face->style_name, error, error_message); +#else + Rml::Log::Message(Rml::Log::LT_WARNING, "Unable to render glyph at index '%u' in font face '%s %s'; FreeType error 0x%x.", + (unsigned int)glyph_index, ft_face->family_name, ft_face->style_name, error); +#endif + return false; + } + + auto result = glyphs.emplace(glyph_index, FontGlyphData{Rml::FontGlyph{}, character}); + if (!result.second) + { + Rml::Log::Message(Rml::Log::LT_WARNING, "Glyph index '%u' is already loaded in the font face '%s %s'.", (unsigned int)glyph_index, + ft_face->family_name, ft_face->style_name); + return false; + } + + Rml::FontGlyph& glyph = result.first->second.bitmap; + + FT_GlyphSlot ft_glyph = ft_face->glyph; + + glyph.bearing.x = ft_glyph->bitmap_left; + glyph.bearing.y = ft_glyph->bitmap_top; + glyph.advance = ft_glyph->metrics.horiAdvance >> 6; + glyph.bitmap_dimensions.x = ft_glyph->bitmap.width; + glyph.bitmap_dimensions.y = ft_glyph->bitmap.rows; + + // Determine new metrics if we need to scale the bitmap received from FreeType. Only allow bitmap downscaling. + const bool scale_bitmap = (bitmap_scaling_factor < 1.f); + if (scale_bitmap) + { + glyph.bearing = Rml::Vector2i(Rml::Vector2f(glyph.bearing) * bitmap_scaling_factor); + glyph.advance = int(float(glyph.advance) * bitmap_scaling_factor); + glyph.bitmap_dimensions = Rml::Vector2i(Rml::Vector2f(glyph.bitmap_dimensions) * bitmap_scaling_factor); + } + + // Copy the glyph's bitmap data from the FreeType glyph handle to our glyph handle. + if (glyph.bitmap_dimensions.x * glyph.bitmap_dimensions.y != 0) + { + // Check if the pixel mode is supported. + if (ft_glyph->bitmap.pixel_mode != FT_PIXEL_MODE_MONO && ft_glyph->bitmap.pixel_mode != FT_PIXEL_MODE_GRAY && + ft_glyph->bitmap.pixel_mode != FT_PIXEL_MODE_BGRA) + { + Rml::Log::Message(Rml::Log::LT_WARNING, "Unable to render glyph on the font face '%s %s': unsupported pixel mode (%d).", + ft_glyph->face->family_name, ft_glyph->face->style_name, ft_glyph->bitmap.pixel_mode); + } + else if (ft_glyph->bitmap.pixel_mode == FT_PIXEL_MODE_MONO && scale_bitmap) + { + Rml::Log::Message(Rml::Log::LT_WARNING, "Unable to render glyph on the font face '%s %s': bitmap scaling unsupported in mono pixel mode.", + ft_glyph->face->family_name, ft_glyph->face->style_name); + } + else + { + const int num_bytes_per_pixel = (ft_glyph->bitmap.pixel_mode == FT_PIXEL_MODE_BGRA ? 4 : 1); + glyph.color_format = (ft_glyph->bitmap.pixel_mode == FT_PIXEL_MODE_BGRA ? Rml::ColorFormat::RGBA8 : Rml::ColorFormat::A8); + + glyph.bitmap_owned_data.reset(new Rml::byte[glyph.bitmap_dimensions.x * glyph.bitmap_dimensions.y * num_bytes_per_pixel]); + glyph.bitmap_data = glyph.bitmap_owned_data.get(); + Rml::byte* destination_bitmap = glyph.bitmap_owned_data.get(); + const Rml::byte* source_bitmap = ft_glyph->bitmap.buffer; + + // Copy the bitmap data into the newly-allocated space on our glyph. + switch (ft_glyph->bitmap.pixel_mode) + { + case FT_PIXEL_MODE_MONO: + { + // Unpack 1-bit data into 8-bit. + for (int i = 0; i < glyph.bitmap_dimensions.y; ++i) + { + int mask = 0x80; + const Rml::byte* source_byte = source_bitmap; + for (int j = 0; j < glyph.bitmap_dimensions.x; ++j) + { + if ((*source_byte & mask) == mask) + destination_bitmap[j] = 255; + else + destination_bitmap[j] = 0; + + mask >>= 1; + if (mask <= 0) + { + mask = 0x80; + ++source_byte; + } + } + + destination_bitmap += glyph.bitmap_dimensions.x; + source_bitmap += ft_glyph->bitmap.pitch; + } + } + break; + case FT_PIXEL_MODE_GRAY: + case FT_PIXEL_MODE_BGRA: + { + if (scale_bitmap) + { + // Resize the glyph data to the new dimensions. + BitmapDownscale(destination_bitmap, glyph.bitmap_dimensions.x, glyph.bitmap_dimensions.y, source_bitmap, + (int)ft_glyph->bitmap.width, (int)ft_glyph->bitmap.rows, ft_glyph->bitmap.pitch, glyph.color_format); + } + else + { + // Copy the glyph data directly. + const int num_bytes_per_line = glyph.bitmap_dimensions.x * num_bytes_per_pixel; + for (int i = 0; i < glyph.bitmap_dimensions.y; ++i) + { + memcpy(destination_bitmap, source_bitmap, num_bytes_per_line); + destination_bitmap += num_bytes_per_line; + source_bitmap += ft_glyph->bitmap.pitch; + } + } + + if (glyph.color_format == Rml::ColorFormat::RGBA8) + { + // Swizzle channels (BGRA -> RGBA) + destination_bitmap = glyph.bitmap_owned_data.get(); + + for (int k = 0; k < glyph.bitmap_dimensions.x * glyph.bitmap_dimensions.y * num_bytes_per_pixel; k += 4) + { + std::swap(destination_bitmap[k], destination_bitmap[k + 2]); +#ifdef RMLUI_DEBUG + const Rml::byte alpha = destination_bitmap[k + 3]; + for (int c = 0; c < 3; c++) + RMLUI_ASSERTMSG(destination_bitmap[k + c] <= alpha, + "Glyph data is assumed to be encoded in premultiplied alpha, but that is not the case."); +#endif + } + } + } + break; + } + } + } + + return true; +} + +static void BuildGlyphMap(FT_Face ft_face, int size, FontGlyphMap& glyphs, const float bitmap_scaling_factor, const bool load_default_glyphs) +{ + if (load_default_glyphs) + { + glyphs.reserve(128); + + // Add the ASCII characters now. Other characters are added later as needed. + FT_ULong code_min = 32; + FT_ULong code_max = 126; + + for (FT_ULong character_code = code_min; character_code <= code_max; ++character_code) + { + FT_UInt index = FT_Get_Char_Index(ft_face, character_code); + BuildGlyph(ft_face, index, static_cast(character_code), glyphs, bitmap_scaling_factor); + } + } + + // Add a replacement character for rendering unknown characters. + FontGlyphIndex replacement_glyph_index = FT_Get_Char_Index(ft_face, (FT_ULong)Rml::Character::Replacement); + auto it = glyphs.find(replacement_glyph_index); + if (it == glyphs.end()) + { + Rml::FontGlyph glyph; + glyph.bitmap_dimensions = {size / 3, (size * 2) / 3}; + glyph.advance = glyph.bitmap_dimensions.x + 2; + glyph.bearing = {1, glyph.bitmap_dimensions.y}; + + glyph.bitmap_owned_data.reset(new Rml::byte[glyph.bitmap_dimensions.x * glyph.bitmap_dimensions.y]); + glyph.bitmap_data = glyph.bitmap_owned_data.get(); + + for (int y = 0; y < glyph.bitmap_dimensions.y; y++) + { + for (int x = 0; x < glyph.bitmap_dimensions.x; x++) + { + constexpr int stroke = 1; + int i = y * glyph.bitmap_dimensions.x + x; + bool near_edge = (x < stroke || x >= glyph.bitmap_dimensions.x - stroke || y < stroke || y >= glyph.bitmap_dimensions.y - stroke); + glyph.bitmap_owned_data[i] = (near_edge ? 0xdd : 0); + } + } + + glyphs[replacement_glyph_index].bitmap = std::move(glyph); + glyphs[replacement_glyph_index].character = Rml::Character::Replacement; + } +} + +static void GenerateMetrics(FT_Face ft_face, FontMetrics& metrics, float bitmap_scaling_factor) +{ + metrics.ascent = ft_face->size->metrics.ascender * bitmap_scaling_factor / float(1 << 6); + metrics.descent = -ft_face->size->metrics.descender * bitmap_scaling_factor / float(1 << 6); + metrics.line_spacing = ft_face->size->metrics.height * bitmap_scaling_factor / float(1 << 6); + + metrics.underline_position = FT_MulFix(-ft_face->underline_position, ft_face->size->metrics.y_scale) * bitmap_scaling_factor / float(1 << 6); + metrics.underline_thickness = FT_MulFix(ft_face->underline_thickness, ft_face->size->metrics.y_scale) * bitmap_scaling_factor / float(1 << 6); + metrics.underline_thickness = Rml::Math::Max(metrics.underline_thickness, 1.0f); + + // Determine the x-height of this font face. + FT_UInt index = FT_Get_Char_Index(ft_face, 'x'); + if (index != 0 && FT_Load_Glyph(ft_face, index, 0) == 0) + metrics.x_height = ft_face->glyph->metrics.height * bitmap_scaling_factor / float(1 << 6); + else + metrics.x_height = 0.5f * metrics.line_spacing; + + FT_UInt ellipsis_index = FT_Get_Char_Index(ft_face, 0x2026); + metrics.has_ellipsis = (ellipsis_index != 0); +} + +static bool SetFontSize(FT_Face ft_face, int font_size, float& out_bitmap_scaling_factor) +{ + RMLUI_ASSERT(out_bitmap_scaling_factor == 1.f); + + FT_Error error = 0; + + // Set the character size on the font face. + error = FT_Set_Char_Size(ft_face, 0, font_size << 6, 0, 0); + + // If setting char size fails, try to select a bitmap strike instead when available. + if (error != 0 && FT_HAS_FIXED_SIZES(ft_face)) + { + constexpr int a_big_number = INT_MAX / 2; + int heuristic_min = INT_MAX; + int index_min = -1; + + // Select the bitmap strike with the smallest size *above* font_size, or else the largest size. + for (int i = 0; i < ft_face->num_fixed_sizes; i++) + { + const int size_diff = ft_face->available_sizes[i].height - font_size; + const int heuristic = (size_diff < 0 ? a_big_number - size_diff : size_diff); + + if (heuristic < heuristic_min) + { + index_min = i; + heuristic_min = heuristic; + } + } + + if (index_min >= 0) + { + out_bitmap_scaling_factor = float(font_size) / ft_face->available_sizes[index_min].height; + + // Add some tolerance to the scaling factor to avoid unnecessary scaling. Only allow downscaling. + constexpr float bitmap_scaling_factor_threshold = 0.95f; + if (out_bitmap_scaling_factor >= bitmap_scaling_factor_threshold) + out_bitmap_scaling_factor = 1.f; + + error = FT_Select_Size(ft_face, index_min); + } + } + + if (error != 0) + { + Rml::Log::Message(Rml::Log::LT_ERROR, "Unable to set the character size '%d' on the font face '%s %s'.", font_size, ft_face->family_name, + ft_face->style_name); + return false; + } + + return true; +} + +static void BitmapDownscale(Rml::byte* bitmap_new, const int new_width, const int new_height, const Rml::byte* bitmap_source, const int width, + const int height, const int pitch, const Rml::ColorFormat color_format) +{ + // Average filter for downscaling bitmap images, based on https://stackoverflow.com/a/9571580 + constexpr int max_num_channels = 4; + const int num_channels = (color_format == Rml::ColorFormat::RGBA8 ? 4 : 1); + + const float xscale = float(new_width) / width; + const float yscale = float(new_height) / height; + const float sumscale = xscale * yscale; + + float yend = 0.0f; + for (int f = 0; f < new_height; f++) // y on output + { + const float ystart = yend; + yend = (f + 1) * (1.f / yscale); + if (yend >= height) + yend = height - 0.001f; + + float xend = 0.0; + for (int g = 0; g < new_width; g++) // x on output + { + float xstart = xend; + xend = (g + 1) * (1.f / xscale); + if (xend >= width) + xend = width - 0.001f; + + float sum[max_num_channels] = {}; + for (int y = (int)ystart; y <= (int)yend; ++y) + { + float yportion = 1.0f; + if (y == (int)ystart) + yportion -= ystart - y; + if (y == (int)yend) + yportion -= y + 1 - yend; + + for (int x = (int)xstart; x <= (int)xend; ++x) + { + float xportion = 1.0f; + if (x == (int)xstart) + xportion -= xstart - x; + if (x == (int)xend) + xportion -= x + 1 - xend; + + for (int i = 0; i < num_channels; i++) + sum[i] += bitmap_source[y * pitch + x * num_channels + i] * yportion * xportion; + } + } + + for (int i = 0; i < num_channels; i++) + bitmap_new[(f * new_width + g) * num_channels + i] = (Rml::byte)Rml::Math::Min(sum[i] * sumscale, 255.f); + } + } +} +} // namespace FreeType diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FreeTypeInterface.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FreeTypeInterface.h new file mode 100644 index 00000000..9fb7df7f --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/FreeTypeInterface.h @@ -0,0 +1,20 @@ +#include "FontEngineDefault/FontTypes.h" +#include "FontGlyph.h" +#include + +using Rml::Character; +using Rml::FontFaceHandleFreetype; +using Rml::FontMetrics; + +namespace FreeType { + +// Initializes a face for a given font size. Glyphs are filled with the ASCII subset, and the font face metrics are set. +bool InitialiseFaceHandle(FontFaceHandleFreetype face, int font_size, FontGlyphMap& glyphs, FontMetrics& metrics, bool load_default_glyphs); + +// Build a new glyph representing the given glyph index and append to 'glyphs'. +bool AppendGlyph(FontFaceHandleFreetype face, int font_size, FontGlyphIndex glyph_index, Character character, FontGlyphMap& glyphs); + +// Returns the corresponding glyph index from a character code. +FontGlyphIndex GetGlyphIndexFromCharacter(FontFaceHandleFreetype face, Character character); + +} // namespace FreeType diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/LanguageData.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/LanguageData.h new file mode 100644 index 00000000..d4e9cb78 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/LanguageData.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +enum class TextFlowDirection { + LeftToRight, + RightToLeft, +}; + +struct LanguageData { + Rml::String script_code; + TextFlowDirection text_flow_direction; +}; + +using LanguageDataMap = Rml::UnorderedMap; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/TextureLayout.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/TextureLayout.cpp new file mode 100644 index 00000000..98622195 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/TextureLayout.cpp @@ -0,0 +1,64 @@ +#include "TextureLayout.h" +#include + +struct RectangleSort { + bool operator()(const TextureLayoutRectangle& lhs, const TextureLayoutRectangle& rhs) const + { + return lhs.GetDimensions().y > rhs.GetDimensions().y; + } +}; + +TextureLayout::TextureLayout() {} + +TextureLayout::~TextureLayout() {} + +void TextureLayout::AddRectangle(uint64_t id, Vector2i dimensions) +{ + rectangles.push_back(TextureLayoutRectangle(id, dimensions)); +} + +TextureLayoutRectangle& TextureLayout::GetRectangle(int index) +{ + RMLUI_ASSERT(index >= 0); + RMLUI_ASSERT(index < GetNumRectangles()); + + return rectangles[index]; +} + +int TextureLayout::GetNumRectangles() const +{ + return (int)rectangles.size(); +} + +TextureLayoutTexture& TextureLayout::GetTexture(int index) +{ + RMLUI_ASSERT(index >= 0); + RMLUI_ASSERT(index < GetNumTextures()); + + return textures[index]; +} + +int TextureLayout::GetNumTextures() const +{ + return (int)textures.size(); +} + +bool TextureLayout::GenerateLayout(int max_texture_dimensions) +{ + // Sort the rectangles by height. + std::sort(rectangles.begin(), rectangles.end(), RectangleSort()); + + int num_placed_rectangles = 0; + while (num_placed_rectangles != GetNumRectangles()) + { + TextureLayoutTexture texture; + int texture_size = texture.Generate(*this, max_texture_dimensions); + if (texture_size == 0) + return false; + + textures.push_back(texture); + num_placed_rectangles += texture_size; + } + + return true; +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/TextureLayout.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/TextureLayout.h new file mode 100644 index 00000000..1b3e8f89 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/TextureLayout.h @@ -0,0 +1,55 @@ +#pragma once + +#include "TextureLayoutRectangle.h" +#include "TextureLayoutTexture.h" +#include + +/** + A texture layout generates and stores a layout of rectangles within a series of textures. It is + used primarily by the font system for generating font textures. + + Modified to support 64-bit IDs. + */ + +using Rml::Vector; +using Rml::Vector2i; + +class TextureLayout { +public: + TextureLayout(); + ~TextureLayout(); + + /// Adds a rectangle to the list of rectangles to be laid out. All rectangles must be added to + /// the layout before the layout is generated. + /// @param[in] id The id of the rectangle; used to identify the rectangle after it has been positioned. + /// @param[in] dimensions The dimensions of the rectangle. + void AddRectangle(uint64_t id, Vector2i dimensions); + + /// Returns one of the layout's rectangles. + /// @param[in] index The index of the desired rectangle. + /// @return The desired rectangle. + TextureLayoutRectangle& GetRectangle(int index); + /// Returns the number of rectangles in the layout. + /// @return The layout's rectangle count. + int GetNumRectangles() const; + + /// Returns one of the layout's textures. + /// @param[in] index The index of the desired texture. + /// @return The desired texture. + TextureLayoutTexture& GetTexture(int index); + /// Returns the number of textures in the layout. + /// @return The layout's texture count. + int GetNumTextures() const; + + /// Attempts to generate an efficient texture layout for the rectangles. + /// @param[in] max_texture_dimensions The maximum dimensions allowed for any single texture. + /// @return True if the layout was generated successfully, false if not. + bool GenerateLayout(int max_texture_dimensions); + +private: + using RectangleList = Vector; + using TextureList = Vector; + + TextureList textures; + RectangleList rectangles; +}; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/TextureLayoutRectangle.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/TextureLayoutRectangle.cpp new file mode 100644 index 00000000..bdfe1ca6 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/TextureLayoutRectangle.cpp @@ -0,0 +1,64 @@ +#include "TextureLayoutRectangle.h" + +TextureLayoutRectangle::TextureLayoutRectangle(const uint64_t _id, const Vector2i dimensions) : dimensions(dimensions), texture_position(0, 0) +{ + id = _id; + texture_index = -1; + + texture_data = nullptr; + texture_stride = 0; +} + +TextureLayoutRectangle::~TextureLayoutRectangle() {} + +uint64_t TextureLayoutRectangle::GetId() const +{ + return id; +} + +Vector2i TextureLayoutRectangle::GetPosition() const +{ + return texture_position; +} + +Vector2i TextureLayoutRectangle::GetDimensions() const +{ + return dimensions; +} + +void TextureLayoutRectangle::Place(const int _texture_index, const Vector2i position) +{ + texture_index = _texture_index; + texture_position = position; +} + +void TextureLayoutRectangle::Unplace() +{ + texture_index = -1; +} + +bool TextureLayoutRectangle::IsPlaced() const +{ + return texture_index > -1; +} + +void TextureLayoutRectangle::Allocate(byte* _texture_data, int _texture_stride) +{ + texture_data = _texture_data + ((texture_position.y * _texture_stride) + texture_position.x * 4); + texture_stride = _texture_stride; +} + +int TextureLayoutRectangle::GetTextureIndex() +{ + return texture_index; +} + +byte* TextureLayoutRectangle::GetTextureData() +{ + return texture_data; +} + +int TextureLayoutRectangle::GetTextureStride() const +{ + return texture_stride; +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/TextureLayoutRectangle.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/TextureLayoutRectangle.h new file mode 100644 index 00000000..e8ece308 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/TextureLayoutRectangle.h @@ -0,0 +1,63 @@ +#pragma once + +#include + +using Rml::byte; +using Rml::Vector2i; + +/** + A texture layout rectangle is an area positioned with a texture layout. + + Modified to support 64-bit IDs. + */ + +class TextureLayoutRectangle { +public: + TextureLayoutRectangle(uint64_t id, Vector2i dimensions); + ~TextureLayoutRectangle(); + + /// Returns the rectangle's id. + /// @return The rectangle's id. + uint64_t GetId() const; + /// Returns the rectangle's position; this is only valid if it has been placed. + /// @return The rectangle's position within its texture. + Vector2i GetPosition() const; + /// Returns the rectangle's dimensions. + /// @return The rectangle's dimensions. + Vector2i GetDimensions() const; + + /// Places the rectangle within a texture. + /// @param[in] texture_index The index of the texture this rectangle is placed on. + /// @param[in] position The position within the texture of this rectangle's top-left corner. + void Place(int texture_index, Vector2i position); + /// Unplaces the rectangle. + void Unplace(); + /// Returns the rectangle's placed state. + /// @return True if the rectangle has been placed, false if not. + bool IsPlaced() const; + + /// Sets the rectangle's texture data and stride. + /// @param[in] texture_data The pointer to the top-left corner of the texture's data. + /// @param[in] texture_stride The stride of the texture data, in bytes. + void Allocate(byte* texture_data, int texture_stride); + + /// Returns the index of the texture this rectangle is placed on. + /// @return The texture index. + int GetTextureIndex(); + /// Returns the rectangle's allocated texture data. + /// @return The texture data. + byte* GetTextureData(); + /// Returns the stride of the rectangle's texture data. + /// @return The texture data stride. + int GetTextureStride() const; + +private: + uint64_t id; + Vector2i dimensions; + + int texture_index; + Vector2i texture_position; + + byte* texture_data; + int texture_stride; +}; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/TextureLayoutRow.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/TextureLayoutRow.cpp new file mode 100644 index 00000000..0de8ddb2 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/TextureLayoutRow.cpp @@ -0,0 +1,70 @@ +#include "TextureLayoutRow.h" +#include "TextureLayout.h" + +TextureLayoutRow::TextureLayoutRow() +{ + height = 0; +} + +TextureLayoutRow::~TextureLayoutRow() {} + +int TextureLayoutRow::Generate(TextureLayout& layout, int max_width, int y) +{ + int width = 1; + int first_unplaced_index = 0; + int placed_rectangles = 0; + + while (width < max_width) + { + // Find the first unplaced rectangle we can fit. + int index; + for (index = first_unplaced_index; index < layout.GetNumRectangles(); ++index) + { + TextureLayoutRectangle& rectangle = layout.GetRectangle(index); + if (!rectangle.IsPlaced()) + { + if (width + rectangle.GetDimensions().x + 1 <= max_width) + break; + } + } + + if (index == layout.GetNumRectangles()) + return placed_rectangles; + + TextureLayoutRectangle& rectangle = layout.GetRectangle(index); + + // Increment the row height if necessary. + height = Rml::Math::Max(height, rectangle.GetDimensions().y); + + // Add this glyph onto our list and mark it as placed. + rectangles.push_back(&rectangle); + rectangle.Place(layout.GetNumTextures(), Vector2i(width, y)); + ++placed_rectangles; + + // Increment our width. An extra pixel is added on so the rectangles aren't pushed up + // against each other. This will avoid filtering artifacts. + if (rectangle.GetDimensions().x > 0) + width += rectangle.GetDimensions().x + 1; + + first_unplaced_index = index + 1; + } + + return placed_rectangles; +} + +void TextureLayoutRow::Allocate(byte* texture_data, int stride) +{ + for (size_t i = 0; i < rectangles.size(); ++i) + rectangles[i]->Allocate(texture_data, stride); +} + +int TextureLayoutRow::GetHeight() const +{ + return height; +} + +void TextureLayoutRow::Unplace() +{ + for (size_t i = 0; i < rectangles.size(); ++i) + rectangles[i]->Unplace(); +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/TextureLayoutRow.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/TextureLayoutRow.h new file mode 100644 index 00000000..4f502ee1 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/TextureLayoutRow.h @@ -0,0 +1,46 @@ +#pragma once + +#include "TextureLayoutRectangle.h" +#include + +using Rml::byte; +using Rml::Vector; + +class TextureLayout; + +/** + A texture layout row is a single row of rectangles positioned vertically within a texture. + + Modified to support 64-bit IDs. + */ + +class TextureLayoutRow { +public: + TextureLayoutRow(); + ~TextureLayoutRow(); + + /// Attempts to position unplaced rectangles from the layout into this row. + /// @param[in] layout The layout to position rectangles from. + /// @param[in] width The maximum width of this row. + /// @param[in] y The y-coordinate of this row. + /// @return The number of placed rectangles. + int Generate(TextureLayout& layout, int width, int y); + + /// Assigns allocated texture data to all rectangles in this row. + /// @param[in] texture_data The pointer to the beginning of the texture's data. + /// @param[in] stride The stride of the texture's surface, in bytes; + void Allocate(byte* texture_data, int stride); + + /// Returns the height of the row. + /// @return The row's height. + int GetHeight() const; + + /// Resets the placed status for all of the rectangles within this row. + void Unplace(); + +private: + using RectangleList = Vector; + + int height; + RectangleList rectangles; +}; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/TextureLayoutTexture.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/TextureLayoutTexture.cpp new file mode 100644 index 00000000..54098cba --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/TextureLayoutTexture.cpp @@ -0,0 +1,119 @@ +#include "TextureLayoutTexture.h" +#include "TextureDatabase.h" +#include "TextureLayout.h" + +TextureLayoutTexture::TextureLayoutTexture() : dimensions(0, 0) {} + +TextureLayoutTexture::~TextureLayoutTexture() +{ + // Don't free texture data; freed in the texture loader. +} + +Vector2i TextureLayoutTexture::GetDimensions() const +{ + return dimensions; +} + +int TextureLayoutTexture::Generate(TextureLayout& layout, int maximum_dimensions) +{ + // Come up with an estimate for how big a texture we need. Calculate the total square pixels + // required by the remaining rectangles to place, square-root it to get the dimensions of the + // smallest texture necessary (under optimal circumstances) and round it up to the nearest + // power of two. + int square_pixels = 0; + int unplaced_rectangles = 0; + for (int i = 0; i < layout.GetNumRectangles(); ++i) + { + const TextureLayoutRectangle& rectangle = layout.GetRectangle(i); + + if (!rectangle.IsPlaced()) + { + int x = rectangle.GetDimensions().x + 1; + int y = rectangle.GetDimensions().y + 1; + + square_pixels += x * y; + ++unplaced_rectangles; + } + } + + int texture_width = int(Rml::Math::SquareRoot((float)square_pixels)); + + dimensions.y = Rml::Math::ToPowerOfTwo(texture_width); + dimensions.x = dimensions.y >> 1; + + dimensions.x = Rml::Math::Min(dimensions.x, maximum_dimensions); + dimensions.y = Rml::Math::Min(dimensions.y, maximum_dimensions); + + // Now we're layout out the rectangles in the texture. If we don't fit all the rectangles on + // and have room to grow (ie, haven't hit the maximum texture size in both dimensions) then + // we'll have another go with a bigger texture. + int num_placed_rectangles = 0; + for (;;) + { + bool success = true; + int height = 1; + + while (num_placed_rectangles != unplaced_rectangles) + { + TextureLayoutRow row; + int row_size = row.Generate(layout, dimensions.x, height); + if (row_size == 0) + { + success = false; + break; + } + + height += row.GetHeight() + 1; + if (height > dimensions.y) + { + // D'oh! We've exceeded our height boundaries. This row should be unplaced. + row.Unplace(); + success = false; + break; + } + + rows.push_back(row); + num_placed_rectangles += row_size; + } + + // If the rectangles were successfully laid out within the texture limits, we're done. + if (success) + return num_placed_rectangles; + + // Couldn't do it! Increase the texture size, clear the rectangles and try again - unless + // we've hit the maximum texture size, in which case return true if we've placed any + // rectangles (ie, the layout isn't empty). + if (dimensions.y > dimensions.x) + dimensions.x = dimensions.y; + else + { + if (dimensions.y << 1 > maximum_dimensions) + return num_placed_rectangles; + + dimensions.y <<= 1; + } + + // Unplace all of the glyphs we tried to place and have an other crack. + for (size_t i = 0; i < rows.size(); i++) + rows[i].Unplace(); + + rows.clear(); + num_placed_rectangles = 0; + } +} + +Vector TextureLayoutTexture::AllocateTexture() +{ + Vector texture_data; + + if (dimensions.x > 0 && dimensions.y > 0) + { + // Set the texture to transparent black. + texture_data.resize(dimensions.x * dimensions.y * 4, 0); + + for (size_t i = 0; i < rows.size(); ++i) + rows[i].Allocate(texture_data.data(), dimensions.x * 4); + } + + return texture_data; +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/TextureLayoutTexture.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/TextureLayoutTexture.h new file mode 100644 index 00000000..66d29748 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/TextureLayoutTexture.h @@ -0,0 +1,44 @@ +#pragma once + +#include "TextureLayoutRow.h" +#include + +using Rml::byte; +using Rml::Vector; + +class TextureLayout; + +/** + A texture layout texture is a single rectangular area which sub-rectangles are placed on within + a complete texture layout. + + Modified to support 64-bit IDs. + */ + +class TextureLayoutTexture { +public: + TextureLayoutTexture(); + ~TextureLayoutTexture(); + + /// Returns the texture's dimensions. This is only valid after the texture has been generated. + /// @return The texture's dimensions. + Vector2i GetDimensions() const; + + /// Attempts to position unplaced rectangles from the layout into this texture. The size of + /// this texture will be determined by its contents. + /// @param[in] layout The layout to position rectangles from. + /// @param[in] maximum_dimensions The maximum dimensions of this texture. If this is not big enough to place all the rectangles, then as many will + /// be placed as possible. + /// @return The number of placed rectangles. + int Generate(TextureLayout& layout, int maximum_dimensions); + + /// Allocates the texture. + /// @return The allocated texture data. + Vector AllocateTexture(); + +private: + using RowList = Vector; + + Vector2i dimensions; + RowList rows; +}; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/main.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/main.cpp new file mode 100644 index 00000000..3a153420 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/harfbuzz/src/main.cpp @@ -0,0 +1,156 @@ +#include "FontEngineInterfaceHarfBuzz.h" +#include +#include +#include +#include + +/* + This demo shows how to create a custom text-shaping font engine implementation using HarfBuzz. +*/ + +// Toggle this variable to enable/disable text shaping. +constexpr bool EnableTextShaping = true; + +class HarfBuzzEventListener : public Rml::EventListener { +public: + HarfBuzzEventListener(const Rml::String& value, Rml::ElementDocument* document) : + value(value), english_element(document->GetElementById("lorem-ipsum-en")), arabic_element(document->GetElementById("lorem-ipsum-ar")), + hindi_element(document->GetElementById("lorem-ipsum-hi")) + {} + + void ProcessEvent(Rml::Event& /*event*/) override + { + if (value == "set-english") + { + english_element->SetClass("hide-lorem-ipsum", false); + arabic_element->SetClass("hide-lorem-ipsum", true); + hindi_element->SetClass("hide-lorem-ipsum", true); + } + else if (value == "set-arabic") + { + english_element->SetClass("hide-lorem-ipsum", true); + arabic_element->SetClass("hide-lorem-ipsum", false); + hindi_element->SetClass("hide-lorem-ipsum", true); + } + else if (value == "set-hindi") + { + english_element->SetClass("hide-lorem-ipsum", true); + arabic_element->SetClass("hide-lorem-ipsum", true); + hindi_element->SetClass("hide-lorem-ipsum", false); + } + } + + void OnDetach(Rml::Element* /*element*/) override { delete this; } + +private: + Rml::String value; + + Rml::Element* english_element; + Rml::Element* arabic_element; + Rml::Element* hindi_element; +}; + +#if defined RMLUI_PLATFORM_WIN32 + #include +int APIENTRY WinMain(HINSTANCE /*instance_handle*/, HINSTANCE /*previous_instance_handle*/, char* /*command_line*/, int /*command_show*/) +#else +int main(int /*argc*/, char** /*argv*/) +#endif +{ + int window_width = 1024; + int window_height = 768; + + // Initializes the shell which provides common functionality used by the included samples. + if (!Shell::Initialize()) + return -1; + + // Constructs the system and render interfaces, creates a window, and attaches the renderer. + if (!Backend::Initialize("HarfBuzz Text Shaping Sample", window_width, window_height, true)) + { + Shell::Shutdown(); + return -1; + } + + // Install the custom interfaces constructed by the backend before initializing RmlUi. + Rml::SetSystemInterface(Backend::GetSystemInterface()); + Rml::SetRenderInterface(Backend::GetRenderInterface()); + + // Construct and load the font interface. + Rml::UniquePtr font_interface; + if (EnableTextShaping) + { + font_interface = Rml::MakeUnique(); + Rml::SetFontEngineInterface(font_interface.get()); + + // Add language data to the font engine's internal language lookup table. + font_interface->RegisterLanguage("en", "Latn", TextFlowDirection::LeftToRight); + font_interface->RegisterLanguage("ar", "Arab", TextFlowDirection::RightToLeft); + font_interface->RegisterLanguage("hi", "Deva", TextFlowDirection::LeftToRight); + } + + // RmlUi initialisation. + Rml::Initialise(); + + // Create the main RmlUi context. + Rml::Context* context = Rml::CreateContext("main", Rml::Vector2i(window_width, window_height)); + if (!context) + { + Rml::Shutdown(); + Backend::Shutdown(); + Shell::Shutdown(); + return -1; + } + + Rml::Debugger::Initialise(context); + + // Load required fonts. + Rml::String font_paths[3] = { + "assets/LatoLatin-Regular.ttf", + "basic/harfbuzz/data/Cairo-Regular.ttf", + "basic/harfbuzz/data/Poppins-Regular.ttf", + }; + for (const Rml::String& font_path : font_paths) + if (!Rml::LoadFontFace(font_path)) + { + Rml::Shutdown(); + Backend::Shutdown(); + Shell::Shutdown(); + return -1; + } + + // Load and show the demo document. + if (Rml::ElementDocument* document = context->LoadDocument("basic/harfbuzz/data/harfbuzz.rml")) + { + if (auto el = document->GetElementById("title")) + el->SetInnerRML("HarfBuzz Text Shaping"); + + document->Show(); + + // Create event handlers. + for (const Rml::String button_id : {"set-english", "set-arabic", "set-hindi"}) + document->GetElementById(button_id)->AddEventListener(Rml::EventId::Click, new HarfBuzzEventListener(button_id, document)); + } + + bool running = true; + while (running) + { + running = Backend::ProcessEvents(context, &Shell::ProcessKeyDownShortcuts, true); + + context->Update(); + + Backend::BeginFrame(); + context->Render(); + Backend::PresentFrame(); + } + + // Shut down debugger before font interface. + Rml::Debugger::Shutdown(); + + // Shutdown RmlUi. + Rml::Shutdown(); + + Backend::Shutdown(); + Shell::Shutdown(); + + return 0; +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/ime/CMakeLists.txt b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/ime/CMakeLists.txt new file mode 100644 index 00000000..dd66b4bf --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/ime/CMakeLists.txt @@ -0,0 +1,40 @@ +set(SAMPLE_NAME "ime") +set(TARGET_NAME "${RMLUI_SAMPLE_PREFIX}${SAMPLE_NAME}") + +if(RMLUI_IME_SAMPLE_USE_NOTO_FONTS) + add_executable(${TARGET_NAME} WIN32 + src/main.cpp + ) +else() + add_executable(${TARGET_NAME} WIN32 + src/SystemFontWin32.cpp + src/SystemFontWin32.h + src/main.cpp + ) +endif() + +set_common_target_options(${TARGET_NAME}) + +target_link_libraries(${TARGET_NAME} PRIVATE rmlui_shell) + +install_sample_target(${TARGET_NAME}) + +if(RMLUI_IME_SAMPLE_USE_NOTO_FONTS) + target_compile_definitions(${TARGET_NAME} PRIVATE "RMLUI_IME_SAMPLE_USE_NOTO_FONTS") + set(external_assets_dir "${CMAKE_SOURCE_DIR}/Samples/assets/external") + file(DOWNLOAD "https://github.com/google/fonts/raw/47831f08ec6d6d7ad6b465f23dc9f9a890a2a04b/ofl/notosanssc/NotoSansSC%5Bwght%5D.ttf" "${external_assets_dir}/NotoSansSC[wght].ttf" + EXPECTED_HASH SHA256=a3041811a78c361b1de50f953c805e0244951c21c5bd412f7232ef0d899af0da + ) + file(DOWNLOAD "https://github.com/google/fonts/raw/47831f08ec6d6d7ad6b465f23dc9f9a890a2a04b/ofl/notosansjp/NotoSansJP%5Bwght%5D.ttf" "${external_assets_dir}/NotoSansJP[wght].ttf" + EXPECTED_HASH SHA256=c2f3b4d463500a2ddcd3849cded1fceeb9fd6d1c32e6cbecd568453ba50fc68f + ) + file(DOWNLOAD "https://github.com/google/fonts/raw/47831f08ec6d6d7ad6b465f23dc9f9a890a2a04b/ofl/notosanskr/NotoSansKR%5Bwght%5D.ttf" "${external_assets_dir}/NotoSansKR[wght].ttf" + EXPECTED_HASH SHA256=194018e6b2b293a7964f037b25c0249ce1418bc9ab3c971060a03aa57861e252 + ) + file(DOWNLOAD "https://github.com/google/fonts/raw/47831f08ec6d6d7ad6b465f23dc9f9a890a2a04b/ofl/notosansthai/NotoSansThai%5Bwdth,wght%5D.ttf" "${external_assets_dir}/NotoSansThai[wdth,wght].ttf" + EXPECTED_HASH SHA256=5a1c559bb539583c8a1fd99d1c5b9491e5e14478c9cd2bd0970d5c3096cc9ef8 + ) + file(DOWNLOAD "https://github.com/google/fonts/raw/47831f08ec6d6d7ad6b465f23dc9f9a890a2a04b/ofl/notosansarabic/NotoSansArabic%5Bwdth,wght%5D.ttf" "${external_assets_dir}/NotoSansArabic[wdth,wght].ttf" + EXPECTED_HASH SHA256=63111b5b2e074dd48cc67692e0a2726d86ee94c1c37fe8598257b7b4e87e869e + ) +endif() diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/ime/data/ime.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/ime/data/ime.rml new file mode 100644 index 00000000..b5c7ed64 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/ime/data/ime.rml @@ -0,0 +1,119 @@ + + + + IME + + + +

    IME Sample

    +

    Input Method Editor (IME) is a software component that allows the user to type characters not otherwise + available on a standard QWERTY keyboard. This is crucial for languages using a writing system different + from Latin, such as Japanese, Chinese, Vietnamese, and others. You must add the language in the system + options to use such a keyboard.

    +

    IME is also used for emojis (Win + .) and the clipboard history (Win + V) on Windows.

    + +
    + + + + + +
    + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/ime/src/SystemFontWin32.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/ime/src/SystemFontWin32.cpp new file mode 100644 index 00000000..314a5bb3 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/ime/src/SystemFontWin32.cpp @@ -0,0 +1,55 @@ +#include "SystemFontWin32.h" +#include +#include +#include + +static Rml::String GetSystemFontDirectory() +{ + Rml::String font_path; + PWSTR fonts_path_wide; + if (SHGetKnownFolderPath(FOLDERID_Fonts, 0, NULL, &fonts_path_wide) == S_OK) + { + int buffer_size = WideCharToMultiByte(CP_ACP, 0, fonts_path_wide, -1, NULL, 0, NULL, NULL); + font_path.resize(std::max(buffer_size - 1, 0)); + WideCharToMultiByte(CP_ACP, 0, fonts_path_wide, -1, &font_path[0], buffer_size, NULL, NULL); + + CoTaskMemFree(fonts_path_wide); + } + + return font_path; +} + +Rml::Vector GetSelectedSystemFonts() +{ + Rml::Vector result; + + const Rml::String system_font_directory = GetSystemFontDirectory(); + if (!system_font_directory.empty()) + { + // Partly based on: https://stackoverflow.com/a/57362436/2555318 + const char* system_font_files[] = { + "segoeui.ttf ", // Segoe UI (Latin; Greek; Cyrillic; Armenian; Georgian; Georgian Khutsuri; Arabic; Hebrew; Fraser) + "tahoma.ttf ", // Tahoma (Latin; Greek; Cyrillic; Armenian; Hebrew; Arabic; Thai) + "meiryo.ttc ", // Meiryo UI (Japanese) + "msgothic.ttc", // MS Gothic (Japanese) + "msjh.ttc", // Microsoft JhengHei (Chinese Traditional; Han; Han with Bopomofo) + "msyh.ttc", // Microsoft YaHei (Chinese Simplified; Han) + "malgun.ttf ", // Malgun Gothic (Korean) + "simsun.ttc ", // SimSun (Han Simplified) + "seguiemj.ttf ", // Segoe UI (Latin; Greek; Cyrillic; Armenian; Georgian; Georgian Khutsuri; Arabic; Hebrew; Fraser) + }; + + for (const char* font_file : system_font_files) + { + Rml::String path = system_font_directory + '\\' + font_file; + DWORD attributes = GetFileAttributesA(path.c_str()); + + if (attributes != INVALID_FILE_ATTRIBUTES && !(attributes & FILE_ATTRIBUTE_DIRECTORY)) + result.push_back(path); + else + Rml::Log::Message(Rml::Log::LT_INFO, "Could not find system font file '%s', skipping.", path.c_str()); + } + } + + return result; +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/ime/src/SystemFontWin32.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/ime/src/SystemFontWin32.h new file mode 100644 index 00000000..f6aab532 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/ime/src/SystemFontWin32.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +Rml::Vector GetSelectedSystemFonts(); diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/ime/src/main.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/ime/src/main.cpp new file mode 100644 index 00000000..fa78ed91 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/ime/src/main.cpp @@ -0,0 +1,117 @@ +#include +#if !defined RMLUI_IME_SAMPLE_USE_NOTO_FONTS + #include "SystemFontWin32.h" +#endif +#include +#include +#include +#include +#include + +#if !defined RMLUI_IME_SAMPLE_USE_NOTO_FONTS && !defined RMLUI_PLATFORM_WIN32 + #error "No Asian language fonts available. Please set RMLUI_IME_SAMPLE_USE_NOTO_FONTS to use Noto fonts." +#endif + +static void LoadFonts() +{ + struct FontFace { + Rml::String filename; + bool fallback_face; + }; + Rml::Vector font_faces = { + {"assets/LatoLatin-Regular.ttf", false}, + {"assets/LatoLatin-Italic.ttf", false}, + {"assets/LatoLatin-Bold.ttf", false}, + {"assets/LatoLatin-BoldItalic.ttf", false}, + }; + +#if !defined RMLUI_IME_SAMPLE_USE_NOTO_FONTS + for (const Rml::String& path : GetSelectedSystemFonts()) + font_faces.push_back({path, true}); +#endif + + for (const FontFace& face : font_faces) + Rml::LoadFontFace(face.filename, face.fallback_face); + +#if defined RMLUI_IME_SAMPLE_USE_NOTO_FONTS + Rml::Vector noto_font_faces = { + {"assets/external/NotoSansSC[wght].ttf", true}, + {"assets/external/NotoSansJP[wght].ttf", true}, + {"assets/external/NotoSansKR[wght].ttf", true}, + {"assets/external/NotoSansThai[wdth,wght].ttf", true}, + {"assets/external/NotoSansArabic[wdth,wght].ttf", true}, + {"assets/NotoEmoji-Regular.ttf", true}, + }; + + for (const FontFace& face : noto_font_faces) + Rml::LoadFontFace(face.filename, face.fallback_face, Rml::Style::FontWeight::Normal); +#endif +} + +#if defined RMLUI_PLATFORM_WIN32 + #include +int APIENTRY WinMain(HINSTANCE /*instance_handle*/, HINSTANCE /*previous_instance_handle*/, char* /*command_line*/, int /*command_show*/) +#else +int main(int /*argc*/, char** /*argv*/) +#endif +{ + const int window_width = 1024; + const int window_height = 768; + + // Initializes the shell which provides common functionality used by the included samples. + if (!Shell::Initialize()) + return -1; + + // Constructs the system and render interfaces, creates a window, and attaches the renderer. + if (!Backend::Initialize("IME Sample", window_width, window_height, true)) + { + Shell::Shutdown(); + return -1; + } + + // Install the custom interfaces constructed by the backend before initializing RmlUi. + Rml::SetSystemInterface(Backend::GetSystemInterface()); + Rml::SetRenderInterface(Backend::GetRenderInterface()); + + // RmlUi initialisation. + Rml::Initialise(); + + // Create the main RmlUi context. + Rml::Context* context = Rml::CreateContext("main", Rml::Vector2i(window_width, window_height)); + if (!context) + { + Rml::Shutdown(); + Backend::Shutdown(); + Shell::Shutdown(); + return -1; + } + + Rml::Debugger::Initialise(context); + + // Load required fonts with support for most character sets. + LoadFonts(); + + // Load and show the demo document. + Rml::ElementDocument* document = context->LoadDocument("basic/ime/data/ime.rml"); + if (document) + document->Show(); + + bool running = true; + while (running) + { + running = Backend::ProcessEvents(context, &Shell::ProcessKeyDownShortcuts, true); + + context->Update(); + + Backend::BeginFrame(); + context->Render(); + Backend::PresentFrame(); + } + + Rml::Shutdown(); + + Backend::Shutdown(); + Shell::Shutdown(); + + return 0; +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/load_document/CMakeLists.txt b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/load_document/CMakeLists.txt new file mode 100644 index 00000000..5964a39b --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/load_document/CMakeLists.txt @@ -0,0 +1,12 @@ +set(SAMPLE_NAME "load_document") +set(TARGET_NAME "${RMLUI_SAMPLE_PREFIX}${SAMPLE_NAME}") + +add_executable(${TARGET_NAME} WIN32 + src/main.cpp +) + +set_common_target_options(${TARGET_NAME}) + +target_link_libraries(${TARGET_NAME} PRIVATE rmlui_shell) + +install_sample_target(${TARGET_NAME}) diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/load_document/src/main.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/load_document/src/main.cpp new file mode 100644 index 00000000..fa3e9496 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/load_document/src/main.cpp @@ -0,0 +1,78 @@ +#include +#include +#include +#include + +#if defined RMLUI_PLATFORM_WIN32 + #include +int APIENTRY WinMain(HINSTANCE /*instance_handle*/, HINSTANCE /*previous_instance_handle*/, char* /*command_line*/, int /*command_show*/) +#else +int main(int /*argc*/, char** /*argv*/) +#endif +{ + const int window_width = 1024; + const int window_height = 768; + + // Initializes the shell which provides common functionality used by the included samples. + if (!Shell::Initialize()) + return -1; + + // Constructs the system and render interfaces, creates a window, and attaches the renderer. + if (!Backend::Initialize("Load Document Sample", window_width, window_height, true)) + { + Shell::Shutdown(); + return -1; + } + + // Install the custom interfaces constructed by the backend before initializing RmlUi. + Rml::SetSystemInterface(Backend::GetSystemInterface()); + Rml::SetRenderInterface(Backend::GetRenderInterface()); + + // RmlUi initialisation. + Rml::Initialise(); + + // Create the main RmlUi context. + Rml::Context* context = Rml::CreateContext("main", Rml::Vector2i(window_width, window_height)); + if (!context) + { + Rml::Shutdown(); + Backend::Shutdown(); + Shell::Shutdown(); + return -1; + } + + // The RmlUi debugger is optional but very useful. Try it by pressing 'F8' after starting this sample. + Rml::Debugger::Initialise(context); + + // Fonts should be loaded before any documents are loaded. + Shell::LoadFonts(); + + // Load and show the demo document. + if (Rml::ElementDocument* document = context->LoadDocument("assets/demo.rml")) + document->Show(); + + bool running = true; + while (running) + { + // Handle input and window events. + running = Backend::ProcessEvents(context, &Shell::ProcessKeyDownShortcuts, true); + + // This is a good place to update your game or application. + + // Always update the context before rendering. + context->Update(); + + // Prepare the backend for taking rendering commands from RmlUi and then render the context. + Backend::BeginFrame(); + context->Render(); + Backend::PresentFrame(); + } + + // Shutdown RmlUi. + Rml::Shutdown(); + + Backend::Shutdown(); + Shell::Shutdown(); + + return 0; +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/lottie/CMakeLists.txt b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/lottie/CMakeLists.txt new file mode 100644 index 00000000..965e1829 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/lottie/CMakeLists.txt @@ -0,0 +1,12 @@ +set(SAMPLE_NAME "lottie") +set(TARGET_NAME "${RMLUI_SAMPLE_PREFIX}${SAMPLE_NAME}") + +add_executable(${TARGET_NAME} WIN32 + src/main.cpp +) + +set_common_target_options(${TARGET_NAME}) + +target_link_libraries(${TARGET_NAME} PRIVATE rmlui_shell) + +install_sample_target(${TARGET_NAME}) diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/lottie/data/LICENSE.txt b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/lottie/data/LICENSE.txt new file mode 100644 index 00000000..f2f5fc0c --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/lottie/data/LICENSE.txt @@ -0,0 +1,12 @@ +The file 'a_mountain.json' included in this directory is from the rlottie project [1], licensed under MIT. The license is restated below. +[1] https://github.com/Samsung/rlottie + +---------------------------- + +Copyright 2020 (see AUTHORS) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/lottie/data/a_mountain.json b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/lottie/data/a_mountain.json new file mode 100644 index 00000000..65fed4be --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/lottie/data/a_mountain.json @@ -0,0 +1 @@ +{"v":"5.1.15","fr":25,"ip":0,"op":89,"w":300,"h":300,"nm":"MountainCircle","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"CloudA contornos","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":25,"s":[65,155,0],"e":[136,155,0],"to":[11.8333330154419,0,0],"ti":[-11.8333330154419,0,0]},{"t":88}],"ix":2},"a":{"a":0,"k":[21.25,16.25,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.25,0.25,0.667],"y":[1,1,1]},"o":{"x":[0.75,0.75,0.333],"y":[0,0,0]},"n":["0p25_1_0p75_0","0p25_1_0p75_0","0p667_1_0p333_0"],"t":10,"s":[100,0,100],"e":[100,110,100]},{"i":{"x":[0.25,0.25,0.667],"y":[1,1,1]},"o":{"x":[0.75,0.75,0.333],"y":[0,0,0]},"n":["0p25_1_0p75_0","0p25_1_0p75_0","0p667_1_0p333_0"],"t":17,"s":[100,110,100],"e":[100,90,100]},{"i":{"x":[0.25,0.25,0.667],"y":[1,1,1]},"o":{"x":[0.75,0.75,0.333],"y":[0,0,0]},"n":["0p25_1_0p75_0","0p25_1_0p75_0","0p667_1_0p333_0"],"t":22,"s":[100,90,100],"e":[100,100,100]},{"t":24}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-4.418],[4.418,0],[0,4.418],[-4.418,0]],"o":[[0,4.418],[-4.418,0],[0,-4.418],[4.418,0]],"v":[[8,0],[0,8],[-8,0],[0,-8]],"c":true},"ix":2},"nm":"Trazado 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-78,149],[222,149],[222,-151],[-78,-151]],"c":true},"ix":2},"nm":"Trazado 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-78,-151],[222,-151],[222,149],[-78,149]],"c":true},"ix":2},"nm":"Trazado 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Grupo 1","np":2,"cix":2,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"mm","mm":4,"nm":"Combinar trazados 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.929000016755,0.929000016755,0.929000016755,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Relleno 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[34.25,12.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Grupo 1","np":4,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-6.627],[6.627,0],[0,6.627],[-6.627,0]],"o":[[0,6.627],[-6.627,0],[0,-6.627],[6.627,0]],"v":[[12,0],[0,12],[-12,0],[0,-12]],"c":true},"ix":2},"nm":"Trazado 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-72,141],[228,141],[228,-159],[-72,-159]],"c":true},"ix":2},"nm":"Trazado 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-72,-159],[228,-159],[228,141],[-72,141]],"c":true},"ix":2},"nm":"Trazado 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Grupo 1","np":2,"cix":2,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"mm","mm":4,"nm":"Combinar trazados 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.929000016755,0.929000016755,0.929000016755,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Relleno 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[28.25,20.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Grupo 2","np":4,"cix":2,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-6.075],[6.075,0],[0,6.075],[-6.075,0]],"o":[[0,6.075],[-6.075,0],[0,-6.075],[6.075,0]],"v":[[11,0],[0,11],[-11,0],[0,-11]],"c":true},"ix":2},"nm":"Trazado 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-65,150],[235,150],[235,-150],[-65,-150]],"c":true},"ix":2},"nm":"Trazado 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-65,-150],[235,-150],[235,150],[-65,150]],"c":true},"ix":2},"nm":"Trazado 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Grupo 1","np":2,"cix":2,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"mm","mm":4,"nm":"Combinar trazados 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.929000016755,0.929000016755,0.929000016755,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Relleno 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[21.25,11.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Grupo 3","np":4,"cix":2,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-7.18],[7.18,0],[0,7.18],[-7.18,0]],"o":[[0,7.18],[-7.18,0],[0,-7.18],[7.18,0]],"v":[[13,0],[0,13],[-13,0],[0,-13]],"c":true},"ix":2},"nm":"Trazado 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-57,145],[243,145],[243,-155],[-57,-155]],"c":true},"ix":2},"nm":"Trazado 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-57,-155],[243,-155],[243,145],[-57,145]],"c":true},"ix":2},"nm":"Trazado 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Grupo 1","np":2,"cix":2,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"mm","mm":4,"nm":"Combinar trazados 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.929000016755,0.929000016755,0.929000016755,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Relleno 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[13.25,16.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Grupo 4","np":4,"cix":2,"ix":4,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":89,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Circle 4","td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,150,0],"ix":2},"a":{"a":0,"k":[137.25,137.25,0],"ix":1},"s":{"a":0,"k":[97.333,97.333,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-75.663],[75.663,0],[0,75.663],[-75.663,0]],"o":[[0,75.663],[-75.663,0],[0,-75.663],[75.663,0]],"v":[[137,0],[0,137],[-137,0],[0,-137]],"c":true},"ix":2},"nm":"Trazado 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-150,150],[150,150],[150,-150],[-150,-150]],"c":true},"ix":2},"nm":"Trazado 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-150,-150],[150,-150],[150,150],[-150,150]],"c":true},"ix":2},"nm":"Trazado 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Grupo 1","np":2,"cix":2,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"mm","mm":4,"nm":"Combinar trazados 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.081999999402,0.626999978458,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Relleno 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[137.25,137.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Grupo 1","np":4,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":89,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Sun contornos","tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.25,"y":1},"o":{"x":0.75,"y":0},"n":"0p25_1_0p75_0","t":11,"s":[182,-13,0],"e":[182,83,0],"to":[0,16,0],"ti":[0,-13.6666669845581,0]},{"i":{"x":0.25,"y":1},"o":{"x":0.75,"y":0},"n":"0p25_1_0p75_0","t":29,"s":[182,83,0],"e":[182,69,0],"to":[0,13.6666669845581,0],"ti":[0,2.33333325386047,0]},{"t":33}],"ix":2},"a":{"a":0,"k":[20.25,20.25,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-11.05],[11.05,0],[0,0]],"o":[[0,11.05],[0,0],[11.05,0]],"v":[[10,0],[-10,20],[-10,-20]],"c":true},"ix":2},"nm":"Trazado 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-192,207],[108,207],[108,-93],[-192,-93]],"c":true},"ix":2},"nm":"Trazado 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-192,-93],[108,-93],[108,207],[-192,207]],"c":true},"ix":2},"nm":"Trazado 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Grupo 1","np":2,"cix":2,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"mm","mm":4,"nm":"Combinar trazados 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.952999997606,0.611999990426,0.071000005685,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Relleno 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[30.25,20.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Grupo 1","np":4,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-11.046],[11.046,0],[0,11.046],[-11.046,0]],"o":[[0,11.046],[-11.046,0],[0,-11.046],[11.046,0]],"v":[[20,0],[0,20],[-20,0],[0,-20]],"c":true},"ix":2},"nm":"Trazado 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-182,207],[118,207],[118,-93],[-182,-93]],"c":true},"ix":2},"nm":"Trazado 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-182,-93],[118,-93],[118,207],[-182,207]],"c":true},"ix":2},"nm":"Trazado 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Grupo 1","np":2,"cix":2,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"mm","mm":4,"nm":"Combinar trazados 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.944999964097,0.769000004787,0.059000000299,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Relleno 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[20.25,20.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Grupo 2","np":4,"cix":2,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":89,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Circle 2","td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,150,0],"ix":2},"a":{"a":0,"k":[137.25,137.25,0],"ix":1},"s":{"a":0,"k":[97.333,97.333,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-75.663],[75.663,0],[0,75.663],[-75.663,0]],"o":[[0,75.663],[-75.663,0],[0,-75.663],[75.663,0]],"v":[[137,0],[0,137],[-137,0],[0,-137]],"c":true},"ix":2},"nm":"Trazado 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-150,150],[150,150],[150,-150],[-150,-150]],"c":true},"ix":2},"nm":"Trazado 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-150,-150],[150,-150],[150,150],[-150,150]],"c":true},"ix":2},"nm":"Trazado 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Grupo 1","np":2,"cix":2,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"mm","mm":4,"nm":"Combinar trazados 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.081999999402,0.626999978458,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Relleno 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[137.25,137.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Grupo 1","np":4,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":89,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"MountainF contornos","tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,280.135,0],"ix":2},"a":{"a":0,"k":[113.88,151.115,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.25,0.25,0.667],"y":[1,1,1]},"o":{"x":[0.75,0.75,0.333],"y":[0,0,0]},"n":["0p25_1_0p75_0","0p25_1_0p75_0","0p667_1_0p333_0"],"t":0,"s":[100,0,100],"e":[100,110,100]},{"i":{"x":[0.25,0.25,0.667],"y":[1,1,1]},"o":{"x":[0.75,0.75,0.333],"y":[0,0,0]},"n":["0p25_1_0p75_0","0p25_1_0p75_0","0p667_1_0p333_0"],"t":7,"s":[100,110,100],"e":[100,90,100]},{"i":{"x":[0.25,0.25,0.667],"y":[1,1,1]},"o":{"x":[0.75,0.75,0.333],"y":[0,0,0]},"n":["0p25_1_0p75_0","0p25_1_0p75_0","0p667_1_0p333_0"],"t":12,"s":[100,90,100],"e":[100,100,100]},{"t":14}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[30.77,0],[14.62,5.52],[0,0],[-0.921,-0.9]],"o":[[-22.529,17.55],[-16.54,0],[0,0],[1.17,-0.03],[0,0]],"v":[[64.32,48.55],[-17.32,76.55],[-64.32,68.01],[-64.32,-76.52],[-61.039,-75.22]],"c":true},"ix":2},"nm":"Trazado 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-167.32,93.55],[132.68,93.55],[132.68,-206.45],[-167.32,-206.45]],"c":true},"ix":2},"nm":"Trazado 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-167.32,-206.45],[132.68,-206.45],[132.68,93.55],[-167.32,93.55]],"c":true},"ix":2},"nm":"Trazado 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Grupo 1","np":2,"cix":2,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"mm","mm":4,"nm":"Combinar trazados 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.172999991623,0.243000000598,0.313999998803,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Relleno 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[131.2,77.429],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Grupo 1","np":4,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[30.77,0],[23.34,38.3],[0,0],[-1.987,-1.96]],"o":[[-22.529,17.55],[-48.13,0],[0,0],[1.634,-2.264],[0,0]],"v":[[97.635,48.864],[15.995,76.865],[-97.635,12.995],[-34.613,-74.329],[-27.725,-74.905]],"c":true},"ix":2},"nm":"Trazado 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-134.005,93.865],[165.995,93.865],[165.995,-206.135],[-134.005,-206.135]],"c":true},"ix":2},"nm":"Trazado 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-134.005,-206.135],[165.995,-206.135],[165.995,93.865],[-134.005,93.865]],"c":true},"ix":2},"nm":"Trazado 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Grupo 1","np":2,"cix":2,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"mm","mm":4,"nm":"Combinar trazados 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.204000001795,0.286000001197,0.368999974868,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Relleno 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[97.885,77.115],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Grupo 2","np":4,"cix":2,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":89,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"CloudB contornos","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":32,"s":[181.824,192.221,0],"e":[216.824,192.221,0],"to":[5.83333349227905,0,0],"ti":[-5.83333349227905,0,0]},{"t":88}],"ix":2},"a":{"a":0,"k":[14.733,8.87,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.25,0.25,0.667],"y":[1,1,1]},"o":{"x":[0.75,0.75,0.333],"y":[0,0,0]},"n":["0p25_1_0p75_0","0p25_1_0p75_0","0p667_1_0p333_0"],"t":17,"s":[100,0,100],"e":[100,110,100]},{"i":{"x":[0.25,0.25,0.667],"y":[1,1,1]},"o":{"x":[0.75,0.75,0.333],"y":[0,0,0]},"n":["0p25_1_0p75_0","0p25_1_0p75_0","0p667_1_0p333_0"],"t":24,"s":[100,110,100],"e":[100,90,100]},{"i":{"x":[0.25,0.25,0.667],"y":[1,1,1]},"o":{"x":[0.75,0.75,0.333],"y":[0,0,0]},"n":["0p25_1_0p75_0","0p25_1_0p75_0","0p667_1_0p333_0"],"t":29,"s":[100,90,100],"e":[100,100,100]},{"t":31}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.392,-3.246],[3.246,0.392],[-0.391,3.246],[-3.246,-0.392]],"o":[[-0.391,3.246],[-3.246,-0.391],[0.391,-3.246],[3.246,0.391]],"v":[[5.877,0.708],[-0.709,5.878],[-5.878,-0.709],[0.708,-5.878]],"c":true},"ix":2},"nm":"Trazado 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-190.038,106.159],[109.962,106.159],[109.962,-193.841],[-190.038,-193.841]],"c":true},"ix":2},"nm":"Trazado 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-190.038,-193.841],[109.962,-193.841],[109.962,106.159],[-190.038,106.159]],"c":true},"ix":2},"nm":"Trazado 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Grupo 1","np":2,"cix":2,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"mm","mm":4,"nm":"Combinar trazados 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.808000033509,0.808000033509,0.808000033509,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Relleno 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[22.948,10.49],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Grupo 1","np":4,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.245,-2.028],[2.029,0.245],[-0.244,2.029],[-2.029,-0.244]],"o":[[-0.244,2.029],[-2.029,-0.245],[0.244,-2.029],[2.029,0.245]],"v":[[3.673,0.442],[-0.443,3.673],[-3.674,-0.443],[0.443,-3.674]],"c":true},"ix":2},"nm":"Trazado 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-191.304,110.478],[108.696,110.478],[108.696,-189.522],[-191.304,-189.522]],"c":true},"ix":2},"nm":"Trazado 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-191.304,-189.522],[108.696,-189.522],[108.696,110.478],[-191.304,110.478]],"c":true},"ix":2},"nm":"Trazado 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Grupo 1","np":2,"cix":2,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"mm","mm":4,"nm":"Combinar trazados 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.808000033509,0.808000033509,0.808000033509,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Relleno 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[24.213,6.171],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Grupo 2","np":4,"cix":2,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.391,-3.246],[3.246,0.391],[-0.39,3.246],[-3.246,-0.391]],"o":[[-0.392,3.246],[-3.246,-0.392],[0.392,-3.246],[3.246,0.392]],"v":[[5.878,0.708],[-0.709,5.878],[-5.879,-0.708],[0.708,-5.878]],"c":true},"ix":2},"nm":"Trazado 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-173.609,105.902],[126.391,105.902],[126.391,-194.098],[-173.609,-194.098]],"c":true},"ix":2},"nm":"Trazado 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-173.609,-194.098],[126.391,-194.098],[126.391,105.902],[-173.609,105.902]],"c":true},"ix":2},"nm":"Trazado 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Grupo 1","np":2,"cix":2,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"mm","mm":4,"nm":"Combinar trazados 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.808000033509,0.808000033509,0.808000033509,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Relleno 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[6.518,10.746],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Grupo 3","np":4,"cix":2,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.538,-4.464],[4.464,0.539],[-0.538,4.464],[-4.464,-0.538]],"o":[[-0.537,4.463],[-4.463,-0.538],[0.538,-4.463],[4.463,0.538]],"v":[[8.082,0.974],[-0.975,8.081],[-8.082,-0.974],[0.975,-8.082]],"c":true},"ix":2},"nm":"Trazado 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-182.779,107.779],[117.221,107.779],[117.221,-192.221],[-182.779,-192.221]],"c":true},"ix":2},"nm":"Trazado 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-182.779,-192.221],[117.221,-192.221],[117.221,107.779],[-182.779,107.779]],"c":true},"ix":2},"nm":"Trazado 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Grupo 1","np":2,"cix":2,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"mm","mm":4,"nm":"Combinar trazados 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.808000033509,0.808000033509,0.808000033509,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Relleno 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[15.689,8.87],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Grupo 4","np":4,"cix":2,"ix":4,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":89,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"Circle 3","td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,150,0],"ix":2},"a":{"a":0,"k":[137.25,137.25,0],"ix":1},"s":{"a":0,"k":[96.667,96.667,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-75.663],[75.663,0],[0,75.663],[-75.663,0]],"o":[[0,75.663],[-75.663,0],[0,-75.663],[75.663,0]],"v":[[137,0],[0,137],[-137,0],[0,-137]],"c":true},"ix":2},"nm":"Trazado 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-150,150],[150,150],[150,-150],[-150,-150]],"c":true},"ix":2},"nm":"Trazado 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-150,-150],[150,-150],[150,150],[-150,150]],"c":true},"ix":2},"nm":"Trazado 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Grupo 1","np":2,"cix":2,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"mm","mm":4,"nm":"Combinar trazados 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.081999999402,0.626999978458,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Relleno 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[137.25,137.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Grupo 1","np":4,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":89,"st":0,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"MountainB contornos","tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[174.57,280.73,0],"ix":2},"a":{"a":0,"k":[0.249,105.98,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.25,0.25,0.667],"y":[1,1,1]},"o":{"x":[0.75,0.75,0.333],"y":[0,0,0]},"n":["0p25_1_0p75_0","0p25_1_0p75_0","0p667_1_0p333_0"],"t":0,"s":[100,0,100],"e":[100,110,100]},{"i":{"x":[0.25,0.25,0.667],"y":[1,1,1]},"o":{"x":[0.75,0.75,0.333],"y":[0,0,0]},"n":["0p25_1_0p75_0","0p25_1_0p75_0","0p667_1_0p333_0"],"t":7,"s":[100,110,100],"e":[100,90,100]},{"i":{"x":[0.25,0.25,0.667],"y":[1,1,1]},"o":{"x":[0.75,0.75,0.333],"y":[0,0,0]},"n":["0p25_1_0p75_0","0p25_1_0p75_0","0p667_1_0p333_0"],"t":12,"s":[100,90,100],"e":[100,100,100]},{"t":14}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[36.889,-6.891],[0,0]],"o":[[-19.399,30.58],[0,0],[0,0]],"v":[[43.91,-6.726],[-43.91,52.866],[8.52,-52.866]],"c":true},"ix":2},"nm":"Trazado 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-218.48,72.135],[81.52,72.135],[81.52,-227.865],[-218.48,-227.865]],"c":true},"ix":2},"nm":"Trazado 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-218.48,-227.865],[81.52,-227.865],[81.52,72.135],[-218.48,72.135]],"c":true},"ix":2},"nm":"Trazado 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Grupo 1","np":2,"cix":2,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"mm","mm":4,"nm":"Combinar trazados 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.109999997008,0.165000002992,0.20800000359,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Relleno 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[44.159,53.116],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Grupo 1","np":4,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":89,"st":0,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"Circle","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,150,0],"ix":2},"a":{"a":0,"k":[137.25,137.25,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-75.663],[75.663,0],[0,75.663],[-75.663,0]],"o":[[0,75.663],[-75.663,0],[0,-75.663],[75.663,0]],"v":[[137,0],[0,137],[-137,0],[0,-137]],"c":true},"ix":2},"nm":"Trazado 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-150,150],[150,150],[150,-150],[-150,-150]],"c":true},"ix":2},"nm":"Trazado 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-150,-150],[150,-150],[150,150],[-150,150]],"c":true},"ix":2},"nm":"Trazado 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Grupo 1","np":2,"cix":2,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"mm","mm":4,"nm":"Combinar trazados 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.081999999402,0.626999978458,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Relleno 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[137.25,137.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Grupo 1","np":4,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":89,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/lottie/data/lottie.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/lottie/data/lottie.rml new file mode 100644 index 00000000..300e444d --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/lottie/data/lottie.rml @@ -0,0 +1,33 @@ + + + Demo + + + + + + + diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/lottie/src/main.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/lottie/src/main.cpp new file mode 100644 index 00000000..91822104 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/lottie/src/main.cpp @@ -0,0 +1,73 @@ +#include +#include +#include +#include + +#if defined RMLUI_PLATFORM_WIN32 + #include +int APIENTRY WinMain(HINSTANCE /*instance_handle*/, HINSTANCE /*previous_instance_handle*/, char* /*command_line*/, int /*command_show*/) +#else +int main(int /*argc*/, char** /*argv*/) +#endif +{ + int window_width = 1024; + int window_height = 768; + + // Initializes the shell which provides common functionality used by the included samples. + if (!Shell::Initialize()) + return -1; + + // Constructs the system and render interfaces, creates a window, and attaches the renderer. + if (!Backend::Initialize("Lottie sample", window_width, window_height, true)) + { + Shell::Shutdown(); + return -1; + } + + // Install the custom interfaces constructed by the backend before initializing RmlUi. + Rml::SetSystemInterface(Backend::GetSystemInterface()); + Rml::SetRenderInterface(Backend::GetRenderInterface()); + + // RmlUi initialisation. + Rml::Initialise(); + + // Create the main RmlUi context. + Rml::Context* context = Rml::CreateContext("main", Rml::Vector2i(window_width, window_height)); + if (!context) + { + Rml::Shutdown(); + Backend::Shutdown(); + Shell::Shutdown(); + return -1; + } + + Rml::Debugger::Initialise(context); + Shell::LoadFonts(); + + // Load and show the demo document. + if (Rml::ElementDocument* document = context->LoadDocument("basic/lottie/data/lottie.rml")) + { + document->Show(); + document->GetElementById("title")->SetInnerRML("Lottie"); + } + + bool running = true; + while (running) + { + running = Backend::ProcessEvents(context, &Shell::ProcessKeyDownShortcuts, true); + + context->Update(); + + Backend::BeginFrame(); + context->Render(); + Backend::PresentFrame(); + } + + // Shutdown RmlUi. + Rml::Shutdown(); + + Backend::Shutdown(); + Shell::Shutdown(); + + return 0; +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/svg/CMakeLists.txt b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/svg/CMakeLists.txt new file mode 100644 index 00000000..e1131912 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/svg/CMakeLists.txt @@ -0,0 +1,12 @@ +set(SAMPLE_NAME "svg") +set(TARGET_NAME "${RMLUI_SAMPLE_PREFIX}${SAMPLE_NAME}") + +add_executable(${TARGET_NAME} WIN32 + src/main.cpp +) + +set_common_target_options(${TARGET_NAME}) + +target_link_libraries(${TARGET_NAME} PRIVATE rmlui_shell) + +install_sample_target(${TARGET_NAME}) diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/svg/data/LICENSE.txt b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/svg/data/LICENSE.txt new file mode 100644 index 00000000..51774ea3 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/svg/data/LICENSE.txt @@ -0,0 +1,669 @@ +The file 'tiger.svg' included in this directory is licensed under the GNU Affero General Public License. The license is restated below. +This work was downloaded from [1] on 2021-04-24 and included without modifications. Originally derived from tiger.eps (GPL Ghostscript SVN) [2]. + +[1] https://commons.wikimedia.org/wiki/File:Ghostscript_Tiger.svg +[2] http://git.ghostscript.com/?p=ghostpdl.git;a=blob;f=examples/tiger.eps;hb=HEAD + + +---------------------------- + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/svg/data/svg_decorator.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/svg/data/svg_decorator.rml new file mode 100644 index 00000000..f9803418 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/svg/data/svg_decorator.rml @@ -0,0 +1,27 @@ + + + SVG Decorator + + + + +
    + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/svg/data/svg_element.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/svg/data/svg_element.rml new file mode 100644 index 00000000..b7700e2b --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/svg/data/svg_element.rml @@ -0,0 +1,25 @@ + + + SVG Element + + + + + + + diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/svg/data/svg_inline.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/svg/data/svg_inline.rml new file mode 100644 index 00000000..35bf20ce --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/svg/data/svg_inline.rml @@ -0,0 +1,32 @@ + + + SVG Inline + + + + + + + + + + + + diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/svg/data/tiger.svg b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/svg/data/tiger.svg new file mode 100644 index 00000000..679edec2 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/svg/data/tiger.svg @@ -0,0 +1,725 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/svg/src/main.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/svg/src/main.cpp new file mode 100644 index 00000000..bc549f12 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/svg/src/main.cpp @@ -0,0 +1,121 @@ +#include +#include +#include +#include + +struct SVGToogleStruct { + Rml::String svg_data = R"()"; + Rml::String line_color = "black"; + Rml::String fill_color = "red"; + Rml::Element* svg_element; + bool toggle_state = false; + void Toggle(Rml::DataModelHandle model, Rml::Event& /*event*/, const Rml::VariantList& /*args*/) + { + toggle_state = !toggle_state; + + // This example uses 3 methods of setting inline SVG data + // - Used to set the svg data via data-rml with the main svg data in the data-rml attribute but concatenating colour variables from the model + line_color = toggle_state ? "yellow" : "black"; + fill_color = toggle_state ? "green" : "red"; + + // - Used to set the svg data via data-rml with all the svg data contained within a model property + svg_data = R"()"; + + // - Using SetInnerRML directly on an SVG element to change the SVG data + if (svg_element) + svg_element->SetInnerRML(svg_data); + + model.DirtyVariable("svg_data"); + model.DirtyVariable("line_color"); + model.DirtyVariable("fill_color"); + } +} toggle_model; + +#if defined RMLUI_PLATFORM_WIN32 + #include +int APIENTRY WinMain(HINSTANCE /*instance_handle*/, HINSTANCE /*previous_instance_handle*/, char* /*command_line*/, int /*command_show*/) +#else +int main(int /*argc*/, char** /*argv*/) +#endif +{ + int window_width = 1024; + int window_height = 768; + + // Initializes the shell which provides common functionality used by the included samples. + if (!Shell::Initialize()) + return -1; + + // Constructs the system and render interfaces, creates a window, and attaches the renderer. + if (!Backend::Initialize("SVG sample", window_width, window_height, true)) + { + Shell::Shutdown(); + return -1; + } + + // Install the custom interfaces constructed by the backend before initializing RmlUi. + Rml::SetSystemInterface(Backend::GetSystemInterface()); + Rml::SetRenderInterface(Backend::GetRenderInterface()); + + // RmlUi initialisation. + Rml::Initialise(); + + // Create the main RmlUi context. + Rml::Context* context = Rml::CreateContext("main", Rml::Vector2i(window_width, window_height)); + if (context == nullptr) + { + Rml::Shutdown(); + Backend::Shutdown(); + Shell::Shutdown(); + return -1; + } + + Rml::String svg_data; + Rml::DataModelConstructor dm_con = context->CreateDataModel("svg_test_model"); + if (auto model_handle = dm_con.RegisterStruct()) + { + model_handle.RegisterMember("name", &SVGToogleStruct::svg_data); + model_handle.RegisterMember("line_color", &SVGToogleStruct::line_color); + model_handle.RegisterMember("fill_color", &SVGToogleStruct::fill_color); + model_handle.RegisterMember("sprite", &SVGToogleStruct::toggle_state); + } + dm_con.Bind("svg_data", &toggle_model.svg_data); + dm_con.Bind("line_color", &toggle_model.line_color); + dm_con.Bind("fill_color", &toggle_model.fill_color); + dm_con.BindEventCallback("toggle_svg", &SVGToogleStruct::Toggle, &toggle_model); + + Rml::Debugger::Initialise(context); + Shell::LoadFonts(); + + // Load and show the documents. + std::vector rml_docs = {"basic/svg/data/svg_element.rml", "basic/svg/data/svg_decorator.rml", "basic/svg/data/svg_inline.rml"}; + for (const auto& rml_doc : rml_docs) + { + if (Rml::ElementDocument* document = context->LoadDocument(rml_doc)) + { + document->Show(); + document->GetElementById("title")->SetInnerRML(document->GetTitle()); + if (Rml::Element* svg_element = document->GetElementById("svg_1")) + toggle_model.svg_element = svg_element; + } + } + + bool running = true; + while (running) + { + running = Backend::ProcessEvents(context, &Shell::ProcessKeyDownShortcuts, true); + + context->Update(); + + Backend::BeginFrame(); + context->Render(); + Backend::PresentFrame(); + } + + // Shutdown RmlUi. + Rml::Shutdown(); + + Backend::Shutdown(); + Shell::Shutdown(); + + return 0; +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/transform/CMakeLists.txt b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/transform/CMakeLists.txt new file mode 100644 index 00000000..f09a8b16 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/transform/CMakeLists.txt @@ -0,0 +1,12 @@ +set(SAMPLE_NAME "transform") +set(TARGET_NAME "${RMLUI_SAMPLE_PREFIX}${SAMPLE_NAME}") + +add_executable(${TARGET_NAME} WIN32 + src/main.cpp +) + +set_common_target_options(${TARGET_NAME}) + +target_link_libraries(${TARGET_NAME} PRIVATE rmlui_shell) + +install_sample_target(${TARGET_NAME}) diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/transform/data/transform.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/transform/data/transform.rml new file mode 100644 index 00000000..83dff156 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/transform/data/transform.rml @@ -0,0 +1,225 @@ + + + +Transform Sample + + + +
    +
    +
    6
    +
    5
    +
    4
    +
    3
    +
    2
    +
    1
    +
    + perspective: 650dp; +
    + +

    Press 'space' to toggle rotation.

    + +

    Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod +tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At +vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd +gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum +dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor +invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero +eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no +sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit +amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut +labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam +et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata +sanctus est Lorem ipsum dolor sit amet.

    + +

    Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse +molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et +accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit +augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, +consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut +laoreet dolore magna aliquam erat volutpat.

    + + + +

    Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit +lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure +dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore +eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui +blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla +facilisi.

    + +

    Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming +id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, +consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut +laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis +nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea +commodo consequat.

    + +
    +
    +
    6
    +
    5
    +
    4
    +
    3
    +
    2
    +
    1
    +
    + perspective: 250dp; clip:none;
    + (rendered outside the window) +
    + +

    Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse +molestie consequat, vel illum dolore eu feugiat nulla facilisis.

    + +

    At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd +gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum +dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor +invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero +eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no +sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit +amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores +duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet +clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero +voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, +consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore +et dolore magna aliquyam erat.

    + +

    Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut +labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam +et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata +sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur +sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore +magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo +dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est +Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing +elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna +aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores +et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus.

    + +

    Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy +eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam +voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita +kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem +ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod +tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At +vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd +gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum +dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor +invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero +eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no +sea takimata sanctus est Lorem ipsum dolor sit amet.

    + +

    Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse +molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et +accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit +augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, +consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut +laoreet dolore magna aliquam erat volutpat.

    + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/transform/src/main.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/transform/src/main.cpp new file mode 100644 index 00000000..cbdea346 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/transform/src/main.cpp @@ -0,0 +1,165 @@ +#include +#include +#include +#include +#include +#include + +static bool run_rotate = true; + +class DemoWindow : public Rml::EventListener { +public: + DemoWindow(const Rml::String& title, const Rml::Vector2f& position, Rml::Context* context) + { + document = context->LoadDocument("basic/transform/data/transform.rml"); + if (document) + { + document->GetElementById("title")->SetInnerRML(title); + document->SetProperty(Rml::PropertyId::Left, Rml::Property(position.x, Rml::Unit::DP)); + document->SetProperty(Rml::PropertyId::Top, Rml::Property(position.y, Rml::Unit::DP)); + document->Show(); + } + } + + ~DemoWindow() + { + if (document) + document->Close(); + } + + void SetPerspective(float distance) + { + perspective = distance; + + if (document && perspective > 0) + { + std::stringstream s; + s << "perspective(" << perspective << "dp) "; + document->SetProperty("transform", s.str()); + } + } + + void SetRotation(float degrees) + { + if (document) + { + std::stringstream s; + if (perspective > 0) + s << "perspective(" << perspective << "dp) "; + s << "rotate3d(0.0, 1.0, 0.0, " << degrees << "deg)"; + document->SetProperty("transform", s.str()); + } + } + + void ProcessEvent(Rml::Event& ev) override + { + if (ev == Rml::EventId::Keydown) + { + Rml::Input::KeyIdentifier key_identifier = (Rml::Input::KeyIdentifier)ev.GetParameter("key_identifier", 0); + + if (key_identifier == Rml::Input::KI_SPACE) + { + run_rotate = !run_rotate; + } + else if (key_identifier == Rml::Input::KI_ESCAPE) + { + Backend::RequestExit(); + } + } + } + +private: + float perspective = 0; + Rml::ElementDocument* document; +}; + +#if defined RMLUI_PLATFORM_WIN32 + #include +int APIENTRY WinMain(HINSTANCE /*instance_handle*/, HINSTANCE /*previous_instance_handle*/, char* /*command_line*/, int /*command_show*/) +#else +int main(int /*argc*/, char** /*argv*/) +#endif +{ + const int window_width = 1600; + const int window_height = 950; + + // Initializes the shell which provides common functionality used by the included samples. + if (!Shell::Initialize()) + return -1; + + // Constructs the system and render interfaces, creates a window, and attaches the renderer. + if (!Backend::Initialize("Transform Sample", window_width, window_height, true)) + { + Shell::Shutdown(); + return -1; + } + + // Install the custom interfaces constructed by the backend before initializing RmlUi. + Rml::SetSystemInterface(Backend::GetSystemInterface()); + Rml::SetRenderInterface(Backend::GetRenderInterface()); + + // RmlUi initialisation. + Rml::Initialise(); + + // Create the main RmlUi context. + Rml::Context* context = Rml::CreateContext("main", Rml::Vector2i(window_width, window_height)); + if (!context) + { + Rml::Shutdown(); + Backend::Shutdown(); + Shell::Shutdown(); + return -1; + } + + Rml::Debugger::Initialise(context); + Shell::LoadFonts(); + + Rml::UniquePtr window_1 = Rml::MakeUnique("Orthographic transform", Rml::Vector2f(120, 180), context); + if (window_1) + context->GetRootElement()->AddEventListener(Rml::EventId::Keydown, window_1.get()); + + Rml::UniquePtr window_2 = Rml::MakeUnique("Perspective transform", Rml::Vector2f(900, 180), context); + if (window_2) + window_2->SetPerspective(800); + + bool running = true; + while (running) + { + running = Backend::ProcessEvents(context, &Shell::ProcessKeyDownShortcuts); + + context->Update(); + + Backend::BeginFrame(); + context->Render(); + Backend::PresentFrame(); + + double t = Rml::GetSystemInterface()->GetElapsedTime(); + static double t_prev = t; + double dt = t - t_prev; + t_prev = t; + + if (run_rotate) + { + static float deg = 0; + deg = (float)std::fmod(deg + dt * 50.0, 360.0); + if (window_1) + window_1->SetRotation(deg); + if (window_2) + window_2->SetRotation(deg); + } + } + + if (window_1) + context->GetRootElement()->RemoveEventListener(Rml::EventId::Keydown, window_1.get()); + + window_1.reset(); + window_2.reset(); + + // Shutdown RmlUi. + Rml::Shutdown(); + + Backend::Shutdown(); + Shell::Shutdown(); + + return 0; +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/tree_view/CMakeLists.txt b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/tree_view/CMakeLists.txt new file mode 100644 index 00000000..110209ae --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/tree_view/CMakeLists.txt @@ -0,0 +1,14 @@ +set(SAMPLE_NAME "tree_view") +set(TARGET_NAME "${RMLUI_SAMPLE_PREFIX}${SAMPLE_NAME}") + +add_executable(${TARGET_NAME} WIN32 + src/FileBrowser.cpp + src/FileBrowser.h + src/main.cpp +) + +set_common_target_options(${TARGET_NAME}) + +target_link_libraries(${TARGET_NAME} PRIVATE rmlui_shell) + +install_sample_target(${TARGET_NAME}) diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/tree_view/data/tree_view.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/tree_view/data/tree_view.rml new file mode 100644 index 00000000..66009818 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/tree_view/data/tree_view.rml @@ -0,0 +1,54 @@ + + + + Tree View + + + + + + + + + + + + +
    + + + {{ file.name }} +
    + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/tree_view/src/FileBrowser.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/tree_view/src/FileBrowser.cpp new file mode 100644 index 00000000..d996d9d4 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/tree_view/src/FileBrowser.cpp @@ -0,0 +1,98 @@ +#include "FileBrowser.h" +#include +#include +#include +#include + +namespace FileBrowser { + +struct File { + File(bool directory, int depth, Rml::String name) : + visible(depth == 0), directory(directory), collapsed(true), depth(depth), name(std::move(name)) + {} + + bool visible; + bool directory; + bool collapsed; + int depth; + Rml::String name; +}; + +static Rml::Vector files; + +static void BuildTree(const Rml::String& current_directory, int current_depth) +{ + const Rml::StringList directories = PlatformExtensions::ListDirectories(current_directory); + + for (const Rml::String& directory : directories) + { + files.push_back(File(true, current_depth, directory)); + + // Recurse into the child directory + const Rml::String next_directory = current_directory + directory + '/'; + BuildTree(next_directory, current_depth + 1); + } + + const Rml::StringList filenames = PlatformExtensions::ListFiles(current_directory); + for (const Rml::String& filename : filenames) + { + files.push_back(File(false, current_depth, filename)); + } +} + +static void ToggleExpand(Rml::DataModelHandle handle, Rml::Event& /*ev*/, const Rml::VariantList& parameters) +{ + if (parameters.empty()) + return; + + // The index of the file/directory being toggled is passed in as the first parameter. + const size_t toggle_index = (size_t)parameters[0].Get(); + if (toggle_index >= files.size()) + return; + + File& toggle_file = files[toggle_index]; + + const bool collapsing = !toggle_file.collapsed; + const int depth = toggle_file.depth; + + toggle_file.collapsed = collapsing; + + // Loop through all descendent entries. + for (size_t i = toggle_index + 1; i < files.size() && files[i].depth > depth; i++) + { + // Hide all items if we are collapsing. If instead we are expanding, make all direct children visible. + files[i].visible = !collapsing && (files[i].depth == depth + 1); + files[i].collapsed = true; + } + + handle.DirtyVariable("files"); +} + +bool Initialise(Rml::Context* context, const Rml::String& root_dir) +{ + BuildTree(root_dir, 0); + + Rml::DataModelConstructor constructor = context->CreateDataModel("filebrowser"); + + if (!constructor) + return false; + + if (auto file_handle = constructor.RegisterStruct()) + { + file_handle.RegisterMember("visible", &File::visible); + file_handle.RegisterMember("directory", &File::directory); + file_handle.RegisterMember("collapsed", &File::collapsed); + file_handle.RegisterMember("depth", &File::depth); + file_handle.RegisterMember("name", &File::name); + } + + constructor.RegisterArray(); + + constructor.Bind("files", &files); + + constructor.BindEventCallback("toggle_expand", &ToggleExpand); + + return true; +} + +} // namespace FileBrowser diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/tree_view/src/FileBrowser.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/tree_view/src/FileBrowser.h new file mode 100644 index 00000000..f9f733c8 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/tree_view/src/FileBrowser.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +namespace FileBrowser { + +bool Initialise(Rml::Context* context, const Rml::String& root_dir); + +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/tree_view/src/main.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/tree_view/src/main.cpp new file mode 100644 index 00000000..faaab3ed --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/basic/tree_view/src/main.cpp @@ -0,0 +1,79 @@ +#include "FileBrowser.h" +#include +#include +#include +#include +#include + +#if defined RMLUI_PLATFORM_WIN32 + #include +int APIENTRY WinMain(HINSTANCE /*instance_handle*/, HINSTANCE /*previous_instance_handle*/, char* /*command_line*/, int /*command_show*/) +#else +int main(int /*argc*/, char** /*argv*/) +#endif +{ + const int window_width = 1024; + const int window_height = 768; + + // Initializes the shell which provides common functionality used by the included samples. + if (!Shell::Initialize()) + return -1; + + // Constructs the system and render interfaces, creates a window, and attaches the renderer. + if (!Backend::Initialize("Tree View Sample", window_width, window_height, true)) + { + Shell::Shutdown(); + return -1; + } + + // Install the custom interfaces constructed by the backend before initializing RmlUi. + Rml::SetSystemInterface(Backend::GetSystemInterface()); + Rml::SetRenderInterface(Backend::GetRenderInterface()); + + // RmlUi initialisation. + Rml::Initialise(); + + // Create the main RmlUi context. + Rml::Context* context = Rml::CreateContext("main", Rml::Vector2i(window_width, window_height)); + if (!context) + { + Rml::Shutdown(); + Backend::Shutdown(); + Shell::Shutdown(); + return -1; + } + + Rml::Debugger::Initialise(context); + Shell::LoadFonts(); + + // Create the file data source and formatter. + const Rml::String root = PlatformExtensions::FindSamplesRoot(); + FileBrowser::Initialise(context, root); + + // Load and show the demo document. + Rml::ElementDocument* document = context->LoadDocument("basic/tree_view/data/tree_view.rml"); + if (document) + { + document->GetElementById("title")->SetInnerRML(document->GetTitle()); + document->Show(); + } + + bool running = true; + while (running) + { + running = Backend::ProcessEvents(context, &Shell::ProcessKeyDownShortcuts, true); + + context->Update(); + + Backend::BeginFrame(); + context->Render(); + Backend::PresentFrame(); + } + + Rml::Shutdown(); + + Backend::Shutdown(); + Shell::Shutdown(); + + return 0; +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/CMakeLists.txt b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/CMakeLists.txt new file mode 100644 index 00000000..4d65d516 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/CMakeLists.txt @@ -0,0 +1,48 @@ +set(SAMPLE_NAME "invaders") +set(TARGET_NAME "${RMLUI_SAMPLE_PREFIX}${SAMPLE_NAME}") + +add_executable(${TARGET_NAME} WIN32 + src/DecoratorDefender.cpp + src/DecoratorDefender.h + src/DecoratorStarfield.cpp + src/DecoratorStarfield.h + src/Defender.cpp + src/Defender.h + src/ElementGame.cpp + src/ElementGame.h + src/EventHandler.cpp + src/EventHandler.h + src/EventHandlerHighScore.cpp + src/EventHandlerHighScore.h + src/EventHandlerOptions.cpp + src/EventHandlerOptions.h + src/EventHandlerStartGame.cpp + src/EventHandlerStartGame.h + src/EventListener.cpp + src/EventListener.h + src/EventListenerInstancer.cpp + src/EventListenerInstancer.h + src/EventManager.cpp + src/EventManager.h + src/Game.cpp + src/Game.h + src/GameDetails.cpp + src/GameDetails.h + src/HighScores.cpp + src/HighScores.h + src/Invader.cpp + src/Invader.h + src/main.cpp + src/Mothership.cpp + src/Mothership.h + src/Shield.cpp + src/Shield.h + src/Sprite.cpp + src/Sprite.h +) + +set_common_target_options(${TARGET_NAME}) + +target_link_libraries(${TARGET_NAME} PRIVATE rmlui_shell) + +install_sample_target(${TARGET_NAME}) diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/background.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/background.rml new file mode 100644 index 00000000..0ed7aaf0 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/background.rml @@ -0,0 +1,41 @@ + + + + + + + + + diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/background.tga b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/background.tga new file mode 100644 index 0000000000000000000000000000000000000000..faec89c90e8620c7edcd8fa1262d1be6a0dfbdfd GIT binary patch literal 4194322 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 z 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 +
    + + high score: +
    +
    + + lives: +
    +
    + + wave: +
    + + + diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/help.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/help.rml new file mode 100644 index 00000000..adeeca24 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/help.rml @@ -0,0 +1,54 @@ + + + Help + + + + +

    Story

    +

    + One day, without warning, they came for us; endless waves of the invaders, numbers too vast to count, fresh from the Martian foundries. Earth's orbital defences took a heavy toll, but were inevitably overrun, buying enough time only for a single rmlui ship to launch. The prototype X-42 'Defender'-class, not yet tested but piloted by the finest astronaut the Space Corps had to offer. +

    +

    + Do you have what it takes to defeat the invaders and save Earth? +

    +

    The X-42

    +

    + The culmination of decades of research and the pinnacle of Earth's technology, the X-42 'Defender' is the finest weapon mankind has to fight the alien menace. Equipped with the dual-layer ablative tridranium armour, low-velocity depleted-narconium chain-fed belt gun and state-of-the-art liquid-hydrangea turbo-thrusters, it is still going to be nearly impossible for its pilot to secure the safety of mankind. +

    +

    The Invaders

    +

    + The most numerous invader you'll face is a primitive drone that is primarily employed by its Martian overlords in the mines of Cydonia. It is large, ungainly and poorly-armed, so ill-suited to its new role as the backbone of the invasion fleet. However, whatever it lacks in pugilistic prowess it makes up for with numbers. Each drone is worth 10 points. +

    +

    + Behind the drones stand the formidable berserker units, encased in their zyterium battle-suits and wielding the dreaded Nimbus death-rods. A lone unit is no match for a skilled X-42 pilot, but in large groups they adopt complex flight patterns that can confuse even the best targetting computer. Pick them off and separate them when you can. Each berserker is worth 20 points. +

    +

    + The dreaded commander death-droids lie at the back of every Martian wave. Small, nimble and deadly, these ships are the true test of an X-42 pilot's abilities. Consider yourself warned, and watch out especially for their mind control attack! Each droid is worth 40 points. +

    +

    + Occasionally one of the invasion motherships will drop into low-orbit to provide the Martian Overbrain with a close look at the progress of the battle; this is when they are most vulnerable, destroy them if you can! The loss of even a single mothership will be keenly felt by the fleet. The points value of each mothership is variable, depending on the seniority of its command synapse. +

    +

    + Destroy all the invaders in each wave before any get past your ship to invade Earth, unleash their deadly toxin and wipe out all of humanity! +

    + + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/help_defender.tga b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/help_defender.tga new file mode 100644 index 0000000000000000000000000000000000000000..e10f9463b76a37c9387878a77659913703eebc79 GIT binary patch literal 32786 zcmeI3y>1gh6onTkjD#qVK%qpbs1Ow`TR~6DJETcV5z!*V6YvUofKcq4Ih=1ZBkhiz zm|cfRGxuEU%3j;Ep7Y(EpS?5gy2o9%i>~k9Jk3XbHUSeb0TVC*6EFc2FaZ-V0TVC* z6EFc2FaZ-V0TVC*6EJ~$An-`%cp~4(pUYp!U&&A8uN!r=p%JZUMmr2(5zWy3Ob(N` z@(=P}{z?A1QAb-ZM=P4q4g*+3Gp>E~zn6cJpUJ<=f69L~>S#kFTG5Pl7{DT$(f`o~ z;P69!B_Dz~TG5Pl7{DT$F$U5P(iX0+7ut5ay~b-bG$-vafJHQ89@NX}4~JUgxVGMC zPTG4t!y=k7?lB)?OpF?Tw8H=v(TsaM=K0L^qQ)QXFn~ogbNp|veaGMJ-|c_xM5@wq z>g<{Sx&Qb0@BZKYzx)46`|-8acK<)#^Z5SH_kX_s_x=B{wjZsvocsSV=jr@!-2AWC ze{R;kdi4bn*ME*$|E;y(&-GT!qp$y#`#)xTKQgCQp3`_g)fj#M$3{%1`@g>F_%d^9 zCGOg#GCs1dq!F#r_kWf9Kgk80Cq6@?@#p$RBU+>H|19@^pC&G}jsG71+%6yLIzy8j zZNwg}jDPfjQTKmK`pnAtz`gDVE#rJB{;Jt@tUmU&_j}Glz4w5&Fr5#uFO@2LB;>x%YnZC9-?6!B;5 z@8uEm^SeardgrM11xbybpRX(0yR~J$HT{3->>j?H|LME4&vhI*(w84TpY|R?Y{=54=q7 s{Cw-tw>}dv0TVC*6EFc2FaZ-V0TVC*6EFc2FaZ-V0TVC*69`1$FASy}dH?_b literal 0 HcmV?d00001 diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/help_invader_mothership.tga b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/help_invader_mothership.tga new file mode 100644 index 0000000000000000000000000000000000000000..3bcd56d0e52bf740c9a860a9c6e655dca7059a51 GIT binary patch literal 32786 zcmeHOv5wO~6bw)t5~4r?L5Z%Sq@ul4oM@@|iZpbHlcHArwpIC>gBEzDT9Z}aT< z*;#MwcxR3EH~|AsaE)ic1@IDh1-vf7I?u6Loc-lo1Efv>;@*p^_O=h#LY+NLgZ z8&B%_PVJxjJMVGo{0sOC{42pa&#{d*v`t;+HlEb;J+*%uUoSC$Yv3mU-FLfv6l;NP zw4trd2dqEl3~vDL7w<~2&U0<~gzX^~sq1&(0XU>T+h{{un-5rj%o*0e zXMpwu>uaDbpRoO7$AinLbTDv%wlLoF-Y&I2s6XQYZD?!r0qbuAn_J)xxG%vv&#}$x zA3GjgLZyQN8(*nyfY$v#Xm8{L)?b)Eg#B1=%O`Aq(`WE_I;NgQlZ~e~9&`Wx0^9)L z+Vbli7i)!m>4Uxo`it#v$Jg-3oTDq>K=Y!Dji)vqf5Ys50DpjE^=DuDps#`c3cVuw zx9T7I^YRT0dC|p-$Gh;BV&pn^HHs){n%I&z=>&OK;Ava4e{O#rn z*aAny_oc0mVox9!QTy-5GPJuf@`wg2zy&m179{om$)L9W*)9;|lYD}WadLR;1+ zw>R_6F8{ON|D5NkR#NfL0qeN!dG*rczkUA0=OG=x?<`V(YJceWs-^31 zpZ{KB!dwHq7jSP_z3uTL?tjjebLQL`54k7uz92WbmB@U+u8+AL7x{g)=kqhS=RMwf z=Gq_dd+*%z^~O`{pKHAL^pDI3JpQL{FZ9nj-h28-<^xmf>s`OlU-8~s1&{4#o)6Uf zeHHT+?;o}8Q#++*o{te=7TLA`&6al3C_@%>np()IK51IFiA+`-48 z|A(cUnLPJlz{?M=;UpGa{}1gmM%koez{?L9pJS2p|FzO7C(mL)42S_SAO^&M7!U(u jKn#chF(3xSfEW-1Vn7Ut0Wly3#DEwO17bi7jLN`&D4cmB literal 0 HcmV?d00001 diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/help_invader_rank1.tga b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/help_invader_rank1.tga new file mode 100644 index 0000000000000000000000000000000000000000..722557738ec6db0728f9b7932098800726a8d939 GIT binary patch literal 16402 zcmeI2KZ_JW5XE=b-~=%c6pciT42_JGo5;iT0`VJ&iGqTGC>%zD9A*llk(wwd7#Q3I z`_abtTc(!2W_PN4d*`^j?Se-#k6JRfQ2h45QQi64H9frr9f!CNoTK^L1;|8Rj5 z+;Z0&ne+;|4Cgz!p2e;*mER}nJ`)}btOb(-rN7|aB zb)?h%sPdigRq)mebkM~nCw}9UyWXgoUSYm6X6d&BC)`###>2|L`t6JGgStd}bg{wK z{LIP(d-4lcIK#bEF3Z8TuqUMQ4>|vo-7jo2?Xkf&^p)EFp6uWZ_fjd1e--{mY!tx! zKB527-4nKjb;Msf{^#7qC$;~M*kET!h`)6F$Ip9v9zPL>Z^pw)k5620b}HKlvlwH2 z?>_KnIf5fxZNHJbABdm#_<1C}6g~^qPAT73zCx7xo7s09;Rvt$0Kf-iP@#71h__lV!vgH33a@i~KdcgjGJrZMrcjXwIw>}Ww2g+xO|ds~5Mso+msLj?k9buBs&)m2C|w1;RYuGycq zyzk^hjyJL6c(a?6#?n(9+nJd+_Dme_&Sp1fRawrKv!{2eJG+_(%md~D^MHB4JYXI$ z510qc1LlDY58Tk$Tf%MOzVJYJBs>A04Qt#v>Ei=s?%lr7fNxN-1cIU)D17`B_+R5`{6sZ->6) zk&O;?;e#-Lg!ZvchUSa5cs`aq&=$Yg^+C80`tqsQN5WkjWIEaCKo>a!KLG03gl&L& zX}-XBVLY$Y2HN76xw+!5>sb6NAE2u(f2`CFu&*><#NxRvezC1a&-u(<|FwMSiscXN zE6o?gIO_;sUWR_w&vUQ`}T%)p8erKJ?wAX573VfupJI?$t~=w+l3SwuLw!@b7g>B`{B8bi{agGG$$!!J z-_-J5XphT(CGWrJ{wLHowR^|EWd9T0|4xl>Xpi`p?tjDYpL(x>Z$o|I{d_*(_z3$` z_lw11<0CcR&9`r3>Gx0g{hRfD>vN}`TYT=+Q^n(dp*psfe*cE&AG}9V={8?lx6WepEK|I(w4Qj`x;{>tr_WqzzvFOByG*_Io8 z$)2D(-_FVhx%^V&Wk`&eIcpjbT#s+NJcj32(>Yc#vIE^#Ri2dx#ehQpT_@J#m z2a7z9f6;}`D%WGnzRM8)*!@X(Ct$k|+UR4<`Wo;bvi`q_+Z%zs3E!kXXrqrYDgLa1 zL$Ckn_YoWdq~6b2`wPh?R!)SW=lgoJsPJ3kv9J2h0QJ0rP-) I;QD#sKUNipPyhe` literal 0 HcmV?d00001 diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/help_invader_rank3.tga b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/help_invader_rank3.tga new file mode 100644 index 0000000000000000000000000000000000000000..9f832c3b74288da9c6c2bbb919df653f022166be GIT binary patch literal 16402 zcmeI2zm5|z5XSST2m(C<}GW6ykJ&t&6dGMP=HG?QlX3crUUT#0U?g$Tsr@~8N89q+19 zdG=EKZGIkUf;YmkaK163XT*SgKrT=ZJV6It#+;eyHhM;Ev4u@+r`8t@$r78N`0-Kr zC2Zr5ziVMBP!~Nx2i>)>Yd&mY6WgivMMJV=q0!xZXDu$c>$=O|wfg$xPfW3i?bQ0B zAz5Pc6JJ_v*{>z`e``;+Q|n6>c@5qtKfC_@(3tna3*m7X|3Fvv(4zp_JY#Nj&}9r` zyK`;ZU#aoclv%P*eny{v%hG3oJ^D(37qzVsUJK+epxp@cp@S}C7|R@SzHR*JTMJ8J zpZX%TEJ@}6MbGn`xG-Z}h|DFl&gztjqAN{Q1#X4I5?R}VUr@pnPdwjqbe8RWVa$5$D1oHNi zu+RQ%V`TZ-d(GGC(suf>jSu*OPxw|^{&)V@+5hdD+V!;eTD(@5w%0G6q4)ol_0RfS z`d<8p{`}`Xr4jhN`fGhk^R@HiL-3{172iq=?`piS@R>ap{`i7V_*PnYXQker3S;4q zFZhISr6q~~bUNLD*=%+JyMOKdd_LcFVf*d9$g=;3p|du$b9$s7p(_@|CsXKi$_*H2z+C%a~)g0 zd;GKNL5KfX^WEd0RSyFHG3UF-KdT-D{x>P#y?EGq;Qjt_)AHTpZ|lKI1NgprB5-a2 zX!DFde)~%Kzl_vJEw&zbzkd$>e}>Y*v-N=U1I{V<4#`;>pv^P-$d^OD&&zz38i5*t Y8i5*t8i5*t8i5*t8i5*te=!1o0Dv&GtN;K2 literal 0 HcmV?d00001 diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/high_score.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/high_score.rml new file mode 100644 index 00000000..a5718c71 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/high_score.rml @@ -0,0 +1,82 @@ + + + High Scores + + + + + + + + + + + + + + + + + + + + + + + + + +
    PilotShipWaveScore
    + + + {{score.name}} + + + + {{score.wave}} + + {{score.score}} +
    No scores recorded. Play a game!
    + + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/high_scores_defender.tga b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/high_scores_defender.tga new file mode 100644 index 0000000000000000000000000000000000000000..05163e9eb44a3c9916eb33e9905748eb0b451b53 GIT binary patch literal 4114 zcmeH}K?=e!5Jgj0(n9MA#G`osTXf;RLc4I&_`WotC&k=J?KG7KZglhdZA=LfP*7di0jBec*mkEpY^E;C6c7^}| literal 0 HcmV?d00001 diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/invaders.tga b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/invaders.tga new file mode 100644 index 0000000000000000000000000000000000000000..5f0e40d006f80a4043f7d30fdc9b6466c8091c03 GIT binary patch literal 65554 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 literal 0 HcmV?d00001 diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/logo.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/logo.rml new file mode 100644 index 00000000..6ab86604 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/logo.rml @@ -0,0 +1,23 @@ + + + + + + + + diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/logo.tga b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/logo.tga new file mode 100644 index 0000000000000000000000000000000000000000..4242510711624f9bf8cf142e601c9b8603388fda GIT binary patch literal 524306 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 literal 0 HcmV?d00001 diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/main_menu.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/main_menu.rml new file mode 100644 index 00000000..46c8d3d4 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/main_menu.rml @@ -0,0 +1,27 @@ + + + Main Menu + + + + +
    +
    +
    +
    + + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/options.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/options.rml new file mode 100644 index 00000000..7e372f47 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/options.rml @@ -0,0 +1,47 @@ + + + Options + + + + +
    +
    +

    + Graphics
    +
    +
    +
    +

    + +

    + Audio
    +
    + +

    +
    + + +
    + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/pause.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/pause.rml new file mode 100644 index 00000000..cd57e361 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/pause.rml @@ -0,0 +1,26 @@ + + + Quit? + + + + +
    +

    Are you sure you want to end this game?

    + + + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/start_game.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/start_game.rml new file mode 100644 index 00000000..0a2308ee --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/data/start_game.rml @@ -0,0 +1,58 @@ + + + Start Game + + + + +
    +
    +

    + Difficulty
    +
    + +

    +

    + Colour
    + +

    +
    + Start Game! +
    + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/DecoratorDefender.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/DecoratorDefender.cpp new file mode 100644 index 00000000..8a42cba6 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/DecoratorDefender.cpp @@ -0,0 +1,87 @@ +#include "DecoratorDefender.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct DecoratorDefenderElementData { + Rml::Texture texture; + Rml::Geometry geometry; +}; + +DecoratorDefender::~DecoratorDefender() {} + +bool DecoratorDefender::Initialise(const Rml::Texture& texture) +{ + image_index = AddTexture(texture); + if (image_index == -1) + { + return false; + } + + return true; +} + +Rml::DecoratorDataHandle DecoratorDefender::GenerateElementData(Rml::Element* element, Rml::BoxArea /*paint_area*/) const +{ + Rml::RenderManager* render_manager = element->GetRenderManager(); + if (!render_manager) + return Rml::Decorator::INVALID_DECORATORDATAHANDLE; + + Rml::Vector2f position = element->GetAbsoluteOffset(Rml::BoxArea::Padding); + Rml::Vector2f size = element->GetBox().GetSize(Rml::BoxArea::Padding); + Rml::Math::SnapToPixelGrid(position, size); + + Rml::ColourbPremultiplied color = element->GetProperty("image-color").ToPremultiplied(); + Rml::Mesh mesh; + Rml::MeshUtilities::GenerateQuad(mesh, Rml::Vector2f(0.f), size, color); + + DecoratorDefenderElementData* element_data = new DecoratorDefenderElementData{ + GetTexture(image_index), + render_manager->MakeGeometry(std::move(mesh)), + }; + + if (!element_data->texture || !element_data->geometry) + return Rml::Decorator::INVALID_DECORATORDATAHANDLE; + + return reinterpret_cast(element_data); +} + +void DecoratorDefender::ReleaseElementData(Rml::DecoratorDataHandle element_data_handle) const +{ + delete reinterpret_cast(element_data_handle); +} + +void DecoratorDefender::RenderElement(Rml::Element* element, Rml::DecoratorDataHandle element_data_handle) const +{ + Rml::Vector2f position = element->GetAbsoluteOffset(Rml::BoxArea::Padding).Round(); + DecoratorDefenderElementData* element_data = reinterpret_cast(element_data_handle); + element_data->geometry.Render(position, element_data->texture); +} + +DecoratorInstancerDefender::DecoratorInstancerDefender() +{ + id_image_src = RegisterProperty("image-src", "").AddParser("string").GetId(); + RegisterShorthand("decorator", "image-src", Rml::ShorthandType::FallThrough); +} + +DecoratorInstancerDefender::~DecoratorInstancerDefender() {} + +Rml::SharedPtr DecoratorInstancerDefender::InstanceDecorator(const Rml::String& /*name*/, const Rml::PropertyDictionary& properties, + const Rml::DecoratorInstancerInterface& instancer_interface) +{ + const Rml::Property* image_source_property = properties.GetProperty(id_image_src); + Rml::String image_source = image_source_property->Get(); + Rml::Texture texture = instancer_interface.GetTexture(image_source); + + auto decorator = Rml::MakeShared(); + if (decorator->Initialise(texture)) + return decorator; + + return nullptr; +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/DecoratorDefender.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/DecoratorDefender.h new file mode 100644 index 00000000..ef53251a --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/DecoratorDefender.h @@ -0,0 +1,34 @@ +#pragma once + +#include + +class DecoratorDefender : public Rml::Decorator { +public: + virtual ~DecoratorDefender(); + + bool Initialise(const Rml::Texture& texture); + + /// Called on a decorator to generate any required per-element data for a newly decorated element. + Rml::DecoratorDataHandle GenerateElementData(Rml::Element* element, Rml::BoxArea paint_area) const override; + /// Called to release element data generated by this decorator. + void ReleaseElementData(Rml::DecoratorDataHandle element_data) const override; + + /// Called to render the decorator on an element. + void RenderElement(Rml::Element* element, Rml::DecoratorDataHandle element_data) const override; + +private: + int image_index; +}; + +class DecoratorInstancerDefender : public Rml::DecoratorInstancer { +public: + DecoratorInstancerDefender(); + ~DecoratorInstancerDefender(); + + /// Instances a decorator given the property tag and attributes from the RCSS file. + Rml::SharedPtr InstanceDecorator(const Rml::String& name, const Rml::PropertyDictionary& properties, + const Rml::DecoratorInstancerInterface& instancer_interface) override; + +private: + Rml::PropertyId id_image_src; +}; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/DecoratorStarfield.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/DecoratorStarfield.cpp new file mode 100644 index 00000000..f40f3fc0 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/DecoratorStarfield.cpp @@ -0,0 +1,155 @@ +#include "DecoratorStarfield.h" +#include "GameDetails.h" +#include "Sprite.h" +#include +#include +#include +#include +#include +#include + +DecoratorStarfield::~DecoratorStarfield() {} + +bool DecoratorStarfield::Initialise(int _num_layers, const Rml::Colourb& _top_colour, const Rml::Colourb& _bottom_colour, float _top_speed, + float _bottom_speed, int _top_density, int _bottom_density) +{ + num_layers = _num_layers; + top_colour = _top_colour; + bottom_colour = _bottom_colour; + top_speed = _top_speed; + bottom_speed = _bottom_speed; + top_density = _top_density; + bottom_density = _bottom_density; + + return true; +} + +Rml::DecoratorDataHandle DecoratorStarfield::GenerateElementData(Rml::Element* element, Rml::BoxArea /*paint_area*/) const +{ + const double t = Rml::GetSystemInterface()->GetElapsedTime(); + + StarField* star_field = new StarField(); + star_field->star_layers.resize(num_layers); + + for (int i = 0; i < num_layers; i++) + { + float layer_depth = i / (float)num_layers; + + int density = int((top_density * layer_depth) + (bottom_density * (1.0f - layer_depth))); + star_field->star_layers[i].stars.resize(density); + + Rml::Colourb colour = (top_colour * layer_depth) + (bottom_colour * (1.0f - layer_depth)); + star_field->star_layers[i].colour = colour; + + float speed = (top_speed * layer_depth) + (bottom_speed * (1.0f - layer_depth)); + star_field->star_layers[i].speed = speed; + + star_field->dimensions = element->GetBox().GetSize(Rml::BoxArea::Padding); + + if (star_field->dimensions.x > 0) + { + for (int j = 0; j < density; j++) + { + star_field->star_layers[i].stars[j].x = (float)Rml::Math::RandomReal(star_field->dimensions.x); + star_field->star_layers[i].stars[j].y = (float)Rml::Math::RandomReal(star_field->dimensions.y); + } + } + + star_field->last_update = t; + } + + return reinterpret_cast(star_field); +} + +void DecoratorStarfield::ReleaseElementData(Rml::DecoratorDataHandle element_data) const +{ + delete reinterpret_cast(element_data); +} + +void DecoratorStarfield::RenderElement(Rml::Element* element, Rml::DecoratorDataHandle element_data) const +{ + const double t = Rml::GetSystemInterface()->GetElapsedTime(); + + StarField* star_field = reinterpret_cast(element_data); + star_field->Update(t); + + const float dp_ratio = Rml::ElementUtilities::GetDensityIndependentPixelRatio(element); + const float point_size = Rml::Math::RoundUp(2.f * dp_ratio); + + int num_stars = 0; + + for (size_t i = 0; i < star_field->star_layers.size(); i++) + num_stars += (int)star_field->star_layers[i].stars.size(); + + ColoredPointList points; + points.reserve(num_stars); + + for (size_t i = 0; i < star_field->star_layers.size(); i++) + { + Rml::ColourbPremultiplied color = star_field->star_layers[i].colour.ToPremultiplied(); + + for (size_t j = 0; j < star_field->star_layers[i].stars.size(); j++) + { + const Rml::Vector2f position = star_field->star_layers[i].stars[j]; + points.push_back(ColoredPoint{color, position}); + } + } + + if (Rml::RenderManager* render_manager = element->GetRenderManager()) + DrawPoints(*render_manager, point_size, points); +} + +void DecoratorStarfield::StarField::Update(double t) +{ + float delta_time = float(t - last_update); + last_update = t; + + if (!GameDetails::GetPaused()) + { + for (size_t i = 0; i < star_layers.size(); i++) + { + float movement = star_layers[i].speed * delta_time; + + for (size_t j = 0; j < star_layers[i].stars.size(); j++) + { + star_layers[i].stars[j].y += movement; + if (star_layers[i].stars[j].y > dimensions.y) + { + star_layers[i].stars[j].y = 0; + star_layers[i].stars[j].x = Rml::Math::RandomReal(dimensions.x); + } + } + } + } +} + +DecoratorInstancerStarfield::DecoratorInstancerStarfield() +{ + id_num_layers = RegisterProperty("num-layers", "3").AddParser("number").GetId(); + id_top_colour = RegisterProperty("top-colour", "#dddc").AddParser("color").GetId(); + id_bottom_colour = RegisterProperty("bottom-colour", "#333c").AddParser("color").GetId(); + id_top_speed = RegisterProperty("top-speed", "10.0").AddParser("number").GetId(); + id_bottom_speed = RegisterProperty("bottom-speed", "2.0").AddParser("number").GetId(); + id_top_density = RegisterProperty("top-density", "15").AddParser("number").GetId(); + id_bottom_density = RegisterProperty("bottom-density", "10").AddParser("number").GetId(); +} + +DecoratorInstancerStarfield::~DecoratorInstancerStarfield() {} + +Rml::SharedPtr DecoratorInstancerStarfield::InstanceDecorator(const Rml::String& /*name*/, const Rml::PropertyDictionary& properties, + const Rml::DecoratorInstancerInterface& /*instancer_interface*/) +{ + int num_layers = properties.GetProperty(id_num_layers)->Get(); + Rml::Colourb top_colour = properties.GetProperty(id_top_colour)->Get(); + Rml::Colourb bottom_colour = properties.GetProperty(id_bottom_colour)->Get(); + float top_speed = properties.GetProperty(id_top_speed)->Get(); + float bottom_speed = properties.GetProperty(id_bottom_speed)->Get(); + int top_density = properties.GetProperty(id_top_density)->Get(); + int bottom_density = properties.GetProperty(id_bottom_density)->Get(); + + auto decorator = Rml::MakeShared(); + if (decorator->Initialise(num_layers, top_colour, bottom_colour, top_speed, bottom_speed, top_density, bottom_density)) + return decorator; + + return nullptr; +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/DecoratorStarfield.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/DecoratorStarfield.h new file mode 100644 index 00000000..a3fafa4a --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/DecoratorStarfield.h @@ -0,0 +1,58 @@ +#pragma once + +#include +#include + +class DecoratorStarfield : public Rml::Decorator { +public: + virtual ~DecoratorStarfield(); + + bool Initialise(int num_layers, const Rml::Colourb& top_colour, const Rml::Colourb& bottom_colour, float top_speed, float bottom_speed, + int top_density, int bottom_density); + + /// Called on a decorator to generate any required per-element data for a newly decorated element. + Rml::DecoratorDataHandle GenerateElementData(Rml::Element* element, Rml::BoxArea paint_area) const override; + /// Called to release element data generated by this decorator. + void ReleaseElementData(Rml::DecoratorDataHandle element_data) const override; + + /// Called to render the decorator on an element. + void RenderElement(Rml::Element* element, Rml::DecoratorDataHandle element_data) const override; + +private: + int num_layers; + Rml::Colourb top_colour; + Rml::Colourb bottom_colour; + float top_speed; + float bottom_speed; + int top_density; + int bottom_density; + + struct StarLayer { + typedef Rml::Vector StarList; + StarList stars; + Rml::Colourb colour; + float speed; + }; + + struct StarField { + void Update(double t); + double last_update; + Rml::Vector2f dimensions; + + typedef Rml::Vector StarLayerList; + StarLayerList star_layers; + }; +}; + +class DecoratorInstancerStarfield : public Rml::DecoratorInstancer { +public: + DecoratorInstancerStarfield(); + ~DecoratorInstancerStarfield(); + + /// Instances a decorator given the property tag and attributes from the RCSS file. + Rml::SharedPtr InstanceDecorator(const Rml::String& name, const Rml::PropertyDictionary& properties, + const Rml::DecoratorInstancerInterface& instancer_interface) override; + +private: + Rml::PropertyId id_num_layers, id_top_colour, id_bottom_colour, id_top_speed, id_bottom_speed, id_top_density, id_bottom_density; +}; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/Defender.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/Defender.cpp new file mode 100644 index 00000000..d52cd383 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/Defender.cpp @@ -0,0 +1,154 @@ +#include "Defender.h" +#include "Game.h" +#include "GameDetails.h" +#include "Invader.h" +#include "Mothership.h" +#include "Shield.h" +#include "Sprite.h" +#include + +const float UPDATE_FREQ = 0.01f; +const float MOVEMENT_SPEED = 300; +const float BULLET_SPEED = 15; +const int SPRITE_WIDTH = 64; +const float RESPAWN_TIME = 1.0f; + +Sprite defender_sprite(Rml::Vector2f(60, 31), Rml::Vector2f(0, 0.5), Rml::Vector2f(0.23437500, 0.98437500)); +Sprite bullet_sprite(Rml::Vector2f(4, 20), Rml::Vector2f(0.4921875, 0.515625), Rml::Vector2f(0.5078125, 0.828125)); +Sprite explosion_sprite(Rml::Vector2f(52, 28), Rml::Vector2f(0.71484375f, 0.51562500f), Rml::Vector2f(0.91796875f, 0.95312500f)); + +Defender::Defender(Game* _game) +{ + move_direction = 0; + defender_frame_start = 0; + bullet_in_flight = false; + respawn_start = 0; + game = _game; + position.x = game->GetWindowDimensions().x / 2; + position.y = game->GetWindowDimensions().y - 50; + state = ALIVE; + render = true; +} + +Defender::~Defender() {} + +void Defender::Update(double t) +{ + float dt = float(t - defender_frame_start); + if (dt < UPDATE_FREQ) + return; + + dt = Rml::Math::Min(dt, 0.1f); + + defender_frame_start = t; + + position.x += (move_direction * dt * MOVEMENT_SPEED); + + if (position.x < 5) + position.x = 5; + else if (position.x > (game->GetWindowDimensions().x - SPRITE_WIDTH - 5)) + position.x = game->GetWindowDimensions().x - SPRITE_WIDTH - 5; + + UpdateBullet(t); + + if (state == RESPAWN) + { + // Switch the render flag so the defender "flickers" + render = !render; + + // Check if we should switch back to our alive state + if (float(t - respawn_start) > RESPAWN_TIME) + { + state = ALIVE; + render = true; + } + } +} + +void Defender::Render(Rml::RenderManager& render_manager, float dp_ratio, Rml::Texture texture) +{ + Rml::ColourbPremultiplied color = GameDetails::GetDefenderColour().ToPremultiplied(); + + // Render our sprite if rendering is enabled + if (render) + defender_sprite.Render(render_manager, position, dp_ratio, color, texture); + + // Render the bullet + if (bullet_in_flight) + bullet_sprite.Render(render_manager, bullet_position, dp_ratio, color, texture); +} + +void Defender::StartMove(float direction) +{ + move_direction = direction > 0.0f ? 1.0f : -1.0f; +} + +void Defender::StopMove(float direction) +{ + float stop_direction = direction > 0.0f ? 1.0f : -1.0f; + if (stop_direction == move_direction) + move_direction = 0.0f; +} + +void Defender::Fire() +{ + if (!bullet_in_flight) + { + bullet_position = position + Rml::Vector2f((SPRITE_WIDTH / 2) - 4, 0); + bullet_in_flight = true; + } +} + +bool Defender::CheckHit(double t, const Rml::Vector2f& check_position) +{ + float sprite_width = defender_sprite.dimensions.x; + float sprite_height = defender_sprite.dimensions.y; + + // If the position is within our bounds, set ourselves + // as exploding and return a valid hit. + if (state == ALIVE && check_position.x >= position.x && check_position.x <= position.x + sprite_width && check_position.y >= position.y && + check_position.y <= position.y + sprite_height) + { + game->RemoveLife(); + state = RESPAWN; + respawn_start = t; + + return true; + } + + return false; +} + +void Defender::UpdateBullet(double t) +{ + if (bullet_in_flight) + { + // Move the bullet up and mark it dead if it flies off the top of the screen + bullet_position.y -= BULLET_SPEED; + if (bullet_position.y < 0) + { + bullet_in_flight = false; + return; + } + + // Check if we hit the shields + for (int i = 0; i < game->GetNumShields(); i++) + { + if (game->GetShield(i)->CheckHit(bullet_position)) + { + bullet_in_flight = false; + return; + } + } + + // Check if we hit any invaders + for (int i = 0; i < game->GetNumInvaders(); i++) + { + if (game->GetInvader(i)->CheckHit(t, bullet_position)) + { + bullet_in_flight = false; + return; + } + } + } +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/Defender.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/Defender.h new file mode 100644 index 00000000..50ced63e --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/Defender.h @@ -0,0 +1,51 @@ +#pragma once + +#include + +class Game; +class Sprite; + +/** + Represents the Earth defender. Stores position and performs the update of the bullet position and collision detection. + */ + +class Defender { +public: + Defender(Game* game); + ~Defender(); + + /// Update the defender state. + void Update(double t); + /// Render the defender. + void Render(Rml::RenderManager& render_manager, float dp_ratio, Rml::Texture texture); + + /// Move the defender left. + void StartMove(float direction); + /// Stop the movement. + void StopMove(float direction); + /// Fire a bullet (if one isn't already in flight). + void Fire(); + + /// Check if an object at the given position would hit the defender. + bool CheckHit(double t, const Rml::Vector2f& position); + +private: + /// Update the bullet, doing collision detection. + void UpdateBullet(double t); + + Game* game; + Rml::Vector2f position; + + float move_direction; + + bool bullet_in_flight; + Rml::Vector2f bullet_position; + + double defender_frame_start; + double respawn_start; + + bool render; + + enum State { ALIVE, RESPAWN }; + State state; +}; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/ElementGame.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/ElementGame.cpp new file mode 100644 index 00000000..3ee13fb9 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/ElementGame.cpp @@ -0,0 +1,86 @@ +#include "ElementGame.h" +#include "Defender.h" +#include "EventManager.h" +#include "Game.h" +#include +#include +#include +#include +#include + +ElementGame::ElementGame(const Rml::String& tag) : Rml::Element(tag) +{ + game = new Game(); +} + +ElementGame::~ElementGame() +{ + delete game; +} + +void ElementGame::ProcessEvent(Rml::Event& event) +{ + if (event == Rml::EventId::Keydown || event == Rml::EventId::Keyup) + { + bool key_down = (event == Rml::EventId::Keydown); + Rml::Input::KeyIdentifier key_identifier = (Rml::Input::KeyIdentifier)event.GetParameter("key_identifier", 0); + + // Process left and right keys + if (key_down) + { + if (key_identifier == Rml::Input::KI_LEFT) + game->GetDefender()->StartMove(-1.0f); + if (key_identifier == Rml::Input::KI_RIGHT) + game->GetDefender()->StartMove(1.0f); + if (key_identifier == Rml::Input::KI_SPACE) + game->GetDefender()->Fire(); + } + else + { + if (key_identifier == Rml::Input::KI_LEFT) + game->GetDefender()->StopMove(-1.0f); + if (key_identifier == Rml::Input::KI_RIGHT) + game->GetDefender()->StopMove(1.0f); + } + } + + if (event == Rml::EventId::Load) + { + game->Initialise(); + } +} + +void ElementGame::OnUpdate() +{ + game->Update(Rml::GetSystemInterface()->GetElapsedTime()); +} + +void ElementGame::OnRender() +{ + if (Rml::Context* context = GetContext()) + game->Render(context->GetRenderManager(), context->GetDensityIndependentPixelRatio()); +} + +void ElementGame::OnChildAdd(Rml::Element* element) +{ + Rml::Element::OnChildAdd(element); + + if (element == this) + { + GetOwnerDocument()->AddEventListener(Rml::EventId::Load, this); + GetOwnerDocument()->AddEventListener(Rml::EventId::Keydown, this); + GetOwnerDocument()->AddEventListener(Rml::EventId::Keyup, this); + } +} + +void ElementGame::OnChildRemove(Rml::Element* element) +{ + Rml::Element::OnChildRemove(element); + + if (element == this) + { + GetOwnerDocument()->RemoveEventListener(Rml::EventId::Load, this); + GetOwnerDocument()->RemoveEventListener(Rml::EventId::Keydown, this); + GetOwnerDocument()->RemoveEventListener(Rml::EventId::Keyup, this); + } +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/ElementGame.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/ElementGame.h new file mode 100644 index 00000000..a59e54bb --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/ElementGame.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +class Game; + +class ElementGame : public Rml::Element, public Rml::EventListener { +public: + ElementGame(const Rml::String& tag); + virtual ~ElementGame(); + + /// Intercepts and handles key events. + void ProcessEvent(Rml::Event& event) override; + + /// This will get called when we're added to the tree, which allows us to bind to events. + void OnChildAdd(Rml::Element* element) override; + + /// This will get called when we're removed from the tree, which allows us to clean up the event listeners previously added. + void OnChildRemove(Element* element) override; + +protected: + /// Updates the game. + void OnUpdate() override; + /// Renders the game. + void OnRender() override; + +private: + Game* game; +}; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventHandler.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventHandler.cpp new file mode 100644 index 00000000..a88c8be3 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventHandler.cpp @@ -0,0 +1,3 @@ +#include "EventHandler.h" + +EventHandler::~EventHandler() {} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventHandler.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventHandler.h new file mode 100644 index 00000000..2d2b62cc --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventHandler.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +class EventHandler { +public: + virtual ~EventHandler(); + virtual void ProcessEvent(Rml::Event& event, const Rml::String& value) = 0; +}; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventHandlerHighScore.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventHandlerHighScore.cpp new file mode 100644 index 00000000..c44c1cb7 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventHandlerHighScore.cpp @@ -0,0 +1,44 @@ +#include "EventHandlerHighScore.h" +#include "EventManager.h" +#include "GameDetails.h" +#include "HighScores.h" +#include +#include +#include + +EventHandlerHighScore::EventHandlerHighScore() {} + +EventHandlerHighScore::~EventHandlerHighScore() {} + +void EventHandlerHighScore::ProcessEvent(Rml::Event& event, const Rml::String& value) +{ + if (value == "add_score") + { + int score = GameDetails::GetScore(); + if (score > 0) + { + // Submit the score the player just got to the high scores chart. + HighScores::SubmitScore(GameDetails::GetDefenderColour(), GameDetails::GetWave(), GameDetails::GetScore()); + // Reset the score so the chart won't get confused next time we enter. + GameDetails::ResetScore(); + } + } + else if (value == "enter_name") + { + if (event.GetParameter("key_identifier", Rml::Input::KI_UNKNOWN) == Rml::Input::KI_RETURN) + { + Rml::String name = event.GetCurrentElement()->GetAttribute("value", "Anon."); + HighScores::SubmitName(name); + } + } + else if (value == "check_name") + { + Rml::String name = "Anon."; + + // Submit the name the user started typing + if (auto element = event.GetCurrentElement()->GetOwnerDocument()->GetElementById("player_input")) + name = element->GetAttribute("value", name); + + HighScores::SubmitName(name); + } +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventHandlerHighScore.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventHandlerHighScore.h new file mode 100644 index 00000000..d39d9979 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventHandlerHighScore.h @@ -0,0 +1,11 @@ +#pragma once + +#include "EventHandler.h" + +class EventHandlerHighScore : public EventHandler { +public: + EventHandlerHighScore(); + virtual ~EventHandlerHighScore(); + + void ProcessEvent(Rml::Event& event, const Rml::String& value) override; +}; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventHandlerOptions.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventHandlerOptions.cpp new file mode 100644 index 00000000..cc244283 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventHandlerOptions.cpp @@ -0,0 +1,131 @@ +#include "EventHandlerOptions.h" +#include "EventManager.h" +#include "GameDetails.h" +#include +#include +#include +#include + +EventHandlerOptions::EventHandlerOptions() {} + +EventHandlerOptions::~EventHandlerOptions() {} + +void EventHandlerOptions::ProcessEvent(Rml::Event& event, const Rml::String& value) +{ + // Sent from the 'onload' of the options screen; we set the options on the interface to match those previously set + // this game session. + if (value == "restore") + { + // Fetch the document from the target of the 'onload' event. From here we can fetch the options elements by ID + // to manipulate them directly. + Rml::ElementDocument* options_body = event.GetTargetElement()->GetOwnerDocument(); + if (options_body == nullptr) + return; + + // Get the current graphics setting, and translate that into the ID of the radio button we need to set. + Rml::String graphics_option_id; + switch (GameDetails::GetGraphicsQuality()) + { + case GameDetails::GOOD: graphics_option_id = "good"; break; + case GameDetails::OK: graphics_option_id = "ok"; break; + case GameDetails::BAD: graphics_option_id = "bad"; break; + default: break; + } + + // Fetch the radio button from the document by ID, cast it to a radio button interface and set it as checked. + // This will automatically pop the other radio buttons in the set. Note that we could have not cast and called + // the 'Click()' function instead, but this method will avoid event overhead. + Rml::ElementFormControlInput* graphics_option = + rmlui_dynamic_cast(options_body->GetElementById(graphics_option_id)); + if (graphics_option != nullptr) + graphics_option->SetAttribute("checked", ""); + + // Fetch the reverb option by ID and set its checked status from the game options. + Rml::ElementFormControlInput* reverb_option = rmlui_dynamic_cast(options_body->GetElementById("reverb")); + if (reverb_option != nullptr) + { + if (GameDetails::GetReverb()) + reverb_option->SetAttribute("checked", ""); + else + reverb_option->RemoveAttribute("checked"); + } + + // Similarly, fetch the 3D spatialisation option by ID and set its checked status. + Rml::ElementFormControlInput* spatialisation_option = rmlui_dynamic_cast(options_body->GetElementById("3d")); + if (spatialisation_option != nullptr) + { + if (GameDetails::Get3DSpatialisation()) + spatialisation_option->SetAttribute("checked", ""); + else + spatialisation_option->RemoveAttribute("checked"); + } + + // Disable the accept button when default values are given + Rml::ElementFormControlInput* accept = rmlui_dynamic_cast(options_body->GetElementById("accept")); + if (accept != nullptr) + { + accept->SetDisabled(true); + } + } + + // Sent from the 'onsubmit' action of the options menu; we read the values sent from the form and make the + // necessary changes on the game details structure. + else if (value == "store") + { + // First check which button was clicked to submit the form; we process the changes only if it was 'Accept'. + if (event.GetParameter("submit", "") == "Accept") + { + // Fetch the results of the form submission. These are stored as parameters directly on the event itself. + // Like HTML form events, the name of the parameter is the 'name' attribute of the control, and the value + // is whatever was put into the 'value' attribute. Checkbox values are only sent through if the box was + // clicked. Radio buttons send through one value for the active button. + Rml::String graphics = event.GetParameter("graphics", "ok"); + bool reverb = event.GetParameter("reverb", "") == "true"; + bool spatialisation = event.GetParameter("3d", "") == "true"; + + if (graphics == "good") + GameDetails::SetGraphicsQuality(GameDetails::GOOD); + else if (graphics == "ok") + GameDetails::SetGraphicsQuality(GameDetails::OK); + else if (graphics == "bad") + GameDetails::SetGraphicsQuality(GameDetails::BAD); + + GameDetails::SetReverb(reverb); + GameDetails::Set3DSpatialisation(spatialisation); + } + } + + // This event is sent from the 'onchange' of the bad graphics radio button. It shows or hides the bad graphics + // warning message. + else if (value == "bad_graphics") + { + using namespace Rml; + ElementDocument* options_body = event.GetTargetElement()->GetOwnerDocument(); + if (options_body == nullptr) + return; + + Element* bad_warning = options_body->GetElementById("bad_warning"); + if (bad_warning) + { + // The 'value' parameter of an onchange event is set to the value the control would send if it was + // submitted; so, the empty string if it is clear or to the 'value' attribute of the control if it is set. + if (event.GetParameter("value", "").empty()) + bad_warning->SetProperty(PropertyId::Display, Property(Style::Display::None)); + else + bad_warning->SetProperty(PropertyId::Display, Property(Style::Display::Block)); + } + } + else if (value == "enable_accept") + { + Rml::ElementDocument* options_body = event.GetTargetElement()->GetOwnerDocument(); + if (options_body == nullptr) + return; + + // Enable the accept button when values are changed + Rml::ElementFormControlInput* accept = rmlui_dynamic_cast(options_body->GetElementById("accept")); + if (accept != nullptr) + { + accept->SetDisabled(false); + } + } +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventHandlerOptions.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventHandlerOptions.h new file mode 100644 index 00000000..cb98176a --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventHandlerOptions.h @@ -0,0 +1,11 @@ +#pragma once + +#include "EventHandler.h" + +class EventHandlerOptions : public EventHandler { +public: + EventHandlerOptions(); + virtual ~EventHandlerOptions(); + + void ProcessEvent(Rml::Event& event, const Rml::String& value) override; +}; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventHandlerStartGame.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventHandlerStartGame.cpp new file mode 100644 index 00000000..3cfb8d15 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventHandlerStartGame.cpp @@ -0,0 +1,52 @@ +#include "EventHandlerStartGame.h" +#include "EventManager.h" +#include "GameDetails.h" +#include +#include +#include +#include + +// The game's element context (declared in main.cpp). +extern Rml::Context* context; + +EventHandlerStartGame::EventHandlerStartGame() {} + +EventHandlerStartGame::~EventHandlerStartGame() {} + +void EventHandlerStartGame::ProcessEvent(Rml::Event& event, const Rml::String& value) +{ + if (value == "start") + { + // Set the difficulty. + Rml::String difficulty = event.GetParameter("difficulty", "easy"); + if (difficulty == "hard") + GameDetails::SetDifficulty(GameDetails::HARD); + else + GameDetails::SetDifficulty(GameDetails::EASY); + + // Set the defender colour. + Rml::StringList colour_components; + Rml::StringUtilities::ExpandString(colour_components, event.GetParameter("colour", "255,255,255")); + + Rml::Colourb colour(255, 255, 255); + for (size_t i = 0; i < colour_components.size() && i < 3; ++i) + { + int colour_component = atoi(colour_components[i].c_str()); + if (colour_component < 0) + colour_component = 0; + if (colour_component > 255) + colour_component = 255; + + colour[i] = (Rml::byte)colour_component; + } + + GameDetails::SetDefenderColour(colour); + + // Go to the game window and close the start game window. + if (EventManager::LoadWindow("game")) + { + Rml::ElementUtilities::GetElementById(context->GetFocusElement(), "game")->Focus(); + event.GetTargetElement()->GetOwnerDocument()->Close(); + } + } +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventHandlerStartGame.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventHandlerStartGame.h new file mode 100644 index 00000000..3fa731ef --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventHandlerStartGame.h @@ -0,0 +1,11 @@ +#pragma once + +#include "EventHandler.h" + +class EventHandlerStartGame : public EventHandler { +public: + EventHandlerStartGame(); + virtual ~EventHandlerStartGame(); + + void ProcessEvent(Rml::Event& event, const Rml::String& value) override; +}; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventListener.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventListener.cpp new file mode 100644 index 00000000..dc8704b1 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventListener.cpp @@ -0,0 +1,18 @@ +#include "EventListener.h" +#include "EventManager.h" + +EventListener::EventListener(const Rml::String& value) : value(value) {} + +EventListener::~EventListener() {} + +void EventListener::ProcessEvent(Rml::Event& event) +{ + // Sends the event value through to Invader's event processing system. + EventManager::ProcessEvent(event, value); +} + +void EventListener::OnDetach(Rml::Element* /*element*/) +{ + // Destroys the event listener. + delete this; +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventListener.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventListener.h new file mode 100644 index 00000000..42385a99 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventListener.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +class EventListener : public Rml::EventListener { +public: + EventListener(const Rml::String& value); + virtual ~EventListener(); + + /// Sends the event value through to Invader's event processing system. + void ProcessEvent(Rml::Event& event) override; + + /// Destroys the event. + void OnDetach(Rml::Element* element) override; + +private: + Rml::String value; +}; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventListenerInstancer.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventListenerInstancer.cpp new file mode 100644 index 00000000..010c8c20 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventListenerInstancer.cpp @@ -0,0 +1,12 @@ +#include "EventListenerInstancer.h" +#include "EventListener.h" + +EventListenerInstancer::EventListenerInstancer() {} + +EventListenerInstancer::~EventListenerInstancer() {} + +Rml::EventListener* EventListenerInstancer::InstanceEventListener(const Rml::String& value, Rml::Element* /*element*/) +{ + // Instances a new event handler for Invaders. + return new EventListener(value); +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventListenerInstancer.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventListenerInstancer.h new file mode 100644 index 00000000..dfc6aad2 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventListenerInstancer.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +class EventListenerInstancer : public Rml::EventListenerInstancer { +public: + EventListenerInstancer(); + virtual ~EventListenerInstancer(); + + /// Instances a new event listener for Invaders. + Rml::EventListener* InstanceEventListener(const Rml::String& value, Rml::Element* element) override; +}; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventManager.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventManager.cpp new file mode 100644 index 00000000..0bd73238 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventManager.cpp @@ -0,0 +1,123 @@ +#include "EventManager.h" +#include "EventHandler.h" +#include "GameDetails.h" +#include +#include +#include +#include +#include + +// The game's element context (declared in main.cpp). +extern Rml::Context* context; + +// The event handler for the current screen. This may be nullptr if the current screen has no specific functionality. +static EventHandler* event_handler = nullptr; + +// The event handlers registered with the manager. +using EventHandlerMap = Rml::SmallUnorderedMap>; +EventHandlerMap event_handlers; + +EventManager::EventManager() {} + +EventManager::~EventManager() {} + +void EventManager::Shutdown() +{ + event_handlers.clear(); + event_handler = nullptr; +} + +void EventManager::RegisterEventHandler(const Rml::String& handler_name, Rml::UniquePtr handler) +{ + // Any handler bound under the same name will be released. + event_handlers[handler_name] = std::move(handler); +} + +void EventManager::ProcessEvent(Rml::Event& event, const Rml::String& value) +{ + Rml::StringList commands; + Rml::StringUtilities::ExpandString(commands, value, ';'); + for (size_t i = 0; i < commands.size(); ++i) + { + // Check for custom commands. + Rml::StringList values; + Rml::StringUtilities::ExpandString(values, commands[i], ' '); + + if (values.empty()) + return; + + if (values[0] == "onescape" && values.size() > 1) + { + Rml::Input::KeyIdentifier key_identifier = (Rml::Input::KeyIdentifier)event.GetParameter("key_identifier", 0); + if (key_identifier == Rml::Input::KI_ESCAPE) + values.erase(values.begin()); + } + + if (values[0] == "goto" && values.size() > 1) + { + if (LoadWindow(values[1])) + event.GetTargetElement()->GetOwnerDocument()->Close(); + } + else if (values[0] == "load" && values.size() > 1) + { + LoadWindow(values[1]); + } + else if (values[0] == "close") + { + Rml::ElementDocument* target_document = nullptr; + + if (values.size() > 1) + target_document = context->GetDocument(values[1].c_str()); + else + target_document = event.GetTargetElement()->GetOwnerDocument(); + + if (target_document != nullptr) + target_document->Close(); + } + else if (values[0] == "exit") + { + Backend::RequestExit(); + } + else if (values[0] == "pause") + { + GameDetails::SetPaused(true); + } + else if (values[0] == "unpause") + { + GameDetails::SetPaused(false); + } + else + { + if (event_handler) + event_handler->ProcessEvent(event, commands[i]); + } + } +} + +Rml::ElementDocument* EventManager::LoadWindow(const Rml::String& window_name) +{ + // Set the event handler for the new screen, if one has been registered. + EventHandler* old_event_handler = event_handler; + auto it = event_handlers.find(window_name); + if (it != event_handlers.end()) + event_handler = it->second.get(); + else + event_handler = nullptr; + + // Attempt to load the referenced RML document. + Rml::String document_path = Rml::String("invaders/data/") + window_name + Rml::String(".rml"); + Rml::ElementDocument* document = context->LoadDocument(document_path.c_str()); + if (!document) + { + event_handler = old_event_handler; + return nullptr; + } + + // Set the element's title on the title; IDd 'title' in the RML. + if (Rml::Element* title = document->GetElementById("title")) + title->SetInnerRML(document->GetTitle()); + + document->Show(); + + return document; +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventManager.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventManager.h new file mode 100644 index 00000000..5111c6f2 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/EventManager.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include + +class EventHandler; + +class EventManager { +public: + /// Releases all event handlers registered with the manager. + static void Shutdown(); + + /// Registers a new event handler with the manager. + /// @param[in] handler_name The name of the handler; this must be the same as the window it is handling events for. + /// @param[in] handler The event handler. + static void RegisterEventHandler(const Rml::String& handler_name, Rml::UniquePtr handler); + + /// Processes an event coming through from RmlUi. + /// @param[in] event The RmlUi event that spawned the application event. + /// @param[in] value The application-specific event value. + static void ProcessEvent(Rml::Event& event, const Rml::String& value); + /// Loads a window and binds the event handler for it. + /// @param[in] window_name The name of the window to load. + static Rml::ElementDocument* LoadWindow(const Rml::String& window_name); + +private: + EventManager(); + ~EventManager(); +}; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/Game.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/Game.cpp new file mode 100644 index 00000000..a538904d --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/Game.cpp @@ -0,0 +1,396 @@ +#include "Game.h" +#include "Defender.h" +#include "EventManager.h" +#include "GameDetails.h" +#include "HighScores.h" +#include "Invader.h" +#include "Mothership.h" +#include "Shield.h" +#include +#include + +const int WINDOW_WIDTH = 1024; +const int WINDOW_HEIGHT = 768; + +const int NUM_LIVES = 3; + +const int NUM_INVADER_ROWS = 5; +const int NUM_INVADERS_PER_ROW = 11; +const int NUM_INVADERS = (NUM_INVADER_ROWS * NUM_INVADERS_PER_ROW); +const int INVADER_SPACING_X = 64; +const int INVADER_SPACING_Y = 48; +const int INVADER_START_Y = 96; + +const float INVADER_MOVEMENT = 10; +const float INVADER_UPDATE_MODIFIER = 0.7f; + +const int MOTHERSHIP = NUM_INVADERS; + +const int NUM_SHIELD_ARRAYS = 4; +const int NUM_SHIELDS_PER_ARRAY = 10; +const int NUM_SHIELDS = (NUM_SHIELD_ARRAYS * NUM_SHIELDS_PER_ARRAY); +const int SHIELD_SIZE = (NUM_SHIELD_CELLS * PIXEL_SIZE); +const int SHIELD_SPACING_X = 192; +const int SHIELD_START_X = 176; +const int SHIELD_START_Y = 600; + +// The game's element context (declared in main.cpp). +extern Rml::Context* context; + +Game::Game() +{ + initialized = false; + invader_frame_start = 0; + defender_lives = 3; + current_invader_direction = 1.0f; + invaders = new Invader*[NUM_INVADERS + 1]; + for (int i = 0; i < NUM_INVADERS + 1; i++) + invaders[i] = nullptr; + + shields = new Shield*[NUM_SHIELDS]; + for (int i = 0; i < NUM_SHIELDS; i++) + shields[i] = nullptr; + + defender = new Defender(this); +} + +Game::~Game() +{ + delete defender; + + for (int i = 0; i < NUM_INVADERS + 1; i++) + delete invaders[i]; + + delete[] invaders; + + for (int i = 0; i < NUM_SHIELDS; i++) + delete shields[i]; + + delete[] shields; +} + +void Game::Initialise() +{ + // Initialise the scores and wave information + SetScore(0); + SetWave(1); + SetHighScore(HighScores::GetHighScore()); + SetLives(NUM_LIVES); + + // Initialise the shields. + InitialiseShields(); + + // Create a new wave + InitialiseWave(); + + initialized = true; +} + +void Game::Update(double t) +{ + if (!GameDetails::GetPaused() && initialized) + { + if (defender_lives <= 0) + return; + + // Determine if we should advance the invaders + if (t - invader_frame_start >= invader_move_freq) + { + MoveInvaders(); + + invader_frame_start = t; + } + + // Update all invaders + for (int i = 0; i < NUM_INVADERS + 1; i++) + invaders[i]->Update(t); + + defender->Update(t); + } +} + +void Game::Render(Rml::RenderManager& render_manager, float dp_ratio) +{ + if (defender_lives <= 0) + return; + + if (!texture) + texture = render_manager.LoadTexture("invaders/data/invaders.tga"); + + // Render all available shields + for (int i = 0; i < NUM_SHIELDS; i++) + shields[i]->Render(render_manager, dp_ratio); + + // Render all available invaders + for (int i = 0; i < NUM_INVADERS + 1; i++) + invaders[i]->Render(render_manager, dp_ratio, texture); + + defender->Render(render_manager, dp_ratio, texture); +} + +Defender* Game::GetDefender() +{ + return defender; +} + +Invader* Game::GetInvader(int index) +{ + return invaders[index]; +} + +int Game::GetNumInvaders() +{ + return NUM_INVADERS + 1; +} + +bool Game::CanDropBomb(int index) +{ + // Determines if the given invader can drop a bomb by checking if theres any other invaders under it + int y = (index / NUM_INVADERS_PER_ROW); + int x = index - (y * NUM_INVADERS_PER_ROW); + + // The mothership can never drop bombs + if (index == MOTHERSHIP) + return false; + + // Check each row under the given invader to see if there is space + for (int i = y + 1; i < NUM_INVADER_ROWS; i++) + { + if (invaders[(i * NUM_INVADERS_PER_ROW) + x]->GetState() == Invader::ALIVE) + return false; + } + + return true; +} + +Shield* Game::GetShield(int index) +{ + return shields[index]; +} + +int Game::GetNumShields() +{ + return NUM_SHIELDS; +} + +void Game::AddScore(int score) +{ + SetScore(GameDetails::GetScore() + score); +} + +void Game::SetScore(int score) +{ + GameDetails::SetScore(score); + + Rml::Element* score_element = context->GetDocument("game_window")->GetElementById("score"); + if (score_element != nullptr) + score_element->SetInnerRML(Rml::CreateString("%d", score).c_str()); + + // Update the high score if we've beaten it. + if (score > HighScores::GetHighScore()) + SetHighScore(score); +} + +void Game::SetHighScore(int score) +{ + Rml::Element* high_score_element = context->GetDocument("game_window")->GetElementById("hiscore"); + if (high_score_element != nullptr) + high_score_element->SetInnerRML(Rml::CreateString("%d", score).c_str()); +} + +void Game::SetLives(int lives) +{ + defender_lives = lives; + + Rml::Element* score_element = context->GetDocument("game_window")->GetElementById("lives"); + if (score_element != nullptr) + score_element->SetInnerRML(Rml::CreateString("%d", defender_lives).c_str()); +} + +void Game::SetWave(int wave) +{ + GameDetails::SetWave(wave); + + Rml::Element* waves_element = context->GetDocument("game_window")->GetElementById("waves"); + if (waves_element != nullptr) + waves_element->SetInnerRML(Rml::CreateString("%d", wave).c_str()); +} + +void Game::RemoveLife() +{ + if (defender_lives > 0) + { + SetLives(defender_lives - 1); + + if (defender_lives == 0) + { + OnGameOver(); + } + } +} + +const Rml::Vector2f Game::GetWindowDimensions() +{ + return Rml::Vector2f((float)WINDOW_WIDTH, (float)WINDOW_HEIGHT); +} + +void Game::MoveInvaders() +{ + Rml::Vector2f new_positions[NUM_INVADERS]; + + // We loop through all invaders, calculating their new positions, if any of them go over the screen bounds, + // then we switch direction, move the invaders down and start at 0 again + for (int i = 0; i < NUM_INVADERS; i++) + { + if (invaders[i]->GetState() == Invader::DEAD) + continue; + + new_positions[i] = invaders[i]->GetPosition(); + new_positions[i].x += INVADER_MOVEMENT * current_invader_direction; + if (new_positions[i].x < 0 || new_positions[i].x + INVADER_SPACING_X > WINDOW_WIDTH) + { + // Switch direction and start back at 0 (-1 as the for loop increments) + current_invader_direction *= -1.0f; + i = -1; + + // Move all invaders down + for (int j = 0; j < NUM_INVADERS; j++) + { + if (invaders[j]->GetState() == Invader::DEAD) + continue; + + Rml::Vector2f position = invaders[j]->GetPosition(); + position.y += INVADER_SPACING_Y; + invaders[j]->SetPosition(position); + } + + // Increase speed of invaders + invader_move_freq *= INVADER_UPDATE_MODIFIER; + } + } + + // Assign invaders their new position and advance their animation frame + bool invaders_alive = false; + + for (int i = 0; i < NUM_INVADERS; i++) + { + if (invaders[i]->GetState() == Invader::DEAD) + continue; + + invaders[i]->SetPosition(new_positions[i]); + invaders[i]->UpdateAnimation(); + + // If an invader hits the bottom, instant death + if (new_positions[i].y >= GetWindowDimensions().y - INVADER_SPACING_Y) + { + OnGameOver(); + return; + } + + invaders_alive = true; + } + + if (!invaders_alive) + { + SetWave(GameDetails::GetWave() + 1); + InitialiseWave(); + } +} + +void Game::OnGameOver() +{ + // Set lives to zero and continue to the high scores + defender_lives = 0; + EventManager::LoadWindow("high_score"); +} + +void Game::InitialiseShields() +{ + Rml::Vector2f shield_array_start_position((float)SHIELD_START_X, (float)SHIELD_START_Y); + + for (int x = 0; x < NUM_SHIELD_ARRAYS; x++) + { + // Top row (row of 4) + for (int i = 0; i < 4; i++) + { + Shield::ShieldType type = Shield::REGULAR; + if (i == 0) + type = Shield::TOP_LEFT; + else if (i == 3) + { + type = Shield::TOP_RIGHT; + } + + shields[(x * NUM_SHIELDS_PER_ARRAY) + i] = new Shield(this, type); + shields[(x * NUM_SHIELDS_PER_ARRAY) + i]->SetPosition(shield_array_start_position + Rml::Vector2f((float)SHIELD_SIZE * i, 0)); + } + + // Middle row (row of 4) + for (int i = 0; i < 4; i++) + { + Shield::ShieldType type = Shield::REGULAR; + if (i == 1) + type = Shield::BOTTOM_RIGHT; + else if (i == 2) + { + type = Shield::BOTTOM_LEFT; + } + + shields[(x * NUM_SHIELDS_PER_ARRAY) + 4 + i] = new Shield(this, type); + shields[(x * NUM_SHIELDS_PER_ARRAY) + 4 + i]->SetPosition( + shield_array_start_position + Rml::Vector2f((float)SHIELD_SIZE * i, (float)SHIELD_SIZE)); + } + + // Bottom row (2, one on each end) + shields[(x * NUM_SHIELDS_PER_ARRAY) + 8] = new Shield(this, Shield::REGULAR); + shields[(x * NUM_SHIELDS_PER_ARRAY) + 8]->SetPosition(shield_array_start_position + Rml::Vector2f(0, (float)SHIELD_SIZE * 2)); + + shields[(x * NUM_SHIELDS_PER_ARRAY) + 9] = new Shield(this, Shield::REGULAR); + shields[(x * NUM_SHIELDS_PER_ARRAY) + 9]->SetPosition( + shield_array_start_position + Rml::Vector2f((float)SHIELD_SIZE * 3, (float)SHIELD_SIZE * 2)); + + shield_array_start_position.x += SHIELD_SPACING_X; + } +} + +void Game::InitialiseWave() +{ + // Set up the rows + for (int y = 0; y < NUM_INVADER_ROWS; y++) + { + // Determine invader type + Invader::InvaderType type = Invader::UNKNOWN; + switch (y) + { + case 0: type = Invader::RANK3; break; + case 1: + case 2: type = Invader::RANK2; break; + default: type = Invader::RANK1; break; + } + + // Determine position of top left invader + Rml::Vector2f invader_position((float)(WINDOW_WIDTH - (NUM_INVADERS_PER_ROW * INVADER_SPACING_X)) / 2, + (float)(INVADER_START_Y + (y * INVADER_SPACING_Y))); + + for (int x = 0; x < NUM_INVADERS_PER_ROW; x++) + { + // Determine invader type based on row position + int index = (y * NUM_INVADERS_PER_ROW) + x; + + delete invaders[index]; + invaders[index] = new Invader(this, type, (y * NUM_INVADERS_PER_ROW) + x); + invaders[index]->SetPosition(invader_position); + + // Increase invader position + invader_position.x += INVADER_SPACING_X; + } + } + + // Reset mother ship + delete invaders[MOTHERSHIP]; + invaders[MOTHERSHIP] = new Mothership(this, MOTHERSHIP); + + // Update the move frequency + invader_move_freq = ((((float)GameDetails::GetWave()) - 100.0f) / 140.0f); + invader_move_freq *= invader_move_freq; +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/Game.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/Game.h new file mode 100644 index 00000000..61459158 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/Game.h @@ -0,0 +1,91 @@ +#pragma once + +#include +#include + +class Shield; +class Invader; +class Defender; +class Mothership; + +/** + Runs the game. + - Updates the Invader positions, animations and bombs. + - Updates the player position and bullets + */ + +class Game { +public: + Game(); + ~Game(); + + /// Initialise a new game + void Initialise(); + + /// Update the game + void Update(double t); + + /// Render the game + void Render(Rml::RenderManager& render_manager, float dp_ratio); + + /// Access the defender + Defender* GetDefender(); + /// Access the invaders + Invader* GetInvader(int index); + /// Get the total number of invaders + int GetNumInvaders(); + + /// Returns true if the given invader can drop bomb + bool CanDropBomb(int invader_index); + + /// Access the shields. + Shield* GetShield(int index); + /// Get the total number of shields + int GetNumShields(); + + /// Adds a score onto the player's score. + void AddScore(int score); + /// Sets the player's score. + void SetScore(int score); + /// Sets the player's high-score. + void SetHighScore(int score); + /// Set the number of defender lives. + void SetLives(int lives); + /// Set the current wave + void SetWave(int wave); + /// Remove a defender life + void RemoveLife(); + + /// Get the dimensions of the game window. + const Rml::Vector2f GetWindowDimensions(); + +private: + bool initialized; + // The current invaders + Invader** invaders; + // The direction they're moving + float current_invader_direction; + // Time of the last invader update + double invader_frame_start; + // How often the invaders move + double invader_move_freq; + + // Helper function to move the invaders + void MoveInvaders(); + + // Our current defener + Defender* defender; + + // Number of lives the defender has left + int defender_lives; + + // The current shields + Shield** shields; + + // Texture that contains the sprites + Rml::Texture texture; + + void InitialiseShields(); + void InitialiseWave(); + void OnGameOver(); +}; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/GameDetails.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/GameDetails.cpp new file mode 100644 index 00000000..ef207aab --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/GameDetails.cpp @@ -0,0 +1,100 @@ +#include "GameDetails.h" + +static GameDetails::Difficulty difficulty = GameDetails::EASY; +static Rml::Colourb colour = Rml::Colourb(255, 0, 0); +static int score = -1; +static int wave = 0; +static bool paused = false; + +static GameDetails::GraphicsQuality graphics_quality = GameDetails::OK; +static bool reverb = true; +static bool spatialisation = false; + +GameDetails::GameDetails() {} + +GameDetails::~GameDetails() {} + +void GameDetails::SetDifficulty(Difficulty _difficulty) +{ + difficulty = _difficulty; +} + +GameDetails::Difficulty GameDetails::GetDifficulty() +{ + return difficulty; +} + +void GameDetails::SetDefenderColour(const Rml::Colourb& _colour) +{ + colour = _colour; +} + +const Rml::Colourb& GameDetails::GetDefenderColour() +{ + return colour; +} + +void GameDetails::SetScore(int _score) +{ + score = _score; +} + +void GameDetails::ResetScore() +{ + score = -1; +} + +int GameDetails::GetScore() +{ + return score; +} + +void GameDetails::SetWave(int _wave) +{ + wave = _wave; +} + +int GameDetails::GetWave() +{ + return wave; +} + +void GameDetails::SetPaused(bool _paused) +{ + paused = _paused; +} + +bool GameDetails::GetPaused() +{ + return paused; +} + +void GameDetails::SetGraphicsQuality(GraphicsQuality quality) +{ + graphics_quality = quality; +} + +GameDetails::GraphicsQuality GameDetails::GetGraphicsQuality() +{ + return graphics_quality; +} + +void GameDetails::SetReverb(bool enabled) +{ + reverb = enabled; +} + +bool GameDetails::GetReverb() +{ + return reverb; +} + +void GameDetails::Set3DSpatialisation(bool enabled) +{ + spatialisation = enabled; +} + +bool GameDetails::Get3DSpatialisation() +{ + return spatialisation; +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/GameDetails.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/GameDetails.h new file mode 100644 index 00000000..11feb22f --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/GameDetails.h @@ -0,0 +1,72 @@ +#pragma once + +#include + +class GameDetails { +public: + enum Difficulty { EASY, HARD }; + + /// Sets the game difficulty. + /// @param[in] difficulty The new game difficulty. + static void SetDifficulty(Difficulty difficulty); + /// Returns the game difficulty. + /// @return The current game difficulty. + static Difficulty GetDifficulty(); + + /// Sets the colour of the player's ship. + /// @param[in] colour The new ship colour. + static void SetDefenderColour(const Rml::Colourb& colour); + /// Returns the player's ship colour. + /// @return The colour of the player's ship. + static const Rml::Colourb& GetDefenderColour(); + + /// Sets the score the player achieved in the last game. + /// @param[in] score The player's score. + static void SetScore(int score); + /// Resets the player's score. + static void ResetScore(); + /// Returns the score the player achieved in the last game. + /// @return The player's score. + static int GetScore(); + + /// Sets the current wave number + /// @param[in] wave The wave number + static void SetWave(int wave); + /// Get the current wave number + /// @return The wave number + static int GetWave(); + + /// Sets the pauses state of the game + /// @param[in] paused Whether the game is paused or not + static void SetPaused(bool paused); + /// Gets if the game is paused or not + /// @return True if the game is paused, false otherwise. + static bool GetPaused(); + + enum GraphicsQuality { GOOD, OK, BAD }; + + /// Sets the quality of the graphics used in-game. + /// @param[in] quality The new graphics quality. + static void SetGraphicsQuality(GraphicsQuality quality); + /// Returns the quality of the graphics to use in-game. + /// @return The currently-set graphical quality. + static GraphicsQuality GetGraphicsQuality(); + + /// Enables or disables reverb. + /// @param[in] enabled True to enable reverb, false to disable. + static void SetReverb(bool enabled); + /// Returns the current state of reverb. + /// @return The current reverb state. + static bool GetReverb(); + + /// Enables or disables 3D spatialisation in the audio engine. + /// @param[in] enabled True to enable spatialisation, false to disable. + static void Set3DSpatialisation(bool enabled); + /// Returns the current state of audio spatialisation. + /// @return The current audio spatialisation state. + static bool Get3DSpatialisation(); + +private: + GameDetails(); + ~GameDetails(); +}; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/HighScores.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/HighScores.cpp new file mode 100644 index 00000000..0db59e3d --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/HighScores.cpp @@ -0,0 +1,157 @@ +#include "HighScores.h" +#include +#include +#include +#include +#include + +HighScores* HighScores::instance = nullptr; + +HighScores::HighScores(Rml::Context* context) +{ + RMLUI_ASSERT(instance == nullptr); + instance = this; + + Rml::DataModelConstructor constructor = context->CreateDataModel("high_scores"); + if (!constructor) + return; + + if (auto score_handle = constructor.RegisterStruct()) + { + score_handle.RegisterMember("name_required", &Score::name_required); + score_handle.RegisterMember("name", &Score::name); + score_handle.RegisterMember("colour", &Score::GetColour); + score_handle.RegisterMember("wave", &Score::wave); + score_handle.RegisterMember("score", &Score::score); + } + + constructor.RegisterArray(); + + constructor.Bind("scores", &scores); + + model_handle = constructor.GetModelHandle(); + + LoadScores(); +} + +HighScores::~HighScores() +{ + RMLUI_ASSERT(instance == this); + + SaveScores(); + + instance = nullptr; +} + +void HighScores::Initialise(Rml::Context* context) +{ + new HighScores(context); +} + +void HighScores::Shutdown() +{ + delete instance; +} + +int HighScores::GetHighScore() +{ + if (instance->scores.empty()) + return 0; + + return instance->scores[0].score; +} + +void HighScores::SubmitScore(const Rml::String& name, const Rml::Colourb& colour, int wave, int score) +{ + instance->SubmitScore(name, colour, wave, score, false); +} + +void HighScores::SubmitScore(const Rml::Colourb& colour, int wave, int score) +{ + instance->SubmitScore("", colour, wave, score, true); +} + +void HighScores::SubmitName(const Rml::String& name) +{ + for (Score& score : instance->scores) + { + if (score.name_required) + { + score.name = name; + score.name_required = false; + + instance->model_handle.DirtyVariable("scores"); + } + } +} + +void HighScores::SubmitScore(const Rml::String& name, const Rml::Colourb& colour, int wave, int score, bool name_required) +{ + Score entry; + entry.name = name; + entry.colour = colour; + entry.wave = wave; + entry.score = score; + entry.name_required = name_required; + + auto it = std::find_if(scores.begin(), scores.end(), [score](const Score& other) { return score > other.score; }); + + scores.insert(it, std::move(entry)); + + constexpr size_t MaxNumberScores = 10; + + if (scores.size() > MaxNumberScores) + scores.pop_back(); + + model_handle.DirtyVariable("scores"); +} + +void HighScores::LoadScores() +{ + // Open and read the high score file. + FILE* scores_file = fopen("scores.txt", "rt"); + + if (scores_file) + { + char buffer[1024]; + while (fgets(buffer, 1024, scores_file)) + { + Rml::StringList score_parts; + Rml::StringUtilities::ExpandString(score_parts, Rml::String(buffer), '\t'); + if (score_parts.size() == 4) + { + Rml::Colourb colour; + int wave; + int score; + + if (Rml::TypeConverter::Convert(score_parts[1], colour) && + Rml::TypeConverter::Convert(score_parts[2], wave) && + Rml::TypeConverter::Convert(score_parts[3], score)) + { + SubmitScore(score_parts[0], colour, wave, score); + } + } + } + + fclose(scores_file); + } +} + +void HighScores::SaveScores() +{ + FILE* scores_file = fopen("scores.txt", "wt"); + + if (scores_file) + { + for (const Score& score : scores) + { + Rml::String colour_string; + Rml::TypeConverter::Convert(score.colour, colour_string); + + Rml::String score_str = Rml::CreateString("%s\t%s\t%d\t%d\n", score.name.c_str(), colour_string.c_str(), score.wave, score.score); + fputs(score_str.c_str(), scores_file); + } + + fclose(scores_file); + } +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/HighScores.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/HighScores.h new file mode 100644 index 00000000..f81be0fd --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/HighScores.h @@ -0,0 +1,44 @@ +#pragma once + +#include +#include + +class HighScores { +public: + static void Initialise(Rml::Context* context); + static void Shutdown(); + + static int GetHighScore(); + + /// Two functions to add a score to the chart. + /// Adds a full score, including a name. This won't prompt the user to enter their name. + static void SubmitScore(const Rml::String& name, const Rml::Colourb& colour, int wave, int score); + /// Adds a score, and causes an input field to appear to request the user for their name. + static void SubmitScore(const Rml::Colourb& colour, int wave, int score); + /// Sets the name of the last player to submit their score. + static void SubmitName(const Rml::String& name); + +private: + HighScores(Rml::Context* context); + ~HighScores(); + + static HighScores* instance; + + void SubmitScore(const Rml::String& name, const Rml::Colourb& colour, int wave, int score, bool name_required); + void LoadScores(); + void SaveScores(); + + struct Score { + Rml::String name; + bool name_required; + Rml::Colourb colour; + int score; + int wave; + + Rml::String GetColour() { return Rml::ToString(colour); } + }; + using ScoreList = Rml::Vector; + ScoreList scores; + + Rml::DataModelHandle model_handle; +}; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/Invader.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/Invader.cpp new file mode 100644 index 00000000..72f4b667 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/Invader.cpp @@ -0,0 +1,222 @@ +#include "Invader.h" +#include "Defender.h" +#include "Game.h" +#include "GameDetails.h" +#include "Shield.h" +#include "Sprite.h" +#include +#include + +const float BOMB_UPDATE_FREQ = 0.04f; +const float BOMB_RAY_SPEED = 10; +const float BOMB_MISSILE_SPEED = 7; +const float BOMB_PROBABILITY_EASY = 0.002f; +const float BOMB_PROBABILITY_HARD = 0.005f; +const float EXPLOSION_TIME = 0.25f; +const Rml::Colourb MOTHERSHIP_COLOUR = Rml::Colourb(255, 0, 0, 255); + +Sprite invader_sprites[] = { + // Rank 1 + Sprite(Rml::Vector2f(48, 32), Rml::Vector2f(0.609375f, 0), Rml::Vector2f(0.796875f, 0.5f)), + Sprite(Rml::Vector2f(48, 32), Rml::Vector2f(0.80078125f, 0), Rml::Vector2f(0.98828125f, 0.5f)), + // Rank 2 + Sprite(Rml::Vector2f(44, 32), Rml::Vector2f(0.2578125f, 0), Rml::Vector2f(0.4296875f, 0.5f)), + Sprite(Rml::Vector2f(44, 32), Rml::Vector2f(0.43359375f, 0), Rml::Vector2f(0.60546875f, 0.5f)), + // Rank 3 + Sprite(Rml::Vector2f(32, 32), Rml::Vector2f(0, 0), Rml::Vector2f(0.125f, 0.5f)), + Sprite(Rml::Vector2f(32, 32), Rml::Vector2f(0.12890625f, 0), Rml::Vector2f(0.25390625f, 0.5f)), + // Mothership + Sprite(Rml::Vector2f(64, 28), Rml::Vector2f(0.23828125f, 0.515625f), Rml::Vector2f(0.48828125f, 0.953125f)), + // Explosion + Sprite(Rml::Vector2f(52, 28), Rml::Vector2f(0.71484375f, 0.51562500f), Rml::Vector2f(0.91796875f, 0.95312500f))}; + +Sprite bomb_sprites[] = { + // Ray + Sprite(Rml::Vector2f(12, 20), Rml::Vector2f(0.51171875f, 0.51562500f), Rml::Vector2f(0.55859375f, 0.82812500f)), + Sprite(Rml::Vector2f(12, 20), Rml::Vector2f(0.56250000, 0.51562500), Rml::Vector2f(0.60937500, 0.82812500)), + Sprite(Rml::Vector2f(12, 20), Rml::Vector2f(0.61328125, 0.51562500), Rml::Vector2f(0.66015625, 0.82812500)), + Sprite(Rml::Vector2f(12, 20), Rml::Vector2f(0.66406250, 0.51562500), Rml::Vector2f(0.71093750, 0.82812500)), + // Missile + Sprite(Rml::Vector2f(12, 20), Rml::Vector2f(0.92578125, 0.51562500), Rml::Vector2f(0.97265625, 0.82812500))}; + +Invader::Invader(Game* _game, InvaderType _type, int _index) : position(0, 0) +{ + type = UNKNOWN; + animation_frame = 0; + bomb_animation_frame = 0; + bomb_frame_start = 0; + death_time = 0; + state = ALIVE; + bomb = NONE; + game = _game; + type = _type; + death_time = 0; + invader_index = _index; + + bomb_probability = GameDetails::GetDifficulty() == GameDetails::EASY ? BOMB_PROBABILITY_EASY : BOMB_PROBABILITY_HARD; + bomb_probability *= float(type); +} + +Invader::~Invader() {} + +void Invader::SetPosition(const Rml::Vector2f& _position) +{ + position = _position; +} + +const Rml::Vector2f& Invader::GetPosition() const +{ + return position; +} + +void Invader::Update(double t) +{ + // Update the bombs + if (float(t - bomb_frame_start) > BOMB_UPDATE_FREQ) + { + // Update the bomb position if its in flight, or check if we should drop one + if (bomb != NONE) + { + if (bomb == RAY) + { + bomb_animation_frame++; + if (bomb_animation_frame > 3) + bomb_animation_frame = 0; + + bomb_position.y += BOMB_RAY_SPEED; + } + else + { + bomb_position.y += BOMB_MISSILE_SPEED; + } + + if (bomb_position.y > game->GetWindowDimensions().y) + bomb = NONE; + + // Check if we hit the shields + for (int i = 0; i < game->GetNumShields(); i++) + { + if (game->GetShield(i)->CheckHit(bomb_position)) + { + bomb = NONE; + break; + } + } + + // Check if we hit the defender + if (bomb != NONE) + { + if (game->GetDefender()->CheckHit(t, bomb_position)) + bomb = NONE; + } + } + else if (state == ALIVE && Rml::Math::RandomReal(1.0f) < bomb_probability && game->CanDropBomb(invader_index)) + { + bomb = Rml::Math::RandomInteger(2) == 0 ? RAY : MISSILE; + bomb_position = position; + bomb_position.x += invader_sprites[GetSpriteIndex()].dimensions.x / 2; + + if (bomb == RAY) + bomb_animation_frame = 0; + else + bomb_animation_frame = 4; + } + + bomb_frame_start = t; + } + + if (state == EXPLODING && t - death_time > 0.0) + state = DEAD; +} + +void Invader::UpdateAnimation() +{ + switch (state) + { + case ALIVE: + animation_frame++; + if (animation_frame > 1) + animation_frame = 0; + break; + + default: break; + } +} + +void Invader::Render(Rml::RenderManager& render_manager, float dp_ratio, Rml::Texture texture) +{ + Rml::ColourbPremultiplied color(255); + + if (type == MOTHERSHIP) + color = MOTHERSHIP_COLOUR.ToPremultiplied(); + + int sprite_index = GetSpriteIndex(); + int sprite_offset = int((invader_sprites[sprite_index].dimensions.x - 48) / 2); + + if (state != DEAD) + invader_sprites[sprite_index].Render(render_manager, Rml::Vector2f(position.x - sprite_offset, position.y), dp_ratio, color, texture); + + if (bomb != NONE) + bomb_sprites[bomb_animation_frame].Render(render_manager, bomb_position, dp_ratio, color, texture); +} + +Invader::InvaderState Invader::GetState() +{ + return state; +} + +bool Invader::CheckHit(double t, const Rml::Vector2f& check_position) +{ + // Get the sprite index we're currently using for collision detection + int sprite_index = GetSpriteIndex(); + int sprite_offset = int((invader_sprites[sprite_index].dimensions.x - 48) / 2); + float sprite_width = invader_sprites[sprite_index].dimensions.x; + float sprite_height = invader_sprites[sprite_index].dimensions.y; + + // If we're alive and the position is within our bounds, set ourselves + // as exploding and return a valid hit + if (state == ALIVE && check_position.x >= position.x - sprite_offset && check_position.x <= position.x - sprite_offset + sprite_width && + check_position.y >= position.y && check_position.y <= position.y + sprite_height) + { + int score = 0; + switch (type) + { + case MOTHERSHIP: score = (Rml::Math::RandomInteger(6) + 1) * 50; break; // 50 -> 300 + case RANK3: score = 40; break; + case RANK2: score = 20; break; + case RANK1: score = 10; break; + case UNKNOWN: break; + } + + // Add the number of points + game->AddScore(score); + + // Set our state to exploding and start the timer to our doom + state = EXPLODING; + death_time = t + EXPLOSION_TIME; + + return true; + } + + return false; +} + +int Invader::GetSpriteIndex() const +{ + // Calculate our sprite index based on animation and type + int index = animation_frame; + switch (type) + { + case RANK1: break; // animation_frame is the right index already + case RANK2: index += 2; break; + case RANK3: index += 4; break; + case MOTHERSHIP: index = 6; break; + case UNKNOWN: break; + } + + // If we're in exploding state, use the exploding sprite + if (state == EXPLODING) + index = 7; + + return index; +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/Invader.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/Invader.h new file mode 100644 index 00000000..d9bce256 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/Invader.h @@ -0,0 +1,81 @@ +#pragma once + +#include + +class Game; + +/** + An alien invader. + */ + +class Invader { +public: + enum InvaderType { UNKNOWN, RANK1, RANK2, RANK3, MOTHERSHIP }; + enum BombType { NONE, RAY, MISSILE }; + + /// Construct the invader + Invader(Game* game, InvaderType type, int index); + virtual ~Invader(); + + /// Set the invaders screen position + /// @param position Position in screen space + void SetPosition(const Rml::Vector2f& position); + /// Get the current invader position + /// @returns The invaders position in screen space + const Rml::Vector2f& GetPosition() const; + + /// Update the invader + virtual void Update(double t); + + /// Render the invader + void Render(Rml::RenderManager& render_manager, float dp_ratio, Rml::Texture texture); + + /// Update the invaders animation + void UpdateAnimation(); + + /// The current invaders state + enum InvaderState { ALIVE, EXPLODING, DEAD }; + /// Get the current invader state + /// @returns Invader state + InvaderState GetState(); + + /// Returns true if the position hits the invader + /// If a hit is detected, will explode and start the death timer + /// @param position Position to do the hit check at + /// @returns If the invader was hit + bool CheckHit(double t, const Rml::Vector2f& position); + +protected: + // Game this invader is in + Game* game; + // The index/id of this invader + int invader_index; + + // The invader type we represent + InvaderType type; + // The current position in screen space of the invader + Rml::Vector2f position; + // Our current animation frame + int animation_frame; + + // Our current state + InvaderState state; + + // Our current in-flight bomb, or none. (may be not none if we're dead.) + BombType bomb; + // The current position of the bomb in screen space + Rml::Vector2f bomb_position; + // The animation frame the bomb is on + int bomb_animation_frame; + // When the last bomb update occured + double bomb_frame_start; + // Probability of us dropping a bomb - this is calculated + // at construction time and based on our rank and the game + // difficulty level + float bomb_probability; + + // Time when we should die - 0, until we're hit + double death_time; + + int GetSpriteIndex() const; +}; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/Mothership.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/Mothership.cpp new file mode 100644 index 00000000..249ce71e --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/Mothership.cpp @@ -0,0 +1,54 @@ +#include "Mothership.h" +#include "Game.h" +#include "Shell.h" +#include "Sprite.h" +#include + +const int SPRITE_WIDTH = 64; + +const float APPEARANCE_PROBABILITY = 0.001f; +const double UPDATE_FREQ = 0.025; +const float MOVEMENT_SPEED = 5; + +Mothership::Mothership(Game* game, int index) : Invader(game, Invader::MOTHERSHIP, index) +{ + // Start off dead, and set up our position + state = DEAD; + update_frame_start = 0; + direction = 0; + position = Rml::Vector2f(-SPRITE_WIDTH, 64.0f); +} + +Mothership::~Mothership() {} + +void Mothership::Update(double t) +{ + // Generic Invader update + Invader::Update(t); + + if (t - update_frame_start < UPDATE_FREQ) + return; + + // We're alive, keep moving! + if (state == ALIVE) + { + position.x += (direction * MOVEMENT_SPEED); + + if ((direction < 0.0f && position.x < -SPRITE_WIDTH) || (direction > 0.0f && position.x > game->GetWindowDimensions().x)) + state = DEAD; + + update_frame_start = t; + } + // Determine if we should come out of hiding + else if (Rml::Math::RandomReal(1.0f) < APPEARANCE_PROBABILITY) + { + direction = Rml::Math::RandomReal(1.0f) < 0.5 ? -1.0f : 1.0f; + + if (direction < 0) + position.x = game->GetWindowDimensions().x + SPRITE_WIDTH; + else + position.x = -SPRITE_WIDTH; + + state = ALIVE; + } +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/Mothership.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/Mothership.h new file mode 100644 index 00000000..d5d0908e --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/Mothership.h @@ -0,0 +1,19 @@ +#pragma once + +#include "Invader.h" + +class Mothership : public Invader { +public: + Mothership(Game* game, int index); + ~Mothership(); + + /// Update the mothership + void Update(double t) override; + +private: + // Time of the last update + double update_frame_start; + + // Direction mothership is flying in + float direction; +}; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/Shield.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/Shield.cpp new file mode 100644 index 00000000..9da5f71d --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/Shield.cpp @@ -0,0 +1,154 @@ +#include "Shield.h" +#include "Game.h" +#include "GameDetails.h" +#include "Sprite.h" +#include + +const int MAX_HEALTH = 4; + +Shield::Shield(Game* _game, ShieldType _type) : position(0, 0) +{ + game = _game; + type = _type; + health = MAX_HEALTH; + + InitialiseCells(); +} + +Shield::~Shield() {} + +void Shield::InitialiseCells() +{ + if (type == REGULAR || type == TOP_LEFT || type == TOP_RIGHT) + { + for (int i = 0; i < NUM_SHIELD_CELLS; i++) + { + for (int j = 0; j < NUM_SHIELD_CELLS; j++) + { + shield_cells[i][j] = ON; + } + } + + // Take the bites out of the cells if they're a corner cell: + if (type == TOP_LEFT) + { + for (int x = 0; x < NUM_SHIELD_CELLS - 1; x++) + { + for (int y = 0; y < (NUM_SHIELD_CELLS - 1) - x; y++) + shield_cells[x][y] = OFF; + } + } + + else if (type == TOP_RIGHT) + { + for (int x = 0; x < NUM_SHIELD_CELLS - 1; x++) + { + for (int y = 0; y < (NUM_SHIELD_CELLS - 1) - x; y++) + shield_cells[(NUM_SHIELD_CELLS - 1) - x][y] = OFF; + } + } + } + else + { + for (int i = 0; i < NUM_SHIELD_CELLS; i++) + { + for (int j = 0; j < NUM_SHIELD_CELLS; j++) + { + shield_cells[i][j] = OFF; + } + } + + if (type == BOTTOM_LEFT) + { + for (int x = 0; x < NUM_SHIELD_CELLS - 1; x++) + { + for (int y = 0; y < (NUM_SHIELD_CELLS - 1) - x; y++) + shield_cells[(NUM_SHIELD_CELLS - 1) - x][y] = ON; + } + } + + else if (type == BOTTOM_RIGHT) + { + for (int x = 0; x < NUM_SHIELD_CELLS - 1; x++) + { + for (int y = 0; y < (NUM_SHIELD_CELLS - 1) - x; y++) + shield_cells[x][y] = ON; + } + } + } +} + +void Shield::SetPosition(const Rml::Vector2f& _position) +{ + position = _position; +} + +const Rml::Vector2f& Shield::GetPosition() const +{ + return position; +} + +void Shield::Render(Rml::RenderManager& render_manager, float dp_ratio) +{ + if (health > 0) + { + const Rml::Vector2f scaled_position = (dp_ratio * position).Round(); + const int scaled_pixel = Rml::Math::RoundUpToInteger(PIXEL_SIZE * dp_ratio); + + Rml::ColourbPremultiplied color = GameDetails::GetDefenderColour().ToPremultiplied(); + ColoredPointList points; + points.reserve(NUM_SHIELD_CELLS * NUM_SHIELD_CELLS); + + for (int i = 0; i < NUM_SHIELD_CELLS; i++) + { + for (int j = 0; j < NUM_SHIELD_CELLS; j++) + { + if (shield_cells[i][j] == ON) + { + Rml::Vector2f cell_position = scaled_position + Rml::Vector2f(float(scaled_pixel * i), float(scaled_pixel * j)); + points.push_back(ColoredPoint{color, cell_position}); + } + } + } + + DrawPoints(render_manager, (float)scaled_pixel, points); + } +} + +bool Shield::CheckHit(const Rml::Vector2f& check_position) +{ + float sprite_size = PIXEL_SIZE * NUM_SHIELD_CELLS; + + // If we're alive and the position is within our bounds, set ourselves + // as exploding and return a valid hit + if (health > 0 && check_position.x >= position.x && check_position.x <= position.x + sprite_size && check_position.y >= position.y && + check_position.y <= position.y + sprite_size) + { + // Take damage. + SustainDamage(); + + return true; + } + + return false; +} + +void Shield::SustainDamage() +{ + health--; + + if (health > 0) + { + int num_shields_to_lose = (NUM_SHIELD_CELLS * NUM_SHIELD_CELLS) / MAX_HEALTH; + while (num_shields_to_lose > 0) + { + int x = Rml::Math::RandomInteger(NUM_SHIELD_CELLS); + int y = Rml::Math::RandomInteger(NUM_SHIELD_CELLS); + if (shield_cells[x][y] != DESTROYED) + { + shield_cells[x][y] = DESTROYED; + num_shields_to_lose--; + } + } + } +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/Shield.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/Shield.h new file mode 100644 index 00000000..68618393 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/Shield.h @@ -0,0 +1,57 @@ +#pragma once + +#include + +class Game; + +const int NUM_SHIELD_CELLS = 6; +const int PIXEL_SIZE = 4; + +/** + A shield, protecting the player. + */ + +class Shield { +public: + enum ShieldType { REGULAR, TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT }; + + /// Construct the invader + Shield(Game* game, ShieldType type); + ~Shield(); + + /// Sets up which cells are on and off, based on the type. + void InitialiseCells(); + + /// Set the shield's screen position + /// @param position Position in screen space + void SetPosition(const Rml::Vector2f& position); + /// Get the current shield position + /// @returns The shield's position in screen space + const Rml::Vector2f& GetPosition() const; + + /// Render the shield. + void Render(Rml::RenderManager& render_manager, float dp_ratio); + + /// Returns true if the position hits the shield + /// If a hit is detected, will degrade the shield. + /// @param position Position to do the hit check at + /// @returns If the shield was hit + bool CheckHit(const Rml::Vector2f& position); + +protected: + void SustainDamage(); + + // Game this shield is in + Game* game; + + // The invader type we represent + ShieldType type; + // The current position in screen space of the shield + Rml::Vector2f position; + + // Our current state - starts at 4, degrades once per hit. + int health; + + enum ShieldCellState { ON, OFF, DESTROYED }; + ShieldCellState shield_cells[NUM_SHIELD_CELLS][NUM_SHIELD_CELLS]; +}; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/Sprite.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/Sprite.cpp new file mode 100644 index 00000000..e184be26 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/Sprite.cpp @@ -0,0 +1,48 @@ +#include "Sprite.h" +#include +#include +#include +#include +#include + +Sprite::Sprite(const Rml::Vector2f& dimensions, const Rml::Vector2f& top_left_texcoord, const Rml::Vector2f& bottom_right_texcoord) : + dimensions(dimensions), top_left_texcoord(top_left_texcoord), bottom_right_texcoord(bottom_right_texcoord) +{} + +Sprite::~Sprite() {} + +void Sprite::Render(Rml::RenderManager& render_manager, Rml::Vector2f position, const float dp_ratio, Rml::ColourbPremultiplied color, + Rml::Texture texture) +{ + position = dp_ratio * position; + Rml::Vector2f dimensions_px = dp_ratio * dimensions; + Rml::Math::SnapToPixelGrid(position, dimensions_px); + + Rml::Mesh mesh; + Rml::MeshUtilities::GenerateQuad(mesh, Rml::Vector2f(0.f), dimensions_px, color, top_left_texcoord, bottom_right_texcoord); + + Rml::Geometry geometry = render_manager.MakeGeometry(std::move(mesh)); + geometry.Render(position, texture); +} + +void DrawPoints(Rml::RenderManager& render_manager, float point_size, const ColoredPointList& points) +{ + constexpr int num_quad_vertices = 4; + constexpr int num_quad_indices = 6; + + const int num_points = (int)points.size(); + + Rml::Mesh mesh; + mesh.vertices.reserve(num_points * num_quad_vertices); + mesh.indices.reserve(num_points * num_quad_indices); + + for (const ColoredPoint& point : points) + { + Rml::Vector2f position = point.position; + Rml::Vector2f size = Rml::Vector2f(point_size); + Rml::MeshUtilities::GenerateQuad(mesh, position, size, point.color); + } + + Rml::Geometry geometry = render_manager.MakeGeometry(std::move(mesh)); + geometry.Render(Rml::Vector2f(0.f)); +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/Sprite.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/Sprite.h new file mode 100644 index 00000000..778cf9d3 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/Sprite.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +class Sprite { +public: + Sprite(const Rml::Vector2f& dimensions, const Rml::Vector2f& top_left_texcoord, const Rml::Vector2f& bottom_right_texcoord); + ~Sprite(); + + void Render(Rml::RenderManager& render_manager, Rml::Vector2f position, float dp_ratio, Rml::ColourbPremultiplied color, Rml::Texture texture); + + Rml::Vector2f dimensions; + Rml::Vector2f top_left_texcoord; + Rml::Vector2f bottom_right_texcoord; +}; + +struct ColoredPoint { + Rml::ColourbPremultiplied color; + Rml::Vector2f position; +}; +using ColoredPointList = Rml::Vector; + +void DrawPoints(Rml::RenderManager& render_manager, float point_size, const ColoredPointList& points); diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/main.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/main.cpp new file mode 100644 index 00000000..281a8f45 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/invaders/src/main.cpp @@ -0,0 +1,108 @@ +#include "DecoratorDefender.h" +#include "DecoratorStarfield.h" +#include "ElementGame.h" +#include "EventHandlerHighScore.h" +#include "EventHandlerOptions.h" +#include "EventHandlerStartGame.h" +#include "EventListenerInstancer.h" +#include "EventManager.h" +#include "HighScores.h" +#include +#include +#include +#include + +Rml::Context* context = nullptr; + +#if defined RMLUI_PLATFORM_WIN32 + #include +int APIENTRY WinMain(HINSTANCE /*instance_handle*/, HINSTANCE /*previous_instance_handle*/, char* /*command_line*/, int /*command_show*/) +#else +int main(int /*argc*/, char** /*argv*/) +#endif +{ + const int window_width = 1024; + const int window_height = 768; + + // Initializes the shell which provides common functionality used by the included samples. + if (!Shell::Initialize()) + return -1; + + // Constructs the system and render interfaces, creates a window, and attaches the renderer. + if (!Backend::Initialize("RmlUi Invaders from Mars", window_width, window_height, false)) + { + Shell::Shutdown(); + return -1; + } + + // Install the custom interfaces constructed by the backend before initializing RmlUi. + Rml::SetSystemInterface(Backend::GetSystemInterface()); + Rml::SetRenderInterface(Backend::GetRenderInterface()); + + // RmlUi initialisation. + Rml::Initialise(); + + // Create the main RmlUi context. + context = Rml::CreateContext("main", Rml::Vector2i(window_width, window_height)); + if (!context) + { + Rml::Shutdown(); + Backend::Shutdown(); + Shell::Shutdown(); + return -1; + } + + // Initialise the RmlUi debugger. + Rml::Debugger::Initialise(context); + + // Load the font faces required for Invaders. + Shell::LoadFonts(); + + // Register Invader's custom element and decorator instancers. + Rml::ElementInstancerGeneric element_instancer_game; + Rml::Factory::RegisterElementInstancer("game", &element_instancer_game); + + DecoratorInstancerStarfield decorator_instancer_starfield; + DecoratorInstancerDefender decorator_instancer_defender; + Rml::Factory::RegisterDecoratorInstancer("starfield", &decorator_instancer_starfield); + Rml::Factory::RegisterDecoratorInstancer("defender", &decorator_instancer_defender); + + // Construct the game singletons. + HighScores::Initialise(context); + + // Initialise the event listener instancer and handlers. + EventListenerInstancer event_listener_instancer; + Rml::Factory::RegisterEventListenerInstancer(&event_listener_instancer); + + EventManager::RegisterEventHandler("start_game", Rml::MakeUnique()); + EventManager::RegisterEventHandler("high_score", Rml::MakeUnique()); + EventManager::RegisterEventHandler("options", Rml::MakeUnique()); + + // Start the game. + bool running = (EventManager::LoadWindow("background") && EventManager::LoadWindow("main_menu")); + + while (running) + { + running = Backend::ProcessEvents(context, &Shell::ProcessKeyDownShortcuts); + + context->Update(); + + Backend::BeginFrame(); + context->Render(); + Backend::PresentFrame(); + } + + // Shut down the game singletons. + HighScores::Shutdown(); + + // Release the event handlers. + EventManager::Shutdown(); + + // Shutdown RmlUi. + Rml::Shutdown(); + + Shell::Shutdown(); + Backend::Shutdown(); + + return 0; +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/CMakeLists.txt b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/CMakeLists.txt new file mode 100644 index 00000000..bb79c621 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/CMakeLists.txt @@ -0,0 +1,42 @@ +set(SAMPLE_NAME "lua_invaders") +set(TARGET_NAME "${RMLUI_SAMPLE_PREFIX}${SAMPLE_NAME}") + +add_executable(${TARGET_NAME} WIN32 + src/DecoratorDefender.cpp + src/DecoratorDefender.h + src/DecoratorStarfield.cpp + src/DecoratorStarfield.h + src/Defender.cpp + src/Defender.h + src/ElementGame.cpp + src/ElementGame.h + src/ElementGameInstancer.cpp + src/ElementGameInstancer.h + src/Game.cpp + src/Game.h + src/GameDetails.cpp + src/GameDetails.h + src/HighScores.cpp + src/HighScores.h + src/Invader.cpp + src/Invader.h + src/LuaInterface.cpp + src/LuaInterface.h + src/main.cpp + src/Mothership.cpp + src/Mothership.h + src/Shield.cpp + src/Shield.h + src/Sprite.cpp + src/Sprite.h +) + +set_common_target_options(${TARGET_NAME}) + +target_link_libraries(${TARGET_NAME} PRIVATE + rmlui_shell + rmlui_lua + RmlUi::External::Lua +) + +install_sample_target(${TARGET_NAME}) diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/background.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/background.rml new file mode 100644 index 00000000..0ed7aaf0 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/background.rml @@ -0,0 +1,41 @@ + + + + + + + + + diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/background.tga b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/background.tga new file mode 100644 index 0000000000000000000000000000000000000000..faec89c90e8620c7edcd8fa1262d1be6a0dfbdfd GIT binary patch literal 4194322 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
    q)?@)$qah`wA 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 z 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 +
    + + high score: +
    +
    + + lives: +
    +
    + + wave: +
    + + + diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/help.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/help.rml new file mode 100644 index 00000000..fa3f871b --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/help.rml @@ -0,0 +1,54 @@ + + + Help + + + + +

    Story

    +

    + One day, without warning, they came for us; endless waves of the invaders, numbers too vast to count, fresh from the Martian foundries. Earth's orbital defences took a heavy toll, but were inevitably overrun, buying enough time only for a single rmlui ship to launch. The prototype X-42 'Defender'-class, not yet tested but piloted by the finest astronaut the Space Corps had to offer. +

    +

    + Do you have what it takes to defeat the invaders and save Earth? +

    +

    The X-42

    +

    + The culmination of decades of research and the pinnacle of Earth's technology, the X-42 'Defender' is the finest weapon mankind has to fight the alien menace. Equipped with the dual-layer ablative tridranium armour, low-velocity depleted-narconium chain-fed belt gun and state-of-the-art liquid-hydrangea turbo-thrusters, it is still going to be nearly impossible for its pilot to secure the safety of mankind. +

    +

    The Invaders

    +

    + The most numerous invader you'll face is a primitive drone that is primarily employed by its Martian overlords in the mines of Cydonia. It is large, ungainly and poorly-armed, so ill-suited to its new role as the backbone of the invasion fleet. However, whatever it lacks in pugilistic prowess it makes up for with numbers. Each drone is worth 10 points. +

    +

    + Behind the drones stand the formidable berserker units, encased in their zyterium battle-suits and wielding the dreaded Nimbus death-rods. A lone unit is no match for a skilled X-42 pilot, but in large groups they adopt complex flight patterns that can confuse even the best targetting computer. Pick them off and separate them when you can. Each berserker is worth 20 points. +

    +

    + The dreaded commander death-droids lie at the back of every Martian wave. Small, nimble and deadly, these ships are the true test of an X-42 pilot's abilities. Consider yourself warned, and watch out especially for their mind control attack! Each droid is worth 40 points. +

    +

    + Occasionally one of the invasion motherships will drop into low-orbit to provide the Martian Overbrain with a close look at the progress of the battle; this is when they are most vulnerable, destroy them if you can! The loss of even a single mothership will be keenly felt by the fleet. The points value of each mothership is variable, depending on the seniority of its command synapse. +

    +

    + Destroy all the invaders in each wave before any get past your ship to invade Earth, unleash their deadly toxin and wipe out all of humanity! +

    + + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/help_defender.tga b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/help_defender.tga new file mode 100644 index 0000000000000000000000000000000000000000..e10f9463b76a37c9387878a77659913703eebc79 GIT binary patch literal 32786 zcmeI3y>1gh6onTkjD#qVK%qpbs1Ow`TR~6DJETcV5z!*V6YvUofKcq4Ih=1ZBkhiz zm|cfRGxuEU%3j;Ep7Y(EpS?5gy2o9%i>~k9Jk3XbHUSeb0TVC*6EFc2FaZ-V0TVC* z6EFc2FaZ-V0TVC*6EJ~$An-`%cp~4(pUYp!U&&A8uN!r=p%JZUMmr2(5zWy3Ob(N` z@(=P}{z?A1QAb-ZM=P4q4g*+3Gp>E~zn6cJpUJ<=f69L~>S#kFTG5Pl7{DT$(f`o~ z;P69!B_Dz~TG5Pl7{DT$F$U5P(iX0+7ut5ay~b-bG$-vafJHQ89@NX}4~JUgxVGMC zPTG4t!y=k7?lB)?OpF?Tw8H=v(TsaM=K0L^qQ)QXFn~ogbNp|veaGMJ-|c_xM5@wq z>g<{Sx&Qb0@BZKYzx)46`|-8acK<)#^Z5SH_kX_s_x=B{wjZsvocsSV=jr@!-2AWC ze{R;kdi4bn*ME*$|E;y(&-GT!qp$y#`#)xTKQgCQp3`_g)fj#M$3{%1`@g>F_%d^9 zCGOg#GCs1dq!F#r_kWf9Kgk80Cq6@?@#p$RBU+>H|19@^pC&G}jsG71+%6yLIzy8j zZNwg}jDPfjQTKmK`pnAtz`gDVE#rJB{;Jt@tUmU&_j}Glz4w5&Fr5#uFO@2LB;>x%YnZC9-?6!B;5 z@8uEm^SeardgrM11xbybpRX(0yR~J$HT{3->>j?H|LME4&vhI*(w84TpY|R?Y{=54=q7 s{Cw-tw>}dv0TVC*6EFc2FaZ-V0TVC*6EFc2FaZ-V0TVC*69`1$FASy}dH?_b literal 0 HcmV?d00001 diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/help_invader_mothership.tga b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/help_invader_mothership.tga new file mode 100644 index 0000000000000000000000000000000000000000..3bcd56d0e52bf740c9a860a9c6e655dca7059a51 GIT binary patch literal 32786 zcmeHOv5wO~6bw)t5~4r?L5Z%Sq@ul4oM@@|iZpbHlcHArwpIC>gBEzDT9Z}aT< z*;#MwcxR3EH~|AsaE)ic1@IDh1-vf7I?u6Loc-lo1Efv>;@*p^_O=h#LY+NLgZ z8&B%_PVJxjJMVGo{0sOC{42pa&#{d*v`t;+HlEb;J+*%uUoSC$Yv3mU-FLfv6l;NP zw4trd2dqEl3~vDL7w<~2&U0<~gzX^~sq1&(0XU>T+h{{un-5rj%o*0e zXMpwu>uaDbpRoO7$AinLbTDv%wlLoF-Y&I2s6XQYZD?!r0qbuAn_J)xxG%vv&#}$x zA3GjgLZyQN8(*nyfY$v#Xm8{L)?b)Eg#B1=%O`Aq(`WE_I;NgQlZ~e~9&`Wx0^9)L z+Vbli7i)!m>4Uxo`it#v$Jg-3oTDq>K=Y!Dji)vqf5Ys50DpjE^=DuDps#`c3cVuw zx9T7I^YRT0dC|p-$Gh;BV&pn^HHs){n%I&z=>&OK;Ava4e{O#rn z*aAny_oc0mVox9!QTy-5GPJuf@`wg2zy&m179{om$)L9W*)9;|lYD}WadLR;1+ zw>R_6F8{ON|D5NkR#NfL0qeN!dG*rczkUA0=OG=x?<`V(YJceWs-^31 zpZ{KB!dwHq7jSP_z3uTL?tjjebLQL`54k7uz92WbmB@U+u8+AL7x{g)=kqhS=RMwf z=Gq_dd+*%z^~O`{pKHAL^pDI3JpQL{FZ9nj-h28-<^xmf>s`OlU-8~s1&{4#o)6Uf zeHHT+?;o}8Q#++*o{te=7TLA`&6al3C_@%>np()IK51IFiA+`-48 z|A(cUnLPJlz{?M=;UpGa{}1gmM%koez{?L9pJS2p|FzO7C(mL)42S_SAO^&M7!U(u jKn#chF(3xSfEW-1Vn7Ut0Wly3#DEwO17bi7jLN`&D4cmB literal 0 HcmV?d00001 diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/help_invader_rank1.tga b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/help_invader_rank1.tga new file mode 100644 index 0000000000000000000000000000000000000000..722557738ec6db0728f9b7932098800726a8d939 GIT binary patch literal 16402 zcmeI2KZ_JW5XE=b-~=%c6pciT42_JGo5;iT0`VJ&iGqTGC>%zD9A*llk(wwd7#Q3I z`_abtTc(!2W_PN4d*`^j?Se-#k6JRfQ2h45QQi64H9frr9f!CNoTK^L1;|8Rj5 z+;Z0&ne+;|4Cgz!p2e;*mER}nJ`)}btOb(-rN7|aB zb)?h%sPdigRq)mebkM~nCw}9UyWXgoUSYm6X6d&BC)`###>2|L`t6JGgStd}bg{wK z{LIP(d-4lcIK#bEF3Z8TuqUMQ4>|vo-7jo2?Xkf&^p)EFp6uWZ_fjd1e--{mY!tx! zKB527-4nKjb;Msf{^#7qC$;~M*kET!h`)6F$Ip9v9zPL>Z^pw)k5620b}HKlvlwH2 z?>_KnIf5fxZNHJbABdm#_<1C}6g~^qPAT73zCx7xo7s09;Rvt$0Kf-iP@#71h__lV!vgH33a@i~KdcgjGJrZMrcjXwIw>}Ww2g+xO|ds~5Mso+msLj?k9buBs&)m2C|w1;RYuGycq zyzk^hjyJL6c(a?6#?n(9+nJd+_Dme_&Sp1fRawrKv!{2eJG+_(%md~D^MHB4JYXI$ z510qc1LlDY58Tk$Tf%MOzVJYJBs>A04Qt#v>Ei=s?%lr7fNxN-1cIU)D17`B_+R5`{6sZ->6) zk&O;?;e#-Lg!ZvchUSa5cs`aq&=$Yg^+C80`tqsQN5WkjWIEaCKo>a!KLG03gl&L& zX}-XBVLY$Y2HN76xw+!5>sb6NAE2u(f2`CFu&*><#NxRvezC1a&-u(<|FwMSiscXN zE6o?gIO_;sUWR_w&vUQ`}T%)p8erKJ?wAX573VfupJI?$t~=w+l3SwuLw!@b7g>B`{B8bi{agGG$$!!J z-_-J5XphT(CGWrJ{wLHowR^|EWd9T0|4xl>Xpi`p?tjDYpL(x>Z$o|I{d_*(_z3$` z_lw11<0CcR&9`r3>Gx0g{hRfD>vN}`TYT=+Q^n(dp*psfe*cE&AG}9V={8?lx6WepEK|I(w4Qj`x;{>tr_WqzzvFOByG*_Io8 z$)2D(-_FVhx%^V&Wk`&eIcpjbT#s+NJcj32(>Yc#vIE^#Ri2dx#ehQpT_@J#m z2a7z9f6;}`D%WGnzRM8)*!@X(Ct$k|+UR4<`Wo;bvi`q_+Z%zs3E!kXXrqrYDgLa1 zL$Ckn_YoWdq~6b2`wPh?R!)SW=lgoJsPJ3kv9J2h0QJ0rP-) I;QD#sKUNipPyhe` literal 0 HcmV?d00001 diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/help_invader_rank3.tga b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/help_invader_rank3.tga new file mode 100644 index 0000000000000000000000000000000000000000..9f832c3b74288da9c6c2bbb919df653f022166be GIT binary patch literal 16402 zcmeI2zm5|z5XSST2m(C<}GW6ykJ&t&6dGMP=HG?QlX3crUUT#0U?g$Tsr@~8N89q+19 zdG=EKZGIkUf;YmkaK163XT*SgKrT=ZJV6It#+;eyHhM;Ev4u@+r`8t@$r78N`0-Kr zC2Zr5ziVMBP!~Nx2i>)>Yd&mY6WgivMMJV=q0!xZXDu$c>$=O|wfg$xPfW3i?bQ0B zAz5Pc6JJ_v*{>z`e``;+Q|n6>c@5qtKfC_@(3tna3*m7X|3Fvv(4zp_JY#Nj&}9r` zyK`;ZU#aoclv%P*eny{v%hG3oJ^D(37qzVsUJK+epxp@cp@S}C7|R@SzHR*JTMJ8J zpZX%TEJ@}6MbGn`xG-Z}h|DFl&gztjqAN{Q1#X4I5?R}VUr@pnPdwjqbe8RWVa$5$D1oHNi zu+RQ%V`TZ-d(GGC(suf>jSu*OPxw|^{&)V@+5hdD+V!;eTD(@5w%0G6q4)ol_0RfS z`d<8p{`}`Xr4jhN`fGhk^R@HiL-3{172iq=?`piS@R>ap{`i7V_*PnYXQker3S;4q zFZhISr6q~~bUNLD*=%+JyMOKdd_LcFVf*d9$g=;3p|du$b9$s7p(_@|CsXKi$_*H2z+C%a~)g0 zd;GKNL5KfX^WEd0RSyFHG3UF-KdT-D{x>P#y?EGq;Qjt_)AHTpZ|lKI1NgprB5-a2 zX!DFde)~%Kzl_vJEw&zbzkd$>e}>Y*v-N=U1I{V<4#`;>pv^P-$d^OD&&zz38i5*t Y8i5*t8i5*t8i5*t8i5*te=!1o0Dv&GtN;K2 literal 0 HcmV?d00001 diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/high_score.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/high_score.rml new file mode 100644 index 00000000..0fe53476 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/high_score.rml @@ -0,0 +1,91 @@ + + + High Scores + + + + + + + + + + + + + + + + + + + + + + + + + + +
    PilotShipWaveScore
    + + + {{score.name}} + + + + {{score.wave}} + + {{score.score}} +
    No scores recorded. Play a game!
    + + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/high_scores_defender.tga b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/high_scores_defender.tga new file mode 100644 index 0000000000000000000000000000000000000000..05163e9eb44a3c9916eb33e9905748eb0b451b53 GIT binary patch literal 4114 zcmeH}K?=e!5Jgj0(n9MA#G`osTXf;RLc4I&_`WotC&k=J?KG7KZglhdZA=LfP*7di0jBec*mkEpY^E;C6c7^}| literal 0 HcmV?d00001 diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/invaders.tga b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/invaders.tga new file mode 100644 index 0000000000000000000000000000000000000000..5f0e40d006f80a4043f7d30fdc9b6466c8091c03 GIT binary patch literal 65554 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 literal 0 HcmV?d00001 diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/logo.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/logo.rml new file mode 100644 index 00000000..6ab86604 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/logo.rml @@ -0,0 +1,23 @@ + + + + + + + + diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/logo.tga b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/logo.tga new file mode 100644 index 0000000000000000000000000000000000000000..4242510711624f9bf8cf142e601c9b8603388fda GIT binary patch literal 524306 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 literal 0 HcmV?d00001 diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/main_menu.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/main_menu.rml new file mode 100644 index 00000000..71076f93 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/main_menu.rml @@ -0,0 +1,37 @@ + + + Main Menu + + + + + +
    +
    +
    +
    + + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/options.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/options.rml new file mode 100644 index 00000000..294fac1d --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/options.rml @@ -0,0 +1,126 @@ + + + Options + + + + + +
    +
    +

    + Graphics
    +
    +
    +
    +

    +

    Are you sure about this? Bad graphics are just plain bad.

    +

    + Audio
    + +
    +
    +

    +
    + + +
    + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/pause.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/pause.rml new file mode 100644 index 00000000..898f62ec --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/pause.rml @@ -0,0 +1,26 @@ + + + Quit? + + + + +
    +

    Are you sure you want to end this game?

    + + + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/start_game.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/start_game.rml new file mode 100644 index 00000000..cee40cb5 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/start_game.rml @@ -0,0 +1,78 @@ + + + Start Game + + + + + +
    +
    +

    + Difficulty
    +
    + +

    +

    + Colour
    + +

    +
    + Start Game! +
    + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/window.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/window.rml new file mode 100644 index 00000000..d2516216 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/data/window.rml @@ -0,0 +1,35 @@ + diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/lua/start.lua b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/lua/start.lua new file mode 100644 index 00000000..d43861b5 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/lua/start.lua @@ -0,0 +1,7 @@ +function Startup() + maincontext = rmlui.contexts["main"] + maincontext:LoadDocument("lua_invaders/data/background.rml"):Show() + maincontext:LoadDocument("lua_invaders/data/main_menu.rml"):Show() +end + +Startup() diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/DecoratorDefender.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/DecoratorDefender.cpp new file mode 100644 index 00000000..8a42cba6 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/DecoratorDefender.cpp @@ -0,0 +1,87 @@ +#include "DecoratorDefender.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct DecoratorDefenderElementData { + Rml::Texture texture; + Rml::Geometry geometry; +}; + +DecoratorDefender::~DecoratorDefender() {} + +bool DecoratorDefender::Initialise(const Rml::Texture& texture) +{ + image_index = AddTexture(texture); + if (image_index == -1) + { + return false; + } + + return true; +} + +Rml::DecoratorDataHandle DecoratorDefender::GenerateElementData(Rml::Element* element, Rml::BoxArea /*paint_area*/) const +{ + Rml::RenderManager* render_manager = element->GetRenderManager(); + if (!render_manager) + return Rml::Decorator::INVALID_DECORATORDATAHANDLE; + + Rml::Vector2f position = element->GetAbsoluteOffset(Rml::BoxArea::Padding); + Rml::Vector2f size = element->GetBox().GetSize(Rml::BoxArea::Padding); + Rml::Math::SnapToPixelGrid(position, size); + + Rml::ColourbPremultiplied color = element->GetProperty("image-color").ToPremultiplied(); + Rml::Mesh mesh; + Rml::MeshUtilities::GenerateQuad(mesh, Rml::Vector2f(0.f), size, color); + + DecoratorDefenderElementData* element_data = new DecoratorDefenderElementData{ + GetTexture(image_index), + render_manager->MakeGeometry(std::move(mesh)), + }; + + if (!element_data->texture || !element_data->geometry) + return Rml::Decorator::INVALID_DECORATORDATAHANDLE; + + return reinterpret_cast(element_data); +} + +void DecoratorDefender::ReleaseElementData(Rml::DecoratorDataHandle element_data_handle) const +{ + delete reinterpret_cast(element_data_handle); +} + +void DecoratorDefender::RenderElement(Rml::Element* element, Rml::DecoratorDataHandle element_data_handle) const +{ + Rml::Vector2f position = element->GetAbsoluteOffset(Rml::BoxArea::Padding).Round(); + DecoratorDefenderElementData* element_data = reinterpret_cast(element_data_handle); + element_data->geometry.Render(position, element_data->texture); +} + +DecoratorInstancerDefender::DecoratorInstancerDefender() +{ + id_image_src = RegisterProperty("image-src", "").AddParser("string").GetId(); + RegisterShorthand("decorator", "image-src", Rml::ShorthandType::FallThrough); +} + +DecoratorInstancerDefender::~DecoratorInstancerDefender() {} + +Rml::SharedPtr DecoratorInstancerDefender::InstanceDecorator(const Rml::String& /*name*/, const Rml::PropertyDictionary& properties, + const Rml::DecoratorInstancerInterface& instancer_interface) +{ + const Rml::Property* image_source_property = properties.GetProperty(id_image_src); + Rml::String image_source = image_source_property->Get(); + Rml::Texture texture = instancer_interface.GetTexture(image_source); + + auto decorator = Rml::MakeShared(); + if (decorator->Initialise(texture)) + return decorator; + + return nullptr; +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/DecoratorDefender.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/DecoratorDefender.h new file mode 100644 index 00000000..ef53251a --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/DecoratorDefender.h @@ -0,0 +1,34 @@ +#pragma once + +#include + +class DecoratorDefender : public Rml::Decorator { +public: + virtual ~DecoratorDefender(); + + bool Initialise(const Rml::Texture& texture); + + /// Called on a decorator to generate any required per-element data for a newly decorated element. + Rml::DecoratorDataHandle GenerateElementData(Rml::Element* element, Rml::BoxArea paint_area) const override; + /// Called to release element data generated by this decorator. + void ReleaseElementData(Rml::DecoratorDataHandle element_data) const override; + + /// Called to render the decorator on an element. + void RenderElement(Rml::Element* element, Rml::DecoratorDataHandle element_data) const override; + +private: + int image_index; +}; + +class DecoratorInstancerDefender : public Rml::DecoratorInstancer { +public: + DecoratorInstancerDefender(); + ~DecoratorInstancerDefender(); + + /// Instances a decorator given the property tag and attributes from the RCSS file. + Rml::SharedPtr InstanceDecorator(const Rml::String& name, const Rml::PropertyDictionary& properties, + const Rml::DecoratorInstancerInterface& instancer_interface) override; + +private: + Rml::PropertyId id_image_src; +}; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/DecoratorStarfield.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/DecoratorStarfield.cpp new file mode 100644 index 00000000..f40f3fc0 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/DecoratorStarfield.cpp @@ -0,0 +1,155 @@ +#include "DecoratorStarfield.h" +#include "GameDetails.h" +#include "Sprite.h" +#include +#include +#include +#include +#include +#include + +DecoratorStarfield::~DecoratorStarfield() {} + +bool DecoratorStarfield::Initialise(int _num_layers, const Rml::Colourb& _top_colour, const Rml::Colourb& _bottom_colour, float _top_speed, + float _bottom_speed, int _top_density, int _bottom_density) +{ + num_layers = _num_layers; + top_colour = _top_colour; + bottom_colour = _bottom_colour; + top_speed = _top_speed; + bottom_speed = _bottom_speed; + top_density = _top_density; + bottom_density = _bottom_density; + + return true; +} + +Rml::DecoratorDataHandle DecoratorStarfield::GenerateElementData(Rml::Element* element, Rml::BoxArea /*paint_area*/) const +{ + const double t = Rml::GetSystemInterface()->GetElapsedTime(); + + StarField* star_field = new StarField(); + star_field->star_layers.resize(num_layers); + + for (int i = 0; i < num_layers; i++) + { + float layer_depth = i / (float)num_layers; + + int density = int((top_density * layer_depth) + (bottom_density * (1.0f - layer_depth))); + star_field->star_layers[i].stars.resize(density); + + Rml::Colourb colour = (top_colour * layer_depth) + (bottom_colour * (1.0f - layer_depth)); + star_field->star_layers[i].colour = colour; + + float speed = (top_speed * layer_depth) + (bottom_speed * (1.0f - layer_depth)); + star_field->star_layers[i].speed = speed; + + star_field->dimensions = element->GetBox().GetSize(Rml::BoxArea::Padding); + + if (star_field->dimensions.x > 0) + { + for (int j = 0; j < density; j++) + { + star_field->star_layers[i].stars[j].x = (float)Rml::Math::RandomReal(star_field->dimensions.x); + star_field->star_layers[i].stars[j].y = (float)Rml::Math::RandomReal(star_field->dimensions.y); + } + } + + star_field->last_update = t; + } + + return reinterpret_cast(star_field); +} + +void DecoratorStarfield::ReleaseElementData(Rml::DecoratorDataHandle element_data) const +{ + delete reinterpret_cast(element_data); +} + +void DecoratorStarfield::RenderElement(Rml::Element* element, Rml::DecoratorDataHandle element_data) const +{ + const double t = Rml::GetSystemInterface()->GetElapsedTime(); + + StarField* star_field = reinterpret_cast(element_data); + star_field->Update(t); + + const float dp_ratio = Rml::ElementUtilities::GetDensityIndependentPixelRatio(element); + const float point_size = Rml::Math::RoundUp(2.f * dp_ratio); + + int num_stars = 0; + + for (size_t i = 0; i < star_field->star_layers.size(); i++) + num_stars += (int)star_field->star_layers[i].stars.size(); + + ColoredPointList points; + points.reserve(num_stars); + + for (size_t i = 0; i < star_field->star_layers.size(); i++) + { + Rml::ColourbPremultiplied color = star_field->star_layers[i].colour.ToPremultiplied(); + + for (size_t j = 0; j < star_field->star_layers[i].stars.size(); j++) + { + const Rml::Vector2f position = star_field->star_layers[i].stars[j]; + points.push_back(ColoredPoint{color, position}); + } + } + + if (Rml::RenderManager* render_manager = element->GetRenderManager()) + DrawPoints(*render_manager, point_size, points); +} + +void DecoratorStarfield::StarField::Update(double t) +{ + float delta_time = float(t - last_update); + last_update = t; + + if (!GameDetails::GetPaused()) + { + for (size_t i = 0; i < star_layers.size(); i++) + { + float movement = star_layers[i].speed * delta_time; + + for (size_t j = 0; j < star_layers[i].stars.size(); j++) + { + star_layers[i].stars[j].y += movement; + if (star_layers[i].stars[j].y > dimensions.y) + { + star_layers[i].stars[j].y = 0; + star_layers[i].stars[j].x = Rml::Math::RandomReal(dimensions.x); + } + } + } + } +} + +DecoratorInstancerStarfield::DecoratorInstancerStarfield() +{ + id_num_layers = RegisterProperty("num-layers", "3").AddParser("number").GetId(); + id_top_colour = RegisterProperty("top-colour", "#dddc").AddParser("color").GetId(); + id_bottom_colour = RegisterProperty("bottom-colour", "#333c").AddParser("color").GetId(); + id_top_speed = RegisterProperty("top-speed", "10.0").AddParser("number").GetId(); + id_bottom_speed = RegisterProperty("bottom-speed", "2.0").AddParser("number").GetId(); + id_top_density = RegisterProperty("top-density", "15").AddParser("number").GetId(); + id_bottom_density = RegisterProperty("bottom-density", "10").AddParser("number").GetId(); +} + +DecoratorInstancerStarfield::~DecoratorInstancerStarfield() {} + +Rml::SharedPtr DecoratorInstancerStarfield::InstanceDecorator(const Rml::String& /*name*/, const Rml::PropertyDictionary& properties, + const Rml::DecoratorInstancerInterface& /*instancer_interface*/) +{ + int num_layers = properties.GetProperty(id_num_layers)->Get(); + Rml::Colourb top_colour = properties.GetProperty(id_top_colour)->Get(); + Rml::Colourb bottom_colour = properties.GetProperty(id_bottom_colour)->Get(); + float top_speed = properties.GetProperty(id_top_speed)->Get(); + float bottom_speed = properties.GetProperty(id_bottom_speed)->Get(); + int top_density = properties.GetProperty(id_top_density)->Get(); + int bottom_density = properties.GetProperty(id_bottom_density)->Get(); + + auto decorator = Rml::MakeShared(); + if (decorator->Initialise(num_layers, top_colour, bottom_colour, top_speed, bottom_speed, top_density, bottom_density)) + return decorator; + + return nullptr; +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/DecoratorStarfield.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/DecoratorStarfield.h new file mode 100644 index 00000000..a3fafa4a --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/DecoratorStarfield.h @@ -0,0 +1,58 @@ +#pragma once + +#include +#include + +class DecoratorStarfield : public Rml::Decorator { +public: + virtual ~DecoratorStarfield(); + + bool Initialise(int num_layers, const Rml::Colourb& top_colour, const Rml::Colourb& bottom_colour, float top_speed, float bottom_speed, + int top_density, int bottom_density); + + /// Called on a decorator to generate any required per-element data for a newly decorated element. + Rml::DecoratorDataHandle GenerateElementData(Rml::Element* element, Rml::BoxArea paint_area) const override; + /// Called to release element data generated by this decorator. + void ReleaseElementData(Rml::DecoratorDataHandle element_data) const override; + + /// Called to render the decorator on an element. + void RenderElement(Rml::Element* element, Rml::DecoratorDataHandle element_data) const override; + +private: + int num_layers; + Rml::Colourb top_colour; + Rml::Colourb bottom_colour; + float top_speed; + float bottom_speed; + int top_density; + int bottom_density; + + struct StarLayer { + typedef Rml::Vector StarList; + StarList stars; + Rml::Colourb colour; + float speed; + }; + + struct StarField { + void Update(double t); + double last_update; + Rml::Vector2f dimensions; + + typedef Rml::Vector StarLayerList; + StarLayerList star_layers; + }; +}; + +class DecoratorInstancerStarfield : public Rml::DecoratorInstancer { +public: + DecoratorInstancerStarfield(); + ~DecoratorInstancerStarfield(); + + /// Instances a decorator given the property tag and attributes from the RCSS file. + Rml::SharedPtr InstanceDecorator(const Rml::String& name, const Rml::PropertyDictionary& properties, + const Rml::DecoratorInstancerInterface& instancer_interface) override; + +private: + Rml::PropertyId id_num_layers, id_top_colour, id_bottom_colour, id_top_speed, id_bottom_speed, id_top_density, id_bottom_density; +}; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/Defender.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/Defender.cpp new file mode 100644 index 00000000..d52cd383 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/Defender.cpp @@ -0,0 +1,154 @@ +#include "Defender.h" +#include "Game.h" +#include "GameDetails.h" +#include "Invader.h" +#include "Mothership.h" +#include "Shield.h" +#include "Sprite.h" +#include + +const float UPDATE_FREQ = 0.01f; +const float MOVEMENT_SPEED = 300; +const float BULLET_SPEED = 15; +const int SPRITE_WIDTH = 64; +const float RESPAWN_TIME = 1.0f; + +Sprite defender_sprite(Rml::Vector2f(60, 31), Rml::Vector2f(0, 0.5), Rml::Vector2f(0.23437500, 0.98437500)); +Sprite bullet_sprite(Rml::Vector2f(4, 20), Rml::Vector2f(0.4921875, 0.515625), Rml::Vector2f(0.5078125, 0.828125)); +Sprite explosion_sprite(Rml::Vector2f(52, 28), Rml::Vector2f(0.71484375f, 0.51562500f), Rml::Vector2f(0.91796875f, 0.95312500f)); + +Defender::Defender(Game* _game) +{ + move_direction = 0; + defender_frame_start = 0; + bullet_in_flight = false; + respawn_start = 0; + game = _game; + position.x = game->GetWindowDimensions().x / 2; + position.y = game->GetWindowDimensions().y - 50; + state = ALIVE; + render = true; +} + +Defender::~Defender() {} + +void Defender::Update(double t) +{ + float dt = float(t - defender_frame_start); + if (dt < UPDATE_FREQ) + return; + + dt = Rml::Math::Min(dt, 0.1f); + + defender_frame_start = t; + + position.x += (move_direction * dt * MOVEMENT_SPEED); + + if (position.x < 5) + position.x = 5; + else if (position.x > (game->GetWindowDimensions().x - SPRITE_WIDTH - 5)) + position.x = game->GetWindowDimensions().x - SPRITE_WIDTH - 5; + + UpdateBullet(t); + + if (state == RESPAWN) + { + // Switch the render flag so the defender "flickers" + render = !render; + + // Check if we should switch back to our alive state + if (float(t - respawn_start) > RESPAWN_TIME) + { + state = ALIVE; + render = true; + } + } +} + +void Defender::Render(Rml::RenderManager& render_manager, float dp_ratio, Rml::Texture texture) +{ + Rml::ColourbPremultiplied color = GameDetails::GetDefenderColour().ToPremultiplied(); + + // Render our sprite if rendering is enabled + if (render) + defender_sprite.Render(render_manager, position, dp_ratio, color, texture); + + // Render the bullet + if (bullet_in_flight) + bullet_sprite.Render(render_manager, bullet_position, dp_ratio, color, texture); +} + +void Defender::StartMove(float direction) +{ + move_direction = direction > 0.0f ? 1.0f : -1.0f; +} + +void Defender::StopMove(float direction) +{ + float stop_direction = direction > 0.0f ? 1.0f : -1.0f; + if (stop_direction == move_direction) + move_direction = 0.0f; +} + +void Defender::Fire() +{ + if (!bullet_in_flight) + { + bullet_position = position + Rml::Vector2f((SPRITE_WIDTH / 2) - 4, 0); + bullet_in_flight = true; + } +} + +bool Defender::CheckHit(double t, const Rml::Vector2f& check_position) +{ + float sprite_width = defender_sprite.dimensions.x; + float sprite_height = defender_sprite.dimensions.y; + + // If the position is within our bounds, set ourselves + // as exploding and return a valid hit. + if (state == ALIVE && check_position.x >= position.x && check_position.x <= position.x + sprite_width && check_position.y >= position.y && + check_position.y <= position.y + sprite_height) + { + game->RemoveLife(); + state = RESPAWN; + respawn_start = t; + + return true; + } + + return false; +} + +void Defender::UpdateBullet(double t) +{ + if (bullet_in_flight) + { + // Move the bullet up and mark it dead if it flies off the top of the screen + bullet_position.y -= BULLET_SPEED; + if (bullet_position.y < 0) + { + bullet_in_flight = false; + return; + } + + // Check if we hit the shields + for (int i = 0; i < game->GetNumShields(); i++) + { + if (game->GetShield(i)->CheckHit(bullet_position)) + { + bullet_in_flight = false; + return; + } + } + + // Check if we hit any invaders + for (int i = 0; i < game->GetNumInvaders(); i++) + { + if (game->GetInvader(i)->CheckHit(t, bullet_position)) + { + bullet_in_flight = false; + return; + } + } + } +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/Defender.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/Defender.h new file mode 100644 index 00000000..50ced63e --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/Defender.h @@ -0,0 +1,51 @@ +#pragma once + +#include + +class Game; +class Sprite; + +/** + Represents the Earth defender. Stores position and performs the update of the bullet position and collision detection. + */ + +class Defender { +public: + Defender(Game* game); + ~Defender(); + + /// Update the defender state. + void Update(double t); + /// Render the defender. + void Render(Rml::RenderManager& render_manager, float dp_ratio, Rml::Texture texture); + + /// Move the defender left. + void StartMove(float direction); + /// Stop the movement. + void StopMove(float direction); + /// Fire a bullet (if one isn't already in flight). + void Fire(); + + /// Check if an object at the given position would hit the defender. + bool CheckHit(double t, const Rml::Vector2f& position); + +private: + /// Update the bullet, doing collision detection. + void UpdateBullet(double t); + + Game* game; + Rml::Vector2f position; + + float move_direction; + + bool bullet_in_flight; + Rml::Vector2f bullet_position; + + double defender_frame_start; + double respawn_start; + + bool render; + + enum State { ALIVE, RESPAWN }; + State state; +}; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/ElementGame.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/ElementGame.cpp new file mode 100644 index 00000000..b41427fc --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/ElementGame.cpp @@ -0,0 +1,89 @@ +#include "ElementGame.h" +#include "Defender.h" +#include "Game.h" +#include +#include +#include +#include +#include +#include + +ElementGame::ElementGame(const Rml::String& tag) : Rml::Element(tag) +{ + game = new Game(); +} + +ElementGame::~ElementGame() +{ + delete game; +} + +void ElementGame::ProcessEvent(Rml::Event& event) +{ + if (event == Rml::EventId::Keydown || event == Rml::EventId::Keyup) + { + bool key_down = (event == Rml::EventId::Keydown); + Rml::Input::KeyIdentifier key_identifier = (Rml::Input::KeyIdentifier)event.GetParameter("key_identifier", 0); + + // Process left and right keys + if (key_down) + { + if (key_identifier == Rml::Input::KI_LEFT) + game->GetDefender()->StartMove(-1.0f); + if (key_identifier == Rml::Input::KI_RIGHT) + game->GetDefender()->StartMove(1.0f); + if (key_identifier == Rml::Input::KI_SPACE) + game->GetDefender()->Fire(); + } + else + { + if (key_identifier == Rml::Input::KI_LEFT) + game->GetDefender()->StopMove(-1.0f); + if (key_identifier == Rml::Input::KI_RIGHT) + game->GetDefender()->StopMove(1.0f); + } + } + + if (event == Rml::EventId::Load) + { + game->Initialise(); + } +} + +void ElementGame::OnUpdate() +{ + game->Update(Rml::GetSystemInterface()->GetElapsedTime()); + + if (game->IsGameOver()) + DispatchEvent("gameover", Rml::Dictionary()); +} + +void ElementGame::OnRender() +{ + if (Rml::Context* context = GetContext()) + game->Render(context->GetRenderManager(), context->GetDensityIndependentPixelRatio()); +} + +void ElementGame::OnChildAdd(Rml::Element* element) +{ + Rml::Element::OnChildAdd(element); + + if (element == this) + { + GetOwnerDocument()->AddEventListener(Rml::EventId::Load, this); + GetOwnerDocument()->AddEventListener(Rml::EventId::Keydown, this); + GetOwnerDocument()->AddEventListener(Rml::EventId::Keyup, this); + } +} + +void ElementGame::OnChildRemove(Rml::Element* element) +{ + Rml::Element::OnChildRemove(element); + + if (element == this) + { + GetOwnerDocument()->RemoveEventListener(Rml::EventId::Load, this); + GetOwnerDocument()->RemoveEventListener(Rml::EventId::Keydown, this); + GetOwnerDocument()->RemoveEventListener(Rml::EventId::Keyup, this); + } +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/ElementGame.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/ElementGame.h new file mode 100644 index 00000000..a59e54bb --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/ElementGame.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +class Game; + +class ElementGame : public Rml::Element, public Rml::EventListener { +public: + ElementGame(const Rml::String& tag); + virtual ~ElementGame(); + + /// Intercepts and handles key events. + void ProcessEvent(Rml::Event& event) override; + + /// This will get called when we're added to the tree, which allows us to bind to events. + void OnChildAdd(Rml::Element* element) override; + + /// This will get called when we're removed from the tree, which allows us to clean up the event listeners previously added. + void OnChildRemove(Element* element) override; + +protected: + /// Updates the game. + void OnUpdate() override; + /// Renders the game. + void OnRender() override; + +private: + Game* game; +}; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/ElementGameInstancer.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/ElementGameInstancer.cpp new file mode 100644 index 00000000..f7101d53 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/ElementGameInstancer.cpp @@ -0,0 +1,14 @@ +#include "ElementGameInstancer.h" +#include "ElementGame.h" + +ElementGameInstancer::~ElementGameInstancer() {} + +Rml::ElementPtr ElementGameInstancer::InstanceElement(Rml::Element* /*parent*/, const Rml::String& tag, const Rml::XMLAttributes& /*attributes*/) +{ + return Rml::ElementPtr(new ElementGame(tag)); +} + +void ElementGameInstancer::ReleaseElement(Rml::Element* element) +{ + delete element; +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/ElementGameInstancer.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/ElementGameInstancer.h new file mode 100644 index 00000000..1a0bd519 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/ElementGameInstancer.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +class ElementGameInstancer : public Rml::ElementInstancer { +public: + virtual ~ElementGameInstancer(); + + /// Instances an element given the tag name and attributes + /// @param tag Name of the element to instance + /// @param attributes vector of name value pairs + Rml::ElementPtr InstanceElement(Rml::Element* parent, const Rml::String& tag, const Rml::XMLAttributes& attributes) override; + + /// Releases the given element + /// @param element to release + void ReleaseElement(Rml::Element* element) override; +}; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/Game.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/Game.cpp new file mode 100644 index 00000000..0496ff6c --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/Game.cpp @@ -0,0 +1,399 @@ +#include "Game.h" +#include "Defender.h" +#include "GameDetails.h" +#include "HighScores.h" +#include "Invader.h" +#include "Mothership.h" +#include "Shield.h" +#include +#include + +const int WINDOW_WIDTH = 1024; +const int WINDOW_HEIGHT = 768; + +const int NUM_LIVES = 3; + +const int NUM_INVADER_ROWS = 5; +const int NUM_INVADERS_PER_ROW = 11; +const int NUM_INVADERS = (NUM_INVADER_ROWS * NUM_INVADERS_PER_ROW); +const int INVADER_SPACING_X = 64; +const int INVADER_SPACING_Y = 48; +const int INVADER_START_Y = 96; + +const float INVADER_MOVEMENT = 10; +const float INVADER_UPDATE_MODIFIER = 0.7f; + +const int MOTHERSHIP = NUM_INVADERS; + +const int NUM_SHIELD_ARRAYS = 4; +const int NUM_SHIELDS_PER_ARRAY = 10; +const int NUM_SHIELDS = (NUM_SHIELD_ARRAYS * NUM_SHIELDS_PER_ARRAY); +const int SHIELD_SIZE = (NUM_SHIELD_CELLS * PIXEL_SIZE); +const int SHIELD_SPACING_X = 192; +const int SHIELD_START_X = 176; +const int SHIELD_START_Y = 600; + +// The game's element context (declared in main.cpp). +extern Rml::Context* context; + +Game::Game() +{ + invader_frame_start = 0; + defender_lives = 3; + invader_move_freq = 0; + game_over = false; + current_invader_direction = 1.0f; + invaders = new Invader*[NUM_INVADERS + 1]; + for (int i = 0; i < NUM_INVADERS + 1; i++) + invaders[i] = nullptr; + + shields = new Shield*[NUM_SHIELDS]; + for (int i = 0; i < NUM_SHIELDS; i++) + shields[i] = nullptr; + + defender = new Defender(this); +} + +Game::~Game() +{ + delete defender; + + for (int i = 0; i < NUM_INVADERS + 1; i++) + delete invaders[i]; + + delete[] invaders; + + for (int i = 0; i < NUM_SHIELDS; i++) + delete shields[i]; + + delete[] shields; +} + +void Game::Initialise() +{ + // Initialise the scores and wave information + SetScore(0); + SetWave(1); + SetHighScore(HighScores::GetHighScore()); + SetLives(NUM_LIVES); + + // Initialise the shields. + InitialiseShields(); + + // Create a new wave + InitialiseWave(); +} + +void Game::Update(double t) +{ + if (!GameDetails::GetPaused()) + { + if (defender_lives <= 0) + return; + + // Determine if we should advance the invaders + if (t - invader_frame_start >= invader_move_freq) + { + MoveInvaders(); + + invader_frame_start = t; + } + + // Update all invaders + for (int i = 0; i < NUM_INVADERS + 1; i++) + invaders[i]->Update(t); + + defender->Update(t); + } +} + +void Game::Render(Rml::RenderManager& render_manager, float dp_ratio) +{ + if (defender_lives <= 0) + return; + + if (!texture) + texture = render_manager.LoadTexture("lua_invaders/data/invaders.tga"); + + // Render all available shields + for (int i = 0; i < NUM_SHIELDS; i++) + shields[i]->Render(render_manager, dp_ratio); + + // Render all available invaders + for (int i = 0; i < NUM_INVADERS + 1; i++) + invaders[i]->Render(render_manager, dp_ratio, texture); + + defender->Render(render_manager, dp_ratio, texture); +} + +Defender* Game::GetDefender() +{ + return defender; +} + +Invader* Game::GetInvader(int index) +{ + return invaders[index]; +} + +int Game::GetNumInvaders() +{ + return NUM_INVADERS + 1; +} + +bool Game::CanDropBomb(int index) +{ + // Determines if the given invader can drop a bomb by checking if theres any other invaders under it + int y = (index / NUM_INVADERS_PER_ROW); + int x = index - (y * NUM_INVADERS_PER_ROW); + + // The mothership can never drop bombs + if (index == MOTHERSHIP) + return false; + + // Check each row under the given invader to see if there is space + for (int i = y + 1; i < NUM_INVADER_ROWS; i++) + { + if (invaders[(i * NUM_INVADERS_PER_ROW) + x]->GetState() == Invader::ALIVE) + return false; + } + + return true; +} + +Shield* Game::GetShield(int index) +{ + return shields[index]; +} + +int Game::GetNumShields() +{ + return NUM_SHIELDS; +} + +void Game::AddScore(int score) +{ + SetScore(GameDetails::GetScore() + score); +} + +void Game::SetScore(int score) +{ + GameDetails::SetScore(score); + + Rml::Element* score_element = context->GetDocument("game_window")->GetElementById("score"); + if (score_element != nullptr) + score_element->SetInnerRML(Rml::CreateString("%d", score).c_str()); + + // Update the high score if we've beaten it. + if (score > HighScores::GetHighScore()) + SetHighScore(score); +} + +void Game::SetHighScore(int score) +{ + Rml::Element* high_score_element = context->GetDocument("game_window")->GetElementById("hiscore"); + if (high_score_element != nullptr) + high_score_element->SetInnerRML(Rml::CreateString("%d", score).c_str()); +} + +void Game::SetLives(int lives) +{ + defender_lives = lives; + + Rml::Element* score_element = context->GetDocument("game_window")->GetElementById("lives"); + if (score_element != nullptr) + score_element->SetInnerRML(Rml::CreateString("%d", defender_lives).c_str()); +} + +void Game::SetWave(int wave) +{ + GameDetails::SetWave(wave); + + Rml::Element* waves_element = context->GetDocument("game_window")->GetElementById("waves"); + if (waves_element != nullptr) + waves_element->SetInnerRML(Rml::CreateString("%d", wave).c_str()); +} + +void Game::RemoveLife() +{ + if (defender_lives > 0) + { + SetLives(defender_lives - 1); + + if (defender_lives == 0) + { + OnGameOver(); + } + } +} + +bool Game::IsGameOver() const +{ + return game_over; +} + +const Rml::Vector2f Game::GetWindowDimensions() +{ + return Rml::Vector2f((float)WINDOW_WIDTH, (float)WINDOW_HEIGHT); +} + +void Game::MoveInvaders() +{ + Rml::Vector2f new_positions[NUM_INVADERS]; + + // We loop through all invaders, calculating their new positions, if any of them go over the screen bounds, + // then we switch direction, move the invaders down and start at 0 again + for (int i = 0; i < NUM_INVADERS; i++) + { + if (invaders[i]->GetState() == Invader::DEAD) + continue; + + new_positions[i] = invaders[i]->GetPosition(); + new_positions[i].x += INVADER_MOVEMENT * current_invader_direction; + if (new_positions[i].x < 0 || new_positions[i].x + INVADER_SPACING_X > WINDOW_WIDTH) + { + // Switch direction and start back at 0 (-1 as the for loop increments) + current_invader_direction *= -1.0f; + i = -1; + + // Move all invaders down + for (int j = 0; j < NUM_INVADERS; j++) + { + if (invaders[j]->GetState() == Invader::DEAD) + continue; + + Rml::Vector2f position = invaders[j]->GetPosition(); + position.y += INVADER_SPACING_Y; + invaders[j]->SetPosition(position); + } + + // Increase speed of invaders + invader_move_freq *= INVADER_UPDATE_MODIFIER; + } + } + + // Assign invaders their new position and advance their animation frame + bool invaders_alive = false; + + for (int i = 0; i < NUM_INVADERS; i++) + { + if (invaders[i]->GetState() == Invader::DEAD) + continue; + + invaders[i]->SetPosition(new_positions[i]); + invaders[i]->UpdateAnimation(); + + // If an invader hits the bottom, instant death + if (new_positions[i].y >= GetWindowDimensions().y - INVADER_SPACING_Y) + { + OnGameOver(); + return; + } + + invaders_alive = true; + } + + if (!invaders_alive) + { + SetWave(GameDetails::GetWave() + 1); + InitialiseWave(); + } +} + +void Game::OnGameOver() +{ + // Set lives to zero and continue to the high scores + defender_lives = 0; + game_over = true; +} + +void Game::InitialiseShields() +{ + Rml::Vector2f shield_array_start_position((float)SHIELD_START_X, (float)SHIELD_START_Y); + + for (int x = 0; x < NUM_SHIELD_ARRAYS; x++) + { + // Top row (row of 4) + for (int i = 0; i < 4; i++) + { + Shield::ShieldType type = Shield::REGULAR; + if (i == 0) + type = Shield::TOP_LEFT; + else if (i == 3) + { + type = Shield::TOP_RIGHT; + } + + shields[(x * NUM_SHIELDS_PER_ARRAY) + i] = new Shield(this, type); + shields[(x * NUM_SHIELDS_PER_ARRAY) + i]->SetPosition(shield_array_start_position + Rml::Vector2f((float)SHIELD_SIZE * i, 0)); + } + + // Middle row (row of 4) + for (int i = 0; i < 4; i++) + { + Shield::ShieldType type = Shield::REGULAR; + if (i == 1) + type = Shield::BOTTOM_RIGHT; + else if (i == 2) + { + type = Shield::BOTTOM_LEFT; + } + + shields[(x * NUM_SHIELDS_PER_ARRAY) + 4 + i] = new Shield(this, type); + shields[(x * NUM_SHIELDS_PER_ARRAY) + 4 + i]->SetPosition( + shield_array_start_position + Rml::Vector2f((float)SHIELD_SIZE * i, (float)SHIELD_SIZE)); + } + + // Bottom row (2, one on each end) + shields[(x * NUM_SHIELDS_PER_ARRAY) + 8] = new Shield(this, Shield::REGULAR); + shields[(x * NUM_SHIELDS_PER_ARRAY) + 8]->SetPosition(shield_array_start_position + Rml::Vector2f(0, (float)SHIELD_SIZE * 2)); + + shields[(x * NUM_SHIELDS_PER_ARRAY) + 9] = new Shield(this, Shield::REGULAR); + shields[(x * NUM_SHIELDS_PER_ARRAY) + 9]->SetPosition( + shield_array_start_position + Rml::Vector2f((float)SHIELD_SIZE * 3, (float)SHIELD_SIZE * 2)); + + shield_array_start_position.x += SHIELD_SPACING_X; + } +} + +void Game::InitialiseWave() +{ + // Set up the rows + for (int y = 0; y < NUM_INVADER_ROWS; y++) + { + // Determine invader type + Invader::InvaderType type = Invader::UNKNOWN; + switch (y) + { + case 0: type = Invader::RANK3; break; + case 1: + case 2: type = Invader::RANK2; break; + default: type = Invader::RANK1; break; + } + + // Determine position of top left invader + Rml::Vector2f invader_position((float)(WINDOW_WIDTH - (NUM_INVADERS_PER_ROW * INVADER_SPACING_X)) / 2, + (float)(INVADER_START_Y + (y * INVADER_SPACING_Y))); + + for (int x = 0; x < NUM_INVADERS_PER_ROW; x++) + { + // Determine invader type based on row position + int index = (y * NUM_INVADERS_PER_ROW) + x; + + delete invaders[index]; + invaders[index] = new Invader(this, type, (y * NUM_INVADERS_PER_ROW) + x); + invaders[index]->SetPosition(invader_position); + + // Increase invader position + invader_position.x += INVADER_SPACING_X; + } + } + + // Reset mother ship + delete invaders[MOTHERSHIP]; + invaders[MOTHERSHIP] = new Mothership(this, MOTHERSHIP); + + // Update the move frequency + invader_move_freq = ((float)GameDetails::GetWave() - 100.0f) / 140.0f; + invader_move_freq *= invader_move_freq; +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/Game.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/Game.h new file mode 100644 index 00000000..25f7c1d7 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/Game.h @@ -0,0 +1,94 @@ +#pragma once + +#include +#include + +class Shield; +class Invader; +class Defender; +class Mothership; + +/** + Runs the game. + - Updates the Invader positions, animations and bombs. + - Updates the player position and bullets + */ + +class Game { +public: + Game(); + ~Game(); + + /// Initialise a new game + void Initialise(); + + /// Update the game + void Update(double t); + + /// Render the game + void Render(Rml::RenderManager& render_manager, float dp_ratio); + + /// Access the defender + Defender* GetDefender(); + /// Access the invaders + Invader* GetInvader(int index); + /// Get the total number of invaders + int GetNumInvaders(); + + /// Returns true if the given invader can drop bomb + bool CanDropBomb(int invader_index); + + /// Access the shields. + Shield* GetShield(int index); + /// Get the total number of shields + int GetNumShields(); + + /// Adds a score onto the player's score. + void AddScore(int score); + /// Sets the player's score. + void SetScore(int score); + /// Sets the player's high-score. + void SetHighScore(int score); + /// Set the number of defender lives. + void SetLives(int lives); + /// Set the current wave + void SetWave(int wave); + /// Remove a defender life + void RemoveLife(); + /// Checks if the game is over + bool IsGameOver() const; + + /// Get the dimensions of the game window. + const Rml::Vector2f GetWindowDimensions(); + +private: + // The current invaders + Invader** invaders; + // The direction they're moving + float current_invader_direction; + // Time of the last invader update + double invader_frame_start; + // How often the invaders move + double invader_move_freq; + // Is the game over + bool game_over; + + // Helper function to move the invaders + void MoveInvaders(); + + // Our current defener + Defender* defender; + + // Number of lives the defender has left + int defender_lives; + + // The current shields + Shield** shields; + + // Texture that contains the sprites + Rml::Texture texture; + + void InitialiseShields(); + void InitialiseWave(); + void OnGameOver(); +}; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/GameDetails.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/GameDetails.cpp new file mode 100644 index 00000000..ef207aab --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/GameDetails.cpp @@ -0,0 +1,100 @@ +#include "GameDetails.h" + +static GameDetails::Difficulty difficulty = GameDetails::EASY; +static Rml::Colourb colour = Rml::Colourb(255, 0, 0); +static int score = -1; +static int wave = 0; +static bool paused = false; + +static GameDetails::GraphicsQuality graphics_quality = GameDetails::OK; +static bool reverb = true; +static bool spatialisation = false; + +GameDetails::GameDetails() {} + +GameDetails::~GameDetails() {} + +void GameDetails::SetDifficulty(Difficulty _difficulty) +{ + difficulty = _difficulty; +} + +GameDetails::Difficulty GameDetails::GetDifficulty() +{ + return difficulty; +} + +void GameDetails::SetDefenderColour(const Rml::Colourb& _colour) +{ + colour = _colour; +} + +const Rml::Colourb& GameDetails::GetDefenderColour() +{ + return colour; +} + +void GameDetails::SetScore(int _score) +{ + score = _score; +} + +void GameDetails::ResetScore() +{ + score = -1; +} + +int GameDetails::GetScore() +{ + return score; +} + +void GameDetails::SetWave(int _wave) +{ + wave = _wave; +} + +int GameDetails::GetWave() +{ + return wave; +} + +void GameDetails::SetPaused(bool _paused) +{ + paused = _paused; +} + +bool GameDetails::GetPaused() +{ + return paused; +} + +void GameDetails::SetGraphicsQuality(GraphicsQuality quality) +{ + graphics_quality = quality; +} + +GameDetails::GraphicsQuality GameDetails::GetGraphicsQuality() +{ + return graphics_quality; +} + +void GameDetails::SetReverb(bool enabled) +{ + reverb = enabled; +} + +bool GameDetails::GetReverb() +{ + return reverb; +} + +void GameDetails::Set3DSpatialisation(bool enabled) +{ + spatialisation = enabled; +} + +bool GameDetails::Get3DSpatialisation() +{ + return spatialisation; +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/GameDetails.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/GameDetails.h new file mode 100644 index 00000000..11feb22f --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/GameDetails.h @@ -0,0 +1,72 @@ +#pragma once + +#include + +class GameDetails { +public: + enum Difficulty { EASY, HARD }; + + /// Sets the game difficulty. + /// @param[in] difficulty The new game difficulty. + static void SetDifficulty(Difficulty difficulty); + /// Returns the game difficulty. + /// @return The current game difficulty. + static Difficulty GetDifficulty(); + + /// Sets the colour of the player's ship. + /// @param[in] colour The new ship colour. + static void SetDefenderColour(const Rml::Colourb& colour); + /// Returns the player's ship colour. + /// @return The colour of the player's ship. + static const Rml::Colourb& GetDefenderColour(); + + /// Sets the score the player achieved in the last game. + /// @param[in] score The player's score. + static void SetScore(int score); + /// Resets the player's score. + static void ResetScore(); + /// Returns the score the player achieved in the last game. + /// @return The player's score. + static int GetScore(); + + /// Sets the current wave number + /// @param[in] wave The wave number + static void SetWave(int wave); + /// Get the current wave number + /// @return The wave number + static int GetWave(); + + /// Sets the pauses state of the game + /// @param[in] paused Whether the game is paused or not + static void SetPaused(bool paused); + /// Gets if the game is paused or not + /// @return True if the game is paused, false otherwise. + static bool GetPaused(); + + enum GraphicsQuality { GOOD, OK, BAD }; + + /// Sets the quality of the graphics used in-game. + /// @param[in] quality The new graphics quality. + static void SetGraphicsQuality(GraphicsQuality quality); + /// Returns the quality of the graphics to use in-game. + /// @return The currently-set graphical quality. + static GraphicsQuality GetGraphicsQuality(); + + /// Enables or disables reverb. + /// @param[in] enabled True to enable reverb, false to disable. + static void SetReverb(bool enabled); + /// Returns the current state of reverb. + /// @return The current reverb state. + static bool GetReverb(); + + /// Enables or disables 3D spatialisation in the audio engine. + /// @param[in] enabled True to enable spatialisation, false to disable. + static void Set3DSpatialisation(bool enabled); + /// Returns the current state of audio spatialisation. + /// @return The current audio spatialisation state. + static bool Get3DSpatialisation(); + +private: + GameDetails(); + ~GameDetails(); +}; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/HighScores.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/HighScores.cpp new file mode 100644 index 00000000..0db59e3d --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/HighScores.cpp @@ -0,0 +1,157 @@ +#include "HighScores.h" +#include +#include +#include +#include +#include + +HighScores* HighScores::instance = nullptr; + +HighScores::HighScores(Rml::Context* context) +{ + RMLUI_ASSERT(instance == nullptr); + instance = this; + + Rml::DataModelConstructor constructor = context->CreateDataModel("high_scores"); + if (!constructor) + return; + + if (auto score_handle = constructor.RegisterStruct()) + { + score_handle.RegisterMember("name_required", &Score::name_required); + score_handle.RegisterMember("name", &Score::name); + score_handle.RegisterMember("colour", &Score::GetColour); + score_handle.RegisterMember("wave", &Score::wave); + score_handle.RegisterMember("score", &Score::score); + } + + constructor.RegisterArray(); + + constructor.Bind("scores", &scores); + + model_handle = constructor.GetModelHandle(); + + LoadScores(); +} + +HighScores::~HighScores() +{ + RMLUI_ASSERT(instance == this); + + SaveScores(); + + instance = nullptr; +} + +void HighScores::Initialise(Rml::Context* context) +{ + new HighScores(context); +} + +void HighScores::Shutdown() +{ + delete instance; +} + +int HighScores::GetHighScore() +{ + if (instance->scores.empty()) + return 0; + + return instance->scores[0].score; +} + +void HighScores::SubmitScore(const Rml::String& name, const Rml::Colourb& colour, int wave, int score) +{ + instance->SubmitScore(name, colour, wave, score, false); +} + +void HighScores::SubmitScore(const Rml::Colourb& colour, int wave, int score) +{ + instance->SubmitScore("", colour, wave, score, true); +} + +void HighScores::SubmitName(const Rml::String& name) +{ + for (Score& score : instance->scores) + { + if (score.name_required) + { + score.name = name; + score.name_required = false; + + instance->model_handle.DirtyVariable("scores"); + } + } +} + +void HighScores::SubmitScore(const Rml::String& name, const Rml::Colourb& colour, int wave, int score, bool name_required) +{ + Score entry; + entry.name = name; + entry.colour = colour; + entry.wave = wave; + entry.score = score; + entry.name_required = name_required; + + auto it = std::find_if(scores.begin(), scores.end(), [score](const Score& other) { return score > other.score; }); + + scores.insert(it, std::move(entry)); + + constexpr size_t MaxNumberScores = 10; + + if (scores.size() > MaxNumberScores) + scores.pop_back(); + + model_handle.DirtyVariable("scores"); +} + +void HighScores::LoadScores() +{ + // Open and read the high score file. + FILE* scores_file = fopen("scores.txt", "rt"); + + if (scores_file) + { + char buffer[1024]; + while (fgets(buffer, 1024, scores_file)) + { + Rml::StringList score_parts; + Rml::StringUtilities::ExpandString(score_parts, Rml::String(buffer), '\t'); + if (score_parts.size() == 4) + { + Rml::Colourb colour; + int wave; + int score; + + if (Rml::TypeConverter::Convert(score_parts[1], colour) && + Rml::TypeConverter::Convert(score_parts[2], wave) && + Rml::TypeConverter::Convert(score_parts[3], score)) + { + SubmitScore(score_parts[0], colour, wave, score); + } + } + } + + fclose(scores_file); + } +} + +void HighScores::SaveScores() +{ + FILE* scores_file = fopen("scores.txt", "wt"); + + if (scores_file) + { + for (const Score& score : scores) + { + Rml::String colour_string; + Rml::TypeConverter::Convert(score.colour, colour_string); + + Rml::String score_str = Rml::CreateString("%s\t%s\t%d\t%d\n", score.name.c_str(), colour_string.c_str(), score.wave, score.score); + fputs(score_str.c_str(), scores_file); + } + + fclose(scores_file); + } +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/HighScores.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/HighScores.h new file mode 100644 index 00000000..f81be0fd --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/HighScores.h @@ -0,0 +1,44 @@ +#pragma once + +#include +#include + +class HighScores { +public: + static void Initialise(Rml::Context* context); + static void Shutdown(); + + static int GetHighScore(); + + /// Two functions to add a score to the chart. + /// Adds a full score, including a name. This won't prompt the user to enter their name. + static void SubmitScore(const Rml::String& name, const Rml::Colourb& colour, int wave, int score); + /// Adds a score, and causes an input field to appear to request the user for their name. + static void SubmitScore(const Rml::Colourb& colour, int wave, int score); + /// Sets the name of the last player to submit their score. + static void SubmitName(const Rml::String& name); + +private: + HighScores(Rml::Context* context); + ~HighScores(); + + static HighScores* instance; + + void SubmitScore(const Rml::String& name, const Rml::Colourb& colour, int wave, int score, bool name_required); + void LoadScores(); + void SaveScores(); + + struct Score { + Rml::String name; + bool name_required; + Rml::Colourb colour; + int score; + int wave; + + Rml::String GetColour() { return Rml::ToString(colour); } + }; + using ScoreList = Rml::Vector; + ScoreList scores; + + Rml::DataModelHandle model_handle; +}; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/Invader.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/Invader.cpp new file mode 100644 index 00000000..72f4b667 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/Invader.cpp @@ -0,0 +1,222 @@ +#include "Invader.h" +#include "Defender.h" +#include "Game.h" +#include "GameDetails.h" +#include "Shield.h" +#include "Sprite.h" +#include +#include + +const float BOMB_UPDATE_FREQ = 0.04f; +const float BOMB_RAY_SPEED = 10; +const float BOMB_MISSILE_SPEED = 7; +const float BOMB_PROBABILITY_EASY = 0.002f; +const float BOMB_PROBABILITY_HARD = 0.005f; +const float EXPLOSION_TIME = 0.25f; +const Rml::Colourb MOTHERSHIP_COLOUR = Rml::Colourb(255, 0, 0, 255); + +Sprite invader_sprites[] = { + // Rank 1 + Sprite(Rml::Vector2f(48, 32), Rml::Vector2f(0.609375f, 0), Rml::Vector2f(0.796875f, 0.5f)), + Sprite(Rml::Vector2f(48, 32), Rml::Vector2f(0.80078125f, 0), Rml::Vector2f(0.98828125f, 0.5f)), + // Rank 2 + Sprite(Rml::Vector2f(44, 32), Rml::Vector2f(0.2578125f, 0), Rml::Vector2f(0.4296875f, 0.5f)), + Sprite(Rml::Vector2f(44, 32), Rml::Vector2f(0.43359375f, 0), Rml::Vector2f(0.60546875f, 0.5f)), + // Rank 3 + Sprite(Rml::Vector2f(32, 32), Rml::Vector2f(0, 0), Rml::Vector2f(0.125f, 0.5f)), + Sprite(Rml::Vector2f(32, 32), Rml::Vector2f(0.12890625f, 0), Rml::Vector2f(0.25390625f, 0.5f)), + // Mothership + Sprite(Rml::Vector2f(64, 28), Rml::Vector2f(0.23828125f, 0.515625f), Rml::Vector2f(0.48828125f, 0.953125f)), + // Explosion + Sprite(Rml::Vector2f(52, 28), Rml::Vector2f(0.71484375f, 0.51562500f), Rml::Vector2f(0.91796875f, 0.95312500f))}; + +Sprite bomb_sprites[] = { + // Ray + Sprite(Rml::Vector2f(12, 20), Rml::Vector2f(0.51171875f, 0.51562500f), Rml::Vector2f(0.55859375f, 0.82812500f)), + Sprite(Rml::Vector2f(12, 20), Rml::Vector2f(0.56250000, 0.51562500), Rml::Vector2f(0.60937500, 0.82812500)), + Sprite(Rml::Vector2f(12, 20), Rml::Vector2f(0.61328125, 0.51562500), Rml::Vector2f(0.66015625, 0.82812500)), + Sprite(Rml::Vector2f(12, 20), Rml::Vector2f(0.66406250, 0.51562500), Rml::Vector2f(0.71093750, 0.82812500)), + // Missile + Sprite(Rml::Vector2f(12, 20), Rml::Vector2f(0.92578125, 0.51562500), Rml::Vector2f(0.97265625, 0.82812500))}; + +Invader::Invader(Game* _game, InvaderType _type, int _index) : position(0, 0) +{ + type = UNKNOWN; + animation_frame = 0; + bomb_animation_frame = 0; + bomb_frame_start = 0; + death_time = 0; + state = ALIVE; + bomb = NONE; + game = _game; + type = _type; + death_time = 0; + invader_index = _index; + + bomb_probability = GameDetails::GetDifficulty() == GameDetails::EASY ? BOMB_PROBABILITY_EASY : BOMB_PROBABILITY_HARD; + bomb_probability *= float(type); +} + +Invader::~Invader() {} + +void Invader::SetPosition(const Rml::Vector2f& _position) +{ + position = _position; +} + +const Rml::Vector2f& Invader::GetPosition() const +{ + return position; +} + +void Invader::Update(double t) +{ + // Update the bombs + if (float(t - bomb_frame_start) > BOMB_UPDATE_FREQ) + { + // Update the bomb position if its in flight, or check if we should drop one + if (bomb != NONE) + { + if (bomb == RAY) + { + bomb_animation_frame++; + if (bomb_animation_frame > 3) + bomb_animation_frame = 0; + + bomb_position.y += BOMB_RAY_SPEED; + } + else + { + bomb_position.y += BOMB_MISSILE_SPEED; + } + + if (bomb_position.y > game->GetWindowDimensions().y) + bomb = NONE; + + // Check if we hit the shields + for (int i = 0; i < game->GetNumShields(); i++) + { + if (game->GetShield(i)->CheckHit(bomb_position)) + { + bomb = NONE; + break; + } + } + + // Check if we hit the defender + if (bomb != NONE) + { + if (game->GetDefender()->CheckHit(t, bomb_position)) + bomb = NONE; + } + } + else if (state == ALIVE && Rml::Math::RandomReal(1.0f) < bomb_probability && game->CanDropBomb(invader_index)) + { + bomb = Rml::Math::RandomInteger(2) == 0 ? RAY : MISSILE; + bomb_position = position; + bomb_position.x += invader_sprites[GetSpriteIndex()].dimensions.x / 2; + + if (bomb == RAY) + bomb_animation_frame = 0; + else + bomb_animation_frame = 4; + } + + bomb_frame_start = t; + } + + if (state == EXPLODING && t - death_time > 0.0) + state = DEAD; +} + +void Invader::UpdateAnimation() +{ + switch (state) + { + case ALIVE: + animation_frame++; + if (animation_frame > 1) + animation_frame = 0; + break; + + default: break; + } +} + +void Invader::Render(Rml::RenderManager& render_manager, float dp_ratio, Rml::Texture texture) +{ + Rml::ColourbPremultiplied color(255); + + if (type == MOTHERSHIP) + color = MOTHERSHIP_COLOUR.ToPremultiplied(); + + int sprite_index = GetSpriteIndex(); + int sprite_offset = int((invader_sprites[sprite_index].dimensions.x - 48) / 2); + + if (state != DEAD) + invader_sprites[sprite_index].Render(render_manager, Rml::Vector2f(position.x - sprite_offset, position.y), dp_ratio, color, texture); + + if (bomb != NONE) + bomb_sprites[bomb_animation_frame].Render(render_manager, bomb_position, dp_ratio, color, texture); +} + +Invader::InvaderState Invader::GetState() +{ + return state; +} + +bool Invader::CheckHit(double t, const Rml::Vector2f& check_position) +{ + // Get the sprite index we're currently using for collision detection + int sprite_index = GetSpriteIndex(); + int sprite_offset = int((invader_sprites[sprite_index].dimensions.x - 48) / 2); + float sprite_width = invader_sprites[sprite_index].dimensions.x; + float sprite_height = invader_sprites[sprite_index].dimensions.y; + + // If we're alive and the position is within our bounds, set ourselves + // as exploding and return a valid hit + if (state == ALIVE && check_position.x >= position.x - sprite_offset && check_position.x <= position.x - sprite_offset + sprite_width && + check_position.y >= position.y && check_position.y <= position.y + sprite_height) + { + int score = 0; + switch (type) + { + case MOTHERSHIP: score = (Rml::Math::RandomInteger(6) + 1) * 50; break; // 50 -> 300 + case RANK3: score = 40; break; + case RANK2: score = 20; break; + case RANK1: score = 10; break; + case UNKNOWN: break; + } + + // Add the number of points + game->AddScore(score); + + // Set our state to exploding and start the timer to our doom + state = EXPLODING; + death_time = t + EXPLOSION_TIME; + + return true; + } + + return false; +} + +int Invader::GetSpriteIndex() const +{ + // Calculate our sprite index based on animation and type + int index = animation_frame; + switch (type) + { + case RANK1: break; // animation_frame is the right index already + case RANK2: index += 2; break; + case RANK3: index += 4; break; + case MOTHERSHIP: index = 6; break; + case UNKNOWN: break; + } + + // If we're in exploding state, use the exploding sprite + if (state == EXPLODING) + index = 7; + + return index; +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/Invader.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/Invader.h new file mode 100644 index 00000000..d9bce256 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/Invader.h @@ -0,0 +1,81 @@ +#pragma once + +#include + +class Game; + +/** + An alien invader. + */ + +class Invader { +public: + enum InvaderType { UNKNOWN, RANK1, RANK2, RANK3, MOTHERSHIP }; + enum BombType { NONE, RAY, MISSILE }; + + /// Construct the invader + Invader(Game* game, InvaderType type, int index); + virtual ~Invader(); + + /// Set the invaders screen position + /// @param position Position in screen space + void SetPosition(const Rml::Vector2f& position); + /// Get the current invader position + /// @returns The invaders position in screen space + const Rml::Vector2f& GetPosition() const; + + /// Update the invader + virtual void Update(double t); + + /// Render the invader + void Render(Rml::RenderManager& render_manager, float dp_ratio, Rml::Texture texture); + + /// Update the invaders animation + void UpdateAnimation(); + + /// The current invaders state + enum InvaderState { ALIVE, EXPLODING, DEAD }; + /// Get the current invader state + /// @returns Invader state + InvaderState GetState(); + + /// Returns true if the position hits the invader + /// If a hit is detected, will explode and start the death timer + /// @param position Position to do the hit check at + /// @returns If the invader was hit + bool CheckHit(double t, const Rml::Vector2f& position); + +protected: + // Game this invader is in + Game* game; + // The index/id of this invader + int invader_index; + + // The invader type we represent + InvaderType type; + // The current position in screen space of the invader + Rml::Vector2f position; + // Our current animation frame + int animation_frame; + + // Our current state + InvaderState state; + + // Our current in-flight bomb, or none. (may be not none if we're dead.) + BombType bomb; + // The current position of the bomb in screen space + Rml::Vector2f bomb_position; + // The animation frame the bomb is on + int bomb_animation_frame; + // When the last bomb update occured + double bomb_frame_start; + // Probability of us dropping a bomb - this is calculated + // at construction time and based on our rank and the game + // difficulty level + float bomb_probability; + + // Time when we should die - 0, until we're hit + double death_time; + + int GetSpriteIndex() const; +}; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/LuaInterface.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/LuaInterface.cpp new file mode 100644 index 00000000..1c48b65d --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/LuaInterface.cpp @@ -0,0 +1,122 @@ +#include "LuaInterface.h" +#include "ElementGameInstancer.h" +#include "Game.h" +#include "GameDetails.h" +#include "HighScores.h" +#include +#include +#include +#include +#include +#include +#include + +/* + * We have to create the binding ourselves, and these are the functions that will be + * called. It has to match the function signature of int (*ftnptr)(lua_State*). + */ +int GameShutdown(lua_State* L); +int GameSetPaused(lua_State* L); +int GameSetDifficulty(lua_State* L); +int GameSetDefenderColour(lua_State* L); +int GameSubmitHighScore(lua_State* L); +int GameSetHighScoreName(lua_State* L); + +static ElementGameInstancer game_instancer; + +void LuaInterface::Initialise(lua_State* L) +{ + InitGame(L); + Rml::Factory::RegisterElementInstancer("game", &game_instancer); +} + +void LuaInterface::InitGame(lua_State* L) +{ + luaL_dostring(L, "Game = Game or {}"); // doing this in Lua because it would be 10+ lines in C++ + lua_getglobal(L, "Game"); + int game = lua_gettop(L); + + if (lua_isnil(L, game)) + { + Rml::Log::Message(Rml::Log::LT_ERROR, "Error creating the Game table from C++ in LuaInterface::InitGame"); + return; + } + + // this can be done in a loop similar to the implementation of LuaType::_regfunctions, but I am + // defining them explicitly so that there is no black magic + lua_pushcfunction(L, GameShutdown); + lua_setfield(L, game, "Shutdown"); + + lua_pushcfunction(L, GameSetPaused); + lua_setfield(L, game, "SetPaused"); + + lua_pushcfunction(L, GameSetDifficulty); + lua_setfield(L, game, "SetDifficulty"); + + lua_pushcfunction(L, GameSetDefenderColour); + lua_setfield(L, game, "SetDefenderColour"); + + lua_pushcfunction(L, GameSubmitHighScore); + lua_setfield(L, game, "SubmitHighScore"); + + lua_pushcfunction(L, GameSetHighScoreName); + lua_setfield(L, game, "SetHighScoreName"); + + lua_newtable(L); // table, Game + lua_pushinteger(L, GameDetails::HARD); // int,table,Game + lua_setfield(L, -2, "HARD"); // table,Game + + lua_pushinteger(L, GameDetails::EASY); // int,table,Game + lua_setfield(L, -2, "EASY"); // table,Game + + lua_setfield(L, game, "difficulty"); // Game + + lua_pop(L, 1); // pop Game +} + +int GameShutdown(lua_State* /*L*/) +{ + Backend::RequestExit(); + return 0; +} + +int GameSetPaused(lua_State* L) +{ + bool paused = RMLUI_CHECK_BOOL(L, 1); // RMLUI_CHECK_BOOL defined in LuaType.h + GameDetails::SetPaused(paused); + return 0; +} + +int GameSetDifficulty(lua_State* L) +{ + int difficulty = (int)luaL_checkinteger(L, 1); + GameDetails::SetDifficulty((GameDetails::Difficulty)difficulty); + return 0; +} + +int GameSetDefenderColour(lua_State* L) +{ + Rml::Colourb* colour = Rml::Lua::LuaType::check(L, 1); + GameDetails::SetDefenderColour(*colour); + return 0; +} + +int GameSubmitHighScore(lua_State* /*L*/) +{ + int score = GameDetails::GetScore(); + if (score > 0) + { + // Submit the score the player just got to the high scores chart. + HighScores::SubmitScore(GameDetails::GetDefenderColour(), GameDetails::GetWave(), GameDetails::GetScore()); + // Reset the score so the chart won't get confused next time we enter. + GameDetails::ResetScore(); + } + return 0; +} + +int GameSetHighScoreName(lua_State* L) +{ + const char* name = luaL_checkstring(L, 1); + HighScores::SubmitName(name); + return 0; +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/LuaInterface.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/LuaInterface.h new file mode 100644 index 00000000..93a9da5d --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/LuaInterface.h @@ -0,0 +1,23 @@ +#pragma once +/* + This will define the "Game" global table in Lua and some functions with it. + + In Lua, the skeleton definition of Game with the fake function definitions (because it calls c++ code) would look something like + + Game = Game or {} --so if something else made a "Game" table, we would save the previous table, and just add on to it + Game.Shutdown = function() Shell::RequestExit() end + Game.SetPaused = function(paused) GameDetails::SetPaused(paused) end --where paused is a bool + Game.SetDifficulty = function(difficulty) GameDetails::SetDifficulty(difficulty) end --difficulty is a value from Game.difficulty + Game.SetDefenderColour = function(colour) GameDetails::SetDefenderColour(colour) end --colour is of type Colourb + Game.SubmitHighScore = function() HighScores::SubmitScore(stuff from GameDetails) end + Game.SetHighScoreName = function(name) HighScore::SubmitName(name) end -- name is a string + Game.difficulty = { "HARD" = GameDetails::HARD, "EASY" = GameDetails::EASY } +*/ + +struct lua_State; +class Game; +class LuaInterface { +public: + static void Initialise(lua_State* L); + static void InitGame(lua_State* L); +}; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/Mothership.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/Mothership.cpp new file mode 100644 index 00000000..249ce71e --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/Mothership.cpp @@ -0,0 +1,54 @@ +#include "Mothership.h" +#include "Game.h" +#include "Shell.h" +#include "Sprite.h" +#include + +const int SPRITE_WIDTH = 64; + +const float APPEARANCE_PROBABILITY = 0.001f; +const double UPDATE_FREQ = 0.025; +const float MOVEMENT_SPEED = 5; + +Mothership::Mothership(Game* game, int index) : Invader(game, Invader::MOTHERSHIP, index) +{ + // Start off dead, and set up our position + state = DEAD; + update_frame_start = 0; + direction = 0; + position = Rml::Vector2f(-SPRITE_WIDTH, 64.0f); +} + +Mothership::~Mothership() {} + +void Mothership::Update(double t) +{ + // Generic Invader update + Invader::Update(t); + + if (t - update_frame_start < UPDATE_FREQ) + return; + + // We're alive, keep moving! + if (state == ALIVE) + { + position.x += (direction * MOVEMENT_SPEED); + + if ((direction < 0.0f && position.x < -SPRITE_WIDTH) || (direction > 0.0f && position.x > game->GetWindowDimensions().x)) + state = DEAD; + + update_frame_start = t; + } + // Determine if we should come out of hiding + else if (Rml::Math::RandomReal(1.0f) < APPEARANCE_PROBABILITY) + { + direction = Rml::Math::RandomReal(1.0f) < 0.5 ? -1.0f : 1.0f; + + if (direction < 0) + position.x = game->GetWindowDimensions().x + SPRITE_WIDTH; + else + position.x = -SPRITE_WIDTH; + + state = ALIVE; + } +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/Mothership.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/Mothership.h new file mode 100644 index 00000000..d5d0908e --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/Mothership.h @@ -0,0 +1,19 @@ +#pragma once + +#include "Invader.h" + +class Mothership : public Invader { +public: + Mothership(Game* game, int index); + ~Mothership(); + + /// Update the mothership + void Update(double t) override; + +private: + // Time of the last update + double update_frame_start; + + // Direction mothership is flying in + float direction; +}; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/Shield.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/Shield.cpp new file mode 100644 index 00000000..9da5f71d --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/Shield.cpp @@ -0,0 +1,154 @@ +#include "Shield.h" +#include "Game.h" +#include "GameDetails.h" +#include "Sprite.h" +#include + +const int MAX_HEALTH = 4; + +Shield::Shield(Game* _game, ShieldType _type) : position(0, 0) +{ + game = _game; + type = _type; + health = MAX_HEALTH; + + InitialiseCells(); +} + +Shield::~Shield() {} + +void Shield::InitialiseCells() +{ + if (type == REGULAR || type == TOP_LEFT || type == TOP_RIGHT) + { + for (int i = 0; i < NUM_SHIELD_CELLS; i++) + { + for (int j = 0; j < NUM_SHIELD_CELLS; j++) + { + shield_cells[i][j] = ON; + } + } + + // Take the bites out of the cells if they're a corner cell: + if (type == TOP_LEFT) + { + for (int x = 0; x < NUM_SHIELD_CELLS - 1; x++) + { + for (int y = 0; y < (NUM_SHIELD_CELLS - 1) - x; y++) + shield_cells[x][y] = OFF; + } + } + + else if (type == TOP_RIGHT) + { + for (int x = 0; x < NUM_SHIELD_CELLS - 1; x++) + { + for (int y = 0; y < (NUM_SHIELD_CELLS - 1) - x; y++) + shield_cells[(NUM_SHIELD_CELLS - 1) - x][y] = OFF; + } + } + } + else + { + for (int i = 0; i < NUM_SHIELD_CELLS; i++) + { + for (int j = 0; j < NUM_SHIELD_CELLS; j++) + { + shield_cells[i][j] = OFF; + } + } + + if (type == BOTTOM_LEFT) + { + for (int x = 0; x < NUM_SHIELD_CELLS - 1; x++) + { + for (int y = 0; y < (NUM_SHIELD_CELLS - 1) - x; y++) + shield_cells[(NUM_SHIELD_CELLS - 1) - x][y] = ON; + } + } + + else if (type == BOTTOM_RIGHT) + { + for (int x = 0; x < NUM_SHIELD_CELLS - 1; x++) + { + for (int y = 0; y < (NUM_SHIELD_CELLS - 1) - x; y++) + shield_cells[x][y] = ON; + } + } + } +} + +void Shield::SetPosition(const Rml::Vector2f& _position) +{ + position = _position; +} + +const Rml::Vector2f& Shield::GetPosition() const +{ + return position; +} + +void Shield::Render(Rml::RenderManager& render_manager, float dp_ratio) +{ + if (health > 0) + { + const Rml::Vector2f scaled_position = (dp_ratio * position).Round(); + const int scaled_pixel = Rml::Math::RoundUpToInteger(PIXEL_SIZE * dp_ratio); + + Rml::ColourbPremultiplied color = GameDetails::GetDefenderColour().ToPremultiplied(); + ColoredPointList points; + points.reserve(NUM_SHIELD_CELLS * NUM_SHIELD_CELLS); + + for (int i = 0; i < NUM_SHIELD_CELLS; i++) + { + for (int j = 0; j < NUM_SHIELD_CELLS; j++) + { + if (shield_cells[i][j] == ON) + { + Rml::Vector2f cell_position = scaled_position + Rml::Vector2f(float(scaled_pixel * i), float(scaled_pixel * j)); + points.push_back(ColoredPoint{color, cell_position}); + } + } + } + + DrawPoints(render_manager, (float)scaled_pixel, points); + } +} + +bool Shield::CheckHit(const Rml::Vector2f& check_position) +{ + float sprite_size = PIXEL_SIZE * NUM_SHIELD_CELLS; + + // If we're alive and the position is within our bounds, set ourselves + // as exploding and return a valid hit + if (health > 0 && check_position.x >= position.x && check_position.x <= position.x + sprite_size && check_position.y >= position.y && + check_position.y <= position.y + sprite_size) + { + // Take damage. + SustainDamage(); + + return true; + } + + return false; +} + +void Shield::SustainDamage() +{ + health--; + + if (health > 0) + { + int num_shields_to_lose = (NUM_SHIELD_CELLS * NUM_SHIELD_CELLS) / MAX_HEALTH; + while (num_shields_to_lose > 0) + { + int x = Rml::Math::RandomInteger(NUM_SHIELD_CELLS); + int y = Rml::Math::RandomInteger(NUM_SHIELD_CELLS); + if (shield_cells[x][y] != DESTROYED) + { + shield_cells[x][y] = DESTROYED; + num_shields_to_lose--; + } + } + } +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/Shield.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/Shield.h new file mode 100644 index 00000000..68618393 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/Shield.h @@ -0,0 +1,57 @@ +#pragma once + +#include + +class Game; + +const int NUM_SHIELD_CELLS = 6; +const int PIXEL_SIZE = 4; + +/** + A shield, protecting the player. + */ + +class Shield { +public: + enum ShieldType { REGULAR, TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT }; + + /// Construct the invader + Shield(Game* game, ShieldType type); + ~Shield(); + + /// Sets up which cells are on and off, based on the type. + void InitialiseCells(); + + /// Set the shield's screen position + /// @param position Position in screen space + void SetPosition(const Rml::Vector2f& position); + /// Get the current shield position + /// @returns The shield's position in screen space + const Rml::Vector2f& GetPosition() const; + + /// Render the shield. + void Render(Rml::RenderManager& render_manager, float dp_ratio); + + /// Returns true if the position hits the shield + /// If a hit is detected, will degrade the shield. + /// @param position Position to do the hit check at + /// @returns If the shield was hit + bool CheckHit(const Rml::Vector2f& position); + +protected: + void SustainDamage(); + + // Game this shield is in + Game* game; + + // The invader type we represent + ShieldType type; + // The current position in screen space of the shield + Rml::Vector2f position; + + // Our current state - starts at 4, degrades once per hit. + int health; + + enum ShieldCellState { ON, OFF, DESTROYED }; + ShieldCellState shield_cells[NUM_SHIELD_CELLS][NUM_SHIELD_CELLS]; +}; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/Sprite.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/Sprite.cpp new file mode 100644 index 00000000..e184be26 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/Sprite.cpp @@ -0,0 +1,48 @@ +#include "Sprite.h" +#include +#include +#include +#include +#include + +Sprite::Sprite(const Rml::Vector2f& dimensions, const Rml::Vector2f& top_left_texcoord, const Rml::Vector2f& bottom_right_texcoord) : + dimensions(dimensions), top_left_texcoord(top_left_texcoord), bottom_right_texcoord(bottom_right_texcoord) +{} + +Sprite::~Sprite() {} + +void Sprite::Render(Rml::RenderManager& render_manager, Rml::Vector2f position, const float dp_ratio, Rml::ColourbPremultiplied color, + Rml::Texture texture) +{ + position = dp_ratio * position; + Rml::Vector2f dimensions_px = dp_ratio * dimensions; + Rml::Math::SnapToPixelGrid(position, dimensions_px); + + Rml::Mesh mesh; + Rml::MeshUtilities::GenerateQuad(mesh, Rml::Vector2f(0.f), dimensions_px, color, top_left_texcoord, bottom_right_texcoord); + + Rml::Geometry geometry = render_manager.MakeGeometry(std::move(mesh)); + geometry.Render(position, texture); +} + +void DrawPoints(Rml::RenderManager& render_manager, float point_size, const ColoredPointList& points) +{ + constexpr int num_quad_vertices = 4; + constexpr int num_quad_indices = 6; + + const int num_points = (int)points.size(); + + Rml::Mesh mesh; + mesh.vertices.reserve(num_points * num_quad_vertices); + mesh.indices.reserve(num_points * num_quad_indices); + + for (const ColoredPoint& point : points) + { + Rml::Vector2f position = point.position; + Rml::Vector2f size = Rml::Vector2f(point_size); + Rml::MeshUtilities::GenerateQuad(mesh, position, size, point.color); + } + + Rml::Geometry geometry = render_manager.MakeGeometry(std::move(mesh)); + geometry.Render(Rml::Vector2f(0.f)); +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/Sprite.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/Sprite.h new file mode 100644 index 00000000..778cf9d3 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/Sprite.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +class Sprite { +public: + Sprite(const Rml::Vector2f& dimensions, const Rml::Vector2f& top_left_texcoord, const Rml::Vector2f& bottom_right_texcoord); + ~Sprite(); + + void Render(Rml::RenderManager& render_manager, Rml::Vector2f position, float dp_ratio, Rml::ColourbPremultiplied color, Rml::Texture texture); + + Rml::Vector2f dimensions; + Rml::Vector2f top_left_texcoord; + Rml::Vector2f bottom_right_texcoord; +}; + +struct ColoredPoint { + Rml::ColourbPremultiplied color; + Rml::Vector2f position; +}; +using ColoredPointList = Rml::Vector; + +void DrawPoints(Rml::RenderManager& render_manager, float point_size, const ColoredPointList& points); diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/main.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/main.cpp new file mode 100644 index 00000000..57479cd0 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/lua_invaders/src/main.cpp @@ -0,0 +1,96 @@ +#include "DecoratorDefender.h" +#include "DecoratorStarfield.h" +#include "ElementGame.h" +#include "HighScores.h" +#include "LuaInterface.h" +#include +#include +#include +#include +#include + +Rml::Context* context = nullptr; + +#if defined RMLUI_PLATFORM_WIN32 + #include +int APIENTRY WinMain(HINSTANCE /*instance_handle*/, HINSTANCE /*previous_instance_handle*/, char* /*command_line*/, int /*command_show*/) +#else +int main(int /*argc*/, char** /*argv*/) +#endif +{ + int window_width = 1024; + int window_height = 768; + + // Initializes the shell which provides common functionality used by the included samples. + if (!Shell::Initialize()) + return -1; + + // Constructs the system and render interfaces, creates a window, and attaches the renderer. + if (!Backend::Initialize("RmlUi Invaders from Mars (Lua Powered)", window_width, window_height, false)) + { + Shell::Shutdown(); + return -1; + } + + // Install the custom interfaces constructed by the backend before initializing RmlUi. + Rml::SetSystemInterface(Backend::GetSystemInterface()); + Rml::SetRenderInterface(Backend::GetRenderInterface()); + + // RmlUi initialisation. + Rml::Initialise(); + + // Initialise the Lua interface + Rml::Lua::Initialise(); + + // Create the main RmlUi context. + context = Rml::CreateContext("main", Rml::Vector2i(window_width, window_height)); + if (context == nullptr) + { + Rml::Shutdown(); + Backend::Shutdown(); + Shell::Shutdown(); + return -1; + } + + // Initialise the RmlUi debugger. + Rml::Debugger::Initialise(context); + + // Load the font faces required for Invaders. + Shell::LoadFonts(); + + // Register Invader's custom decorator instancers. + DecoratorInstancerStarfield decorator_starfield; + DecoratorInstancerDefender decorator_defender; + Rml::Factory::RegisterDecoratorInstancer("starfield", &decorator_starfield); + Rml::Factory::RegisterDecoratorInstancer("defender", &decorator_defender); + + // Construct the game singletons. + HighScores::Initialise(context); + + // Fire off the startup script. + LuaInterface::Initialise(Rml::Lua::Interpreter::GetLuaState()); // the tables/functions defined in the samples + Rml::Lua::Interpreter::LoadFile(Rml::String("lua_invaders/lua/start.lua")); + + bool running = true; + while (running) + { + running = Backend::ProcessEvents(context, &Shell::ProcessKeyDownShortcuts); + + context->Update(); + + Backend::BeginFrame(); + context->Render(); + Backend::PresentFrame(); + } + + // Shut down the game singletons. + HighScores::Shutdown(); + + // Shutdown RmlUi. + Rml::Shutdown(); + + Backend::Shutdown(); + Shell::Shutdown(); + + return 0; +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/readme.md b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/readme.md new file mode 100644 index 00000000..79970155 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/readme.md @@ -0,0 +1,47 @@ +## RmlUi Sample Applications + +This directory contains a collection of sample applications that demonstrate the use of RmlUi in small, easy to understand applications. + +### Directory Overview + +#### `assets` + +This directory contains the assets shared by all the sample applications. + +#### `basic` + +This directory contains basic applications that demonstrate initialisation, usage, shutdown and installation of custom interfaces. + +- `animation` Animations and transitions. +- `benchmark` A benchmark to measure performance. +- `bitmap_font` Using a custom font engine. Available even without FreeType, i.e. `RMLUI_FONT_ENGINE="none"`. +- `custom_log` Setting up custom logging. +- `data_binding` Setting up and using data bindings. +- `demo` Demonstrates a variety of features in RmlUi and includes a sandbox for playing with RML/RCSS. +- `drag` Dragging elements between containers. +- `effects` Advanced rendering effects, including filters, gradients and box shadows. Only enabled with supported backends. +- `harfbuzz` Advanced text shaping. Only enabled when [HarfBuzz](https://harfbuzz.github.io/) is enabled. +- `ime` A showcase of Input Method Editor (IME) with fallback fonts to support different writing systems. Available only when using a Windows backend. +- `load_document` Loading your first document. +- `lottie` Playing Lottie animations, only enabled with the [Lottie plugin](https://mikke89.github.io/RmlUiDoc/pages/cpp_manual/lottie.html). +- `svg` Render SVG images, only enabled with the [SVG plugin](https://mikke89.github.io/RmlUiDoc/pages/cpp_manual/svg.html). +- `transform` Demonstration of transforms. +- `tree_view` Using data bindings to create a file browser. + +#### `invaders` + +A full implementation of the 1970s classic Space Invaders using the RmlUi interface. + +#### `lua_invaders` + +Lua version of the invaders sample. Only installed with the Lua plugin. + +#### `shell` + +The shell provides some common functionality that are specific to the included samples and tests, such as loading fonts and handling global keyboard shortcuts. + +Note that, the code for rendering, opening and closing windows, and providing inputs, is instead located in the [backends](../Backends/), however some extensions to these backends are found in the shell. + +#### `tutorial` + +Tutorial code that should be used in conjunction with the tutorials in the [RmlUi documentation](https://mikke89.github.io/RmlUiDoc/). diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/shell/CMakeLists.txt b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/shell/CMakeLists.txt new file mode 100644 index 00000000..4fd27484 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/shell/CMakeLists.txt @@ -0,0 +1,39 @@ +#[[ + Note: rmlui_shell is NOT a sample. It's a utility static library with code common to the RmlUi samples. +]] + +add_library(rmlui_shell STATIC + src/PlatformExtensions.cpp + src/RendererExtensions.cpp + src/Shell.cpp + src/ShellFileInterface.cpp + + include/PlatformExtensions.h + include/RendererExtensions.h + include/Shell.h + include/ShellFileInterface.h +) + +set_common_target_options(rmlui_shell) + +target_link_libraries(rmlui_shell PRIVATE rmlui_backend_${RMLUI_BACKEND}) + +target_include_directories(rmlui_shell PUBLIC "include" "${PROJECT_SOURCE_DIR}/Backends") + +if(CMAKE_SYSTEM_NAME STREQUAL "Windows") + target_link_libraries(rmlui_shell PRIVATE Windows::Shell::LightweightUtility) +elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + target_link_libraries(rmlui_shell PRIVATE macOS::Cocoa) +endif() + +target_link_libraries(rmlui_shell PUBLIC rmlui_core rmlui_debugger) + +if(RMLUI_BACKEND MATCHES "GL2$") + target_compile_definitions(rmlui_shell PRIVATE "RMLUI_RENDERER_GL2") +endif() +if(RMLUI_BACKEND MATCHES "GL3$") + target_compile_definitions(rmlui_shell PRIVATE "RMLUI_RENDERER_GL3") +endif() +if(RMLUI_BACKEND MATCHES "DX12$") + target_compile_definitions(rmlui_shell PRIVATE "RMLUI_RENDERER_DX12") +endif() diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/shell/include/PlatformExtensions.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/shell/include/PlatformExtensions.h new file mode 100644 index 00000000..f76d9327 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/shell/include/PlatformExtensions.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +namespace PlatformExtensions { + +Rml::String FindSamplesRoot(); + +Rml::StringList ListDirectories(const Rml::String& in_directory); +Rml::StringList ListFiles(const Rml::String& in_directory, const Rml::String& extension = Rml::String()); + +} // namespace PlatformExtensions diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/shell/include/RendererExtensions.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/shell/include/RendererExtensions.h new file mode 100644 index 00000000..951e3c22 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/shell/include/RendererExtensions.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +namespace RendererExtensions { + +// Extensions used by the test suite +struct Image { + int width = 0; + int height = 0; + int num_components = 0; + Rml::UniquePtr data; +}; +Image CaptureScreen(); + +} // namespace RendererExtensions diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/shell/include/Shell.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/shell/include/Shell.h new file mode 100644 index 00000000..57e2cde4 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/shell/include/Shell.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include +#include + +/** + Provides common functionality required for the built-in RmlUi samples. + */ +namespace Shell { + +// Initializes and sets a custom file interface used for locating the included RmlUi asset files. +bool Initialize(); +// Destroys all resources constructed by the shell. +void Shutdown(); + +// Loads the fonts included with the RmlUi samples. +void LoadFonts(); + +// Process key down events to handle shortcuts common to all samples. +// @return True if the event is still propagating, false if it was handled here. +bool ProcessKeyDownShortcuts(Rml::Context* context, Rml::Input::KeyIdentifier key, int key_modifier, float native_dp_ratio, bool priority); + +} // namespace Shell diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/shell/include/ShellFileInterface.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/shell/include/ShellFileInterface.h new file mode 100644 index 00000000..1d418a8b --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/shell/include/ShellFileInterface.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include + +/** + RmlUi file interface for the shell examples. + */ + +class ShellFileInterface : public Rml::FileInterface { +public: + ShellFileInterface(const Rml::String& root); + virtual ~ShellFileInterface(); + + /// Opens a file. + Rml::FileHandle Open(const Rml::String& path) override; + + /// Closes a previously opened file. + void Close(Rml::FileHandle file) override; + + /// Reads data from a previously opened file. + size_t Read(void* buffer, size_t size, Rml::FileHandle file) override; + + /// Seeks to a point in a previously opened file. + bool Seek(Rml::FileHandle file, long offset, int origin) override; + + /// Returns the current position of the file pointer. + size_t Tell(Rml::FileHandle file) override; + +private: + Rml::String root; +}; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/shell/src/PlatformExtensions.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/shell/src/PlatformExtensions.cpp new file mode 100644 index 00000000..53b18608 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/shell/src/PlatformExtensions.cpp @@ -0,0 +1,262 @@ +#include "../include/PlatformExtensions.h" +#include +#include + +#if defined RMLUI_PLATFORM_WIN32 + + #include + #include + #include + +#elif defined RMLUI_PLATFORM_MACOSX + + #include + #include + #include + +#elif defined RMLUI_PLATFORM_UNIX + + #include + #include + #include + #include + #include + #include + +#endif + +Rml::String PlatformExtensions::FindSamplesRoot() +{ +#ifdef RMLUI_PLATFORM_WIN32 + // Test various relative paths to the "Samples" directory, based on common build and install locations. + const char* candidate_paths[] = { + ".\\", + "Samples\\", + "..\\Samples\\", + "..\\share\\Samples\\", + "..\\..\\Samples\\", + "..\\..\\..\\Samples\\", + "..\\..\\..\\..\\Samples\\", + }; + + char path_buffer[MAX_PATH]; + + // Fetch the path of the executable. + if (GetModuleFileNameA(NULL, path_buffer, MAX_PATH) >= MAX_PATH && GetLastError() == ERROR_INSUFFICIENT_BUFFER) + return {}; + Rml::String executable_directory_path = Rml::String(path_buffer); + executable_directory_path = executable_directory_path.substr(0, executable_directory_path.rfind('\\') + 1); + + // We assume we have found the correct path if we can find the lookup file from it. + const char* lookup_file = "assets\\rml.rcss"; + + // Test the candidate paths relative to the executable folder, and the current working directory, respectively. + for (const Rml::String relative_target_path : candidate_paths) + { + const Rml::String absolute_target_path = executable_directory_path + relative_target_path; + const Rml::String absolute_lookup_path = absolute_target_path + lookup_file; + if (PathFileExistsA(absolute_lookup_path.c_str())) + { + if (!PathCanonicalizeA(path_buffer, absolute_target_path.c_str())) + { + Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to canonicalize the path to the samples root: %s", absolute_target_path.c_str()); + return {}; + } + + return Rml::String(path_buffer); + } + + const Rml::String relative_lookup_path = relative_target_path + lookup_file; + if (PathFileExistsA(relative_lookup_path.c_str())) + { + const DWORD working_directory_length = GetFullPathNameA(relative_target_path.c_str(), MAX_PATH, path_buffer, nullptr); + if (working_directory_length <= 0 || working_directory_length >= MAX_PATH) + { + Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to get the full path to the samples root: %s", relative_target_path.c_str()); + return {}; + } + + return Rml::String(path_buffer); + } + } + + Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to find the path to the samples root"); + + return Rml::String(); + +#elif defined RMLUI_PLATFORM_MACOSX + + Rml::String path = "../Samples/"; + + // Find the location of the executable. + CFBundleRef bundle = CFBundleGetMainBundle(); + CFURLRef executable_url = CFBundleCopyExecutableURL(bundle); + CFStringRef executable_posix_file_name = CFURLCopyFileSystemPath(executable_url, kCFURLPOSIXPathStyle); + CFIndex max_length = CFStringGetMaximumSizeOfFileSystemRepresentation(executable_posix_file_name); + char* executable_file_name = new char[max_length]; + if (!CFStringGetFileSystemRepresentation(executable_posix_file_name, executable_file_name, max_length)) + executable_file_name[0] = 0; + + Rml::String executable_path = Rml::String(executable_file_name); + executable_path = executable_path.substr(0, executable_path.rfind("/") + 1); + + delete[] executable_file_name; + CFRelease(executable_posix_file_name); + CFRelease(executable_url); + + return executable_path + "../../../" + path; + +#elif defined RMLUI_PLATFORM_EMSCRIPTEN + + return Rml::String("Samples/"); + +#elif defined RMLUI_PLATFORM_UNIX + + char path_buffer[PATH_MAX + 1]; + ssize_t len = readlink("/proc/self/exe", path_buffer, PATH_MAX); + if (len == -1) + { + Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to determine the executable path"); + path_buffer[0] = 0; + } + else + { + // readlink() does not append a null byte to buf. + path_buffer[len] = 0; + } + Rml::String executable_directory_path = Rml::String(path_buffer); + executable_directory_path = executable_directory_path.substr(0, executable_directory_path.rfind("/") + 1); + + // We assume we have found the correct path if we can find the lookup file from it. + const char* lookup_file = "assets/rml.rcss"; + + // Test various relative paths to the "Samples" directory, based on common build and install locations. + const char* candidate_paths[] = { + "./", + "Samples/", + "../", + "../Samples/", + "../share/Samples/", + "../../Samples/", + "../../../Samples/", + "../../../../Samples/", + }; + + auto isRegularFile = [](const Rml::String& path) -> bool { + struct stat sb; + return stat(path.c_str(), &sb) == 0 && S_ISREG(sb.st_mode); + }; + auto GetAbsoluteFilePath = [&](const Rml::String& path) -> Rml::String { + const char* absolute_path = realpath(path.c_str(), path_buffer); + if (!absolute_path) + { + Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to canonicalize the path to the samples root: %s", path.c_str()); + return {}; + } + return Rml::String(absolute_path) + '/'; + }; + + for (const Rml::String relative_target_path : candidate_paths) + { + const Rml::String absolute_target_path = executable_directory_path + relative_target_path; + const Rml::String absolute_lookup_path = absolute_target_path + lookup_file; + if (isRegularFile(absolute_lookup_path)) + return GetAbsoluteFilePath(absolute_target_path); + + const Rml::String relative_lookup_path = relative_target_path + lookup_file; + if (isRegularFile(relative_lookup_path)) + return GetAbsoluteFilePath(relative_target_path); + } + + Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to find the path to the samples root"); + + return Rml::String(); + +#else + + return Rml::String(); + +#endif +} + +enum class ListType { Files, Directories }; + +static Rml::StringList ListFilesOrDirectories(ListType type, const Rml::String& directory, const Rml::String& extension) +{ + if (directory.empty()) + return Rml::StringList(); + + Rml::StringList result; + +#ifdef RMLUI_PLATFORM_WIN32 + + const Rml::String find_path = directory + "/*." + (extension.empty() ? Rml::String("*") : extension); + + _finddata_t find_data; + intptr_t find_handle = _findfirst(find_path.c_str(), &find_data); + if (find_handle != -1) + { + do + { + if (strcmp(find_data.name, ".") == 0 || strcmp(find_data.name, "..") == 0) + continue; + + bool is_directory = ((find_data.attrib & _A_SUBDIR) == _A_SUBDIR); + bool is_file = (!is_directory && ((find_data.attrib & _A_NORMAL) == _A_NORMAL)); + + if (((type == ListType::Files) && is_file) || ((type == ListType::Directories) && is_directory)) + { + result.push_back(find_data.name); + } + + } while (_findnext(find_handle, &find_data) == 0); + + _findclose(find_handle); + } + +#else + + struct dirent** file_list = nullptr; + const int file_count = scandir(directory.c_str(), &file_list, 0, alphasort); + if (file_count == -1) + return Rml::StringList(); + + for (int i = 0; i < file_count; i++) + { + if (strcmp(file_list[i]->d_name, ".") == 0 || strcmp(file_list[i]->d_name, "..") == 0) + continue; + + bool is_directory = ((file_list[i]->d_type & DT_DIR) == DT_DIR); + bool is_file = ((file_list[i]->d_type & DT_REG) == DT_REG); + + if (!extension.empty()) + { + const char* last_dot = strrchr(file_list[i]->d_name, '.'); + if (!last_dot || strcmp(last_dot + 1, extension.c_str()) != 0) + continue; + } + + if ((type == ListType::Files && is_file) || (type == ListType::Directories && is_directory)) + { + result.push_back(file_list[i]->d_name); + } + } + for (int i = 0; i < file_count; i++) + free(file_list[i]); + + free(file_list); + +#endif + + return result; +} + +Rml::StringList PlatformExtensions::ListDirectories(const Rml::String& in_directory) +{ + return ListFilesOrDirectories(ListType::Directories, in_directory, Rml::String()); +} + +Rml::StringList PlatformExtensions::ListFiles(const Rml::String& in_directory, const Rml::String& extension) +{ + return ListFilesOrDirectories(ListType::Files, in_directory, extension); +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/shell/src/RendererExtensions.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/shell/src/RendererExtensions.cpp new file mode 100644 index 00000000..8e5b4475 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/shell/src/RendererExtensions.cpp @@ -0,0 +1,99 @@ +#include "../include/RendererExtensions.h" +#include +#include +#include +#include + +#if defined RMLUI_RENDERER_GL2 + + #if defined RMLUI_PLATFORM_WIN32 + #include + #include + #elif defined RMLUI_PLATFORM_MACOSX + #include + #include + #include + #elif defined RMLUI_PLATFORM_UNIX + #include + #include + #include + #include + #endif + +#elif defined RMLUI_RENDERER_GL3 && !defined RMLUI_PLATFORM_EMSCRIPTEN + + #include + +#elif defined RMLUI_RENDERER_GL3 && defined RMLUI_PLATFORM_EMSCRIPTEN + + #include + +#elif defined RMLUI_RENDERER_DX12 + + #include + +#endif + +#if defined RMLUI_RENDERER_GL2 || defined RMLUI_RENDERER_GL3 + +RendererExtensions::Image RendererExtensions::CaptureScreen() +{ + int viewport[4] = {}; // x, y, width, height + glGetIntegerv(GL_VIEWPORT, viewport); + + Image image; + image.num_components = 3; + image.width = viewport[2]; + image.height = viewport[3]; + + if (image.width < 1 || image.height < 1) + return Image(); + + const int byte_size = image.width * image.height * image.num_components; + image.data = Rml::UniquePtr(new Rml::byte[byte_size]); + + glReadPixels(0, 0, image.width, image.height, GL_RGB, GL_UNSIGNED_BYTE, image.data.get()); + + bool result = true; + GLenum err; + while ((err = glGetError()) != GL_NO_ERROR) + { + result = false; + Rml::Log::Message(Rml::Log::LT_ERROR, "Could not capture screenshot, got GL error: 0x%x", err); + } + + if (!result) + return Image(); + + return image; +} + +#elif defined RMLUI_RENDERER_DX12 + +RendererExtensions::Image RendererExtensions::CaptureScreen() +{ + auto* render_interface = rmlui_static_cast(Rml::GetRenderInterface()); + if (!render_interface) + { + Rml::Log::Message(Rml::Log::LT_ERROR, "Could not capture screen. RmlUi render interface not set, or unexpected type."); + return {}; + } + + RendererExtensions::Image image; + if (!render_interface->CaptureScreen(image.width, image.height, image.num_components, image.data)) + { + Rml::Log::Message(Rml::Log::LT_ERROR, "The DirectX 12 renderer was unable to capture screen."); + return {}; + } + + return image; +} + +#else + +RendererExtensions::Image RendererExtensions::CaptureScreen() +{ + return Image(); +} + +#endif diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/shell/src/Shell.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/shell/src/Shell.cpp new file mode 100644 index 00000000..0eb18b0a --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/shell/src/Shell.cpp @@ -0,0 +1,117 @@ +#include "../include/Shell.h" +#include "../include/PlatformExtensions.h" +#include "../include/ShellFileInterface.h" +#include +#include +#include +#include +#include + +static Rml::UniquePtr file_interface; + +bool Shell::Initialize() +{ + // Find the path to the 'Samples' directory. + Rml::String root = PlatformExtensions::FindSamplesRoot(); + if (root.empty()) + return false; + + // The shell overrides the default file interface so that absolute paths in RML/RCSS-documents are relative to the 'Samples' directory. + file_interface = Rml::MakeUnique(root); + Rml::SetFileInterface(file_interface.get()); + + return true; +} + +void Shell::Shutdown() +{ + file_interface.reset(); +} + +void Shell::LoadFonts() +{ + const Rml::String directory = "assets/"; + + struct FontFace { + const char* filename; + bool fallback_face; + }; + FontFace font_faces[] = { + {"LatoLatin-Regular.ttf", false}, + {"LatoLatin-Italic.ttf", false}, + {"LatoLatin-Bold.ttf", false}, + {"LatoLatin-BoldItalic.ttf", false}, + {"NotoEmoji-Regular.ttf", true}, + }; + + for (const FontFace& face : font_faces) + Rml::LoadFontFace(directory + face.filename, face.fallback_face); +} + +bool Shell::ProcessKeyDownShortcuts(Rml::Context* context, Rml::Input::KeyIdentifier key, int key_modifier, float native_dp_ratio, bool priority) +{ + if (!context) + return true; + + // Result should return true to allow the event to propagate to the next handler. + bool result = false; + + // This function is intended to be called twice by the backend, before and after submitting the key event to the context. This way we can + // intercept shortcuts that should take priority over the context, and then handle any shortcuts of lower priority if the context did not + // intercept it. + if (priority) + { + // Priority shortcuts are handled before submitting the key to the context. + + // Toggle debugger and set dp-ratio using Ctrl +/-/0 keys. + if (key == Rml::Input::KI_F8) + { + Rml::Debugger::SetVisible(!Rml::Debugger::IsVisible()); + } + else if (key == Rml::Input::KI_0 && key_modifier & Rml::Input::KM_CTRL) + { + context->SetDensityIndependentPixelRatio(native_dp_ratio); + } + else if (key == Rml::Input::KI_1 && key_modifier & Rml::Input::KM_CTRL) + { + context->SetDensityIndependentPixelRatio(1.f); + } + else if ((key == Rml::Input::KI_OEM_MINUS || key == Rml::Input::KI_SUBTRACT) && key_modifier & Rml::Input::KM_CTRL) + { + const float new_dp_ratio = Rml::Math::Max(context->GetDensityIndependentPixelRatio() / 1.2f, 0.5f); + context->SetDensityIndependentPixelRatio(new_dp_ratio); + } + else if ((key == Rml::Input::KI_OEM_PLUS || key == Rml::Input::KI_ADD) && key_modifier & Rml::Input::KM_CTRL) + { + const float new_dp_ratio = Rml::Math::Min(context->GetDensityIndependentPixelRatio() * 1.2f, 2.5f); + context->SetDensityIndependentPixelRatio(new_dp_ratio); + } + else + { + // Propagate the key down event to the context. + result = true; + } + } + else + { + // We arrive here when no priority keys are detected and the key was not consumed by the context. Check for shortcuts of lower priority. + if (key == Rml::Input::KI_R && key_modifier & Rml::Input::KM_CTRL) + { + for (int i = 0; i < context->GetNumDocuments(); i++) + { + Rml::ElementDocument* document = context->GetDocument(i); + const Rml::String& src = document->GetSourceURL(); + if (src.size() > 4 && src.substr(src.size() - 4) == ".rml") + { + document->ReloadStyleSheet(); + } + } + } + else + { + result = true; + } + } + + return result; +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/shell/src/ShellFileInterface.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/shell/src/ShellFileInterface.cpp new file mode 100644 index 00000000..645960c8 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/shell/src/ShellFileInterface.cpp @@ -0,0 +1,38 @@ +#include "../include/ShellFileInterface.h" +#include + +ShellFileInterface::ShellFileInterface(const Rml::String& root) : root(root) {} + +ShellFileInterface::~ShellFileInterface() {} + +Rml::FileHandle ShellFileInterface::Open(const Rml::String& path) +{ + // Attempt to open the file relative to the application's root. + FILE* fp = fopen((root + path).c_str(), "rb"); + if (fp != nullptr) + return (Rml::FileHandle)fp; + + // Attempt to open the file relative to the current working directory. + fp = fopen(path.c_str(), "rb"); + return (Rml::FileHandle)fp; +} + +void ShellFileInterface::Close(Rml::FileHandle file) +{ + fclose((FILE*)file); +} + +size_t ShellFileInterface::Read(void* buffer, size_t size, Rml::FileHandle file) +{ + return fread(buffer, 1, size, (FILE*)file); +} + +bool ShellFileInterface::Seek(Rml::FileHandle file, long offset, int origin) +{ + return fseek((FILE*)file, offset, origin) == 0; +} + +size_t ShellFileInterface::Tell(Rml::FileHandle file) +{ + return ftell((FILE*)file); +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/tutorial/CMakeLists.txt b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/tutorial/CMakeLists.txt new file mode 100644 index 00000000..ee74b411 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/tutorial/CMakeLists.txt @@ -0,0 +1,4 @@ +set(RMLUI_TUTORIAL_PREFIX "rmlui_tutorial_") + +add_subdirectory("drag") +add_subdirectory("template") diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/tutorial/drag/CMakeLists.txt b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/tutorial/drag/CMakeLists.txt new file mode 100644 index 00000000..d2900762 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/tutorial/drag/CMakeLists.txt @@ -0,0 +1,14 @@ +set(TUTORIAL_NAME "drag") +set(TARGET_NAME "${RMLUI_TUTORIAL_PREFIX}${TUTORIAL_NAME}") + +add_executable(${TARGET_NAME} WIN32 + src/Inventory.cpp + src/Inventory.h + src/main.cpp +) + +set_common_target_options(${TARGET_NAME}) + +target_link_libraries(${TARGET_NAME} PRIVATE rmlui_shell) + +install_sample_target(${TARGET_NAME}) diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/tutorial/drag/data/inventory.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/tutorial/drag/data/inventory.rml new file mode 100644 index 00000000..6b8bb957 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/tutorial/drag/data/inventory.rml @@ -0,0 +1,16 @@ + + + + + Inventory + + + + + diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/tutorial/drag/data/tutorial.rcss b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/tutorial/drag/data/tutorial.rcss new file mode 100644 index 00000000..52131bf1 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/tutorial/drag/data/tutorial.rcss @@ -0,0 +1,24 @@ +/* Hide the window icon. */ +div#title_bar div#icon +{ + display: none; +} + +icon +{ + /* The icons are floated left so they appear left-to-right. */ + float: left; + + /* The padding is added to push the text down to the bottom of the element. */ + box-sizing: border-box; + width: 100dp; + height: 100dp; + padding: 60dp 10dp 0dp 10dp; + margin: 10dp; + + decorator: image( /assets/present.tga ); + + font-size: 12dp; + text-align: center; + font-effect: shadow(1dp 1dp black); +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/tutorial/drag/src/Inventory.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/tutorial/drag/src/Inventory.cpp new file mode 100644 index 00000000..505d5343 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/tutorial/drag/src/Inventory.cpp @@ -0,0 +1,35 @@ +#include "Inventory.h" +#include + +Inventory::Inventory(const Rml::String& title, const Rml::Vector2f& position, Rml::Context* context) +{ + document = context->LoadDocument("tutorial/drag/data/inventory.rml"); + if (document) + { + document->GetElementById("title")->SetInnerRML(title); + document->SetProperty(Rml::PropertyId::Left, Rml::Property(position.x, Rml::Unit::DP)); + document->SetProperty(Rml::PropertyId::Top, Rml::Property(position.y, Rml::Unit::DP)); + document->Show(); + } +} + +Inventory::~Inventory() +{ + if (document) + document->Close(); +} + +void Inventory::AddItem(const Rml::String& name) +{ + if (!document) + return; + + Rml::Element* content = document->GetElementById("content"); + if (!content) + return; + + // Create the new 'icon' element. + Rml::ElementPtr icon = Rml::Factory::InstanceElement(content, "icon", "icon", Rml::XMLAttributes()); + icon->SetInnerRML(name); + content->AppendChild(std::move(icon)); +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/tutorial/drag/src/Inventory.h b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/tutorial/drag/src/Inventory.h new file mode 100644 index 00000000..a9b52ff7 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/tutorial/drag/src/Inventory.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include +#include + +class Inventory { +public: + /// Constructs a new inventory and opens its window. + /// @param[in] title The title of the new inventory. + /// @param[in] position The position of the inventory window. + /// @param[in] context The context to open the inventory window in. + Inventory(const Rml::String& title, const Rml::Vector2f& position, Rml::Context* context); + /// Destroys the inventory and closes its window. + ~Inventory(); + + /// Adds a brand-new item into this inventory. + /// @param[in] name The name of this item. + void AddItem(const Rml::String& name); + +private: + Rml::ElementDocument* document; +}; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/tutorial/drag/src/main.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/tutorial/drag/src/main.cpp new file mode 100644 index 00000000..cae0faf6 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/tutorial/drag/src/main.cpp @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2006 - 2008 + * Wandering Monster Studios Limited + * + * Any use of this program is governed by the terms of Wandering Monster + * Studios Limited's Licence Agreement included with this program, a copy + * of which can be obtained by contacting Wandering Monster Studios + * Limited at info@wanderingmonster.co.nz. + * + */ + +#include "Inventory.h" +#include +#include +#include +#include + +#if defined RMLUI_PLATFORM_WIN32 + #include +int APIENTRY WinMain(HINSTANCE /*instance_handle*/, HINSTANCE /*previous_instance_handle*/, char* /*command_line*/, int /*command_show*/) +#else +int main(int /*argc*/, char** /*argv*/) +#endif +{ + int window_width = 1024; + int window_height = 768; + + // Initializes the shell which provides common functionality used by the included samples. + if (!Shell::Initialize()) + return -1; + + // Constructs the system and render interfaces, creates a window, and attaches the renderer. + if (!Backend::Initialize("Drag Tutorial", window_width, window_height, true)) + { + Shell::Shutdown(); + return -1; + } + + // Install the custom interfaces constructed by the backend before initializing RmlUi. + Rml::SetSystemInterface(Backend::GetSystemInterface()); + Rml::SetRenderInterface(Backend::GetRenderInterface()); + + // RmlUi initialisation. + Rml::Initialise(); + + // Create the main RmlUi context. + Rml::Context* context = Rml::CreateContext("main", Rml::Vector2i(window_width, window_height)); + if (!context) + { + Rml::Shutdown(); + Backend::Shutdown(); + Shell::Shutdown(); + return -1; + } + + Rml::Debugger::Initialise(context); + Shell::LoadFonts(); + + // Load and show the inventory document. + Inventory* inventory_1 = new Inventory("Inventory 1", Rml::Vector2f(50, 200), context); + Inventory* inventory_2 = new Inventory("Inventory 2", Rml::Vector2f(540, 240), context); + + // Add items into the inventory. + inventory_1->AddItem("Mk III L.A.S.E.R."); + inventory_1->AddItem("Gravity Descender"); + inventory_1->AddItem("Closed-Loop Ion Beam"); + inventory_1->AddItem("5kT Mega-Bomb"); + + bool running = true; + while (running) + { + running = Backend::ProcessEvents(context, &Shell::ProcessKeyDownShortcuts, true); + + context->Update(); + + Backend::BeginFrame(); + context->Render(); + Backend::PresentFrame(); + } + + delete inventory_1; + delete inventory_2; + + // Shutdown RmlUi. + Rml::Shutdown(); + + Backend::Shutdown(); + Shell::Shutdown(); + + return 0; +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/tutorial/template/CMakeLists.txt b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/tutorial/template/CMakeLists.txt new file mode 100644 index 00000000..a860dabe --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/tutorial/template/CMakeLists.txt @@ -0,0 +1,12 @@ +set(TUTORIAL_NAME "template") +set(TARGET_NAME "${RMLUI_TUTORIAL_PREFIX}${TUTORIAL_NAME}") + +add_executable(${TARGET_NAME} WIN32 + src/main.cpp +) + +set_common_target_options(${TARGET_NAME}) + +target_link_libraries(${TARGET_NAME} PRIVATE rmlui_shell) + +install_sample_target(${TARGET_NAME}) diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/tutorial/template/data/tutorial.rcss b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/tutorial/template/data/tutorial.rcss new file mode 100644 index 00000000..b4f66852 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/tutorial/template/data/tutorial.rcss @@ -0,0 +1,37 @@ +@spritesheet theme +{ + src: /assets/invader.tga; + + window-tl: 0px 0px 133px 140px; + window-t: 134px 0px 1px 140px; + window-tr: 136px 0px 10px 140px; + window-l: 0px 139px 10px 1px; + window-c: 11px 139px 1px 1px; + window-r: 10px 139px -10px 1px; /* mirrored left */ + window-bl: 0px 140px 11px 11px; + window-b: 11px 140px 1px 11px; + window-br: 136px 140px 10px 11px; +} + +body +{ + font-family: LatoLatin; + font-weight: normal; + font-style: normal; + font-size: 15dp; + color: white; +} + +body.window +{ + decorator: tiled-box( + window-tl, window-t, window-tr, + window-l, window-c, window-r, + window-bl, window-b, window-br + ); +} + +div +{ + display: block; +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/tutorial/template/data/tutorial.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/tutorial/template/data/tutorial.rml new file mode 100644 index 00000000..7380a589 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/tutorial/template/data/tutorial.rml @@ -0,0 +1,17 @@ + + + + Window + + + + + diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Samples/tutorial/template/src/main.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/tutorial/template/src/main.cpp new file mode 100644 index 00000000..2bb948ea --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Samples/tutorial/template/src/main.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2006 - 2008 + * Wandering Monster Studios Limited + * + * Any use of this program is governed by the terms of Wandering Monster + * Studios Limited's Licence Agreement included with this program, a copy + * of which can be obtained by contacting Wandering Monster Studios + * Limited at info@wanderingmonster.co.nz. + * + */ + +#include +#include +#include +#include + +#if defined RMLUI_PLATFORM_WIN32 + #include +int APIENTRY WinMain(HINSTANCE /*instance_handle*/, HINSTANCE /*previous_instance_handle*/, char* /*command_line*/, int /*command_show*/) +#else +int main(int /*argc*/, char** /*argv*/) +#endif +{ + int window_width = 1024; + int window_height = 768; + + // Initializes the shell which provides common functionality used by the included samples. + if (!Shell::Initialize()) + return -1; + + // Constructs the system and render interfaces, creates a window, and attaches the renderer. + if (!Backend::Initialize("Template Tutorial", window_width, window_height, true)) + { + Shell::Shutdown(); + return -1; + } + + // Install the custom interfaces constructed by the backend before initializing RmlUi. + Rml::SetSystemInterface(Backend::GetSystemInterface()); + Rml::SetRenderInterface(Backend::GetRenderInterface()); + + // RmlUi initialisation. + Rml::Initialise(); + + // Create the main RmlUi context. + Rml::Context* context = Rml::CreateContext("main", Rml::Vector2i(window_width, window_height)); + if (!context) + { + Rml::Shutdown(); + Backend::Shutdown(); + Shell::Shutdown(); + return -1; + } + + Rml::Debugger::Initialise(context); + Shell::LoadFonts(); + + // Load and show the tutorial document. + if (Rml::ElementDocument* document = context->LoadDocument("tutorial/template/data/tutorial.rml")) + document->Show(); + + bool running = true; + while (running) + { + running = Backend::ProcessEvents(context, &Shell::ProcessKeyDownShortcuts, true); + + context->Update(); + + Backend::BeginFrame(); + context->Render(); + Backend::PresentFrame(); + } + + // Shutdown RmlUi. + Rml::Shutdown(); + + Backend::Shutdown(); + Shell::Shutdown(); + + return 0; +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/CMakeLists.txt b/Minecraft.Client/Windows64/Libs/RmlUi/Source/CMakeLists.txt new file mode 100644 index 00000000..a8685623 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/CMakeLists.txt @@ -0,0 +1,14 @@ +add_subdirectory("Core") + +if(RMLUI_LOTTIE_PLUGIN) + add_subdirectory("Lottie") +endif() +if(RMLUI_SVG_PLUGIN) + add_subdirectory("SVG") +endif() + +add_subdirectory("Debugger") + +if(RMLUI_LUA_BINDINGS) + add_subdirectory("Lua") +endif() diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/BaseXMLParser.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/BaseXMLParser.cpp new file mode 100644 index 00000000..f01a6fa1 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/BaseXMLParser.cpp @@ -0,0 +1,519 @@ +#include "../../Include/RmlUi/Core/BaseXMLParser.h" +#include "../../Include/RmlUi/Core/Profiling.h" +#include "../../Include/RmlUi/Core/Stream.h" +#include "XMLParseTools.h" +#include + +namespace Rml { + +BaseXMLParser::BaseXMLParser() {} + +BaseXMLParser::~BaseXMLParser() {} + +void BaseXMLParser::RegisterCDATATag(const String& tag) +{ + if (!tag.empty()) + cdata_tags.insert(StringUtilities::ToLower(tag)); +} + +bool BaseXMLParser::IsCDATATag(const String& tag) +{ + return cdata_tags.find(StringUtilities::ToLower(tag)) != cdata_tags.end(); +} + +void BaseXMLParser::RegisterInnerXMLAttribute(const String& attribute_name) +{ + attributes_for_inner_xml_data.insert(attribute_name); +} + +void BaseXMLParser::Parse(Stream* stream) +{ + source_url = &stream->GetSourceURL(); + + xml_source.clear(); + + // We read in the whole XML file here. + // TODO: It doesn't look like the Stream interface is used for anything useful. We + // might as well just use a span or StringView, and get completely rid of it. + // @performance Otherwise, use the temporary allocator. + const size_t source_size = stream->Length(); + stream->Read(xml_source, source_size); + + xml_index = 0; + line_number = 1; + line_number_open_tag = 1; + + inner_xml_data = false; + inner_xml_data_terminate_depth = 0; + inner_xml_data_index_begin = 0; + + // Read (er ... skip) the header, if one exists. + ReadHeader(); + // Read the XML body. + ReadBody(); + + xml_source.clear(); + source_url = nullptr; +} + +int BaseXMLParser::GetLineNumber() const +{ + return line_number; +} + +int BaseXMLParser::GetLineNumberOpenTag() const +{ + return line_number_open_tag; +} + +void BaseXMLParser::HandleElementStart(const String& /*name*/, const XMLAttributes& /*attributes*/) {} + +void BaseXMLParser::HandleElementEnd(const String& /*name*/) {} + +void BaseXMLParser::HandleData(const String& /*data*/, XMLDataType /*type*/) {} + +const URL* BaseXMLParser::GetSourceURLPtr() const +{ + return source_url; +} + +void BaseXMLParser::Next() +{ + xml_index += 1; +} + +bool BaseXMLParser::AtEnd() const +{ + return xml_index >= xml_source.size(); +} + +char BaseXMLParser::Look() const +{ + RMLUI_ASSERT(!AtEnd()); + return xml_source[xml_index]; +} + +void BaseXMLParser::HandleElementStartInternal(const String& name, const XMLAttributes& attributes) +{ + line_number_open_tag = line_number; + if (!inner_xml_data) + HandleElementStart(name, attributes); +} + +void BaseXMLParser::HandleElementEndInternal(const String& name) +{ + if (!inner_xml_data) + HandleElementEnd(name); +} + +void BaseXMLParser::HandleDataInternal(const String& data, XMLDataType type) +{ + if (!inner_xml_data) + HandleData(data, type); +} + +void BaseXMLParser::ReadHeader() +{ + if (PeekString("", temp); + } +} + +void BaseXMLParser::ReadBody() +{ + RMLUI_ZoneScoped; + + open_tag_depth = 0; + line_number_open_tag = 0; + + for (;;) + { + // Find the next open tag. + if (!FindString("<", data, true)) + break; + + const size_t xml_index_tag = xml_index - 1; + + // Check what kind of tag this is. + if (PeekString("!--")) + { + // Comment. + String temp; + if (!FindString("-->", temp)) + break; + } + else if (PeekString("![CDATA[")) + { + // CDATA tag; read everything (including markup) until the ending + // CDATA tag. + if (!ReadCDATA()) + break; + } + else if (PeekString("/")) + { + if (!ReadCloseTag(xml_index_tag)) + break; + + // Bail if we've hit the end of the XML data. + if (open_tag_depth == 0) + break; + } + else + { + if (!ReadOpenTag()) + break; + } + } + + if (open_tag_depth > 0) + { + Log::Message(Log::LT_WARNING, "XML parse error on line %d of %s.", GetLineNumber(), source_url->GetURL().c_str()); + } +} + +bool BaseXMLParser::ReadOpenTag() +{ + // Increase the open depth + open_tag_depth++; + + // Opening tag; send data immediately and open the tag. + if (!data.empty()) + { + HandleDataInternal(data, XMLDataType::Text); + data.clear(); + } + + String tag_name; + if (!FindWord(tag_name, "/>")) + return false; + + bool section_opened = false; + + if (PeekString(">")) + { + // Simple open tag. + HandleElementStartInternal(tag_name, XMLAttributes()); + section_opened = true; + } + else if (PeekString("/") && PeekString(">")) + { + // Empty open tag. + HandleElementStartInternal(tag_name, XMLAttributes()); + HandleElementEndInternal(tag_name); + + // Tag immediately closed, reduce count + open_tag_depth--; + } + else + { + // It appears we have some attributes. Let's parse them. + bool parse_inner_xml_as_data = false; + XMLAttributes attributes; + if (!ReadAttributes(attributes, parse_inner_xml_as_data)) + return false; + + if (PeekString(">")) + { + HandleElementStartInternal(tag_name, attributes); + section_opened = true; + } + else if (PeekString("/") && PeekString(">")) + { + HandleElementStartInternal(tag_name, attributes); + HandleElementEndInternal(tag_name); + + // Tag immediately closed, reduce count + open_tag_depth--; + } + else + { + return false; + } + + if (section_opened && parse_inner_xml_as_data && !inner_xml_data) + { + inner_xml_data = true; + inner_xml_data_terminate_depth = open_tag_depth; + inner_xml_data_index_begin = xml_index; + } + } + + // Check if this tag needs to be processed as CDATA. + if (section_opened) + { + if (IsCDATATag(tag_name)) + { + if (ReadCDATA(StringUtilities::ToLower(tag_name).c_str())) + { + open_tag_depth--; + if (!data.empty()) + { + HandleDataInternal(data, XMLDataType::CDATA); + data.clear(); + } + HandleElementEndInternal(tag_name); + + return true; + } + + return false; + } + } + + return true; +} + +bool BaseXMLParser::ReadCloseTag(const size_t xml_index_tag) +{ + if (open_tag_depth <= 0) + { + Log::Message(Log::LT_WARNING, "Found unexpected XML close tag on line %d of %s", GetLineNumber(), source_url->GetURL().c_str()); + return false; + } + + if (inner_xml_data && open_tag_depth == inner_xml_data_terminate_depth) + { + // Closing the tag that initiated the inner xml data parsing. Set all its contents as Data to be + // submitted next, and disable the mode to resume normal parsing behavior. + RMLUI_ASSERT(inner_xml_data_index_begin <= xml_index_tag); + inner_xml_data = false; + data = xml_source.substr(inner_xml_data_index_begin, xml_index_tag - inner_xml_data_index_begin); + HandleDataInternal(data, XMLDataType::InnerXML); + data.clear(); + } + + // Closing tag; send data immediately and close the tag. + if (!data.empty()) + { + HandleDataInternal(data, XMLDataType::Text); + data.clear(); + } + + String tag_name; + if (!FindString(">", tag_name)) + return false; + + HandleElementEndInternal(StringUtilities::StripWhitespace(tag_name)); + + // Tag closed, reduce count + open_tag_depth--; + + return true; +} + +bool BaseXMLParser::ReadAttributes(XMLAttributes& attributes, bool& parse_raw_xml_content) +{ + for (;;) + { + String attribute; + String value; + + // Get the attribute name + if (!FindWord(attribute, "=/>")) + { + return false; + } + + // Check if theres an assigned value + if (PeekString("=")) + { + if (PeekString("\"")) + { + if (!FindString("\"", value)) + return false; + } + else if (PeekString("'")) + { + if (!FindString("'", value)) + return false; + } + else if (!FindWord(value, "/>")) + { + return false; + } + } + + if (attributes_for_inner_xml_data.count(attribute) == 1) + parse_raw_xml_content = true; + + attributes[attribute] = StringUtilities::DecodeRml(value); + + // Check for the end of the tag. + if (PeekString("/", false) || PeekString(">", false)) + return true; + } +} + +bool BaseXMLParser::ReadCDATA(const char* tag_terminator) +{ + String cdata; + if (tag_terminator == nullptr) + { + FindString("]]>", cdata); + data += cdata; + return true; + } + else + { + for (;;) + { + // Search for the next tag opening. + if (!FindString("<", cdata)) + return false; + + if (PeekString("/", false)) + { + String tag; + if (FindString(">", tag)) + { + size_t slash_pos = tag.find('/'); + String tag_name = StringUtilities::StripWhitespace(slash_pos == String::npos ? tag : tag.substr(slash_pos + 1)); + if (StringUtilities::ToLower(std::move(tag_name)) == tag_terminator) + { + data += cdata; + return true; + } + else + { + cdata += '<' + tag + '>'; + } + } + else + cdata += "<"; + } + else + cdata += "<"; + } + } +} + +bool BaseXMLParser::FindWord(String& word, const char* terminators) +{ + while (!AtEnd()) + { + char c = Look(); + + // Count line numbers + if (c == '\n') + { + line_number++; + } + + // Ignore white space + if (StringUtilities::IsWhitespace(c)) + { + if (word.empty()) + { + Next(); + continue; + } + else + return true; + } + + // Check for termination condition + if (terminators && strchr(terminators, c)) + { + return !word.empty(); + } + + word += c; + Next(); + } + + return false; +} + +bool BaseXMLParser::FindString(const char* string, String& data, bool escape_brackets) +{ + const char first_char = string[0]; + bool in_brackets = false; + bool in_string = false; + char previous = 0; + + while (!AtEnd()) + { + const char c = Look(); + + // Count line numbers + if (c == '\n') + { + line_number++; + } + + if (escape_brackets) + { + const char* error_str = XMLParseTools::ParseDataBrackets(in_brackets, in_string, c, previous); + if (error_str) + { + Log::Message(Log::LT_WARNING, "XML parse error. %s", error_str); + return false; + } + } + + if (c == first_char && !in_brackets && PeekString(string)) + { + return true; + } + + data += c; + previous = c; + Next(); + } + + return false; +} + +bool BaseXMLParser::PeekString(const char* string, bool consume) +{ + const size_t start_index = xml_index; + const int start_line = line_number; + bool success = true; + int i = 0; + while (string[i]) + { + if (AtEnd()) + { + success = false; + break; + } + + const char c = Look(); + + // Count line numbers + if (c == '\n') + { + line_number++; + } + + // Seek past all the whitespace if we haven't hit the initial character yet. + if (i == 0 && StringUtilities::IsWhitespace(c)) + { + Next(); + } + else + { + if (c != string[i]) + { + success = false; + break; + } + + i++; + Next(); + } + } + + // Set the index to the start index unless we are consuming. + if (!consume || !success) + { + xml_index = start_index; + line_number = start_line; + } + + return success; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Box.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Box.cpp new file mode 100644 index 00000000..648ef979 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Box.cpp @@ -0,0 +1,107 @@ +#include "../../Include/RmlUi/Core/Box.h" +#include + +namespace Rml { + +Box::Box() = default; +Box::Box(Vector2f content) : content(content) {} + +Box::~Box() = default; + +Vector2f Box::GetPosition(BoxArea area) const +{ + RMLUI_ASSERT(area != BoxArea::Auto); + Vector2f area_position(-GetEdge(BoxArea::Margin, BoxEdge::Left), -GetEdge(BoxArea::Margin, BoxEdge::Top)); + for (int i = 0; i < (int)area; i++) + { + area_position.x += area_edges[i][(int)BoxEdge::Left]; + area_position.y += area_edges[i][(int)BoxEdge::Top]; + } + + return area_position; +} + +Vector2f Box::GetSize() const +{ + return content; +} + +Vector2f Box::GetSize(BoxArea area) const +{ + RMLUI_ASSERT(area != BoxArea::Auto); + Vector2f area_size(content); + for (int i = (int)area; i <= (int)BoxArea::Padding; i++) + { + area_size.x += (area_edges[i][(int)BoxEdge::Left] + area_edges[i][(int)BoxEdge::Right]); + area_size.y += (area_edges[i][(int)BoxEdge::Top] + area_edges[i][(int)BoxEdge::Bottom]); + } + + return area_size; +} + +void Box::SetContent(Vector2f _content) +{ + content = _content; +} + +void Box::SetEdge(BoxArea area, BoxEdge edge, float size) +{ + RMLUI_ASSERT(area != BoxArea::Auto); + area_edges[(int)area][(int)edge] = size; +} + +float Box::GetEdge(BoxArea area, BoxEdge edge) const +{ + RMLUI_ASSERT(area != BoxArea::Auto); + return area_edges[(int)area][(int)edge]; +} + +float Box::GetCumulativeEdge(BoxArea area, BoxEdge edge) const +{ + RMLUI_ASSERT(area != BoxArea::Auto); + float size = 0; + int max_area = Math::Min((int)area, (int)BoxArea::Padding); + for (int i = 0; i <= max_area; i++) + size += area_edges[i][(int)edge]; + + return size; +} + +float Box::GetSizeAcross(BoxDirection direction, BoxArea area_outer, BoxArea area_inner) const +{ + static_assert((int)BoxDirection::Horizontal == 1 && (int)BoxDirection::Vertical == 0, ""); + RMLUI_ASSERT((int)area_outer <= (int)area_inner && (int)direction <= 1 && area_inner != BoxArea::Auto); + + float size = 0.0f; + + if (area_inner == BoxArea::Content) + size = (direction == BoxDirection::Horizontal ? content.x : content.y); + + for (int i = (int)area_outer; i <= (int)area_inner && i < (int)BoxArea::Content; i++) + size += (area_edges[i][(int)BoxEdge::Top + (int)direction] + area_edges[i][(int)BoxEdge::Bottom + (int)direction]); + + return size; +} + +Vector2f Box::GetFrameSize(BoxArea area) const +{ + if (area == BoxArea::Content) + return content; + + return { + area_edges[(int)area][(int)BoxEdge::Right] + area_edges[(int)area][(int)BoxEdge::Left], + area_edges[(int)area][(int)BoxEdge::Top] + area_edges[(int)area][(int)BoxEdge::Bottom], + }; +} + +bool Box::operator==(const Box& rhs) const +{ + return content == rhs.content && memcmp(area_edges, rhs.area_edges, sizeof(area_edges)) == 0; +} + +bool Box::operator!=(const Box& rhs) const +{ + return !(*this == rhs); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/BoxShadowCache.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/BoxShadowCache.cpp new file mode 100644 index 00000000..6b8155c4 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/BoxShadowCache.cpp @@ -0,0 +1,97 @@ +#include "BoxShadowCache.h" +#include "../../Include/RmlUi/Core/ComputedValues.h" +#include "../../Include/RmlUi/Core/MeshUtilities.h" +#include "../../Include/RmlUi/Core/Profiling.h" +#include "../../Include/RmlUi/Core/RenderManager.h" +#include "../Core/ControlledLifetimeResource.h" +#include "BoxShadowHash.h" +#include "GeometryBoxShadow.h" + +namespace Rml { + +struct BoxShadowCacheData { + StableUnorderedMap> handles; +}; + +static void ReleaseHandle(BoxShadowRenderable* handle); + +BoxShadowRenderable::BoxShadowRenderable(const BoxShadowGeometryInfo& geometry_info) : cache_key(geometry_info) {} + +BoxShadowRenderable::~BoxShadowRenderable() +{ + ReleaseHandle(this); +} + +static ControlledLifetimeResource shadow_cache_data; + +void BoxShadowCache::Initialize() +{ + shadow_cache_data.Initialize(); +} + +void BoxShadowCache::Shutdown() +{ + shadow_cache_data.Shutdown(); +} + +static SharedPtr GetOrCreateBoxShadow(RenderManager& render_manager, const BoxShadowGeometryInfo& info) +{ + RMLUI_ZoneScoped; + auto it_handle = shadow_cache_data->handles.find(info); + if (it_handle != shadow_cache_data->handles.end()) + { + SharedPtr result = it_handle->second.lock(); + RMLUI_ASSERTMSG(result, "Failed to lock handle in Box Shadow cache"); + return result; + } + + const auto iterator_inserted = shadow_cache_data->handles.emplace(info, WeakPtr()); + RMLUI_ASSERTMSG(iterator_inserted.second, "Could not insert entry into the Box Shadow cache handle map, duplicate key."); + const BoxShadowGeometryInfo& inserted_key = iterator_inserted.first->first; + WeakPtr& inserted_weak_data_pointer = iterator_inserted.first->second; + + auto shadow_handle = MakeShared(inserted_key); + GeometryBoxShadow::GenerateTexture(shadow_handle->texture, shadow_handle->background_border_geometry, render_manager, inserted_key); + + Mesh mesh; + const byte alpha = byte(info.opacity * 255.f); + MeshUtilities::GenerateQuad(mesh, -info.element_offset_in_texture, Vector2f(info.texture_dimensions), ColourbPremultiplied(alpha, alpha)); + shadow_handle->geometry = render_manager.MakeGeometry(std::move(mesh)); + + inserted_weak_data_pointer = shadow_handle; + return shadow_handle; +} + +static void ReleaseHandle(BoxShadowRenderable* handle) +{ + // There are no longer any users of the cache entry uniquely identified by the handle address. Start from the + // tip (i.e. per-color data) and remove that entry from its parent. Move up the cache ancestry and erase any + // entries that no longer have any children. + auto& handles = shadow_cache_data->handles; + const BoxShadowGeometryInfo& key = handle->cache_key; + + auto it_handle = handles.find(key); + RMLUI_ASSERT(it_handle != handles.cend()); + + handles.erase(it_handle); +} + +SharedPtr BoxShadowCache::GetHandle(Element* element, const ComputedValues& computed) +{ + RenderManager* render_manager = element->GetRenderManager(); + if (!render_manager) + return {}; + + ColourbPremultiplied background_color = computed.background_color().ToPremultiplied(); + Array border_colors = { + computed.border_top_color().ToPremultiplied(), + computed.border_right_color().ToPremultiplied(), + computed.border_bottom_color().ToPremultiplied(), + computed.border_left_color().ToPremultiplied(), + }; + const CornerSizes border_radius = computed.border_radius(); + BoxShadowGeometryInfo geom_info = GeometryBoxShadow::Resolve(element, border_radius, background_color, border_colors, computed.opacity()); + return GetOrCreateBoxShadow(*render_manager, geom_info); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/BoxShadowCache.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/BoxShadowCache.h new file mode 100644 index 00000000..a80e60ed --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/BoxShadowCache.h @@ -0,0 +1,35 @@ +#pragma once + +#include "../../Include/RmlUi/Core/CallbackTexture.h" +#include "../../Include/RmlUi/Core/Geometry.h" +#include "../../Include/RmlUi/Core/Types.h" + +namespace Rml { +namespace Style { + class ComputedValues; +} +struct BoxShadowGeometryInfo; + +struct BoxShadowRenderable : NonCopyMoveable { + BoxShadowRenderable(const BoxShadowGeometryInfo& geometry_info); + ~BoxShadowRenderable(); + + CallbackTexture texture; + Geometry geometry; + Geometry background_border_geometry; + const BoxShadowGeometryInfo& cache_key; +}; + +class BoxShadowCache { +public: + static void Initialize(); + static void Shutdown(); + + /// Returns a handle to BoxShadow renderable matching the element's style - creates new data if none is found. + /// @param[in] element Element for which to calculate and cache the box shadow. + /// @param[in] computed The computed style values of the element. + /// @return A handle to the BoxShadow data, with automatic reference counting. + static SharedPtr GetHandle(Element* element, const Style::ComputedValues& computed); +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/BoxShadowHash.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/BoxShadowHash.h new file mode 100644 index 00000000..fa3d8869 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/BoxShadowHash.h @@ -0,0 +1,143 @@ +#pragma once + +#include "../../Include/RmlUi/Core/ComputedValues.h" +#include "../../Include/RmlUi/Core/NumericValue.h" +#include "../../Include/RmlUi/Core/RenderBox.h" +#include "../../Include/RmlUi/Core/Types.h" +#include "../../Include/RmlUi/Core/Unit.h" +#include "../../Include/RmlUi/Core/Utilities.h" +#include "BoxShadowCache.h" +#include "GeometryBoxShadow.h" + +namespace std { + +template <> +struct hash<::Rml::Unit> { + using utype = underlying_type_t<::Rml::Unit>; + size_t operator()(const ::Rml::Unit& t) const noexcept + { + hash h; + return h(static_cast(t)); + } +}; + +template <> +struct hash<::Rml::Vector2i> { + size_t operator()(const ::Rml::Vector2i& v) const noexcept + { + using namespace ::Rml::Utilities; + size_t seed = hash{}(v.x); + HashCombine(seed, v.y); + return seed; + } +}; +template <> +struct hash<::Rml::Vector2f> { + size_t operator()(const ::Rml::Vector2f& v) const noexcept + { + using namespace ::Rml::Utilities; + size_t seed = hash{}(v.x); + HashCombine(seed, v.y); + return seed; + } +}; +template <> +struct hash<::Rml::Colourb> { + size_t operator()(const ::Rml::Colourb& v) const noexcept { return static_cast(hash{}(reinterpret_cast(v))); } +}; +template <> +struct hash<::Rml::ColourbPremultiplied> { + size_t operator()(const ::Rml::ColourbPremultiplied& v) const noexcept + { + return static_cast(hash{}(reinterpret_cast(v))); + } +}; + +template <> +struct hash<::Rml::NumericValue> { + size_t operator()(const ::Rml::NumericValue& v) const noexcept + { + using namespace ::Rml::Utilities; + size_t seed = hash{}(v.number); + HashCombine(seed, v.unit); + return seed; + } +}; + +template <> +struct hash<::Rml::BoxShadow> { + size_t operator()(const ::Rml::BoxShadow& s) const noexcept + { + using namespace ::Rml; + using namespace ::Rml::Utilities; + size_t seed = std::hash{}(s.color); + + HashCombine(seed, s.offset_x); + HashCombine(seed, s.offset_y); + HashCombine(seed, s.blur_radius); + HashCombine(seed, s.spread_distance); + HashCombine(seed, s.inset); + return seed; + } +}; + +template <> +struct hash<::Rml::RenderBox> { + size_t operator()(const ::Rml::RenderBox& box) const noexcept + { + using namespace ::Rml::Utilities; + static auto HashArray4 = [](const ::Rml::Array& arr) -> size_t { + size_t seed = 0; + for (const auto& v : arr) + HashCombine(seed, v); + return seed; + }; + + size_t seed = 0; + HashCombine(seed, box.GetFillSize()); + HashCombine(seed, box.GetBorderOffset()); + HashCombine(seed, HashArray4(box.GetBorderRadius())); + HashCombine(seed, HashArray4(box.GetBorderWidths())); + return seed; + } +}; + +template <> +struct hash<::Rml::BoxShadowGeometryInfo> { + size_t operator()(const ::Rml::BoxShadowGeometryInfo& in) const noexcept + { + using namespace ::Rml::Utilities; + size_t seed = size_t(849128392); + + HashCombine(seed, in.background_color); + for (const auto& v : in.border_colors) + { + HashCombine(seed, v); + } + + for (const auto& v : in.border_radius) + { + HashCombine(seed, v); + } + + HashCombine(seed, in.texture_dimensions); + HashCombine(seed, in.element_offset_in_texture); + + for (const auto& v : in.padding_render_boxes) + { + HashCombine(seed, v); + } + for (const auto& v : in.border_render_boxes) + { + HashCombine(seed, v); + } + for (const ::Rml::BoxShadow& v : in.shadow_list) + { + HashCombine(seed, v); + } + HashCombine(seed, in.opacity); + return seed; + } +}; + +} // namespace std diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/CMakeLists.txt b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/CMakeLists.txt new file mode 100644 index 00000000..3ac5facf --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/CMakeLists.txt @@ -0,0 +1,480 @@ +# Declare RmlUi core library +# Not explicitly setting library type so that it can be chosen by consumer using BUILD_SHARED_LIBS. Header files are not +# necessary, but are included to improve navigation and code completion on IDEs and language servers. +add_library(rmlui_core + BaseXMLParser.cpp + Box.cpp + BoxShadowCache.h + BoxShadowCache.cpp + BoxShadowHash.h + CallbackTexture.cpp + Clock.cpp + Clock.h + CompiledFilterShader.cpp + ComputedValues.cpp + ComputeProperty.cpp + ComputeProperty.h + Context.cpp + ContextInstancer.cpp + ContextInstancerDefault.cpp + ContextInstancerDefault.h + ControlledLifetimeResource.h + ConvolutionFilter.cpp + Core.cpp + DataController.cpp + DataController.h + DataControllerDefault.cpp + DataControllerDefault.h + DataExpression.cpp + DataExpression.h + DataModel.cpp + DataModel.h + DataModelHandle.cpp + DataTypeRegister.cpp + DataVariable.cpp + DataView.cpp + DataView.h + DataViewDefault.cpp + DataViewDefault.h + Decorator.cpp + DecoratorGradient.cpp + DecoratorGradient.h + DecoratorNinePatch.cpp + DecoratorNinePatch.h + DecoratorShader.cpp + DecoratorShader.h + DecoratorText.cpp + DecoratorText.h + DecoratorTiled.cpp + DecoratorTiled.h + DecoratorTiledBox.cpp + DecoratorTiledBox.h + DecoratorTiledHorizontal.cpp + DecoratorTiledHorizontal.h + DecoratorTiledImage.cpp + DecoratorTiledImage.h + DecoratorTiledVertical.cpp + DecoratorTiledVertical.h + DecoratorUtilities.cpp + DecoratorUtilities.h + DocumentHeader.cpp + DocumentHeader.h + EffectSpecification.cpp + Element.cpp + ElementAnimation.cpp + ElementAnimation.h + ElementBackgroundBorder.cpp + ElementBackgroundBorder.h + ElementDefinition.cpp + ElementDefinition.h + ElementDocument.cpp + ElementEffects.cpp + ElementEffects.h + ElementHandle.cpp + ElementHandle.h + ElementInstancer.cpp + ElementMeta.cpp + ElementMeta.h + ElementScroll.cpp + ElementStyle.cpp + ElementStyle.h + ElementText.cpp + ElementUtilities.cpp + Event.cpp + EventDispatcher.cpp + EventDispatcher.h + EventInstancer.cpp + EventInstancerDefault.cpp + EventInstancerDefault.h + EventListenerInstancer.cpp + EventSpecification.cpp + EventSpecification.h + Factory.cpp + FileInterface.cpp + FileInterfaceDefault.cpp + FileInterfaceDefault.h + Filter.cpp + FilterBasic.cpp + FilterBasic.h + FilterBlur.cpp + FilterBlur.h + FilterDropShadow.cpp + FilterDropShadow.h + FontEffect.cpp + FontEffectBlur.cpp + FontEffectBlur.h + FontEffectGlow.cpp + FontEffectGlow.h + FontEffectInstancer.cpp + FontEffectOutline.cpp + FontEffectOutline.h + FontEffectShadow.cpp + FontEffectShadow.h + FontEngineInterface.cpp + Geometry.cpp + GeometryBackgroundBorder.cpp + GeometryBackgroundBorder.h + GeometryBoxShadow.cpp + GeometryBoxShadow.h + IdNameMap.h + Log.cpp + LogDefault.cpp + LogDefault.h + Math.cpp + Memory.cpp + Memory.h + MeshUtilities.cpp + ObserverPtr.cpp + Plugin.cpp + PluginRegistry.cpp + PluginRegistry.h + Pool.h + precompiled.h + Profiling.cpp + PropertiesIterator.h + PropertiesIteratorView.cpp + Property.cpp + PropertyDefinition.cpp + PropertyDictionary.cpp + PropertyParserAnimation.cpp + PropertyParserAnimation.h + PropertyParserBoxShadow.cpp + PropertyParserBoxShadow.h + PropertyParserColorStopList.cpp + PropertyParserColorStopList.h + PropertyParserColour.cpp + PropertyParserColour.h + PropertyParserDecorator.cpp + PropertyParserDecorator.h + PropertyParserFilter.cpp + PropertyParserFilter.h + PropertyParserFontEffect.cpp + PropertyParserFontEffect.h + PropertyParserKeyword.cpp + PropertyParserKeyword.h + PropertyParserNumber.cpp + PropertyParserNumber.h + PropertyParserRatio.cpp + PropertyParserRatio.h + PropertyParserString.cpp + PropertyParserString.h + PropertyParserTransform.cpp + PropertyParserTransform.h + PropertyShorthandDefinition.h + PropertySpecification.cpp + RenderInterface.cpp + RenderInterfaceCompatibility.cpp + RenderManager.cpp + RenderManagerAccess.cpp + RenderManagerAccess.h + ScrollController.cpp + ScrollController.h + Spritesheet.cpp + Stream.cpp + StreamFile.cpp + StreamFile.h + StreamMemory.cpp + StringUtilities.cpp + StyleSheet.cpp + StyleSheetContainer.cpp + StyleSheetFactory.cpp + StyleSheetFactory.h + StyleSheetNode.cpp + StyleSheetNode.h + StyleSheetParser.cpp + StyleSheetParser.h + StyleSheetSelector.cpp + StyleSheetSelector.h + StyleSheetSpecification.cpp + SystemInterface.cpp + Template.cpp + Template.h + TemplateCache.cpp + TemplateCache.h + Texture.cpp + TextureDatabase.cpp + TextureDatabase.h + TextureLayout.cpp + TextureLayout.h + TextureLayoutRectangle.cpp + TextureLayoutRectangle.h + TextureLayoutRow.cpp + TextureLayoutRow.h + TextureLayoutTexture.cpp + TextureLayoutTexture.h + Traits.cpp + Transform.cpp + TransformPrimitive.cpp + TransformState.cpp + TransformState.h + TransformUtilities.cpp + TransformUtilities.h + Tween.cpp + TypeConverter.cpp + URL.cpp + Variant.cpp + WidgetScroll.cpp + WidgetScroll.h + XMLNodeHandler.cpp + XMLNodeHandlerBody.cpp + XMLNodeHandlerBody.h + XMLNodeHandlerDefault.cpp + XMLNodeHandlerDefault.h + XMLNodeHandlerHead.cpp + XMLNodeHandlerHead.h + XMLNodeHandlerTemplate.cpp + XMLNodeHandlerTemplate.h + XMLParser.cpp + XMLParseTools.cpp + XMLParseTools.h +) + +# Add public headers as files in the project (it's not necessary but convenient for IDE integration) +# Setting them as PRIVATE so that it's addition doesn't propagate, it won't affect availability since +# the entire include directory has already been declared as public +target_sources(rmlui_core PRIVATE + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Config/Config.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/BaseXMLParser.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Box.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/CallbackTexture.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Colour.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Colour.inl" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/CompiledFilterShader.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ComputedValues.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Containers/itlib/flat_map.hpp" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Containers/itlib/flat_set.hpp" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Containers/robin_hood.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Context.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ContextInstancer.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ConvolutionFilter.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Core.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/DataModelHandle.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/DataStructHandle.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/DataTypeRegister.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/DataTypes.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/DataVariable.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Debug.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/DecorationTypes.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Decorator.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Dictionary.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/EffectSpecification.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Element.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Element.inl" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ElementDocument.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ElementInstancer.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ElementScroll.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Elements/ElementForm.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Elements/ElementFormControl.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Elements/ElementFormControlInput.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Elements/ElementFormControlSelect.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Elements/ElementFormControlTextArea.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Elements/ElementProgress.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Elements/ElementTabSet.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ElementText.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ElementUtilities.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Event.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/EventInstancer.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/EventListener.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/EventListenerInstancer.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Factory.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/FileInterface.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Filter.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/FontEffect.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/FontEffectInstancer.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/FontEngineInterface.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/FontGlyph.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/FontMetrics.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Geometry.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Header.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ID.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Input.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Log.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Math.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Matrix4.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Matrix4.inl" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Mesh.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/MeshUtilities.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/NumericValue.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ObserverPtr.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Platform.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Plugin.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Profiling.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/PropertiesIteratorView.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Property.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/PropertyDefinition.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/PropertyDictionary.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/PropertyIdSet.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/PropertyParser.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/PropertySpecification.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Rectangle.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/RenderBox.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/RenderInterface.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/RenderInterfaceCompatibility.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/RenderManager.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ScriptInterface.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ScrollTypes.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Span.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Spritesheet.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/StableVector.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Stream.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/StreamMemory.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/StringUtilities.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/StyleSheet.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/StyleSheetContainer.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/StyleSheetSpecification.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/StyleSheetTypes.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/StyleTypes.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/SystemInterface.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/TextInputContext.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/TextInputHandler.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/TextShapingContext.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Texture.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Traits.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Transform.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/TransformPrimitive.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Tween.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/TypeConverter.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/TypeConverter.inl" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Types.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/UniqueRenderResource.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Unit.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/URL.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Utilities.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Variant.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Variant.inl" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Vector2.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Vector2.inl" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Vector3.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Vector3.inl" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Vector4.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Vector4.inl" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Vertex.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/XMLNodeHandler.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/XMLParser.h" +) + +set_common_target_options(rmlui_core) + +target_include_directories(rmlui_core PRIVATE "${PROJECT_SOURCE_DIR}/Include") +target_include_directories(rmlui_core INTERFACE + "$" + "$" +) + +add_library(RmlUi::Core ALIAS rmlui_core) + +set_target_properties(rmlui_core PROPERTIES + # Add export name so that it can be exported with a namespaced name instead + # of using the name we actually used to declare the target + EXPORT_NAME "Core" + + # Change output name of the final library file + OUTPUT_NAME "rmlui" +) + +generate_rmlui_version_string() +target_compile_definitions(rmlui_core PRIVATE "RMLUI_VERSION=\"${RMLUI_VERSION_SHORT}\"") + +add_subdirectory("Elements") +add_subdirectory("Layout") + +# Set up definitions to export functions and classes as appropriate +get_target_property(rmlui_core_TYPE rmlui_core "TYPE") +if(rmlui_core_TYPE STREQUAL "STATIC_LIBRARY") + # If RmlUi is being compiled as a static library, notify code to disable usage of __dllspec() + # Since public headers also detect it, it needs to be a public definition + target_compile_definitions(rmlui_core PUBLIC "RMLUI_STATIC_LIB") +elseif(rmlui_core_TYPE STREQUAL "SHARED_LIBRARY") + # If RmlUi is being compiled as a shared library, notify code to export functions using __dllspec() + # For applications consuming the library, the headers will automatically use dllimport + # Platform and compiler handling is already done by code via pre-processor macros + target_compile_definitions(rmlui_core PRIVATE "RMLUI_CORE_EXPORTS") +endif() +unset(rmlui_core_TYPE) + +if(RMLUI_FONT_ENGINE STREQUAL "freetype") + # Include the source files for the default font engine. + add_subdirectory("FontEngineDefault") + + # RMLUI_CMAKE_MINIMUM_VERSION_RAISE_NOTICE: + # From CMake 3.13 the next line can be moved into `FontEngineDefault/CMakeLists.txt`, see CMP0079. + target_link_libraries(rmlui_core PRIVATE Freetype::Freetype) +endif() + +if(RMLUI_LOTTIE_PLUGIN) + # RMLUI_CMAKE_MINIMUM_VERSION_RAISE_NOTICE: + # From CMake 3.13 we could move this to `Lottie/CMakeLists.txt`, see CMP0079. + target_link_libraries(rmlui_core PRIVATE rlottie::rlottie) +endif() + +if(RMLUI_SVG_PLUGIN) + # RMLUI_CMAKE_MINIMUM_VERSION_RAISE_NOTICE: + # From CMake 3.13 we could move this to `Lottie/CMakeLists.txt`, see CMP0079. + target_link_libraries(rmlui_core PRIVATE lunasvg::lunasvg) +endif() + +if(RMLUI_TRACY_PROFILING) + if(CMAKE_CONFIGURATION_TYPES AND RMLUI_TRACY_CONFIGURATION) + target_link_libraries(rmlui_core PUBLIC "$<$:Tracy::TracyClient>") + target_compile_definitions(rmlui_core PUBLIC "$<$:RMLUI_TRACY_PROFILING>") + if(RMLUI_TRACY_MEMORY_PROFILING) + target_compile_definitions(rmlui_core PRIVATE "$<$:RMLUI_TRACY_MEMORY_PROFILING>") + endif() + message(STATUS "Tracy profiling enabled in configuration `Tracy`.") + else() + target_link_libraries(rmlui_core PUBLIC Tracy::TracyClient) + target_compile_definitions(rmlui_core PUBLIC "RMLUI_TRACY_PROFILING") + if(RMLUI_TRACY_MEMORY_PROFILING) + target_compile_definitions(rmlui_core PRIVATE "RMLUI_TRACY_MEMORY_PROFILING") + endif() + message(STATUS "Tracy profiling enabled.") + endif() +endif() + +if(NOT RMLUI_THIRDPARTY_CONTAINERS) + target_compile_definitions(rmlui_core PUBLIC "RMLUI_NO_THIRDPARTY_CONTAINERS") + message(STATUS "Disabling third-party containers for RmlUi.") +endif() + +if(RMLUI_CUSTOM_RTTI) + target_compile_definitions(rmlui_core PUBLIC "RMLUI_CUSTOM_RTTI") + message(STATUS "Enabling custom RTTI for RmlUi.") +endif() + +if(RMLUI_MATRIX_ROW_MAJOR) + target_compile_definitions(rmlui_core PUBLIC "RMLUI_MATRIX_ROW_MAJOR") + message(STATUS "Configuring RmlUi to use row-major matrix types.") +endif() + +# RMLUI_CMAKE_MINIMUM_VERSION_RAISE_NOTICE: +# From CMake 3.16 we can skip the version check. +if(RMLUI_PRECOMPILED_HEADERS AND CMAKE_VERSION VERSION_GREATER_EQUAL "3.16") + target_precompile_headers(rmlui_core PRIVATE "${PROJECT_SOURCE_DIR}/Source/Core/precompiled.h") +elseif(RMLUI_PRECOMPILED_HEADERS) + message(STATUS "Could not enable precompiled headers, requires CMake version 3.16 or greater.") +endif() + +if(RMLUI_CUSTOM_CONFIGURATION AND RMLUI_CUSTOM_CONFIGURATION_FILE) + target_compile_definitions(rmlui_core PUBLIC "RMLUI_CUSTOM_CONFIGURATION_FILE=\"${RMLUI_CUSTOM_CONFIGURATION_FILE}\"") + message(STATUS "Including ${RMLUI_CUSTOM_CONFIGURATION_FILE} instead of ") +endif() +if(RMLUI_CUSTOM_CONFIGURATION AND RMLUI_CUSTOM_INCLUDE_DIRS) + target_include_directories(rmlui_core PUBLIC ${RMLUI_CUSTOM_INCLUDE_DIRS}) +endif() +if(RMLUI_CUSTOM_CONFIGURATION AND RMLUI_CUSTOM_LINK_LIBRARIES) + target_link_libraries(rmlui_core PUBLIC ${RMLUI_CUSTOM_LINK_LIBRARIES}) +endif() + +# RMLUI_CMAKE_MINIMUM_VERSION_RAISE_NOTICE: +# We use default paths provided from GNUInstallDirs. From CMake 3.14 these paths (CMAKE_INSTALL_...) will be used +# automatically and can be removed from the following call. The same applies to many other calls to install(TARGETS...). +# Note that GNUInstallDirs should still be included in the project root. +install(TARGETS rmlui_core + EXPORT RmlUiTargets + ${RMLUI_RUNTIME_DEPENDENCY_SET_ARG} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} +) +install_target_pdb(rmlui_core) diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/CallbackTexture.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/CallbackTexture.cpp new file mode 100644 index 00000000..136b7602 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/CallbackTexture.cpp @@ -0,0 +1,96 @@ +#include "../../Include/RmlUi/Core/CallbackTexture.h" +#include "../../Include/RmlUi/Core/Texture.h" +#include "RenderManagerAccess.h" + +namespace Rml { + +void CallbackTexture::Release() +{ + if (resource_handle != StableVectorIndex::Invalid) + { + RenderManagerAccess::ReleaseResource(render_manager, *this); + Clear(); + } +} + +Rml::CallbackTexture::operator Texture() const +{ + return Texture(render_manager, resource_handle); +} + +CallbackTextureInterface::CallbackTextureInterface(RenderManager& render_manager, RenderInterface& render_interface, TextureHandle& texture_handle, + Vector2i& dimensions) : render_manager(render_manager), render_interface(render_interface), texture_handle(texture_handle), dimensions(dimensions) +{} + +bool CallbackTextureInterface::GenerateTexture(Span source, Vector2i new_dimensions) const +{ + if (texture_handle) + { + RMLUI_ERRORMSG("Texture already set"); + return false; + } + texture_handle = render_interface.GenerateTexture(source, new_dimensions); + if (texture_handle) + dimensions = new_dimensions; + return texture_handle != TextureHandle{}; +} + +void CallbackTextureInterface::SaveLayerAsTexture() const +{ + if (texture_handle) + { + RMLUI_ERRORMSG("Texture already set"); + return; + } + + const Rectanglei region = render_manager.GetScissorRegion(); + if (!region.Valid()) + { + RMLUI_ERRORMSG("Save layer as texture requires a scissor region to be set first"); + return; + } + + texture_handle = render_interface.SaveLayerAsTexture(); + if (texture_handle) + dimensions = region.Size(); +} + +void CallbackTextureInterface::SetTextureHandle(TextureHandle handle, Vector2i new_dimensions) const +{ + if (texture_handle) + { + RMLUI_ERRORMSG("Texture already set"); + return; + } + + texture_handle = handle; + dimensions = new_dimensions; +} + +RenderManager& CallbackTextureInterface::GetRenderManager() const +{ + return render_manager; +} + +CallbackTextureSource::CallbackTextureSource(CallbackTextureFunction&& callback) : callback(std::move(callback)) {} + +CallbackTextureSource::CallbackTextureSource(CallbackTextureSource&& other) noexcept : + callback(std::move(other.callback)), textures(std::move(other.textures)) +{} + +CallbackTextureSource& CallbackTextureSource::operator=(CallbackTextureSource&& other) noexcept +{ + callback = std::move(other.callback); + textures = std::move(other.textures); + return *this; +} + +Texture CallbackTextureSource::GetTexture(RenderManager& render_manager) const +{ + CallbackTexture& texture = textures[&render_manager]; + if (!texture) + texture = render_manager.MakeCallbackTexture(callback); + return Texture(texture); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Clock.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Clock.cpp new file mode 100644 index 00000000..70a49966 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Clock.cpp @@ -0,0 +1,16 @@ +#include "Clock.h" +#include "../../Include/RmlUi/Core/Core.h" +#include "../../Include/RmlUi/Core/SystemInterface.h" + +namespace Rml { + +RMLUICORE_API double Clock::GetElapsedTime() +{ + SystemInterface* system_interface = GetSystemInterface(); + if (system_interface != nullptr) + return system_interface->GetElapsedTime(); + else + return 0; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Clock.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Clock.h new file mode 100644 index 00000000..e3ddb8a0 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Clock.h @@ -0,0 +1,17 @@ +#pragma once + +#include "../../Include/RmlUi/Core/Header.h" + +namespace Rml { + +/** + RmlUi's Interface to Time. + */ +class Clock { +public: + /// Get the elapsed time since application startup + /// @return Seconds elapsed since application startup. + RMLUICORE_API static double GetElapsedTime(); +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/CompiledFilterShader.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/CompiledFilterShader.cpp new file mode 100644 index 00000000..f0138dd5 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/CompiledFilterShader.cpp @@ -0,0 +1,32 @@ +#include "../../Include/RmlUi/Core/CompiledFilterShader.h" +#include "RenderManagerAccess.h" + +namespace Rml { + +void CompiledFilter::AddHandleTo(FilterHandleList& list) +{ + if (resource_handle != InvalidHandle()) + { + list.push_back(resource_handle); + } +} + +void CompiledFilter::Release() +{ + if (resource_handle != InvalidHandle()) + { + RenderManagerAccess::ReleaseResource(render_manager, *this); + Clear(); + } +} + +void CompiledShader::Release() +{ + if (resource_handle != InvalidHandle()) + { + RenderManagerAccess::ReleaseResource(render_manager, *this); + Clear(); + } +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ComputeProperty.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ComputeProperty.cpp new file mode 100644 index 00000000..2dff457b --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ComputeProperty.cpp @@ -0,0 +1,265 @@ +#include "ComputeProperty.h" +#include "../../Include/RmlUi/Core/ComputedValues.h" +#include "../../Include/RmlUi/Core/Property.h" +#include "../../Include/RmlUi/Core/StringUtilities.h" +#include "ControlledLifetimeResource.h" + +namespace Rml { + +struct ComputedPropertyData { + const Style::ComputedValues computed{nullptr}; +}; +static ControlledLifetimeResource computed_property_data; + +const Style::ComputedValues& DefaultComputedValues() +{ + return computed_property_data->computed; +} + +void InitializeComputeProperty() +{ + computed_property_data.Initialize(); +} + +void ShutdownComputeProperty() +{ + computed_property_data.Shutdown(); +} + +static constexpr float PixelsPerInch = 96.0f; + +static float ComputePPILength(NumericValue value, float dp_ratio) +{ + RMLUI_ASSERT(Any(value.unit & Unit::PPI_UNIT)); + + // Values based on pixels-per-inch. Scaled by the dp-ratio as a placeholder solution until we make the pixel unit itself scalable. + const float inch = value.number * PixelsPerInch * dp_ratio; + + switch (value.unit) + { + case Unit::INCH: return inch; + case Unit::CM: return inch * (1.0f / 2.54f); + case Unit::MM: return inch * (1.0f / 25.4f); + case Unit::PT: return inch * (1.0f / 72.0f); + case Unit::PC: return inch * (1.0f / 6.0f); + default: break; + } + + RMLUI_ERROR; + return 0.f; +} + +float ComputeLength(NumericValue value, float font_size, float document_font_size, float dp_ratio, Vector2f vp_dimensions) +{ + if (Any(value.unit & Unit::PPI_UNIT)) + return ComputePPILength(value, dp_ratio); + + switch (value.unit) + { + case Unit::PX: return value.number; + case Unit::EM: return value.number * font_size; + case Unit::REM: return value.number * document_font_size; + case Unit::DP: return value.number * dp_ratio; + case Unit::VW: return value.number * vp_dimensions.x * 0.01f; + case Unit::VH: return value.number * vp_dimensions.y * 0.01f; + default: break; + } + + RMLUI_ERROR; + return 0.0f; +} + +float ComputeAngle(NumericValue value) +{ + switch (value.unit) + { + case Unit::NUMBER: + case Unit::RAD: return value.number; + + case Unit::DEG: return Math::DegreesToRadians(value.number); + default: break; + } + + RMLUI_ERROR; + return 0.0f; +} + +float ComputeFontsize(NumericValue value, const Style::ComputedValues& values, const Style::ComputedValues* parent_values, + const Style::ComputedValues* document_values, float dp_ratio, Vector2f vp_dimensions) +{ + if (Any(value.unit & (Unit::PERCENT | Unit::EM | Unit::REM))) + { + // Relative values are based on the parent's or document's font size instead of our own. + float multiplier = 1.0f; + + switch (value.unit) + { + case Unit::PERCENT: + multiplier = 0.01f; + //-fallthrough + case Unit::EM: + if (!parent_values) + return 0; + return value.number * multiplier * parent_values->font_size(); + + case Unit::REM: + // If the current element is a document, the rem unit is relative to the default size. + if (!document_values || &values == document_values) + return value.number * DefaultComputedValues().font_size(); + + // Otherwise it is relative to the document font size. + return value.number * document_values->font_size(); + default: break; + } + } + + // Font-relative lengths handled above, other lengths should be handled as normal. + return ComputeLength(value, 0.f, 0.f, dp_ratio, vp_dimensions); +} + +String ComputeFontFamily(String font_family) +{ + return StringUtilities::ToLower(std::move(font_family)); +} + +Style::Clip ComputeClip(const Property* property) +{ + const int value = property->Get(); + if (property->unit == Unit::KEYWORD) + return Style::Clip(static_cast(value)); + else if (property->unit == Unit::NUMBER) + return Style::Clip(Style::Clip::Type::Number, static_cast(value)); + RMLUI_ERRORMSG("Invalid clip type"); + return Style::Clip(); +} + +Style::LineHeight ComputeLineHeight(const Property* property, float font_size, float document_font_size, float dp_ratio, Vector2f vp_dimensions) +{ + if (Any(property->unit & Unit::LENGTH)) + { + float value = ComputeLength(property->GetNumericValue(), font_size, document_font_size, dp_ratio, vp_dimensions); + return Style::LineHeight(value, Style::LineHeight::Length, value); + } + + float scale_factor = 1.0f; + + switch (property->unit) + { + case Unit::NUMBER: scale_factor = property->value.Get(); break; + case Unit::PERCENT: scale_factor = property->value.Get() * 0.01f; break; + default: RMLUI_ERRORMSG("Invalid unit for line-height"); + } + + float value = font_size * scale_factor; + return Style::LineHeight(value, Style::LineHeight::Number, scale_factor); +} + +Style::VerticalAlign ComputeVerticalAlign(const Property* property, float line_height, float font_size, float document_font_size, float dp_ratio, + Vector2f vp_dimensions) +{ + if (Any(property->unit & Unit::LENGTH)) + { + float value = ComputeLength(property->GetNumericValue(), font_size, document_font_size, dp_ratio, vp_dimensions); + return Style::VerticalAlign(value); + } + else if (property->unit == Unit::PERCENT) + { + return Style::VerticalAlign(property->Get() * line_height * 0.01f); + } + + RMLUI_ASSERT(property->unit == Unit::KEYWORD); + return Style::VerticalAlign((Style::VerticalAlign::Type)property->Get()); +} + +Style::LengthPercentage ComputeLengthPercentage(const Property* property, float font_size, float document_font_size, float dp_ratio, + Vector2f vp_dimensions) +{ + using namespace Style; + if (property->unit == Unit::PERCENT) + return LengthPercentage(LengthPercentage::Percentage, property->Get()); + + return LengthPercentage(LengthPercentage::Length, + ComputeLength(property->GetNumericValue(), font_size, document_font_size, dp_ratio, vp_dimensions)); +} + +Style::LengthPercentageAuto ComputeLengthPercentageAuto(const Property* property, float font_size, float document_font_size, float dp_ratio, + Vector2f vp_dimensions) +{ + using namespace Style; + if (property->unit == Unit::PERCENT) + return LengthPercentageAuto(LengthPercentageAuto::Percentage, property->Get()); + else if (property->unit == Unit::KEYWORD) + return LengthPercentageAuto(LengthPercentageAuto::Auto); + + return LengthPercentageAuto(LengthPercentageAuto::Length, + ComputeLength(property->GetNumericValue(), font_size, document_font_size, dp_ratio, vp_dimensions)); +} + +Style::LengthPercentage ComputeOrigin(const Property* property, float font_size, float document_font_size, float dp_ratio, Vector2f vp_dimensions) +{ + using namespace Style; + static_assert( + (int)OriginX::Left == (int)OriginY::Top && (int)OriginX::Center == (int)OriginY::Center && (int)OriginX::Right == (int)OriginY::Bottom, ""); + + if (property->unit == Unit::KEYWORD) + { + float percent = 0.0f; + OriginX origin = (OriginX)property->Get(); + switch (origin) + { + case OriginX::Left: percent = 0.0f; break; + case OriginX::Center: percent = 50.0f; break; + case OriginX::Right: percent = 100.f; break; + } + return LengthPercentage(LengthPercentage::Percentage, percent); + } + else if (property->unit == Unit::PERCENT) + return LengthPercentage(LengthPercentage::Percentage, property->Get()); + + return LengthPercentage(LengthPercentage::Length, + ComputeLength(property->GetNumericValue(), font_size, document_font_size, dp_ratio, vp_dimensions)); +} + +Style::LengthPercentage ComputeMaxSize(const Property* property, float font_size, float document_font_size, float dp_ratio, Vector2f vp_dimensions) +{ + using namespace Style; + if (Any(property->unit & Unit::KEYWORD)) + return LengthPercentage(LengthPercentage::Length, FLT_MAX); + else if (Any(property->unit & Unit::PERCENT)) + return LengthPercentage(LengthPercentage::Percentage, property->Get()); + + const float length = ComputeLength(property->GetNumericValue(), font_size, document_font_size, dp_ratio, vp_dimensions); + return LengthPercentage(LengthPercentage::Length, length < 0.f ? FLT_MAX : length); +} + +uint16_t ComputeBorderWidth(float computed_length) +{ + if (computed_length <= 0.f) + return 0; + + if (computed_length <= 1.f) + return 1; + + return uint16_t(computed_length + 0.5f); +} + +String GetFontFaceDescription(const String& font_family, Style::FontStyle style, Style::FontWeight weight) +{ + String font_attributes; + + if (style == Style::FontStyle::Italic) + font_attributes += "italic, "; + if (weight == Style::FontWeight::Bold) + font_attributes += "bold, "; + else if (weight != Style::FontWeight::Auto && weight != Style::FontWeight::Normal) + font_attributes += "weight=" + ToString((int)weight) + ", "; + + if (font_attributes.empty()) + font_attributes = "regular"; + else + font_attributes.resize(font_attributes.size() - 2); + + return CreateString("'%s' [%s]", font_family.c_str(), font_attributes.c_str()); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ComputeProperty.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ComputeProperty.h new file mode 100644 index 00000000..e061a79c --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ComputeProperty.h @@ -0,0 +1,46 @@ +#pragma once + +#include "../../Include/RmlUi/Core/NumericValue.h" +#include "../../Include/RmlUi/Core/StyleTypes.h" + +namespace Rml { + +class Property; + +// Note that numbers and percentages are not lengths, they have to be resolved elsewhere. +float ComputeLength(NumericValue value, float font_size, float document_font_size, float dp_ratio, Vector2f vp_dimensions); + +float ComputeAngle(NumericValue value); + +float ComputeFontsize(NumericValue value, const Style::ComputedValues& values, const Style::ComputedValues* parent_values, + const Style::ComputedValues* document_values, float dp_ratio, Vector2f vp_dimensions); + +String ComputeFontFamily(String font_family); + +Style::Clip ComputeClip(const Property* property); + +Style::LineHeight ComputeLineHeight(const Property* property, float font_size, float document_font_size, float dp_ratio, Vector2f vp_dimensions); + +Style::VerticalAlign ComputeVerticalAlign(const Property* property, float line_height, float font_size, float document_font_size, float dp_ratio, + Vector2f vp_dimensions); + +Style::LengthPercentage ComputeLengthPercentage(const Property* property, float font_size, float document_font_size, float dp_ratio, + Vector2f vp_dimensions); + +Style::LengthPercentageAuto ComputeLengthPercentageAuto(const Property* property, float font_size, float document_font_size, float dp_ratio, + Vector2f vp_dimensions); + +Style::LengthPercentage ComputeOrigin(const Property* property, float font_size, float document_font_size, float dp_ratio, Vector2f vp_dimensions); + +Style::LengthPercentage ComputeMaxSize(const Property* property, float font_size, float document_font_size, float dp_ratio, Vector2f vp_dimensions); + +uint16_t ComputeBorderWidth(float computed_length); + +String GetFontFaceDescription(const String& font_family, Style::FontStyle style, Style::FontWeight weight); + +const Style::ComputedValues& DefaultComputedValues(); + +void InitializeComputeProperty(); +void ShutdownComputeProperty(); + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ComputedValues.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ComputedValues.cpp new file mode 100644 index 00000000..eaf2cb6a --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ComputedValues.cpp @@ -0,0 +1,71 @@ +#include "../../Include/RmlUi/Core/ComputedValues.h" +#include "../../Include/RmlUi/Core/Element.h" +#include "ComputeProperty.h" + +namespace Rml { + +const AnimationList* Style::ComputedValues::animation() const +{ + if (auto p = element->GetLocalProperty(PropertyId::Animation)) + { + if (p->unit == Unit::ANIMATION) + return &(p->value.GetReference()); + } + return nullptr; +} + +const TransitionList* Style::ComputedValues::transition() const +{ + if (auto p = element->GetLocalProperty(PropertyId::Transition)) + { + if (p->unit == Unit::TRANSITION) + return &(p->value.GetReference()); + } + return nullptr; +} + +String Style::ComputedValues::font_family() const +{ + if (auto p = element->GetProperty(PropertyId::FontFamily)) + return ComputeFontFamily(p->Get()); + + return String(); +} + +String Style::ComputedValues::cursor() const +{ + if (auto p = element->GetProperty(PropertyId::Cursor)) + return p->Get(); + + return String(); +} + +float Style::ComputedValues::letter_spacing() const +{ + if (inherited.has_letter_spacing) + { + if (auto p = element->GetProperty(PropertyId::LetterSpacing)) + return element->ResolveLength(p->GetNumericValue()); + } + return 0.f; +} + +float ResolveValueOr(Style::LengthPercentageAuto length, float base_value, float default_value) +{ + if (length.type == Style::LengthPercentageAuto::Length) + return length.value; + else if (length.type == Style::LengthPercentageAuto::Percentage && base_value >= 0.f) + return length.value * 0.01f * base_value; + return default_value; +} + +float ResolveValueOr(Style::LengthPercentage length, float base_value, float default_value) +{ + if (length.type == Style::LengthPercentage::Length) + return length.value; + else if (length.type == Style::LengthPercentage::Percentage && base_value >= 0.f) + return length.value * 0.01f * base_value; + return default_value; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Context.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Context.cpp new file mode 100644 index 00000000..6af67fb5 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Context.cpp @@ -0,0 +1,1637 @@ +#include "../../Include/RmlUi/Core/Context.h" +#include "../../Include/RmlUi/Core/ComputedValues.h" +#include "../../Include/RmlUi/Core/ContextInstancer.h" +#include "../../Include/RmlUi/Core/Core.h" +#include "../../Include/RmlUi/Core/DataModelHandle.h" +#include "../../Include/RmlUi/Core/Debug.h" +#include "../../Include/RmlUi/Core/ElementDocument.h" +#include "../../Include/RmlUi/Core/ElementUtilities.h" +#include "../../Include/RmlUi/Core/Factory.h" +#include "../../Include/RmlUi/Core/Profiling.h" +#include "../../Include/RmlUi/Core/RenderManager.h" +#include "../../Include/RmlUi/Core/StreamMemory.h" +#include "../../Include/RmlUi/Core/SystemInterface.h" +#include "DataModel.h" +#include "EventDispatcher.h" +#include "PluginRegistry.h" +#include "ScrollController.h" +#include "StreamFile.h" +#include +#include +#include +#include + +namespace Rml { + +static constexpr float DOUBLE_CLICK_TIME = 0.5f; // [s] +static constexpr float DOUBLE_CLICK_MAX_DIST = 3.f; // [dp] +static constexpr float UNIT_SCROLL_LENGTH = 80.f; // [dp] + +// If the user stops scrolling for this amount of time in seconds before touch/click release, don't apply inertia. +static constexpr float SCROLL_INERTIA_DELAY = 0.1f; +static constexpr float TOUCH_MOVEMENT_DECAY_RATE = 5.0f; +static constexpr float TOUCH_CLICK_MAX_DISTANCE = DOUBLE_CLICK_MAX_DIST; // [dp] + +static void DebugVerifyLocaleSetting() +{ +#ifdef RMLUI_DEBUG + constexpr float expected_value = 1000.5f; + const Rml::String expected_string = "1000.5"; + const Rml::String formatted_string = Rml::ToString(expected_value); + const float parsed_value = Rml::FromString(expected_string); + + const char* description = "RmlUi expects the global locale to be set to the default minimal \"C\" locale, please see `std::setlocale`."; + if (formatted_string != expected_string) + { + Rml::Log::Message(Rml::Log::LT_ERROR, + "Incompatible locale setting detected while formatting %f. Formatted: \"%s\". Expected: \"%s\". Current locale: %s. %s", expected_value, + formatted_string.c_str(), expected_string.c_str(), std::setlocale(LC_ALL, nullptr), description); + } + if (parsed_value != expected_value) + { + Rml::Log::Message(Rml::Log::LT_ERROR, + "Incompatible locale setting detected while parsing \"%s\". Parsed: %f. Expected: %f. Current locale: %s. %s", expected_string.c_str(), + parsed_value, expected_value, std::setlocale(LC_ALL, nullptr), description); + } +#endif +} + +Context::Context(const String& name, RenderManager* render_manager, TextInputHandler* text_input_handler) : + name(name), render_manager(render_manager), text_input_handler(text_input_handler) +{ + instancer = nullptr; + + root = Factory::InstanceElement(nullptr, "*", "#root", XMLAttributes()); + root->SetId(name); + root->SetOffset(Vector2f(0, 0), nullptr); + root->SetProperty(PropertyId::ZIndex, Property(0, Unit::NUMBER)); + + cursor_proxy = Factory::InstanceElement(nullptr, documents_base_tag, documents_base_tag, XMLAttributes()); + ElementDocument* cursor_proxy_document = rmlui_dynamic_cast(cursor_proxy.get()); + RMLUI_ASSERT(cursor_proxy_document); + cursor_proxy_document->context = this; + + // The cursor proxy takes the style from its cloned element's document. The latter may define style rules for `` which we don't want on the + // proxy. Thus, we override some properties here that we in particular don't want to inherit from the client document, especially those that + // result in decoration of the body element. + cursor_proxy_document->SetProperty(PropertyId::BackgroundColor, Property(Colourb(255, 255, 255, 0), Unit::COLOUR)); + cursor_proxy_document->SetProperty(PropertyId::BorderTopWidth, Property(0, Unit::PX)); + cursor_proxy_document->SetProperty(PropertyId::BorderRightWidth, Property(0, Unit::PX)); + cursor_proxy_document->SetProperty(PropertyId::BorderBottomWidth, Property(0, Unit::PX)); + cursor_proxy_document->SetProperty(PropertyId::BorderLeftWidth, Property(0, Unit::PX)); + cursor_proxy_document->SetProperty(PropertyId::Decorator, Property()); + cursor_proxy_document->SetProperty(PropertyId::OverflowX, Property(Style::Overflow::Visible)); + cursor_proxy_document->SetProperty(PropertyId::OverflowY, Property(Style::Overflow::Visible)); + + document_focus_history.push_back(root.get()); + focus = root.get(); + hover = nullptr; + active = nullptr; + drag = nullptr; + + drag_started = false; + drag_verbose = false; + drag_clone = nullptr; + drag_hover = nullptr; + last_click_element = nullptr; + last_click_time = 0; + + mouse_active = false; + enable_cursor = true; + + scroll_controller = MakeUnique(); +} + +Context::~Context() +{ + PluginRegistry::NotifyContextDestroy(this); + + UnloadAllDocuments(); + + ReleaseUnloadedDocuments(); + + for (const auto& pair : data_models) + PluginRegistry::NotifyDataModelDestroy(this, pair.first); + data_models.clear(); + + root.reset(); + + cursor_proxy.reset(); + + instancer = nullptr; +} + +const String& Context::GetName() const +{ + return name; +} + +void Context::SetDimensions(const Vector2i _dimensions) +{ + if (dimensions != _dimensions) + { + dimensions = _dimensions; + render_manager->SetViewport(dimensions); + root->SetBox(Box(Vector2f(dimensions))); + root->DirtyLayout(); + + for (int i = 0; i < root->GetNumChildren(); ++i) + { + ElementDocument* document = root->GetChild(i)->GetOwnerDocument(); + if (document != nullptr) + { + document->DirtyMediaQueries(); + document->DirtyVwAndVhProperties(); + document->DirtyLayout(); + document->DirtyPosition(); + document->DispatchEvent(EventId::Resize, Dictionary()); + } + } + } +} + +Vector2i Context::GetDimensions() const +{ + return dimensions; +} + +void Context::SetDensityIndependentPixelRatio(float dp_ratio) +{ + if (density_independent_pixel_ratio != dp_ratio) + { + density_independent_pixel_ratio = dp_ratio; + + for (int i = 0; i < root->GetNumChildren(true); ++i) + { + ElementDocument* document = root->GetChild(i)->GetOwnerDocument(); + if (document) + { + document->DirtyMediaQueries(); + document->OnDpRatioChangeRecursive(); + } + } + } +} + +float Context::GetDensityIndependentPixelRatio() const +{ + return density_independent_pixel_ratio; +} + +bool Context::Update() +{ + RMLUI_ZoneScoped; + DebugVerifyLocaleSetting(); + + next_update_timeout = std::numeric_limits::infinity(); + + if (scroll_controller->Update(mouse_position, density_independent_pixel_ratio)) + RequestNextUpdate(0); + + // Update the hover chain to detect any new or moved elements under the mouse. + if (mouse_active) + UpdateHoverChain(mouse_position); + + // Update all the data models before updating properties and layout. + for (auto& data_model : data_models) + data_model.second->Update(true); + + // The style definition of each document should be independent of each other. By manually resetting these flags we avoid unnecessary definition + // lookups in unrelated documents, such as when adding a new document. Adding an element dirties the parent definition, which in this case is the + // root. By extension the definition of all the other documents are also dirtied, unnecessarily. + root->dirty_definition = false; + root->dirty_child_definitions = false; + + root->Update(density_independent_pixel_ratio, Vector2f(dimensions)); + + for (int i = 0; i < root->GetNumChildren(); ++i) + { + if (auto doc = root->GetChild(i)->GetOwnerDocument()) + { + doc->UpdateLayout(); + doc->UpdatePosition(); + } + } + + // Release any documents that were unloaded during the update. + ReleaseUnloadedDocuments(); + + return true; +} + +bool Context::Render() +{ + RMLUI_ZoneScoped; + + render_manager->PrepareRender(dimensions); + + root->Render(); + + // Render the cursor proxy so that any attached drag clone will be rendered below the cursor. + if (drag_clone) + { + static_cast(*cursor_proxy).UpdateDocument(); + cursor_proxy->SetOffset( + Vector2f((float)Math::Clamp(mouse_position.x, 0, dimensions.x), (float)Math::Clamp(mouse_position.y, 0, dimensions.y)), nullptr); + cursor_proxy->Render(); + } + + render_manager->ResetState(); + + return true; +} + +ElementDocument* Context::CreateDocument(const String& instancer_name) +{ + ElementPtr element = Factory::InstanceElement(nullptr, instancer_name, documents_base_tag, XMLAttributes()); + if (!element) + { + Log::Message(Log::LT_ERROR, "Failed to instance document on instancer_name '%s', instancer returned nullptr.", instancer_name.c_str()); + return nullptr; + } + + ElementDocument* document = rmlui_dynamic_cast(element.get()); + if (!document) + { + Log::Message(Log::LT_ERROR, + "Failed to instance document on instancer_name '%s', Found type '%s', was expecting derivative of ElementDocument.", + instancer_name.c_str(), rmlui_type_name(*element)); + return nullptr; + } + + document->context = this; + root->AppendChild(std::move(element)); + + PluginRegistry::NotifyDocumentLoad(document); + + return document; +} + +ElementDocument* Context::LoadDocument(const String& document_path) +{ + auto stream = MakeUnique(); + + if (!stream->Open(document_path)) + return nullptr; + + ElementDocument* document = LoadDocument(stream.get()); + + return document; +} + +ElementDocument* Context::LoadDocument(Stream* stream) +{ + DebugVerifyLocaleSetting(); + PluginRegistry::NotifyDocumentOpen(this, stream->GetSourceURL().GetURL()); + + ElementPtr element = Factory::InstanceDocumentStream(this, stream, GetDocumentsBaseTag()); + if (!element) + return nullptr; + + ElementDocument* document = rmlui_static_cast(element.get()); + + root->AppendChild(std::move(element)); + + // The 'load' event is fired before updating the document, because the user might + // need to initalize things before running an update. The drawback is that computed + // values and layouting are not performed yet, resulting in default values when + // querying such information in the event handler. + PluginRegistry::NotifyDocumentLoad(document); + document->DispatchEvent(EventId::Load, Dictionary()); + + // Data models are updated after the 'load' event so that the user has a chance to change + // any data variables first. We do not clear dirty variables here, since users may need to + // retrieve whether or not eg. a data variable has changed in a controller. + for (auto& data_model : data_models) + data_model.second->Update(false); + + document->UpdateDocument(); + + return document; +} + +ElementDocument* Context::LoadDocumentFromMemory(const String& string, const String& source_url) +{ + // Open the stream based on the string contents. + auto stream = MakeUnique(reinterpret_cast(string.c_str()), string.size()); + + stream->SetSourceURL(source_url); + + // Load the document from the stream. + ElementDocument* document = LoadDocument(stream.get()); + + return document; +} + +void Context::UnloadDocument(ElementDocument* _document) +{ + // Has this document already been unloaded? + for (size_t i = 0; i < unloaded_documents.size(); ++i) + { + if (unloaded_documents[i].get() == _document) + return; + } + + ElementDocument* document = _document; + + if (document->GetParentNode() == root.get()) + { + // Dispatch the unload notifications. + document->DispatchEvent(EventId::Unload, Dictionary()); + PluginRegistry::NotifyDocumentUnload(document); + + // Move document to a temporary location to be released later. + unloaded_documents.push_back(root->RemoveChild(document)); + } + + // Remove the item from the focus history. + ElementList::iterator itr = std::find(document_focus_history.begin(), document_focus_history.end(), document); + if (itr != document_focus_history.end()) + document_focus_history.erase(itr); + + // Focus to the previous document if the old document is the current focus. + if (focus && focus->GetOwnerDocument() == document) + { + focus = nullptr; + document_focus_history.back()->GetFocusLeafNode()->Focus(); + } + + // Clear the active element if the old document is the active element. + if (active && active->GetOwnerDocument() == document) + { + active = nullptr; + } + + // Clear other pointers to elements whose owner was deleted + if (drag && drag->GetOwnerDocument() == document) + { + drag = nullptr; + ReleaseDragClone(); + } + + if (drag_hover && drag_hover->GetOwnerDocument() == document) + { + drag_hover = nullptr; + } + + // Rebuild the hover state. + UpdateHoverChain(mouse_position); +} + +void Context::UnloadAllDocuments() +{ + // Unload all children. + while (root->GetNumChildren(true) > 0) + UnloadDocument(root->GetChild(0)->GetOwnerDocument()); + + // The element lists may point to elements that are getting removed. + active_chain.clear(); + hover_chain.clear(); + drag_hover_chain.clear(); +} + +void Context::EnableMouseCursor(bool enable) +{ + // The cursor is set to an invalid name so that it is forced to update in the next update loop. + cursor_name = ":reset:"; + enable_cursor = enable; +} + +void Context::ActivateTheme(const String& theme_name, bool activate) +{ + bool theme_changed = false; + + if (activate) + theme_changed = active_themes.insert(theme_name).second; + else + theme_changed = (active_themes.erase(theme_name) > 0); + + if (theme_changed) + { + for (int i = 0; i < root->GetNumChildren(true); ++i) + { + if (ElementDocument* document = root->GetChild(i)->GetOwnerDocument()) + document->DirtyMediaQueries(); + } + } +} + +bool Context::IsThemeActive(const String& theme_name) const +{ + return active_themes.count(theme_name); +} + +ElementDocument* Context::GetDocument(const String& id) +{ + for (int i = 0; i < root->GetNumChildren(); i++) + { + ElementDocument* document = root->GetChild(i)->GetOwnerDocument(); + if (document == nullptr) + continue; + + if (document->GetId() == id) + return document; + } + + return nullptr; +} + +ElementDocument* Context::GetDocument(int index) +{ + Element* element = root->GetChild(index); + if (element == nullptr) + return nullptr; + + return element->GetOwnerDocument(); +} + +int Context::GetNumDocuments() const +{ + return root->GetNumChildren(); +} + +Element* Context::GetHoverElement() +{ + return hover; +} + +Element* Context::GetFocusElement() +{ + return focus; +} + +Element* Context::GetRootElement() +{ + return root.get(); +} + +void Context::PullDocumentToFront(ElementDocument* document) +{ + if (document != root->GetLastChild()) + { + // Calling RemoveChild() / AppendChild() would be cleaner, but that dirties the document's layout + // unnecessarily, so we'll go under the hood here. + for (int i = 0; i < root->GetNumChildren(); ++i) + { + if (root->GetChild(i) == document) + { + ElementPtr element = std::move(root->children[i]); + root->children.erase(root->children.begin() + i); + root->children.insert(root->children.begin() + root->GetNumChildren(), std::move(element)); + + root->DirtyStackingContext(); + } + } + } +} + +void Context::PushDocumentToBack(ElementDocument* document) +{ + if (document != root->GetFirstChild()) + { + // See PullDocumentToFront(). + for (int i = 0; i < root->GetNumChildren(); ++i) + { + if (root->GetChild(i) == document) + { + ElementPtr element = std::move(root->children[i]); + root->children.erase(root->children.begin() + i); + root->children.insert(root->children.begin(), std::move(element)); + + root->DirtyStackingContext(); + } + } + } +} + +void Context::UnfocusDocument(ElementDocument* document) +{ + auto it = std::find(document_focus_history.begin(), document_focus_history.end(), document); + if (it != document_focus_history.end()) + document_focus_history.erase(it); + + if (!document_focus_history.empty()) + document_focus_history.back()->GetFocusLeafNode()->Focus(); +} + +void Context::AddEventListener(const String& event, EventListener* listener, bool in_capture_phase) +{ + root->AddEventListener(event, listener, in_capture_phase); +} + +void Context::RemoveEventListener(const String& event, EventListener* listener, bool in_capture_phase) +{ + root->RemoveEventListener(event, listener, in_capture_phase); +} + +bool Context::ProcessKeyDown(Input::KeyIdentifier key_identifier, int key_modifier_state) +{ + // Generate the parameters for the key event. + Dictionary parameters; + GenerateKeyEventParameters(parameters, key_identifier); + GenerateKeyModifierEventParameters(parameters, key_modifier_state); + + if (focus) + return focus->DispatchEvent(EventId::Keydown, parameters); + else + return root->DispatchEvent(EventId::Keydown, parameters); +} + +bool Context::ProcessKeyUp(Input::KeyIdentifier key_identifier, int key_modifier_state) +{ + // Generate the parameters for the key event. + Dictionary parameters; + GenerateKeyEventParameters(parameters, key_identifier); + GenerateKeyModifierEventParameters(parameters, key_modifier_state); + + if (focus) + return focus->DispatchEvent(EventId::Keyup, parameters); + else + return root->DispatchEvent(EventId::Keyup, parameters); +} + +bool Context::ProcessTextInput(char character) +{ + // Only the standard ASCII character set is a valid subset of UTF-8. + if (static_cast(character) > 127) + return false; + return ProcessTextInput(static_cast(character)); +} + +bool Context::ProcessTextInput(Character character) +{ + // Generate the parameters for the key event. + String text = StringUtilities::ToUTF8(character); + return ProcessTextInput(text); +} + +bool Context::ProcessTextInput(const String& string) +{ + Element* target = (focus ? focus : root.get()); + + Dictionary parameters; + parameters["text"] = string; + + bool consumed = target->DispatchEvent(EventId::Textinput, parameters); + + return consumed; +} + +bool Context::ProcessMouseMove(int x, int y, int key_modifier_state) +{ + // Check whether the mouse moved since the last event came through. + Vector2i old_mouse_position = mouse_position; + mouse_position = {x, y}; + const bool mouse_moved = (mouse_position != old_mouse_position || !mouse_active); + mouse_active = true; + + // Update the current hover chain. This will send all necessary 'onmouseout', 'onmouseover', 'ondragout' and 'ondragover' messages. + Dictionary parameters, drag_parameters; + UpdateHoverChain(old_mouse_position, key_modifier_state, ¶meters, &drag_parameters); + + // Dispatch any 'onmousemove' events. + if (mouse_moved) + { + if (hover) + { + hover->DispatchEvent(EventId::Mousemove, parameters); + + if (drag_hover && drag_verbose) + drag_hover->DispatchEvent(EventId::Dragmove, drag_parameters); + } + } + + return !IsMouseInteracting(); +} + +static Element* FindFocusElement(Element* element) +{ + ElementDocument* owner_document = element->GetOwnerDocument(); + if (!owner_document || owner_document->GetComputedValues().focus() == Style::Focus::None) + return nullptr; + + while (element && element->GetComputedValues().focus() == Style::Focus::None) + { + element = element->GetParentNode(); + } + + return element; +} + +bool Context::ProcessMouseButtonDown(int button_index, int key_modifier_state) +{ + Dictionary parameters; + GenerateMouseEventParameters(parameters, button_index); + GenerateKeyModifierEventParameters(parameters, key_modifier_state); + + bool propagate = true; + + if (button_index == 0) + { + Element* new_focus = hover; + + // Set the currently hovered element to focus if it isn't already the focus. + if (hover) + { + new_focus = FindFocusElement(hover); + if (new_focus && new_focus != focus) + { + if (!new_focus->Focus()) + return !IsMouseInteracting(); + } + } + + // Save the just-pressed-on element as the pressed element. + active = new_focus; + + // Call 'onmousedown' on every item in the hover chain, and copy the hover chain to the active chain. + if (hover) + propagate = hover->DispatchEvent(EventId::Mousedown, parameters); + + if (propagate) + { + // Check for a double-click on an element; if one has occured, we send the 'dblclick' event to the hover + // element. If not, we'll start a timer to catch the next one. + float mouse_distance_squared = float((mouse_position - last_click_mouse_position).SquaredMagnitude()); + float max_mouse_distance = DOUBLE_CLICK_MAX_DIST * density_independent_pixel_ratio; + + double click_time = GetSystemInterface()->GetElapsedTime(); + + if (active == last_click_element && float(click_time - last_click_time) < DOUBLE_CLICK_TIME && + mouse_distance_squared < max_mouse_distance * max_mouse_distance) + { + if (hover) + propagate = hover->DispatchEvent(EventId::Dblclick, parameters); + + last_click_element = nullptr; + last_click_time = 0; + } + else + { + last_click_element = active; + last_click_time = click_time; + } + } + + last_click_mouse_position = mouse_position; + + active_chain.insert(active_chain.end(), hover_chain.begin(), hover_chain.end()); + + if (propagate) + { + // Traverse down the hierarchy of the newly focused element (if any), and see if we can begin dragging it. + drag_started = false; + drag = hover; + while (drag) + { + Style::Drag drag_style = drag->GetComputedValues().drag(); + switch (drag_style) + { + case Style::Drag::None: drag = drag->GetParentNode(); continue; + case Style::Drag::Block: drag = nullptr; continue; + default: drag_verbose = (drag_style == Style::Drag::DragDrop || drag_style == Style::Drag::Clone); + } + + break; + } + } + } + else + { + // Not the primary mouse button, so we're not doing any special processing. + if (hover) + propagate = hover->DispatchEvent(EventId::Mousedown, parameters); + } + + if (scroll_controller->GetMode() == ScrollController::Mode::Autoscroll) + { + scroll_controller->Reset(); + } + else if (button_index == 2 && hover && propagate) + { + Dictionary scroll_parameters; + GenerateMouseEventParameters(scroll_parameters); + GenerateKeyModifierEventParameters(scroll_parameters, key_modifier_state); + scroll_parameters["autoscroll"] = true; + + // Dispatch a mouse scroll event, this gives elements an opportunity to block autoscroll from being initialized. + if (hover->DispatchEvent(EventId::Mousescroll, scroll_parameters)) + scroll_controller->ActivateAutoscroll(hover->GetClosestScrollableContainer(), mouse_position); + } + + return !IsMouseInteracting(); +} + +bool Context::ProcessMouseButtonUp(int button_index, int key_modifier_state) +{ + Dictionary parameters; + GenerateMouseEventParameters(parameters, button_index); + GenerateKeyModifierEventParameters(parameters, key_modifier_state); + + // We want to return the interaction state before handling the mouse up events, so that any active element that is released is considered to + // capture the event. + const bool result = !IsMouseInteracting(); + + // Process primary click. + if (button_index == 0) + { + // The elements in the new hover chain have the 'onmouseup' event called on them. + if (hover) + hover->DispatchEvent(EventId::Mouseup, parameters); + + // If the active element (the one that was being hovered over when the mouse button was pressed) is still being + // hovered over, we click it. + if (hover && active && active == FindFocusElement(hover)) + { + active->DispatchEvent(EventId::Click, parameters); + } + + ResetActiveChain(); + + if (drag) + { + if (drag_started) + { + Dictionary drag_parameters; + GenerateMouseEventParameters(drag_parameters); + GenerateDragEventParameters(drag_parameters); + GenerateKeyModifierEventParameters(drag_parameters, key_modifier_state); + + if (drag_hover) + { + if (drag_verbose) + { + drag_hover->DispatchEvent(EventId::Dragdrop, drag_parameters); + // User may have removed the element, do an extra check. + if (drag_hover) + drag_hover->DispatchEvent(EventId::Dragout, drag_parameters); + } + } + + if (drag) + drag->DispatchEvent(EventId::Dragend, drag_parameters); + + ReleaseDragClone(); + } + + drag = nullptr; + drag_hover = nullptr; + drag_hover_chain.clear(); + + // We may have changes under our mouse, this ensures that the hover chain is properly updated + ProcessMouseMove(mouse_position.x, mouse_position.y, key_modifier_state); + } + } + else + { + // Not the left mouse button, so we're not doing any special processing. + if (hover) + hover->DispatchEvent(EventId::Mouseup, parameters); + } + + // If we have autoscrolled while holding the middle mouse button, release the autoscroll mode now. + if (scroll_controller->HasAutoscrollMoved()) + scroll_controller->Reset(); + + return result; +} + +bool Context::ProcessMouseWheel(float wheel_delta, int key_modifier_state) +{ + return ProcessMouseWheel(Vector2f{0.f, wheel_delta}, key_modifier_state); +} + +bool Context::ProcessMouseWheel(Vector2f wheel_delta, int key_modifier_state) +{ + if (scroll_controller->GetMode() == ScrollController::Mode::Autoscroll) + { + scroll_controller->Reset(); + return false; + } + else if (!hover) + { + scroll_controller->Reset(); + return true; + } + + Dictionary scroll_parameters; + GenerateMouseEventParameters(scroll_parameters); + GenerateKeyModifierEventParameters(scroll_parameters, key_modifier_state); + scroll_parameters["wheel_delta_x"] = wheel_delta.x; + scroll_parameters["wheel_delta_y"] = wheel_delta.y; + + // Dispatch a mouse scroll event, this gives elements an opportunity to block scrolling from being performed. + if (!hover->DispatchEvent(EventId::Mousescroll, scroll_parameters)) + return false; + + const float unit_scroll_length = UNIT_SCROLL_LENGTH * density_independent_pixel_ratio; + const Vector2f scroll_length = wheel_delta * unit_scroll_length; + Element* target = hover->GetClosestScrollableContainer(); + + if (scroll_controller->GetMode() == ScrollController::Mode::Smoothscroll && scroll_controller->GetTarget() == target) + scroll_controller->IncrementSmoothscrollTarget(scroll_length); + else + scroll_controller->ActivateSmoothscroll(target, scroll_length, ScrollBehavior::Auto); + + return target == nullptr; +} + +bool Context::ProcessMouseLeave() +{ + mouse_active = false; + + // Update the hover chain. Now that 'mouse_active' is disabled this will remove the hover state from all elements. + UpdateHoverChain(mouse_position); + + return !IsMouseInteracting(); +} + +bool Context::IsMouseInteracting() const +{ + return (hover && hover != root.get()) || (active && active != root.get()) || scroll_controller->GetMode() == ScrollController::Mode::Autoscroll; +} + +Context::TouchState* Context::LookupTouch(TouchId identifier) +{ + auto touch_it = touch_states.find(identifier); + return touch_it != touch_states.end() ? &touch_it->second : nullptr; +} + +bool Context::ProcessTouchStart(const TouchList& touches, int key_modifier_state) +{ + bool result = true; + for (const auto& touch : touches) + result &= ProcessTouchStart(touch, key_modifier_state); + return result; +} + +bool Context::ProcessTouchMove(const TouchList& touches, int key_modifier_state) +{ + bool result = true; + for (const auto& touch : touches) + result &= ProcessTouchMove(touch, key_modifier_state); + return result; +} + +bool Context::ProcessTouchEnd(const TouchList& touches, int key_modifier_state) +{ + bool result = true; + for (const auto& touch : touches) + result &= ProcessTouchEnd(touch, key_modifier_state); + return result; +} + +bool Context::ProcessTouchCancel(const TouchList& touches) +{ + bool result = true; + for (const auto& touch : touches) + result &= ProcessTouchCancel(touch); + return result; +} + +bool Context::ProcessTouchStart(const Touch& touch, int key_modifier_state) +{ + TouchState* state = LookupTouch(touch.identifier); + RMLUI_ASSERTMSG(state == nullptr, "Receiving touch start event for an already started touch."); + if (!state) + { + auto it_inserted = touch_states.emplace(touch.identifier, TouchState()).first; + state = &it_inserted->second; + } + + state->start_position = touch.position; + state->inertia_position = touch.position; + state->last_position = touch.position; + state->scrolling_start_time_x = 0; + state->scrolling_start_time_y = 0; + state->scrolling_last_time = GetSystemInterface()->GetElapsedTime(); + + Element* touch_element = GetElementAtPoint(touch.position); + state->scroll_container = touch_element ? touch_element->GetClosestScrollableContainer() : nullptr; + + // reset any scrolling when we touch the element + if (state->scroll_container && scroll_controller->GetTarget() == state->scroll_container) + scroll_controller->Reset(); + + ProcessMouseMove(static_cast(touch.position.x), static_cast(touch.position.y), key_modifier_state); + + // always assume touch press/release events are handled as left mouse button + return ProcessMouseButtonDown(0, key_modifier_state); +} + +bool Context::ProcessTouchMove(const Touch& touch, int key_modifier_state) +{ + TouchState* state = LookupTouch(touch.identifier); + if (!state) + return true; + + state->scrolling_last_time = GetSystemInterface()->GetElapsedTime(); + + if (state->scroll_container) + { + const Vector2f delta = touch.position - state->last_position; + + if (drag) + { + // Don't scroll and reset scrolling state when dragging any element (scrollbars and others) + state->inertia_position = touch.position; + state->last_position = touch.position; + state->scrolling_start_time_x = 0; + state->scrolling_start_time_y = 0; + } + else if (delta.x != 0 || delta.y != 0) + { + // Use instant scrolling when touch is pressed even when default scroll behavior is smooth. + scroll_controller->InstantScrollOnTarget(state->scroll_container, -delta); + + const double current_time = GetSystemInterface()->GetElapsedTime(); + + enum { Horizontal = 0, Vertical = 1 }; + for (int axis : {Horizontal, Vertical}) + { + bool& state_scrolling_positive = (axis == Horizontal ? state->scrolling_right : state->scrolling_down); + double& state_scrolling_start_time = (axis == Horizontal ? state->scrolling_start_time_x : state->scrolling_start_time_y); + + // Time set to 0 means no touch move events happened before and direction is unclear. + const bool scroll_start = (state_scrolling_start_time == 0); + + // If the user changes direction, reset the start time and position. + const bool going_positive = (delta[axis] > 0); + if (delta[axis] != 0 && (going_positive != state_scrolling_positive || scroll_start)) + { + state->inertia_position[axis] = touch.position[axis]; + state_scrolling_positive = going_positive; + state_scrolling_start_time = state->scrolling_last_time; + } + else + { + // Move inertia position towards end position with a weight of e^-kt to better capture and + // calculate velocity of the very last touch movements before touch release. + const float elapsed_time = static_cast(current_time - state_scrolling_start_time); + const float weight = Math::Exp(-elapsed_time * TOUCH_MOVEMENT_DECAY_RATE); + + state->inertia_position[axis] = touch.position[axis] - (touch.position[axis] - state->inertia_position[axis]) * weight; + state_scrolling_start_time = current_time - (current_time - state_scrolling_start_time) * weight; + } + } + + const float touch_max_distance = TOUCH_CLICK_MAX_DISTANCE * density_independent_pixel_ratio; + const Vector2f delta_from_start = touch.position - state->start_position; + if (delta_from_start.SquaredMagnitude() >= touch_max_distance * touch_max_distance) + ResetActiveChain(); + } + } + + state->last_position = touch.position; + + return ProcessMouseMove(static_cast(touch.position.x), static_cast(touch.position.y), key_modifier_state); +} + +bool Context::ProcessTouchEnd(const Touch& touch, int key_modifier_state) +{ + TouchState* state = LookupTouch(touch.identifier); + if (!state) + return true; + + if (state->scroll_container) + { + double current_time = GetSystemInterface()->GetElapsedTime(); + double time_since_last_move = current_time - state->scrolling_last_time; + if (time_since_last_move < SCROLL_INERTIA_DELAY) + { + // apply scrolling inertia + Vector2f delta = touch.position - state->inertia_position; + + Vector2f velocity = -delta; + float elapsed_time_x = static_cast(current_time - state->scrolling_start_time_x); + float elapsed_time_y = static_cast(current_time - state->scrolling_start_time_y); + if (elapsed_time_x > 0) + velocity.x /= elapsed_time_x; + if (elapsed_time_y > 0) + velocity.y /= elapsed_time_y; + + scroll_controller->ActivateInertia(state->scroll_container, velocity); + } + } + + touch_states.erase(touch.identifier); + + ProcessMouseMove(static_cast(touch.position.x), static_cast(touch.position.y), key_modifier_state); + + // always assume touch press/release events are handled as left mouse button + return ProcessMouseButtonUp(0, key_modifier_state); +} + +bool Context::ProcessTouchCancel(const Touch& touch) +{ + TouchState* state = LookupTouch(touch.identifier); + if (!state) + return false; + + touch_states.erase(touch.identifier); + + return ProcessMouseButtonUp(0, 0); +} + +void Context::SetDefaultScrollBehavior(ScrollBehavior scroll_behavior, float speed_factor) +{ + scroll_controller->SetDefaultScrollBehavior(scroll_behavior, speed_factor); +} + +RenderManager& Context::GetRenderManager() +{ + return *render_manager; +} + +TextInputHandler* Context::GetTextInputHandler() const +{ + return text_input_handler; +} + +void Context::SetInstancer(ContextInstancer* _instancer) +{ + RMLUI_ASSERT(instancer == nullptr); + instancer = _instancer; +} + +DataModelConstructor Context::CreateDataModel(const String& name, DataTypeRegister* data_type_register, bool allow_missing_variables) +{ + if (!data_type_register) + { + if (!default_data_type_register) + default_data_type_register = MakeUnique(); + data_type_register = default_data_type_register.get(); + } + + auto result = data_models.emplace(name, MakeUnique(data_type_register, allow_missing_variables)); + bool inserted = result.second; + if (inserted) + { + DataModel* model = result.first->second.get(); + PluginRegistry::NotifyDataModelCreate(this, name); + return DataModelConstructor(model); + } + + Log::Message(Log::LT_ERROR, "Data model name '%s' already exists.", name.c_str()); + return DataModelConstructor(); +} + +DataModelConstructor Context::GetDataModel(const String& name) +{ + if (DataModel* model = GetDataModelPtr(name)) + return DataModelConstructor(model); + + Log::Message(Log::LT_ERROR, "Data model name '%s' could not be found.", name.c_str()); + return DataModelConstructor(); +} + +UnorderedMap Context::GetDataModels() const +{ + UnorderedMap result; + result.reserve(data_models.size()); + for (const auto& pair : data_models) + result.emplace(pair.first, DataModelConstructor(pair.second.get())); + return result; +} + +bool Context::RemoveDataModel(const String& name) +{ + auto it = data_models.find(name); + if (it == data_models.end()) + return false; + + DataModel* model = it->second.get(); + ElementList elements = model->GetAttachedModelRootElements(); + + for (Element* element : elements) + element->SetDataModel(nullptr); + + PluginRegistry::NotifyDataModelDestroy(this, it->first); + + data_models.erase(it); + + return true; +} + +void Context::OnElementDetach(Element* element) +{ + auto it_hover = hover_chain.find(element); + if (it_hover != hover_chain.end()) + { + Dictionary parameters; + GenerateMouseEventParameters(parameters, -1); + element->DispatchEvent(EventId::Mouseout, parameters); + + hover_chain.erase(it_hover); + + if (hover == element) + hover = nullptr; + } + + auto it_active = std::find(active_chain.begin(), active_chain.end(), element); + if (it_active != active_chain.end()) + { + active_chain.erase(it_active); + + if (active == element) + active = nullptr; + } + + if (drag) + { + auto it = drag_hover_chain.find(element); + if (it != drag_hover_chain.end()) + { + drag_hover_chain.erase(it); + + if (drag_hover == element) + drag_hover = nullptr; + } + + if (drag == element) + { + // The dragged element is being removed, silently cancel the drag operation + if (drag_started) + ReleaseDragClone(); + + drag = nullptr; + drag_hover = nullptr; + drag_hover_chain.clear(); + } + } + + // Focus normally cleared and set by parent during Element::RemoveChild. + // However, there are some exceptions, such as when an there are multiple + // ElementDocuments in the hierarchy above the current element. + if (element == focus) + focus = nullptr; + + // If the element is a document lower down in the hierarchy, we may need to remove it from the focus history. + if (element->GetOwnerDocument() == element) + { + auto it = std::find(document_focus_history.begin(), document_focus_history.end(), element); + if (it != document_focus_history.end()) + document_focus_history.erase(it); + } + + if (scroll_controller->GetTarget() == element) + scroll_controller->Reset(); + + // Clear TouchState if we're touching element + for (auto touch_it = touch_states.begin(); touch_it != touch_states.end();) + { + if (touch_it->second.scroll_container == element) + touch_it = touch_states.erase(touch_it); + else + ++touch_it; + } +} + +bool Context::OnFocusChange(Element* new_focus, bool focus_visible) +{ + RMLUI_ASSERT(new_focus); + + ElementSet old_chain; + ElementSet new_chain; + + Element* old_focus = focus; + ElementDocument* old_document = old_focus ? old_focus->GetOwnerDocument() : nullptr; + ElementDocument* new_document = new_focus->GetOwnerDocument(); + + // If the current focus is modal and the new focus is cannot receive focus from modal, deny the request. + if (old_document && old_document->IsModal() && (!new_document || !(new_document->IsModal() || new_document->IsFocusableFromModal()))) + return false; + + // If the document of the new focus has been closed, deny the request. + if (std::find_if(unloaded_documents.begin(), unloaded_documents.end(), + [&](const auto& unloaded_document) { return unloaded_document.get() == new_document; }) != unloaded_documents.end()) + { + return false; + } + + // Build the old chains + Element* element = old_focus; + while (element) + { + old_chain.insert(element); + element = element->GetParentNode(); + } + + // Build the new chain + element = new_focus; + while (element) + { + new_chain.insert(element); + element = element->GetParentNode(); + } + + // Send out blur/focus events. + Dictionary parameters; + SendEvents(old_chain, new_chain, EventId::Blur, parameters); + + if (focus_visible) + parameters["focus_visible"] = true; + + SendEvents(new_chain, old_chain, EventId::Focus, parameters); + + focus = new_focus; + + // Raise the element's document to the front, if desired. + ElementDocument* document = focus->GetOwnerDocument(); + if (document != nullptr) + { + Style::ZIndex z_index_property = document->GetComputedValues().z_index(); + if (z_index_property.type == Style::ZIndex::Auto) + document->PullToFront(); + } + + // Update the focus history + if (old_document != new_document) + { + // If documents have changed, add the new document to the end of the history + ElementList::iterator itr = std::find(document_focus_history.begin(), document_focus_history.end(), new_document); + if (itr != document_focus_history.end()) + document_focus_history.erase(itr); + + if (new_document != nullptr) + document_focus_history.push_back(new_document); + } + + return true; +} + +void Context::GenerateClickEvent(Element* element) +{ + Dictionary parameters; + GenerateMouseEventParameters(parameters, 0); + + element->DispatchEvent(EventId::Click, parameters); +} + +void Context::UpdateHoverChain(Vector2i old_mouse_position, int key_modifier_state, Dictionary* out_parameters, Dictionary* out_drag_parameters) +{ + const Vector2f position(mouse_position); + + Dictionary local_parameters, local_drag_parameters; + Dictionary& parameters = out_parameters ? *out_parameters : local_parameters; + Dictionary& drag_parameters = out_drag_parameters ? *out_drag_parameters : local_drag_parameters; + + // Generate the parameters for the mouse events (there could be a few!). + GenerateMouseEventParameters(parameters); + GenerateKeyModifierEventParameters(parameters, key_modifier_state); + + GenerateMouseEventParameters(drag_parameters); + GenerateDragEventParameters(drag_parameters); + GenerateKeyModifierEventParameters(drag_parameters, key_modifier_state); + + // Send out drag events. + if (drag) + { + if (mouse_position != old_mouse_position) + { + if (!drag_started) + { + Dictionary drag_start_parameters = drag_parameters; + drag_start_parameters["mouse_x"] = old_mouse_position.x; + drag_start_parameters["mouse_y"] = old_mouse_position.y; + drag->DispatchEvent(EventId::Dragstart, drag_start_parameters); + drag_started = true; + + if (drag->GetComputedValues().drag() == Style::Drag::Clone) + { + // Clone the element and attach it to the mouse cursor. + CreateDragClone(drag); + } + } + + drag->DispatchEvent(EventId::Drag, drag_parameters); + } + } + + hover = mouse_active ? GetElementAtPoint(position) : nullptr; + + if (enable_cursor) + { + String new_cursor_name; + + if (scroll_controller->GetMode() == ScrollController::Mode::Autoscroll) + new_cursor_name = scroll_controller->GetAutoscrollCursor(mouse_position, density_independent_pixel_ratio); + else if (drag) + new_cursor_name = drag->GetComputedValues().cursor(); + else if (hover) + new_cursor_name = hover->GetComputedValues().cursor(); + + if (new_cursor_name != cursor_name) + { + GetSystemInterface()->SetMouseCursor(new_cursor_name); + cursor_name = new_cursor_name; + } + } + + // Build the new hover chain. + ElementSet new_hover_chain; + Element* element = hover; + while (element != nullptr) + { + new_hover_chain.insert(element); + element = element->GetParentNode(); + } + + // Send mouseout / mouseover events. + SendEvents(hover_chain, new_hover_chain, EventId::Mouseout, parameters); + SendEvents(new_hover_chain, hover_chain, EventId::Mouseover, parameters); + + // Send out drag events. + if (drag && mouse_active) + { + drag_hover = GetElementAtPoint(position, drag); + + ElementSet new_drag_hover_chain; + element = drag_hover; + while (element != nullptr) + { + new_drag_hover_chain.insert(element); + element = element->GetParentNode(); + } + + if (drag_started && drag_verbose) + { + // Send out ondragover and ondragout events as appropriate. + SendEvents(drag_hover_chain, new_drag_hover_chain, EventId::Dragout, drag_parameters); + SendEvents(new_drag_hover_chain, drag_hover_chain, EventId::Dragover, drag_parameters); + } + + drag_hover_chain.swap(new_drag_hover_chain); + } + + // Swap the new chain in. + hover_chain.swap(new_hover_chain); +} + +void Context::ResetActiveChain() +{ + // Unset the 'active' pseudo-class on all the elements in the active chain; because they may not necessarily + // have had 'onmouseup' called on them, we can't guarantee this has happened already. + for (Element* element : active_chain) + element->SetPseudoClass("active", false); + + active_chain.clear(); + active = nullptr; +} + +Element* Context::GetElementAtPoint(Vector2f point, const Element* ignore_element, Element* element) const +{ + if (!element) + { + if (ignore_element == root.get()) + return nullptr; + + element = root.get(); + } + + bool is_modal = false; + ElementDocument* focus_document = nullptr; + + // If we have modal focus, only check down documents that can receive focus from modals. + if (element == root.get() && focus) + { + focus_document = focus->GetOwnerDocument(); + if (focus_document && focus_document->IsModal()) + is_modal = true; + } + + // Check any elements within our stacking context. We want to return the lowest-down element + // that is under the cursor. + if (element->local_stacking_context) + { + if (element->stacking_context_dirty) + element->BuildLocalStackingContext(); + + for (int i = (int)element->stacking_context.size() - 1; i >= 0; --i) + { + Element* stacking_child = element->stacking_context[i]; + if (ignore_element) + { + // Check if the element is a descendant of the element we're ignoring. + Element* element_hierarchy = stacking_child; + while (element_hierarchy) + { + if (element_hierarchy == ignore_element) + break; + + element_hierarchy = element_hierarchy->GetParentNode(); + } + + if (element_hierarchy) + continue; + } + + if (is_modal) + { + ElementDocument* child_document = stacking_child->GetOwnerDocument(); + if (!child_document || !(child_document == focus_document || child_document->IsFocusableFromModal())) + continue; + } + + Element* child_element = GetElementAtPoint(point, ignore_element, stacking_child); + if (child_element) + return child_element; + } + } + + // Ignore elements whose pointer events are disabled. + if (element->GetComputedValues().pointer_events() == Style::PointerEvents::None) + return nullptr; + + // Projection may fail if we have a singular transformation matrix. + bool projection_result = element->Project(point); + + // Check if the point is actually within this element. + bool within_element = (projection_result && element->IsPointWithinElement(point)); + if (within_element) + { + // The element may have been clipped out of view if it overflows an ancestor, so check its clipping region. + Rectanglei clip_region; + if (ElementUtilities::GetClippingRegion(element, clip_region)) + within_element = clip_region.Contains(Vector2i(point)); + } + + if (within_element) + return element; + + return nullptr; +} + +void Context::CreateDragClone(Element* element) +{ + RMLUI_ASSERTMSG(cursor_proxy, "Unable to create drag clone, no cursor proxy document."); + + ReleaseDragClone(); + + // Instance the drag clone. + ElementPtr element_drag_clone = element->Clone(); + if (!element_drag_clone) + { + Log::Message(Log::LT_ERROR, "Unable to duplicate drag clone."); + return; + } + + // Set the style sheet on the cursor proxy. + if (ElementDocument* document = element->GetOwnerDocument()) + { + // Borrow the target document's style sheet. Sharing style sheet containers should be used with care, and + // only within the same context. + static_cast(*cursor_proxy).SetStyleSheetContainer(document->style_sheet_container); + } + + drag_clone = element_drag_clone.get(); + + // Append the clone to the cursor proxy element. + cursor_proxy->AppendChild(std::move(element_drag_clone)); + + // Position the clone. Use projected mouse coordinates to handle any ancestor transforms. + const Vector2f absolute_pos = element->GetAbsoluteOffset(BoxArea::Border); + Vector2f projected_mouse_position = Vector2f(mouse_position); + if (Element* parent = element->GetParentNode()) + parent->Project(projected_mouse_position); + + drag_clone->SetProperty(PropertyId::Position, Property(Style::Position::Absolute)); + drag_clone->SetProperty(PropertyId::Left, Property(absolute_pos.x - projected_mouse_position.x, Unit::PX)); + drag_clone->SetProperty(PropertyId::Top, Property(absolute_pos.y - projected_mouse_position.y, Unit::PX)); + // We remove margins so that percentage- and auto-margins are evaluated correctly. + drag_clone->SetProperty(PropertyId::MarginLeft, Property(0.f, Unit::PX)); + drag_clone->SetProperty(PropertyId::MarginTop, Property(0.f, Unit::PX)); + drag_clone->SetPseudoClass("drag", true); +} + +void Context::ReleaseDragClone() +{ + if (drag_clone) + { + cursor_proxy->RemoveChild(drag_clone); + drag_clone = nullptr; + static_cast(*cursor_proxy).SetStyleSheetContainer(nullptr); + } +} + +void Context::PerformSmoothscrollOnTarget(Element* target, Vector2f delta_offset, ScrollBehavior scroll_behavior) +{ + scroll_controller->ActivateSmoothscroll(target, delta_offset, scroll_behavior); +} + +DataModel* Context::GetDataModelPtr(const String& name) const +{ + auto it = data_models.find(name); + if (it != data_models.end()) + return it->second.get(); + return nullptr; +} + +void Context::GenerateKeyEventParameters(Dictionary& parameters, Input::KeyIdentifier key_identifier) +{ + parameters["key_identifier"] = (int)key_identifier; +} + +void Context::GenerateMouseEventParameters(Dictionary& parameters, int button_index) +{ + parameters.reserve(3); + parameters["mouse_x"] = mouse_position.x; + parameters["mouse_y"] = mouse_position.y; + if (button_index >= 0) + parameters["button"] = button_index; +} + +void Context::GenerateKeyModifierEventParameters(Dictionary& parameters, int key_modifier_state) +{ + static const String property_names[] = {"ctrl_key", "shift_key", "alt_key", "meta_key", "caps_lock_key", "num_lock_key", "scroll_lock_key"}; + + for (int i = 0; i < 7; i++) + parameters[property_names[i]] = (int)((key_modifier_state & (1 << i)) > 0); +} + +void Context::GenerateDragEventParameters(Dictionary& parameters) +{ + parameters["drag_element"] = (void*)drag; +} + +void Context::ReleaseUnloadedDocuments() +{ + if (!unloaded_documents.empty()) + { + OwnedElementList documents = std::move(unloaded_documents); + unloaded_documents.clear(); + + // Clear the deleted list. + for (size_t i = 0; i < documents.size(); ++i) + documents[i]->GetEventDispatcher()->DetachAllEvents(); + documents.clear(); + } +} + +using ElementObserverList = Vector>; + +class ElementObserverListBackInserter { +public: + using iterator_category = std::output_iterator_tag; + using value_type = void; + using difference_type = void; + using pointer = void; + using reference = void; + using container_type = ElementObserverList; + + ElementObserverListBackInserter(ElementObserverList& elements) : elements(&elements) {} + ElementObserverListBackInserter& operator=(Element* element) + { + elements->push_back(element->GetObserverPtr()); + return *this; + } + ElementObserverListBackInserter& operator*() { return *this; } + ElementObserverListBackInserter& operator++() { return *this; } + ElementObserverListBackInserter& operator++(int) { return *this; } + +private: + ElementObserverList* elements; +}; + +void Context::SendEvents(const ElementSet& old_items, const ElementSet& new_items, EventId id, const Dictionary& parameters) +{ + // We put our elements in observer pointers in case some of them are deleted during dispatch. + ElementObserverList elements; + std::set_difference(old_items.begin(), old_items.end(), new_items.begin(), new_items.end(), ElementObserverListBackInserter(elements)); + for (auto& element : elements) + { + if (element) + element->DispatchEvent(id, parameters); + } +} + +void Context::Release() +{ + if (instancer) + { + instancer->ReleaseContext(this); + } +} + +void Context::SetDocumentsBaseTag(const String& tag) +{ + documents_base_tag = tag; +} + +const String& Context::GetDocumentsBaseTag() +{ + return documents_base_tag; +} + +void Context::RequestNextUpdate(double delay) +{ + RMLUI_ASSERT(delay >= 0.0); + next_update_timeout = Math::Min(next_update_timeout, delay); +} + +double Context::GetNextUpdateDelay() const +{ + return next_update_timeout; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ContextInstancer.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ContextInstancer.cpp new file mode 100644 index 00000000..ee0ced73 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ContextInstancer.cpp @@ -0,0 +1,7 @@ +#include "../../Include/RmlUi/Core/ContextInstancer.h" + +namespace Rml { + +ContextInstancer::~ContextInstancer() {} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ContextInstancerDefault.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ContextInstancerDefault.cpp new file mode 100644 index 00000000..6408a73c --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ContextInstancerDefault.cpp @@ -0,0 +1,25 @@ +#include "ContextInstancerDefault.h" +#include "../../Include/RmlUi/Core/Context.h" + +namespace Rml { + +ContextInstancerDefault::ContextInstancerDefault() {} + +ContextInstancerDefault::~ContextInstancerDefault() {} + +ContextPtr ContextInstancerDefault::InstanceContext(const String& name, RenderManager* render_manager, TextInputHandler* text_input_handler) +{ + return ContextPtr(new Context(name, render_manager, text_input_handler)); +} + +void ContextInstancerDefault::ReleaseContext(Context* context) +{ + delete context; +} + +void ContextInstancerDefault::Release() +{ + delete this; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ContextInstancerDefault.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ContextInstancerDefault.h new file mode 100644 index 00000000..a5fbc846 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ContextInstancerDefault.h @@ -0,0 +1,26 @@ +#pragma once + +#include "../../Include/RmlUi/Core/ContextInstancer.h" + +namespace Rml { + +/** + Default instancer for instancing contexts. + */ + +class ContextInstancerDefault : public ContextInstancer { +public: + ContextInstancerDefault(); + virtual ~ContextInstancerDefault(); + + /// Instances a context. + ContextPtr InstanceContext(const String& name, RenderManager* render_manager, TextInputHandler* text_input_handler) override; + + /// Releases a context previously created by this context. + void ReleaseContext(Context* context) override; + + /// Releases this context instancer. + void Release() override; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ControlledLifetimeResource.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ControlledLifetimeResource.h new file mode 100644 index 00000000..dfd5b919 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ControlledLifetimeResource.h @@ -0,0 +1,62 @@ +#pragma once + +#include "../../Include/RmlUi/Core/Debug.h" +#include "../../Include/RmlUi/Core/Traits.h" + +namespace Rml { + +template +class ControlledLifetimeResource : NonCopyMoveable { +public: + ControlledLifetimeResource() = default; + ~ControlledLifetimeResource() noexcept + { +#if defined(RMLUI_PLATFORM_WIN32) && !defined(RMLUI_STATIC_LIB) + RMLUI_ASSERTMSG(!pointer || intentionally_leaked, "Resource was not properly shut down."); +#endif + } + + explicit operator bool() const noexcept { return pointer != nullptr; } + + void Initialize() + { + RMLUI_ASSERTMSG(!pointer, "Resource already initialized."); + pointer = new T(); + } + + void InitializeIfEmpty() + { + if (!pointer) + Initialize(); + else + SetIntentionallyLeaked(false); + } + + void Leak() { SetIntentionallyLeaked(true); } + + void Shutdown() + { + RMLUI_ASSERTMSG(pointer, "Shutting down resource that was not initialized, or has been shut down already."); + RMLUI_ASSERTMSG(!intentionally_leaked, "Shutting down resource that was marked as leaked."); + delete pointer; + pointer = nullptr; + } + + T* operator->() + { + RMLUI_ASSERTMSG(pointer, "Resource used before it was initialized, or after it was shut down."); + return pointer; + } + +private: +#ifdef RMLUI_DEBUG + void SetIntentionallyLeaked(bool leaked) { intentionally_leaked = leaked; } + bool intentionally_leaked = false; +#else + void SetIntentionallyLeaked(bool /*leaked*/) {} +#endif + + T* pointer = nullptr; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ConvolutionFilter.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ConvolutionFilter.cpp new file mode 100644 index 00000000..2189aca1 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ConvolutionFilter.cpp @@ -0,0 +1,90 @@ +#include "../../Include/RmlUi/Core/ConvolutionFilter.h" +#include "../../Include/RmlUi/Core/Profiling.h" +#include +#include + +namespace Rml { + +ConvolutionFilter::ConvolutionFilter() {} + +ConvolutionFilter::~ConvolutionFilter() {} + +bool ConvolutionFilter::Initialise(int _kernel_radius, FilterOperation _operation) +{ + return Initialise(Vector2i(_kernel_radius), _operation); +} + +bool ConvolutionFilter::Initialise(Vector2i _kernel_radii, FilterOperation _operation) +{ + if (_kernel_radii.x < 0 || _kernel_radii.y < 0) + { + RMLUI_ERRORMSG("Invalid input parameters to convolution filter."); + return false; + } + + kernel_size = _kernel_radii * 2 + Vector2i(1); + + kernel = UniquePtr(new float[kernel_size.x * kernel_size.y]()); + + operation = _operation; + return true; +} + +float* ConvolutionFilter::operator[](int kernel_y_index) +{ + RMLUI_ASSERT(kernel != nullptr && kernel_y_index >= 0 && kernel_y_index < kernel_size.y); + + kernel_y_index = Math::Clamp(kernel_y_index, 0, kernel_size.y - 1); + + return kernel.get() + kernel_size.x * kernel_y_index; +} + +void ConvolutionFilter::Run(byte* destination, const Vector2i destination_dimensions, const int destination_stride, + const ColorFormat destination_color_format, const byte* source, const Vector2i source_dimensions, const Vector2i source_offset, + const ColorFormat source_color_format) const +{ + RMLUI_ZoneScopedNC("ConvFilter::Run", 0xd6bf49); + + const int destination_bytes_per_pixel = (destination_color_format == ColorFormat::RGBA8 ? 4 : 1); + const int destination_alpha_offset = (destination_color_format == ColorFormat::RGBA8 ? 3 : 0); + const int source_bytes_per_pixel = (source_color_format == ColorFormat::RGBA8 ? 4 : 1); + const int source_alpha_offset = (source_color_format == ColorFormat::RGBA8 ? 3 : 0); + + const Vector2i kernel_radius = (kernel_size - Vector2i(1)) / 2; + + for (int y = 0; y < destination_dimensions.y; ++y) + { + for (int x = 0; x < destination_dimensions.x; ++x) + { + float opacity = 0.f; + + for (int kernel_y = 0; kernel_y < kernel_size.y; ++kernel_y) + { + const int source_y = y - source_offset.y - kernel_radius.y + kernel_y; + + for (int kernel_x = 0; kernel_x < kernel_size.x; ++kernel_x) + { + const int source_x = x - source_offset.x - kernel_radius.x + kernel_x; + if (source_y >= 0 && source_y < source_dimensions.y && source_x >= 0 && source_x < source_dimensions.x) + { + const int source_index = (source_y * source_dimensions.x + source_x) * source_bytes_per_pixel + source_alpha_offset; + const float pixel_opacity = float(source[source_index]) * kernel[kernel_y * kernel_size.x + kernel_x]; + + switch (operation) + { + case FilterOperation::Sum: opacity += pixel_opacity; break; + case FilterOperation::Dilation: opacity = Math::Max(opacity, pixel_opacity); break; + } + } + } + } + + opacity = Math::Min(255.f, opacity); + + const int destination_index = y * destination_stride + x * destination_bytes_per_pixel + destination_alpha_offset; + destination[destination_index] = byte(opacity); + } + } +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Core.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Core.cpp new file mode 100644 index 00000000..c6f597f7 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Core.cpp @@ -0,0 +1,465 @@ +#include "../../Include/RmlUi/Core/Core.h" +#include "../../Include/RmlUi/Core/Context.h" +#include "../../Include/RmlUi/Core/Element.h" +#include "../../Include/RmlUi/Core/ElementInstancer.h" +#include "../../Include/RmlUi/Core/Factory.h" +#include "../../Include/RmlUi/Core/FileInterface.h" +#include "../../Include/RmlUi/Core/FontEngineInterface.h" +#include "../../Include/RmlUi/Core/Plugin.h" +#include "../../Include/RmlUi/Core/RenderInterface.h" +#include "../../Include/RmlUi/Core/RenderManager.h" +#include "../../Include/RmlUi/Core/StyleSheetSpecification.h" +#include "../../Include/RmlUi/Core/SystemInterface.h" +#include "../../Include/RmlUi/Core/TextInputHandler.h" +#include "../../Include/RmlUi/Core/Types.h" +#include "BoxShadowCache.h" +#include "ComputeProperty.h" +#include "ControlledLifetimeResource.h" +#include "ElementMeta.h" +#include "EventSpecification.h" +#include "FileInterfaceDefault.h" +#include "Layout/LayoutPools.h" +#include "PluginRegistry.h" +#include "RenderManagerAccess.h" +#include "StyleSheetFactory.h" +#include "StyleSheetParser.h" +#include "TemplateCache.h" + +#ifdef RMLUI_FONT_ENGINE_FREETYPE + #include "FontEngineDefault/FontEngineInterfaceDefault.h" +#endif + +#ifdef RMLUI_LOTTIE_PLUGIN + #include "../Lottie/LottiePlugin.h" +#endif + +#ifdef RMLUI_SVG_PLUGIN + #include "../SVG/SVGPlugin.h" +#endif + +#include + +namespace Rml { + +static RenderInterface* render_interface = nullptr; +static SystemInterface* system_interface = nullptr; +static FileInterface* file_interface = nullptr; +static FontEngineInterface* font_interface = nullptr; +static TextInputHandler* text_input_handler = nullptr; + +struct CoreData { + // Default interfaces should be created and destroyed on Initialise and Shutdown, respectively. + UniquePtr default_system_interface; + UniquePtr default_file_interface; + UniquePtr default_font_interface; + UniquePtr default_text_input_handler; + + SmallUnorderedMap> render_managers; + UnorderedMap contexts; +}; + +static ControlledLifetimeResource core_data; + +static bool initialised = false; + +static void InitializeMemoryPools() +{ + Detail::InitializeElementInstancerPools(); + ElementMetaPool::Initialize(); + LayoutPools::Initialize(); +} +static void ReleaseMemoryPools() +{ + LayoutPools::Shutdown(); + ElementMetaPool::Shutdown(); + Detail::ShutdownElementInstancerPools(); +} + +#ifndef RMLUI_VERSION + #define RMLUI_VERSION "custom" +#endif + +bool Initialise() +{ + RMLUI_ASSERTMSG(!initialised, "Rml::Initialise() called, but RmlUi is already initialised!"); + + InitializeMemoryPools(); + InitializeComputeProperty(); + + core_data.Initialize(); + + // Install default interfaces as appropriate. + if (!system_interface) + { + core_data->default_system_interface = MakeUnique(); + system_interface = core_data->default_system_interface.get(); + } + + if (!file_interface) + { +#ifndef RMLUI_NO_FILE_INTERFACE_DEFAULT + core_data->default_file_interface = MakeUnique(); + file_interface = core_data->default_file_interface.get(); +#else + Log::Message(Log::LT_ERROR, "No file interface set!"); + return false; +#endif + } + + if (!font_interface) + { +#ifdef RMLUI_FONT_ENGINE_FREETYPE + core_data->default_font_interface = MakeUnique(); + font_interface = core_data->default_font_interface.get(); +#else + Log::Message(Log::LT_ERROR, "No font engine interface set!"); + return false; +#endif + } + + if (!text_input_handler) + { + core_data->default_text_input_handler = MakeUnique(); + text_input_handler = core_data->default_text_input_handler.get(); + } + + EventSpecificationInterface::Initialize(); + + Detail::InitializeObserverPtrPool(); + + if (render_interface) + core_data->render_managers[render_interface] = MakeUnique(render_interface); + + font_interface->Initialize(); + + StyleSheetSpecification::Initialise(); + StyleSheetParser::Initialise(); + StyleSheetFactory::Initialise(); + + TemplateCache::Initialise(); + + Factory::Initialise(); + + // Initialise plugins integrated with Core. +#ifdef RMLUI_LOTTIE_PLUGIN + Lottie::Initialise(); +#endif +#ifdef RMLUI_SVG_PLUGIN + SVG::Initialise(); +#endif + BoxShadowCache::Initialize(); + + // Notify all plugins we're starting up. + PluginRegistry::NotifyInitialise(); + + initialised = true; + + return true; +} + +void Shutdown() +{ + RMLUI_ASSERTMSG(initialised, "Rml::Shutdown() called, but RmlUi is not initialised!"); + + // Clear out all contexts, which should also clean up all attached elements. + core_data->contexts.clear(); + + // Notify all plugins we're being shutdown. + PluginRegistry::NotifyShutdown(); + + BoxShadowCache::Shutdown(); + + Factory::Shutdown(); + TemplateCache::Shutdown(); + StyleSheetFactory::Shutdown(); + StyleSheetParser::Shutdown(); + StyleSheetSpecification::Shutdown(); + + font_interface->Shutdown(); + + core_data->render_managers.clear(); + + Detail::ShutdownObserverPtrPool(); + + initialised = false; + + text_input_handler = nullptr; + font_interface = nullptr; + render_interface = nullptr; + file_interface = nullptr; + system_interface = nullptr; + + core_data.Shutdown(); + + EventSpecificationInterface::Shutdown(); + + ShutdownComputeProperty(); + ReleaseMemoryPools(); +} + +String GetVersion() +{ + return RMLUI_VERSION; +} + +void SetSystemInterface(SystemInterface* _system_interface) +{ + system_interface = _system_interface; +} + +SystemInterface* GetSystemInterface() +{ + return system_interface; +} + +void SetRenderInterface(RenderInterface* _render_interface) +{ + render_interface = _render_interface; +} + +RenderInterface* GetRenderInterface() +{ + return render_interface; +} + +void SetFileInterface(FileInterface* _file_interface) +{ + file_interface = _file_interface; +} + +FileInterface* GetFileInterface() +{ + return file_interface; +} + +void SetFontEngineInterface(FontEngineInterface* _font_interface) +{ + font_interface = _font_interface; +} + +FontEngineInterface* GetFontEngineInterface() +{ + return font_interface; +} + +void SetTextInputHandler(TextInputHandler* _text_input_handler) +{ + text_input_handler = _text_input_handler; +} + +TextInputHandler* GetTextInputHandler() +{ + return text_input_handler; +} + +Context* CreateContext(const String& name, const Vector2i dimensions, RenderInterface* render_interface_for_context, + TextInputHandler* text_input_handler_for_context) +{ + if (!initialised) + return nullptr; + + if (!render_interface_for_context) + render_interface_for_context = render_interface; + + if (!text_input_handler_for_context) + text_input_handler_for_context = text_input_handler; + + if (!render_interface_for_context) + { + Log::Message(Log::LT_WARNING, "Failed to create context '%s', no render interface specified and no default render interface exists.", + name.c_str()); + return nullptr; + } + + if (GetContext(name)) + { + Log::Message(Log::LT_WARNING, "Failed to create context '%s', context already exists.", name.c_str()); + return nullptr; + } + + // Each unique render interface gets its own render manager. + auto& render_manager = core_data->render_managers[render_interface_for_context]; + if (!render_manager) + render_manager = MakeUnique(render_interface_for_context); + + ContextPtr new_context = Factory::InstanceContext(name, render_manager.get(), text_input_handler_for_context); + if (!new_context) + { + Log::Message(Log::LT_WARNING, "Failed to instance context '%s', instancer returned nullptr.", name.c_str()); + return nullptr; + } + + new_context->SetDimensions(dimensions); + + Context* new_context_raw = new_context.get(); + core_data->contexts[name] = std::move(new_context); + + PluginRegistry::NotifyContextCreate(new_context_raw); + + return new_context_raw; +} + +bool RemoveContext(const String& name) +{ + return core_data->contexts.erase(name) != 0; +} + +Context* GetContext(const String& name) +{ + auto it = core_data->contexts.find(name); + if (it == core_data->contexts.end()) + return nullptr; + + return it->second.get(); +} + +Context* GetContext(int index) +{ + if (index < 0 || index >= GetNumContexts()) + return nullptr; + + auto it = core_data->contexts.begin(); + std::advance(it, index); + + if (it == core_data->contexts.end()) + return nullptr; + + return it->second.get(); +} + +int GetNumContexts() +{ + return (int)core_data->contexts.size(); +} + +bool LoadFontFace(const String& file_path, bool fallback_face, Style::FontWeight weight, int face_index) +{ + return font_interface->LoadFontFace(file_path, face_index, fallback_face, weight); +} + +bool LoadFontFace(const String& file_path, const String& family, Style::FontStyle style, Style::FontWeight weight, bool fallback_face, int face_index) +{ + return font_interface->LoadFontFace(file_path, face_index, family, style, weight, fallback_face); +} + +bool LoadFontFace(Span data, const String& family, Style::FontStyle style, Style::FontWeight weight, bool fallback_face, int face_index) +{ + return font_interface->LoadFontFace(data, face_index, family, style, weight, fallback_face); +} + +void RegisterPlugin(Plugin* plugin) +{ + if (initialised) + plugin->OnInitialise(); + + PluginRegistry::RegisterPlugin(plugin); +} + +void UnregisterPlugin(Plugin* plugin) +{ + PluginRegistry::UnregisterPlugin(plugin); + + if (initialised) + plugin->OnShutdown(); +} + +EventId RegisterEventType(const String& type, bool interruptible, bool bubbles, DefaultActionPhase default_action_phase) +{ + return EventSpecificationInterface::InsertOrReplaceCustom(type, interruptible, bubbles, default_action_phase); +} + +StringList GetTextureSourceList() +{ + StringList result; + if (!core_data) + return result; + for (const auto& render_manager : core_data->render_managers) + { + RenderManagerAccess::GetTextureSourceList(render_manager.second.get(), result); + } + return result; +} + +void ReleaseTextures(RenderInterface* match_render_interface) +{ + if (!core_data) + return; + for (auto& render_manager : core_data->render_managers) + { + if (!match_render_interface || render_manager.first == match_render_interface) + RenderManagerAccess::ReleaseAllTextures(render_manager.second.get()); + } +} + +bool ReleaseTexture(const String& source, RenderInterface* match_render_interface) +{ + bool result = false; + if (!core_data) + return result; + for (auto& render_manager : core_data->render_managers) + { + if (!match_render_interface || render_manager.first == match_render_interface) + { + if (RenderManagerAccess::ReleaseTexture(render_manager.second.get(), source)) + result = true; + } + } + return result; +} + +void ReleaseCompiledGeometry(RenderInterface* match_render_interface) +{ + if (!core_data) + return; + for (auto& render_manager : core_data->render_managers) + { + if (!match_render_interface || render_manager.first == match_render_interface) + RenderManagerAccess::ReleaseAllCompiledGeometry(render_manager.second.get()); + } +} + +void ReleaseFontResources() +{ + if (!font_interface) + return; + + for (const auto& name_context : core_data->contexts) + name_context.second->GetRootElement()->DirtyFontFaceRecursive(); + + font_interface->ReleaseFontResources(); + + for (const auto& name_context : core_data->contexts) + name_context.second->Update(); +} + +void ReleaseRenderManagers() +{ + auto& contexts = core_data->contexts; + auto& render_managers = core_data->render_managers; + + ReleaseFontResources(); + + for (auto it = render_managers.begin(); it != render_managers.end();) + { + RenderManager* render_manager = it->second.get(); + const auto num_contexts_using_manager = std::count_if(contexts.begin(), contexts.end(), + [&](const auto& context_pair) { return &context_pair.second->GetRenderManager() == render_manager; }); + + if (num_contexts_using_manager == 0) + it = render_managers.erase(it); + else + ++it; + } +} + +// Functions that need to be accessible within the Core library, but not publicly. +namespace CoreInternal { + + bool HasRenderManager(RenderInterface* match_render_interface) + { + return core_data && core_data->render_managers.find(match_render_interface) != core_data->render_managers.end(); + } + +} // namespace CoreInternal + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataController.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataController.cpp new file mode 100644 index 00000000..3fcc0272 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataController.cpp @@ -0,0 +1,41 @@ +#include "DataController.h" +#include "../../Include/RmlUi/Core/Element.h" +#include "EventSpecification.h" + +namespace Rml { + +DataController::DataController(Element* element) : attached_element(element->GetObserverPtr()) {} + +DataController::~DataController() {} +Element* DataController::GetElement() const +{ + return attached_element.get(); +} + +bool DataController::IsValid() const +{ + return static_cast(attached_element); +} + +DataControllers::DataControllers() {} + +DataControllers::~DataControllers() {} + +void DataControllers::Add(DataControllerPtr controller) +{ + RMLUI_ASSERT(controller); + + Element* element = controller->GetElement(); + RMLUI_ASSERTMSG(element, "Invalid controller, make sure it is valid before adding"); + if (!element) + return; + + controllers.emplace(element, std::move(controller)); +} + +void DataControllers::OnElementRemove(Element* element) +{ + controllers.erase(element); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataController.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataController.h new file mode 100644 index 00000000..5653827e --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataController.h @@ -0,0 +1,83 @@ +#pragma once + +#include "../../Include/RmlUi/Core/Header.h" +#include "../../Include/RmlUi/Core/Traits.h" +#include "../../Include/RmlUi/Core/Types.h" + +namespace Rml { + +class Element; +class DataModel; + +class DataControllerInstancer : public NonCopyMoveable { +public: + DataControllerInstancer() {} + virtual ~DataControllerInstancer() {} + virtual DataControllerPtr InstanceController(Element* element) = 0; +}; + +template +class DataControllerInstancerDefault final : public DataControllerInstancer { +public: + DataControllerPtr InstanceController(Element* element) override { return DataControllerPtr(new T(element)); } +}; + +/** + Data controller. + + Data controllers are used to respond to some change in the document, + usually by setting data variables. Such document changes are usually + a result of user input. + A data controller is declared in the document by the element attribute: + + data-[type]-[modifier]="[assignment_expression]" + + This is similar to declaration of data views, except that controllers + instead take an assignment expression to set a variable. Note that, as + opposed to views, controllers only respond to certain changes in the + document, not to changed data variables. + + The modifier may or may not be required depending on the data controller. + + */ + +class DataController : public Releasable { +public: + virtual ~DataController(); + + // Initialize the data controller. + // @param[in] model The data model the controller will be attached to. + // @param[in] element The element which spawned the controller. + // @param[in] expression The value of the element's 'data-' attribute which spawned the controller (see above). + // @param[in] modifier The modifier for the given controller type (see above). + // @return True on success. + virtual bool Initialize(DataModel& model, Element* element, const String& expression, const String& modifier) = 0; + + // Returns the attached element if it still exists. + Element* GetElement() const; + + // Returns true if the element still exists. + bool IsValid() const; + +protected: + DataController(Element* element); + +private: + ObserverPtr attached_element; +}; + +class DataControllers : NonCopyMoveable { +public: + DataControllers(); + ~DataControllers(); + + void Add(DataControllerPtr controller); + + void OnElementRemove(Element* element); + +private: + using ElementControllersMap = UnorderedMultimap; + ElementControllersMap controllers; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataControllerDefault.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataControllerDefault.cpp new file mode 100644 index 00000000..69953ae2 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataControllerDefault.cpp @@ -0,0 +1,120 @@ +#include "DataControllerDefault.h" +#include "../../Include/RmlUi/Core/Element.h" +#include "DataController.h" +#include "DataExpression.h" +#include "DataModel.h" +#include "EventSpecification.h" + +namespace Rml { + +DataControllerValue::DataControllerValue(Element* element) : DataController(element) {} + +DataControllerValue::~DataControllerValue() +{ + if (Element* element = GetElement()) + element->RemoveEventListener(EventId::Change, this); +} + +bool DataControllerValue::Initialize(DataModel& model, Element* element, const String& variable_name, const String& /*modifier*/) +{ + RMLUI_ASSERT(element); + + DataAddress variable_address = model.ResolveAddress(variable_name, element); + if (variable_address.empty()) + return false; + + if (model.AllowMissingVariables() || model.GetVariable(variable_address)) + address = std::move(variable_address); + + element->AddEventListener(EventId::Change, this); + + return true; +} + +void DataControllerValue::ProcessEvent(Event& event) +{ + Element* element = GetElement(); + if (!element || event.GetTargetElement() != element) + return; + + Variant value_to_set; + const auto& parameters = event.GetParameters(); + const auto override_value_it = parameters.find("data-binding-override-value"); + const auto value_it = parameters.find("value"); + if (override_value_it != parameters.cend()) + value_to_set = override_value_it->second; + else if (value_it != parameters.cend()) + value_to_set = value_it->second; + else + Log::Message(Log::LT_WARNING, + "A 'change' event was received, but it did not contain the attribute 'value' when processing a data binding in %s", + element->GetAddress().c_str()); + + DataModel* model = element->GetDataModel(); + if (value_to_set.GetType() == Variant::NONE || !model) + return; + + if (DataVariable variable = model->GetVariable(address)) + if (variable.Set(value_to_set)) + model->DirtyVariable(address.front().name); +} + +void DataControllerValue::Release() +{ + delete this; +} + +DataControllerEvent::DataControllerEvent(Element* element) : DataController(element) {} + +DataControllerEvent::~DataControllerEvent() +{ + if (Element* element = GetElement()) + { + if (id != EventId::Invalid) + element->RemoveEventListener(id, this); + } +} + +bool DataControllerEvent::Initialize(DataModel& model, Element* element, const String& expression_str, const String& modifier) +{ + RMLUI_ASSERT(element); + + expression = MakeUnique(expression_str); + DataExpressionInterface expr_interface(&model, element); + + if (!expression->Parse(expr_interface, true)) + return false; + + id = EventSpecificationInterface::GetIdOrInsert(modifier); + if (id == EventId::Invalid) + { + Log::Message(Log::LT_WARNING, "Event type '%s' could not be recognized, while adding 'data-event' to %s", modifier.c_str(), + element->GetAddress().c_str()); + return false; + } + + element->AddEventListener(id, this); + + return true; +} + +void DataControllerEvent::ProcessEvent(Event& event) +{ + if (!expression) + return; + + Element* element = GetElement(); + if (!element) + return; + + DataExpressionInterface expr_interface(element->GetDataModel(), element, &event); + Variant unused_value_out; + expression->Run(expr_interface, unused_value_out); +} + +void DataControllerEvent::Release() +{ + delete this; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataControllerDefault.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataControllerDefault.h new file mode 100644 index 00000000..b6de1bb9 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataControllerDefault.h @@ -0,0 +1,52 @@ +#pragma once + +#include "../../Include/RmlUi/Core/DataVariable.h" +#include "../../Include/RmlUi/Core/EventListener.h" +#include "../../Include/RmlUi/Core/Header.h" +#include "../../Include/RmlUi/Core/Types.h" +#include "DataController.h" + +namespace Rml { + +class Element; +class DataModel; +class DataExpression; +using DataExpressionPtr = UniquePtr; + +class DataControllerValue : public DataController, private EventListener { +public: + DataControllerValue(Element* element); + ~DataControllerValue() override; + + bool Initialize(DataModel& model, Element* element, const String& expression, const String& modifier) override; + +private: + // Responds to 'Change' events. + void ProcessEvent(Event& event) override; + + // Delete this. + void Release() override; + + DataAddress address; +}; + +class DataControllerEvent final : public DataController, private EventListener { +public: + DataControllerEvent(Element* element); + ~DataControllerEvent() override; + + bool Initialize(DataModel& model, Element* element, const String& expression, const String& modifier) override; + +protected: + // Responds to the event type specified in the attribute modifier. + void ProcessEvent(Event& event) override; + + // Delete this. + void Release() override; + +private: + EventId id = EventId::Invalid; + DataExpressionPtr expression; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataExpression.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataExpression.cpp new file mode 100644 index 00000000..165b7d0b --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataExpression.cpp @@ -0,0 +1,1218 @@ +#include "DataExpression.h" +#include "../../Include/RmlUi/Core/DataModelHandle.h" +#include "../../Include/RmlUi/Core/Event.h" +#include "../../Include/RmlUi/Core/Variant.h" +#include "DataModel.h" +#include + +#ifdef _MSC_VER + #pragma warning(default : 4061) + #pragma warning(default : 4062) +#endif + +namespace Rml { + +class DataParser; + +/* + The abstract machine for RmlUi data expressions. + + The machine can execute a program which contains a list of instructions listed below. + + The abstract machine has three registers: + R Typically results and right-hand side arguments. + L Typically left-hand side arguments. + + And a stack: + S The program stack. + + In addition, each instruction has an optional payload: + D Instruction data (payload). + + Notation used in the instruction list below: + S+ Push to stack S. + S- Pop stack S (returns the popped value). +*/ +enum class Instruction { + // clang-format off + // Assignment (register/stack) = Read (register R/L, instruction data D, or stack) + Push = 'P', // S+ = R + Pop = 'o', // = S- (D determines R/L) + Literal = 'D', // R = D + Variable = 'V', // R = DataModel.GetVariable(D) (D is an index into the variable address list) + Add = '+', // R = L + R + Subtract = '-', // R = L - R + Multiply = '*', // R = L * R + Divide = '/', // R = L / R + Not = '!', // R = !R + And = '&', // R = L && R + Or = '|', // R = L || R + Less = '<', // R = L < R + LessEq = 'L', // R = L <= R + Greater = '>', // R = L > R + GreaterEq = 'G', // R = L >= R + Equal = '=', // R = L == R + NotEqual = 'N', // R = L != R + NumArguments = '#', // R = D (Contains the num. arguments currently on the stack, immediately followed by a 'T' or 'E' instruction) + TransformFnc = 'T', // R = DataModel.Execute(D, A) where A = S[TOP - R, TOP]; S -= R; (D determines function name, input R the num. arguments, A the arguments) + EventFnc = 'E', // DataModel.EventCallback(D, A); S -= R; + Assign = 'A', // DataModel.SetVariable(D, R) + DynamicVariable = 'Y', // DataModel.GetVariable(DataModel.ParseAddress(R)) (Looks up a variable by path in R) + CastToInt = 'I', // R = (int)R + Jump = 'J', // Jumps to instruction index D + JumpIfZero = 'Z', // If R is false, jumps to instruction index D + // clang-format on +}; + +enum class Register { + R, + L, +}; + +struct InstructionData { + Instruction instruction; + Variant data; +}; + +struct ProgramState { + size_t program_length; + int stack_size; +}; + +namespace Parse { + static void Assignment(DataParser& parser); + static void Expression(DataParser& parser); +} // namespace Parse + +class DataParser { +public: + DataParser(String expression, DataExpressionInterface expression_interface) : + expression(std::move(expression)), expression_interface(expression_interface) + {} + + char Look() + { + if (reached_end) + return '\0'; + return expression[index]; + } + + bool Match(char c, bool skip_whitespace = true) + { + if (c == Look()) + { + Next(); + if (skip_whitespace) + SkipWhitespace(); + return true; + } + Expected(c); + return false; + } + + char Next() + { + ++index; + if (index >= expression.size()) + reached_end = true; + return Look(); + } + + void SkipWhitespace() + { + char c = Look(); + while (StringUtilities::IsWhitespace(c)) + c = Next(); + } + + void Error(const String& message) + { + parse_error = true; + Log::Message(Log::LT_WARNING, "Error in data expression at %zu. %s", index, message.c_str()); + Log::Message(Log::LT_WARNING, " \"%s\"", expression.c_str()); + + const size_t cursor_offset = size_t(index) + 3; + const String cursor_string = String(cursor_offset, ' ') + '^'; + Log::Message(Log::LT_WARNING, "%s", cursor_string.c_str()); + } + void Expected(const String& expected_symbols) + { + const char c = Look(); + if (c == '\0') + Error(CreateString("Expected %s but found end of string.", expected_symbols.c_str())); + else + Error(CreateString("Expected %s but found character '%c'.", expected_symbols.c_str(), c)); + } + void Expected(char expected) { Expected(String(1, '\'') + expected + '\''); } + + bool Parse(bool is_assignment_expression) + { + program.clear(); + variable_addresses.clear(); + index = 0; + reached_end = false; + parse_error = false; + if (expression.empty()) + reached_end = true; + + SkipWhitespace(); + + if (is_assignment_expression) + Parse::Assignment(*this); + else + Parse::Expression(*this); + + if (!reached_end) + { + parse_error = true; + Error(CreateString("Unexpected character '%c' encountered.", Look())); + } + if (!parse_error && program_stack_size != 0) + { + parse_error = true; + Error(CreateString("Internal parser error, inconsistent stack operations. Stack size is %d at parse end.", program_stack_size)); + } + + return !parse_error; + } + + Program ReleaseProgram() + { + RMLUI_ASSERT(!parse_error); + return std::move(program); + } + AddressList ReleaseAddresses() + { + RMLUI_ASSERT(!parse_error); + return std::move(variable_addresses); + } + + void Emit(Instruction instruction, Variant data = Variant()) + { + RMLUI_ASSERTMSG(instruction != Instruction::Push && instruction != Instruction::Pop && instruction != Instruction::NumArguments && + instruction != Instruction::TransformFnc && instruction != Instruction::EventFnc && instruction != Instruction::Variable && + instruction != Instruction::Assign, + "Use Push(), Pop(), Function(), Variable(), and Assign() procedures for stack manipulation and variable instructions."); + + program.push_back(InstructionData{instruction, std::move(data)}); + } + void Push() + { + program_stack_size += 1; + program.push_back(InstructionData{Instruction::Push, Variant()}); + } + void Pop(Register destination) + { + if (program_stack_size <= 0) + { + Error("Internal parser error: Tried to pop an empty stack."); + return; + } + program_stack_size -= 1; + program.push_back(InstructionData{Instruction::Pop, Variant(int(destination))}); + } + void Function(Instruction instruction, int num_arguments, String&& name) + { + RMLUI_ASSERT(instruction == Instruction::TransformFnc || instruction == Instruction::EventFnc); + RMLUI_ASSERT(num_arguments >= 0); + if (program_stack_size < num_arguments) + { + Error(CreateString("Internal parser error: Popping %d arguments, but the stack contains only %d elements.", num_arguments, + program_stack_size)); + return; + } + program_stack_size -= num_arguments; + program.push_back(InstructionData{Instruction::NumArguments, Variant(int(num_arguments))}); + program.push_back(InstructionData{instruction, Variant(std::move(name))}); + } + void Variable(const String& data_address) { VariableGetSet(data_address, false); } + void Assign(const String& data_address) { VariableGetSet(data_address, true); } + size_t InstructionIndex() const { return program.size(); } + void PatchInstruction(size_t index, InstructionData data) { program[index] = data; } + + ProgramState GetProgramState() { return ProgramState{program.size(), program_stack_size}; } + + void SetProgramState(const ProgramState& state) + { + RMLUI_ASSERT(state.program_length <= program.size()); + program.resize(state.program_length); + program_stack_size = state.stack_size; + } + + bool AddVariableAddress(const String& name) + { + DataAddress address = expression_interface.ParseAddress(name); + if (address.empty()) + { + return false; + } + + variable_addresses.push_back(std::move(address)); + return true; + } + +private: + void VariableGetSet(const String& name, bool is_assignment) + { + DataAddress address = expression_interface.ParseAddress(name); + if (address.empty()) + { + Error(CreateString("Could not find data variable with name '%s'.", name.c_str())); + return; + } + int index = int(variable_addresses.size()); + variable_addresses.push_back(std::move(address)); + program.push_back(InstructionData{is_assignment ? Instruction::Assign : Instruction::Variable, Variant(int(index))}); + } + + const String expression; + DataExpressionInterface expression_interface; + + size_t index = 0; + bool reached_end = false; + bool parse_error = true; + int program_stack_size = 0; + + Program program; + + AddressList variable_addresses; +}; + +namespace Parse { + + // Forward declare all parse functions. + static void Assignment(DataParser& parser); + + // The following in order of precedence. + static void Expression(DataParser& parser); + static void Relational(DataParser& parser); + static void Additive(DataParser& parser); + static void Term(DataParser& parser); + static void Factor(DataParser& parser); + + static void NumberLiteral(DataParser& parser); + static void StringLiteral(DataParser& parser); + static String VariableExpression(DataParser& parser, const String& address_prefix); + static void VariableOrFunction(DataParser& parser); + + static void Add(DataParser& parser); + static void Subtract(DataParser& parser); + static void Multiply(DataParser& parser); + static void Divide(DataParser& parser); + + static void Not(DataParser& parser); + static void And(DataParser& parser); + static void Or(DataParser& parser); + static void Less(DataParser& parser); + static void Greater(DataParser& parser); + static void Equal(DataParser& parser); + static void NotEqual(DataParser& parser); + + static void Ternary(DataParser& parser); + static void Function(DataParser& parser, Instruction function_type, String&& name, bool first_argument_piped); + + // Helper functions + static bool IsVariableCharacter(char c, bool is_first_character) + { + const bool is_alpha = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); + + if (is_first_character) + return is_alpha; + + if (is_alpha || (c >= '0' && c <= '9')) + return true; + + for (char valid_char : "_.") + { + if (c == valid_char && valid_char != '\0') + return true; + } + + return false; + } + static String VariableOrFunctionName(DataParser& parser, bool* out_valid_function_name) + { + String name; + bool is_first_character = true; + char c = parser.Look(); + + while (IsVariableCharacter(c, is_first_character)) + { + name += c; + c = parser.Next(); + is_first_character = false; + } + + if (out_valid_function_name) + *out_valid_function_name = (name.find_first_of(". ") == String::npos); + + return name; + } + + static String FindNumberLiteral(DataParser& parser, bool silent) + { + String str; + + bool first_match = false; + bool has_dot = false; + char c = parser.Look(); + if (c == '-') + { + str += c; + c = parser.Next(); + } + + while ((c >= '0' && c <= '9') || (c == '.' && !has_dot)) + { + first_match = true; + str += c; + if (c == '.') + has_dot = true; + c = parser.Next(); + } + + if (!first_match) + { + if (!silent) + parser.Error(CreateString("Invalid number literal. Expected '0-9' or '.' but found '%c'.", c)); + + return String(); + } + + return str; + } + + // Parser functions + static void Assignment(DataParser& parser) + { + bool looping = true; + while (looping) + { + if (parser.Look() != '\0') + { + String variable_name = VariableOrFunctionName(parser, nullptr); + if (variable_name.empty()) + { + parser.Error("Expected a variable for assignment but got an empty name."); + return; + } + + parser.SkipWhitespace(); + + const char c = parser.Look(); + if (c == '=') + { + parser.Match('='); + Expression(parser); + parser.Assign(variable_name); + } + else if (c == '(' || c == ';' || c == '\0') + { + Function(parser, Instruction::EventFnc, std::move(variable_name), false); + } + else + { + parser.Expected("one of = ; ( or end of string"); + return; + } + } + + const char c = parser.Look(); + if (c == ';') + parser.Match(';'); + else if (c == '\0') + looping = false; + else + { + parser.Expected("';' or end of string"); + looping = false; + } + } + } + static void Expression(DataParser& parser) + { + Relational(parser); + + bool looping = true; + while (looping) + { + switch (parser.Look()) + { + case '&': And(parser); break; + case '|': + { + parser.Match('|', false); + if (parser.Look() == '|') + Or(parser); + else + { + parser.Push(); + parser.SkipWhitespace(); + bool valid_function_name = true; + String name = VariableOrFunctionName(parser, &valid_function_name); + if (name.empty()) + { + parser.Error("Expected a transform function name but got an empty name."); + return; + } + if (!valid_function_name) + { + parser.Error("Expected a transform function name but got an invalid name '" + name + "'."); + return; + } + + Function(parser, Instruction::TransformFnc, std::move(name), true); + } + } + break; + case '?': Ternary(parser); break; + default: looping = false; + } + } + } + + static void Relational(DataParser& parser) + { + Additive(parser); + + bool looping = true; + while (looping) + { + switch (parser.Look()) + { + case '=': Equal(parser); break; + case '!': NotEqual(parser); break; + case '<': Less(parser); break; + case '>': Greater(parser); break; + default: looping = false; + } + } + } + + static void Additive(DataParser& parser) + { + Term(parser); + + bool looping = true; + while (looping) + { + switch (parser.Look()) + { + case '+': Add(parser); break; + case '-': Subtract(parser); break; + default: looping = false; + } + } + } + + static void Term(DataParser& parser) + { + Factor(parser); + + bool looping = true; + while (looping) + { + switch (parser.Look()) + { + case '*': Multiply(parser); break; + case '/': Divide(parser); break; + default: looping = false; + } + } + } + static void Factor(DataParser& parser) + { + const char c = parser.Look(); + + if (c == '(') + { + parser.Match('('); + Expression(parser); + parser.Match(')'); + } + else if (c == '\'') + { + parser.Match('\'', false); + StringLiteral(parser); + parser.Match('\''); + } + else if (c == '!') + { + Not(parser); + parser.SkipWhitespace(); + } + else if (c == '-' || (c >= '0' && c <= '9')) + { + NumberLiteral(parser); + parser.SkipWhitespace(); + } + else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) + { + VariableOrFunction(parser); + parser.SkipWhitespace(); + } + else + parser.Expected("literal, variable name, function name, parenthesis, or '!'"); + } + + static void NumberLiteral(DataParser& parser) + { + String str = FindNumberLiteral(parser, false); + + if (str.empty()) + return; + + const double number = FromString(str, 0.0); + + parser.Emit(Instruction::Literal, Variant(number)); + } + + static void StringLiteral(DataParser& parser) + { + String str; + + char c = parser.Look(); + char c_prev = '\0'; + + while (c != '\0' && (c != '\'' || c_prev == '\\')) + { + if (c_prev == '\\' && (c == '\\' || c == '\'')) + { + str.pop_back(); + c_prev = '\0'; + } + else + { + c_prev = c; + } + + str += c; + c = parser.Next(); + } + + parser.Emit(Instruction::Literal, Variant(str)); + } + + static String VariableExpression(DataParser& parser, const String& address_prefix) + { + if (parser.Look() == '[') + { + parser.Next(); + String prefix = address_prefix; + + prefix.push_back('['); + + // Backup program state before trying to parse number inside brackets. + // Could turn out to be expression and needs reparsing + auto backup_state = parser.GetProgramState(); + + String index = FindNumberLiteral(parser, true); + if (!index.empty() && parser.Look() == ']') + { + parser.Next(); + prefix.append(index); + prefix.push_back(']'); + return VariableExpression(parser, prefix); + } + else + { + parser.SetProgramState(backup_state); + + parser.Emit(Instruction::Literal, Variant(prefix)); + parser.Push(); + + Expression(parser); + parser.Emit(Instruction::CastToInt); + parser.Push(); + + parser.Match(']'); + VariableExpression(parser, "]"); + + parser.Pop(Register::L); + parser.Emit(Instruction::Add, Variant()); + parser.Pop(Register::L); + parser.Emit(Instruction::Add, Variant()); + + return ""; + } + } + else if (parser.Look() == '.') + { + parser.Next(); + return VariableExpression(parser, address_prefix + "."); + } + else + { + String next = VariableOrFunctionName(parser, nullptr); + if (next.empty()) + { + if (!address_prefix.empty()) + parser.Emit(Instruction::Literal, Variant(address_prefix)); + return address_prefix; + } + return VariableExpression(parser, address_prefix + next); + } + } + + static void VariableOrFunction(DataParser& parser) + { + bool valid_function_name = true; + String name = VariableOrFunctionName(parser, &valid_function_name); + if (name.empty()) + { + parser.Error("Expected a variable or function name but got an empty name."); + return; + } + + // Keywords are parsed like variables, but are really literals. Check for them here. + if (name == "true") + parser.Emit(Instruction::Literal, Variant(true)); + else if (name == "false") + parser.Emit(Instruction::Literal, Variant(false)); + else if (parser.Look() == '(') + { + if (!valid_function_name) + { + parser.Error("Invalid function name '" + name + "'."); + return; + } + Function(parser, Instruction::TransformFnc, std::move(name), false); + } + else if (parser.Look() == '[') + { + // Backup program state before trying to parse part inside brackets. + // Could turn out to be expression and needs reparsing + auto backup_state = parser.GetProgramState(); + + String full_address = VariableExpression(parser, name); + + if (!full_address.empty()) + { + parser.SetProgramState(backup_state); + parser.Variable(full_address); + } + else + { + // add the root of a variable expression as dependency into the address list + parser.AddVariableAddress(name); + + parser.Emit(Instruction::DynamicVariable, Variant()); + } + } + else + parser.Variable(name); + } + + static void Add(DataParser& parser) + { + parser.Match('+'); + parser.Push(); + Term(parser); + parser.Pop(Register::L); + parser.Emit(Instruction::Add); + } + static void Subtract(DataParser& parser) + { + parser.Match('-'); + parser.Push(); + Term(parser); + parser.Pop(Register::L); + parser.Emit(Instruction::Subtract); + } + static void Multiply(DataParser& parser) + { + parser.Match('*'); + parser.Push(); + Factor(parser); + parser.Pop(Register::L); + parser.Emit(Instruction::Multiply); + } + static void Divide(DataParser& parser) + { + parser.Match('/'); + parser.Push(); + Factor(parser); + parser.Pop(Register::L); + parser.Emit(Instruction::Divide); + } + + static void Not(DataParser& parser) + { + parser.Match('!'); + Factor(parser); + parser.Emit(Instruction::Not); + } + static void Or(DataParser& parser) + { + // We already skipped the first '|' during expression + parser.Match('|'); + parser.Push(); + Relational(parser); + parser.Pop(Register::L); + parser.Emit(Instruction::Or); + } + static void And(DataParser& parser) + { + parser.Match('&', false); + parser.Match('&'); + parser.Push(); + Relational(parser); + parser.Pop(Register::L); + parser.Emit(Instruction::And); + } + static void Less(DataParser& parser) + { + Instruction instruction = Instruction::Less; + parser.Match('<', false); + if (parser.Look() == '=') + { + parser.Match('='); + instruction = Instruction::LessEq; + } + else + { + parser.SkipWhitespace(); + } + parser.Push(); + Additive(parser); + parser.Pop(Register::L); + parser.Emit(instruction); + } + static void Greater(DataParser& parser) + { + Instruction instruction = Instruction::Greater; + parser.Match('>', false); + if (parser.Look() == '=') + { + parser.Match('='); + instruction = Instruction::GreaterEq; + } + else + { + parser.SkipWhitespace(); + } + parser.Push(); + Additive(parser); + parser.Pop(Register::L); + parser.Emit(instruction); + } + static void Equal(DataParser& parser) + { + parser.Match('=', false); + parser.Match('='); + parser.Push(); + Additive(parser); + parser.Pop(Register::L); + parser.Emit(Instruction::Equal); + } + static void NotEqual(DataParser& parser) + { + parser.Match('!', false); + parser.Match('='); + parser.Push(); + Additive(parser); + parser.Pop(Register::L); + parser.Emit(Instruction::NotEqual); + } + + static void Ternary(DataParser& parser) + { + size_t jump_false_branch = parser.InstructionIndex(); + parser.Emit(Instruction::JumpIfZero); + + parser.Match('?'); + Expression(parser); + size_t jump_end = parser.InstructionIndex(); + parser.Emit(Instruction::Jump); + + parser.Match(':'); + size_t false_branch = parser.InstructionIndex(); + Expression(parser); + + size_t end = parser.InstructionIndex(); + parser.PatchInstruction(jump_false_branch, InstructionData{Instruction::JumpIfZero, Variant((uint64_t)false_branch)}); + parser.PatchInstruction(jump_end, InstructionData{Instruction::Jump, Variant((uint64_t)end)}); + } + + static void Function(DataParser& parser, Instruction function_type, String&& func_name, bool first_argument_piped) + { + RMLUI_ASSERT(function_type == Instruction::TransformFnc || function_type == Instruction::EventFnc); + + // We already matched the variable name, and also pushed the first argument to the stack if it was piped using '|'. + int num_arguments = first_argument_piped ? 1 : 0; + if (parser.Look() == '(') + { + bool looping = true; + + parser.Match('('); + if (parser.Look() == ')') + { + parser.Match(')'); + looping = false; + } + + while (looping) + { + num_arguments += 1; + Expression(parser); + parser.Push(); + + switch (parser.Look()) + { + case ')': + parser.Match(')'); + looping = false; + break; + case ',': parser.Match(','); break; + default: + parser.Expected("one of ')' or ','"); + looping = false; + break; + } + } + } + else + { + parser.SkipWhitespace(); + } + + parser.Function(function_type, num_arguments, std::move(func_name)); + } + +} // namespace Parse + +static String DumpProgram(const Program& program) +{ + String str; + for (size_t i = 0; i < program.size(); i++) + { + String instruction_str = program[i].data.Get(); + str += CreateString(" %4zu '%c' %s\n", i, char(program[i].instruction), instruction_str.c_str()); + } + return str; +} + +class DataInterpreter { +public: + DataInterpreter(const Program& program, const AddressList& addresses, DataExpressionInterface expression_interface) : + program(program), addresses(addresses), expression_interface(expression_interface) + {} + + bool Error(const String& message) const + { + Log::Message(Log::LT_WARNING, "Error during execution. %s", message.c_str()); + RMLUI_ERROR; + return false; + } + + bool Run() + { + bool success = true; + size_t i = 0; + while (i < program.size()) + { + size_t next_instruction = i + 1; + if (!Execute(program[i].instruction, program[i].data, next_instruction)) + { + success = false; + break; + } + i = next_instruction; + } + + if (success && !stack.empty()) + Log::Message(Log::LT_WARNING, "Possible data interpreter stack corruption. Stack size is %zu at end of execution (should be zero).", + stack.size()); + + if (!success) + { + String program_str = DumpProgram(program); + Log::Message(Log::LT_WARNING, "Failed to execute program with %zu instructions:", program.size()); + Log::Message(Log::LT_WARNING, "%s", program_str.c_str()); + } + + return success; + } + + Variant Result() const { return R; } + +private: + Variant R, L; + Vector stack; + + const Program& program; + const AddressList& addresses; + DataExpressionInterface expression_interface; + + bool Execute(const Instruction instruction, const Variant& data, size_t& next_instruction) + { + auto AnyString = [](const Variant& v1, const Variant& v2) { return v1.GetType() == Variant::STRING || v2.GetType() == Variant::STRING; }; + + switch (instruction) + { + case Instruction::Push: + { + stack.push_back(std::move(R)); + R.Clear(); + } + break; + case Instruction::Pop: + { + if (stack.empty()) + return Error("Cannot pop stack, it is empty."); + + Register reg = Register(data.Get(-1)); + switch (reg) + { + // clang-format off + case Register::R: R = stack.back(); stack.pop_back(); break; + case Register::L: L = stack.back(); stack.pop_back(); break; + // clang-format on + default: return Error(CreateString("Invalid register %d.", int(reg))); + } + } + break; + case Instruction::Literal: + { + R = data; + } + break; + case Instruction::DynamicVariable: + { + auto str = R.Get(); + auto address = expression_interface.ParseAddress(str); + if (address.empty()) + return Error("Variable address not found."); + R = expression_interface.GetValue(address); + } + break; + case Instruction::Variable: + { + size_t variable_index = size_t(data.Get(-1)); + if (variable_index < addresses.size()) + R = expression_interface.GetValue(addresses[variable_index]); + else + return Error("Variable address not found."); + } + break; + case Instruction::Add: + { + if (AnyString(L, R)) + R = Variant(L.Get() + R.Get()); + else + R = Variant(L.Get() + R.Get()); + } + break; + // clang-format off + case Instruction::Subtract: R = Variant(L.Get() - R.Get()); break; + case Instruction::Multiply: R = Variant(L.Get() * R.Get()); break; + case Instruction::Divide: R = Variant(L.Get() / R.Get()); break; + case Instruction::Not: R = Variant(!R.Get()); break; + case Instruction::And: R = Variant(L.Get() && R.Get()); break; + case Instruction::Or: R = Variant(L.Get() || R.Get()); break; + case Instruction::Less: R = Variant(L.Get() < R.Get()); break; + case Instruction::LessEq: R = Variant(L.Get() <= R.Get()); break; + case Instruction::Greater: R = Variant(L.Get() > R.Get()); break; + case Instruction::GreaterEq: R = Variant(L.Get() >= R.Get()); break; + // clang-format on + case Instruction::Equal: + { + if (AnyString(L, R)) + R = Variant(L.Get() == R.Get()); + else + R = Variant(L.Get() == R.Get()); + } + break; + case Instruction::NotEqual: + { + if (AnyString(L, R)) + R = Variant(L.Get() != R.Get()); + else + R = Variant(L.Get() != R.Get()); + } + break; + case Instruction::NumArguments: + { + const int num_arguments = data.Get(-1); + R = num_arguments; + } + break; + case Instruction::TransformFnc: + case Instruction::EventFnc: + { + Vector arguments; + if (!ExtractArgumentsFromStack(arguments)) + return false; + + const String function_name = data.Get(); + const bool result = (instruction == Instruction::TransformFnc ? expression_interface.CallTransform(function_name, arguments, R) + : expression_interface.EventCallback(function_name, arguments)); + if (!result) + { + String arguments_str; + for (size_t i = 0; i < arguments.size(); i++) + { + arguments_str += arguments[i].Get(); + if (i < arguments.size() - 1) + arguments_str += ", "; + } + return Error( + CreateString("Failed to execute %s: %s(%s)", instruction == Instruction::TransformFnc ? "transform function" : "event callback", + function_name.c_str(), arguments_str.c_str())); + } + } + break; + case Instruction::Assign: + { + size_t variable_index = size_t(data.Get(-1)); + if (variable_index < addresses.size()) + { + if (!expression_interface.SetValue(addresses[variable_index], R)) + return Error("Could not assign to variable."); + } + else + return Error("Variable address not found."); + } + break; + case Instruction::CastToInt: + { + int tmp; + if (!R.GetInto(tmp)) + return Error("Could not cast value to int."); + else + R = tmp; + } + break; + case Instruction::JumpIfZero: + { + if (!R.Get()) + next_instruction = data.Get(0); + } + break; + case Instruction::Jump: + { + next_instruction = data.Get(0); + } + break; + default: RMLUI_ERRORMSG("Instruction not implemented."); break; + } + return true; + } + + bool ExtractArgumentsFromStack(Vector& out_arguments) + { + int num_arguments = R.Get(-1); + if (num_arguments < 0) + return Error("Invalid number of arguments."); + if (stack.size() < size_t(num_arguments)) + return Error(CreateString("Cannot pop %d arguments, stack contains only %zu elements.", num_arguments, stack.size())); + + const auto it_stack_begin_arguments = stack.end() - num_arguments; + out_arguments.insert(out_arguments.end(), std::make_move_iterator(it_stack_begin_arguments), std::make_move_iterator(stack.end())); + + stack.erase(it_stack_begin_arguments, stack.end()); + return true; + } +}; + +DataExpression::DataExpression(String expression) : expression(std::move(expression)) {} + +DataExpression::~DataExpression() {} + +bool DataExpression::Parse(const DataExpressionInterface& expression_interface, bool is_assignment_expression) +{ + DataParser parser(expression, expression_interface); + if (!parser.Parse(is_assignment_expression)) + return false; + + program = parser.ReleaseProgram(); + addresses = parser.ReleaseAddresses(); + + return true; +} + +bool DataExpression::Run(const DataExpressionInterface& expression_interface, Variant& out_value) +{ + DataInterpreter interpreter(program, addresses, expression_interface); + + if (!interpreter.Run()) + return false; + + out_value = interpreter.Result(); + return true; +} + +StringList DataExpression::GetVariableNameList() const +{ + StringList list; + list.reserve(addresses.size()); + for (const DataAddress& address : addresses) + { + if (!address.empty()) + list.push_back(address[0].name); + } + return list; +} + +DataExpressionInterface::DataExpressionInterface(DataModel* data_model, Element* element, Event* event) : + data_model(data_model), element(element), event(event) +{} + +DataAddress DataExpressionInterface::ParseAddress(const String& address_str) const +{ + if (address_str.size() >= 4 && address_str[0] == 'e' && address_str[1] == 'v' && address_str[2] == '.') + return DataAddress{DataAddressEntry("ev"), DataAddressEntry(address_str.substr(3))}; + + return data_model ? data_model->ResolveAddress(address_str, element) : DataAddress(); +} +Variant DataExpressionInterface::GetValue(const DataAddress& address) const +{ + Variant result; + if (event && address.size() == 2 && address.front().name == "ev") + { + auto& parameters = event->GetParameters(); + auto it = parameters.find(address.back().name); + if (it != parameters.end()) + result = it->second; + } + else if (data_model) + { + data_model->GetVariableInto(address, result); + } + return result; +} + +bool DataExpressionInterface::SetValue(const DataAddress& address, const Variant& value) const +{ + bool result = false; + if (data_model && !address.empty()) + { + if (DataVariable variable = data_model->GetVariable(address)) + result = variable.Set(value); + + if (result) + data_model->DirtyVariable(address.front().name); + } + return result; +} + +bool DataExpressionInterface::CallTransform(const String& name, const VariantList& arguments, Variant& out_result) +{ + return data_model ? data_model->CallTransform(name, arguments, out_result) : false; +} + +bool DataExpressionInterface::EventCallback(const String& name, const VariantList& arguments) +{ + if (!data_model || !event) + return false; + + const DataEventFunc* func = data_model->GetEventCallback(name); + if (!func || !*func) + return false; + + DataModelHandle handle(data_model); + func->operator()(handle, *event, arguments); + return true; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataExpression.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataExpression.h new file mode 100644 index 00000000..4c809338 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataExpression.h @@ -0,0 +1,51 @@ +#pragma once + +#include "../../Include/RmlUi/Core/DataTypes.h" +#include "../../Include/RmlUi/Core/Header.h" +#include "../../Include/RmlUi/Core/Types.h" + +namespace Rml { + +class Element; +class DataModel; +struct InstructionData; +using Program = Vector; +using AddressList = Vector; + +class DataExpressionInterface { +public: + DataExpressionInterface() = default; + DataExpressionInterface(DataModel* data_model, Element* element, Event* event = nullptr); + + DataAddress ParseAddress(const String& address_str) const; + Variant GetValue(const DataAddress& address) const; + bool SetValue(const DataAddress& address, const Variant& value) const; + bool CallTransform(const String& name, const VariantList& arguments, Variant& out_result); + bool EventCallback(const String& name, const VariantList& arguments); + +private: + DataModel* data_model = nullptr; + Element* element = nullptr; + Event* event = nullptr; +}; + +class DataExpression { +public: + DataExpression(String expression); + ~DataExpression(); + + bool Parse(const DataExpressionInterface& expression_interface, bool is_assignment_expression); + + bool Run(const DataExpressionInterface& expression_interface, Variant& out_value); + + // Available after Parse() + StringList GetVariableNameList() const; + +private: + String expression; + + Program program; + AddressList addresses; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataModel.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataModel.cpp new file mode 100644 index 00000000..7e5c3a05 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataModel.cpp @@ -0,0 +1,383 @@ +#include "DataModel.h" +#include "../../Include/RmlUi/Core/DataTypeRegister.h" +#include "../../Include/RmlUi/Core/Element.h" +#include "DataController.h" +#include "DataView.h" + +namespace Rml { + +static DataAddress ParseAddress(const String& address_str) +{ + StringList list; + StringUtilities::ExpandString(list, address_str, '.'); + + DataAddress address; + address.reserve(list.size() * 2); + + for (const auto& item : list) + { + if (item.empty()) + return DataAddress(); + + size_t i_open = item.find('[', 0); + if (i_open == 0) + return DataAddress(); + + address.emplace_back(item.substr(0, i_open)); + + while (i_open != String::npos) + { + size_t i_close = item.find(']', i_open + 1); + if (i_close == String::npos) + return DataAddress(); + + int index = FromString(item.substr(i_open + 1, i_close - i_open), -1); + if (index < 0) + return DataAddress(); + + address.emplace_back(index); + + i_open = item.find('[', i_close + 1); + } + // TODO: Abort on invalid characters among [ ] and after the last found bracket? + } + + RMLUI_ASSERT(!address.empty() && !address[0].name.empty()); + + return address; +} + +// Returns an error string on error, or nullptr on success. +static const char* LegalVariableName(const String& name) +{ + static SmallUnorderedSet reserved_names{"it", "it_index", "ev", "true", "false", "size", "literal"}; + + if (name.empty()) + return "Name cannot be empty."; + + const String name_lower = StringUtilities::ToLower(name); + + const char first = name_lower.front(); + if (!(first >= 'a' && first <= 'z')) + return "First character must be 'a-z' or 'A-Z'."; + + for (const char c : name_lower) + { + if (!(c == '_' || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'))) + return "Name must strictly contain characters a-z, A-Z, 0-9 and under_score."; + } + + if (reserved_names.count(name_lower) == 1) + return "Name is reserved."; + + return nullptr; +} + +static String DataAddressToString(const DataAddress& address) +{ + String result; + bool is_first = true; + for (auto& entry : address) + { + if (entry.index >= 0) + result += '[' + ToString(entry.index) + ']'; + else + { + if (!is_first) + result += "."; + result += entry.name; + } + is_first = false; + } + return result; +} + +DataModel::DataModel(DataTypeRegister* data_type_register, bool allow_missing_variables) : + data_type_register(data_type_register), allow_missing_variables(allow_missing_variables) +{ + views = MakeUnique(); + controllers = MakeUnique(); +} + +DataModel::~DataModel() +{ + RMLUI_ASSERT(attached_elements.empty()); +} + +void DataModel::AddView(DataViewPtr view) +{ + views->Add(std::move(view)); +} + +void DataModel::AddController(DataControllerPtr controller) +{ + controllers->Add(std::move(controller)); +} + +bool DataModel::BindVariable(const String& name, DataVariable variable) +{ + const char* name_error_str = LegalVariableName(name); + if (name_error_str) + { + Log::Message(Log::LT_WARNING, "Could not bind data variable '%s'. %s", name.c_str(), name_error_str); + return false; + } + + if (!variable) + { + Log::Message(Log::LT_WARNING, "Could not bind variable '%s' to data model, data type not registered.", name.c_str()); + return false; + } + + bool inserted = variables.emplace(name, variable).second; + if (!inserted) + { + Log::Message(Log::LT_WARNING, "Data model variable with name '%s' already exists.", name.c_str()); + return false; + } + + return true; +} + +bool DataModel::BindFunc(const String& name, DataGetFunc get_func, DataSetFunc set_func) +{ + auto result = function_variable_definitions.emplace(name, nullptr); + auto& it = result.first; + bool inserted = result.second; + if (!inserted) + { + Log::Message(Log::LT_ERROR, "Data get/set function with name %s already exists in model", name.c_str()); + return false; + } + auto& func_definition_ptr = it->second; + func_definition_ptr = MakeUnique(std::move(get_func), std::move(set_func)); + + return BindVariable(name, DataVariable(func_definition_ptr.get(), nullptr)); +} + +bool DataModel::BindEventCallback(const String& name, DataEventFunc event_func) +{ + const char* name_error_str = LegalVariableName(name); + if (name_error_str) + { + Log::Message(Log::LT_WARNING, "Could not bind data event callback '%s'. %s", name.c_str(), name_error_str); + return false; + } + + if (!event_func) + { + Log::Message(Log::LT_WARNING, "Could not bind data event callback '%s' to data model, empty function provided.", name.c_str()); + return false; + } + + bool inserted = event_callbacks.emplace(name, std::move(event_func)).second; + if (!inserted) + { + Log::Message(Log::LT_WARNING, "Data event callback with name '%s' already exists.", name.c_str()); + return false; + } + + return true; +} + +bool DataModel::InsertAlias(Element* element, const String& alias_name, DataAddress replace_with_address) +{ + if (replace_with_address.empty() || replace_with_address.front().name.empty()) + { + Log::Message(Log::LT_WARNING, "Could not add alias variable '%s' to data model, replacement address invalid.", alias_name.c_str()); + return false; + } + + if (variables.count(alias_name) == 1) + Log::Message(Log::LT_WARNING, "Alias variable '%s' is shadowed by a global variable.", alias_name.c_str()); + + auto& map = aliases.emplace(element, SmallUnorderedMap()).first->second; + + auto it = map.find(alias_name); + if (it != map.end()) + Log::Message(Log::LT_WARNING, "Alias name '%s' in data model already exists, replaced.", alias_name.c_str()); + + map[alias_name] = std::move(replace_with_address); + + return true; +} + +bool DataModel::EraseAliases(Element* element) +{ + return aliases.erase(element) == 1; +} + +void DataModel::CopyAliases(Element* from_element, Element* to_element) +{ + if (from_element == to_element) + return; + + auto existing_map = aliases.find(from_element); + if (existing_map != aliases.end()) + { + // Need to create a copy to prevent errors during concurrent modification for 3rd party containers + SmallUnorderedMap copy = existing_map->second; + for (auto& [name, address] : copy) + aliases[to_element][name] = std::move(address); + } +} + +DataAddress DataModel::ResolveAddress(const String& address_str, Element* element) const +{ + DataAddress address = ParseAddress(address_str); + + if (address.empty()) + return address; + + const String& first_name = address.front().name; + + auto it = variables.find(first_name); + if (it != variables.end()) + return address; + + // Look for a variable alias for the first name. + + Element* ancestor = element; + while (ancestor && ancestor->GetDataModel() == this) + { + auto it_element = aliases.find(ancestor); + if (it_element != aliases.end()) + { + const auto& alias_names = it_element->second; + auto it_alias_name = alias_names.find(first_name); + if (it_alias_name != alias_names.end()) + { + const DataAddress& replace_address = it_alias_name->second; + if (replace_address.empty() || replace_address.front().name.empty()) + { + // Variable alias is invalid + return DataAddress(); + } + + // Insert the full alias address, replacing the first element. + address[0] = replace_address[0]; + address.insert(address.begin() + 1, replace_address.begin() + 1, replace_address.end()); + return address; + } + } + + ancestor = ancestor->GetParentNode(); + } + + if (allow_missing_variables) + return address; + + Log::Message(Log::LT_WARNING, "Could not find variable name '%s' in data model.", address_str.c_str()); + + return DataAddress(); +} + +DataVariable DataModel::GetVariable(const DataAddress& address) const +{ + if (address.empty()) + return DataVariable(); + + auto it = variables.find(address.front().name); + if (it != variables.end()) + { + DataVariable variable = it->second; + + for (int i = 1; i < (int)address.size() && variable; i++) + { + variable = variable.Child(address[i]); + if (!variable) + return DataVariable(); + } + + return variable; + } + + if (address[0].name == "literal") + { + if (address.size() > 2 && address[1].name == "int") + return MakeLiteralIntVariable(address[2].index); + } + + return DataVariable(); +} + +const DataEventFunc* DataModel::GetEventCallback(const String& name) +{ + auto it = event_callbacks.find(name); + if (it == event_callbacks.end()) + { + Log::Message(Log::LT_WARNING, "Could not find data event callback '%s' in data model.", name.c_str()); + return nullptr; + } + + return &it->second; +} + +bool DataModel::GetVariableInto(const DataAddress& address, Variant& out_value) const +{ + DataVariable variable = GetVariable(address); + bool result = (variable && variable.Get(out_value)); + if (!result && !allow_missing_variables) + Log::Message(Log::LT_WARNING, "Could not get value from data variable '%s'.", DataAddressToString(address).c_str()); + return result; +} + +void DataModel::DirtyVariable(const String& variable_name) +{ + RMLUI_ASSERTMSG(LegalVariableName(variable_name) == nullptr, "Illegal variable name provided. Only top-level variables can be dirtied."); + RMLUI_ASSERTMSG(allow_missing_variables || variables.count(variable_name) == 1, + "In DirtyVariable: Variable name not found among added variables."); + dirty_variables.emplace(variable_name); +} + +bool DataModel::IsVariableDirty(const String& variable_name) const +{ + RMLUI_ASSERTMSG(LegalVariableName(variable_name) == nullptr, "Illegal variable name provided. Only top-level variables can be dirtied."); + return dirty_variables.count(variable_name) == 1; +} + +void DataModel::DirtyAllVariables() +{ + dirty_variables.reserve(variables.size()); + for (const auto& variable : variables) + { + dirty_variables.emplace(variable.first); + } +} + +bool DataModel::CallTransform(const String& name, const VariantList& arguments, Variant& out_result) const +{ + if (const auto transform_register = data_type_register->GetTransformFuncRegister()) + return transform_register->Call(name, arguments, out_result); + return false; +} + +void DataModel::AttachModelRootElement(Element* element) +{ + attached_elements.insert(element); +} + +ElementList DataModel::GetAttachedModelRootElements() const +{ + return ElementList(attached_elements.begin(), attached_elements.end()); +} + +void DataModel::OnElementRemove(Element* element) +{ + EraseAliases(element); + views->OnElementRemove(element); + controllers->OnElementRemove(element); + attached_elements.erase(element); +} + +bool DataModel::Update(bool clear_dirty_variables) +{ + const bool result = views->Update(*this, dirty_variables); + + if (clear_dirty_variables) + dirty_variables.clear(); + + return result; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataModel.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataModel.h new file mode 100644 index 00000000..54b69360 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataModel.h @@ -0,0 +1,77 @@ +#pragma once + +#include "../../Include/RmlUi/Core/DataModelHandle.h" +#include "../../Include/RmlUi/Core/DataTypes.h" +#include "../../Include/RmlUi/Core/Header.h" +#include "../../Include/RmlUi/Core/Traits.h" +#include "../../Include/RmlUi/Core/Types.h" + +namespace Rml { + +class DataViews; +class DataControllers; +class DataVariable; +class Element; +class FuncDefinition; + +class DataModel : NonCopyMoveable { +public: + DataModel(DataTypeRegister* data_type_register = nullptr, bool allow_missing_variables = false); + ~DataModel(); + + void AddView(DataViewPtr view); + void AddController(DataControllerPtr controller); + + bool BindVariable(const String& name, DataVariable variable); + bool BindFunc(const String& name, DataGetFunc get_func, DataSetFunc set_func); + + bool BindEventCallback(const String& name, DataEventFunc event_func); + + bool InsertAlias(Element* element, const String& alias_name, DataAddress replace_with_address); + bool EraseAliases(Element* element); + void CopyAliases(Element* source_element, Element* target_element); + + DataAddress ResolveAddress(const String& address_str, Element* element) const; + const DataEventFunc* GetEventCallback(const String& name); + + DataVariable GetVariable(const DataAddress& address) const; + bool GetVariableInto(const DataAddress& address, Variant& out_value) const; + + void DirtyVariable(const String& variable_name); + bool IsVariableDirty(const String& variable_name) const; + void DirtyAllVariables(); + + bool CallTransform(const String& name, const VariantList& arguments, Variant& out_result) const; + + // Elements declaring 'data-model' need to be attached. + void AttachModelRootElement(Element* element); + ElementList GetAttachedModelRootElements() const; + + void OnElementRemove(Element* element); + + bool Update(bool clear_dirty_variables); + + DataTypeRegister* GetDataTypeRegister() const { return data_type_register; } + bool AllowMissingVariables() const { return allow_missing_variables; } + const UnorderedMap& GetAllVariables() const { return variables; } + +private: + UniquePtr views; + UniquePtr controllers; + + UnorderedMap variables; + DirtyVariables dirty_variables; + + UnorderedMap> function_variable_definitions; + UnorderedMap event_callbacks; + + using ScopedAliases = UnorderedMap>; + ScopedAliases aliases; + + DataTypeRegister* data_type_register; + bool allow_missing_variables; + + SmallUnorderedSet attached_elements; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataModelHandle.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataModelHandle.cpp new file mode 100644 index 00000000..41b3dc91 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataModelHandle.cpp @@ -0,0 +1,56 @@ +#include "../../Include/RmlUi/Core/DataModelHandle.h" +#include "DataModel.h" + +namespace Rml { + +DataModelHandle::DataModelHandle(DataModel* model) : model(model) {} + +bool DataModelHandle::IsVariableDirty(const String& variable_name) +{ + return model->IsVariableDirty(variable_name); +} + +void DataModelHandle::DirtyVariable(const String& variable_name) +{ + model->DirtyVariable(variable_name); +} + +void DataModelHandle::DirtyAllVariables() +{ + model->DirtyAllVariables(); +} + +DataModelConstructor::DataModelConstructor() : model(nullptr), type_register(nullptr) {} + +DataModelConstructor::DataModelConstructor(DataModel* model) : model(model), type_register(model->GetDataTypeRegister()) +{ + RMLUI_ASSERT(model); +} + +DataModelHandle DataModelConstructor::GetModelHandle() const +{ + return DataModelHandle(model); +} + +bool DataModelConstructor::BindFunc(const String& name, DataGetFunc get_func, DataSetFunc set_func) +{ + return model->BindFunc(name, std::move(get_func), std::move(set_func)); +} + +bool DataModelConstructor::BindEventCallback(const String& name, DataEventFunc event_func) +{ + return model->BindEventCallback(name, std::move(event_func)); +} + +bool DataModelConstructor::BindVariable(const String& name, DataVariable data_variable) +{ + return model->BindVariable(name, data_variable); +} + +const UnorderedMap& Detail::DataModelConstructorAccessor::GetAllVariables(const DataModelConstructor& data_model_constructor) +{ + RMLUI_ASSERT(data_model_constructor.model); + return data_model_constructor.model->GetAllVariables(); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataTypeRegister.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataTypeRegister.cpp new file mode 100644 index 00000000..43553399 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataTypeRegister.cpp @@ -0,0 +1,103 @@ +#include "../../Include/RmlUi/Core/DataTypeRegister.h" +#include "../../Include/RmlUi/Core/StringUtilities.h" +#include "../../Include/RmlUi/Core/Variant.h" + +namespace Rml { + +DataTypeRegister::DataTypeRegister() +{ + // Add default transform functions. + + transform_register.Register("to_lower", [](const VariantList& arguments) -> Variant { + if (arguments.size() != 1) + return {}; + String value; + if (!arguments[0].GetInto(value)) + return {}; + return Variant(StringUtilities::ToLower(std::move(value))); + }); + + transform_register.Register("to_upper", [](const VariantList& arguments) -> Variant { + if (arguments.size() != 1) + return {}; + String value; + if (!arguments[0].GetInto(value)) + return {}; + return Variant(StringUtilities::ToUpper(value)); + }); + + transform_register.Register("format", [](const VariantList& arguments) -> Variant { + // Arguments in: + // 0 : number Number to format. + // 1 : int[0,32] Precision. Number of digits after the decimal point. + // [2]: bool True to remove trailing zeros (default = false). + if (arguments.size() < 2 || arguments.size() > 3) + { + Log::Message(Log::LT_WARNING, "Transform function 'format' requires at least two arguments, at most three arguments."); + return {}; + } + int precision = 0; + if (!arguments[1].GetInto(precision) || precision < 0 || precision > 32) + { + Log::Message(Log::LT_WARNING, "Transform function 'format': Second argument must be an integer in [0, 32]."); + return {}; + } + bool remove_trailing_zeros = false; + if (arguments.size() >= 3) + { + if (!arguments[2].GetInto(remove_trailing_zeros)) + return {}; + } + + double value = 0; + if (!arguments[0].GetInto(value)) + return {}; + + String format_specifier = String(remove_trailing_zeros ? "%#." : "%.") + ToString(precision) + 'f'; + String result; + if (FormatString(result, format_specifier.c_str(), value) == 0) + return {}; + + if (remove_trailing_zeros) + StringUtilities::TrimTrailingDotZeros(result); + + return Variant(std::move(result)); + }); + + transform_register.Register("round", [](const VariantList& arguments) -> Variant { + if (arguments.size() != 1) + return {}; + double value; + if (!arguments[0].GetInto(value)) + return {}; + return Variant(Math::Round(value)); + }); +} + +DataTypeRegister::~DataTypeRegister() {} + +void TransformFuncRegister::Register(const String& name, DataTransformFunc transform_func) +{ + RMLUI_ASSERT(transform_func); + bool inserted = transform_functions.emplace(name, std::move(transform_func)).second; + if (!inserted) + { + Log::Message(Log::LT_ERROR, "Transform function '%s' already exists.", name.c_str()); + RMLUI_ERROR; + } +} + +bool TransformFuncRegister::Call(const String& name, const VariantList& arguments, Variant& out_result) const +{ + auto it = transform_functions.find(name); + if (it == transform_functions.end()) + return false; + + const DataTransformFunc& transform_func = it->second; + RMLUI_ASSERT(transform_func); + + out_result = transform_func(arguments); + return true; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataVariable.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataVariable.cpp new file mode 100644 index 00000000..5db4d3ac --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataVariable.cpp @@ -0,0 +1,171 @@ +#include "../../Include/RmlUi/Core/DataVariable.h" + +namespace Rml { + +bool DataVariable::Get(Variant& variant) const +{ + return definition->Get(ptr, variant); +} + +bool DataVariable::Set(const Variant& variant) +{ + return definition->Set(ptr, variant); +} + +int DataVariable::Size() const +{ + return definition->Size(ptr); +} + +DataVariable DataVariable::Child(const DataAddressEntry& address) const +{ + return definition->Child(ptr, address); +} + +DataVariableType DataVariable::Type() const +{ + return definition->Type(); +} + +bool VariableDefinition::Get(void* /*ptr*/, Variant& /*variant*/) +{ + Log::Message(Log::LT_WARNING, "Values can only be retrieved from scalar data types."); + return false; +} +bool VariableDefinition::Set(void* /*ptr*/, const Variant& /*variant*/) +{ + Log::Message(Log::LT_WARNING, "Values can only be assigned to scalar data types."); + return false; +} +int VariableDefinition::Size(void* /*ptr*/) +{ + Log::Message(Log::LT_WARNING, "Tried to get the size from a non-array data type."); + return 0; +} +DataVariable VariableDefinition::Child(void* /*ptr*/, const DataAddressEntry& /*address*/) +{ + Log::Message(Log::LT_WARNING, "Tried to get the child of a scalar type."); + return DataVariable(); +} + +StringList VariableDefinition::ReflectMemberNames() +{ + Log::Message(Log::LT_WARNING, "Tried to get the member names of a non-struct type."); + return StringList(); +} + +class LiteralIntDefinition final : public VariableDefinition { +public: + LiteralIntDefinition() : VariableDefinition(DataVariableType::Scalar) {} + + bool Get(void* ptr, Variant& variant) override + { + variant = static_cast(reinterpret_cast(ptr)); + return true; + } +}; + +DataVariable MakeLiteralIntVariable(int value) +{ + static LiteralIntDefinition literal_int_definition; + return DataVariable(&literal_int_definition, reinterpret_cast(static_cast(value))); +} + +StructDefinition::StructDefinition() : VariableDefinition(DataVariableType::Struct) {} + +DataVariable StructDefinition::Child(void* ptr, const DataAddressEntry& address) +{ + const String& name = address.name; + if (name.empty()) + { + Log::Message(Log::LT_WARNING, "Expected a struct member name but none given."); + return DataVariable(); + } + + auto it = members.find(name); + if (it == members.end()) + { + Log::Message(Log::LT_WARNING, "Member %s not found in data struct.", name.c_str()); + return DataVariable(); + } + + VariableDefinition* next_definition = it->second.get(); + + return DataVariable(next_definition, ptr); +} + +StringList StructDefinition::ReflectMemberNames() +{ + StringList names; + names.reserve(members.size()); + for (const auto& entry : members) + names.push_back(entry.first); + return names; +} + +void StructDefinition::AddMember(const String& name, UniquePtr member) +{ + RMLUI_ASSERT(member); + bool inserted = members.emplace(name, std::move(member)).second; + RMLUI_ASSERTMSG(inserted, "Member name already exists."); + (void)inserted; +} + +FuncDefinition::FuncDefinition(DataGetFunc get, DataSetFunc set) : + VariableDefinition(DataVariableType::Scalar), get(std::move(get)), set(std::move(set)) +{} + +bool FuncDefinition::Get(void* /*ptr*/, Variant& variant) +{ + if (!get) + return false; + get(variant); + return true; +} + +bool FuncDefinition::Set(void* /*ptr*/, const Variant& variant) +{ + if (!set) + return false; + set(variant); + return true; +} + +BasePointerDefinition::BasePointerDefinition(VariableDefinition* underlying_definition) : + VariableDefinition(underlying_definition->Type()), underlying_definition(underlying_definition) +{} + +bool BasePointerDefinition::Get(void* ptr, Variant& variant) +{ + if (!ptr) + return false; + return underlying_definition->Get(DereferencePointer(ptr), variant); +} + +bool BasePointerDefinition::Set(void* ptr, const Variant& variant) +{ + if (!ptr) + return false; + return underlying_definition->Set(DereferencePointer(ptr), variant); +} + +int BasePointerDefinition::Size(void* ptr) +{ + if (!ptr) + return 0; + return underlying_definition->Size(DereferencePointer(ptr)); +} + +DataVariable BasePointerDefinition::Child(void* ptr, const DataAddressEntry& address) +{ + if (!ptr) + return DataVariable(); + return underlying_definition->Child(DereferencePointer(ptr), address); +} + +StringList BasePointerDefinition::ReflectMemberNames() +{ + return underlying_definition->ReflectMemberNames(); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataView.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataView.cpp new file mode 100644 index 00000000..13791e68 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataView.cpp @@ -0,0 +1,138 @@ +#include "DataView.h" +#include "../../Include/RmlUi/Core/Element.h" +#include + +namespace Rml { + +DataView::~DataView() {} + +Element* DataView::GetElement() const +{ + Element* result = attached_element.get(); + if (!result) + Log::Message(Log::LT_WARNING, "Could not retrieve element in view, was it destroyed?"); + return result; +} + +int DataView::GetSortOrder() const +{ + return sort_order; +} + +bool DataView::IsValid() const +{ + return static_cast(attached_element); +} + +DataView::DataView(Element* element, int bias) : attached_element(element->GetObserverPtr()), sort_order(bias + 1000) +{ + RMLUI_ASSERT(bias >= -1000 && bias <= 999); + + if (element) + { + for (Element* parent = element->GetParentNode(); parent; parent = parent->GetParentNode()) + sort_order += 2000; + } +} + +DataViews::DataViews() {} + +DataViews::~DataViews() {} + +void DataViews::Add(DataViewPtr view) +{ + views_to_add.push_back(std::move(view)); +} + +void DataViews::OnElementRemove(Element* element) +{ + for (auto it = views.begin(); it != views.end();) + { + auto& view = *it; + if (view && view->GetElement() == element) + { + views_to_remove.push_back(std::move(view)); + it = views.erase(it); + } + else + ++it; + } +} + +bool DataViews::Update(DataModel& model, const DirtyVariables& dirty_variables) +{ + bool result = false; + size_t num_dirty_variables_prev = 0; + + // View updates may result in newly added views, or even new dirty variables. Thus, we do the + // update recursively but with an upper limit. Without the loop, newly added views won't be + // updated until the next Update() call. + for (int i = 0; (i == 0 || !views_to_add.empty() || num_dirty_variables_prev != dirty_variables.size()) && i < 10; i++) + { + num_dirty_variables_prev = dirty_variables.size(); + + Vector dirty_views; + + if (!views_to_add.empty()) + { + views.reserve(views.size() + views_to_add.size()); + for (auto&& view : views_to_add) + { + dirty_views.push_back(view.get()); + for (const String& variable_name : view->GetVariableNameList()) + name_view_map.emplace(variable_name, view.get()); + + views.push_back(std::move(view)); + } + views_to_add.clear(); + } + + for (const String& variable_name : dirty_variables) + { + auto pair = name_view_map.equal_range(variable_name); + for (auto it = pair.first; it != pair.second; ++it) + dirty_views.push_back(it->second); + } + + // Remove duplicate entries + std::sort(dirty_views.begin(), dirty_views.end()); + auto it_remove = std::unique(dirty_views.begin(), dirty_views.end()); + dirty_views.erase(it_remove, dirty_views.end()); + + // Sort by the element's depth in the document tree so that any structural changes due to a changed variable are reflected in the element's + // children. Eg. the 'data-for' view will remove children if any of its data variable array size is reduced. + std::sort(dirty_views.begin(), dirty_views.end(), [](auto&& left, auto&& right) { return left->GetSortOrder() < right->GetSortOrder(); }); + + for (DataView* view : dirty_views) + { + RMLUI_ASSERT(view); + if (!view) + continue; + + if (view->IsValid()) + result |= view->Update(model); + } + + // Destroy views marked for destruction + // @performance: Horrible... + if (!views_to_remove.empty()) + { + for (const auto& view : views_to_remove) + { + for (auto it = name_view_map.begin(); it != name_view_map.end();) + { + if (it->second == view.get()) + it = name_view_map.erase(it); + else + ++it; + } + } + + views_to_remove.clear(); + } + } + + return result; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataView.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataView.h new file mode 100644 index 00000000..df6b54b7 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataView.h @@ -0,0 +1,100 @@ +#pragma once + +#include "../../Include/RmlUi/Core/DataTypes.h" +#include "../../Include/RmlUi/Core/Header.h" +#include "../../Include/RmlUi/Core/Traits.h" +#include "../../Include/RmlUi/Core/Types.h" + +namespace Rml { + +class Element; +class DataModel; + +class DataViewInstancer : public NonCopyMoveable { +public: + DataViewInstancer() {} + virtual ~DataViewInstancer() {} + virtual DataViewPtr InstanceView(Element* element) = 0; +}; + +template +class DataViewInstancerDefault final : public DataViewInstancer { +public: + DataViewPtr InstanceView(Element* element) override { return DataViewPtr(new T(element)); } +}; + +/** + Data view. + + Data views are used to present a data variable in the document by different means. + A data view is declared in the document by the element attribute: + + data-[type]-[modifier]="[expression]" + + The modifier may or may not be required depending on the data view. + */ + +class DataView : public Releasable { +public: + virtual ~DataView(); + + // Initialize the data view. + // @param[in] model The data model the view will be attached to. + // @param[in] element The element which spawned the view. + // @param[in] expression The value of the element's 'data-' attribute which spawned the view (see class documentation). + // @param[in] modifier The modifier for the given view type (see class documentation). + // @return True on success. + virtual bool Initialize(DataModel& model, Element* element, const String& expression, const String& modifier) = 0; + + // Update the data view. + // Returns true if the update resulted in a document change. + virtual bool Update(DataModel& model) = 0; + + // Returns the list of data variable name(s) which can modify this view. + virtual StringList GetVariableNameList() const = 0; + + // Returns the attached element if it still exists. + Element* GetElement() const; + + // Data views are first sorted by the depth of the attached element in the + // document tree, then optionally by an offset specified for each data view. + int GetSortOrder() const; + + // Returns true if the element still exists. + bool IsValid() const; + +protected: + // @param[in] element The element this data view is attached to. + // @param[in] sort_offset A number [-1000, 999] specifying the update order of this + // data view at the same tree depth, negative numbers are updated first. + DataView(Element* element, int sort_offset); + +private: + ObserverPtr attached_element; + int sort_order; +}; + +class DataViews : NonCopyMoveable { +public: + DataViews(); + ~DataViews(); + + void Add(DataViewPtr view); + + void OnElementRemove(Element* element); + + bool Update(DataModel& model, const DirtyVariables& dirty_variables); + +private: + using DataViewList = Vector; + + DataViewList views; + + DataViewList views_to_add; + DataViewList views_to_remove; + + using NameViewMap = UnorderedMultimap; + NameViewMap name_view_map; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataViewDefault.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataViewDefault.cpp new file mode 100644 index 00000000..e54fb41a --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataViewDefault.cpp @@ -0,0 +1,602 @@ +#include "DataViewDefault.h" +#include "../../Include/RmlUi/Core/Core.h" +#include "../../Include/RmlUi/Core/DataVariable.h" +#include "../../Include/RmlUi/Core/Element.h" +#include "../../Include/RmlUi/Core/ElementText.h" +#include "../../Include/RmlUi/Core/Factory.h" +#include "../../Include/RmlUi/Core/SystemInterface.h" +#include "../../Include/RmlUi/Core/Variant.h" +#include "DataExpression.h" +#include "DataModel.h" +#include "XMLParseTools.h" + +namespace Rml { + +// Some data views need to offset the update order for proper behavior. +// 'data-value' may need other attributes applied first, e.g. min/max attributes. +static constexpr int SortOffset_DataValue = 100; +// 'data-checked' may need a value attribute already set. +static constexpr int SortOffset_DataChecked = 110; + +DataViewCommon::DataViewCommon(Element* element, String override_modifier, int sort_offset) : + DataView(element, sort_offset), modifier(std::move(override_modifier)) +{} + +bool DataViewCommon::Initialize(DataModel& model, Element* element, const String& expression_str, const String& in_modifier) +{ + // The modifier can be overridden in the constructor + if (modifier.empty()) + modifier = in_modifier; + + expression = MakeUnique(expression_str); + DataExpressionInterface expr_interface(&model, element); + + bool result = expression->Parse(expr_interface, false); + return result; +} + +StringList DataViewCommon::GetVariableNameList() const +{ + RMLUI_ASSERT(expression); + return expression->GetVariableNameList(); +} + +const String& DataViewCommon::GetModifier() const +{ + return modifier; +} + +DataExpression& DataViewCommon::GetExpression() +{ + RMLUI_ASSERT(expression); + return *expression; +} + +void DataViewCommon::Release() +{ + delete this; +} + +DataViewAttribute::DataViewAttribute(Element* element) : DataViewCommon(element) {} + +DataViewAttribute::DataViewAttribute(Element* element, String override_attribute, int sort_offset) : + DataViewCommon(element, std::move(override_attribute), sort_offset) +{} + +bool DataViewAttribute::Update(DataModel& model) +{ + const String& attribute_name = GetModifier(); + bool result = false; + Variant variant; + Element* element = GetElement(); + DataExpressionInterface expr_interface(&model, element); + + if (element && GetExpression().Run(expr_interface, variant)) + { + const String value = variant.Get(); + const Variant* attribute = element->GetAttribute(attribute_name); + + if (!attribute || attribute->Get() != value) + { + element->SetAttribute(attribute_name, value); + result = true; + } + } + return result; +} + +DataViewAttributeIf::DataViewAttributeIf(Element* element) : DataViewCommon(element) {} + +bool DataViewAttributeIf::Update(DataModel& model) +{ + const String& attribute_name = GetModifier(); + bool result = false; + Variant variant; + Element* element = GetElement(); + DataExpressionInterface expr_interface(&model, element); + + if (element && GetExpression().Run(expr_interface, variant)) + { + const bool value = variant.Get(); + const bool is_set = static_cast(element->GetAttribute(attribute_name)); + if (is_set != value) + { + if (value) + element->SetAttribute(attribute_name, String()); + else + element->RemoveAttribute(attribute_name); + result = true; + } + } + return result; +} + +DataViewValue::DataViewValue(Element* element) : DataViewAttribute(element, "value", SortOffset_DataValue) {} + +DataViewChecked::DataViewChecked(Element* element) : DataViewCommon(element, String(), SortOffset_DataChecked) {} + +bool DataViewChecked::Update(DataModel& model) +{ + bool result = false; + Variant variant; + Element* element = GetElement(); + DataExpressionInterface expr_interface(&model, element); + + if (element && GetExpression().Run(expr_interface, variant)) + { + bool new_checked_state = false; + + if (variant.GetType() == Variant::BOOL) + { + new_checked_state = variant.Get(); + } + else + { + const String value = variant.Get(); + new_checked_state = (!value.empty() && value == element->GetAttribute("value", "")); + } + + const bool current_checked_state = element->HasAttribute("checked"); + + if (new_checked_state != current_checked_state) + { + result = true; + if (new_checked_state) + element->SetAttribute("checked", String()); + else + element->RemoveAttribute("checked"); + } + } + + return result; +} + +DataViewStyle::DataViewStyle(Element* element) : DataViewCommon(element) {} + +bool DataViewStyle::Update(DataModel& model) +{ + const String& property_name = GetModifier(); + bool result = false; + Variant variant; + Element* element = GetElement(); + DataExpressionInterface expr_interface(&model, element); + + if (element && GetExpression().Run(expr_interface, variant)) + { + const String value = variant.Get(); + const Property* p = element->GetLocalProperty(property_name); + if (!p || p->Get() != value) + { + element->SetProperty(property_name, value); + result = true; + } + } + return result; +} + +DataViewClass::DataViewClass(Element* element) : DataViewCommon(element) {} + +bool DataViewClass::Update(DataModel& model) +{ + const String& class_name = GetModifier(); + bool result = false; + Variant variant; + Element* element = GetElement(); + DataExpressionInterface expr_interface(&model, element); + + if (element && GetExpression().Run(expr_interface, variant)) + { + const bool activate = variant.Get(); + const bool is_set = element->IsClassSet(class_name); + if (activate != is_set) + { + element->SetClass(class_name, activate); + result = true; + } + } + return result; +} + +DataViewRml::DataViewRml(Element* element) : DataViewCommon(element) {} + +bool DataViewRml::Update(DataModel& model) +{ + bool result = false; + Variant variant; + Element* element = GetElement(); + DataExpressionInterface expr_interface(&model, element); + + if (element && GetExpression().Run(expr_interface, variant)) + { + String new_rml = variant.Get(); + if (new_rml != previous_rml) + { + element->SetInnerRML(new_rml); + previous_rml = std::move(new_rml); + result = true; + } + } + return result; +} + +DataViewIf::DataViewIf(Element* element) : DataViewCommon(element) {} + +bool DataViewIf::Update(DataModel& model) +{ + bool result = false; + Variant variant; + Element* element = GetElement(); + DataExpressionInterface expr_interface(&model, element); + + if (element && GetExpression().Run(expr_interface, variant)) + { + const bool value = variant.Get(); + const bool is_visible = (element->GetLocalStyleProperties().count(PropertyId::Display) == 0); + if (is_visible != value) + { + if (value) + element->RemoveProperty(PropertyId::Display); + else + element->SetProperty(PropertyId::Display, Property(Style::Display::None)); + result = true; + } + } + return result; +} + +DataViewVisible::DataViewVisible(Element* element) : DataViewCommon(element) {} + +bool DataViewVisible::Update(DataModel& model) +{ + bool result = false; + Variant variant; + Element* element = GetElement(); + DataExpressionInterface expr_interface(&model, element); + + if (element && GetExpression().Run(expr_interface, variant)) + { + const bool value = variant.Get(); + const bool is_visible = (element->GetLocalStyleProperties().count(PropertyId::Visibility) == 0); + if (is_visible != value) + { + if (value) + element->RemoveProperty(PropertyId::Visibility); + else + element->SetProperty(PropertyId::Visibility, Property(Style::Visibility::Hidden)); + result = true; + } + } + return result; +} + +DataViewText::DataViewText(Element* element) : DataView(element, 0) {} + +bool DataViewText::Initialize(DataModel& model, Element* element, const String& /*expression*/, const String& /*modifier*/) +{ + ElementText* element_text = rmlui_dynamic_cast(element); + if (!element_text) + return false; + + const String& in_text = element_text->GetText(); + + text.reserve(in_text.size()); + + DataExpressionInterface expression_interface(&model, element); + + size_t begin_brackets = 0; + size_t cur = 0; + char previous = 0; + bool was_in_brackets = false; + bool in_brackets = false; + bool in_string = false; + + for (char c : in_text) + { + was_in_brackets = in_brackets; + + const char* error_str = XMLParseTools::ParseDataBrackets(in_brackets, in_string, c, previous); + if (error_str) + { + Log::Message(Log::LT_WARNING, "Failed to parse data view text '%s'. %s", in_text.c_str(), error_str); + return false; + } + + if (!was_in_brackets && in_brackets) + { + begin_brackets = cur; + } + else if (was_in_brackets && !in_brackets) + { + DataEntry entry; + entry.index = text.size(); + entry.data_expression = MakeUnique(String(in_text.begin() + begin_brackets + 1, in_text.begin() + cur - 1)); + entry.value = "#rmlui#"; // A random value that the user string will not be initialized with. + + if (entry.data_expression->Parse(expression_interface, false)) + data_entries.push_back(std::move(entry)); + + // Reset char so that it won't be appended to the output + c = 0; + } + else if (!in_brackets && previous) + { + text.push_back(previous); + } + + cur++; + previous = c; + } + + if (!in_brackets && previous) + { + text.push_back(previous); + } + + if (data_entries.empty()) + return false; + + return true; +} + +bool DataViewText::Update(DataModel& model) +{ + bool entries_modified = false; + { + Element* element = GetElement(); + DataExpressionInterface expression_interface(&model, element); + + for (DataEntry& entry : data_entries) + { + RMLUI_ASSERT(entry.data_expression); + Variant variant; + bool result = entry.data_expression->Run(expression_interface, variant); + const String value = variant.Get(); + if (result && entry.value != value) + { + entry.value = value; + entries_modified = true; + } + } + } + + if (entries_modified) + { + if (Element* element = GetElement()) + { + String new_text = BuildText(); + String text; + if (SystemInterface* system_interface = GetSystemInterface()) + system_interface->TranslateString(text, new_text); + + rmlui_static_cast(element)->SetText(text); + } + else + { + Log::Message(Log::LT_WARNING, "Could not update data view text, element no longer valid. Was it destroyed?"); + } + } + + return entries_modified; +} + +StringList DataViewText::GetVariableNameList() const +{ + StringList full_list; + full_list.reserve(data_entries.size()); + + for (const DataEntry& entry : data_entries) + { + RMLUI_ASSERT(entry.data_expression); + + StringList entry_list = entry.data_expression->GetVariableNameList(); + full_list.insert(full_list.end(), MakeMoveIterator(entry_list.begin()), MakeMoveIterator(entry_list.end())); + } + + return full_list; +} + +void DataViewText::Release() +{ + delete this; +} + +String DataViewText::BuildText() const +{ + size_t reserve_size = text.size(); + + for (const DataEntry& entry : data_entries) + reserve_size += entry.value.size(); + + String result; + result.reserve(reserve_size); + + size_t previous_index = 0; + for (const DataEntry& entry : data_entries) + { + result += text.substr(previous_index, entry.index - previous_index); + result += entry.value; + previous_index = entry.index; + } + + if (previous_index < text.size()) + result += text.substr(previous_index); + + return result; +} + +DataViewFor::DataViewFor(Element* element) : DataView(element, 0) {} + +bool DataViewFor::Initialize(DataModel& model, Element* element, const String& in_expression, const String& /*modifier*/) +{ + StringList iterator_container_pair; + StringUtilities::ExpandString(iterator_container_pair, in_expression, ':'); + + if (iterator_container_pair.empty() || iterator_container_pair.size() > 2 || iterator_container_pair.front().empty() || + iterator_container_pair.back().empty()) + { + Log::Message(Log::LT_WARNING, "Invalid syntax in data-for '%s'", in_expression.c_str()); + return false; + } + + if (iterator_container_pair.size() == 2) + { + StringList iterator_index_pair; + StringUtilities::ExpandString(iterator_index_pair, iterator_container_pair.front(), ','); + + if (iterator_index_pair.empty()) + { + Log::Message(Log::LT_WARNING, "Invalid syntax in data-for '%s'", in_expression.c_str()); + return false; + } + else if (iterator_index_pair.size() == 1) + { + iterator_name = iterator_index_pair.front(); + } + else if (iterator_index_pair.size() == 2) + { + iterator_name = iterator_index_pair.front(); + iterator_index_name = iterator_index_pair.back(); + } + } + + if (iterator_name.empty()) + iterator_name = "it"; + + if (iterator_index_name.empty()) + iterator_index_name = "it_index"; + + const String& container_name = iterator_container_pair.back(); + + container_address = model.ResolveAddress(container_name, element); + if (container_address.empty()) + return false; + + element->SetProperty(PropertyId::Display, Property(Style::Display::None)); + + // Copy over the attributes, but remove the 'data-for' which would otherwise recreate the data-for loop on all + // constructed children recursively. There is also no need for the 'rmlui-inner-rml' attribute if that is present. + const ElementAttributes& element_attributes = element->GetAttributes(); + constexpr size_t num_data_for_attributes = 1; + if (element_attributes.size() < num_data_for_attributes) + { + Log::Message(Log::LT_WARNING, "Expected attributes for data-for view missing from element: %s", element->GetAddress().c_str()); + return false; + } + attributes.reserve(element_attributes.size() - num_data_for_attributes); + for (const auto& attribute : element->GetAttributes()) + { + if (attribute.first == "data-for" || attribute.first == "rmlui-inner-rml") + continue; + attributes.emplace(attribute.first, attribute.second); + } + + return true; +} + +bool DataViewFor::Update(DataModel& model) +{ + DataVariable variable = model.GetVariable(container_address); + if (!variable) + return false; + + bool result = false; + const int size = variable.Size(); + const int num_elements = (int)elements.size(); + Element* element = GetElement(); + + for (int i = 0; i < Math::Max(size, num_elements); i++) + { + if (i >= num_elements) + { + ElementPtr new_element_ptr = Factory::InstanceElement(nullptr, element->GetTagName(), element->GetTagName(), attributes); + + DataAddress iterator_address; + iterator_address.reserve(container_address.size() + 1); + iterator_address = container_address; + iterator_address.push_back(DataAddressEntry(i)); + + DataAddress iterator_index_address = {{"literal"}, {"int"}, {i}}; + + model.InsertAlias(new_element_ptr.get(), iterator_name, std::move(iterator_address)); + model.InsertAlias(new_element_ptr.get(), iterator_index_name, std::move(iterator_index_address)); + + Element* new_element = element->GetParentNode()->InsertBefore(std::move(new_element_ptr), element); + elements.push_back(new_element); + + const String* rml_contents = RMLContents(); + elements[i]->SetInnerRML(rml_contents ? *rml_contents : ""); + + RMLUI_ASSERT(i < (int)elements.size()); + } + if (i >= size) + { + model.EraseAliases(elements[i]); + elements[i]->GetParentNode()->RemoveChild(elements[i]).reset(); + elements[i] = nullptr; + } + } + + if (num_elements > size) + elements.resize(size); + + return result; +} + +StringList DataViewFor::GetVariableNameList() const +{ + RMLUI_ASSERT(!container_address.empty()); + return StringList{container_address.front().name}; +} + +void DataViewFor::Release() +{ + delete this; +} + +const String* DataViewFor::RMLContents() const +{ + if (Element* element = GetElement()) + { + if (Variant* attribute = element->GetAttribute("rmlui-inner-rml")) + { + if (attribute->GetType() == Variant::STRING) + return &attribute->GetReference(); + } + Log::Message(Log::LT_WARNING, "Missing or invalid RML contents in data-for view on element %s", element->GetAddress().c_str()); + } + else + { + Log::Message(Log::LT_WARNING, "Invalid element in data-for view"); + } + return nullptr; +} + +DataViewAlias::DataViewAlias(Element* element) : DataView(element, 0) {} + +StringList DataViewAlias::GetVariableNameList() const +{ + return variables; +} + +bool DataViewAlias::Update(DataModel&) +{ + return false; +} + +bool DataViewAlias::Initialize(DataModel& model, Element* element, const String& expression, const String& modifier) +{ + auto address = model.ResolveAddress(expression, element); + if (address.empty()) + return false; + + variables.push_back(modifier); + model.InsertAlias(element, modifier, address); + return true; +} + +void DataViewAlias::Release() +{ + delete this; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataViewDefault.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataViewDefault.h new file mode 100644 index 00000000..1dcefacd --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DataViewDefault.h @@ -0,0 +1,161 @@ +#pragma once + +#include "../../Include/RmlUi/Core/Header.h" +#include "../../Include/RmlUi/Core/Types.h" +#include "../../Include/RmlUi/Core/Variant.h" +#include "DataView.h" + +namespace Rml { + +class Element; +class DataExpression; +using DataExpressionPtr = UniquePtr; + +class DataViewCommon : public DataView { +public: + DataViewCommon(Element* element, String override_modifier = String(), int sort_offset = 0); + + bool Initialize(DataModel& model, Element* element, const String& expression, const String& modifier) override; + + StringList GetVariableNameList() const override; + +protected: + const String& GetModifier() const; + DataExpression& GetExpression(); + + void Release() override; + +private: + String modifier; + DataExpressionPtr expression; +}; + +class DataViewAttribute : public DataViewCommon { +public: + DataViewAttribute(Element* element); + DataViewAttribute(Element* element, String override_attribute, int sort_offset); + + bool Update(DataModel& model) override; +}; + +class DataViewAttributeIf final : public DataViewCommon { +public: + DataViewAttributeIf(Element* element); + + bool Update(DataModel& model) override; +}; + +class DataViewValue final : public DataViewAttribute { +public: + DataViewValue(Element* element); +}; + +class DataViewChecked final : public DataViewCommon { +public: + DataViewChecked(Element* element); + + bool Update(DataModel& model) override; +}; + +class DataViewStyle final : public DataViewCommon { +public: + DataViewStyle(Element* element); + + bool Update(DataModel& model) override; +}; + +class DataViewClass final : public DataViewCommon { +public: + DataViewClass(Element* element); + + bool Update(DataModel& model) override; +}; + +class DataViewRml final : public DataViewCommon { +public: + DataViewRml(Element* element); + + bool Update(DataModel& model) override; + +private: + String previous_rml; +}; + +class DataViewIf final : public DataViewCommon { +public: + DataViewIf(Element* element); + + bool Update(DataModel& model) override; +}; + +class DataViewVisible final : public DataViewCommon { +public: + DataViewVisible(Element* element); + + bool Update(DataModel& model) override; +}; + +class DataViewText final : public DataView { +public: + DataViewText(Element* in_element); + + bool Initialize(DataModel& model, Element* element, const String& expression, const String& modifier) override; + + bool Update(DataModel& model) override; + StringList GetVariableNameList() const override; + +protected: + void Release() override; + +private: + String BuildText() const; + + struct DataEntry { + size_t index = 0; // Index into 'text' + DataExpressionPtr data_expression; + String value; + }; + + String text; + Vector data_entries; +}; + +class DataViewFor final : public DataView { +public: + DataViewFor(Element* element); + + bool Initialize(DataModel& model, Element* element, const String& expression, const String& modifier) override; + + bool Update(DataModel& model) override; + + StringList GetVariableNameList() const override; + +protected: + void Release() override; + +private: + const String* RMLContents() const; + + DataAddress container_address; + String iterator_name; + String iterator_index_name; + ElementAttributes attributes; + + ElementList elements; +}; + +class DataViewAlias final : public DataView { +public: + DataViewAlias(Element* element); + StringList GetVariableNameList() const override; + bool Update(DataModel& model) override; + bool Initialize(DataModel& model, Element* element, const String& expression, const String& modifier) override; + +protected: + void Release() override; + +private: + StringList variables; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Decorator.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Decorator.cpp new file mode 100644 index 00000000..68428ac1 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Decorator.cpp @@ -0,0 +1,77 @@ +#include "../../Include/RmlUi/Core/Decorator.h" +#include "../../Include/RmlUi/Core/PropertyDefinition.h" +#include "../../Include/RmlUi/Core/RenderManager.h" +#include "../../Include/RmlUi/Core/StyleSheet.h" +#include "../../Include/RmlUi/Core/Texture.h" +#include + +namespace Rml { + +Decorator::Decorator() {} + +Decorator::~Decorator() {} + +int Decorator::AddTexture(Texture texture) +{ + if (!texture) + return -1; + + if (!first_texture) + first_texture = texture; + + if (first_texture == texture) + return 0; + + auto it = std::find(additional_textures.begin(), additional_textures.end(), texture); + if (it != additional_textures.end()) + return (int)(it - additional_textures.begin()) + 1; + + additional_textures.push_back(texture); + return (int)additional_textures.size(); +} + +int Decorator::GetNumTextures() const +{ + int result = (first_texture ? 1 : 0); + result += (int)additional_textures.size(); + return result; +} + +Texture Decorator::GetTexture(int index) const +{ + if (index == 0) + return first_texture; + + index -= 1; + if (index < 0 || index >= (int)additional_textures.size()) + return {}; + + return additional_textures[index]; +} + +DecoratorInstancer::DecoratorInstancer() {} + +DecoratorInstancer::~DecoratorInstancer() {} + +const Sprite* DecoratorInstancerInterface::GetSprite(const String& name) const +{ + return style_sheet.GetSprite(name); +} + +Texture DecoratorInstancerInterface::GetTexture(const String& filename) const +{ + if (!property_source) + { + Log::Message(Log::LT_WARNING, "Texture name '%s' in decorator could not be loaded, no property source available.", filename.c_str()); + return {}; + } + + return render_manager.LoadTexture(filename, property_source->path); +} + +RenderManager& DecoratorInstancerInterface::GetRenderManager() const +{ + return render_manager; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorGradient.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorGradient.cpp new file mode 100644 index 00000000..e3680df2 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorGradient.cpp @@ -0,0 +1,697 @@ +#include "DecoratorGradient.h" +#include "../../Include/RmlUi/Core/ComputedValues.h" +#include "../../Include/RmlUi/Core/Element.h" +#include "../../Include/RmlUi/Core/ElementUtilities.h" +#include "../../Include/RmlUi/Core/Geometry.h" +#include "../../Include/RmlUi/Core/Math.h" +#include "../../Include/RmlUi/Core/MeshUtilities.h" +#include "../../Include/RmlUi/Core/PropertyDefinition.h" +#include "ComputeProperty.h" +#include "DecoratorShader.h" + +namespace Rml { + +// Returns the point along the input line ('line_point', 'line_vector') closest to the input 'point'. +static Vector2f IntersectionPointToLineNormal(const Vector2f point, const Vector2f line_point, const Vector2f line_vector) +{ + const Vector2f delta = line_point - point; + return line_point - delta.DotProduct(line_vector) * line_vector; +} + +/// Convert all color stop positions to normalized numbers. +/// @param[in] element The element to resolve lengths against. +/// @param[in] gradient_line_length The length of the gradient line, along which color stops are placed. +/// @param[in] soft_spacing The desired minimum distance between stops to avoid aliasing, in normalized number units. +/// @param[in] unresolved_stops +/// @return A list of resolved color stops, all in number units. +static ColorStopList ResolveColorStops(Element* element, const float gradient_line_length, const float soft_spacing, + const ColorStopList& unresolved_stops) +{ + ColorStopList stops = unresolved_stops; + const int num_stops = (int)stops.size(); + + // Resolve all lengths, percentages, and angles to numbers. After this step all stops with a unit other than Number are considered as Auto. + for (ColorStop& stop : stops) + { + if (Any(stop.position.unit & Unit::LENGTH)) + { + const float resolved_position = element->ResolveLength(stop.position); + stop.position = NumericValue(resolved_position / gradient_line_length, Unit::NUMBER); + } + else if (stop.position.unit == Unit::PERCENT) + { + stop.position = NumericValue(stop.position.number * 0.01f, Unit::NUMBER); + } + else if (Any(stop.position.unit & Unit::ANGLE)) + { + stop.position = NumericValue(ComputeAngle(stop.position) * (1.f / (2.f * Math::RMLUI_PI)), Unit::NUMBER); + } + } + + // Resolve auto positions of the first and last color stops. + auto resolve_edge_stop = [](ColorStop& stop, float auto_to_number) { + if (stop.position.unit != Unit::NUMBER) + stop.position = NumericValue(auto_to_number, Unit::NUMBER); + }; + resolve_edge_stop(stops[0], 0.f); + resolve_edge_stop(stops[num_stops - 1], 1.f); + + // Ensures that color stop positions are strictly increasing, and have at least 1px spacing to avoid aliasing. + auto nudge_stop = [prev_position = stops[0].position.number](ColorStop& stop, bool update_prev = true) mutable { + stop.position.number = Math::Max(stop.position.number, prev_position); + if (update_prev) + prev_position = stop.position.number; + }; + int auto_begin_i = -1; + + // Evenly space stops with sequential auto indices, and nudge stop positions to ensure strictly increasing positions. + for (int i = 1; i < num_stops; i++) + { + ColorStop& stop = stops[i]; + if (stop.position.unit != Unit::NUMBER) + { + // Mark the first of any consecutive auto stops. + if (auto_begin_i < 0) + auto_begin_i = i; + } + else if (auto_begin_i < 0) + { + // The stop has a definite position and there are no previous autos to handle, just ensure it is properly spaced. + nudge_stop(stop); + } + else + { + // Space out all the previous auto stops, indices [auto_begin_i, i). + nudge_stop(stop, false); + const int num_auto_stops = i - auto_begin_i; + const float t0 = stops[auto_begin_i - 1].position.number; + const float t1 = stop.position.number; + + for (int j = 0; j < num_auto_stops; j++) + { + const float fraction_along_t0_t1 = float(j + 1) / float(num_auto_stops + 1); + stops[j + auto_begin_i].position = NumericValue(t0 + (t1 - t0) * fraction_along_t0_t1, Unit::NUMBER); + nudge_stop(stops[j + auto_begin_i]); + } + + nudge_stop(stop); + auto_begin_i = -1; + } + } + + // Ensures that stops are placed some minimum distance from each other to avoid aliasing, if possible. + for (int i = 1; i < num_stops - 1; i++) + { + const float p0 = stops[i - 1].position.number; + const float p1 = stops[i].position.number; + const float p2 = stops[i + 1].position.number; + float& new_position = stops[i].position.number; + + if (p1 - p0 < soft_spacing) + { + if (p2 - p0 < 2.f * soft_spacing) + new_position = 0.5f * (p2 + p0); + else + new_position = p0 + soft_spacing; + } + } + + RMLUI_ASSERT(std::all_of(stops.begin(), stops.end(), [](auto&& stop) { return stop.position.unit == Unit::NUMBER; })); + + return stops; +} + +DecoratorStraightGradient::DecoratorStraightGradient() {} + +DecoratorStraightGradient::~DecoratorStraightGradient() {} + +bool DecoratorStraightGradient::Initialise(const Direction in_direction, const Colourb in_start, const Colourb in_stop) +{ + direction = in_direction; + start = in_start; + stop = in_stop; + return true; +} + +DecoratorDataHandle DecoratorStraightGradient::GenerateElementData(Element* element, BoxArea paint_area) const +{ + const RenderBox render_box = element->GetRenderBox(paint_area); + const ComputedValues& computed = element->GetComputedValues(); + const float opacity = computed.opacity(); + + Mesh mesh; + MeshUtilities::GenerateBackground(mesh, render_box, ColourbPremultiplied()); + + ColourbPremultiplied colour_start = start.ToPremultiplied(opacity); + ColourbPremultiplied colour_stop = stop.ToPremultiplied(opacity); + + const Vector2f offset = render_box.GetFillOffset(); + const Vector2f size = render_box.GetFillSize(); + + Vector& vertices = mesh.vertices; + + if (direction == Direction::Horizontal) + { + for (int i = 0; i < (int)vertices.size(); i++) + { + const float t = Math::Clamp((vertices[i].position.x - offset.x) / size.x, 0.0f, 1.0f); + vertices[i].colour = Math::RoundedLerp(t, colour_start, colour_stop); + } + } + else if (direction == Direction::Vertical) + { + for (int i = 0; i < (int)vertices.size(); i++) + { + const float t = Math::Clamp((vertices[i].position.y - offset.y) / size.y, 0.0f, 1.0f); + vertices[i].colour = Math::RoundedLerp(t, colour_start, colour_stop); + } + } + + Geometry* geometry = new Geometry(element->GetRenderManager()->MakeGeometry(std::move(mesh))); + + return reinterpret_cast(geometry); +} + +void DecoratorStraightGradient::ReleaseElementData(DecoratorDataHandle element_data) const +{ + delete reinterpret_cast(element_data); +} + +void DecoratorStraightGradient::RenderElement(Element* element, DecoratorDataHandle element_data) const +{ + auto* data = reinterpret_cast(element_data); + data->Render(element->GetAbsoluteOffset(BoxArea::Border)); +} + +DecoratorStraightGradientInstancer::DecoratorStraightGradientInstancer() +{ + ids.direction = RegisterProperty("direction", "horizontal").AddParser("keyword", "horizontal, vertical").GetId(); + ids.start = RegisterProperty("start-color", "#ffffff").AddParser("color").GetId(); + ids.stop = RegisterProperty("stop-color", "#ffffff").AddParser("color").GetId(); + RegisterShorthand("decorator", "direction, start-color, stop-color", ShorthandType::FallThrough); +} + +DecoratorStraightGradientInstancer::~DecoratorStraightGradientInstancer() {} + +SharedPtr DecoratorStraightGradientInstancer::InstanceDecorator(const String& name, const PropertyDictionary& properties_, + const DecoratorInstancerInterface& /*interface_*/) +{ + using Direction = DecoratorStraightGradient::Direction; + Direction direction; + if (name == "horizontal-gradient") + direction = Direction::Horizontal; + else if (name == "vertical-gradient") + direction = Direction::Vertical; + else + { + direction = (Direction)properties_.GetProperty(ids.direction)->Get(); + Log::Message(Log::LT_WARNING, + "Decorator syntax 'gradient(horizontal|vertical ...)' is deprecated, please replace with 'horizontal-gradient(...)' or " + "'vertical-gradient(...)'"); + } + + Colourb start = properties_.GetProperty(ids.start)->Get(); + Colourb stop = properties_.GetProperty(ids.stop)->Get(); + + auto decorator = MakeShared(); + if (decorator->Initialise(direction, start, stop)) + return decorator; + + return nullptr; +} + +DecoratorLinearGradient::DecoratorLinearGradient() {} + +DecoratorLinearGradient::~DecoratorLinearGradient() {} + +bool DecoratorLinearGradient::Initialise(bool in_repeating, Corner in_corner, float in_angle, const ColorStopList& in_color_stops) +{ + repeating = in_repeating; + corner = in_corner; + angle = in_angle; + color_stops = in_color_stops; + return !color_stops.empty(); +} + +DecoratorDataHandle DecoratorLinearGradient::GenerateElementData(Element* element, BoxArea paint_area) const +{ + RenderManager* render_manager = element->GetRenderManager(); + if (!render_manager) + return INVALID_DECORATORDATAHANDLE; + + RMLUI_ASSERT(!color_stops.empty()); + + const RenderBox render_box = element->GetRenderBox(paint_area); + LinearGradientShape gradient_shape = CalculateShape(render_box.GetFillSize()); + + // One-pixel minimum color stop spacing to avoid aliasing. + const float soft_spacing = 1.f / gradient_shape.length; + + ColorStopList resolved_stops = ResolveColorStops(element, gradient_shape.length, soft_spacing, color_stops); + + CompiledShader shader = render_manager->CompileShader("linear-gradient", + Dictionary{ + {"p0", Variant(gradient_shape.p0)}, + {"p1", Variant(gradient_shape.p1)}, + {"length", Variant(gradient_shape.length)}, + {"repeating", Variant(repeating)}, + {"color_stop_list", Variant(std::move(resolved_stops))}, + }); + if (!shader) + return INVALID_DECORATORDATAHANDLE; + + Mesh mesh; + const ComputedValues& computed = element->GetComputedValues(); + const byte alpha = byte(computed.opacity() * 255.f); + MeshUtilities::GenerateBackground(mesh, render_box, ColourbPremultiplied(alpha, alpha)); + + const Vector2f render_offset = render_box.GetFillOffset(); + for (Vertex& vertex : mesh.vertices) + vertex.tex_coord = vertex.position - render_offset; + + ShaderElementData* element_data = + GetShaderElementDataPool().AllocateAndConstruct(render_manager->MakeGeometry(std::move(mesh)), std::move(shader)); + + return reinterpret_cast(element_data); +} + +void DecoratorLinearGradient::ReleaseElementData(DecoratorDataHandle handle) const +{ + ShaderElementData* element_data = reinterpret_cast(handle); + GetShaderElementDataPool().DestroyAndDeallocate(element_data); +} + +void DecoratorLinearGradient::RenderElement(Element* element, DecoratorDataHandle handle) const +{ + ShaderElementData* element_data = reinterpret_cast(handle); + element_data->geometry.Render(element->GetAbsoluteOffset(BoxArea::Border), {}, element_data->shader); +} + +DecoratorLinearGradient::LinearGradientShape DecoratorLinearGradient::CalculateShape(Vector2f dim) const +{ + using uint = unsigned int; + const Vector2f corners[(int)Corner::Count] = {Vector2f(dim.x, 0), dim, Vector2f(0, dim.y), Vector2f(0, 0)}; + const Vector2f center = 0.5f * dim; + + uint quadrant = 0; + Vector2f line_vector; + + if (corner == Corner::None) + { + // Find the target quadrant and unit vector for the given angle. + quadrant = uint(Math::NormaliseAngle(angle) * (4.f / (2.f * Math::RMLUI_PI))) % 4u; + line_vector = Vector2f(Math::Sin(angle), -Math::Cos(angle)); + } + else + { + // Quadrant given by the corner, need to find the vector perpendicular to the line connecting the neighboring corners. + quadrant = uint(corner); + const Vector2f v_neighbors = (corners[(quadrant + 1u) % 4u] - corners[(quadrant + 3u) % 4u]).Normalise(); + line_vector = {v_neighbors.y, -v_neighbors.x}; + } + + const uint quadrant_opposite = (quadrant + 2u) % 4u; + + const Vector2f starting_point = IntersectionPointToLineNormal(corners[quadrant_opposite], center, line_vector); + const Vector2f ending_point = IntersectionPointToLineNormal(corners[quadrant], center, line_vector); + + const float length = Math::Absolute(dim.x * line_vector.x) + Math::Absolute(-dim.y * line_vector.y); + + return LinearGradientShape{starting_point, ending_point, length}; +} + +DecoratorLinearGradientInstancer::DecoratorLinearGradientInstancer() +{ + ids.angle = RegisterProperty("angle", "180deg").AddParser("angle").GetId(); + ids.direction_to = RegisterProperty("to", "unspecified").AddParser("keyword", "unspecified, to").GetId(); + // See Direction enum for keyword values. + ids.direction_x = RegisterProperty("direction-x", "unspecified").AddParser("keyword", "unspecified=0, left=8, right=2").GetId(); + ids.direction_y = RegisterProperty("direction-y", "unspecified").AddParser("keyword", "unspecified=0, top=1, bottom=4").GetId(); + ids.color_stop_list = RegisterProperty("color-stops", "").AddParser("color_stop_list").GetId(); + + RegisterShorthand("direction", "angle, to, direction-x, direction-y, direction-x", ShorthandType::FallThrough); + RegisterShorthand("decorator", "direction?, color-stops#", ShorthandType::RecursiveCommaSeparated); +} + +DecoratorLinearGradientInstancer::~DecoratorLinearGradientInstancer() {} + +SharedPtr DecoratorLinearGradientInstancer::InstanceDecorator(const String& name, const PropertyDictionary& properties_, + const DecoratorInstancerInterface& /*interface_*/) +{ + const Property* p_angle = properties_.GetProperty(ids.angle); + const Property* p_direction_to = properties_.GetProperty(ids.direction_to); + const Property* p_direction_x = properties_.GetProperty(ids.direction_x); + const Property* p_direction_y = properties_.GetProperty(ids.direction_y); + const Property* p_color_stop_list = properties_.GetProperty(ids.color_stop_list); + + if (!p_angle || !p_direction_to || !p_direction_x || !p_direction_y || !p_color_stop_list) + return nullptr; + + using Corner = DecoratorLinearGradient::Corner; + Corner corner = Corner::None; + float angle = 0.f; + + if (p_direction_to->Get()) + { + const Direction direction = (Direction)(p_direction_x->Get() | p_direction_y->Get()); + switch (direction) + { + case Direction::Top: angle = 0.f; break; + case Direction::Right: angle = 0.5f * Math::RMLUI_PI; break; + case Direction::Bottom: angle = Math::RMLUI_PI; break; + case Direction::Left: angle = 1.5f * Math::RMLUI_PI; break; + case Direction::TopLeft: corner = Corner::TopLeft; break; + case Direction::TopRight: corner = Corner::TopRight; break; + case Direction::BottomRight: corner = Corner::BottomRight; break; + case Direction::BottomLeft: corner = Corner::BottomLeft; break; + case Direction::None: + default: return nullptr; break; + } + } + else + { + angle = ComputeAngle(p_angle->GetNumericValue()); + } + + if (p_color_stop_list->unit != Unit::COLORSTOPLIST) + return nullptr; + const ColorStopList& color_stop_list = p_color_stop_list->value.GetReference(); + const bool repeating = (name == "repeating-linear-gradient"); + + auto decorator = MakeShared(); + if (decorator->Initialise(repeating, corner, angle, color_stop_list)) + return decorator; + + return nullptr; +} + +DecoratorRadialGradient::DecoratorRadialGradient() {} + +DecoratorRadialGradient::~DecoratorRadialGradient() {} + +bool DecoratorRadialGradient::Initialise(bool in_repeating, Shape in_shape, SizeType in_size_type, Vector2Numeric in_size, Vector2Numeric in_position, + const ColorStopList& in_color_stops) +{ + repeating = in_repeating; + shape = in_shape; + size_type = in_size_type; + size = in_size; + position = in_position; + color_stops = in_color_stops; + return !color_stops.empty(); +} + +DecoratorDataHandle DecoratorRadialGradient::GenerateElementData(Element* element, BoxArea paint_area) const +{ + RenderManager* render_manager = element->GetRenderManager(); + if (!render_manager) + return INVALID_DECORATORDATAHANDLE; + + RMLUI_ASSERT(!color_stops.empty() && (shape == Shape::Circle || shape == Shape::Ellipse)); + + const RenderBox render_box = element->GetRenderBox(paint_area); + const Vector2f dimensions = render_box.GetFillSize(); + + RadialGradientShape gradient_shape = CalculateRadialGradientShape(element, dimensions); + + // One-pixel minimum color stop spacing to avoid aliasing. + const float soft_spacing = 1.f / Math::Min(gradient_shape.radius.x, gradient_shape.radius.y); + + ColorStopList resolved_stops = ResolveColorStops(element, gradient_shape.radius.x, soft_spacing, color_stops); + + CompiledShader shader = render_manager->CompileShader("radial-gradient", + Dictionary{ + {"center", Variant(gradient_shape.center)}, + {"radius", Variant(gradient_shape.radius)}, + {"repeating", Variant(repeating)}, + {"color_stop_list", Variant(std::move(resolved_stops))}, + }); + if (!shader) + return INVALID_DECORATORDATAHANDLE; + + Mesh mesh; + const ComputedValues& computed = element->GetComputedValues(); + const byte alpha = byte(computed.opacity() * 255.f); + MeshUtilities::GenerateBackground(mesh, render_box, ColourbPremultiplied(alpha, alpha)); + + const Vector2f render_offset = render_box.GetFillOffset(); + for (Vertex& vertex : mesh.vertices) + vertex.tex_coord = vertex.position - render_offset; + + ShaderElementData* element_data = + GetShaderElementDataPool().AllocateAndConstruct(render_manager->MakeGeometry(std::move(mesh)), std::move(shader)); + return reinterpret_cast(element_data); +} + +void DecoratorRadialGradient::ReleaseElementData(DecoratorDataHandle handle) const +{ + ShaderElementData* element_data = reinterpret_cast(handle); + GetShaderElementDataPool().DestroyAndDeallocate(element_data); +} + +void DecoratorRadialGradient::RenderElement(Element* element, DecoratorDataHandle handle) const +{ + ShaderElementData* element_data = reinterpret_cast(handle); + element_data->geometry.Render(element->GetAbsoluteOffset(BoxArea::Border), {}, element_data->shader); +} + +DecoratorRadialGradient::RadialGradientShape DecoratorRadialGradient::CalculateRadialGradientShape(Element* element, Vector2f dimensions) const +{ + RadialGradientShape result; + result.center.x = element->ResolveNumericValue(position.x, dimensions.x); + result.center.y = element->ResolveNumericValue(position.y, dimensions.y); + const bool is_circle = (shape == Shape::Circle); + + auto Abs = [](Vector2f v) { return Vector2f{Math::Absolute(v.x), Math::Absolute(v.y)}; }; + auto d = dimensions; + auto c = result.center; + Vector2f r; + + switch (size_type) + { + case SizeType::ClosestSide: + { + r = Abs(Math::Min(c, d - c)); + result.radius = (is_circle ? Vector2f(Math::Min(r.x, r.y)) : r); + } + break; + case SizeType::FarthestSide: + { + r = Abs(Math::Max(c, d - c)); + result.radius = (is_circle ? Vector2f(Math::Max(r.x, r.y)) : r); + } + break; + case SizeType::ClosestCorner: + case SizeType::FarthestCorner: + { + if (size_type == SizeType::ClosestCorner) + r = Abs(Math::Min(c, d - c)); // Same as closest-side. + else + r = Abs(Math::Max(c, d - c)); // Same as farthest-side. + + if (is_circle) + { + result.radius = Vector2f(r.Magnitude()); + } + else + { + r = Math::Max(r, Vector2f(1)); // In case r.x ~= 0 + result.radius.x = Math::SquareRoot(2.f * r.x * r.x); + result.radius.y = result.radius.x * (r.y / r.x); + } + } + break; + case SizeType::LengthPercentage: + { + result.radius.x = element->ResolveNumericValue(size.x, d.x); + result.radius.y = (is_circle ? result.radius.x : element->ResolveNumericValue(size.y, d.y)); + result.radius = Abs(result.radius); + } + break; + } + + result.radius = Math::Max(result.radius, Vector2f(1.f)); + return result; +} + +DecoratorRadialGradientInstancer::DecoratorRadialGradientInstancer() +{ + ids.ending_shape = RegisterProperty("ending-shape", "unspecified").AddParser("keyword", "circle, ellipse, unspecified").GetId(); + ids.size_x = RegisterProperty("size-x", "farthest-corner") + .AddParser("keyword", "closest-side, farthest-side, closest-corner, farthest-corner") + .AddParser("length_percent") + .GetId(); + ids.size_y = RegisterProperty("size-y", "unspecified").AddParser("keyword", "unspecified").AddParser("length_percent").GetId(); + + RegisterProperty("at", "unspecified").AddParser("keyword", "at, unspecified"); + ids.position_x = RegisterProperty("position-x", "center").AddParser("keyword", "left, center, right").AddParser("length_percent").GetId(); + ids.position_y = RegisterProperty("position-y", "center").AddParser("keyword", "top, center, bottom").AddParser("length_percent").GetId(); + + ids.color_stop_list = RegisterProperty("color-stops", "").AddParser("color_stop_list").GetId(); + + RegisterShorthand("shape", "ending-shape, size-x, size-y, at, position-x, position-y, position-x", ShorthandType::FallThrough); + + RegisterShorthand("decorator", "shape?, color-stops#", ShorthandType::RecursiveCommaSeparated); +} + +DecoratorRadialGradientInstancer::~DecoratorRadialGradientInstancer() {} + +SharedPtr DecoratorRadialGradientInstancer::InstanceDecorator(const String& name, const PropertyDictionary& properties_, + const DecoratorInstancerInterface& /*interface_*/) +{ + const Property* p_ending_shape = properties_.GetProperty(ids.ending_shape); + const Property* p_size_x = properties_.GetProperty(ids.size_x); + const Property* p_size_y = properties_.GetProperty(ids.size_y); + Array p_position = {properties_.GetProperty(ids.position_x), properties_.GetProperty(ids.position_y)}; + const Property* p_color_stop_list = properties_.GetProperty(ids.color_stop_list); + + if (!p_ending_shape || !p_size_x || !p_size_y || !p_position[0] || !p_position[1] || !p_color_stop_list) + return nullptr; + + using SizeType = DecoratorRadialGradient::SizeType; + using Shape = DecoratorRadialGradient::Shape; + + Shape shape = (Shape)p_ending_shape->Get(); + if (shape == Shape::Unspecified) + { + const bool circle_sized = (Any(p_size_x->unit & Unit::LENGTH_PERCENT) && p_size_y->unit == Unit::KEYWORD); + shape = (circle_sized ? Shape::Circle : Shape::Ellipse); + } + if (shape == Shape::Circle && (p_size_x->unit == Unit::PERCENT || p_size_y->unit != Unit::KEYWORD)) + return nullptr; + + SizeType size_type = {}; + Vector2Numeric size; + if (p_size_x->unit == Unit::KEYWORD) + { + size_type = (SizeType)p_size_x->Get(); + } + else + { + size_type = SizeType::LengthPercentage; + size.x = p_size_x->GetNumericValue(); + size.y = (p_size_y->unit == Unit::KEYWORD ? size.x : p_size_y->GetNumericValue()); + } + + const Vector2Numeric position = ComputePosition(p_position); + const bool repeating = (name == "repeating-radial-gradient"); + + if (p_color_stop_list->unit != Unit::COLORSTOPLIST) + return nullptr; + const ColorStopList& color_stop_list = p_color_stop_list->value.GetReference(); + + auto decorator = MakeShared(); + if (decorator->Initialise(repeating, shape, size_type, size, position, color_stop_list)) + return decorator; + + return nullptr; +} + +DecoratorConicGradient::DecoratorConicGradient() {} + +DecoratorConicGradient::~DecoratorConicGradient() {} + +bool DecoratorConicGradient::Initialise(bool in_repeating, float in_angle, Vector2Numeric in_position, const ColorStopList& in_color_stops) +{ + repeating = in_repeating; + angle = in_angle; + position = in_position; + color_stops = in_color_stops; + return !color_stops.empty(); +} + +DecoratorDataHandle DecoratorConicGradient::GenerateElementData(Element* element, BoxArea paint_area) const +{ + RenderManager* render_manager = element->GetRenderManager(); + if (!render_manager) + return INVALID_DECORATORDATAHANDLE; + + RMLUI_ASSERT(!color_stops.empty()); + + const RenderBox render_box = element->GetRenderBox(paint_area); + const Vector2f dimensions = render_box.GetFillSize(); + + const Vector2f center = + Vector2f{element->ResolveNumericValue(position.x, dimensions.x), element->ResolveNumericValue(position.y, dimensions.y)}.Round(); + + ColorStopList resolved_stops = ResolveColorStops(element, 1.f, 0.f, color_stops); + + CompiledShader shader = render_manager->CompileShader("conic-gradient", + Dictionary{ + {"angle", Variant(angle)}, + {"center", Variant(center)}, + {"repeating", Variant(repeating)}, + {"color_stop_list", Variant(std::move(resolved_stops))}, + }); + if (!shader) + return INVALID_DECORATORDATAHANDLE; + + Mesh mesh; + const ComputedValues& computed = element->GetComputedValues(); + const byte alpha = byte(computed.opacity() * 255.f); + MeshUtilities::GenerateBackground(mesh, render_box, ColourbPremultiplied(alpha, alpha)); + + const Vector2f render_offset = render_box.GetFillOffset(); + for (Vertex& vertex : mesh.vertices) + vertex.tex_coord = vertex.position - render_offset; + + ShaderElementData* element_data = + GetShaderElementDataPool().AllocateAndConstruct(render_manager->MakeGeometry(std::move(mesh)), std::move(shader)); + return reinterpret_cast(element_data); +} + +void DecoratorConicGradient::ReleaseElementData(DecoratorDataHandle handle) const +{ + ShaderElementData* element_data = reinterpret_cast(handle); + GetShaderElementDataPool().DestroyAndDeallocate(element_data); +} + +void DecoratorConicGradient::RenderElement(Element* element, DecoratorDataHandle handle) const +{ + ShaderElementData* element_data = reinterpret_cast(handle); + element_data->geometry.Render(element->GetAbsoluteOffset(BoxArea::Border), {}, element_data->shader); +} + +DecoratorConicGradientInstancer::DecoratorConicGradientInstancer() +{ + RegisterProperty("from", "from").AddParser("keyword", "from"); + ids.angle = RegisterProperty("angle", "0deg").AddParser("angle").GetId(); + + RegisterProperty("at", "unspecified").AddParser("keyword", "at, unspecified"); + ids.position_x = RegisterProperty("position-x", "center").AddParser("keyword", "left, center, right").AddParser("length_percent").GetId(); + ids.position_y = RegisterProperty("position-y", "center").AddParser("keyword", "top, center, bottom").AddParser("length_percent").GetId(); + + ids.color_stop_list = RegisterProperty("color-stops", "").AddParser("color_stop_list", "angle").GetId(); + + RegisterShorthand("shape", "from, angle, at, position-x, position-y, position-x", ShorthandType::FallThrough); + RegisterShorthand("decorator", "shape?, color-stops#", ShorthandType::RecursiveCommaSeparated); +} + +DecoratorConicGradientInstancer::~DecoratorConicGradientInstancer() {} + +SharedPtr DecoratorConicGradientInstancer::InstanceDecorator(const String& name, const PropertyDictionary& properties_, + const DecoratorInstancerInterface& /*interface_*/) +{ + const Property* p_angle = properties_.GetProperty(ids.angle); + Array p_position = {properties_.GetProperty(ids.position_x), properties_.GetProperty(ids.position_y)}; + const Property* p_color_stop_list = properties_.GetProperty(ids.color_stop_list); + + if (!p_angle || !p_position[0] || !p_position[1] || !p_color_stop_list) + return nullptr; + + const float angle = ComputeAngle(p_angle->GetNumericValue()); + const Vector2Numeric position = ComputePosition(p_position); + const bool repeating = (name == "repeating-conic-gradient"); + + if (p_color_stop_list->unit != Unit::COLORSTOPLIST) + return nullptr; + const ColorStopList& color_stop_list = p_color_stop_list->value.GetReference(); + + auto decorator = MakeShared(); + if (decorator->Initialise(repeating, angle, position, color_stop_list)) + return decorator; + + return nullptr; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorGradient.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorGradient.h new file mode 100644 index 00000000..29c242a1 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorGradient.h @@ -0,0 +1,202 @@ +#pragma once + +#include "../../Include/RmlUi/Core/DecorationTypes.h" +#include "../../Include/RmlUi/Core/Decorator.h" +#include "../../Include/RmlUi/Core/Geometry.h" +#include "../../Include/RmlUi/Core/ID.h" +#include "DecoratorUtilities.h" + +namespace Rml { + +/** + Straight gradient. + + CSS usage: + decorator: horizontal-gradient( ); + decorator: vertical-gradient( ); + */ +class DecoratorStraightGradient : public Decorator { +public: + enum class Direction { Horizontal, Vertical }; + + DecoratorStraightGradient(); + virtual ~DecoratorStraightGradient(); + + bool Initialise(Direction direction, Colourb start, Colourb stop); + + DecoratorDataHandle GenerateElementData(Element* element, BoxArea paint_area) const override; + void ReleaseElementData(DecoratorDataHandle element_data) const override; + + void RenderElement(Element* element, DecoratorDataHandle element_data) const override; + +private: + Direction direction = {}; + Colourb start, stop; +}; + +class DecoratorStraightGradientInstancer : public DecoratorInstancer { +public: + DecoratorStraightGradientInstancer(); + virtual ~DecoratorStraightGradientInstancer(); + + SharedPtr InstanceDecorator(const String& name, const PropertyDictionary& properties, + const DecoratorInstancerInterface& instancer_interface) override; + +private: + struct PropertyIds { + PropertyId direction, start, stop; + }; + PropertyIds ids; +}; + +/** + Linear gradient. + */ +class DecoratorLinearGradient : public Decorator { +public: + enum class Corner { TopRight, BottomRight, BottomLeft, TopLeft, None, Count = None }; + + DecoratorLinearGradient(); + virtual ~DecoratorLinearGradient(); + + bool Initialise(bool repeating, Corner corner, float angle, const ColorStopList& color_stops); + + DecoratorDataHandle GenerateElementData(Element* element, BoxArea paint_area) const override; + void ReleaseElementData(DecoratorDataHandle element_data) const override; + + void RenderElement(Element* element, DecoratorDataHandle element_data) const override; + +private: + struct LinearGradientShape { + // Gradient line starting and ending points. + Vector2f p0, p1; + float length; + }; + + LinearGradientShape CalculateShape(Vector2f box_dimensions) const; + + bool repeating = false; + Corner corner = Corner::None; + float angle = 0.f; + ColorStopList color_stops; +}; + +class DecoratorLinearGradientInstancer : public DecoratorInstancer { +public: + DecoratorLinearGradientInstancer(); + virtual ~DecoratorLinearGradientInstancer(); + + SharedPtr InstanceDecorator(const String& name, const PropertyDictionary& properties, + const DecoratorInstancerInterface& instancer_interface) override; + +private: + enum class Direction { + None = 0, + Top = 1, + Right = 2, + Bottom = 4, + Left = 8, + TopLeft = Top | Left, + TopRight = Top | Right, + BottomRight = Bottom | Right, + BottomLeft = Bottom | Left, + }; + struct PropertyIds { + PropertyId angle; + PropertyId direction_to, direction_x, direction_y; + PropertyId color_stop_list; + }; + PropertyIds ids; +}; + +/** + Radial gradient. + */ +class DecoratorRadialGradient : public Decorator { +public: + enum class Shape { Circle, Ellipse, Unspecified }; + enum class SizeType { ClosestSide, FarthestSide, ClosestCorner, FarthestCorner, LengthPercentage }; + + DecoratorRadialGradient(); + virtual ~DecoratorRadialGradient(); + + bool Initialise(bool repeating, Shape shape, SizeType size_type, Vector2Numeric size, Vector2Numeric position, const ColorStopList& color_stops); + + DecoratorDataHandle GenerateElementData(Element* element, BoxArea paint_area) const override; + void ReleaseElementData(DecoratorDataHandle element_data) const override; + + void RenderElement(Element* element, DecoratorDataHandle element_data) const override; + +private: + struct RadialGradientShape { + Vector2f center, radius; + }; + RadialGradientShape CalculateRadialGradientShape(Element* element, Vector2f dimensions) const; + + bool repeating = false; + Shape shape = {}; + SizeType size_type = {}; + Vector2Numeric size; + Vector2Numeric position; + + ColorStopList color_stops; +}; + +class DecoratorRadialGradientInstancer : public DecoratorInstancer { +public: + DecoratorRadialGradientInstancer(); + virtual ~DecoratorRadialGradientInstancer(); + + SharedPtr InstanceDecorator(const String& name, const PropertyDictionary& properties, + const DecoratorInstancerInterface& instancer_interface) override; + +private: + struct GradientPropertyIds { + PropertyId ending_shape; + PropertyId size_x, size_y; + PropertyId position_x, position_y; + PropertyId color_stop_list; + }; + GradientPropertyIds ids; +}; + +/** + Conic gradient. + */ +class DecoratorConicGradient : public Decorator { +public: + DecoratorConicGradient(); + virtual ~DecoratorConicGradient(); + + bool Initialise(bool repeating, float angle, Vector2Numeric position, const ColorStopList& color_stops); + + DecoratorDataHandle GenerateElementData(Element* element, BoxArea paint_area) const override; + void ReleaseElementData(DecoratorDataHandle element_data) const override; + + void RenderElement(Element* element, DecoratorDataHandle element_data) const override; + +private: + bool repeating = false; + float angle = {}; + Vector2Numeric position; + ColorStopList color_stops; +}; + +class DecoratorConicGradientInstancer : public DecoratorInstancer { +public: + DecoratorConicGradientInstancer(); + virtual ~DecoratorConicGradientInstancer(); + + SharedPtr InstanceDecorator(const String& name, const PropertyDictionary& properties, + const DecoratorInstancerInterface& instancer_interface) override; + +private: + struct GradientPropertyIds { + PropertyId angle; + PropertyId position_x, position_y; + PropertyId color_stop_list; + }; + GradientPropertyIds ids; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorNinePatch.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorNinePatch.cpp new file mode 100644 index 00000000..030ae1e3 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorNinePatch.cpp @@ -0,0 +1,227 @@ +#include "DecoratorNinePatch.h" +#include "../../Include/RmlUi/Core/ComputedValues.h" +#include "../../Include/RmlUi/Core/Element.h" +#include "../../Include/RmlUi/Core/ElementUtilities.h" +#include "../../Include/RmlUi/Core/Geometry.h" +#include "../../Include/RmlUi/Core/PropertyDefinition.h" + +namespace Rml { + +DecoratorNinePatch::DecoratorNinePatch() {} + +DecoratorNinePatch::~DecoratorNinePatch() {} + +bool DecoratorNinePatch::Initialise(const Rectanglef& _rect_outer, const Rectanglef& _rect_inner, const Array* _edges, + Texture _texture, float _display_scale) +{ + rect_outer = _rect_outer; + rect_inner = _rect_inner; + + display_scale = _display_scale; + + if (_edges) + edges = MakeUnique>(*_edges); + + int texture_index = AddTexture(_texture); + return (texture_index >= 0); +} + +DecoratorDataHandle DecoratorNinePatch::GenerateElementData(Element* element, BoxArea paint_area) const +{ + const auto& computed = element->GetComputedValues(); + + Texture texture = GetTexture(); + const Vector2f texture_dimensions(texture.GetDimensions()); + + const RenderBox render_box = element->GetRenderBox(paint_area); + const Vector2f surface_offset = render_box.GetFillOffset(); + const Vector2f surface_dimensions = render_box.GetFillSize(); + + const ColourbPremultiplied quad_colour = computed.image_color().ToPremultiplied(computed.opacity()); + + /* In the following, we operate on the four diagonal vertices in the grid, as they define the whole grid. */ + + // Absolute texture coordinates 'px' + Vector2f tex_pos[4] = { + rect_outer.TopLeft(), + rect_inner.TopLeft(), + rect_inner.BottomRight(), + rect_outer.BottomRight(), + }; + + // Normalized texture coordinates [0, 1] + Vector2f tex_coords[4]; + for (int i = 0; i < 4; i++) + tex_coords[i] = tex_pos[i] / texture_dimensions; + + // Natural size is determined from the raw pixel size multiplied by the dp-ratio and the sprite's + // display scale (determined by eg. the inverse of spritesheet's 'src-scale'). + const float scale_raw_to_natural_dimensions = ElementUtilities::GetDensityIndependentPixelRatio(element) * display_scale; + + // Surface position in pixels [0, surface_dimensions] + // Need to keep the corner patches at their natural size, but stretch the inner patches. + Vector2f surface_pos[4]; + surface_pos[0] = {0, 0}; + surface_pos[1] = (tex_pos[1] - tex_pos[0]) * scale_raw_to_natural_dimensions; + surface_pos[2] = surface_dimensions - (tex_pos[3] - tex_pos[2]) * scale_raw_to_natural_dimensions; + surface_pos[3] = surface_dimensions; + + // Change the size of the edges if specified. + if (edges) + { + float lengths[4]; // top, right, bottom, left + lengths[0] = element->ResolveNumericValue((*edges)[0], (surface_pos[1].y - surface_pos[0].y)); + lengths[1] = element->ResolveNumericValue((*edges)[1], (surface_pos[3].x - surface_pos[2].x)); + lengths[2] = element->ResolveNumericValue((*edges)[2], (surface_pos[3].y - surface_pos[2].y)); + lengths[3] = element->ResolveNumericValue((*edges)[3], (surface_pos[1].x - surface_pos[0].x)); + + surface_pos[1].y = lengths[0]; + surface_pos[2].x = surface_dimensions.x - lengths[1]; + surface_pos[2].y = surface_dimensions.y - lengths[2]; + surface_pos[1].x = lengths[3]; + } + + // In case the surface dimensions are less than the size of the corners, we need to scale down the corner rectangles, one dimension at a time. + const Vector2f surface_center_size = surface_pos[2] - surface_pos[1]; + for (int i = 0; i < 2; i++) + { + if (surface_center_size[i] < 0.0f) + { + float top_left_size = tex_pos[1][i] - tex_pos[0][i]; + float bottom_right_size = tex_pos[3][i] - tex_pos[2][i]; + surface_pos[1][i] = top_left_size / (top_left_size + bottom_right_size) * surface_dimensions[i]; + surface_pos[2][i] = surface_pos[1][i]; + } + } + + // Now offset all positions, relative to the border box. + for (Vector2f& surface_pos_entry : surface_pos) + surface_pos_entry += surface_offset; + + // Round the inner corners + surface_pos[1] = surface_pos[1].Round(); + surface_pos[2] = surface_pos[2].Round(); + + /* Now we have all the coordinates we need. Expand the diagonal vertices to the 16 individual vertices. */ + Mesh mesh; + Vector& vertices = mesh.vertices; + Vector& indices = mesh.indices; + + vertices.resize(4 * 4); + + for (int y = 0; y < 4; y++) + { + for (int x = 0; x < 4; x++) + { + Vertex& vertex = vertices[y * 4 + x]; + vertex.colour = quad_colour; + vertex.position = {surface_pos[x].x, surface_pos[y].y}; + vertex.tex_coord = {tex_coords[x].x, tex_coords[y].y}; + } + } + + // Nine rectangles, two triangles per rectangle, three indices per triangle. + indices.resize(9 * 2 * 3); + + // Fill in the indices one rectangle at a time. + const int top_left_indices[9] = {0, 1, 2, 4, 5, 6, 8, 9, 10}; + for (int rectangle = 0; rectangle < 9; rectangle++) + { + int i = rectangle * 6; + int top_left_index = top_left_indices[rectangle]; + indices[i] = top_left_index; + indices[i + 1] = top_left_index + 4; + indices[i + 2] = top_left_index + 1; + indices[i + 3] = top_left_index + 1; + indices[i + 4] = top_left_index + 4; + indices[i + 5] = top_left_index + 5; + } + + Geometry* data = new Geometry(element->GetRenderManager()->MakeGeometry(std::move(mesh))); + + return reinterpret_cast(data); +} + +void DecoratorNinePatch::ReleaseElementData(DecoratorDataHandle element_data) const +{ + delete reinterpret_cast(element_data); +} + +void DecoratorNinePatch::RenderElement(Element* element, DecoratorDataHandle element_data) const +{ + Geometry* data = reinterpret_cast(element_data); + data->Render(element->GetAbsoluteOffset(BoxArea::Border), GetTexture()); +} + +DecoratorNinePatchInstancer::DecoratorNinePatchInstancer() +{ + sprite_outer_id = RegisterProperty("outer", "").AddParser("string").GetId(); + sprite_inner_id = RegisterProperty("inner", "").AddParser("string").GetId(); + edge_ids[0] = RegisterProperty("edge-top", "0px").AddParser("number_length_percent").GetId(); + edge_ids[1] = RegisterProperty("edge-right", "0px").AddParser("number_length_percent").GetId(); + edge_ids[2] = RegisterProperty("edge-bottom", "0px").AddParser("number_length_percent").GetId(); + edge_ids[3] = RegisterProperty("edge-left", "0px").AddParser("number_length_percent").GetId(); + + RegisterShorthand("edge", "edge-top, edge-right, edge-bottom, edge-left", ShorthandType::Box); + + RMLUI_ASSERT(sprite_outer_id != PropertyId::Invalid && sprite_inner_id != PropertyId::Invalid); + + RegisterShorthand("decorator", "outer, inner, edge?", ShorthandType::RecursiveCommaSeparated); +} + +DecoratorNinePatchInstancer::~DecoratorNinePatchInstancer() {} + +SharedPtr DecoratorNinePatchInstancer::InstanceDecorator(const String& /*name*/, const PropertyDictionary& properties, + const DecoratorInstancerInterface& instancer_interface) +{ + bool edges_set = false; + Array edges; + for (int i = 0; i < 4; i++) + { + edges[i] = properties.GetProperty(edge_ids[i])->GetNumericValue(); + if (edges[i].number != 0.0f) + { + edges_set = true; + } + } + + const Sprite* sprite_outer = nullptr; + const Sprite* sprite_inner = nullptr; + + { + const String sprite_name = properties.GetProperty(sprite_outer_id)->Get(); + sprite_outer = instancer_interface.GetSprite(sprite_name); + if (!sprite_outer) + { + Log::Message(Log::LT_WARNING, "Could not find sprite named '%s' in ninepatch decorator.", sprite_name.c_str()); + return nullptr; + } + } + { + const String sprite_name = properties.GetProperty(sprite_inner_id)->Get(); + sprite_inner = instancer_interface.GetSprite(sprite_name); + if (!sprite_inner) + { + Log::Message(Log::LT_WARNING, "Could not find sprite named '%s' in ninepatch decorator.", sprite_name.c_str()); + return nullptr; + } + } + + if (sprite_outer->sprite_sheet != sprite_inner->sprite_sheet) + { + Log::Message(Log::LT_WARNING, "The outer and inner sprites in a ninepatch decorator must be from the same sprite sheet."); + return nullptr; + } + + auto decorator = MakeShared(); + + if (!decorator->Initialise(sprite_outer->rectangle, sprite_inner->rectangle, (edges_set ? &edges : nullptr), + sprite_outer->sprite_sheet->texture_source.GetTexture(instancer_interface.GetRenderManager()), sprite_outer->sprite_sheet->display_scale)) + { + return nullptr; + } + + return decorator; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorNinePatch.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorNinePatch.h new file mode 100644 index 00000000..73e6292c --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorNinePatch.h @@ -0,0 +1,41 @@ +#pragma once + +#include "../../Include/RmlUi/Core/Decorator.h" +#include "../../Include/RmlUi/Core/ID.h" +#include "../../Include/RmlUi/Core/Spritesheet.h" + +namespace Rml { + +class DecoratorNinePatch : public Decorator { +public: + DecoratorNinePatch(); + virtual ~DecoratorNinePatch(); + + bool Initialise(const Rectanglef& rect_outer, const Rectanglef& rect_inner, const Array* _edges, Texture texture, + float display_scale); + + DecoratorDataHandle GenerateElementData(Element* element, BoxArea paint_area) const override; + void ReleaseElementData(DecoratorDataHandle element_data) const override; + + void RenderElement(Element* element, DecoratorDataHandle element_data) const override; + +private: + Rectanglef rect_outer, rect_inner; + float display_scale = 1; + UniquePtr> edges; +}; + +class DecoratorNinePatchInstancer : public DecoratorInstancer { +public: + DecoratorNinePatchInstancer(); + ~DecoratorNinePatchInstancer(); + + SharedPtr InstanceDecorator(const String& name, const PropertyDictionary& properties, + const DecoratorInstancerInterface& instancer_interface) override; + +private: + PropertyId sprite_outer_id, sprite_inner_id; + PropertyId edge_ids[4]; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorShader.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorShader.cpp new file mode 100644 index 00000000..1facf690 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorShader.cpp @@ -0,0 +1,91 @@ +#include "DecoratorShader.h" +#include "../../Include/RmlUi/Core/ComputedValues.h" +#include "../../Include/RmlUi/Core/Element.h" +#include "../../Include/RmlUi/Core/Geometry.h" +#include "../../Include/RmlUi/Core/MeshUtilities.h" +#include "../../Include/RmlUi/Core/PropertyDefinition.h" +#include "../../Include/RmlUi/Core/RenderManager.h" + +namespace Rml { + +Pool& GetShaderElementDataPool() +{ + static Pool gradient_element_data_pool(20, true); + return gradient_element_data_pool; +} + +DecoratorShader::DecoratorShader() {} + +DecoratorShader::~DecoratorShader() {} + +bool DecoratorShader::Initialise(String&& in_value) +{ + value = std::move(in_value); + return true; +} + +DecoratorDataHandle DecoratorShader::GenerateElementData(Element* element, BoxArea paint_area) const +{ + RenderManager* render_manager = element->GetRenderManager(); + if (!render_manager) + return INVALID_DECORATORDATAHANDLE; + + const RenderBox render_box = element->GetRenderBox(paint_area); + const Vector2f dimensions = render_box.GetFillSize(); + CompiledShader shader = render_manager->CompileShader("shader", Dictionary{{"value", Variant(value)}, {"dimensions", Variant(dimensions)}}); + if (!shader) + return INVALID_DECORATORDATAHANDLE; + + Mesh mesh; + + const ComputedValues& computed = element->GetComputedValues(); + const byte alpha = byte(computed.opacity() * 255.f); + MeshUtilities::GenerateBackground(mesh, render_box, ColourbPremultiplied(alpha, alpha)); + + const Vector2f offset = render_box.GetFillOffset(); + for (Vertex& vertex : mesh.vertices) + vertex.tex_coord = (vertex.position - offset) / dimensions; + + ShaderElementData* element_data = + GetShaderElementDataPool().AllocateAndConstruct(render_manager->MakeGeometry(std::move(mesh)), std::move(shader)); + + return reinterpret_cast(element_data); +} + +void DecoratorShader::ReleaseElementData(DecoratorDataHandle handle) const +{ + ShaderElementData* element_data = reinterpret_cast(handle); + GetShaderElementDataPool().DestroyAndDeallocate(element_data); +} + +void DecoratorShader::RenderElement(Element* element, DecoratorDataHandle handle) const +{ + ShaderElementData* element_data = reinterpret_cast(handle); + element_data->geometry.Render(element->GetAbsoluteOffset(BoxArea::Border), {}, element_data->shader); +} + +DecoratorShaderInstancer::DecoratorShaderInstancer() +{ + ids.value = RegisterProperty("value", String()).AddParser("string").GetId(); + RegisterShorthand("decorator", "value", ShorthandType::FallThrough); +} + +DecoratorShaderInstancer::~DecoratorShaderInstancer() {} + +SharedPtr DecoratorShaderInstancer::InstanceDecorator(const String& /*name*/, const PropertyDictionary& properties_, + const DecoratorInstancerInterface& /*interface_*/) +{ + const Property* p_value = properties_.GetProperty(ids.value); + if (!p_value) + return nullptr; + + String value = p_value->Get(); + + auto decorator = MakeShared(); + if (decorator->Initialise(std::move(value))) + return decorator; + + return nullptr; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorShader.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorShader.h new file mode 100644 index 00000000..5093a412 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorShader.h @@ -0,0 +1,49 @@ +#pragma once + +#include "../../Include/RmlUi/Core/Decorator.h" +#include "../../Include/RmlUi/Core/Geometry.h" +#include "../../Include/RmlUi/Core/ID.h" +#include "../../Include/RmlUi/Core/Spritesheet.h" +#include "Pool.h" + +namespace Rml { + +class DecoratorShader : public Decorator { +public: + DecoratorShader(); + virtual ~DecoratorShader(); + + bool Initialise(String&& value); + + DecoratorDataHandle GenerateElementData(Element* element, BoxArea paint_area) const override; + void ReleaseElementData(DecoratorDataHandle element_data) const override; + + void RenderElement(Element* element, DecoratorDataHandle element_data) const override; + +private: + String value; +}; + +class DecoratorShaderInstancer : public DecoratorInstancer { +public: + DecoratorShaderInstancer(); + ~DecoratorShaderInstancer(); + + SharedPtr InstanceDecorator(const String& name, const PropertyDictionary& properties, + const DecoratorInstancerInterface& instancer_interface) override; + +private: + struct PropertyIds { + PropertyId value; + }; + PropertyIds ids; +}; + +struct ShaderElementData { + ShaderElementData(Geometry&& geometry, CompiledShader&& shader) : geometry(std::move(geometry)), shader(std::move(shader)) {} + Geometry geometry; + CompiledShader shader; +}; +Pool& GetShaderElementDataPool(); + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorText.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorText.cpp new file mode 100644 index 00000000..ea0628a0 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorText.cpp @@ -0,0 +1,141 @@ +#include "DecoratorText.h" +#include "../../Include/RmlUi/Core/ComputedValues.h" +#include "../../Include/RmlUi/Core/Context.h" +#include "../../Include/RmlUi/Core/Element.h" +#include "../../Include/RmlUi/Core/FontEngineInterface.h" +#include "../../Include/RmlUi/Core/Geometry.h" +#include "../../Include/RmlUi/Core/PropertyDefinition.h" +#include "../../Include/RmlUi/Core/RenderManager.h" +#include "../../Include/RmlUi/Core/TextShapingContext.h" + +namespace Rml { + +DecoratorText::DecoratorText() {} + +DecoratorText::~DecoratorText() {} + +void DecoratorText::Initialise(String in_text, bool in_inherit_color, Colourb in_color, Vector2Numeric in_align) +{ + text = std::move(in_text); + inherit_color = in_inherit_color; + color = in_color; + align = in_align; +} + +DecoratorDataHandle DecoratorText::GenerateElementData(Element* element, BoxArea paint_area) const +{ + ElementData* data = new ElementData{paint_area, {}, -1}; + + if (!GenerateGeometry(element, *data)) + { + Log::Message(Log::LT_WARNING, "Could not construct text decorator with text %s on element %s", text.c_str(), element->GetAddress().c_str()); + return {}; + } + + return reinterpret_cast(data); +} + +void DecoratorText::ReleaseElementData(DecoratorDataHandle element_data) const +{ + delete reinterpret_cast(element_data); +} + +void DecoratorText::RenderElement(Element* element, DecoratorDataHandle element_data) const +{ + ElementData* data = reinterpret_cast(element_data); + if (!GenerateGeometry(element, *data)) + return; + + const Vector2f translation = element->GetAbsoluteOffset(BoxArea::Border); + + for (size_t i = 0; i < data->textured_geometry.size(); ++i) + data->textured_geometry[i].geometry.Render(translation, data->textured_geometry[i].texture); +} + +bool DecoratorText::GenerateGeometry(Element* element, ElementData& element_data) const +{ + FontFaceHandle font_face_handle = element->GetFontFaceHandle(); + if (font_face_handle == 0) + return false; + + FontEngineInterface* font_engine_interface = GetFontEngineInterface(); + const int new_version = font_engine_interface->GetVersion(font_face_handle); + if (new_version == element_data.font_handle_version) + return true; + + const auto& computed = element->GetComputedValues(); + const TextShapingContext text_shaping_context{computed.language(), computed.direction(), computed.font_kerning(), computed.letter_spacing()}; + + const int string_width = font_engine_interface->GetStringWidth(font_face_handle, text, text_shaping_context); + + const FontMetrics& metrics = font_engine_interface->GetFontMetrics(font_face_handle); + const RenderBox render_box = element->GetRenderBox(element_data.paint_area); + const Vector2f text_size = {float(string_width), metrics.ascent + metrics.descent}; + const Vector2f offset_to_align_area = render_box.GetFillOffset() + Vector2f{0, metrics.ascent}; + const Vector2f size_of_align_area = render_box.GetFillSize() - text_size; + const Vector2f offset_within_align_area = + Vector2f{element->ResolveNumericValue(align.x, size_of_align_area.x), element->ResolveNumericValue(align.y, size_of_align_area.y)}; + + const Vector2f offset = offset_to_align_area + offset_within_align_area; + const float opacity = computed.opacity(); + const ColourbPremultiplied text_color = (inherit_color ? computed.color() : color).ToPremultiplied(opacity); + + RenderManager& render_manager = element->GetContext()->GetRenderManager(); + TexturedMeshList mesh_list; + font_engine_interface->GenerateString(render_manager, font_face_handle, {}, text, offset, text_color, opacity, text_shaping_context, mesh_list); + + if (mesh_list.empty()) + return false; + + Vector textured_geometry(mesh_list.size()); + for (size_t i = 0; i < textured_geometry.size(); i++) + { + textured_geometry[i].geometry = render_manager.MakeGeometry(std::move(mesh_list[i].mesh)); + textured_geometry[i].texture = mesh_list[i].texture; + } + + element_data = ElementData{ + element_data.paint_area, + std::move(textured_geometry), + font_engine_interface->GetVersion(font_face_handle), + }; + + return true; +} + +DecoratorTextInstancer::DecoratorTextInstancer() +{ + ids = {}; + ids.text = RegisterProperty("text", "").AddParser("string").GetId(); + ids.color = RegisterProperty("color", "inherit-color").AddParser("keyword", "inherit-color").AddParser("color").GetId(); + ids.align_x = RegisterProperty("align-x", "center").AddParser("keyword", "left, center, right").AddParser("length_percent").GetId(); + ids.align_y = RegisterProperty("align-y", "center").AddParser("keyword", "top, center, bottom").AddParser("length_percent").GetId(); + + RegisterShorthand("decorator", "text, color, align-x, align-y, align-x", ShorthandType::FallThrough); +} + +DecoratorTextInstancer::~DecoratorTextInstancer() {} + +SharedPtr DecoratorTextInstancer::InstanceDecorator(const String& /*name*/, const PropertyDictionary& properties, + const DecoratorInstancerInterface& /*instancer_interface*/) +{ + const Property* p_text = properties.GetProperty(ids.text); + const Property* p_color = properties.GetProperty(ids.color); + Array p_align = {properties.GetProperty(ids.align_x), properties.GetProperty(ids.align_y)}; + if (!p_text || !p_color || !p_align[0] || !p_align[1]) + return nullptr; + + String text = StringUtilities::DecodeRml(p_text->Get()); + if (text.empty()) + return nullptr; + + const bool inherit_color = (p_color->unit == Unit::KEYWORD); + const Colourb color = (p_color->unit == Unit::COLOUR ? p_color->Get() : Colourb{}); + const Vector2Numeric align = ComputePosition(p_align); + + auto decorator = MakeShared(); + decorator->Initialise(std::move(text), inherit_color, color, align); + return decorator; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorText.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorText.h new file mode 100644 index 00000000..e5ec9dfa --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorText.h @@ -0,0 +1,56 @@ +#pragma once + +#include "../../Include/RmlUi/Core/Decorator.h" +#include "../../Include/RmlUi/Core/Geometry.h" +#include "../../Include/RmlUi/Core/ID.h" +#include "DecoratorUtilities.h" + +namespace Rml { + +class DecoratorText : public Decorator { +public: + DecoratorText(); + virtual ~DecoratorText(); + + void Initialise(String text, bool inherit_color, Colourb color, Vector2Numeric align); + + DecoratorDataHandle GenerateElementData(Element* element, BoxArea paint_area) const override; + void ReleaseElementData(DecoratorDataHandle element_data) const override; + + void RenderElement(Element* element, DecoratorDataHandle element_data) const override; + +private: + struct TexturedGeometry { + Geometry geometry; + Texture texture; + }; + struct ElementData { + BoxArea paint_area; + Vector textured_geometry; + int font_handle_version; + }; + + bool GenerateGeometry(Element* element, ElementData& element_data) const; + + String text; + bool inherit_color = false; + Colourb color; + Vector2Numeric align; +}; + +class DecoratorTextInstancer : public DecoratorInstancer { +public: + DecoratorTextInstancer(); + ~DecoratorTextInstancer(); + + SharedPtr InstanceDecorator(const String& name, const PropertyDictionary& properties, + const DecoratorInstancerInterface& instancer_interface) override; + +private: + struct PropertyIds { + PropertyId text, color, align_x, align_y; + }; + PropertyIds ids; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorTiled.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorTiled.cpp new file mode 100644 index 00000000..8c597716 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorTiled.cpp @@ -0,0 +1,373 @@ +#include "DecoratorTiled.h" +#include "../../Include/RmlUi/Core/Element.h" +#include "../../Include/RmlUi/Core/ElementUtilities.h" +#include "../../Include/RmlUi/Core/Geometry.h" +#include "../../Include/RmlUi/Core/Math.h" +#include "../../Include/RmlUi/Core/MeshUtilities.h" +#include "../../Include/RmlUi/Core/PropertyDefinition.h" +#include "../../Include/RmlUi/Core/Spritesheet.h" +#include + +namespace Rml { + +DecoratorTiled::DecoratorTiled() {} + +DecoratorTiled::~DecoratorTiled() {} + +static const Vector2f oriented_texcoords[4][2] = { + {Vector2f(0, 0), Vector2f(1, 1)}, // ORIENTATION_NONE + {Vector2f(1, 0), Vector2f(0, 1)}, // FLIP_HORIZONTAL + {Vector2f(0, 1), Vector2f(1, 0)}, // FLIP_VERTICAL + {Vector2f(1, 1), Vector2f(0, 0)} // ROTATE_180 +}; + +DecoratorTiled::Tile::Tile() : display_scale(1), position(0, 0), size(0, 0) +{ + texture_index = -1; + fit_mode = FILL; + orientation = ORIENTATION_NONE; +} + +void DecoratorTiled::Tile::CalculateDimensions(Texture texture) const +{ + if (!tile_data_calculated) + { + tile_data_calculated = true; + tile_data = {}; + + const Vector2f texture_dimensions(texture.GetDimensions()); + if (texture_dimensions.x == 0 || texture_dimensions.y == 0) + { + tile_data.size = Vector2f(0, 0); + tile_data.texcoords[0] = Vector2f(0, 0); + tile_data.texcoords[1] = Vector2f(0, 0); + } + else + { + // Need to scale the coordinates to normalized units and 'size' to absolute size (pixels) + if (size.x == 0 && size.y == 0 && position.x == 0 && position.y == 0) + tile_data.size = texture_dimensions; + else + tile_data.size = size; + + const Vector2f size_relative = tile_data.size / texture_dimensions; + + tile_data.size = Vector2f(Math::Absolute(tile_data.size.x), Math::Absolute(tile_data.size.y)); + + tile_data.texcoords[0] = position / texture_dimensions; + tile_data.texcoords[1] = size_relative + tile_data.texcoords[0]; + } + } +} + +Vector2f DecoratorTiled::Tile::GetNaturalDimensions(Element* element) const +{ + if (!tile_data_calculated) + return Vector2f(0, 0); + + const float scale_raw_to_natural_dimensions = ElementUtilities::GetDensityIndependentPixelRatio(element) * display_scale; + const Vector2f raw_dimensions = tile_data.size; + + return raw_dimensions * scale_raw_to_natural_dimensions; +} + +void DecoratorTiled::Tile::GenerateGeometry(Mesh& mesh, const ComputedValues& computed, const Vector2f surface_origin, + const Vector2f surface_dimensions, const Vector2f tile_dimensions) const +{ + if (surface_dimensions.x <= 0 || surface_dimensions.y <= 0) + return; + + const ColourbPremultiplied quad_colour = computed.image_color().ToPremultiplied(computed.opacity()); + + if (!tile_data_calculated) + return; + + // Generate the oriented texture coordinates for the tiles. + Vector2f scaled_texcoords[2]; + for (int i = 0; i < 2; i++) + { + scaled_texcoords[i] = tile_data.texcoords[0] + oriented_texcoords[orientation][i] * (tile_data.texcoords[1] - tile_data.texcoords[0]); + } + + Vector2f final_tile_dimensions; + bool offset_and_clip_tile = false; + Vector2f repeat_factor = Vector2f(1); + + switch (fit_mode) + { + case FILL: + { + final_tile_dimensions = surface_dimensions; + } + break; + case CONTAIN: + { + Vector2f scale_factor = surface_dimensions / tile_dimensions; + float min_factor = std::min(scale_factor.x, scale_factor.y); + final_tile_dimensions = tile_dimensions * min_factor; + + offset_and_clip_tile = true; + } + break; + case COVER: + { + Vector2f scale_factor = surface_dimensions / tile_dimensions; + float max_factor = std::max(scale_factor.x, scale_factor.y); + final_tile_dimensions = tile_dimensions * max_factor; + + offset_and_clip_tile = true; + } + break; + case SCALE_NONE: + { + final_tile_dimensions = tile_dimensions; + + offset_and_clip_tile = true; + } + break; + case SCALE_DOWN: + { + Vector2f scale_factor = surface_dimensions / tile_dimensions; + float min_factor = std::min(scale_factor.x, scale_factor.y); + if (min_factor < 1.0f) + final_tile_dimensions = tile_dimensions * min_factor; + else + final_tile_dimensions = tile_dimensions; + + offset_and_clip_tile = true; + } + break; + case REPEAT: + final_tile_dimensions = surface_dimensions; + repeat_factor = surface_dimensions / tile_dimensions; + break; + case REPEAT_X: + final_tile_dimensions = Vector2f(surface_dimensions.x, tile_dimensions.y); + repeat_factor.x = surface_dimensions.x / tile_dimensions.x; + offset_and_clip_tile = true; + break; + case REPEAT_Y: + final_tile_dimensions = Vector2f(tile_dimensions.x, surface_dimensions.y); + repeat_factor.y = surface_dimensions.y / tile_dimensions.y; + offset_and_clip_tile = true; + break; + } + + Vector2f tile_offset(0, 0); + + if (offset_and_clip_tile) + { + // Offset tile along each dimension. + for (int i = 0; i < 2; i++) + { + switch (align[i].type) + { + case Style::LengthPercentage::Length: tile_offset[i] = align[i].value; break; + case Style::LengthPercentage::Percentage: + tile_offset[i] = (surface_dimensions[i] - final_tile_dimensions[i]) * align[i].value * 0.01f; + break; + } + } + tile_offset = tile_offset.Round(); + + // Clip tile. See if our tile extends outside the boundary at either side, along each dimension. + for (int i = 0; i < 2; i++) + { + // Left/right acts as top/bottom during the second iteration. + float overshoot_left = std::max(-tile_offset[i], 0.0f); + float overshoot_right = std::max(tile_offset[i] + final_tile_dimensions[i] - surface_dimensions[i], 0.0f); + + if (overshoot_left > 0.f || overshoot_right > 0.f) + { + float& left = scaled_texcoords[0][i]; + float& right = scaled_texcoords[1][i]; + float width = right - left; + + left += overshoot_left / final_tile_dimensions[i] * width; + right -= overshoot_right / final_tile_dimensions[i] * width; + + final_tile_dimensions[i] -= overshoot_left + overshoot_right; + tile_offset[i] += overshoot_left; + } + } + } + + scaled_texcoords[0] *= repeat_factor; + scaled_texcoords[1] *= repeat_factor; + + // Generate the vertices for the tiled surface. + Vector2f tile_position = (surface_origin + tile_offset); + Math::SnapToPixelGrid(tile_position, final_tile_dimensions); + + MeshUtilities::GenerateQuad(mesh, tile_position, final_tile_dimensions, quad_colour, scaled_texcoords[0], scaled_texcoords[1]); +} + +void DecoratorTiled::ScaleTileDimensions(Vector2f& tile_dimensions, float axis_value, Axis axis_enum) const +{ + int axis = static_cast(axis_enum); + if (tile_dimensions[axis] != axis_value) + { + tile_dimensions[1 - axis] = tile_dimensions[1 - axis] * (axis_value / tile_dimensions[axis]); + tile_dimensions[axis] = axis_value; + } +} + +DecoratorTiledInstancer::DecoratorTiledInstancer(size_t num_tiles) +{ + tile_property_ids.reserve(num_tiles); +} + +void DecoratorTiledInstancer::RegisterTileProperty(const String& name, bool register_fit_modes) +{ + TilePropertyIds ids = {}; + + ids.src = RegisterProperty(CreateString("%s-src", name.c_str()), "").AddParser("string").GetId(); + + String additional_modes; + + if (register_fit_modes) + { + String fit_name = CreateString("%s-fit", name.c_str()); + ids.fit = RegisterProperty(fit_name, "fill") + .AddParser("keyword", "fill, contain, cover, scale-none, scale-down, repeat, repeat-x, repeat-y") + .GetId(); + + String align_x_name = CreateString("%s-align-x", name.c_str()); + ids.align_x = RegisterProperty(align_x_name, "center").AddParser("keyword", "left, center, right").AddParser("length_percent").GetId(); + + String align_y_name = CreateString("%s-align-y", name.c_str()); + ids.align_y = RegisterProperty(align_y_name, "center").AddParser("keyword", "top, center, bottom").AddParser("length_percent").GetId(); + + additional_modes += ", " + fit_name + ", " + align_x_name + ", " + align_y_name; + } + + ids.orientation = RegisterProperty(CreateString("%s-orientation", name.c_str()), "none") + .AddParser("keyword", "none, flip-horizontal, flip-vertical, rotate-180") + .GetId(); + + RegisterShorthand(name, + CreateString(("%s-src, %s-orientation" + additional_modes).c_str(), name.c_str(), name.c_str(), name.c_str(), name.c_str(), name.c_str(), + name.c_str()), + ShorthandType::FallThrough); + + tile_property_ids.push_back(ids); +} + +bool DecoratorTiledInstancer::GetTileProperties(DecoratorTiled::Tile* tiles, Texture* textures, size_t num_tiles_and_textures, + const PropertyDictionary& properties, const DecoratorInstancerInterface& instancer_interface) const +{ + RMLUI_ASSERT(num_tiles_and_textures == tile_property_ids.size()); + + String previous_texture_name; + Texture previous_texture; + + for (size_t i = 0; i < num_tiles_and_textures; i++) + { + const TilePropertyIds& ids = tile_property_ids[i]; + + const Property* src_property = properties.GetProperty(ids.src); + const String texture_name = src_property->Get(); + + // Skip the tile if it has no source name. + // Declaring the name 'auto' is the same as an empty string. This gives an easy way to skip certain + // tiles in a shorthand since we can't always declare an empty string. + if (texture_name.empty() || texture_name == "auto") + continue; + + // We are required to set default values before instancing the tile, thus, all properties should always be + // dereferencable. If the debugger captures a zero-dereference, check that all properties for every tile is set + // and default values are set just before instancing. + + DecoratorTiled::Tile& tile = tiles[i]; + Texture& texture = textures[i]; + + const Sprite* sprite = instancer_interface.GetSprite(texture_name); + + // A tile is always either a sprite or an image. + if (sprite) + { + tile.position = sprite->rectangle.Position(); + tile.size = sprite->rectangle.Size(); + tile.display_scale = sprite->sprite_sheet->display_scale; + + texture = sprite->sprite_sheet->texture_source.GetTexture(instancer_interface.GetRenderManager()); + } + else + { + // No sprite found, so assume that the name is an image source. Since the common use case is to specify the + // same texture for all tiles, check the previous texture first before fetching from the global database. + if (texture_name == previous_texture_name) + { + texture = previous_texture; + } + else + { + texture = instancer_interface.GetTexture(texture_name); + + if (!texture) + return false; + + previous_texture_name = texture_name; + previous_texture = texture; + } + } + + if (ids.fit != PropertyId::Invalid) + { + RMLUI_ASSERT(ids.align_x != PropertyId::Invalid && ids.align_y != PropertyId::Invalid); + const Property& fit_property = *properties.GetProperty(ids.fit); + tile.fit_mode = (DecoratorTiled::TileFitMode)fit_property.value.Get(); + + if (sprite && + (tile.fit_mode == DecoratorTiled::TileFitMode::REPEAT || tile.fit_mode == DecoratorTiled::TileFitMode::REPEAT_X || + tile.fit_mode == DecoratorTiled::TileFitMode::REPEAT_Y)) + { + Log::Message(Log::LT_WARNING, "Decorator 'fit' value is '%s', which is incompatible with sprites", fit_property.ToString().c_str()); + return false; + } + + const Property* align_properties[2] = {properties.GetProperty(ids.align_x), properties.GetProperty(ids.align_y)}; + + for (int dimension = 0; dimension < 2; dimension++) + { + using Style::LengthPercentage; + + LengthPercentage& align = tile.align[dimension]; + const Property& property = *align_properties[dimension]; + if (property.unit == Unit::KEYWORD) + { + enum { TOP_LEFT, CENTER, BOTTOM_RIGHT }; + switch (property.Get()) + { + case TOP_LEFT: align = LengthPercentage(LengthPercentage::Percentage, 0.0f); break; + case CENTER: align = LengthPercentage(LengthPercentage::Percentage, 50.0f); break; + case BOTTOM_RIGHT: align = LengthPercentage(LengthPercentage::Percentage, 100.0f); break; + } + } + else if (property.unit == Unit::PERCENT) + { + align = LengthPercentage(LengthPercentage::Percentage, property.Get()); + } + else if (property.unit == Unit::PX) + { + align = LengthPercentage(LengthPercentage::Length, property.Get()); + } + else + { + Log::Message(Log::LT_WARNING, "Decorator alignment value is '%s' which uses an unsupported unit (use px, %%, or keyword)", + property.ToString().c_str()); + } + } + } + + if (ids.orientation != PropertyId::Invalid) + { + const Property& orientation_property = *properties.GetProperty(ids.orientation); + tile.orientation = (DecoratorTiled::TileOrientation)orientation_property.value.Get(); + } + } + + return true; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorTiled.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorTiled.h new file mode 100644 index 00000000..3d56602c --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorTiled.h @@ -0,0 +1,126 @@ +#pragma once + +#include "../../Include/RmlUi/Core/ComputedValues.h" +#include "../../Include/RmlUi/Core/Decorator.h" +#include "../../Include/RmlUi/Core/Vertex.h" + +namespace Rml { + +class Texture; +struct Mesh; + +/** + Base class for tiled decorators. + */ + +class DecoratorTiled : public Decorator { +public: + DecoratorTiled(); + virtual ~DecoratorTiled(); + + /** + Stores the orientation of a tile. + */ + enum TileOrientation { + ORIENTATION_NONE, // No orientation. + FLIP_HORIZONTAL, // Flipped horizontally. + FLIP_VERTICAL, // Flipped vertically. + ROTATE_180, // Rotated 180 degrees clockwise. + }; + /** + Stores the fit mode of a tile. + */ + enum TileFitMode { + FILL, // Tile is stretched to boundaries. + CONTAIN, // Tile is stretched to boundaries, keeping aspect ratio fixed, 'letter-boxed'. + COVER, // Tile is stretched to cover the boundaries, keeping aspect ratio fixed. + SCALE_NONE, // Tile is never scaled. + SCALE_DOWN, // Tile acts like 'scale-none' if smaller than boundaries, or like 'contain' otherwise. + REPEAT, // Tile is repeated on both x and y axis + REPEAT_X, // Tile is repeated on the x axis + REPEAT_Y, // Tile is repeated on the y axis + }; + + /** + Structure for storing the different tiles the tiled decorator uses internally over its + surface. + */ + struct Tile { + /// Constructs the tile with safe default values. + Tile(); + + /// Calculates the tile's dimensions from the texture and texture coordinates. + void CalculateDimensions(Texture texture) const; + /// Get the dimensions (in px) that this tile is ideally displayed as. + /// Uses the dp-ratio of the current element and 'display_scale' to calculate the dimensions. + Vector2f GetNaturalDimensions(Element* element) const; + + /// Generates geometry to render this tile across a surface. + /// @param[out] vertices The array to store the generated vertex data. + /// @param[out] indices The array to store the generated index data. + /// @param[in] computed_values The computed values of the element being decorated. + /// @param[in] surface_origin The starting point of the first tile to generate. + /// @param[in] surface_dimensions The dimensions of the surface to be tiled. + /// @param[in] tile_dimensions The dimensions to render this tile at. + void GenerateGeometry(Mesh& mesh, const ComputedValues& computed_values, Vector2f surface_origin, Vector2f surface_dimensions, + Vector2f tile_dimensions) const; + + struct TileData { + Vector2f size; // 'px' units + Vector2f texcoords[2]; // relative units + }; + + int texture_index; + + // Scales the desired displayed size of the tile from raw pixel size. Eg. the 'display_scale' in a sprite sheet. + float display_scale; + + // Position and size within the texture, absolute units (px) + Vector2f position, size; + + mutable TileData tile_data; + mutable bool tile_data_calculated = false; + + TileOrientation orientation; + + TileFitMode fit_mode; + Style::LengthPercentage align[2]; + }; + +protected: + enum class Axis { Horizontal, Vertical }; + + /// Scales a tile dimensions by a fixed value along one axis. + /// @param tile_dimensions[in, out] The tile dimensions to scale. + /// @param axis_value[in] The fixed value to scale against. + /// @param axis[in] The axis to scale against. + void ScaleTileDimensions(Vector2f& tile_dimensions, float axis_value, Axis axis) const; +}; + +class DecoratorTiledInstancer : public DecoratorInstancer { +public: + DecoratorTiledInstancer(size_t num_tiles); + +protected: + /// Adds the property declarations for a tile. + /// @param[in] name The name of the tile property. + /// @param[in] register_fit_modes If true, the tile will have the fit modes registered. + void RegisterTileProperty(const String& name, bool register_fit_modes = false); + + /// Retrieves all the properties for a tile from the property dictionary. + /// @param[out] tile The tile structure for storing the tile properties. + /// @param[out] textures Holds the textures declared for the tile. + /// @param[in] properties The user-defined list of parameters for the decorator. + /// @param[in] instancer_interface An interface for querying the active style sheet. + bool GetTileProperties(DecoratorTiled::Tile* tiles, Texture* textures, size_t num_tiles_and_textures, const PropertyDictionary& properties, + const DecoratorInstancerInterface& instancer_interface) const; + +private: + struct TilePropertyIds { + PropertyId src, fit, align_x, align_y, orientation; + }; + + Vector tile_property_ids; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorTiledBox.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorTiledBox.cpp new file mode 100644 index 00000000..1a3280e5 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorTiledBox.cpp @@ -0,0 +1,269 @@ +#include "DecoratorTiledBox.h" +#include "../../Include/RmlUi/Core/Element.h" +#include "../../Include/RmlUi/Core/Geometry.h" +#include "../../Include/RmlUi/Core/RenderManager.h" + +namespace Rml { + +struct DecoratorTiledBoxData { + DecoratorTiledBoxData(int num_textures) : num_textures(num_textures) { geometry = new Geometry[num_textures]; } + + ~DecoratorTiledBoxData() { delete[] geometry; } + + const int num_textures; + Geometry* geometry; +}; + +DecoratorTiledBox::DecoratorTiledBox() {} + +DecoratorTiledBox::~DecoratorTiledBox() {} + +bool DecoratorTiledBox::Initialise(const Tile* _tiles, const Texture* _textures) +{ + // Load the textures. + for (int i = 0; i < 9; i++) + { + tiles[i] = _tiles[i]; + tiles[i].texture_index = AddTexture(_textures[i]); + } + + // All corners must have a valid texture. + for (int i = TOP_LEFT_CORNER; i <= BOTTOM_RIGHT_CORNER; i++) + { + if (tiles[i].texture_index == -1) + return false; + } + // Check that the centre tile has been specified. + if (tiles[CENTRE].texture_index < 0) + return false; + + // If only one side of the left / right edges have been configured, then mirror the tile for the other side. + if (tiles[LEFT_EDGE].texture_index == -1 && tiles[RIGHT_EDGE].texture_index > -1) + { + tiles[LEFT_EDGE] = tiles[RIGHT_EDGE]; + tiles[LEFT_EDGE].orientation = FLIP_HORIZONTAL; + } + else if (tiles[RIGHT_EDGE].texture_index == -1 && tiles[LEFT_EDGE].texture_index > -1) + { + tiles[RIGHT_EDGE] = tiles[LEFT_EDGE]; + tiles[RIGHT_EDGE].orientation = FLIP_HORIZONTAL; + } + else if (tiles[LEFT_EDGE].texture_index == -1 && tiles[RIGHT_EDGE].texture_index == -1) + return false; + + // If only one side of the top / bottom edges have been configured, then mirror the tile for the other side. + if (tiles[TOP_EDGE].texture_index == -1 && tiles[BOTTOM_EDGE].texture_index > -1) + { + tiles[TOP_EDGE] = tiles[BOTTOM_EDGE]; + tiles[TOP_EDGE].orientation = FLIP_VERTICAL; + } + else if (tiles[BOTTOM_EDGE].texture_index == -1 && tiles[TOP_EDGE].texture_index > -1) + { + tiles[BOTTOM_EDGE] = tiles[TOP_EDGE]; + tiles[BOTTOM_EDGE].orientation = FLIP_VERTICAL; + } + else if (tiles[TOP_EDGE].texture_index == -1 && tiles[BOTTOM_EDGE].texture_index == -1) + return false; + + return true; +} + +DecoratorDataHandle DecoratorTiledBox::GenerateElementData(Element* element, BoxArea paint_area) const +{ + // Initialise the tiles for this element. + for (int i = 0; i < 9; i++) + { + RMLUI_ASSERT(tiles[i].texture_index >= 0); + tiles[i].CalculateDimensions(GetTexture(tiles[i].texture_index)); + } + + const RenderBox render_box = element->GetRenderBox(paint_area); + const Vector2f offset = render_box.GetFillOffset(); + const Vector2f size = render_box.GetFillSize(); + + // Calculate the natural dimensions of tile corners and edges. + const Vector2f natural_top_left = tiles[TOP_LEFT_CORNER].GetNaturalDimensions(element); + const Vector2f natural_top = tiles[TOP_EDGE].GetNaturalDimensions(element); + const Vector2f natural_top_right = tiles[TOP_RIGHT_CORNER].GetNaturalDimensions(element); + + const Vector2f natural_bottom_left = tiles[BOTTOM_LEFT_CORNER].GetNaturalDimensions(element); + const Vector2f natural_bottom = tiles[BOTTOM_EDGE].GetNaturalDimensions(element); + const Vector2f natural_bottom_right = tiles[BOTTOM_RIGHT_CORNER].GetNaturalDimensions(element); + + const Vector2f natural_left = tiles[LEFT_EDGE].GetNaturalDimensions(element); + const Vector2f natural_right = tiles[RIGHT_EDGE].GetNaturalDimensions(element); + + // Initialize the to-be-determined dimensions of the tiles. + Vector2f top_left = natural_top_left; + Vector2f top = natural_top; + Vector2f top_right = natural_top_right; + + Vector2f bottom_left = natural_bottom_left; + Vector2f bottom = natural_bottom; + Vector2f bottom_right = natural_bottom_right; + + Vector2f left = natural_left; + Vector2f right = natural_right; + + // Scale the top corners down if appropriate. If they are scaled, then the left and right edges are also scaled + // if they shared a width with their corner. Best solution? Don't know. + if (size.x < top_left.x + top_right.x) + { + float minimum_width = top_left.x + top_right.x; + + top_left.x = size.x * (top_left.x / minimum_width); + if (natural_top_left.x == natural_left.x) + left.x = top_left.x; + + top_right.x = size.x * (top_right.x / minimum_width); + if (natural_top_right.x == natural_right.x) + right.x = top_right.x; + } + + // Scale the bottom corners down if appropriate. If they are scaled, then the left and right edges are also scaled + // if they shared a width with their corner. Best solution? Don't know. + if (size.x < bottom_left.x + bottom_right.x) + { + float minimum_width = bottom_left.x + bottom_right.x; + + bottom_left.x = size.x * (bottom_left.x / minimum_width); + if (natural_bottom_left.x == natural_left.x) + left.x = bottom_left.x; + + bottom_right.x = size.x * (bottom_right.x / minimum_width); + if (natural_bottom_right.x == natural_right.x) + right.x = bottom_right.x; + } + + // Scale the left corners down if appropriate. If they are scaled, then the top and bottom edges are also scaled + // if they shared a width with their corner. Best solution? Don't know. + if (size.y < top_left.y + bottom_left.y) + { + float minimum_height = top_left.y + bottom_left.y; + + top_left.y = size.y * (top_left.y / minimum_height); + if (natural_top_left.y == natural_top.y) + top.y = top_left.y; + + bottom_left.y = size.y * (bottom_left.y / minimum_height); + if (natural_bottom_left.y == natural_bottom.y) + bottom.y = bottom_left.y; + } + + // Scale the right corners down if appropriate. If they are scaled, then the top and bottom edges are also scaled + // if they shared a width with their corner. Best solution? Don't know. + if (size.y < top_right.y + bottom_right.y) + { + float minimum_height = top_right.y + bottom_right.y; + + top_right.y = size.y * (top_right.y / minimum_height); + if (natural_top_right.y == natural_top.y) + top.y = top_right.y; + + bottom_right.y = size.y * (bottom_right.y / minimum_height); + if (natural_bottom_right.y == natural_bottom.y) + bottom.y = bottom_right.y; + } + + const ComputedValues& computed = element->GetComputedValues(); + Mesh mesh[COUNT]; + + // Generate the geometry for the top-left tile. + tiles[TOP_LEFT_CORNER].GenerateGeometry(mesh[tiles[TOP_LEFT_CORNER].texture_index], computed, offset, top_left, top_left); + // Generate the geometry for the top edge tiles. + tiles[TOP_EDGE].GenerateGeometry(mesh[tiles[TOP_EDGE].texture_index], computed, offset + Vector2f(top_left.x, 0), + Vector2f(size.x - (top_left.x + top_right.x), top.y), top); + // Generate the geometry for the top-right tile. + tiles[TOP_RIGHT_CORNER].GenerateGeometry(mesh[tiles[TOP_RIGHT_CORNER].texture_index], computed, offset + Vector2f(size.x - top_right.x, 0), + top_right, top_right); + + // Generate the geometry for the left side. + tiles[LEFT_EDGE].GenerateGeometry(mesh[tiles[LEFT_EDGE].texture_index], computed, offset + Vector2f(0, top_left.y), + Vector2f(left.x, size.y - (top_left.y + bottom_left.y)), left); + + // Generate the geometry for the right side. + tiles[RIGHT_EDGE].GenerateGeometry(mesh[tiles[RIGHT_EDGE].texture_index], computed, offset + Vector2f((size.x - right.x), top_right.y), + Vector2f(right.x, size.y - (top_right.y + bottom_right.y)), right); + + // Generate the geometry for the bottom-left tile. + tiles[BOTTOM_LEFT_CORNER].GenerateGeometry(mesh[tiles[BOTTOM_LEFT_CORNER].texture_index], computed, offset + Vector2f(0, size.y - bottom_left.y), + bottom_left, bottom_left); + // Generate the geometry for the bottom edge tiles. + tiles[BOTTOM_EDGE].GenerateGeometry(mesh[tiles[BOTTOM_EDGE].texture_index], computed, offset + Vector2f(bottom_left.x, size.y - bottom.y), + Vector2f(size.x - (bottom_left.x + bottom_right.x), bottom.y), bottom); + // Generate the geometry for the bottom-right tile. + tiles[BOTTOM_RIGHT_CORNER].GenerateGeometry(mesh[tiles[BOTTOM_RIGHT_CORNER].texture_index], computed, + offset + Vector2f(size.x - bottom_right.x, size.y - bottom_right.y), bottom_right, bottom_right); + + // Generate the centre geometry. + Vector2f centre_dimensions = tiles[CENTRE].GetNaturalDimensions(element); + Vector2f centre_surface_dimensions(size.x - (left.x + right.x), size.y - (top.y + bottom.y)); + + tiles[CENTRE].GenerateGeometry(mesh[tiles[CENTRE].texture_index], computed, offset + Vector2f(left.x, top.y), centre_surface_dimensions, + centre_dimensions); + + const int num_textures = GetNumTextures(); + DecoratorTiledBoxData* data = new DecoratorTiledBoxData(num_textures); + RenderManager* render_manager = element->GetRenderManager(); + + // Set the mesh and textures on the geometry. + for (int i = 0; i < num_textures; i++) + data->geometry[i] = render_manager->MakeGeometry(std::move(mesh[i])); + + return reinterpret_cast(data); +} + +void DecoratorTiledBox::ReleaseElementData(DecoratorDataHandle element_data) const +{ + delete reinterpret_cast(element_data); +} + +void DecoratorTiledBox::RenderElement(Element* element, DecoratorDataHandle element_data) const +{ + Vector2f translation = element->GetAbsoluteOffset(BoxArea::Border); + DecoratorTiledBoxData* data = reinterpret_cast(element_data); + + for (int i = 0; i < data->num_textures; i++) + data->geometry[i].Render(translation, GetTexture(i)); +} + +DecoratorTiledBoxInstancer::DecoratorTiledBoxInstancer() : DecoratorTiledInstancer(9) +{ + RegisterTileProperty("top-left-image"); + RegisterTileProperty("top-right-image"); + RegisterTileProperty("bottom-left-image"); + RegisterTileProperty("bottom-right-image"); + + RegisterTileProperty("left-image"); + RegisterTileProperty("right-image"); + RegisterTileProperty("top-image"); + RegisterTileProperty("bottom-image"); + + RegisterTileProperty("center-image"); + + RegisterShorthand("decorator", + "top-left-image, top-image, top-right-image, left-image, center-image, right-image, bottom-left-image, bottom-image, bottom-right-image", + ShorthandType::RecursiveCommaSeparated); +} + +DecoratorTiledBoxInstancer::~DecoratorTiledBoxInstancer() {} + +SharedPtr DecoratorTiledBoxInstancer::InstanceDecorator(const String& /*name*/, const PropertyDictionary& properties, + const DecoratorInstancerInterface& instancer_interface) +{ + constexpr size_t num_tiles = 9; + + DecoratorTiled::Tile tiles[num_tiles]; + Texture textures[num_tiles]; + + if (!GetTileProperties(tiles, textures, num_tiles, properties, instancer_interface)) + return nullptr; + + auto decorator = MakeShared(); + if (!decorator->Initialise(tiles, textures)) + return nullptr; + + return decorator; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorTiledBox.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorTiledBox.h new file mode 100644 index 00000000..e980e613 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorTiledBox.h @@ -0,0 +1,53 @@ +#pragma once + +#include "DecoratorTiled.h" + +namespace Rml { + +class DecoratorTiledBox : public DecoratorTiled { +public: + DecoratorTiledBox(); + virtual ~DecoratorTiledBox(); + + /// Initialises the tiles for the decorator. + /// @param[in] tiles The declaration for all eight tiles. + /// @param[in] textures The textures for the eight tiles. + /// @return True if all the tiles and textures are properly specified. + bool Initialise(const Tile* tiles, const Texture* textures); + + /// Called on a decorator to generate any required per-element data for a newly decorated element. + DecoratorDataHandle GenerateElementData(Element* element, BoxArea paint_area) const override; + /// Called to release element data generated by this decorator. + void ReleaseElementData(DecoratorDataHandle element_data) const override; + + /// Called to render the decorator on an element. + void RenderElement(Element* element, DecoratorDataHandle element_data) const override; + +private: + enum { + TOP_LEFT_CORNER, + TOP_RIGHT_CORNER, + BOTTOM_LEFT_CORNER, + BOTTOM_RIGHT_CORNER, + LEFT_EDGE, + RIGHT_EDGE, + TOP_EDGE, + BOTTOM_EDGE, + CENTRE, + COUNT, + }; + + Tile tiles[COUNT]; +}; + +class DecoratorTiledBoxInstancer : public DecoratorTiledInstancer { +public: + DecoratorTiledBoxInstancer(); + ~DecoratorTiledBoxInstancer(); + + /// Instances a box decorator. + SharedPtr InstanceDecorator(const String& name, const PropertyDictionary& properties, + const DecoratorInstancerInterface& instancer_interface) override; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorTiledHorizontal.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorTiledHorizontal.cpp new file mode 100644 index 00000000..9b4a4d7a --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorTiledHorizontal.cpp @@ -0,0 +1,145 @@ +#include "DecoratorTiledHorizontal.h" +#include "../../Include/RmlUi/Core/Element.h" +#include "../../Include/RmlUi/Core/Geometry.h" +#include "../../Include/RmlUi/Core/RenderManager.h" +#include "../../Include/RmlUi/Core/Texture.h" + +namespace Rml { + +struct DecoratorTiledHorizontalData { + DecoratorTiledHorizontalData(int num_textures) : num_textures(num_textures) { geometry = new Geometry[num_textures]; } + + ~DecoratorTiledHorizontalData() { delete[] geometry; } + + const int num_textures; + Geometry* geometry; +}; + +DecoratorTiledHorizontal::DecoratorTiledHorizontal() {} + +DecoratorTiledHorizontal::~DecoratorTiledHorizontal() {} + +bool DecoratorTiledHorizontal::Initialise(const Tile* _tiles, const Texture* _textures) +{ + // Load the textures. + for (int i = 0; i < 3; i++) + { + tiles[i] = _tiles[i]; + tiles[i].texture_index = AddTexture(_textures[i]); + } + + // If only one side of the decorator has been configured, then mirror the texture for the other side. + if (tiles[LEFT].texture_index == -1 && tiles[RIGHT].texture_index > -1) + { + tiles[LEFT] = tiles[RIGHT]; + tiles[LEFT].orientation = FLIP_HORIZONTAL; + } + else if (tiles[RIGHT].texture_index == -1 && tiles[LEFT].texture_index > -1) + { + tiles[RIGHT] = tiles[LEFT]; + tiles[RIGHT].orientation = FLIP_HORIZONTAL; + } + else if (tiles[LEFT].texture_index == -1 && tiles[RIGHT].texture_index == -1) + return false; + + if (tiles[CENTRE].texture_index == -1) + return false; + + return true; +} + +DecoratorDataHandle DecoratorTiledHorizontal::GenerateElementData(Element* element, BoxArea paint_area) const +{ + // Initialise the tiles for this element. + for (int i = 0; i < 3; i++) + tiles[i].CalculateDimensions(GetTexture(tiles[i].texture_index)); + + const RenderBox render_box = element->GetRenderBox(paint_area); + const Vector2f offset = render_box.GetFillOffset(); + const Vector2f size = render_box.GetFillSize(); + + Vector2f left_dimensions = tiles[LEFT].GetNaturalDimensions(element); + Vector2f right_dimensions = tiles[RIGHT].GetNaturalDimensions(element); + Vector2f centre_dimensions = tiles[CENTRE].GetNaturalDimensions(element); + + // Scale the tile sizes by the height scale. + ScaleTileDimensions(left_dimensions, size.y, Axis::Vertical); + ScaleTileDimensions(right_dimensions, size.y, Axis::Vertical); + ScaleTileDimensions(centre_dimensions, size.y, Axis::Vertical); + + // Round the outer tile widths now so that we don't get gaps when rounding again in GenerateGeometry. + left_dimensions.x = Math::Round(left_dimensions.x); + right_dimensions.x = Math::Round(right_dimensions.x); + + // Shrink the x-sizes on the left and right tiles if necessary. + if (size.x < left_dimensions.x + right_dimensions.x) + { + float minimum_width = left_dimensions.x + right_dimensions.x; + left_dimensions.x = size.x * (left_dimensions.x / minimum_width); + right_dimensions.x = size.x * (right_dimensions.x / minimum_width); + } + + const ComputedValues& computed = element->GetComputedValues(); + Mesh mesh[COUNT]; + + tiles[LEFT].GenerateGeometry(mesh[tiles[LEFT].texture_index], computed, offset, left_dimensions, left_dimensions); + + tiles[CENTRE].GenerateGeometry(mesh[tiles[CENTRE].texture_index], computed, offset + Vector2f(left_dimensions.x, 0), + Vector2f(size.x - (left_dimensions.x + right_dimensions.x), centre_dimensions.y), centre_dimensions); + + tiles[RIGHT].GenerateGeometry(mesh[tiles[RIGHT].texture_index], computed, offset + Vector2f(size.x - right_dimensions.x, 0), right_dimensions, + right_dimensions); + + const int num_textures = GetNumTextures(); + DecoratorTiledHorizontalData* data = new DecoratorTiledHorizontalData(num_textures); + RenderManager* render_manager = element->GetRenderManager(); + + // Set the mesh and textures on the geometry. + for (int i = 0; i < num_textures; i++) + data->geometry[i] = render_manager->MakeGeometry(std::move(mesh[i])); + + return reinterpret_cast(data); +} + +void DecoratorTiledHorizontal::ReleaseElementData(DecoratorDataHandle element_data) const +{ + delete reinterpret_cast(element_data); +} + +void DecoratorTiledHorizontal::RenderElement(Element* element, DecoratorDataHandle element_data) const +{ + Vector2f translation = element->GetAbsoluteOffset(BoxArea::Border); + DecoratorTiledHorizontalData* data = reinterpret_cast(element_data); + + for (int i = 0; i < data->num_textures; i++) + data->geometry[i].Render(translation, GetTexture(i)); +} + +DecoratorTiledHorizontalInstancer::DecoratorTiledHorizontalInstancer() : DecoratorTiledInstancer(3) +{ + RegisterTileProperty("left-image"); + RegisterTileProperty("right-image"); + RegisterTileProperty("center-image"); + RegisterShorthand("decorator", "left-image, center-image, right-image", ShorthandType::RecursiveCommaSeparated); +} + +DecoratorTiledHorizontalInstancer::~DecoratorTiledHorizontalInstancer() {} + +SharedPtr DecoratorTiledHorizontalInstancer::InstanceDecorator(const String& /*name*/, const PropertyDictionary& properties, + const DecoratorInstancerInterface& instancer_interface) +{ + constexpr size_t num_tiles = 3; + + DecoratorTiled::Tile tiles[num_tiles]; + Texture textures[num_tiles]; + + if (!GetTileProperties(tiles, textures, num_tiles, properties, instancer_interface)) + return nullptr; + + auto decorator = MakeShared(); + if (!decorator->Initialise(tiles, textures)) + return nullptr; + + return decorator; +} +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorTiledHorizontal.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorTiledHorizontal.h new file mode 100644 index 00000000..cce55ee4 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorTiledHorizontal.h @@ -0,0 +1,42 @@ +#pragma once + +#include "DecoratorTiled.h" + +namespace Rml { + +class DecoratorTiledHorizontal : public DecoratorTiled { +public: + DecoratorTiledHorizontal(); + virtual ~DecoratorTiledHorizontal(); + + /// Initialises the tiles for the decorator. + /// @param[in] tiles The declaration for all three tiles. + /// @param[in] textures The textures for the three tiles. + /// @return True if all the tiles and textures are properly specified. + bool Initialise(const Tile* tiles, const Texture* textures); + + /// Called on a decorator to generate any required per-element data for a newly decorated element. + DecoratorDataHandle GenerateElementData(Element* element, BoxArea paint_area) const override; + /// Called to release element data generated by this decorator. + void ReleaseElementData(DecoratorDataHandle element_data) const override; + + /// Called to render the decorator on an element. + void RenderElement(Element* element, DecoratorDataHandle element_data) const override; + +private: + enum { LEFT, RIGHT, CENTRE, COUNT }; + + Tile tiles[COUNT]; +}; + +class DecoratorTiledHorizontalInstancer : public DecoratorTiledInstancer { +public: + DecoratorTiledHorizontalInstancer(); + ~DecoratorTiledHorizontalInstancer(); + + /// Instances a horizontal decorator. + SharedPtr InstanceDecorator(const String& name, const PropertyDictionary& properties, + const DecoratorInstancerInterface& instancer_interface) override; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorTiledImage.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorTiledImage.cpp new file mode 100644 index 00000000..fd1e6949 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorTiledImage.cpp @@ -0,0 +1,76 @@ +#include "DecoratorTiledImage.h" +#include "../../Include/RmlUi/Core/Element.h" +#include "../../Include/RmlUi/Core/Geometry.h" +#include "../../Include/RmlUi/Core/MeshUtilities.h" +#include "../../Include/RmlUi/Core/RenderManager.h" + +namespace Rml { + +DecoratorTiledImage::DecoratorTiledImage() {} + +DecoratorTiledImage::~DecoratorTiledImage() {} + +bool DecoratorTiledImage::Initialise(const Tile& _tile, Texture _texture) +{ + tile = _tile; + tile.texture_index = AddTexture(_texture); + return (tile.texture_index >= 0); +} + +DecoratorDataHandle DecoratorTiledImage::GenerateElementData(Element* element, BoxArea paint_area) const +{ + // Calculate the tile's dimensions for this element. + tile.CalculateDimensions(GetTexture()); + + const ComputedValues& computed = element->GetComputedValues(); + + const RenderBox render_box = element->GetRenderBox(paint_area); + const Vector2f offset = render_box.GetFillOffset(); + const Vector2f size = render_box.GetFillSize(); + + // Generate the geometry for the tile. + Mesh mesh; + tile.GenerateGeometry(mesh, computed, offset, size, tile.GetNaturalDimensions(element)); + + Geometry* data = new Geometry(element->GetRenderManager()->MakeGeometry(std::move(mesh))); + + return reinterpret_cast(data); +} + +void DecoratorTiledImage::ReleaseElementData(DecoratorDataHandle element_data) const +{ + delete reinterpret_cast(element_data); +} + +void DecoratorTiledImage::RenderElement(Element* element, DecoratorDataHandle element_data) const +{ + Geometry* data = reinterpret_cast(element_data); + data->Render(element->GetAbsoluteOffset(BoxArea::Border), GetTexture()); +} + +DecoratorTiledImageInstancer::DecoratorTiledImageInstancer() : DecoratorTiledInstancer(1) +{ + RegisterTileProperty("image", true); + RegisterShorthand("decorator", "image", ShorthandType::RecursiveRepeat); +} + +DecoratorTiledImageInstancer::~DecoratorTiledImageInstancer() {} + +SharedPtr DecoratorTiledImageInstancer::InstanceDecorator(const String& /*name*/, const PropertyDictionary& properties, + const DecoratorInstancerInterface& instancer_interface) +{ + DecoratorTiled::Tile tile; + Texture texture; + + if (!GetTileProperties(&tile, &texture, 1, properties, instancer_interface)) + return nullptr; + + auto decorator = MakeShared(); + + if (!decorator->Initialise(tile, texture)) + return nullptr; + + return decorator; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorTiledImage.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorTiledImage.h new file mode 100644 index 00000000..cf1042e1 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorTiledImage.h @@ -0,0 +1,40 @@ +#pragma once + +#include "DecoratorTiled.h" + +namespace Rml { + +class DecoratorTiledImage : public DecoratorTiled { +public: + DecoratorTiledImage(); + virtual ~DecoratorTiledImage(); + + /// Initialises the tile for the decorator. + /// @param tile[in] The declaration for the tile. + /// @param texture[in] The texture to apply to the tile. + /// @return True if the image is valid, false otherwise. + bool Initialise(const Tile& tile, Texture texture); + + /// Called on a decorator to generate any required per-element data for a newly decorated element. + DecoratorDataHandle GenerateElementData(Element* element, BoxArea paint_area) const override; + /// Called to release element data generated by this decorator. + void ReleaseElementData(DecoratorDataHandle element_data) const override; + + /// Called to render the decorator on an element. + void RenderElement(Element* element, DecoratorDataHandle element_data) const override; + +private: + Tile tile; +}; + +class DecoratorTiledImageInstancer : public DecoratorTiledInstancer { +public: + DecoratorTiledImageInstancer(); + ~DecoratorTiledImageInstancer(); + + /// Instances an image decorator. + SharedPtr InstanceDecorator(const String& name, const PropertyDictionary& properties, + const DecoratorInstancerInterface& instancer_interface) override; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorTiledVertical.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorTiledVertical.cpp new file mode 100644 index 00000000..a205b150 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorTiledVertical.cpp @@ -0,0 +1,147 @@ +#include "DecoratorTiledVertical.h" +#include "../../Include/RmlUi/Core/Element.h" +#include "../../Include/RmlUi/Core/Geometry.h" +#include "../../Include/RmlUi/Core/MeshUtilities.h" +#include "../../Include/RmlUi/Core/RenderManager.h" +#include "../../Include/RmlUi/Core/Texture.h" + +namespace Rml { + +struct DecoratorTiledVerticalData { + DecoratorTiledVerticalData(int num_textures) : num_textures(num_textures) { geometry = new Geometry[num_textures]; } + + ~DecoratorTiledVerticalData() { delete[] geometry; } + + const int num_textures; + Geometry* geometry; +}; + +DecoratorTiledVertical::DecoratorTiledVertical() {} + +DecoratorTiledVertical::~DecoratorTiledVertical() {} + +bool DecoratorTiledVertical::Initialise(const Tile* _tiles, const Texture* _textures) +{ + // Load the textures. + for (int i = 0; i < 3; i++) + { + tiles[i] = _tiles[i]; + tiles[i].texture_index = AddTexture(_textures[i]); + } + + // If only one side of the decorator has been configured, then mirror the texture for the other side. + if (tiles[TOP].texture_index == -1 && tiles[BOTTOM].texture_index > -1) + { + tiles[TOP] = tiles[BOTTOM]; + tiles[TOP].orientation = FLIP_HORIZONTAL; + } + else if (tiles[BOTTOM].texture_index == -1 && tiles[TOP].texture_index > -1) + { + tiles[BOTTOM] = tiles[TOP]; + tiles[BOTTOM].orientation = FLIP_HORIZONTAL; + } + else if (tiles[TOP].texture_index == -1 && tiles[BOTTOM].texture_index == -1) + return false; + + if (tiles[CENTRE].texture_index == -1) + return false; + + return true; +} + +DecoratorDataHandle DecoratorTiledVertical::GenerateElementData(Element* element, BoxArea paint_area) const +{ + // Initialise the tile for this element. + for (int i = 0; i < 3; i++) + tiles[i].CalculateDimensions(GetTexture(tiles[i].texture_index)); + + const RenderBox render_box = element->GetRenderBox(paint_area); + const Vector2f offset = render_box.GetFillOffset(); + const Vector2f size = render_box.GetFillSize(); + + Vector2f top_dimensions = tiles[TOP].GetNaturalDimensions(element); + Vector2f bottom_dimensions = tiles[BOTTOM].GetNaturalDimensions(element); + Vector2f centre_dimensions = tiles[CENTRE].GetNaturalDimensions(element); + + // Scale the tile sizes by the width scale. + ScaleTileDimensions(top_dimensions, size.x, Axis::Horizontal); + ScaleTileDimensions(bottom_dimensions, size.x, Axis::Horizontal); + ScaleTileDimensions(centre_dimensions, size.x, Axis::Horizontal); + + // Round the outer tile heights now so that we don't get gaps when rounding again in GenerateGeometry. + top_dimensions.y = Math::Round(top_dimensions.y); + bottom_dimensions.y = Math::Round(bottom_dimensions.y); + + // Shrink the y-sizes on the left and right tiles if necessary. + if (size.y < top_dimensions.y + bottom_dimensions.y) + { + float minimum_height = top_dimensions.y + bottom_dimensions.y; + top_dimensions.y = size.y * (top_dimensions.y / minimum_height); + bottom_dimensions.y = size.y * (bottom_dimensions.y / minimum_height); + } + + const ComputedValues& computed = element->GetComputedValues(); + Mesh mesh[COUNT]; + + tiles[TOP].GenerateGeometry(mesh[tiles[TOP].texture_index], computed, offset, top_dimensions, top_dimensions); + + tiles[CENTRE].GenerateGeometry(mesh[tiles[CENTRE].texture_index], computed, offset + Vector2f(0, top_dimensions.y), + Vector2f(centre_dimensions.x, size.y - (top_dimensions.y + bottom_dimensions.y)), centre_dimensions); + + tiles[BOTTOM].GenerateGeometry(mesh[tiles[BOTTOM].texture_index], computed, offset + Vector2f(0, size.y - bottom_dimensions.y), bottom_dimensions, + bottom_dimensions); + + const int num_textures = GetNumTextures(); + DecoratorTiledVerticalData* data = new DecoratorTiledVerticalData(num_textures); + + // Set the mesh and textures on the geometry. + RenderManager* render_manager = element->GetRenderManager(); + for (int i = 0; i < num_textures; i++) + data->geometry[i] = render_manager->MakeGeometry(std::move(mesh[i])); + + return reinterpret_cast(data); +} + +void DecoratorTiledVertical::ReleaseElementData(DecoratorDataHandle element_data) const +{ + delete reinterpret_cast(element_data); +} + +void DecoratorTiledVertical::RenderElement(Element* element, DecoratorDataHandle element_data) const +{ + Vector2f translation = element->GetAbsoluteOffset(BoxArea::Padding).Round(); + DecoratorTiledVerticalData* data = reinterpret_cast(element_data); + + for (int i = 0; i < data->num_textures; i++) + data->geometry[i].Render(translation, GetTexture(i)); +} + +DecoratorTiledVerticalInstancer::DecoratorTiledVerticalInstancer() : DecoratorTiledInstancer(3) +{ + RegisterTileProperty("top-image"); + RegisterTileProperty("bottom-image"); + RegisterTileProperty("center-image"); + RegisterShorthand("decorator", "top-image, center-image, bottom-image", ShorthandType::RecursiveCommaSeparated); +} + +DecoratorTiledVerticalInstancer::~DecoratorTiledVerticalInstancer() {} + +SharedPtr DecoratorTiledVerticalInstancer::InstanceDecorator(const String& /*name*/, const PropertyDictionary& properties, + const DecoratorInstancerInterface& instancer_interface) +{ + constexpr size_t num_tiles = 3; + + DecoratorTiled::Tile tiles[num_tiles]; + Texture textures[num_tiles]; + + if (!GetTileProperties(tiles, textures, num_tiles, properties, instancer_interface)) + return nullptr; + + auto decorator = MakeShared(); + if (!decorator->Initialise(tiles, textures)) + return nullptr; + + return decorator; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorTiledVertical.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorTiledVertical.h new file mode 100644 index 00000000..84b67663 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorTiledVertical.h @@ -0,0 +1,42 @@ +#pragma once + +#include "DecoratorTiled.h" + +namespace Rml { + +class DecoratorTiledVertical : public DecoratorTiled { +public: + DecoratorTiledVertical(); + virtual ~DecoratorTiledVertical(); + + /// Initialises the tiles for the decorator. + /// @param[in] tiles The declaration for all three tiles. + /// @param[in] textures The textures for the three tiles. + /// @return True if all the tiles and textures are properly specified. + bool Initialise(const Tile* tiles, const Texture* textures); + + /// Called on a decorator to generate any required per-element data for a newly decorated element. + DecoratorDataHandle GenerateElementData(Element* element, BoxArea paint_area) const override; + /// Called to release element data generated by this decorator. + void ReleaseElementData(DecoratorDataHandle element_data) const override; + + /// Called to render the decorator on an element. + void RenderElement(Element* element, DecoratorDataHandle element_data) const override; + +private: + enum { TOP, BOTTOM, CENTRE, COUNT }; + + Tile tiles[COUNT]; +}; + +class DecoratorTiledVerticalInstancer : public DecoratorTiledInstancer { +public: + DecoratorTiledVerticalInstancer(); + ~DecoratorTiledVerticalInstancer(); + + /// Instances a vertical decorator. + SharedPtr InstanceDecorator(const String& name, const PropertyDictionary& properties, + const DecoratorInstancerInterface& instancer_interface) override; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorUtilities.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorUtilities.cpp new file mode 100644 index 00000000..fff2f9f2 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorUtilities.cpp @@ -0,0 +1,31 @@ +#include "DecoratorUtilities.h" +#include "../../Include/RmlUi/Core/Property.h" + +namespace Rml { + +Vector2Numeric ComputePosition(Array p_position) +{ + Vector2Numeric position; + for (int dimension = 0; dimension < 2; dimension++) + { + NumericValue& value = position[dimension]; + const Property& property = *p_position[dimension]; + if (property.unit == Unit::KEYWORD) + { + enum { TOP_LEFT, CENTER, BOTTOM_RIGHT }; + switch (property.Get()) + { + case TOP_LEFT: value = NumericValue(0.f, Unit::PERCENT); break; + case CENTER: value = NumericValue(50.f, Unit::PERCENT); break; + case BOTTOM_RIGHT: value = NumericValue(100.f, Unit::PERCENT); break; + } + } + else + { + value = property.GetNumericValue(); + } + } + return position; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorUtilities.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorUtilities.h new file mode 100644 index 00000000..5888d839 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DecoratorUtilities.h @@ -0,0 +1,13 @@ +#pragma once + +#include "../../Include/RmlUi/Core/NumericValue.h" +#include "../../Include/RmlUi/Core/Types.h" + +namespace Rml { + +using Vector2Numeric = Vector2; + +// Compute a 2d-position property value into a percentage-length vector. +Vector2Numeric ComputePosition(Array p_position); + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DocumentHeader.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DocumentHeader.cpp new file mode 100644 index 00000000..64487e11 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DocumentHeader.cpp @@ -0,0 +1,41 @@ +#include "DocumentHeader.h" +#include "../../Include/RmlUi/Core/Core.h" +#include "../../Include/RmlUi/Core/StringUtilities.h" +#include "../../Include/RmlUi/Core/SystemInterface.h" +#include "XMLParseTools.h" + +namespace Rml { + +void DocumentHeader::MergeHeader(const DocumentHeader& header) +{ + // Copy the title across if ours is empty + if (title.empty()) + title = header.title; + // Copy the url across if ours is empty + if (source.empty()) + source = header.source; + + // Combine external data, keeping relative paths + MergePaths(template_resources, header.template_resources, header.source); + MergeResources(rcss, header.rcss); + MergeResources(scripts, header.scripts); +} + +void DocumentHeader::MergePaths(StringList& target, const StringList& source, const String& source_path) +{ + for (size_t i = 0; i < source.size(); i++) + { + String joined_path; + ::Rml::GetSystemInterface()->JoinPath(joined_path, StringUtilities::Replace(source_path, '|', ':'), + StringUtilities::Replace(source[i], '|', ':')); + + target.push_back(StringUtilities::Replace(joined_path, ':', '|')); + } +} + +void DocumentHeader::MergeResources(ResourceList& target, const ResourceList& source) +{ + target.insert(target.end(), source.begin(), source.end()); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DocumentHeader.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DocumentHeader.h new file mode 100644 index 00000000..f352f2b1 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/DocumentHeader.h @@ -0,0 +1,48 @@ +#pragma once + +#include "../../Include/RmlUi/Core/Types.h" + +namespace Rml { + +using LineNumberList = Vector; + +/** + The document header struct contains the + header details gathered from an XML document parse. + */ + +class DocumentHeader { +public: + /// Path and filename this document was loaded from + String source; + /// The title of the document + String title; + /// A list of template resources that can used while parsing the document + StringList template_resources; + + struct Resource { + String path; // Content path for inline resources, source path for external resources. + String content; // Only set for inline resources. + bool is_inline = false; + int line = 0; // Only set for inline resources. + }; + using ResourceList = Vector; + + /// RCSS definitions + ResourceList rcss; + + /// script source + ResourceList scripts; + + /// Merges the specified header with this one + /// @param header Header to merge + void MergeHeader(const DocumentHeader& header); + + /// Merges paths from one string list to another, preserving the base_path + void MergePaths(StringList& target, const StringList& source, const String& base_path); + + /// Merges resources + void MergeResources(ResourceList& target, const ResourceList& source); +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/EffectSpecification.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/EffectSpecification.cpp new file mode 100644 index 00000000..3db1a231 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/EffectSpecification.cpp @@ -0,0 +1,25 @@ +#include "../../Include/RmlUi/Core/EffectSpecification.h" +#include "../../Include/RmlUi/Core/PropertyDefinition.h" + +namespace Rml { + +EffectSpecification::EffectSpecification() : properties(10, 10) {} + +EffectSpecification::~EffectSpecification() {} + +const PropertySpecification& EffectSpecification::GetPropertySpecification() const +{ + return properties; +} + +PropertyDefinition& EffectSpecification::RegisterProperty(const String& property_name, const String& default_value) +{ + return properties.RegisterProperty(property_name, default_value, false, false); +} + +ShorthandId EffectSpecification::RegisterShorthand(const String& shorthand_name, const String& property_names, ShorthandType type) +{ + return properties.RegisterShorthand(shorthand_name, property_names, type); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Element.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Element.cpp new file mode 100644 index 00000000..e459b32c --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Element.cpp @@ -0,0 +1,3018 @@ +#include "../../Include/RmlUi/Core/Element.h" +#include "../../Include/RmlUi/Core/Context.h" +#include "../../Include/RmlUi/Core/Core.h" +#include "../../Include/RmlUi/Core/Dictionary.h" +#include "../../Include/RmlUi/Core/ElementDocument.h" +#include "../../Include/RmlUi/Core/ElementInstancer.h" +#include "../../Include/RmlUi/Core/ElementScroll.h" +#include "../../Include/RmlUi/Core/ElementUtilities.h" +#include "../../Include/RmlUi/Core/Factory.h" +#include "../../Include/RmlUi/Core/Math.h" +#include "../../Include/RmlUi/Core/Profiling.h" +#include "../../Include/RmlUi/Core/PropertiesIteratorView.h" +#include "../../Include/RmlUi/Core/PropertyDefinition.h" +#include "../../Include/RmlUi/Core/PropertyIdSet.h" +#include "../../Include/RmlUi/Core/StyleSheet.h" +#include "../../Include/RmlUi/Core/StyleSheetSpecification.h" +#include "../../Include/RmlUi/Core/TransformPrimitive.h" +#include "Clock.h" +#include "ComputeProperty.h" +#include "DataModel.h" +#include "ElementAnimation.h" +#include "ElementBackgroundBorder.h" +#include "ElementDefinition.h" +#include "ElementEffects.h" +#include "ElementMeta.h" +#include "ElementStyle.h" +#include "EventDispatcher.h" +#include "EventSpecification.h" +#include "Layout/LayoutEngine.h" +#include "PluginRegistry.h" +#include "Pool.h" +#include "PropertiesIterator.h" +#include "StyleSheetNode.h" +#include "StyleSheetParser.h" +#include "TransformState.h" +#include "TransformUtilities.h" +#include "XMLParseTools.h" +#include +#include + +namespace Rml { + +// Determines how many levels up in the hierarchy the OnChildAdd and OnChildRemove are called (starting at the child itself) +static constexpr int ChildNotifyLevels = 2; + +// Helper function to select scroll offset delta +static float GetScrollOffsetDelta(ScrollAlignment alignment, float begin_offset, float end_offset) +{ + switch (alignment) + { + case ScrollAlignment::Start: return begin_offset; + case ScrollAlignment::Center: return (begin_offset + end_offset) / 2.0f; + case ScrollAlignment::End: return end_offset; + case ScrollAlignment::Nearest: + if (begin_offset >= 0.f && end_offset <= 0.f) + return 0.f; // Element is already visible, don't scroll + else if (begin_offset < 0.f && end_offset < 0.f) + return Math::Max(begin_offset, end_offset); + else if (begin_offset > 0.f && end_offset > 0.f) + return Math::Min(begin_offset, end_offset); + return 0.f; // Shouldn't happen + case ScrollAlignment::Adaptive: + if (begin_offset >= 0.f && end_offset <= 0.f) + return 0.f; // Element is already visible, don't scroll + return (begin_offset + end_offset) / 2.0f; + } + return 0.f; +} + +Element::Element(const String& tag) : + local_stacking_context(false), local_stacking_context_forced(false), stacking_context_dirty(false), computed_values_are_default_initialized(true), + visible(true), offset_fixed(false), absolute_offset_dirty(true), rounded_main_padding_size_dirty(true), dirty_definition(false), + dirty_child_definitions(false), dirty_animation(false), dirty_transition(false), dirty_transform(false), dirty_perspective(false), tag(tag), + relative_offset_base(0, 0), relative_offset_position(0, 0), absolute_offset(0, 0), scroll_offset(0, 0) +{ + RMLUI_ASSERT(tag == StringUtilities::ToLower(tag)); + parent = nullptr; + focus = nullptr; + instancer = nullptr; + owner_document = nullptr; + offset_parent = nullptr; + + clip_area = BoxArea::Padding; + + baseline = 0.0f; + + num_non_dom_children = 0; + + z_index = 0; + + meta = ElementMetaPool::element_meta_pool->pool.AllocateAndConstruct(this); + data_model = nullptr; +} + +Element::~Element() +{ + RMLUI_ASSERT(parent == nullptr); + + PluginRegistry::NotifyElementDestroy(this); + + // A simplified version of RemoveChild() for destruction. + for (ElementPtr& child : children) + { + Element* child_ancestor = child.get(); + for (int i = 0; i <= ChildNotifyLevels && child_ancestor; i++, child_ancestor = child_ancestor->GetParentNode()) + child_ancestor->OnChildRemove(child.get()); + + child->SetParent(nullptr); + } + + children.clear(); + num_non_dom_children = 0; + + ElementMetaPool::element_meta_pool->pool.DestroyAndDeallocate(meta); +} + +void Element::Update(float dp_ratio, Vector2f vp_dimensions) +{ +#ifdef RMLUI_TRACY_PROFILING + auto name = GetAddress(false, false); + RMLUI_ZoneScoped; + RMLUI_ZoneText(name.c_str(), name.size()); +#endif + + OnUpdate(); + + HandleTransitionProperty(); + HandleAnimationProperty(); + AdvanceAnimations(); + + meta->scroll.Update(); + + UpdateProperties(dp_ratio, vp_dimensions); + + // Do en extra pass over the animations and properties if the 'animation' property was just changed. + if (dirty_animation) + { + HandleAnimationProperty(); + AdvanceAnimations(); + UpdateProperties(dp_ratio, vp_dimensions); + } + + meta->effects.InstanceEffects(); + + for (size_t i = 0; i < children.size(); i++) + children[i]->Update(dp_ratio, vp_dimensions); + + if (!animations.empty() && IsVisible(true)) + { + if (Context* ctx = GetContext()) + ctx->RequestNextUpdate(0); + } +} + +void Element::UpdateProperties(const float dp_ratio, const Vector2f vp_dimensions) +{ + UpdateDefinition(); + + if (meta->style.AnyPropertiesDirty()) + { + const ComputedValues* parent_values = parent ? &parent->GetComputedValues() : nullptr; + const ComputedValues* document_values = owner_document ? &owner_document->GetComputedValues() : nullptr; + + // Compute values and clear dirty properties + PropertyIdSet dirty_properties = meta->style.ComputeValues(meta->computed_values, parent_values, document_values, + computed_values_are_default_initialized, dp_ratio, vp_dimensions); + + computed_values_are_default_initialized = false; + + // Computed values are just calculated and can safely be used in OnPropertyChange. + // However, new properties set during this call will not be available until the next update loop. + if (!dirty_properties.Empty()) + OnPropertyChange(dirty_properties); + } +} + +void Element::Render() +{ +#ifdef RMLUI_TRACY_PROFILING + auto name = GetAddress(false, false); + RMLUI_ZoneScoped; + RMLUI_ZoneText(name.c_str(), name.size()); +#endif + + UpdateAbsoluteOffsetAndRenderBoxData(); + + if (stacking_context_dirty) + BuildLocalStackingContext(); + + UpdateTransformState(); + ElementUtilities::ApplyTransform(*this); + + meta->effects.RenderEffects(RenderStage::Enter); + + if (ElementUtilities::SetClippingRegion(this)) + { + meta->background_border.Render(this); + meta->effects.RenderEffects(RenderStage::Decoration); + + { + RMLUI_ZoneScopedNC("OnRender", 0x228B22); + OnRender(); + } + } + + for (Element* element : stacking_context) + element->Render(); + + ElementUtilities::ApplyTransform(*this); + meta->effects.RenderEffects(RenderStage::Exit); +} + +ElementPtr Element::Clone() const +{ + ElementPtr clone; + + if (instancer) + { + clone = instancer->InstanceElement(nullptr, GetTagName(), attributes); + if (clone) + clone->SetInstancer(instancer); + } + else + clone = Factory::InstanceElement(nullptr, GetTagName(), GetTagName(), attributes); + + if (clone) + { + // Copy over the attributes. The 'style' and 'class' attributes are skipped because inline styles and class names are copied manually below. + // This is necessary in case any properties or classes have been set manually, in which case the 'style' and 'class' attributes are out of + // sync with the used style and active classes. + ElementAttributes clone_attributes = attributes; + clone_attributes.erase("style"); + clone_attributes.erase("class"); + clone->SetAttributes(clone_attributes); + + for (auto& id_property : GetStyle()->GetLocalStyleProperties()) + clone->SetProperty(id_property.first, id_property.second); + + clone->GetStyle()->SetClassNames(GetStyle()->GetClassNames()); + + String inner_rml; + GetInnerRML(inner_rml); + + clone->SetInnerRML(inner_rml); + } + + return clone; +} + +void Element::SetClass(const String& class_name, bool activate) +{ + if (meta->style.SetClass(class_name, activate)) + DirtyDefinition(DirtyNodes::SelfAndSiblings); +} + +bool Element::IsClassSet(const String& class_name) const +{ + return meta->style.IsClassSet(class_name); +} + +void Element::SetClassNames(const String& class_names) +{ + SetAttribute("class", class_names); +} + +String Element::GetClassNames() const +{ + return meta->style.GetClassNames(); +} + +const StyleSheet* Element::GetStyleSheet() const +{ + if (ElementDocument* document = GetOwnerDocument()) + return document->GetStyleSheet(); + return nullptr; +} + +String Element::GetAddress(bool include_pseudo_classes, bool include_parents) const +{ + // Add the tag name onto the address. + String address(tag); + + // Add the ID if we have one. + if (!id.empty()) + { + address += "#"; + address += id; + } + + String classes = meta->style.GetClassNames(); + if (!classes.empty()) + { + classes = StringUtilities::Replace(classes, ' ', '.'); + address += "."; + address += classes; + } + + if (include_pseudo_classes) + { + const PseudoClassMap& pseudo_classes = meta->style.GetActivePseudoClasses(); + for (auto& pseudo_class : pseudo_classes) + { + address += ":"; + address += pseudo_class.first; + } + } + + if (include_parents && parent) + { + address += " < "; + return address + parent->GetAddress(include_pseudo_classes, true); + } + else + return address; +} + +void Element::SetOffset(Vector2f offset, Element* _offset_parent, bool _offset_fixed) +{ + _offset_fixed |= GetPosition() == Style::Position::Fixed; + + // If our offset has definitely changed, or any of our parenting has, then these are set and + // updated based on our left / right / top / bottom properties. + if (relative_offset_base != offset || offset_parent != _offset_parent || offset_fixed != _offset_fixed) + { + relative_offset_base = offset; + offset_fixed = _offset_fixed; + offset_parent = _offset_parent; + UpdateOffset(); + DirtyAbsoluteOffset(); + } + + // Otherwise, our offset is updated in case left / right / top / bottom will have an impact on + // our final position, and our children are dirtied if they do. + else + { + const Vector2f old_base = relative_offset_base; + const Vector2f old_position = relative_offset_position; + + UpdateOffset(); + + if (old_base != relative_offset_base || old_position != relative_offset_position) + DirtyAbsoluteOffset(); + } +} + +Vector2f Element::GetRelativeOffset(BoxArea area) +{ + return relative_offset_base + relative_offset_position + GetBox().GetPosition(area); +} + +Vector2f Element::GetAbsoluteOffset(BoxArea area) +{ + UpdateAbsoluteOffsetAndRenderBoxData(); + return area == BoxArea::Border ? absolute_offset : absolute_offset + GetBox().GetPosition(area); +} + +void Element::UpdateAbsoluteOffsetAndRenderBoxData() +{ + if (absolute_offset_dirty || rounded_main_padding_size_dirty) + { + absolute_offset_dirty = false; + rounded_main_padding_size_dirty = false; + + Vector2f offset_from_ancestors; + if (offset_parent) + offset_from_ancestors = offset_parent->GetAbsoluteOffset(BoxArea::Border); + + if (!offset_fixed) + { + // Add any parent scrolling onto our position as well. + if (offset_parent) + offset_from_ancestors -= offset_parent->scroll_offset; + + // Finally, there may be relatively positioned elements between ourself and our containing block, add their relative offsets as well. + for (Element* ancestor = parent; ancestor && ancestor != offset_parent; ancestor = ancestor->parent) + offset_from_ancestors += ancestor->relative_offset_position; + } + + const Vector2f relative_offset = relative_offset_base + relative_offset_position; + absolute_offset = relative_offset + offset_from_ancestors; + + // Next, we find the rounded size of the box so that elements can be placed border-to-border next to each other + // without any gaps. To achieve this, we have to adjust their rounded/rendered sizes based on their position, in + // such a way that the bottom-right of this element exactly matches the top-left of the next element. The order + // of floating-point operations matter here, we want to replicate the operations in the layout engine as close + // as possible to avoid any gaps. + const Vector2f main_padding_size = main_box.GetSize(BoxArea::Padding); + const Vector2f bottom_right_absolute_offset = (relative_offset + main_padding_size) + offset_from_ancestors; + const Vector2f new_rounded_main_padding_size = bottom_right_absolute_offset.Round() - absolute_offset.Round(); + if (new_rounded_main_padding_size != rounded_main_padding_size) + { + rounded_main_padding_size = new_rounded_main_padding_size; + meta->background_border.DirtyBackground(); + meta->background_border.DirtyBorder(); + meta->effects.DirtyEffectsData(); + } + } +} + +void Element::SetClipArea(BoxArea _clip_area) +{ + clip_area = _clip_area; +} + +BoxArea Element::GetClipArea() const +{ + return clip_area; +} + +void Element::SetScrollableOverflowRectangle(Vector2f _scrollable_overflow_rectangle, bool clamp_scroll_offset) +{ + if (scrollable_overflow_rectangle != _scrollable_overflow_rectangle) + { + scrollable_overflow_rectangle = _scrollable_overflow_rectangle; + if (clamp_scroll_offset) + ClampScrollOffset(); + } +} + +void Element::SetBox(const Box& box) +{ + if (box != main_box || additional_boxes.size() > 0) + { +#ifdef RMLUI_DEBUG + for (const BoxEdge edge : {BoxEdge::Top, BoxEdge::Right, BoxEdge::Bottom, BoxEdge::Left}) + { + const float border_width = box.GetEdge(BoxArea::Border, edge); + if (border_width != Math::Round(border_width)) + Log::Message(Log::LT_WARNING, "Expected integer border width but got %g px on element: %s", border_width, GetAddress().c_str()); + } +#endif + + main_box = box; + additional_boxes.clear(); + + OnResize(); + rounded_main_padding_size_dirty = true; + meta->background_border.DirtyBackground(); + meta->background_border.DirtyBorder(); + meta->effects.DirtyEffectsData(); + + if (transform_state) + DirtyTransformState(true, true); + } +} + +void Element::AddBox(const Box& box, Vector2f offset) +{ + additional_boxes.emplace_back(PositionedBox{box, offset}); + OnResize(); + meta->background_border.DirtyBackground(); + meta->background_border.DirtyBorder(); + meta->effects.DirtyEffectsData(); +} + +const Box& Element::GetBox() +{ + return main_box; +} + +const Box& Element::GetBox(int index, Vector2f& offset) +{ + offset = Vector2f(0); + + const int additional_box_index = index - 1; + if (index < 1 || additional_box_index >= (int)additional_boxes.size()) + return main_box; + + offset = additional_boxes[additional_box_index].offset; + return additional_boxes[additional_box_index].box; +} + +RenderBox Element::GetRenderBox(BoxArea fill_area, int index) +{ + RMLUI_ASSERTMSG(fill_area >= BoxArea::Border && fill_area <= BoxArea::Content, + "Render box can only be generated with fill area of border, padding or content."); + + UpdateAbsoluteOffsetAndRenderBoxData(); + + struct BoxReference { + const Box& box; + Vector2f padding_size; + Vector2f offset; + }; + auto GetBoxAndOffset = [this, index]() { + const int additional_box_index = index - 1; + if (index < 1 || additional_box_index >= (int)additional_boxes.size()) + return BoxReference{main_box, rounded_main_padding_size, {}}; + const PositionedBox& positioned_box = additional_boxes[additional_box_index]; + return BoxReference{positioned_box.box, positioned_box.box.GetSize(BoxArea::Padding), positioned_box.offset.Round()}; + }; + + BoxReference box = GetBoxAndOffset(); + + EdgeSizes edge_sizes = {}; + for (int area = (int)BoxArea::Border; area < (int)fill_area; area++) + { + edge_sizes[0] += box.box.GetEdge(BoxArea(area), BoxEdge::Top); + edge_sizes[1] += box.box.GetEdge(BoxArea(area), BoxEdge::Right); + edge_sizes[2] += box.box.GetEdge(BoxArea(area), BoxEdge::Bottom); + edge_sizes[3] += box.box.GetEdge(BoxArea(area), BoxEdge::Left); + } + Vector2f inner_size; + switch (fill_area) + { + case BoxArea::Border: inner_size = box.padding_size + box.box.GetFrameSize(BoxArea::Border); break; + case BoxArea::Padding: inner_size = box.padding_size; break; + case BoxArea::Content: inner_size = box.padding_size - box.box.GetFrameSize(BoxArea::Padding); break; + case BoxArea::Margin: + case BoxArea::Auto: RMLUI_ERROR; + } + + return RenderBox{inner_size, box.offset, edge_sizes, meta->computed_values.border_radius()}; +} + +int Element::GetNumBoxes() +{ + return 1 + (int)additional_boxes.size(); +} + +float Element::GetBaseline() const +{ + return baseline; +} + +bool Element::GetIntrinsicDimensions(Vector2f& /*dimensions*/, float& /*ratio*/) +{ + return false; +} + +bool Element::IsReplaced() +{ + Vector2f unused_dimensions; + float unused_ratio = 0.f; + return GetIntrinsicDimensions(unused_dimensions, unused_ratio); +} + +bool Element::IsPointWithinElement(const Vector2f point) +{ + const Vector2f position = GetAbsoluteOffset(BoxArea::Border); + + for (int i = 0; i < GetNumBoxes(); ++i) + { + Vector2f box_offset; + const Box& box = GetBox(i, box_offset); + + const Vector2f box_position = position + box_offset; + const Vector2f box_dimensions = box.GetSize(BoxArea::Border); + if (point.x >= box_position.x && point.x <= (box_position.x + box_dimensions.x) && point.y >= box_position.y && + point.y <= (box_position.y + box_dimensions.y)) + { + return true; + } + } + + return false; +} + +bool Element::IsVisible(bool include_ancestors) const +{ + if (!include_ancestors) + return visible; + const Element* element = this; + while (element) + { + if (!element->visible) + return false; + element = element->parent; + } + return true; +} + +float Element::GetZIndex() const +{ + return z_index; +} + +FontFaceHandle Element::GetFontFaceHandle() const +{ + return meta->computed_values.font_face_handle(); +} + +bool Element::SetProperty(const String& name, const String& value) +{ + // The name may be a shorthand giving us multiple underlying properties + PropertyDictionary properties; + if (!StyleSheetSpecification::ParsePropertyDeclaration(properties, name, value)) + { + Log::Message(Log::LT_WARNING, "Syntax error parsing inline property declaration '%s: %s;'.", name.c_str(), value.c_str()); + return false; + } + for (auto& property : properties.GetProperties()) + { + if (!meta->style.SetProperty(property.first, property.second)) + return false; + } + return true; +} + +bool Element::SetProperty(PropertyId id, const Property& property) +{ + return meta->style.SetProperty(id, property); +} + +void Element::RemoveProperty(const String& name) +{ + auto property_id = StyleSheetSpecification::GetPropertyId(name); + if (property_id != PropertyId::Invalid) + meta->style.RemoveProperty(property_id); + else + { + auto shorthand_id = StyleSheetSpecification::GetShorthandId(name); + if (shorthand_id != ShorthandId::Invalid) + { + auto property_id_set = StyleSheetSpecification::GetShorthandUnderlyingProperties(shorthand_id); + for (auto it = property_id_set.begin(); it != property_id_set.end(); ++it) + meta->style.RemoveProperty(*it); + } + } +} + +void Element::RemoveProperty(PropertyId id) +{ + meta->style.RemoveProperty(id); +} + +const Property* Element::GetProperty(const String& name) +{ + return meta->style.GetProperty(StyleSheetSpecification::GetPropertyId(name)); +} + +const Property* Element::GetProperty(PropertyId id) +{ + return meta->style.GetProperty(id); +} + +const Property* Element::GetLocalProperty(const String& name) +{ + return meta->style.GetLocalProperty(StyleSheetSpecification::GetPropertyId(name)); +} + +const Property* Element::GetLocalProperty(PropertyId id) +{ + return meta->style.GetLocalProperty(id); +} + +const PropertyMap& Element::GetLocalStyleProperties() +{ + return meta->style.GetLocalStyleProperties(); +} + +float Element::ResolveLength(NumericValue value) +{ + float result = 0.f; + if (Any(value.unit & Unit::LENGTH)) + result = meta->style.ResolveNumericValue(value, 0.f); + return result; +} + +float Element::ResolveNumericValue(NumericValue value, float base_value) +{ + float result = 0.f; + if (Any(value.unit & Unit::NUMERIC)) + result = meta->style.ResolveNumericValue(value, base_value); + return result; +} + +Vector2f Element::GetContainingBlock() +{ + Vector2f containing_block; + + if (offset_parent) + { + using namespace Style; + Position position_property = GetPosition(); + const Box& parent_box = offset_parent->GetBox(); + + if (position_property == Position::Static || position_property == Position::Relative) + { + containing_block = parent_box.GetSize(); + containing_block.x -= meta->scroll.GetScrollbarSize(ElementScroll::VERTICAL); + containing_block.y -= meta->scroll.GetScrollbarSize(ElementScroll::HORIZONTAL); + } + else if (position_property == Position::Absolute || position_property == Position::Fixed) + { + containing_block = parent_box.GetSize(BoxArea::Padding); + } + } + else if (Context* context = GetContext()) + { + containing_block = Vector2f(context->GetDimensions()); + } + + return containing_block; +} + +Style::Position Element::GetPosition() +{ + return meta->computed_values.position(); +} + +Style::Float Element::GetFloat() +{ + return meta->computed_values.float_(); +} + +Style::Display Element::GetDisplay() +{ + return meta->computed_values.display(); +} + +float Element::GetLineHeight() +{ + return meta->computed_values.line_height().value; +} + +const TransformState* Element::GetTransformState() const noexcept +{ + return transform_state.get(); +} + +bool Element::Project(Vector2f& point) const noexcept +{ + if (!transform_state || !transform_state->GetTransform()) + return true; + + // The input point is in window coordinates. Need to find the projection of the point onto the current element plane, + // taking into account the full transform applied to the element. + + if (const Matrix4f* inv_transform = transform_state->GetInverseTransform()) + { + // Pick two points forming a line segment perpendicular to the window. + Vector4f window_points[2] = {{point.x, point.y, -10, 1}, {point.x, point.y, 10, 1}}; + + // Project them into the local element space. + window_points[0] = *inv_transform * window_points[0]; + window_points[1] = *inv_transform * window_points[1]; + + Vector3f local_points[2] = {window_points[0].PerspectiveDivide(), window_points[1].PerspectiveDivide()}; + + // Construct a ray from the two projected points in the local space of the current element. + // Find the intersection with the z=0 plane to produce our destination point. + Vector3f ray = local_points[1] - local_points[0]; + + // Only continue if we are not close to parallel with the plane. + if (Math::Absolute(ray.z) > 1.0f) + { + // Solving the line equation p = p0 + t*ray for t, knowing that p.z = 0, produces the following. + float t = -local_points[0].z / ray.z; + Vector3f p = local_points[0] + ray * t; + + point = Vector2f(p.x, p.y); + return true; + } + } + + // The transformation matrix is either singular, or the ray is parallel to the element's plane. + return false; +} + +PropertiesIteratorView Element::IterateLocalProperties() const +{ + return PropertiesIteratorView(MakeUnique(meta->style.Iterate())); +} + +void Element::SetPseudoClass(const String& pseudo_class, bool activate) +{ + if (meta->style.SetPseudoClass(pseudo_class, activate, false)) + { + // Include siblings in case of RCSS presence of sibling combinators '+', '~'. + DirtyDefinition(DirtyNodes::SelfAndSiblings); + OnPseudoClassChange(pseudo_class, activate); + } +} + +bool Element::IsPseudoClassSet(const String& pseudo_class) const +{ + return meta->style.IsPseudoClassSet(pseudo_class); +} + +bool Element::ArePseudoClassesSet(const StringList& pseudo_classes) const +{ + for (const String& pseudo_class : pseudo_classes) + { + if (!IsPseudoClassSet(pseudo_class)) + return false; + } + + return true; +} + +StringList Element::GetActivePseudoClasses() const +{ + const PseudoClassMap& pseudo_classes = meta->style.GetActivePseudoClasses(); + StringList names; + names.reserve(pseudo_classes.size()); + for (auto& pseudo_class : pseudo_classes) + { + names.push_back(pseudo_class.first); + } + + return names; +} + +void Element::OverridePseudoClass(Element* element, const String& pseudo_class, bool activate) +{ + RMLUI_ASSERT(element); + element->GetStyle()->SetPseudoClass(pseudo_class, activate, true); +} + +Variant* Element::GetAttribute(const String& name) +{ + return GetIf(attributes, name); +} + +const Variant* Element::GetAttribute(const String& name) const +{ + return GetIf(attributes, name); +} + +bool Element::HasAttribute(const String& name) const +{ + return attributes.find(name) != attributes.end(); +} + +void Element::RemoveAttribute(const String& name) +{ + auto it = attributes.find(name); + if (it != attributes.end()) + { + attributes.erase(it); + + ElementAttributes changed_attributes; + changed_attributes.emplace(name, Variant()); + OnAttributeChange(changed_attributes); + } +} + +Element* Element::GetFocusLeafNode() +{ + // If there isn't a focus, then we are the leaf. + if (!focus) + { + return this; + } + + // Recurse down the tree until we found the leaf focus element + Element* focus_element = focus; + while (focus_element->focus) + focus_element = focus_element->focus; + + return focus_element; +} + +Context* Element::GetContext() const +{ + ElementDocument* document = GetOwnerDocument(); + if (document != nullptr) + return document->GetContext(); + + return nullptr; +} + +RenderManager* Element::GetRenderManager() const +{ + if (Context* context = GetContext()) + return &context->GetRenderManager(); + return nullptr; +} + +void Element::SetAttributes(const ElementAttributes& _attributes) +{ + attributes.reserve(attributes.size() + _attributes.size()); + for (auto& pair : _attributes) + attributes[pair.first] = pair.second; + + OnAttributeChange(_attributes); +} + +int Element::GetNumAttributes() const +{ + return (int)attributes.size(); +} + +const String& Element::GetTagName() const +{ + return tag; +} + +const String& Element::GetId() const +{ + return id; +} + +void Element::SetId(const String& _id) +{ + SetAttribute("id", _id); +} + +float Element::GetAbsoluteLeft() +{ + return GetAbsoluteOffset(BoxArea::Border).x; +} + +float Element::GetAbsoluteTop() +{ + return GetAbsoluteOffset(BoxArea::Border).y; +} + +float Element::GetClientLeft() +{ + return GetBox().GetPosition(BoxArea::Padding).x; +} + +float Element::GetClientTop() +{ + return GetBox().GetPosition(BoxArea::Padding).y; +} + +float Element::GetClientWidth() +{ + return GetBox().GetSize(BoxArea::Padding).x - meta->scroll.GetScrollbarSize(ElementScroll::VERTICAL); +} + +float Element::GetClientHeight() +{ + return GetBox().GetSize(BoxArea::Padding).y - meta->scroll.GetScrollbarSize(ElementScroll::HORIZONTAL); +} + +Element* Element::GetOffsetParent() +{ + return offset_parent; +} + +float Element::GetOffsetLeft() +{ + return relative_offset_base.x + relative_offset_position.x; +} + +float Element::GetOffsetTop() +{ + return relative_offset_base.y + relative_offset_position.y; +} + +float Element::GetOffsetWidth() +{ + return GetBox().GetSize(BoxArea::Border).x; +} + +float Element::GetOffsetHeight() +{ + return GetBox().GetSize(BoxArea::Border).y; +} + +float Element::GetScrollLeft() +{ + return scroll_offset.x; +} + +void Element::SetScrollLeft(float scroll_left) +{ + const float new_offset = Math::Round(Math::Clamp(scroll_left, 0.0f, GetScrollWidth() - GetClientWidth())); + if (new_offset != scroll_offset.x) + { + scroll_offset.x = new_offset; + meta->scroll.UpdateScrollbar(ElementScroll::HORIZONTAL); + DirtyAbsoluteOffset(); + + DispatchEvent(EventId::Scroll, Dictionary()); + } +} + +float Element::GetScrollTop() +{ + return scroll_offset.y; +} + +void Element::SetScrollTop(float scroll_top) +{ + const float new_offset = Math::Round(Math::Clamp(Math::Round(scroll_top), 0.0f, GetScrollHeight() - GetClientHeight())); + if (new_offset != scroll_offset.y) + { + scroll_offset.y = new_offset; + meta->scroll.UpdateScrollbar(ElementScroll::VERTICAL); + DirtyAbsoluteOffset(); + + DispatchEvent(EventId::Scroll, Dictionary()); + } +} + +float Element::GetScrollWidth() +{ + return Math::Max(scrollable_overflow_rectangle.x, GetClientWidth()); +} + +float Element::GetScrollHeight() +{ + return Math::Max(scrollable_overflow_rectangle.y, GetClientHeight()); +} + +ElementStyle* Element::GetStyle() const +{ + return &meta->style; +} + +ElementDocument* Element::GetOwnerDocument() const +{ +#ifdef RMLUI_DEBUG + if (parent && !owner_document) + { + // Since we have a parent but no owner_document, then we must be a 'loose' element -- that is, constructed + // outside of a document and not attached to a child of any element in the hierarchy of a document. + // This check ensures that we didn't just forget to set the owner document. + RMLUI_ASSERT(!parent->GetOwnerDocument()); + } +#endif + + return owner_document; +} + +Element* Element::GetParentNode() const +{ + return parent; +} + +Element* Element::Closest(const String& selectors) const +{ + StyleSheetNode root_node; + StyleSheetNodeListRaw leaf_nodes = StyleSheetParser::ConstructNodes(root_node, selectors); + + if (leaf_nodes.empty()) + { + Log::Message(Log::LT_WARNING, "Query selector '%s' is empty. In element %s", selectors.c_str(), GetAddress().c_str()); + return nullptr; + } + + Element* parent = GetParentNode(); + + while (parent) + { + for (const StyleSheetNode* node : leaf_nodes) + { + if (node->IsApplicable(parent, this)) + { + return parent; + } + } + + parent = parent->GetParentNode(); + } + + return nullptr; +} + +Element* Element::GetNextSibling() const +{ + if (parent == nullptr) + return nullptr; + + for (size_t i = 0; i < parent->children.size() - (parent->num_non_dom_children + 1); i++) + { + if (parent->children[i].get() == this) + return parent->children[i + 1].get(); + } + + return nullptr; +} + +Element* Element::GetPreviousSibling() const +{ + if (parent == nullptr) + return nullptr; + + for (size_t i = 1; i < parent->children.size() - parent->num_non_dom_children; i++) + { + if (parent->children[i].get() == this) + return parent->children[i - 1].get(); + } + + return nullptr; +} + +Element* Element::GetFirstChild() const +{ + if (GetNumChildren() > 0) + return children[0].get(); + + return nullptr; +} + +Element* Element::GetLastChild() const +{ + if (GetNumChildren() > 0) + return (children.end() - (num_non_dom_children + 1))->get(); + + return nullptr; +} + +Element* Element::GetChild(int index) const +{ + if (index < 0 || index >= (int)children.size()) + return nullptr; + + return children[index].get(); +} + +int Element::GetNumChildren(bool include_non_dom_elements) const +{ + return (int)children.size() - (include_non_dom_elements ? 0 : num_non_dom_children); +} + +void Element::GetInnerRML(String& content) const +{ + for (int i = 0; i < GetNumChildren(); i++) + { + children[i]->GetRML(content); + } +} + +String Element::GetInnerRML() const +{ + String result; + GetInnerRML(result); + return result; +} + +void Element::SetInnerRML(const String& rml) +{ + RMLUI_ZoneScopedC(0x6495ED); + + // Remove all DOM children. + while ((int)children.size() > num_non_dom_children) + RemoveChild(children.front().get()); + + if (!rml.empty()) + Factory::InstanceElementText(this, rml); +} + +bool Element::Focus(bool focus_visible) +{ + // Are we allowed focus? + Style::Focus focus_property = meta->computed_values.focus(); + if (focus_property == Style::Focus::None) + return false; + + // Ask our context if we can switch focus. + Context* context = GetContext(); + if (context == nullptr) + return false; + + if (!context->OnFocusChange(this, focus_visible)) + return false; + + // Set this as the end of the focus chain. + focus = nullptr; + + // Update the focus chain up the hierarchy. + Element* element = this; + while (Element* parent = element->GetParentNode()) + { + parent->focus = element; + element = parent; + } + + return true; +} + +void Element::Blur() +{ + if (parent) + { + Context* context = GetContext(); + if (context == nullptr) + return; + + if (context->GetFocusElement() == this) + { + parent->Focus(); + } + else if (parent->focus == this) + { + parent->focus = nullptr; + } + } +} + +void Element::Click() +{ + Context* context = GetContext(); + if (context == nullptr) + return; + + context->GenerateClickEvent(this); +} + +void Element::AddEventListener(const String& event, EventListener* listener, const bool in_capture_phase) +{ + const EventId id = EventSpecificationInterface::GetIdOrInsert(event); + meta->event_dispatcher.AttachEvent(id, listener, in_capture_phase); +} + +void Element::AddEventListener(const EventId id, EventListener* listener, const bool in_capture_phase) +{ + meta->event_dispatcher.AttachEvent(id, listener, in_capture_phase); +} + +void Element::RemoveEventListener(const String& event, EventListener* listener, bool in_capture_phase) +{ + EventId id = EventSpecificationInterface::GetIdOrInsert(event); + meta->event_dispatcher.DetachEvent(id, listener, in_capture_phase); +} + +void Element::RemoveEventListener(EventId id, EventListener* listener, bool in_capture_phase) +{ + meta->event_dispatcher.DetachEvent(id, listener, in_capture_phase); +} + +bool Element::DispatchEvent(const String& type, const Dictionary& parameters) +{ + const EventSpecification& specification = EventSpecificationInterface::GetOrInsert(type); + return EventDispatcher::DispatchEvent(this, specification.id, type, parameters, specification.interruptible, specification.bubbles, + specification.default_action_phase); +} + +bool Element::DispatchEvent(const String& type, const Dictionary& parameters, bool interruptible, bool bubbles) +{ + const EventSpecification& specification = EventSpecificationInterface::GetOrInsert(type); + return EventDispatcher::DispatchEvent(this, specification.id, type, parameters, interruptible, bubbles, specification.default_action_phase); +} + +bool Element::DispatchEvent(EventId id, const Dictionary& parameters) +{ + const EventSpecification& specification = EventSpecificationInterface::Get(id); + return EventDispatcher::DispatchEvent(this, specification.id, specification.type, parameters, specification.interruptible, specification.bubbles, + specification.default_action_phase); +} + +void Element::ScrollIntoView(const ScrollIntoViewOptions options) +{ + const Vector2f size = main_box.GetSize(BoxArea::Border); + ScrollBehavior scroll_behavior = options.behavior; + + for (Element* scroll_parent = parent; scroll_parent; scroll_parent = scroll_parent->GetParentNode()) + { + using Style::Overflow; + const ComputedValues& computed = scroll_parent->GetComputedValues(); + const bool scrollable_box_x = (computed.overflow_x() != Overflow::Visible && computed.overflow_x() != Overflow::Hidden); + const bool scrollable_box_y = (computed.overflow_y() != Overflow::Visible && computed.overflow_y() != Overflow::Hidden); + + const Vector2f parent_scroll_size = {scroll_parent->GetScrollWidth(), scroll_parent->GetScrollHeight()}; + const Vector2f parent_client_size = {scroll_parent->GetClientWidth(), scroll_parent->GetClientHeight()}; + + if ((scrollable_box_x && parent_scroll_size.x > parent_client_size.x) || (scrollable_box_y && parent_scroll_size.y > parent_client_size.y)) + { + const Vector2f relative_offset = scroll_parent->GetAbsoluteOffset(BoxArea::Border) - GetAbsoluteOffset(BoxArea::Border); + + const Vector2f old_scroll_offset = {scroll_parent->GetScrollLeft(), scroll_parent->GetScrollTop()}; + const Vector2f parent_client_offset = {scroll_parent->GetClientLeft(), scroll_parent->GetClientTop()}; + + const Vector2f delta_scroll_offset_start = parent_client_offset - relative_offset; + const Vector2f delta_scroll_offset_end = delta_scroll_offset_start + size - parent_client_size; + + Vector2f scroll_delta = { + scrollable_box_x ? GetScrollOffsetDelta(options.horizontal, delta_scroll_offset_start.x, delta_scroll_offset_end.x) : 0.f, + scrollable_box_y ? GetScrollOffsetDelta(options.vertical, delta_scroll_offset_start.y, delta_scroll_offset_end.y) : 0.f, + }; + + scroll_parent->ScrollTo(old_scroll_offset + scroll_delta, scroll_behavior); + + // Currently, only a single scrollable parent can be smooth scrolled at a time, so any other parents must be instant scrolled. + scroll_behavior = ScrollBehavior::Instant; + } + + if ((scrollable_box_x || scrollable_box_y) && options.parentage == ScrollParentage::Closest) + break; + } +} + +void Element::ScrollIntoView(bool align_with_top) +{ + ScrollIntoViewOptions options; + options.vertical = (align_with_top ? ScrollAlignment::Start : ScrollAlignment::End); + options.horizontal = ScrollAlignment::Nearest; + ScrollIntoView(options); +} + +void Element::ScrollTo(Vector2f offset, ScrollBehavior behavior) +{ + if (behavior != ScrollBehavior::Instant) + { + if (Context* context = GetContext()) + { + context->PerformSmoothscrollOnTarget(this, offset - scroll_offset, behavior); + return; + } + } + + SetScrollLeft(offset.x); + SetScrollTop(offset.y); +} + +Element* Element::AppendChild(ElementPtr child, bool dom_element) +{ + RMLUI_ASSERT(child); + Element* child_ptr = child.get(); + if (dom_element) + children.insert(children.end() - num_non_dom_children, std::move(child)); + else + { + children.push_back(std::move(child)); + num_non_dom_children++; + } + // Set parent just after inserting into children. This allows us to eg. get our previous sibling in SetParent. + child_ptr->SetParent(this); + + Element* ancestor = child_ptr; + for (int i = 0; i <= ChildNotifyLevels && ancestor; i++, ancestor = ancestor->GetParentNode()) + ancestor->OnChildAdd(child_ptr); + + DirtyStackingContext(); + + // Not only does the element definition of the newly inserted element need to be dirtied, but also our own definition and implicitly all of our + // children's. This ensures correct styles being applied in the presence of tree-structural selectors such as ':first-child'. + DirtyDefinition(DirtyNodes::Self); + + if (dom_element) + DirtyLayout(); + + return child_ptr; +} + +Element* Element::InsertBefore(ElementPtr child, Element* adjacent_element) +{ + RMLUI_ASSERT(child); + // Find the position in the list of children of the adjacent element. If + // it's nullptr or we can't find it, then we insert it at the end of the dom + // children, as a dom element. + size_t child_index = 0; + bool found_child = false; + if (adjacent_element) + { + for (child_index = 0; child_index < children.size(); child_index++) + { + if (children[child_index].get() == adjacent_element) + { + found_child = true; + break; + } + } + } + + Element* child_ptr = nullptr; + + if (found_child) + { + child_ptr = child.get(); + + if ((int)child_index >= GetNumChildren()) + num_non_dom_children++; + else + DirtyLayout(); + + children.insert(children.begin() + child_index, std::move(child)); + child_ptr->SetParent(this); + + Element* ancestor = child_ptr; + for (int i = 0; i <= ChildNotifyLevels && ancestor; i++, ancestor = ancestor->GetParentNode()) + ancestor->OnChildAdd(child_ptr); + + DirtyStackingContext(); + DirtyDefinition(DirtyNodes::Self); + } + else + { + child_ptr = AppendChild(std::move(child)); + } + + return child_ptr; +} + +ElementPtr Element::ReplaceChild(ElementPtr inserted_element, Element* replaced_element) +{ + RMLUI_ASSERT(inserted_element); + auto insertion_point = children.begin(); + while (insertion_point != children.end() && insertion_point->get() != replaced_element) + { + ++insertion_point; + } + + Element* inserted_element_ptr = inserted_element.get(); + + if (insertion_point == children.end()) + { + AppendChild(std::move(inserted_element)); + return nullptr; + } + + children.insert(insertion_point, std::move(inserted_element)); + inserted_element_ptr->SetParent(this); + + ElementPtr result = RemoveChild(replaced_element); + + Element* ancestor = inserted_element_ptr; + for (int i = 0; i <= ChildNotifyLevels && ancestor; i++, ancestor = ancestor->GetParentNode()) + ancestor->OnChildAdd(inserted_element_ptr); + + return result; +} + +ElementPtr Element::RemoveChild(Element* child) +{ + size_t child_index = 0; + + for (auto itr = children.begin(); itr != children.end(); ++itr) + { + // Add the element to the delete list + if (itr->get() == child) + { + Element* ancestor = child; + for (int i = 0; i <= ChildNotifyLevels && ancestor; i++, ancestor = ancestor->GetParentNode()) + ancestor->OnChildRemove(child); + + if (child_index >= children.size() - num_non_dom_children) + num_non_dom_children--; + + ElementPtr detached_child = std::move(*itr); + children.erase(itr); + + // Remove the child element as the focused child of this element. + if (child == focus) + { + focus = nullptr; + + // If this child (or a descendant of this child) is the context's currently + // focused element, set the focus to us instead. + if (Context* context = GetContext()) + { + Element* focus_element = context->GetFocusElement(); + while (focus_element) + { + if (focus_element == child) + { + Focus(); + break; + } + + focus_element = focus_element->GetParentNode(); + } + } + } + + detached_child->SetParent(nullptr); + + DirtyLayout(); + DirtyStackingContext(); + DirtyDefinition(DirtyNodes::Self); + + return detached_child; + } + + child_index++; + } + + return nullptr; +} + +bool Element::HasChildNodes() const +{ + return (int)children.size() > num_non_dom_children; +} + +Element* Element::GetElementById(const String& id) +{ + // Check for special-case tokens. + if (id == "#self") + return this; + else if (id == "#document") + return GetOwnerDocument(); + else if (id == "#parent") + return this->parent; + else + { + Element* search_root = GetOwnerDocument(); + if (search_root == nullptr) + search_root = this; + return ElementUtilities::GetElementById(search_root, id); + } +} + +void Element::GetElementsByTagName(ElementList& elements, const String& tag) +{ + return ElementUtilities::GetElementsByTagName(elements, this, tag); +} + +void Element::GetElementsByClassName(ElementList& elements, const String& class_name) +{ + return ElementUtilities::GetElementsByClassName(elements, this, class_name); +} + +static Element* QuerySelectorMatchRecursive(const StyleSheetNodeListRaw& nodes, Element* element, Element* scope) +{ + const int num_children = element->GetNumChildren(); + + for (int i = 0; i < num_children; i++) + { + Element* child = element->GetChild(i); + if (child->GetTagName() == "#text") + continue; + + for (const StyleSheetNode* node : nodes) + { + if (node->IsApplicable(child, scope)) + return child; + } + + Element* matching_element = QuerySelectorMatchRecursive(nodes, child, scope); + if (matching_element) + return matching_element; + } + + return nullptr; +} + +static void QuerySelectorAllMatchRecursive(ElementList& matching_elements, const StyleSheetNodeListRaw& nodes, Element* element, Element* scope) +{ + const int num_children = element->GetNumChildren(); + + for (int i = 0; i < num_children; i++) + { + Element* child = element->GetChild(i); + if (child->GetTagName() == "#text") + continue; + + for (const StyleSheetNode* node : nodes) + { + if (node->IsApplicable(child, scope)) + { + matching_elements.push_back(child); + break; + } + } + + QuerySelectorAllMatchRecursive(matching_elements, nodes, child, scope); + } +} + +Element* Element::QuerySelector(const String& selectors) +{ + StyleSheetNode root_node; + StyleSheetNodeListRaw leaf_nodes = StyleSheetParser::ConstructNodes(root_node, selectors); + + if (leaf_nodes.empty()) + { + Log::Message(Log::LT_WARNING, "Query selector '%s' is empty. In element %s", selectors.c_str(), GetAddress().c_str()); + return nullptr; + } + + return QuerySelectorMatchRecursive(leaf_nodes, this, this); +} + +void Element::QuerySelectorAll(ElementList& elements, const String& selectors) +{ + StyleSheetNode root_node; + StyleSheetNodeListRaw leaf_nodes = StyleSheetParser::ConstructNodes(root_node, selectors); + + if (leaf_nodes.empty()) + { + Log::Message(Log::LT_WARNING, "Query selector '%s' is empty. In element %s", selectors.c_str(), GetAddress().c_str()); + return; + } + + QuerySelectorAllMatchRecursive(elements, leaf_nodes, this, this); +} + +bool Element::Matches(const String& selectors) +{ + StyleSheetNode root_node; + StyleSheetNodeListRaw leaf_nodes = StyleSheetParser::ConstructNodes(root_node, selectors); + + if (leaf_nodes.empty()) + { + Log::Message(Log::LT_WARNING, "Query selector '%s' is empty. In element %s", selectors.c_str(), GetAddress().c_str()); + return false; + } + + for (const StyleSheetNode* node : leaf_nodes) + { + if (node->IsApplicable(this, this)) + { + return true; + } + } + + return false; +} + +bool Element::Contains(Element* element) const +{ + while (element) + { + if (element == this) + return true; + element = element->GetParentNode(); + } + return false; +} + +EventDispatcher* Element::GetEventDispatcher() const +{ + return &meta->event_dispatcher; +} + +String Element::GetEventDispatcherSummary() const +{ + return meta->event_dispatcher.ToString(); +} + +ElementBackgroundBorder* Element::GetElementBackgroundBorder() const +{ + return &meta->background_border; +} + +ElementScroll* Element::GetElementScroll() const +{ + return &meta->scroll; +} + +DataModel* Element::GetDataModel() const +{ + return data_model; +} + +void Element::SetInstancer(ElementInstancer* _instancer) +{ + // Only record the first instancer being set as some instancers call other instancers to do their dirty work, in + // which case we don't want to update the lowest level instancer. + if (!instancer) + { + instancer = _instancer; + } +} + +void Element::ForceLocalStackingContext() +{ + local_stacking_context_forced = true; + local_stacking_context = true; + + DirtyStackingContext(); +} + +void Element::OnUpdate() {} + +void Element::OnRender() {} + +void Element::OnResize() {} + +void Element::OnLayout() {} + +void Element::OnDpRatioChange() {} + +void Element::OnStyleSheetChange() {} + +void Element::OnAttributeChange(const ElementAttributes& changed_attributes) +{ + for (const auto& element_attribute : changed_attributes) + { + const auto& attribute = element_attribute.first; + const auto& value = element_attribute.second; + if (attribute == "id") + { + id = value.Get(); + } + else if (attribute == "class") + { + meta->style.SetClassNames(value.Get()); + } + else if (((attribute == "colspan" || attribute == "rowspan") && meta->computed_values.display() == Style::Display::TableCell) || + (attribute == "span" && + (meta->computed_values.display() == Style::Display::TableColumn || + meta->computed_values.display() == Style::Display::TableColumnGroup))) + { + DirtyLayout(); + } + else if (attribute.size() > 2 && attribute[0] == 'o' && attribute[1] == 'n') + { + static constexpr size_t on_length = 2; + static constexpr size_t capture_length = 7; + const bool in_capture_phase = StringUtilities::EndsWith(attribute, "capture"); + auto& attribute_event_listeners = meta->attribute_event_listeners; + auto& event_dispatcher = meta->event_dispatcher; + const size_t event_name_length = attribute.size() - on_length - (in_capture_phase ? capture_length : 0); + const auto event_id = EventSpecificationInterface::GetIdOrInsert(attribute.substr(on_length, event_name_length)); + const auto remove_event_listener_if_exists = [&attribute_event_listeners, &event_dispatcher, event_id, in_capture_phase]() { + const auto listener_it = attribute_event_listeners.find(event_id); + if (listener_it != attribute_event_listeners.cend()) + { + event_dispatcher.DetachEvent(event_id, listener_it->second, in_capture_phase); + attribute_event_listeners.erase(listener_it); + } + }; + + if (value.GetType() == Variant::Type::STRING) + { + remove_event_listener_if_exists(); + + const auto value_as_string = value.Get(); + auto insertion_result = attribute_event_listeners.emplace(event_id, Factory::InstanceEventListener(value_as_string, this)); + if (auto* listener = insertion_result.first->second) + event_dispatcher.AttachEvent(event_id, listener, in_capture_phase); + } + else if (value.GetType() == Variant::Type::NONE) + remove_event_listener_if_exists(); + } + else if (attribute == "style") + { + if (value.GetType() == Variant::STRING) + { + PropertyDictionary properties; + StyleSheetParser parser; + parser.ParseProperties(properties, value.GetReference()); + + for (const auto& name_value : properties.GetProperties()) + meta->style.SetProperty(name_value.first, name_value.second); + } + else if (value.GetType() != Variant::NONE) + Log::Message(Log::LT_WARNING, "Invalid 'style' attribute, string type required. In element: %s", GetAddress().c_str()); + } + else if (attribute == "lang") + { + if (value.GetType() == Variant::STRING) + meta->style.SetProperty(PropertyId::RmlUi_Language, Property(value.GetReference(), Unit::STRING)); + else if (value.GetType() != Variant::NONE) + Log::Message(Log::LT_WARNING, "Invalid 'lang' attribute, string type required. In element: %s", GetAddress().c_str()); + } + else if (attribute == "dir") + { + if (value.GetType() == Variant::STRING) + { + const String& dir_value = value.GetReference(); + + if (dir_value == "auto") + meta->style.SetProperty(PropertyId::RmlUi_Direction, Property(Style::Direction::Auto)); + else if (dir_value == "ltr") + meta->style.SetProperty(PropertyId::RmlUi_Direction, Property(Style::Direction::Ltr)); + else if (dir_value == "rtl") + meta->style.SetProperty(PropertyId::RmlUi_Direction, Property(Style::Direction::Rtl)); + else + Log::Message(Log::LT_WARNING, "Invalid 'dir' attribute '%s', value must be 'auto', 'ltr', or 'rtl'. In element: %s", + dir_value.c_str(), GetAddress().c_str()); + } + else if (value.GetType() != Variant::NONE) + Log::Message(Log::LT_WARNING, "Invalid 'dir' attribute, string type required. In element: %s", GetAddress().c_str()); + } + } + + // Any change to the attributes may affect which styles apply to the current element, in particular due to attribute selectors, ID selectors, and + // class selectors. This can further affect all siblings or descendants due to sibling or descendant combinators. + DirtyDefinition(DirtyNodes::SelfAndSiblings); +} + +void Element::OnPropertyChange(const PropertyIdSet& changed_properties) +{ + RMLUI_ZoneScoped; + const bool top_right_bottom_left_changed = ( // + changed_properties.Contains(PropertyId::Top) || // + changed_properties.Contains(PropertyId::Right) || // + changed_properties.Contains(PropertyId::Bottom) || // + changed_properties.Contains(PropertyId::Left) // + ); + + // See if the document layout needs to be updated. + if (!IsLayoutDirty()) + { + // Force a relayout if any of the changed properties require it. + const PropertyIdSet changed_properties_forcing_layout = + (changed_properties & StyleSheetSpecification::GetRegisteredPropertiesForcingLayout()); + + if (!changed_properties_forcing_layout.Empty()) + { + DirtyLayout(); + } + else if (top_right_bottom_left_changed) + { + // Normally, the position properties only affect the position of the element and not the layout. Thus, these properties are not registered + // as affecting layout. However, when absolutely positioned elements with both left & right, or top & bottom are set to definite values, + // they affect the size of the element and thereby also the layout. This layout-dirtying condition needs to be registered manually. + using namespace Style; + const ComputedValues& computed = GetComputedValues(); + const bool absolutely_positioned = (computed.position() == Position::Absolute || computed.position() == Position::Fixed); + const bool sized_width = + (computed.width().type == Width::Auto && computed.left().type != Left::Auto && computed.right().type != Right::Auto); + const bool sized_height = + (computed.height().type == Height::Auto && computed.top().type != Top::Auto && computed.bottom().type != Bottom::Auto); + + if (absolutely_positioned && (sized_width || sized_height)) + DirtyLayout(); + } + } + + // Update the position. + if (top_right_bottom_left_changed) + { + UpdateOffset(); + DirtyAbsoluteOffset(); + } + + // Update the visibility. + if (changed_properties.Contains(PropertyId::Visibility) || changed_properties.Contains(PropertyId::Display)) + { + bool new_visibility = + (meta->computed_values.display() != Style::Display::None && meta->computed_values.visibility() == Style::Visibility::Visible); + + if (visible != new_visibility) + { + visible = new_visibility; + + if (parent != nullptr) + parent->DirtyStackingContext(); + + if (!visible) + Blur(); + } + } + + const bool border_radius_changed = ( // + changed_properties.Contains(PropertyId::BorderTopLeftRadius) || // + changed_properties.Contains(PropertyId::BorderTopRightRadius) || // + changed_properties.Contains(PropertyId::BorderBottomRightRadius) || // + changed_properties.Contains(PropertyId::BorderBottomLeftRadius) // + ); + const bool filter_or_mask_changed = (changed_properties.Contains(PropertyId::Filter) || changed_properties.Contains(PropertyId::BackdropFilter) || + changed_properties.Contains(PropertyId::MaskImage)); + const bool perspective_changed = changed_properties.Contains(PropertyId::Perspective) || + changed_properties.Contains(PropertyId::PerspectiveOriginX) || changed_properties.Contains(PropertyId::PerspectiveOriginY); + const bool transform_changed = changed_properties.Contains(PropertyId::Transform) || changed_properties.Contains(PropertyId::TransformOriginX) || + changed_properties.Contains(PropertyId::TransformOriginY) || changed_properties.Contains(PropertyId::TransformOriginZ); + + // Update the z-index and stacking context. + if (changed_properties.Contains(PropertyId::ZIndex) || filter_or_mask_changed || perspective_changed || transform_changed) + { + const Style::ZIndex z_index_property = meta->computed_values.z_index(); + + const float new_z_index = (z_index_property.type == Style::ZIndex::Auto ? 0.f : z_index_property.value); + const bool enable_local_stacking_context = (z_index_property.type != Style::ZIndex::Auto || local_stacking_context_forced || + meta->computed_values.has_filter() || meta->computed_values.has_backdrop_filter() || meta->computed_values.has_mask_image() || + meta->computed_values.has_local_transform() || meta->computed_values.has_local_perspective()); + + if (z_index != new_z_index || local_stacking_context != enable_local_stacking_context) + { + z_index = new_z_index; + + if (local_stacking_context != enable_local_stacking_context) + { + local_stacking_context = enable_local_stacking_context; + + // If we are no longer acting as a local stacking context, then we clear the list and are all set. Otherwise, we need to rebuild our + // local stacking context. + stacking_context.clear(); + stacking_context_dirty = local_stacking_context; + } + + // When our z-index or local stacking context changes, then we must dirty our parent stacking context so we are re-indexed. + if (parent) + parent->DirtyStackingContext(); + } + } + + // Dirty the background if it's changed. + if (border_radius_changed || // + changed_properties.Contains(PropertyId::BackgroundColor) || // + changed_properties.Contains(PropertyId::Opacity) || // + changed_properties.Contains(PropertyId::ImageColor) || // + changed_properties.Contains(PropertyId::BoxShadow)) // + { + meta->background_border.DirtyBackground(); + } + + // Dirty the border if it's changed. + if (border_radius_changed || // + changed_properties.Contains(PropertyId::BorderTopWidth) || // + changed_properties.Contains(PropertyId::BorderRightWidth) || // + changed_properties.Contains(PropertyId::BorderBottomWidth) || // + changed_properties.Contains(PropertyId::BorderLeftWidth) || // + changed_properties.Contains(PropertyId::BorderTopColor) || // + changed_properties.Contains(PropertyId::BorderRightColor) || // + changed_properties.Contains(PropertyId::BorderBottomColor) || // + changed_properties.Contains(PropertyId::BorderLeftColor) || // + changed_properties.Contains(PropertyId::Opacity)) + { + meta->background_border.DirtyBorder(); + } + + // Dirty the effects if they've changed. + if (border_radius_changed || filter_or_mask_changed || changed_properties.Contains(PropertyId::Decorator)) + { + meta->effects.DirtyEffects(); + } + + const bool font_changed = (changed_properties.Contains(PropertyId::FontFamily) || changed_properties.Contains(PropertyId::FontStyle) || + changed_properties.Contains(PropertyId::FontWeight) || changed_properties.Contains(PropertyId::FontSize) || + changed_properties.Contains(PropertyId::FontKerning) || changed_properties.Contains(PropertyId::LetterSpacing)); + + // Dirty the effects data when their visual looks may have changed. + if (border_radius_changed || // + font_changed || // + changed_properties.Contains(PropertyId::Opacity) || // + changed_properties.Contains(PropertyId::Color) || // + changed_properties.Contains(PropertyId::ImageColor)) + { + meta->effects.DirtyEffectsData(); + } + + if (perspective_changed) + DirtyTransformState(true, false); + + if (transform_changed) + DirtyTransformState(false, true); + + // Check for `animation' changes + if (changed_properties.Contains(PropertyId::Animation)) + { + dirty_animation = true; + } + // Check for `transition' changes + if (changed_properties.Contains(PropertyId::Transition)) + { + dirty_transition = true; + } +} + +void Element::OnPseudoClassChange(const String& /*pseudo_class*/, bool /*activate*/) {} + +void Element::OnChildAdd(Element* /*child*/) {} + +void Element::OnChildRemove(Element* /*child*/) {} + +void Element::DirtyLayout() +{ + if (Element* document = GetOwnerDocument()) + document->DirtyLayout(); +} + +bool Element::IsLayoutDirty() +{ + if (Element* document = GetOwnerDocument()) + return document->IsLayoutDirty(); + return false; +} + +Element* Element::GetClosestScrollableContainer() +{ + using namespace Style; + + Overflow overflow_x = meta->computed_values.overflow_x(); + Overflow overflow_y = meta->computed_values.overflow_y(); + bool scrollable_x = (overflow_x == Overflow::Auto || overflow_x == Overflow::Scroll); + bool scrollable_y = (overflow_y == Overflow::Auto || overflow_y == Overflow::Scroll); + + scrollable_x = (scrollable_x && GetScrollWidth() > GetClientWidth()); + scrollable_y = (scrollable_y && GetScrollHeight() > GetClientHeight()); + + if (scrollable_x || scrollable_y || meta->computed_values.overscroll_behavior() == OverscrollBehavior::Contain) + return this; + else if (parent) + return parent->GetClosestScrollableContainer(); + + return nullptr; +} + +void Element::ProcessDefaultAction(Event& event) +{ + if (event == EventId::Mousedown) + { + const Vector2f mouse_pos(event.GetParameter("mouse_x", 0.f), event.GetParameter("mouse_y", 0.f)); + + if (IsPointWithinElement(mouse_pos) && event.GetParameter("button", 0) == 0) + SetPseudoClass("active", true); + } + + if (event.GetPhase() == EventPhase::Target) + { + switch (event.GetId()) + { + case EventId::Mouseover: SetPseudoClass("hover", true); break; + case EventId::Mouseout: SetPseudoClass("hover", false); break; + case EventId::Focus: + SetPseudoClass("focus", true); + if (event.GetParameter("focus_visible", false)) + SetPseudoClass("focus-visible", true); + break; + case EventId::Blur: + SetPseudoClass("focus", false); + SetPseudoClass("focus-visible", false); + break; + default: break; + } + } +} + +const Style::ComputedValues& Element::GetComputedValues() const +{ + return meta->computed_values; +} + +void Element::GetRML(String& content) +{ + // First we start the open tag, add the attributes then close the open tag. + // Then comes the children in order, then we add our close tag. + content += "<"; + content += tag; + + for (auto& pair : attributes) + { + const String& name = pair.first; + if (name == "style") + continue; + + const Variant& variant = pair.second; + String value; + if (variant.GetInto(value)) + { + content += ' '; + content += name; + content += "=\""; + content += value; + content += "\""; + } + } + + const PropertyMap& local_properties = meta->style.GetLocalStyleProperties(); + if (!local_properties.empty()) + content += " style=\""; + + for (const auto& pair : local_properties) + { + const PropertyId id = pair.first; + const Property& property = pair.second; + + content += StyleSheetSpecification::GetPropertyName(id); + content += ": "; + content += StringUtilities::EncodeRml(property.ToString()); + content += "; "; + } + + if (!local_properties.empty()) + content.back() = '\"'; + + if (HasChildNodes()) + { + content += ">"; + + GetInnerRML(content); + + content += ""; + } + else + { + content += " />"; + } +} + +void Element::SetOwnerDocument(ElementDocument* document) +{ + if (owner_document && !document) + { + // We are detaching from the document and thereby also the context. + if (Context* context = owner_document->GetContext()) + context->OnElementDetach(this); + } + + // If this element is a document, then never change owner_document. + if (owner_document != this && owner_document != document) + { + owner_document = document; + for (ElementPtr& child : children) + child->SetOwnerDocument(document); + } +} + +void Element::SetDataModel(DataModel* new_data_model) +{ + RMLUI_ASSERTMSG(!data_model || !new_data_model, "We must either attach a new data model, or detach the old one."); + + if (data_model == new_data_model) + return; + + // stop descent if a nested data model is encountered + if (data_model && new_data_model && data_model != new_data_model) + return; + + if (data_model) + data_model->OnElementRemove(this); + + data_model = new_data_model; + + if (data_model) + ElementUtilities::ApplyDataViewsControllers(this); + + for (ElementPtr& child : children) + child->SetDataModel(new_data_model); +} + +void Element::Release() +{ + if (instancer) + instancer->ReleaseElement(this); + else + Log::Message(Log::LT_WARNING, "Leak detected: element %s not instanced via RmlUi Factory. Unable to release.", GetAddress().c_str()); +} + +void Element::SetParent(Element* _parent) +{ + // Assumes we are already detached from the hierarchy or we are detaching now. + RMLUI_ASSERT(!parent || !_parent); + + parent = _parent; + + if (parent) + { + // We need to update our definition and make sure we inherit the properties of our new parent. + DirtyDefinition(DirtyNodes::Self); + meta->style.DirtyInheritedProperties(); + } + + // The transform state may require recalculation. + if (transform_state || (parent && parent->transform_state)) + DirtyTransformState(true, true); + + SetOwnerDocument(parent ? parent->GetOwnerDocument() : nullptr); + + if (!parent) + { + if (data_model) + SetDataModel(nullptr); + } + else + { + auto it = attributes.find("data-model"); + if (it == attributes.end()) + { + SetDataModel(parent->data_model); + } + else if (Context* context = GetContext()) + { + String name = it->second.Get(); + + if (DataModel* model = context->GetDataModelPtr(name)) + { + model->AttachModelRootElement(this); + SetDataModel(model); + } + else + Log::Message(Log::LT_ERROR, "Could not locate data model '%s' in element %s.", name.c_str(), GetAddress().c_str()); + } + } +} + +void Element::DirtyAbsoluteOffset() +{ + if (!absolute_offset_dirty) + DirtyAbsoluteOffsetRecursive(); +} + +void Element::DirtyAbsoluteOffsetRecursive() +{ + if (!absolute_offset_dirty) + { + absolute_offset_dirty = true; + + if (transform_state) + DirtyTransformState(true, true); + } + + for (size_t i = 0; i < children.size(); i++) + children[i]->DirtyAbsoluteOffsetRecursive(); +} + +void Element::UpdateOffset() +{ + using namespace Style; + const auto& computed = meta->computed_values; + Position position_property = computed.position(); + + if (position_property == Position::Absolute || position_property == Position::Fixed) + { + if (offset_parent != nullptr) + { + const Box& parent_box = offset_parent->GetBox(); + Vector2f containing_block = parent_box.GetSize(BoxArea::Padding); + + // If the element is anchored left, then the position is offset by that resolved value. + if (computed.left().type != Left::Auto) + relative_offset_base.x = parent_box.GetEdge(BoxArea::Border, BoxEdge::Left) + + (ResolveValue(computed.left(), containing_block.x) + GetBox().GetEdge(BoxArea::Margin, BoxEdge::Left)); + + // If the element is anchored right, then the position is set first so the element's right-most edge + // (including margins) will render up against the containing box's right-most content edge, and then + // offset by the resolved value. + else if (computed.right().type != Right::Auto) + { + relative_offset_base.x = containing_block.x + parent_box.GetEdge(BoxArea::Border, BoxEdge::Left) - + (ResolveValue(computed.right(), containing_block.x) + GetBox().GetSize(BoxArea::Border).x + + GetBox().GetEdge(BoxArea::Margin, BoxEdge::Right)); + } + + // If the element is anchored top, then the position is offset by that resolved value. + if (computed.top().type != Top::Auto) + { + relative_offset_base.y = parent_box.GetEdge(BoxArea::Border, BoxEdge::Top) + + (ResolveValue(computed.top(), containing_block.y) + GetBox().GetEdge(BoxArea::Margin, BoxEdge::Top)); + } + + // If the element is anchored bottom, then the position is set first so the element's right-most edge + // (including margins) will render up against the containing box's right-most content edge, and then + // offset by the resolved value. + else if (computed.bottom().type != Bottom::Auto) + { + relative_offset_base.y = containing_block.y + parent_box.GetEdge(BoxArea::Border, BoxEdge::Top) - + (ResolveValue(computed.bottom(), containing_block.y) + GetBox().GetSize(BoxArea::Border).y + + GetBox().GetEdge(BoxArea::Margin, BoxEdge::Bottom)); + } + } + } + else if (position_property == Position::Relative) + { + if (offset_parent != nullptr) + { + const Box& parent_box = offset_parent->GetBox(); + Vector2f containing_block = parent_box.GetSize(); + + if (computed.left().type != Left::Auto) + relative_offset_position.x = ResolveValue(computed.left(), containing_block.x); + else if (computed.right().type != Right::Auto) + relative_offset_position.x = -1 * ResolveValue(computed.right(), containing_block.x); + else + relative_offset_position.x = 0; + + if (computed.top().type != Top::Auto) + relative_offset_position.y = ResolveValue(computed.top(), containing_block.y); + else if (computed.bottom().type != Bottom::Auto) + relative_offset_position.y = -1 * ResolveValue(computed.bottom(), containing_block.y); + else + relative_offset_position.y = 0; + } + } + else + { + relative_offset_position.x = 0; + relative_offset_position.y = 0; + } +} + +void Element::SetBaseline(float in_baseline) +{ + baseline = in_baseline; +} + +enum class RenderOrder { + StackNegative, // Local stacking context with z < 0. + Block, + TableColumnGroup, + TableColumn, + TableRowGroup, + TableRow, + TableCell, + Floating, + Inline, + Positioned, // Positioned element, or local stacking context with z == 0. + StackPositive, // Local stacking context with z > 0. +}; +struct StackingContextChild { + Element* element = nullptr; + RenderOrder order = {}; +}; +static bool operator<(const StackingContextChild& lhs, const StackingContextChild& rhs) +{ + if (int(lhs.order) == int(rhs.order)) + return lhs.element->GetZIndex() < rhs.element->GetZIndex(); + return int(lhs.order) < int(rhs.order); +} + +// Treat all children in the range [index_begin, end) as if the parent created a new stacking context, by sorting them +// separately and then assigning their parent's paint order. However, positioned and descendants which create a new +// stacking context should be considered part of the parent stacking context. See CSS 2, Appendix E. +static void StackingContext_MakeAtomicRange(Vector& stacking_children, size_t index_begin, RenderOrder parent_render_order) +{ + std::stable_sort(stacking_children.begin() + index_begin, stacking_children.end()); + + for (auto it = stacking_children.begin() + index_begin; it != stacking_children.end(); ++it) + { + auto order = it->order; + if (order != RenderOrder::StackNegative && order != RenderOrder::Positioned && order != RenderOrder::StackPositive) + it->order = parent_render_order; + } +} + +void Element::BuildLocalStackingContext() +{ + stacking_context_dirty = false; + + Vector stacking_children; + AddChildrenToStackingContext(stacking_children); + std::stable_sort(stacking_children.begin(), stacking_children.end()); + + stacking_context.resize(stacking_children.size()); + for (size_t i = 0; i < stacking_children.size(); i++) + stacking_context[i] = stacking_children[i].element; +} + +void Element::AddChildrenToStackingContext(Vector& stacking_children) +{ + bool is_flex_container = (GetDisplay() == Style::Display::Flex); + const int num_children = (int)children.size(); + for (int i = 0; i < num_children; ++i) + { + const bool is_non_dom_element = (i >= num_children - num_non_dom_children); + children[i]->AddToStackingContext(stacking_children, is_flex_container, is_non_dom_element); + } +} + +void Element::AddToStackingContext(Vector& stacking_children, bool is_flex_item, bool is_non_dom_element) +{ + using Style::Display; + + if (!IsVisible()) + return; + + const Display display = GetDisplay(); + + RenderOrder order = RenderOrder::Inline; + bool include_children = true; + bool render_as_atomic_unit = false; + + if (local_stacking_context) + { + if (z_index > 0.f) + order = RenderOrder::StackPositive; + else if (z_index < 0.f) + order = RenderOrder::StackNegative; + else + order = RenderOrder::Positioned; + + include_children = false; + } + else if (display == Display::TableRow || display == Display::TableRowGroup || display == Display::TableColumn || + display == Display::TableColumnGroup) + { + // Handle internal display values taking priority over position and float. + switch (display) + { + case Display::TableRow: order = RenderOrder::TableRow; break; + case Display::TableRowGroup: order = RenderOrder::TableRowGroup; break; + case Display::TableColumn: order = RenderOrder::TableColumn; break; + case Display::TableColumnGroup: order = RenderOrder::TableColumnGroup; break; + default: break; + } + } + else if (GetPosition() != Style::Position::Static) + { + order = RenderOrder::Positioned; + render_as_atomic_unit = true; + } + else if (GetFloat() != Style::Float::None) + { + order = RenderOrder::Floating; + render_as_atomic_unit = true; + } + else + { + switch (display) + { + case Display::Block: + case Display::FlowRoot: + case Display::Table: + case Display::Flex: + order = RenderOrder::Block; + render_as_atomic_unit = (display == Display::Table || is_flex_item); + break; + + case Display::Inline: + case Display::InlineBlock: + case Display::InlineFlex: + case Display::InlineTable: + order = RenderOrder::Inline; + render_as_atomic_unit = (display != Display::Inline || is_flex_item); + break; + + case Display::TableCell: + order = RenderOrder::TableCell; + render_as_atomic_unit = true; + break; + + case Display::TableRow: + case Display::TableRowGroup: + case Display::TableColumn: + case Display::TableColumnGroup: + case Display::None: RMLUI_ERROR; break; // Handled above. + } + } + + if (is_non_dom_element) + render_as_atomic_unit = true; + + stacking_children.push_back(StackingContextChild{this, order}); + + if (include_children && !children.empty()) + { + const size_t index_child_begin = stacking_children.size(); + + AddChildrenToStackingContext(stacking_children); + + if (render_as_atomic_unit) + StackingContext_MakeAtomicRange(stacking_children, index_child_begin, order); + } +} + +void Element::DirtyStackingContext() +{ + if (Element* stacking_context_parent = ClosestStackingContextContainer()) + stacking_context_parent->stacking_context_dirty = true; +} + +Element* Element::ClosestStackingContextContainer() +{ + // Find the first ancestor, or this, that has a local stacking context. That is our stacking context container. + Element* stacking_context_parent = this; + while (stacking_context_parent && !stacking_context_parent->local_stacking_context) + { + stacking_context_parent = stacking_context_parent->GetParentNode(); + } + + return stacking_context_parent; +} + +void Element::DirtyDefinition(DirtyNodes dirty_nodes) +{ + switch (dirty_nodes) + { + case DirtyNodes::Self: dirty_definition = true; break; + case DirtyNodes::SelfAndSiblings: + dirty_definition = true; + if (parent) + parent->dirty_child_definitions = true; + break; + } +} + +void Element::UpdateDefinition() +{ + if (dirty_definition) + { + dirty_definition = false; + + // Dirty definition implies all our descendent elements. Anything that can change the definition of this element can also change the + // definition of any descendants due to the presence of RCSS descendant or child combinators. In principle this also applies to sibling + // combinators, but those are handled during the DirtyDefinition call. + dirty_child_definitions = true; + + GetStyle()->UpdateDefinition(); + } + + if (dirty_child_definitions) + { + dirty_child_definitions = false; + for (const ElementPtr& child : children) + child->dirty_definition = true; + } +} + +bool Element::Animate(const String& property_name, const Property& target_value, float duration, Tween tween, int num_iterations, + bool alternate_direction, float delay, const Property* start_value) +{ + return Animate(StyleSheetSpecification::GetPropertyId(property_name), target_value, duration, tween, num_iterations, alternate_direction, delay, + start_value); +} + +bool Element::Animate(PropertyId id, const Property& target_value, float duration, Tween tween, int num_iterations, bool alternate_direction, + float delay, const Property* start_value) +{ + bool result = false; + auto it_animation = StartAnimation(id, start_value, num_iterations, alternate_direction, delay, false); + if (it_animation != animations.end()) + { + result = it_animation->AddKey(duration, target_value, *this, tween, true); + if (!result) + animations.erase(it_animation); + } + + return result; +} + +bool Element::AddAnimationKey(const String& property_name, const Property& target_value, float duration, Tween tween) +{ + return AddAnimationKey(StyleSheetSpecification::GetPropertyId(property_name), target_value, duration, tween); +} + +bool Element::AddAnimationKey(PropertyId id, const Property& target_value, float duration, Tween tween) +{ + ElementAnimation* animation = nullptr; + for (auto& existing_animation : animations) + { + if (existing_animation.GetPropertyId() == id) + { + animation = &existing_animation; + break; + } + } + if (!animation) + return false; + + bool result = animation->AddKey(animation->GetDuration() + duration, target_value, *this, tween, true); + + return result; +} + +ElementAnimationList::iterator Element::StartAnimation(PropertyId property_id, const Property* start_value, int num_iterations, + bool alternate_direction, float delay, bool initiated_by_animation_property) +{ + auto it = std::find_if(animations.begin(), animations.end(), [&](const ElementAnimation& el) { return el.GetPropertyId() == property_id; }); + + if (it != animations.end()) + { + const bool allow_overwriting_animation = !initiated_by_animation_property; + if (!allow_overwriting_animation) + { + Log::Message(Log::LT_WARNING, + "Could not animate property '%s' on element: %s. " + "Please ensure that the property does not appear in multiple animations on the same element.", + StyleSheetSpecification::GetPropertyName(property_id).c_str(), GetAddress().c_str()); + return it; + } + + *it = ElementAnimation{}; + } + else + { + animations.emplace_back(); + it = animations.end() - 1; + } + + Property value; + + if (start_value) + { + value = *start_value; + if (!value.definition) + if (auto default_value = GetProperty(property_id)) + value.definition = default_value->definition; + } + else if (auto default_value = GetProperty(property_id)) + { + value = *default_value; + } + + if (value.definition) + { + ElementAnimationOrigin origin = (initiated_by_animation_property ? ElementAnimationOrigin::Animation : ElementAnimationOrigin::User); + double start_time = Clock::GetElapsedTime() + (double)delay; + *it = ElementAnimation{property_id, origin, value, *this, start_time, 0.0f, num_iterations, alternate_direction}; + } + + if (!it->IsInitalized()) + { + animations.erase(it); + it = animations.end(); + } + + return it; +} + +bool Element::AddAnimationKeyTime(PropertyId property_id, const Property* target_value, float time, Tween tween) +{ + if (!target_value) + target_value = meta->style.GetProperty(property_id); + if (!target_value) + return false; + + ElementAnimation* animation = nullptr; + + for (auto& existing_animation : animations) + { + if (existing_animation.GetPropertyId() == property_id) + { + animation = &existing_animation; + break; + } + } + if (!animation) + return false; + + bool result = animation->AddKey(time, *target_value, *this, tween, true); + + return result; +} + +bool Element::StartTransition(const Transition& transition, const Property& start_value, const Property& target_value) +{ + auto it = std::find_if(animations.begin(), animations.end(), [&](const ElementAnimation& el) { return el.GetPropertyId() == transition.id; }); + + if (it != animations.end() && !it->IsTransition()) + return false; + + float duration = transition.duration; + double start_time = Clock::GetElapsedTime() + (double)transition.delay; + + if (it == animations.end()) + { + // Add transition as new animation + animations.push_back(ElementAnimation{transition.id, ElementAnimationOrigin::Transition, start_value, *this, start_time, 0.0f, 1, false}); + it = (animations.end() - 1); + } + else + { + // Compress the duration based on the progress of the current animation + float f = it->GetInterpolationFactor(); + f = 1.0f - (1.0f - f) * transition.reverse_adjustment_factor; + duration = duration * f; + // Replace old transition + *it = ElementAnimation{transition.id, ElementAnimationOrigin::Transition, start_value, *this, start_time, 0.0f, 1, false}; + } + + bool result = it->AddKey(duration, target_value, *this, transition.tween, true); + + if (result) + SetProperty(transition.id, start_value); + else + animations.erase(it); + + return result; +} + +void Element::HandleTransitionProperty() +{ + if (dirty_transition) + { + dirty_transition = false; + + // Remove all transitions that are no longer in our local list + const TransitionList* keep_transitions = GetComputedValues().transition(); + + if (keep_transitions && keep_transitions->all) + return; + + auto it_remove = animations.end(); + + if (!keep_transitions || keep_transitions->none) + { + // All transitions should be removed, but only touch the animations that originate from the 'transition' property. + // Move all animations to be erased in a valid state at the end of the list, and erase later. + it_remove = std::partition(animations.begin(), animations.end(), + [](const ElementAnimation& animation) -> bool { return !animation.IsTransition(); }); + } + else + { + RMLUI_ASSERT(keep_transitions); + + // Only remove the transitions that are not in our keep list. + const auto& keep_transitions_list = keep_transitions->transitions; + + it_remove = std::partition(animations.begin(), animations.end(), [&keep_transitions_list](const ElementAnimation& animation) -> bool { + if (!animation.IsTransition()) + return true; + auto it = std::find_if(keep_transitions_list.begin(), keep_transitions_list.end(), + [&animation](const Transition& transition) { return animation.GetPropertyId() == transition.id; }); + bool keep_animation = (it != keep_transitions_list.end()); + return keep_animation; + }); + } + + // We can decide what to do with cancelled transitions here. + for (auto it = it_remove; it != animations.end(); ++it) + RemoveProperty(it->GetPropertyId()); + + animations.erase(it_remove, animations.end()); + } +} + +void Element::HandleAnimationProperty() +{ + // Note: We are effectively restarting all animations whenever 'dirty_animation' is set. Use the dirty flag with care, + // or find another approach which only updates actual "dirty" animations. + if (dirty_animation) + { + dirty_animation = false; + + const AnimationList* animation_list = meta->computed_values.animation(); + bool element_has_animations = ((animation_list && !animation_list->empty()) || !animations.empty()); + const StyleSheet* stylesheet = nullptr; + + if (element_has_animations) + stylesheet = GetStyleSheet(); + + if (stylesheet) + { + // Remove existing animations + { + // We only touch the animations that originate from the 'animation' property. + auto it_remove = std::partition(animations.begin(), animations.end(), + [](const ElementAnimation& animation) { return animation.GetOrigin() != ElementAnimationOrigin::Animation; }); + + // We can decide what to do with cancelled animations here. + for (auto it = it_remove; it != animations.end(); ++it) + RemoveProperty(it->GetPropertyId()); + + animations.erase(it_remove, animations.end()); + } + + // Start animations + if (animation_list) + { + for (const auto& animation : *animation_list) + { + const Keyframes* keyframes_ptr = stylesheet->GetKeyframes(animation.name); + if (keyframes_ptr && keyframes_ptr->blocks.size() >= 1 && !animation.paused) + { + auto& property_ids = keyframes_ptr->property_ids; + auto& blocks = keyframes_ptr->blocks; + + bool has_from_key = (blocks[0].normalized_time == 0); + bool has_to_key = (blocks.back().normalized_time == 1); + + // If the first key defines initial conditions for a given property, use those values, else, use this element's current + // values. + for (PropertyId id : property_ids) + StartAnimation(id, (has_from_key ? blocks[0].properties.GetProperty(id) : nullptr), animation.num_iterations, + animation.alternate, animation.delay, true); + + // Add middle keys: Need to skip the first and last keys if they set the initial and end conditions, respectively. + for (int i = (has_from_key ? 1 : 0); i < (int)blocks.size() + (has_to_key ? -1 : 0); i++) + { + // Add properties of current key to animation + float time = blocks[i].normalized_time * animation.duration; + for (auto& property : blocks[i].properties.GetProperties()) + AddAnimationKeyTime(property.first, &property.second, time, animation.tween); + } + + // If the last key defines end conditions for a given property, use those values, else, use this element's current values. + float time = animation.duration; + for (PropertyId id : property_ids) + AddAnimationKeyTime(id, (has_to_key ? blocks.back().properties.GetProperty(id) : nullptr), time, animation.tween); + } + } + } + } + } +} + +void Element::AdvanceAnimations() +{ + if (!animations.empty()) + { + double time = Clock::GetElapsedTime(); + + for (auto& animation : animations) + { + Property property = animation.UpdateAndGetProperty(time, *this); + if (property.unit != Unit::UNKNOWN) + SetProperty(animation.GetPropertyId(), property); + } + + // Move all completed animations to the end of the list + auto it_completed = + std::partition(animations.begin(), animations.end(), [](const ElementAnimation& animation) { return !animation.IsComplete(); }); + + Vector dictionary_list; + Vector is_transition; + dictionary_list.reserve(animations.end() - it_completed); + is_transition.reserve(animations.end() - it_completed); + + for (auto it = it_completed; it != animations.end(); ++it) + { + const String& property_name = StyleSheetSpecification::GetPropertyName(it->GetPropertyId()); + + dictionary_list.emplace_back(); + dictionary_list.back().emplace("property", Variant(property_name)); + is_transition.push_back(it->IsTransition()); + + // Remove completed transition- and animation-initiated properties. + // Should behave like in HandleTransitionProperty() and HandleAnimationProperty() respectively. + if (it->GetOrigin() != ElementAnimationOrigin::User) + RemoveProperty(it->GetPropertyId()); + } + + // Need to erase elements before submitting event, as iterators might be invalidated when calling external code. + animations.erase(it_completed, animations.end()); + + for (size_t i = 0; i < dictionary_list.size(); i++) + DispatchEvent(is_transition[i] ? EventId::Transitionend : EventId::Animationend, dictionary_list[i]); + } +} + +void Element::DirtyTransformState(bool perspective_dirty, bool transform_dirty) +{ + dirty_perspective |= perspective_dirty; + dirty_transform |= transform_dirty; +} + +void Element::UpdateTransformState() +{ + if (!dirty_perspective && !dirty_transform) + return; + + const ComputedValues& computed = meta->computed_values; + + const Vector2f pos = GetAbsoluteOffset(BoxArea::Border); + const Vector2f size = GetBox().GetSize(BoxArea::Border); + + bool perspective_or_transform_changed = false; + + if (dirty_perspective) + { + // If perspective is set on this element, then it applies to our children. We just calculate it here, + // and let the children's transform update merge it with their transform. + bool had_perspective = (transform_state && transform_state->GetLocalPerspective()); + + float distance = computed.perspective(); + Vector2f vanish = Vector2f(pos.x + size.x * 0.5f, pos.y + size.y * 0.5f); + bool have_perspective = false; + + if (distance > 0.0f) + { + have_perspective = true; + + // Compute the vanishing point from the perspective origin + if (computed.perspective_origin_x().type == Style::PerspectiveOrigin::Percentage) + vanish.x = pos.x + computed.perspective_origin_x().value * 0.01f * size.x; + else + vanish.x = pos.x + computed.perspective_origin_x().value; + + if (computed.perspective_origin_y().type == Style::PerspectiveOrigin::Percentage) + vanish.y = pos.y + computed.perspective_origin_y().value * 0.01f * size.y; + else + vanish.y = pos.y + computed.perspective_origin_y().value; + } + + if (have_perspective) + { + // Equivalent to: Translate(x,y,0) * Perspective(distance) * Translate(-x,-y,0) + Matrix4f perspective = Matrix4f::FromRows( // + {1, 0, -vanish.x / distance, 0}, // + {0, 1, -vanish.y / distance, 0}, // + {0, 0, 1, 0}, // + {0, 0, -1 / distance, 1} // + ); + + if (!transform_state) + transform_state = MakeUnique(); + + perspective_or_transform_changed |= transform_state->SetLocalPerspective(&perspective); + } + else if (transform_state) + transform_state->SetLocalPerspective(nullptr); + + perspective_or_transform_changed |= (have_perspective != had_perspective); + + dirty_perspective = false; + } + + if (dirty_transform) + { + // We want to find the accumulated transform given all our ancestors. It is assumed here that the parent transform is already updated, + // so that we only need to consider our local transform and combine it with our parent's transform and perspective matrices. + bool had_transform = (transform_state && transform_state->GetTransform()); + + bool have_transform = false; + Matrix4f transform = Matrix4f::Identity(); + + if (TransformPtr transform_ptr = computed.transform()) + { + // First find the current element's transform + const int n = transform_ptr->GetNumPrimitives(); + for (int i = 0; i < n; ++i) + { + const TransformPrimitive& primitive = transform_ptr->GetPrimitive(i); + Matrix4f matrix = TransformUtilities::ResolveTransform(primitive, *this); + transform *= matrix; + have_transform = true; + } + + if (have_transform) + { + // Compute the transform origin + Vector3f transform_origin(pos.x + size.x * 0.5f, pos.y + size.y * 0.5f, 0); + + if (computed.transform_origin_x().type == Style::TransformOrigin::Percentage) + transform_origin.x = pos.x + computed.transform_origin_x().value * size.x * 0.01f; + else + transform_origin.x = pos.x + computed.transform_origin_x().value; + + if (computed.transform_origin_y().type == Style::TransformOrigin::Percentage) + transform_origin.y = pos.y + computed.transform_origin_y().value * size.y * 0.01f; + else + transform_origin.y = pos.y + computed.transform_origin_y().value; + + transform_origin.z = computed.transform_origin_z(); + + // Make the transformation apply relative to the transform origin + transform = Matrix4f::Translate(transform_origin) * transform * Matrix4f::Translate(-transform_origin); + } + + // We may want to include the local offsets here, as suggested by the CSS specs, so that the local transform is applied after the offset I + // believe the motivation is. Then we would need to subtract the absolute zero-offsets during geometry submit whenever we have transforms. + } + + Element* stacking_parent = parent ? parent->ClosestStackingContextContainer() : nullptr; + if (stacking_parent && stacking_parent->transform_state) + { + // Apply the parent's local perspective and transform. + // @performance: If we have no local transform and no parent perspective, we can effectively just point to the parent transform instead of + // copying it. + const TransformState& parent_state = *stacking_parent->transform_state; + + if (auto parent_perspective = parent_state.GetLocalPerspective()) + { + transform = *parent_perspective * transform; + have_transform = true; + } + + if (auto parent_transform = parent_state.GetTransform()) + { + transform = *parent_transform * transform; + have_transform = true; + } + } + + if (have_transform) + { + if (!transform_state) + transform_state = MakeUnique(); + + perspective_or_transform_changed |= transform_state->SetTransform(&transform); + } + else if (transform_state) + transform_state->SetTransform(nullptr); + + perspective_or_transform_changed |= (had_transform != have_transform); + } + + // A change in perspective or transform will require an update to children transforms as well. + if (perspective_or_transform_changed) + { + for (Element* stacking_child : stacking_context) + stacking_child->DirtyTransformState(false, true); + } + + // No reason to keep the transform state around if transform and perspective have been removed. + if (transform_state && !transform_state->GetTransform() && !transform_state->GetLocalPerspective()) + { + transform_state.reset(); + } +} + +void Element::OnStyleSheetChangeRecursive() +{ + meta->effects.DirtyEffects(); + + OnStyleSheetChange(); + + // Now dirty all of our descendants. + const int num_children = GetNumChildren(true); + for (int i = 0; i < num_children; ++i) + GetChild(i)->OnStyleSheetChangeRecursive(); +} + +void Element::OnDpRatioChangeRecursive() +{ + meta->effects.DirtyEffects(); + GetStyle()->DirtyPropertiesWithUnits(Unit::DP_SCALABLE_LENGTH); + + OnDpRatioChange(); + + // Now dirty all of our descendants. + const int num_children = GetNumChildren(true); + for (int i = 0; i < num_children; ++i) + GetChild(i)->OnDpRatioChangeRecursive(); +} + +void Element::DirtyFontFaceRecursive() +{ + // Dirty the font size to force the element to update the face handle during the next Update(), and update any existing text geometry. + meta->style.DirtyProperty(PropertyId::FontSize); + meta->computed_values.font_face_handle(0); + + const int num_children = GetNumChildren(true); + for (int i = 0; i < num_children; ++i) + GetChild(i)->DirtyFontFaceRecursive(); +} + +void Element::ClampScrollOffset() +{ + const Vector2f new_scroll_offset = { + Math::Round(Math::Min(scroll_offset.x, GetScrollWidth() - GetClientWidth())), + Math::Round(Math::Min(scroll_offset.y, GetScrollHeight() - GetClientHeight())), + }; + + if (new_scroll_offset != scroll_offset) + { + scroll_offset = new_scroll_offset; + DirtyAbsoluteOffset(); + } + + // At this point the scrollbars have been resolved, both in terms of size and visibility. Update their properties + // now so that any visibility changes in particular are reflected immediately on the next render. Otherwise we risk + // that the scrollbars renders a frame late, since changes to scrollbars can happen during layouting. + meta->scroll.UpdateProperties(); +} + +void Element::ClampScrollOffsetRecursive() +{ + ClampScrollOffset(); + const int num_children = GetNumChildren(); + for (int i = 0; i < num_children; ++i) + GetChild(i)->ClampScrollOffsetRecursive(); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementAnimation.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementAnimation.cpp new file mode 100644 index 00000000..1da53683 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementAnimation.cpp @@ -0,0 +1,785 @@ +#include "ElementAnimation.h" +#include "../../Include/RmlUi/Core/DecorationTypes.h" +#include "../../Include/RmlUi/Core/Decorator.h" +#include "../../Include/RmlUi/Core/Element.h" +#include "../../Include/RmlUi/Core/Filter.h" +#include "../../Include/RmlUi/Core/PropertyDefinition.h" +#include "../../Include/RmlUi/Core/PropertySpecification.h" +#include "../../Include/RmlUi/Core/StyleSheet.h" +#include "../../Include/RmlUi/Core/StyleSheetSpecification.h" +#include "../../Include/RmlUi/Core/StyleSheetTypes.h" +#include "../../Include/RmlUi/Core/Transform.h" +#include "../../Include/RmlUi/Core/TransformPrimitive.h" +#include "ComputeProperty.h" +#include "ElementStyle.h" +#include "TransformUtilities.h" + +namespace Rml { + +static Property InterpolateProperties(const Property& p0, const Property& p1, float alpha, Element& element, const PropertyDefinition* definition); + +template +static T Mix(const T& v0, const T& v1, float alpha) +{ + return v0 * (1.0f - alpha) + v1 * alpha; +} + +static Colourf ColourToLinearSpace(Colourb c) +{ + Colourf result; + // Approximate inverse sRGB function + result.red = c.red / 255.f; + result.red *= result.red; + result.green = c.green / 255.f; + result.green *= result.green; + result.blue = c.blue / 255.f; + result.blue *= result.blue; + result.alpha = c.alpha / 255.f; + return result; +} + +static Colourb ColourFromLinearSpace(Colourf c) +{ + Colourb result; + result.red = (byte)Math::Clamp(Math::SquareRoot(c.red) * 255.f, 0.0f, 255.f); + result.green = (byte)Math::Clamp(Math::SquareRoot(c.green) * 255.f, 0.0f, 255.f); + result.blue = (byte)Math::Clamp(Math::SquareRoot(c.blue) * 255.f, 0.0f, 255.f); + result.alpha = (byte)Math::Clamp(c.alpha * 255.f, 0.0f, 255.f); + return result; +} + +static Colourb InterpolateColour(Colourb c0, Colourb c1, float alpha) +{ + Colourf c0f = ColourToLinearSpace(c0); + Colourf c1f = ColourToLinearSpace(c1); + Colourf c = Mix(c0f, c1f, alpha); + return ColourFromLinearSpace(c); +} + +// Merges all the primitives to a single DecomposedMatrix4 primitive +static bool CombineAndDecompose(Transform& t, Element& e) +{ + Matrix4f m = Matrix4f::Identity(); + + for (TransformPrimitive& primitive : t.GetPrimitives()) + { + Matrix4f m_primitive = TransformUtilities::ResolveTransform(primitive, e); + m *= m_primitive; + } + + Transforms::DecomposedMatrix4 decomposed; + + if (!TransformUtilities::Decompose(decomposed, m)) + return false; + + t.ClearPrimitives(); + t.AddPrimitive(decomposed); + + return true; +} + +/** + An abstraction for decorator and filter declarations. + */ +struct EffectDeclarationView { + EffectDeclarationView() = default; + EffectDeclarationView(const DecoratorDeclaration& declaration) : + instancer(declaration.instancer), type(&declaration.type), properties(&declaration.properties), paint_area(declaration.paint_area) + {} + EffectDeclarationView(const NamedDecorator* named_decorator) : + instancer(named_decorator->instancer), type(&named_decorator->type), properties(&named_decorator->properties) + {} + EffectDeclarationView(const FilterDeclaration& declaration) : + instancer(declaration.instancer), type(&declaration.type), properties(&declaration.properties) + {} + + EffectSpecification* instancer = nullptr; + const String* type = nullptr; + const PropertyDictionary* properties = nullptr; + BoxArea paint_area = BoxArea::Auto; + + explicit operator bool() const { return instancer != nullptr; } +}; + +// Interpolate two effect declarations. One of them can be empty, in which case the empty one is replaced by default values. +static bool InterpolateEffectProperties(PropertyDictionary& properties, const EffectDeclarationView& d0, const EffectDeclarationView& d1, float alpha, + Element& element) +{ + if (d0 && d1) + { + // Both declarations are specified, check if they are compatible for interpolation. + if (!d0.instancer || d0.instancer != d1.instancer || *d0.type != *d1.type || + d0.properties->GetNumProperties() != d1.properties->GetNumProperties() || d0.paint_area != d1.paint_area) + return false; + + const auto& properties0 = d0.properties->GetProperties(); + const auto& properties1 = d1.properties->GetProperties(); + + for (const auto& pair0 : properties0) + { + const PropertyId id = pair0.first; + const Property& prop0 = pair0.second; + + auto it = properties1.find(id); + if (it == properties1.end()) + { + RMLUI_ERRORMSG("Incompatible decorator properties."); + return false; + } + const Property& prop1 = it->second; + + Property p = InterpolateProperties(prop0, prop1, alpha, element, prop0.definition); + p.definition = prop0.definition; + properties.SetProperty(id, p); + } + return true; + } + else if ((d0 && !d1) || (!d0 && d1)) + { + // One of the declarations is empty, interpolate against the default values of its type. + const auto& d_filled = (d0 ? d0 : d1); + + const PropertySpecification& specification = d_filled.instancer->GetPropertySpecification(); + const PropertyMap& properties_filled = d_filled.properties->GetProperties(); + + for (const auto& pair_filled : properties_filled) + { + const PropertyId id = pair_filled.first; + const PropertyDefinition* underlying_definition = specification.GetProperty(id); + if (!underlying_definition) + return false; + + const Property& p_filled = pair_filled.second; + const Property& p_default = *underlying_definition->GetDefaultValue(); + const Property& p_interp0 = (d0 ? p_filled : p_default); + const Property& p_interp1 = (d1 ? p_filled : p_default); + + Property p = InterpolateProperties(p_interp0, p_interp1, alpha, element, p_filled.definition); + p.definition = p_filled.definition; + properties.SetProperty(id, p); + } + return true; + } + + return false; +} + +static NumericValue InterpolateNumericValue(NumericValue v0, NumericValue v1, float alpha, Element& element, const PropertyDefinition* definition) +{ + // If we have the same units, we can simply interpolate regardless of what the value represents. + if (v0.unit == v1.unit) + return NumericValue{Mix(v0.number, v1.number, alpha), v0.unit}; + + // When mixing lengths or relative sizes, resolve them to pixel lengths and interpolate. This only works if we have a definition. + if (Any(v0.unit & Unit::NUMBER_LENGTH_PERCENT) && Any(v1.unit & Unit::NUMBER_LENGTH_PERCENT) && definition) + { + float f0 = element.GetStyle()->ResolveRelativeLength(v0, definition->GetRelativeTarget()); + float f1 = element.GetStyle()->ResolveRelativeLength(v1, definition->GetRelativeTarget()); + return NumericValue{Mix(f0, f1, alpha), Unit::PX}; + } + + // As long as we don't mix lengths and percentages, we can still resolve lengths without a definition. + if (Any(v0.unit & Unit::LENGTH) && Any(v1.unit & Unit::LENGTH)) + { + float f0 = element.ResolveLength(v0); + float f1 = element.ResolveLength(v0); + return NumericValue{Mix(f0, f1, alpha), Unit::PX}; + } + + if (Any(v0.unit & Unit::ANGLE) && Any(v1.unit & Unit::ANGLE)) + { + float f = Mix(ComputeAngle(v0), ComputeAngle(v1), alpha); + return NumericValue{f, Unit::RAD}; + } + + // Fall back to discrete interpolation for incompatible units. + return alpha < 0.5f ? v0 : v1; +} + +static Property InterpolateProperties(const Property& p0, const Property& p1, float alpha, Element& element, const PropertyDefinition* definition) +{ + const Property& p_discrete = (alpha < 0.5f ? p0 : p1); + + if (Any(p0.unit & Unit::NUMERIC) && Any(p1.unit & Unit::NUMERIC)) + { + NumericValue v = InterpolateNumericValue(p0.GetNumericValue(), p1.GetNumericValue(), alpha, element, definition); + return Property{v.number, v.unit}; + } + + if (p0.unit == Unit::KEYWORD && p1.unit == Unit::KEYWORD) + { + // Discrete interpolation, swap at alpha = 0.5. + // Special case for the 'visibility' and 'display' properties as in the CSS specs: + // If present, apply the 'visible' / non-'none' keyword during the entire transition period, i.e. alpha (0,1). + // See: https://www.w3.org/TR/css-display-4/#display-animation + if (definition && definition->GetId() == PropertyId::Visibility) + { + if (p0.Get() == (int)Style::Visibility::Visible) + return alpha < 1.f ? p0 : p1; + else if (p1.Get() == (int)Style::Visibility::Visible) + return alpha <= 0.f ? p0 : p1; + } + if (definition && definition->GetId() == PropertyId::Display) + { + if (p0.Get() == (int)Style::Display::None) + return alpha <= 0.f ? p0 : p1; + else if (p1.Get() == (int)Style::Display::None) + return alpha < 1.f ? p0 : p1; + } + + return p_discrete; + } + + if (p0.unit == Unit::COLOUR && p1.unit == Unit::COLOUR) + { + Colourb c = InterpolateColour(p0.value.Get(), p1.value.Get(), alpha); + return Property{c, Unit::COLOUR}; + } + + if (p0.unit == Unit::TRANSFORM && p1.unit == Unit::TRANSFORM) + { + auto& t0 = p0.value.GetReference(); + auto& t1 = p1.value.GetReference(); + + const auto& prim0 = t0->GetPrimitives(); + const auto& prim1 = t1->GetPrimitives(); + + if (prim0.size() != prim1.size()) + { + RMLUI_ERRORMSG("Transform primitives not of same size during interpolation. Were the transforms properly prepared for interpolation?"); + return Property{t0, Unit::TRANSFORM}; + } + + // Build the new, interpolating transform + UniquePtr t(new Transform); + t->GetPrimitives().reserve(t0->GetPrimitives().size()); + + for (size_t i = 0; i < prim0.size(); i++) + { + TransformPrimitive p = prim0[i]; + if (!TransformUtilities::InterpolateWith(p, prim1[i], alpha)) + { + RMLUI_ERRORMSG("Transform primitives can not be interpolated. Were the transforms properly prepared for interpolation?"); + return Property{t0, Unit::TRANSFORM}; + } + t->AddPrimitive(p); + } + + return Property{TransformPtr(std::move(t)), Unit::TRANSFORM}; + } + + if (p0.unit == Unit::DECORATOR && p1.unit == Unit::DECORATOR) + { + auto GetEffectDeclarationView = [](const Vector& declarations, size_t i, Element& element) -> EffectDeclarationView { + if (i >= declarations.size()) + return EffectDeclarationView(); + + const DecoratorDeclaration& declaration = declarations[i]; + if (declaration.instancer) + return EffectDeclarationView(declaration); + + // If we don't have a decorator instancer, then this should be a named @decorator, look for one now. + const StyleSheet* style_sheet = element.GetStyleSheet(); + if (!style_sheet) + return EffectDeclarationView(); + + const NamedDecorator* named_decorator = style_sheet->GetNamedDecorator(declaration.type); + if (!named_decorator) + { + Log::Message(Log::LT_WARNING, "Could not find a named @decorator '%s'.", declaration.type.c_str()); + return EffectDeclarationView(); + } + + return EffectDeclarationView(named_decorator); + }; + + auto& ptr0 = p0.value.GetReference(); + auto& ptr1 = p1.value.GetReference(); + if (!ptr0 || !ptr1) + { + RMLUI_ERRORMSG("Invalid decorator pointer, were the decorator keys properly prepared?"); + return p_discrete; + } + + // Build the new, interpolated decorator list. + const bool p0_bigger = ptr0->list.size() > ptr1->list.size(); + auto& big_list = (p0_bigger ? ptr0->list : ptr1->list); + auto decorator = MakeUnique(); + auto& list = decorator->list; + list.reserve(big_list.size()); + + for (size_t i = 0; i < big_list.size(); i++) + { + EffectDeclarationView d0 = GetEffectDeclarationView(ptr0->list, i, element); + EffectDeclarationView d1 = GetEffectDeclarationView(ptr1->list, i, element); + + const EffectDeclarationView& declaration = (p0_bigger ? d0 : d1); + list.push_back(DecoratorDeclaration{*declaration.type, static_cast(declaration.instancer), PropertyDictionary(), + declaration.paint_area}); + + if (!InterpolateEffectProperties(list.back().properties, d0, d1, alpha, element)) + return p_discrete; + } + + return Property{DecoratorsPtr(std::move(decorator)), Unit::DECORATOR}; + } + + if (p0.unit == Unit::FILTER && p1.unit == Unit::FILTER) + { + auto GetEffectDeclarationView = [](const Vector& declarations, size_t i) -> EffectDeclarationView { + if (i >= declarations.size()) + return EffectDeclarationView(); + return EffectDeclarationView(declarations[i]); + }; + + auto& ptr0 = p0.value.GetReference(); + auto& ptr1 = p1.value.GetReference(); + if (!ptr0 || !ptr1) + { + RMLUI_ERRORMSG("Invalid filter pointer, were the filter keys properly prepared?"); + return p_discrete; + } + + // Build the new, interpolated filter list. + const bool p0_bigger = ptr0->list.size() > ptr1->list.size(); + auto& big_list = (p0_bigger ? ptr0->list : ptr1->list); + auto filter = MakeUnique(); + auto& list = filter->list; + list.reserve(big_list.size()); + + for (size_t i = 0; i < big_list.size(); i++) + { + EffectDeclarationView d0 = GetEffectDeclarationView(ptr0->list, i); + EffectDeclarationView d1 = GetEffectDeclarationView(ptr1->list, i); + + const EffectDeclarationView& declaration = (p0_bigger ? d0 : d1); + list.push_back(FilterDeclaration{*declaration.type, static_cast(declaration.instancer), PropertyDictionary()}); + + if (!InterpolateEffectProperties(list.back().properties, d0, d1, alpha, element)) + return p_discrete; + } + + return Property{FiltersPtr(std::move(filter)), Unit::FILTER}; + } + + if (p0.unit == Unit::COLORSTOPLIST && p1.unit == Unit::COLORSTOPLIST) + { + RMLUI_ASSERT(p0.value.GetType() == Variant::COLORSTOPLIST && p1.value.GetType() == Variant::COLORSTOPLIST); + const auto& c0 = p0.value.GetReference(); + const auto& c1 = p1.value.GetReference(); + + if (c0.size() != c1.size()) + return p_discrete; + + const size_t N = c0.size(); + ColorStopList result(N); + + for (size_t i = 0; i < N; i++) + { + result[i].color = InterpolateColour(c0[i].color.ToNonPremultiplied(), c1[i].color.ToNonPremultiplied(), alpha).ToPremultiplied(); + + // We don't provide the property definition in the following, because it doesn't actually represent how + // percentages are resolved for stop positions. Here, we don't trivially know how they are resolved, so if + // users try to mix lengths and percentages, we instead fall back to discrete interpolation. See the + // gradient decorators for how stop positions are resolved. + result[i].position = InterpolateNumericValue(c0[i].position, c1[i].position, alpha, element, nullptr); + } + + return Property{std::move(result), Unit::COLORSTOPLIST}; + } + + // Fall back to discrete interpolation for incompatible units. + return p_discrete; +} + +enum class PrepareTransformResult { Unchanged = 0, ChangedT0 = 1, ChangedT1 = 2, ChangedT0andT1 = 3, Invalid = 4 }; + +static PrepareTransformResult PrepareTransformPair(Transform& t0, Transform& t1, Element& element) +{ + using namespace Transforms; + + // Insert or modify primitives such that the two transforms match exactly in both number of and types of primitives. + // Based largely on https://drafts.csswg.org/css-transforms-1/#interpolation-of-transforms + + auto& prims0 = t0.GetPrimitives(); + auto& prims1 = t1.GetPrimitives(); + + // Check for trivial case where they contain the same primitives + if (prims0.size() == prims1.size()) + { + PrepareTransformResult result = PrepareTransformResult::Unchanged; + bool same_primitives = true; + + for (size_t i = 0; i < prims0.size(); i++) + { + auto p0_type = prims0[i].type; + auto p1_type = prims1[i].type; + + // See if they are the same or can be converted to a matching generic type. + if (TransformUtilities::TryConvertToMatchingGenericType(prims0[i], prims1[i])) + { + if (prims0[i].type != p0_type) + result = PrepareTransformResult((int)result | (int)PrepareTransformResult::ChangedT0); + if (prims1[i].type != p1_type) + result = PrepareTransformResult((int)result | (int)PrepareTransformResult::ChangedT1); + } + else + { + same_primitives = false; + break; + } + } + if (same_primitives) + return result; + } + + if (prims0.size() != prims1.size()) + { + // Try to match the smallest set of primitives to the larger set, set missing keys in the small set to identity. + // Requirement: The small set must match types in the same order they appear in the big set. + // Example: (letter indicates type, number represents values) + // big: a0 b0 c0 b1 + // ^ ^ + // small: b2 b3 + // ^ ^ + // new small: a1 b2 c1 b3 + bool prims0_smallest = (prims0.size() < prims1.size()); + + auto& small = (prims0_smallest ? prims0 : prims1); + auto& big = (prims0_smallest ? prims1 : prims0); + + Vector matching_indices; // Indices into 'big' for matching types + matching_indices.reserve(small.size() + 1); + + size_t i_big = 0; + bool match_success = true; + bool changed_big = false; + + // Iterate through the small set to see if its types fit into the big set + for (size_t i_small = 0; i_small < small.size(); i_small++) + { + match_success = false; + + for (; i_big < big.size(); i_big++) + { + auto big_type = big[i_big].type; + + if (TransformUtilities::TryConvertToMatchingGenericType(small[i_small], big[i_big])) + { + // They matched exactly or in their more generic form. One or both primitives may have been converted. + if (big[i_big].type != big_type) + changed_big = true; + + matching_indices.push_back(i_big); + match_success = true; + i_big += 1; + break; + } + } + + if (!match_success) + break; + } + + if (match_success) + { + // Success, insert the missing primitives into the small set + matching_indices.push_back(big.size()); // Needed to copy elements behind the last matching primitive + small.reserve(big.size()); + size_t i0 = 0; + for (size_t match_index : matching_indices) + { + for (size_t i = i0; i < match_index; i++) + { + TransformPrimitive p = big[i]; + TransformUtilities::SetIdentity(p); + small.insert(small.begin() + i, p); + } + + // Next value to copy is one-past the matching primitive + i0 = match_index + 1; + } + + // The small set has always been changed if we get here, but the big set is only changed + // if one or more of its primitives were converted to a general form. + if (changed_big) + return PrepareTransformResult::ChangedT0andT1; + + return (prims0_smallest ? PrepareTransformResult::ChangedT0 : PrepareTransformResult::ChangedT1); + } + } + + // If we get here, things get tricky. Need to do full matrix interpolation. + // In short, we decompose the Transforms into translation, rotation, scale, skew and perspective components. + // Then, during update, interpolate these components and combine into a new transform matrix. + if (!CombineAndDecompose(t0, element)) + return PrepareTransformResult::Invalid; + if (!CombineAndDecompose(t1, element)) + return PrepareTransformResult::Invalid; + + return PrepareTransformResult::ChangedT0andT1; +} + +static bool PrepareTransforms(Vector& keys, Element& element, int start_index) +{ + bool result = true; + + // Prepare each transform individually. + for (int i = start_index; i < (int)keys.size(); i++) + { + Property& property = keys[i].property; + RMLUI_ASSERT(property.value.GetType() == Variant::TRANSFORMPTR); + + if (!property.value.GetReference()) + property.value = MakeShared(); + + bool must_decompose = false; + Transform& transform = *property.value.GetReference(); + + for (TransformPrimitive& primitive : transform.GetPrimitives()) + { + if (!TransformUtilities::PrepareForInterpolation(primitive, element)) + { + must_decompose = true; + break; + } + } + + if (must_decompose) + result &= CombineAndDecompose(transform, element); + } + + if (!result) + return false; + + // We don't need to prepare the transforms pairwise if we only have a single key added so far. + if (keys.size() < 2 || start_index < 1) + return true; + + // Now, prepare the transforms pair-wise so they can be interpolated. + const int N = (int)keys.size(); + + int count_iterations = -1; + const int max_iterations = 3 * N; + + Vector dirty_list(N + 1, false); + dirty_list[start_index] = true; + + // For each pair of keys, match the transform primitives such that they can be interpolated during animation update + for (int i = start_index; i < N && count_iterations < max_iterations; count_iterations++) + { + if (!dirty_list[i]) + { + ++i; + continue; + } + + auto& prop0 = keys[i - 1].property; + auto& prop1 = keys[i].property; + + if (prop0.unit != Unit::TRANSFORM || prop1.unit != Unit::TRANSFORM) + return false; + + auto& t0 = prop0.value.GetReference(); + auto& t1 = prop1.value.GetReference(); + + auto prepare_result = PrepareTransformPair(*t0, *t1, element); + + if (prepare_result == PrepareTransformResult::Invalid) + return false; + + bool changed_t0 = ((int)prepare_result & (int)PrepareTransformResult::ChangedT0); + bool changed_t1 = ((int)prepare_result & (int)PrepareTransformResult::ChangedT1); + + dirty_list[i] = false; + dirty_list[i - 1] = dirty_list[i - 1] || changed_t0; + dirty_list[i + 1] = dirty_list[i + 1] || changed_t1; + + if (changed_t0 && i > 1) + --i; + else + ++i; + } + + // Something has probably gone wrong if we exceeded max_iterations, possibly a bug in PrepareTransformPair() + return (count_iterations < max_iterations); +} + +static void PrepareDecorator(AnimationKey& key) +{ + Property& property = key.property; + RMLUI_ASSERT(property.value.GetType() == Variant::DECORATORSPTR); + + if (!property.value.GetReference()) + property.value = MakeShared(); +} +static void PrepareFilter(AnimationKey& key) +{ + Property& property = key.property; + RMLUI_ASSERT(property.value.GetType() == Variant::FILTERSPTR); + + if (!property.value.GetReference()) + property.value = MakeShared(); +} + +ElementAnimation::ElementAnimation(PropertyId property_id, ElementAnimationOrigin origin, const Property& current_value, Element& element, + double start_world_time, float duration, int num_iterations, bool alternate_direction) : + property_id(property_id), duration(duration), num_iterations(num_iterations), alternate_direction(alternate_direction), + last_update_world_time(start_world_time), origin(origin) +{ + if (!current_value.definition) + { + Log::Message(Log::LT_WARNING, "Property in animation key did not have a definition (while adding key '%s').", + current_value.ToString().c_str()); + } + InternalAddKey(0.0f, current_value, element, Tween{}); +} + +bool ElementAnimation::InternalAddKey(float time, const Property& in_property, Element& element, Tween tween) +{ + const Units valid_units = + (Unit::NUMBER_LENGTH_PERCENT | Unit::ANGLE | Unit::COLOUR | Unit::TRANSFORM | Unit::KEYWORD | Unit::DECORATOR | Unit::FILTER); + + if (!Any(in_property.unit & valid_units)) + { + const char* property_type = (in_property.unit == Unit::BOXSHADOWLIST ? "Box shadows do not" : "Property value does not"); + Log::Message(Log::LT_WARNING, "%s support animations or transitions. Value: %s", property_type, in_property.ToString().c_str()); + return false; + } + + keys.emplace_back(time, in_property, tween); + Property& property = keys.back().property; + bool result = true; + + if (property.unit == Unit::TRANSFORM) + { + result = PrepareTransforms(keys, element, (int)keys.size() - 1); + } + else if (property.unit == Unit::DECORATOR) + { + PrepareDecorator(keys.back()); + } + else if (property.unit == Unit::FILTER) + { + PrepareFilter(keys.back()); + } + + if (!result) + { + Log::Message(Log::LT_WARNING, "Could not add animation key with property '%s'.", in_property.ToString().c_str()); + keys.pop_back(); + } + + return result; +} + +bool ElementAnimation::AddKey(float target_time, const Property& in_property, Element& element, Tween tween, bool extend_duration) +{ + if (!IsInitalized()) + { + Log::Message(Log::LT_WARNING, "Element animation was not initialized properly, can't add key."); + return false; + } + if (!InternalAddKey(target_time, in_property, element, tween)) + { + return false; + } + + if (extend_duration) + duration = target_time; + + return true; +} + +float ElementAnimation::GetInterpolationFactorAndKeys(int* out_key0, int* out_key1) const +{ + float t = time_since_iteration_start; + + if (reverse_direction) + t = duration - t; + + int key0 = -1; + int key1 = -1; + + { + for (int i = 0; i < (int)keys.size(); i++) + { + if (keys[i].time >= t) + { + key1 = i; + break; + } + } + + if (key1 < 0) + key1 = (int)keys.size() - 1; + key0 = (key1 == 0 ? 0 : key1 - 1); + } + + RMLUI_ASSERT(key0 >= 0 && key0 < (int)keys.size() && key1 >= 0 && key1 < (int)keys.size()); + + float alpha = 0.0f; + + { + const float t0 = keys[key0].time; + const float t1 = keys[key1].time; + + const float eps = 1e-3f; + + if (t1 - t0 > eps) + alpha = (t - t0) / (t1 - t0); + + alpha = Math::Clamp(alpha, 0.0f, 1.0f); + } + + alpha = keys[key1].tween(alpha); + + if (out_key0) + *out_key0 = key0; + if (out_key1) + *out_key1 = key1; + + return alpha; +} + +Property ElementAnimation::UpdateAndGetProperty(double world_time, Element& element) +{ + float dt = float(world_time - last_update_world_time); + if (keys.size() < 2 || animation_complete || dt <= 0.0f) + return Property{}; + + dt = Math::Min(dt, 0.1f); + + last_update_world_time = world_time; + time_since_iteration_start += dt; + + if (time_since_iteration_start >= duration) + { + // Next iteration + current_iteration += 1; + + if (num_iterations == -1 || (current_iteration >= 0 && current_iteration < num_iterations)) + { + time_since_iteration_start -= duration; + + if (alternate_direction) + reverse_direction = !reverse_direction; + } + else + { + animation_complete = true; + time_since_iteration_start = duration; + } + } + + int key0 = -1; + int key1 = -1; + + float alpha = GetInterpolationFactorAndKeys(&key0, &key1); + + Property result = InterpolateProperties(keys[key0].property, keys[key1].property, alpha, element, keys[0].property.definition); + + return result; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementAnimation.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementAnimation.h new file mode 100644 index 00000000..c0420129 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementAnimation.h @@ -0,0 +1,62 @@ +#pragma once + +#include "../../Include/RmlUi/Core/Header.h" +#include "../../Include/RmlUi/Core/Property.h" +#include "../../Include/RmlUi/Core/Tween.h" + +namespace Rml { + +struct AnimationKey { + AnimationKey(float time, const Property& property, Tween tween) : time(time), property(property), tween(tween) {} + float time; // Local animation time (Zero means the time when the animation iteration starts) + Property property; + Tween tween; // Tweening between the previous and this key. Ignored for the first animation key. +}; + +// The origin is tracked for determining its behavior when adding and removing animations. +// User: Animation started by the Element API +// Animation: Animation started by the 'animation' property +// Transition: Animation started by the 'transition' property +enum class ElementAnimationOrigin : uint8_t { User, Animation, Transition }; + +class ElementAnimation { +private: + PropertyId property_id = PropertyId::Invalid; + + float duration = 0; // for a single iteration + int num_iterations = 0; // -1 for infinity + bool alternate_direction = false; // between iterations + + Vector keys; + + double last_update_world_time = 0; + float time_since_iteration_start = 0; + int current_iteration = 0; + bool reverse_direction = false; + + bool animation_complete = false; + ElementAnimationOrigin origin = ElementAnimationOrigin::User; + + bool InternalAddKey(float time, const Property& property, Element& element, Tween tween); + + float GetInterpolationFactorAndKeys(int* out_key0, int* out_key1) const; + +public: + ElementAnimation() {} + ElementAnimation(PropertyId property_id, ElementAnimationOrigin origin, const Property& current_value, Element& element, double start_world_time, + float duration, int num_iterations, bool alternate_direction); + + bool AddKey(float target_time, const Property& property, Element& element, Tween tween, bool extend_duration); + + Property UpdateAndGetProperty(double time, Element& element); + + PropertyId GetPropertyId() const { return property_id; } + float GetDuration() const { return duration; } + bool IsComplete() const { return animation_complete; } + bool IsTransition() const { return origin == ElementAnimationOrigin::Transition; } + bool IsInitalized() const { return !keys.empty(); } + float GetInterpolationFactor() const { return GetInterpolationFactorAndKeys(nullptr, nullptr); } + ElementAnimationOrigin GetOrigin() const { return origin; } +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementBackgroundBorder.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementBackgroundBorder.cpp new file mode 100644 index 00000000..409b1d8a --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementBackgroundBorder.cpp @@ -0,0 +1,140 @@ +#include "ElementBackgroundBorder.h" +#include "../../Include/RmlUi/Core/Box.h" +#include "../../Include/RmlUi/Core/ComputedValues.h" +#include "../../Include/RmlUi/Core/Context.h" +#include "../../Include/RmlUi/Core/DecorationTypes.h" +#include "../../Include/RmlUi/Core/Element.h" +#include "../../Include/RmlUi/Core/MeshUtilities.h" +#include "../../Include/RmlUi/Core/Profiling.h" +#include "../../Include/RmlUi/Core/RenderManager.h" +#include "BoxShadowCache.h" +#include "GeometryBoxShadow.h" + +namespace Rml { + +ElementBackgroundBorder::ElementBackgroundBorder() {} + +void ElementBackgroundBorder::Render(Element* element) +{ + if (background_dirty || border_dirty) + { + for (auto& background : backgrounds) + { + if (background.first != BackgroundType::BackgroundBorder) + background.second.geometry.Release(); + } + + GenerateGeometry(element); + + background_dirty = false; + border_dirty = false; + } + + if (Background* shadow = GetBackground(BackgroundType::BoxShadowAndBackgroundBorder)) + { + const Vector2f offset = element->GetAbsoluteOffset(BoxArea::Border); + shadow->box_shadow_and_background_border->geometry.Render(offset, shadow->box_shadow_and_background_border->texture); + } + else if (Background* background = GetBackground(BackgroundType::BackgroundBorder)) + { + const Vector2f offset = element->GetAbsoluteOffset(BoxArea::Border); + background->geometry.Render(offset); + } +} + +void ElementBackgroundBorder::DirtyBackground() +{ + background_dirty = true; +} + +void ElementBackgroundBorder::DirtyBorder() +{ + border_dirty = true; +} + +Geometry* ElementBackgroundBorder::GetClipGeometry(Element* element, BoxArea clip_area) +{ + BackgroundType type = {}; + switch (clip_area) + { + case Rml::BoxArea::Border: type = BackgroundType::ClipBorder; break; + case Rml::BoxArea::Padding: type = BackgroundType::ClipPadding; break; + case Rml::BoxArea::Content: type = BackgroundType::ClipContent; break; + default: RMLUI_ERROR; return nullptr; + } + + RenderManager* render_manager = element->GetRenderManager(); + Geometry& geometry = GetOrCreateBackground(type).geometry; + if (render_manager && !geometry) + { + Mesh mesh = geometry.Release(Geometry::ReleaseMode::ClearMesh); + MeshUtilities::GenerateBackground(mesh, element->GetRenderBox(clip_area), ColourbPremultiplied(255)); + geometry = render_manager->MakeGeometry(std::move(mesh)); + } + + return &geometry; +} + +ElementBackgroundBorder::Background* ElementBackgroundBorder::GetBackground(BackgroundType type) +{ + auto it = backgrounds.find(type); + if (it != backgrounds.end()) + return &it->second; + return nullptr; +} + +ElementBackgroundBorder::Background& ElementBackgroundBorder::GetOrCreateBackground(BackgroundType type) +{ + auto it = backgrounds.find(type); + if (it != backgrounds.end()) + return it->second; + + Background& background = backgrounds[type]; + return background; +} + +void ElementBackgroundBorder::EraseBackground(BackgroundType type) +{ + backgrounds.erase(type); +} + +void ElementBackgroundBorder::GenerateGeometry(Element* element) +{ + RMLUI_ZoneScoped; + RenderManager* render_manager = element->GetRenderManager(); + if (!render_manager) + return; + + const ComputedValues& computed = element->GetComputedValues(); + const bool has_box_shadow = computed.has_box_shadow(); + + if (has_box_shadow) + { + // The box shadow geometry also includes the element's background and border, thus we can skip the normal background generation. + EraseBackground(BackgroundType::BackgroundBorder); + Background& shadow_background = GetOrCreateBackground(BackgroundType::BoxShadowAndBackgroundBorder); + shadow_background.box_shadow_and_background_border = BoxShadowCache::GetHandle(element, computed); + return; + } + + EraseBackground(BackgroundType::BoxShadowAndBackgroundBorder); + + const float opacity = computed.opacity(); + ColourbPremultiplied background_color = computed.background_color().ToPremultiplied(opacity); + Array border_colors = { + computed.border_top_color().ToPremultiplied(opacity), + computed.border_right_color().ToPremultiplied(opacity), + computed.border_bottom_color().ToPremultiplied(opacity), + computed.border_left_color().ToPremultiplied(opacity), + }; + + Geometry& geometry = GetOrCreateBackground(BackgroundType::BackgroundBorder).geometry; + Mesh mesh = geometry.Release(Geometry::ReleaseMode::ClearMesh); + + for (int i = 0; i < element->GetNumBoxes(); i++) + MeshUtilities::GenerateBackgroundBorder(mesh, element->GetRenderBox(BoxArea::Padding, i), background_color, border_colors.data()); + + geometry = render_manager->MakeGeometry(std::move(mesh)); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementBackgroundBorder.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementBackgroundBorder.h new file mode 100644 index 00000000..d0ffcaf6 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementBackgroundBorder.h @@ -0,0 +1,41 @@ +#pragma once + +#include "../../Include/RmlUi/Core/CallbackTexture.h" +#include "../../Include/RmlUi/Core/Geometry.h" +#include "../../Include/RmlUi/Core/Types.h" + +namespace Rml { + +struct BoxShadowRenderable; + +class ElementBackgroundBorder { +public: + ElementBackgroundBorder(); + void Render(Element* element); + + void DirtyBackground(); + void DirtyBorder(); + + Geometry* GetClipGeometry(Element* element, BoxArea clip_area); + +private: + enum class BackgroundType { BackgroundBorder, BoxShadowAndBackgroundBorder, ClipBorder, ClipPadding, ClipContent, Count }; + struct Background { + Geometry geometry; + Texture texture; + SharedPtr box_shadow_and_background_border; + }; + + Background* GetBackground(BackgroundType type); + Background& GetOrCreateBackground(BackgroundType type); + void EraseBackground(BackgroundType type); + + void GenerateGeometry(Element* element); + + bool background_dirty = false; + bool border_dirty = false; + + StableMap backgrounds; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementDefinition.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementDefinition.cpp new file mode 100644 index 00000000..2ef5a878 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementDefinition.cpp @@ -0,0 +1,27 @@ +#include "ElementDefinition.h" +#include "../../Include/RmlUi/Core/PropertyIdSet.h" +#include "StyleSheetNode.h" + +namespace Rml { + +ElementDefinition::ElementDefinition(const Vector& style_sheet_nodes) +{ + // Initialises the element definition from the list of style sheet nodes. + for (size_t i = 0; i < style_sheet_nodes.size(); ++i) + properties.Merge(style_sheet_nodes[i]->GetProperties()); + + for (auto& property : properties.GetProperties()) + property_ids.Insert(property.first); +} + +const Property* ElementDefinition::GetProperty(PropertyId id) const +{ + return properties.GetProperty(id); +} + +const PropertyIdSet& ElementDefinition::GetPropertyIds() const +{ + return property_ids; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementDefinition.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementDefinition.h new file mode 100644 index 00000000..736f1e26 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementDefinition.h @@ -0,0 +1,35 @@ +#pragma once + +#include "../../Include/RmlUi/Core/PropertyDictionary.h" +#include "../../Include/RmlUi/Core/PropertyIdSet.h" +#include "../../Include/RmlUi/Core/Traits.h" + +namespace Rml { + +class StyleSheetNode; +class ElementDefinitionIterator; + +/** + ElementDefinition provides an element's applicable properties from its stylesheet. + */ + +class ElementDefinition : public NonCopyMoveable { +public: + ElementDefinition(const Vector& style_sheet_nodes); + + /// Returns a specific property from the element definition. + /// @param[in] id The id of the property to return. + /// @return The property defined against the give name, or nullptr if no such property was found. + const Property* GetProperty(PropertyId id) const; + + /// Returns the list of property ids this element definition defines. + const PropertyIdSet& GetPropertyIds() const; + + const PropertyDictionary& GetProperties() const { return properties; } + +private: + PropertyDictionary properties; + PropertyIdSet property_ids; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementDocument.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementDocument.cpp new file mode 100644 index 00000000..48dbe1c9 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementDocument.cpp @@ -0,0 +1,819 @@ +#include "../../Include/RmlUi/Core/ElementDocument.h" +#include "../../Include/RmlUi/Core/Context.h" +#include "../../Include/RmlUi/Core/ElementText.h" +#include "../../Include/RmlUi/Core/Factory.h" +#include "../../Include/RmlUi/Core/Profiling.h" +#include "../../Include/RmlUi/Core/StreamMemory.h" +#include "../../Include/RmlUi/Core/StyleSheet.h" +#include "../../Include/RmlUi/Core/StyleSheetContainer.h" +#include "DocumentHeader.h" +#include "ElementStyle.h" +#include "EventDispatcher.h" +#include "Layout/LayoutDetails.h" +#include "Layout/LayoutEngine.h" +#include "StreamFile.h" +#include "StyleSheetFactory.h" +#include "Template.h" +#include "TemplateCache.h" +#include "XMLParseTools.h" +#include + +namespace Rml { + +enum class NavigationSearchDirection { Up, Down, Left, Right }; + +namespace { + constexpr int Infinite = INT_MAX; + + enum class CanFocus { Yes, No, NoAndNoChildren }; + + CanFocus CanFocusElement(Element* element) + { + if (!element->IsVisible()) + return CanFocus::NoAndNoChildren; + + const ComputedValues& computed = element->GetComputedValues(); + + if (computed.focus() == Style::Focus::None) + return CanFocus::NoAndNoChildren; + + if (computed.tab_index() == Style::TabIndex::Auto) + return CanFocus::Yes; + + return CanFocus::No; + } + + bool IsScrollContainer(Element* element) + { + const auto& computed = element->GetComputedValues(); + return LayoutDetails::IsScrollContainer(computed.overflow_x(), computed.overflow_y()); + } + + int GetNavigationHeuristic(const Rectanglef& source, const Rectanglef& target, NavigationSearchDirection direction) + { + enum Axis { Horizontal = 0, Vertical = 1 }; + + auto CalculateHeuristic = [](Axis axis, const Rectanglef& a, const Rectanglef& b) -> int { + // The heuristic is mainly the distance from the source to the target along the specified direction. In + // addition, the following factor determines the penalty for being outside the projected area of the element in + // the given direction, as a multiplier of the cross-axis distance between the target and projected area. + static constexpr int CrossAxisFactor = 10'000; + + const int main_axis = int(a.p0[axis] - b.p1[axis]); + if (main_axis < 0) + return Infinite; + + const Axis cross = Axis((axis + 1) % 2); + const int cross_axis = Math::Max(0, int(b.p0[cross] - a.p1[cross])) + Math::Max(0, int(a.p0[cross] - b.p1[cross])); + + return main_axis + CrossAxisFactor * cross_axis; + }; + + switch (direction) + { + case NavigationSearchDirection::Up: return CalculateHeuristic(Vertical, source, target); + case NavigationSearchDirection::Down: return CalculateHeuristic(Vertical, target, source); + case NavigationSearchDirection::Right: return CalculateHeuristic(Horizontal, target, source); + case NavigationSearchDirection::Left: return CalculateHeuristic(Horizontal, source, target); + } + + RMLUI_ERROR; + return Infinite; + } + + struct SearchNavigationResult { + Element* element = nullptr; + int heuristic = Infinite; + }; + + // Search all descendents to determine which element minimizes the navigation heuristic. + void SearchNavigationTarget(SearchNavigationResult& best_result, Element* element, NavigationSearchDirection direction, + const Rectanglef& bounding_box, Element* exclude_element) + { + const int num_children = element->GetNumChildren(); + for (int child_index = 0; child_index < num_children; child_index++) + { + Element* child = element->GetChild(child_index); + if (child == exclude_element) + continue; + + const CanFocus can_focus = CanFocusElement(child); + if (can_focus == CanFocus::Yes) + { + const Vector2f position = child->GetAbsoluteOffset(BoxArea::Border); + const Rectanglef target_box = Rectanglef::FromPositionSize(position, child->GetBox().GetSize(BoxArea::Border)); + + const int heuristic = GetNavigationHeuristic(bounding_box, target_box, direction); + if (heuristic < best_result.heuristic) + { + best_result.element = child; + best_result.heuristic = heuristic; + } + } + else if (can_focus == CanFocus::NoAndNoChildren || IsScrollContainer(child)) + { + continue; + } + + SearchNavigationTarget(best_result, child, direction, bounding_box, exclude_element); + } + } + +} // namespace + +ElementDocument::ElementDocument(const String& tag) : Element(tag) +{ + context = nullptr; + + modal = false; + focusable_from_modal = false; + + layout_dirty = true; + position_dirty = false; + + ForceLocalStackingContext(); + SetOwnerDocument(this); + + SetProperty(PropertyId::Position, Property(Style::Position::Absolute)); +} + +ElementDocument::~ElementDocument() = default; + +void ElementDocument::ProcessHeader(const DocumentHeader* document_header) +{ + RMLUI_ZoneScoped; + + // Store the source address that we came from + source_url = document_header->source; + + // Construct a new header and copy the template details across + DocumentHeader header; + header.MergePaths(header.template_resources, document_header->template_resources, document_header->source); + + // Merge in any templates, note a merge may cause more templates to merge + for (size_t i = 0; i < header.template_resources.size(); i++) + { + Template* merge_template = TemplateCache::LoadTemplate(URL(header.template_resources[i]).GetURL()); + + if (merge_template) + header.MergeHeader(*merge_template->GetHeader()); + else + Log::Message(Log::LT_WARNING, "Template %s not found", header.template_resources[i].c_str()); + } + + // Merge the document's header last, as it is the most overriding. + header.MergeHeader(*document_header); + + // Set the title to the document title. + title = document_header->title; + + // If a style-sheet (or sheets) has been specified for this element, then we load them and set the combined sheet + // on the element; all of its children will inherit it by default. + SharedPtr new_style_sheet; + + // Combine any inline sheets. + for (const DocumentHeader::Resource& rcss : header.rcss) + { + if (rcss.is_inline) + { + auto inline_sheet = MakeShared(); + auto stream = MakeUnique((const byte*)rcss.content.c_str(), rcss.content.size()); + stream->SetSourceURL(rcss.path); + + if (inline_sheet->LoadStyleSheetContainer(stream.get(), rcss.line)) + { + if (new_style_sheet) + new_style_sheet->MergeStyleSheetContainer(*inline_sheet); + else + new_style_sheet = std::move(inline_sheet); + } + + stream.reset(); + } + else + { + const StyleSheetContainer* sub_sheet = StyleSheetFactory::GetStyleSheetContainer(rcss.path); + if (sub_sheet) + { + if (new_style_sheet) + new_style_sheet->MergeStyleSheetContainer(*sub_sheet); + else + new_style_sheet = sub_sheet->CombineStyleSheetContainer(StyleSheetContainer()); + } + else + Log::Message(Log::LT_ERROR, "Failed to load style sheet %s.", rcss.path.c_str()); + } + } + + // If a style sheet is available, set it on the document. + if (new_style_sheet) + SetStyleSheetContainer(std::move(new_style_sheet)); + + // Load scripts. + for (const DocumentHeader::Resource& script : header.scripts) + { + if (script.is_inline) + { + LoadInlineScript(script.content, script.path, script.line); + } + else + { + LoadExternalScript(script.path); + } + } + + // Hide this document. + SetProperty(PropertyId::Visibility, Property(Style::Visibility::Hidden)); + + const float dp_ratio = (context ? context->GetDensityIndependentPixelRatio() : 1.0f); + const Vector2f vp_dimensions = (context ? Vector2f(context->GetDimensions()) : Vector2f(1.0f)); + + // Update properties so that e.g. visibility status can be queried properly immediately. + UpdateProperties(dp_ratio, vp_dimensions); +} + +Context* ElementDocument::GetContext() +{ + return context; +} + +void ElementDocument::SetTitle(const String& _title) +{ + title = _title; +} + +const String& ElementDocument::GetTitle() const +{ + return title; +} + +const String& ElementDocument::GetSourceURL() const +{ + return source_url; +} + +const StyleSheet* ElementDocument::GetStyleSheet() const +{ + if (style_sheet_container) + return style_sheet_container->GetCompiledStyleSheet(); + return nullptr; +} + +const StyleSheetContainer* ElementDocument::GetStyleSheetContainer() const +{ + return style_sheet_container.get(); +} + +void ElementDocument::SetStyleSheetContainer(SharedPtr _style_sheet_container) +{ + RMLUI_ZoneScoped; + + if (style_sheet_container == _style_sheet_container) + return; + + style_sheet_container = std::move(_style_sheet_container); + + DirtyMediaQueries(); +} + +void ElementDocument::ReloadStyleSheet() +{ + if (!context) + return; + + auto stream = MakeUnique(); + if (!stream->Open(source_url)) + { + Log::Message(Log::LT_WARNING, "Failed to open file to reload style sheet in document: %s", source_url.c_str()); + return; + } + + Factory::ClearStyleSheetCache(); + Factory::ClearTemplateCache(); + ElementPtr temp_doc = Factory::InstanceDocumentStream(nullptr, stream.get(), context->GetDocumentsBaseTag()); + if (!temp_doc) + { + Log::Message(Log::LT_WARNING, "Failed to reload style sheet, could not instance document: %s", source_url.c_str()); + return; + } + + SetStyleSheetContainer(rmlui_static_cast(temp_doc.get())->style_sheet_container); +} + +void ElementDocument::DirtyMediaQueries() +{ + if (context && style_sheet_container) + { + const bool changed_style_sheet = style_sheet_container->UpdateCompiledStyleSheet(context); + + if (changed_style_sheet) + { + DirtyDefinition(Element::DirtyNodes::Self); + OnStyleSheetChangeRecursive(); + } + } +} + +void ElementDocument::PullToFront() +{ + if (context != nullptr) + context->PullDocumentToFront(this); +} + +void ElementDocument::PushToBack() +{ + if (context != nullptr) + context->PushDocumentToBack(this); +} + +void ElementDocument::Show(ModalFlag modal_flag, FocusFlag focus_flag, ScrollFlag scroll_flag) +{ + switch (modal_flag) + { + case ModalFlag::None: modal = false; break; + case ModalFlag::Modal: modal = true; break; + case ModalFlag::Keep: break; + } + + bool focus = false; + bool autofocus = false; + bool focus_previous = false; + + switch (focus_flag) + { + case FocusFlag::None: break; + case FocusFlag::Document: focus = true; break; + case FocusFlag::Keep: + focus = true; + focus_previous = true; + break; + case FocusFlag::Auto: + focus = true; + autofocus = true; + break; + } + + // Set to visible and switch focus if necessary. + SetProperty(PropertyId::Visibility, Property(Style::Visibility::Visible)); + + // Update the document now, otherwise the focusing methods below do not think we are visible. This is also important + // to ensure correct layout for any event handlers, such as for focused input fields to submit the proper caret + // position. + UpdateDocument(); + + if (focus) + { + Element* focus_element = this; + + if (autofocus) + { + Element* first_element = nullptr; + Element* element = FindNextTabElement(this, true); + + while (element && element != first_element) + { + if (!first_element) + first_element = element; + + if (element->HasAttribute("autofocus")) + { + focus_element = element; + break; + } + + element = FindNextTabElement(element, true); + } + } + else if (focus_previous) + { + focus_element = GetFocusLeafNode(); + } + + // Focus the window or element + bool focused = focus_element->Focus(true); + if (focused && focus_element != this && scroll_flag == ScrollFlag::Auto) + focus_element->ScrollIntoView(false); + } + + DispatchEvent(EventId::Show, Dictionary()); +} + +void ElementDocument::Hide() +{ + SetProperty(PropertyId::Visibility, Property(Style::Visibility::Hidden)); + + // We should update the document now, so that the (un)focusing will get the correct visibility + UpdateDocument(); + + DispatchEvent(EventId::Hide, Dictionary()); + + if (context) + { + context->UnfocusDocument(this); + } +} + +void ElementDocument::Close() +{ + if (context != nullptr) + context->UnloadDocument(this); +} + +ElementPtr ElementDocument::CreateElement(const String& name) +{ + return Factory::InstanceElement(nullptr, name, name, XMLAttributes()); +} + +ElementPtr ElementDocument::CreateTextNode(const String& text) +{ + // Create the element. + ElementPtr element = CreateElement("#text"); + if (!element) + { + Log::Message(Log::LT_ERROR, "Failed to create text element, instancer returned nullptr."); + return nullptr; + } + + // Cast up + ElementText* element_text = rmlui_dynamic_cast(element.get()); + if (!element_text) + { + Log::Message(Log::LT_ERROR, "Failed to create text element, instancer didn't return a derivative of ElementText."); + return nullptr; + } + + // Set the text + element_text->SetText(text); + + return element; +} + +bool ElementDocument::IsModal() const +{ + return modal && IsVisible(); +} + +void ElementDocument::LoadInlineScript(const String& /*content*/, const String& /*source_path*/, int /*line*/) {} + +void ElementDocument::LoadExternalScript(const String& /*source_path*/) {} + +void ElementDocument::UpdateDocument() +{ + const float dp_ratio = (context ? context->GetDensityIndependentPixelRatio() : 1.0f); + const Vector2f vp_dimensions = (context ? Vector2f(context->GetDimensions()) : Vector2f(1.0f)); + Update(dp_ratio, vp_dimensions); + UpdateLayout(); + UpdatePosition(); +} + +void ElementDocument::UpdateLayout() +{ + // Note: Carefully consider when to call this function for performance reasons. + // Ideally, only called once per update loop. + if (layout_dirty) + { + RMLUI_ZoneScoped; + RMLUI_ZoneText(source_url.c_str(), source_url.size()); + + Vector2f containing_block(0, 0); + if (GetParentNode() != nullptr) + containing_block = GetParentNode()->GetBox().GetSize(); + + LayoutEngine::FormatElement(this, containing_block); + + // Ignore dirtied layout during document formatting. Layouting must not require re-iteration. + // In particular, scrollbars being enabled may set the dirty flag, but this case is already handled within the layout engine. + layout_dirty = false; + } +} + +void ElementDocument::UpdatePosition() +{ + if (position_dirty) + { + RMLUI_ZoneScoped; + + position_dirty = false; + + Element* root = GetParentNode(); + + // We only position ourselves if we are a child of our context's root element. That is, we don't want to proceed + // if we are unparented or an iframe document. + if (!root || !context || (root != context->GetRootElement())) + return; + + // Work out our containing block; relative offsets are calculated against it. + const Vector2f containing_block = root->GetBox().GetSize(); + auto& computed = GetComputedValues(); + const Box& box = GetBox(); + + Vector2f position; + + if (computed.left().type != Style::Left::Auto) + position.x = ResolveValue(computed.left(), containing_block.x); + else if (computed.right().type != Style::Right::Auto) + position.x = containing_block.x - (box.GetSize(BoxArea::Margin).x + ResolveValue(computed.right(), containing_block.x)); + + if (computed.top().type != Style::Top::Auto) + position.y = ResolveValue(computed.top(), containing_block.y); + else if (computed.bottom().type != Style::Bottom::Auto) + position.y = containing_block.y - (box.GetSize(BoxArea::Margin).y + ResolveValue(computed.bottom(), containing_block.y)); + + // Add the margin edge to the position, since inset properties (top/right/bottom/left) set the margin edge + // position, while offsets use the border edge. + position.x += box.GetEdge(BoxArea::Margin, BoxEdge::Left); + position.y += box.GetEdge(BoxArea::Margin, BoxEdge::Top); + + SetOffset(position, nullptr); + } +} + +void ElementDocument::DirtyPosition() +{ + position_dirty = true; +} + +void ElementDocument::DirtyLayout() +{ + layout_dirty = true; +} + +bool ElementDocument::IsLayoutDirty() +{ + return layout_dirty; +} + +void ElementDocument::DirtyVwAndVhProperties() +{ + GetStyle()->DirtyPropertiesWithUnitsRecursive(Unit::VW | Unit::VH); +} + +void ElementDocument::OnPropertyChange(const PropertyIdSet& changed_properties) +{ + Element::OnPropertyChange(changed_properties); + + // If the document's font-size has been changed, we need to dirty all rem properties. + if (changed_properties.Contains(PropertyId::FontSize)) + GetStyle()->DirtyPropertiesWithUnitsRecursive(Unit::REM); + + if (changed_properties.Contains(PropertyId::Top) || // + changed_properties.Contains(PropertyId::Right) || // + changed_properties.Contains(PropertyId::Bottom) || // + changed_properties.Contains(PropertyId::Left)) + DirtyPosition(); +} + +void ElementDocument::ProcessDefaultAction(Event& event) +{ + Element::ProcessDefaultAction(event); + + // Process generic keyboard events for this window in bubble phase + if (event == EventId::Keydown) + { + int key_identifier = event.GetParameter("key_identifier", Input::KI_UNKNOWN); + + // Process TAB + if (key_identifier == Input::KI_TAB) + { + if (Element* element = FindNextTabElement(event.GetTargetElement(), !event.GetParameter("shift_key", false))) + { + if (element->Focus(true)) + { + element->ScrollIntoView(ScrollAlignment::Adaptive); + event.StopPropagation(); + } + } + } + // Process direction keys + else if (key_identifier == Input::KI_LEFT || key_identifier == Input::KI_RIGHT || key_identifier == Input::KI_UP || + key_identifier == Input::KI_DOWN) + { + NavigationSearchDirection direction = {}; + PropertyId property_id = PropertyId::NavLeft; + switch (key_identifier) + { + case Input::KI_LEFT: + direction = NavigationSearchDirection::Left; + property_id = PropertyId::NavLeft; + break; + case Input::KI_RIGHT: + direction = NavigationSearchDirection::Right; + property_id = PropertyId::NavRight; + break; + case Input::KI_UP: + direction = NavigationSearchDirection::Up; + property_id = PropertyId::NavUp; + break; + case Input::KI_DOWN: + direction = NavigationSearchDirection::Down; + property_id = PropertyId::NavDown; + break; + } + + auto GetNearestFocusable = [this](Element* focus_node) -> Element* { + while (focus_node) + { + if (CanFocusElement(focus_node) == CanFocus::Yes) + break; + focus_node = focus_node->GetParentNode(); + } + return focus_node ? focus_node : this; + }; + Element* focus_node = GetNearestFocusable(GetFocusLeafNode()); + if (const Property* nav_property = focus_node->GetLocalProperty(property_id)) + { + if (Element* next = FindNextNavigationElement(focus_node, direction, *nav_property)) + { + if (next->Focus(true)) + { + next->ScrollIntoView(ScrollAlignment::Adaptive); + event.StopPropagation(); + } + } + } + } + // Process ENTER being pressed on a focusable object (emulate click) + else if (key_identifier == Input::KI_RETURN || key_identifier == Input::KI_NUMPADENTER || key_identifier == Input::KI_SPACE) + { + Element* focus_node = GetFocusLeafNode(); + + if (focus_node && focus_node->GetComputedValues().tab_index() == Style::TabIndex::Auto) + { + focus_node->Click(); + event.StopPropagation(); + } + } + } +} + +void ElementDocument::OnResize() +{ + DirtyPosition(); +} + +bool ElementDocument::IsFocusableFromModal() const +{ + return focusable_from_modal && IsVisible(); +} + +void ElementDocument::SetFocusableFromModal(bool focusable) +{ + focusable_from_modal = focusable; +} + +Element* ElementDocument::FindNextTabElement(Element* current_element, bool forward, bool wrap_around) +{ + // This algorithm is quite sneaky, I originally thought a depth first search would work, but it appears not. What is + // required is to cut the tree in half along the nodes from current_element up the root and then either traverse the + // tree in a clockwise or anticlock wise direction depending if you're searching forward or backward respectively. + + Element* document = current_element->GetOwnerDocument(); + + // If we're searching forward, check the immediate children of this node first off. If we're searching backward we + // only want to consider children if we are the document root. + if (forward || current_element == document) + { + if (Element* result = SearchFocusSubtreeChildren(current_element, forward)) + return result; + } + + // Now walk up the tree, testing either the bottom or top + // of the tree, depending on whether we're going forward + // or backward respectively. + bool search_enabled = false; + Element* child = current_element; + Element* parent = current_element->GetParentNode(); + while (child != document) + { + const int num_children = parent->GetNumChildren(); + for (int i = 0; i < num_children; i++) + { + // Calculate index into children + const int child_index = forward ? i : (num_children - i - 1); + Element* search_child = parent->GetChild(child_index); + + // Do a search if its enabled + if (search_enabled) + if (Element* result = SearchFocusSubtree(search_child, forward)) + return result; + + // Enable searching when we reach the child. + if (search_child == child) + search_enabled = true; + } + + // Advance up the tree + child = parent; + parent = parent->GetParentNode(); + search_enabled = false; + } + + // We could not find anything to focus along this direction. + if (!wrap_around) + return nullptr; + + // If we can focus the document, then focus that now. + if (current_element != document && CanFocusElement(document) == CanFocus::Yes) + return document; + + // Otherwise, search the entire document tree. This way we will wrap around. + return SearchFocusSubtreeChildren(document, forward); +} + +Element* ElementDocument::SearchFocusSubtree(Element* element, bool forward) +{ + CanFocus can_focus = CanFocusElement(element); + if (can_focus == CanFocus::Yes) + return element; + else if (can_focus == CanFocus::NoAndNoChildren) + return nullptr; + + return SearchFocusSubtreeChildren(element, forward); +} + +Element* ElementDocument::SearchFocusSubtreeChildren(Element* element, bool forward) +{ + const int num_children = element->GetNumChildren(); + for (int i = 0; i < num_children; i++) + { + const int child_index = (forward ? i : (num_children - i - 1)); + if (Element* result = SearchFocusSubtree(element->GetChild(child_index), forward)) + return result; + } + + return nullptr; +} + +Element* ElementDocument::FindNextNavigationElement(Element* current_element, NavigationSearchDirection direction, const Property& property) +{ + switch (property.unit) + { + case Unit::STRING: + { + const PropertySource* source = property.source.get(); + const String value = property.Get(); + if (value[0] != '#') + { + Log::Message(Log::LT_WARNING, + "Invalid navigation value '%s': Expected a keyword or a string with an element id prefixed with '#'. Declared at %s:%d", + value.c_str(), source ? source->path.c_str() : "", source ? source->line_number : -1); + return nullptr; + } + + const String id = String(value.begin() + 1, value.end()); + Element* result = GetElementById(id); + if (!result) + { + Log::Message(Log::LT_WARNING, "Trying to navigate to element with id '%s', but could not find element. Declared at %s:%d", id.c_str(), + source ? source->path.c_str() : "", source ? source->line_number : -1); + } + return result; + } + break; + case Unit::KEYWORD: + { + const bool direction_is_horizontal = (direction == NavigationSearchDirection::Left || direction == NavigationSearchDirection::Right); + const bool direction_is_vertical = (direction == NavigationSearchDirection::Up || direction == NavigationSearchDirection::Down); + const bool direction_is_forward = (direction == NavigationSearchDirection::Down || direction == NavigationSearchDirection::Right); + switch (static_cast(property.value.Get())) + { + case Style::Nav::None: return nullptr; + case Style::Nav::Auto: break; + case Style::Nav::Horizontal: + if (!direction_is_horizontal) + return nullptr; + break; + case Style::Nav::Vertical: + if (!direction_is_vertical) + return nullptr; + break; + case Style::Nav::TreeOrder: return FindNextTabElement(current_element, direction_is_forward, false); + } + + if (current_element == this) + return FindNextTabElement(current_element, direction_is_forward); + + const Vector2f position = current_element->GetAbsoluteOffset(BoxArea::Border); + const Rectanglef bounding_box = Rectanglef::FromPositionSize(position, current_element->GetBox().GetSize(BoxArea::Border)); + + auto GetNearestScrollContainer = [this](Element* element) -> Element* { + for (element = element->GetParentNode(); element; element = element->GetParentNode()) + { + if (IsScrollContainer(element)) + return element; + } + return this; + }; + Element* start_element = GetNearestScrollContainer(current_element); + + SearchNavigationResult best_result; + SearchNavigationTarget(best_result, start_element, direction, bounding_box, current_element); + return best_result.element; + } + break; + default: break; + } + return nullptr; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementEffects.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementEffects.cpp new file mode 100644 index 00000000..c8d00696 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementEffects.cpp @@ -0,0 +1,327 @@ +#include "ElementEffects.h" +#include "../../Include/RmlUi/Core/ComputedValues.h" +#include "../../Include/RmlUi/Core/Decorator.h" +#include "../../Include/RmlUi/Core/Element.h" +#include "../../Include/RmlUi/Core/ElementDocument.h" +#include "../../Include/RmlUi/Core/ElementUtilities.h" +#include "../../Include/RmlUi/Core/Filter.h" +#include "../../Include/RmlUi/Core/Profiling.h" +#include "../../Include/RmlUi/Core/StyleSheet.h" + +namespace Rml { + +ElementEffects::ElementEffects(Element* _element) : element(_element) {} + +ElementEffects::~ElementEffects() +{ + ReleaseEffects(); +} + +void ElementEffects::InstanceEffects() +{ + if (!effects_dirty) + return; + + effects_dirty = false; + effects_data_dirty = true; + + RMLUI_ZoneScopedC(0xB22222); + ReleaseEffects(); + + RenderManager* render_manager = element->GetRenderManager(); + if (!render_manager) + { + RMLUI_ERRORMSG("Decorators are being instanced before a render manager is available. Is this element attached to the document?"); + return; + } + + const ComputedValues& computed = element->GetComputedValues(); + + if (computed.has_decorator() || computed.has_mask_image()) + { + const StyleSheet* style_sheet = element->GetStyleSheet(); + if (!style_sheet) + return; + + for (const auto id : {PropertyId::Decorator, PropertyId::MaskImage}) + { + const Property* property = element->GetLocalProperty(id); + if (!property || property->unit != Unit::DECORATOR) + continue; + + DecoratorsPtr decorators_ptr = property->Get(); + if (!decorators_ptr) + continue; + + PropertySource document_source("", 0, ""); + const PropertySource* source = property->source.get(); + + if (!source) + { + if (ElementDocument* document = element->GetOwnerDocument()) + { + document_source.path = document->GetSourceURL(); + source = &document_source; + } + } + + const DecoratorPtrList& decorator_list = style_sheet->InstanceDecorators(*render_manager, *decorators_ptr, source); + RMLUI_ASSERT(decorator_list.empty() || decorator_list.size() == decorators_ptr->list.size()); + + DecoratorEntryList& decorators_target = (id == PropertyId::Decorator ? decorators : mask_images); + decorators_target.reserve(decorators_ptr->list.size()); + + for (size_t i = 0; i < decorator_list.size() && i < decorators_ptr->list.size(); i++) + { + const SharedPtr& decorator = decorator_list[i]; + if (decorator) + { + DecoratorEntry entry; + entry.decorator_data = 0; + entry.decorator = decorator; + entry.paint_area = decorators_ptr->list[i].paint_area; + if (entry.paint_area == BoxArea::Auto) + entry.paint_area = (id == PropertyId::Decorator ? BoxArea::Padding : BoxArea::Border); + + RMLUI_ASSERT(entry.paint_area >= BoxArea::Border && entry.paint_area <= BoxArea::Content); + decorators_target.push_back(std::move(entry)); + } + } + } + } + + if (computed.has_filter() || computed.has_backdrop_filter()) + { + for (const auto id : {PropertyId::Filter, PropertyId::BackdropFilter}) + { + const Property* property = element->GetLocalProperty(id); + if (!property || property->unit != Unit::FILTER) + continue; + + FiltersPtr filters_ptr = property->Get(); + if (!filters_ptr) + continue; + + FilterEntryList& list = (id == PropertyId::Filter ? filters : backdrop_filters); + list.reserve(filters_ptr->list.size()); + + for (const FilterDeclaration& declaration : filters_ptr->list) + { + SharedPtr filter = declaration.instancer->InstanceFilter(declaration.type, declaration.properties); + if (filter) + { + list.push_back({std::move(filter), CompiledFilter{}}); + } + else + { + const auto& source = property->source; + Log::Message(Log::LT_WARNING, "Filter '%s' in '%s' could not be instanced, declared at %s:%d", declaration.type.c_str(), + filters_ptr->value.c_str(), source ? source->path.c_str() : "", source ? source->line_number : -1); + } + } + } + } +} + +void ElementEffects::ReloadEffectsData() +{ + if (effects_data_dirty) + { + effects_data_dirty = false; + + bool decorator_data_failed = false; + for (DecoratorEntryList* list : {&decorators, &mask_images}) + { + for (DecoratorEntry& decorator : *list) + { + const DecoratorDataHandle old_data = decorator.decorator_data; + + decorator.decorator_data = decorator.decorator->GenerateElementData(element, decorator.paint_area); + if (!decorator.decorator_data) + decorator_data_failed = true; + + // Release old element data after generating new data, so that the decorator can reuse any cache. + if (old_data) + decorator.decorator->ReleaseElementData(old_data); + } + } + + if (decorator_data_failed) + Log::Message(Log::LT_WARNING, "Could not generate decorator element data: %s", element->GetAddress().c_str()); + + bool filter_compile_failed = false; + for (FilterEntryList* list : {&filters, &backdrop_filters}) + { + for (FilterEntry& filter : *list) + { + filter.compiled = filter.filter->CompileFilter(element); + if (!filter.compiled) + filter_compile_failed = true; + } + } + + if (filter_compile_failed) + Log::Message(Log::LT_WARNING, "Could not compile filter on element: %s", element->GetAddress().c_str()); + } +} + +void ElementEffects::ReleaseEffects() +{ + for (DecoratorEntryList* list : {&decorators, &mask_images}) + { + for (DecoratorEntry& decorator : *list) + { + if (decorator.decorator_data) + decorator.decorator->ReleaseElementData(decorator.decorator_data); + } + list->clear(); + } + + filters.clear(); + backdrop_filters.clear(); +} + +void ElementEffects::RenderEffects(RenderStage render_stage) +{ + InstanceEffects(); + ReloadEffectsData(); + + if (!decorators.empty()) + { + if (render_stage == RenderStage::Decoration) + { + // Render the decorators attached to this element in its current state. + // Render from back to front for correct render order. + for (int i = (int)decorators.size() - 1; i >= 0; i--) + { + DecoratorEntry& decorator = decorators[i]; + if (decorator.decorator_data) + decorator.decorator->RenderElement(element, decorator.decorator_data); + } + } + } + + if (filters.empty() && backdrop_filters.empty() && mask_images.empty()) + return; + + RenderManager* render_manager = element->GetRenderManager(); + if (!render_manager) + return; + + Rectanglei initial_scissor_region = render_manager->GetScissorRegion(); + + auto ApplyClippingRegion = [this, &render_manager](PropertyId filter_id) { + RMLUI_ASSERT(filter_id == PropertyId::Filter || filter_id == PropertyId::BackdropFilter); + + const bool force_clip_to_self_border_box = (filter_id == PropertyId::BackdropFilter); + ElementUtilities::SetClippingRegion(element, force_clip_to_self_border_box); + + // Find the region being affected by the active filters and apply it as a scissor. + Rectanglef filter_region = Rectanglef::MakeInvalid(); + ElementUtilities::GetBoundingBox(filter_region, element, force_clip_to_self_border_box ? BoxArea::Border : BoxArea::Auto); + + // The filter property may draw outside our normal clipping region due to ink overflow. + if (filter_id == PropertyId::Filter) + { + for (const auto& filter : filters) + filter.filter->ExtendInkOverflow(element, filter_region); + } + + Math::ExpandToPixelGrid(filter_region); + + Rectanglei scissor_region = Rectanglei(filter_region).IntersectIfValid(render_manager->GetScissorRegion()); + render_manager->SetScissorRegion(scissor_region); + }; + auto ApplyScissorRegionForBackdrop = [this, &render_manager]() { + // Set the scissor region for backdrop drawing, which covers the element's border box plus any area we may need + // to read from, such as any blur radius. + Rectanglef filter_region = Rectanglef::MakeInvalid(); + ElementUtilities::GetBoundingBox(filter_region, element, BoxArea::Border); + for (const auto& filter : backdrop_filters) + filter.filter->ExtendInkOverflow(element, filter_region); + Math::ExpandToPixelGrid(filter_region); + render_manager->SetScissorRegion(Rectanglei(filter_region)); + }; + + if (render_stage == RenderStage::Enter) + { + const LayerHandle backdrop_source_layer = render_manager->GetTopLayer(); + + if (!filters.empty() || !mask_images.empty()) + { + render_manager->PushLayer(); + } + + if (!backdrop_filters.empty()) + { + const LayerHandle backdrop_destination_layer = render_manager->GetTopLayer(); + + // @performance We strictly only need this temporary buffer when having to read from outside the element + // boundaries, which currently only applies to blur and drop-shadow. Alternatively, we could avoid this + // completely if we introduced a render interface API concept of different input and output clipping. That + // is, we set a large input scissor to cover all input data, which can be used e.g. during blurring, and use + // our small border-area-only clipping region for the composite layers output. + ApplyScissorRegionForBackdrop(); + render_manager->PushLayer(); + const LayerHandle backdrop_temp_layer = render_manager->GetTopLayer(); + + FilterHandleList filter_handles; + for (auto& filter : backdrop_filters) + filter.compiled.AddHandleTo(filter_handles); + + // Render the backdrop filters in the extended scissor region including any ink overflow. + render_manager->CompositeLayers(backdrop_source_layer, backdrop_temp_layer, BlendMode::Blend, filter_handles); + + // Then composite the filter output to our destination while applying our clipping region, including any border-radius. + ApplyClippingRegion(PropertyId::BackdropFilter); + render_manager->CompositeLayers(backdrop_temp_layer, backdrop_destination_layer, BlendMode::Blend, {}); + render_manager->PopLayer(); + render_manager->SetScissorRegion(initial_scissor_region); + } + } + else if (render_stage == RenderStage::Exit) + { + if (!filters.empty() || !mask_images.empty()) + { + ApplyClippingRegion(PropertyId::Filter); + + CompiledFilter mask_image_filter; + FilterHandleList filter_handles; + filter_handles.reserve(filters.size() + (mask_images.empty() ? 0 : 1)); + + for (auto& filter : filters) + filter.compiled.AddHandleTo(filter_handles); + + if (!mask_images.empty()) + { + render_manager->PushLayer(); + + for (int i = (int)mask_images.size() - 1; i >= 0; i--) + { + DecoratorEntry& mask_image = mask_images[i]; + if (mask_image.decorator_data) + mask_image.decorator->RenderElement(element, mask_image.decorator_data); + } + mask_image_filter = render_manager->SaveLayerAsMaskImage(); + mask_image_filter.AddHandleTo(filter_handles); + render_manager->PopLayer(); + } + + render_manager->CompositeLayers(render_manager->GetTopLayer(), render_manager->GetNextLayer(), BlendMode::Blend, filter_handles); + render_manager->PopLayer(); + render_manager->SetScissorRegion(initial_scissor_region); + } + } +} + +void ElementEffects::DirtyEffects() +{ + effects_dirty = true; +} + +void ElementEffects::DirtyEffectsData() +{ + effects_data_dirty = true; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementEffects.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementEffects.h new file mode 100644 index 00000000..d46a377d --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementEffects.h @@ -0,0 +1,65 @@ +#pragma once + +#include "../../Include/RmlUi/Core/CompiledFilterShader.h" +#include "../../Include/RmlUi/Core/Types.h" + +namespace Rml { + +class Decorator; +class Element; +class Filter; + +enum class RenderStage { Enter, Decoration, Exit }; + +/** + Manages and renders an element's effects: decorators, filters, backdrop filters, and mask images. + */ + +class ElementEffects { +public: + ElementEffects(Element* element); + ~ElementEffects(); + + void InstanceEffects(); + + void RenderEffects(RenderStage render_stage); + + // Mark effects as dirty and force them to reset themselves. + void DirtyEffects(); + // Mark the element data of effects as dirty. + void DirtyEffectsData(); + +private: + // Releases existing element data of effects, and regenerates it. + void ReloadEffectsData(); + // Releases all existing effects and their element data. + void ReleaseEffects(); + + struct DecoratorEntry { + SharedPtr decorator; + DecoratorDataHandle decorator_data; + BoxArea paint_area; + }; + using DecoratorEntryList = Vector; + + struct FilterEntry { + SharedPtr filter; + CompiledFilter compiled; + }; + using FilterEntryList = Vector; + + Element* element; + + // The list of decorators and filters used by this element. + DecoratorEntryList decorators; + DecoratorEntryList mask_images; + FilterEntryList filters; + FilterEntryList backdrop_filters; + + // If set, a full reload is necessary. + bool effects_dirty = false; + // If set, element data of all decorators need to be regenerated. + bool effects_data_dirty = false; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementHandle.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementHandle.cpp new file mode 100644 index 00000000..b27bf16c --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementHandle.cpp @@ -0,0 +1,346 @@ +#include "ElementHandle.h" +#include "../../Include/RmlUi/Core/ComputedValues.h" +#include "../../Include/RmlUi/Core/Context.h" +#include "../../Include/RmlUi/Core/ElementDocument.h" +#include "../../Include/RmlUi/Core/ElementUtilities.h" +#include "../../Include/RmlUi/Core/Event.h" +#include "../../Include/RmlUi/Core/Property.h" +#include "../../Include/RmlUi/Core/PropertyDefinition.h" +#include "../../Include/RmlUi/Core/PropertyDictionary.h" +#include "../../Include/RmlUi/Core/PropertySpecification.h" + +namespace Rml { + +class ElementHandleTargetData { +public: + enum { TOP, RIGHT, BOTTOM, LEFT, NUM_EDGES }; + using MoveData = ElementHandle::MoveData; + using SizeData = ElementHandle::SizeData; + + ElementHandleTargetData(Element* target, Context* context, const Array& edge_margin) : + target(target), computed(target->GetComputedValues()), box(target->GetBox()), parent_box(GetParentBox(target, context)), + containing_block(target->GetContainingBlock()), position(computed.position()), + resolved_edge_margin(ResolveEdgeMargin(target, box, edge_margin)) + { + SetDefiniteMargins(); + } + + // The following table lists the expected behavior for each combination of definite (non-auto) properties: + // + // Definite properties | Move | Size + // ---------------------|-------------------------|-------------------------- + // (none) | left += dx | width += dx + // left | left += dx | width += dx + // right | right -= dx | right -= dx; width += dx + // width | left += dx | width += dx + // left & right | left += dx; right -= dx | right -= dx + // left & width | left += dx; | width += dx + // right & width | right -= dx | right -= dx; width += dx + // right & width | right -= dx | right -= dx; width += dx + // left & right & width | left += dx; | width += dx + // + // For simplicity, this table only specifies the horizontal direction. The same behavior applies for the + // corresponding properties in the vertical direction. For now, we assume that the handle is anchored to the + // bottom-right corner of the target element. + + MoveData GetMoveData(Vector2f& drag_delta_min, Vector2f& drag_delta_max) const + { + using namespace Style; + MoveData data = {}; + + data.original_position_top_left = {GetPositionLeft(), GetPositionTop()}; + data.original_position_bottom_right = {GetPositionRight(), GetPositionBottom()}; + + data.bottom_right.x = (computed.right().type != Right::Auto); + data.bottom_right.y = (computed.bottom().type != Bottom::Auto); + data.top_left.x = (!data.bottom_right.x || computed.left().type != Left::Auto); + data.top_left.y = (!data.bottom_right.y || computed.top().type != Top::Auto); + + const Vector2f distance_to_top_left = DistanceToTopLeft(); + const Vector2f distance_to_bottom_right = DistanceToBottomRight(distance_to_top_left); + + drag_delta_min = Math::Max(drag_delta_min, Vector2f{resolved_edge_margin[LEFT], resolved_edge_margin[TOP]} - distance_to_top_left); + drag_delta_max = Math::Min(drag_delta_max, Vector2f{-resolved_edge_margin[RIGHT], -resolved_edge_margin[BOTTOM]} + distance_to_bottom_right); + + return data; + } + + SizeData GetSizeData(Vector2f& drag_delta_min, Vector2f& drag_delta_max) const + { + using namespace Style; + SizeData data = {}; + + data.original_size = box.GetSize(computed.box_sizing() == BoxSizing::BorderBox ? BoxArea::Border : BoxArea::Content); + data.original_position_bottom_right = {GetPositionRight(), GetPositionBottom()}; + + data.bottom_right.x = (computed.right().type != Right::Auto); + data.bottom_right.y = (computed.bottom().type != Bottom::Auto); + data.width_height.x = (computed.left().type == Left::Auto || computed.right().type == Right::Auto || computed.width().type != Width::Auto); + data.width_height.y = (computed.top().type == Top::Auto || computed.bottom().type == Bottom::Auto || computed.height().type != Height::Auto); + + const Vector2f min_size = { + ResolveValue(computed.min_width(), containing_block.x), + ResolveValue(computed.min_height(), containing_block.y), + }; + const Vector2f max_size = { + ResolveValueOr(computed.max_width(), containing_block.x, FLT_MAX), + ResolveValueOr(computed.max_height(), containing_block.y, FLT_MAX), + }; + const Vector2f distance_to_bottom_right = DistanceToBottomRight(DistanceToTopLeft()); + + drag_delta_min = Math::Max(drag_delta_min, min_size - data.original_size); + drag_delta_max = Math::Min(drag_delta_max, max_size - data.original_size); + drag_delta_max = Math::Min(drag_delta_max, Vector2f{-resolved_edge_margin[RIGHT], -resolved_edge_margin[BOTTOM]} + distance_to_bottom_right); + + return data; + } + +private: + void SetDefiniteMargins() + { + // Set any auto margins to their current value, since auto-margins may affect the size and position of an element. + auto SetDefiniteMargin = [](Element* element, PropertyId margin_id, BoxEdge edge) { + element->SetProperty(margin_id, Property(Math::Round(element->GetBox().GetEdge(BoxArea::Margin, edge)), Unit::PX)); + }; + using Style::Margin; + if (computed.margin_top().type == Margin::Auto) + SetDefiniteMargin(target, PropertyId::MarginTop, BoxEdge::Top); + if (computed.margin_right().type == Margin::Auto) + SetDefiniteMargin(target, PropertyId::MarginRight, BoxEdge::Right); + if (computed.margin_bottom().type == Margin::Auto) + SetDefiniteMargin(target, PropertyId::MarginBottom, BoxEdge::Bottom); + if (computed.margin_left().type == Margin::Auto) + SetDefiniteMargin(target, PropertyId::MarginLeft, BoxEdge::Left); + } + + static Array ResolveEdgeMargin(Element* target, const Box& box, const Array& edge_margin) + { + const Vector2f target_size = box.GetSize(BoxArea::Border); + Array resolved_edge_margin = {}; + for (int i = 0; i < NUM_EDGES; i++) + { + resolved_edge_margin[i] = (edge_margin[i].unit == Unit::UNKNOWN + ? -FLT_MAX + : Math::Round(target->ResolveNumericValue(edge_margin[i], target_size[(i == LEFT || i == RIGHT) ? 0 : 1]))); + } + return resolved_edge_margin; + } + + static const Box& GetParentBox(Element* target, Context* context) + { + return target->GetOffsetParent() ? target->GetOffsetParent()->GetBox() : context->GetRootElement()->GetBox(); + } + + template + static float ResolveValueOrInvoke(const Style::LengthPercentageAuto& value, float containing_block, Style::Position position, + Func&& fallback_func) + { + if (value.type != Style::LengthPercentageAuto::Auto) + return ResolveValue(value, containing_block); + if (position == Style::Position::Relative) + return 0.0f; + return fallback_func(); + } + + // The following is derived at by solving the expressions in 'Element::UpdateOffset' for the computed top/left/bottom/right values. + float GetPositionTop() const + { + return ResolveValueOrInvoke(computed.top(), containing_block.y, position, [&] { + return target->GetOffsetTop() - (box.GetEdge(BoxArea::Margin, BoxEdge::Top) + parent_box.GetEdge(BoxArea::Border, BoxEdge::Top)); + }); + } + float GetPositionLeft() const + { + return ResolveValueOrInvoke(computed.left(), containing_block.x, position, [&] { + return target->GetOffsetLeft() - (box.GetEdge(BoxArea::Margin, BoxEdge::Left) + parent_box.GetEdge(BoxArea::Border, BoxEdge::Left)); + }); + } + float GetPositionBottom() const + { + return ResolveValueOrInvoke(computed.bottom(), containing_block.y, position, [&] { + return containing_block.y + parent_box.GetEdge(BoxArea::Border, BoxEdge::Top) - + (target->GetOffsetTop() + box.GetSize(BoxArea::Border).y + box.GetEdge(BoxArea::Margin, BoxEdge::Bottom)); + }); + } + float GetPositionRight() const + { + return ResolveValueOrInvoke(computed.right(), containing_block.x, position, [&] { + return containing_block.x + parent_box.GetEdge(BoxArea::Border, BoxEdge::Left) - + (target->GetOffsetLeft() + box.GetSize(BoxArea::Border).x + box.GetEdge(BoxArea::Margin, BoxEdge::Right)); + }); + } + + Vector2f DistanceToTopLeft() const + { + return {target->GetOffsetLeft() - parent_box.GetEdge(BoxArea::Border, BoxEdge::Left), + target->GetOffsetTop() - parent_box.GetEdge(BoxArea::Border, BoxEdge::Top)}; + } + Vector2f DistanceToBottomRight(Vector2f distance_to_top_left) const + { + const Vector2f scroll_size = {target->GetParentNode()->GetScrollWidth(), target->GetParentNode()->GetScrollHeight()}; + return scroll_size - box.GetSize(BoxArea::Border) - distance_to_top_left; + } + + Element* target; + const ComputedValues& computed; + const Box& box; + const Box& parent_box; + const Vector2f containing_block; + const Style::Position position; + const Array resolved_edge_margin; +}; + +class HandleEdgeMarginParser { +private: + PropertySpecification specification; + Array ids; + ShorthandId id_constraint; + +public: + HandleEdgeMarginParser() : specification(4, 1) + { + ids = { + specification.RegisterProperty("edge-t", "", false, false).AddParser("length_percent").GetId(), + specification.RegisterProperty("edge-r", "", false, false).AddParser("length_percent").GetId(), + specification.RegisterProperty("edge-b", "", false, false).AddParser("length_percent").GetId(), + specification.RegisterProperty("edge-l", "", false, false).AddParser("length_percent").GetId(), + }; + id_constraint = specification.RegisterShorthand("edge-margin", "edge-t, edge-r, edge-b, edge-l", ShorthandType::Box); + } + + bool Parse(const String& value, Array& out_constraints) + { + PropertyDictionary properties; + if (!specification.ParseShorthandDeclaration(properties, id_constraint, value)) + return false; + + out_constraints = {}; + for (int i = 0; i < 4; i++) + { + if (const Property* p = properties.GetProperty(ids[i])) + out_constraints[i] = p->GetNumericValue(); + } + return true; + } +}; + +ElementHandle::ElementHandle(const String& tag) : Element(tag), drag_start(0, 0) +{ + // Make sure we can be dragged! + SetProperty(PropertyId::Drag, Property(Style::Drag::Drag)); + + move_target = nullptr; + size_target = nullptr; + initialised = false; +} + +ElementHandle::~ElementHandle() {} + +void ElementHandle::OnAttributeChange(const ElementAttributes& changed_attributes) +{ + Element::OnAttributeChange(changed_attributes); + + // Reset initialised state if the move or size targets have changed. + if (changed_attributes.find("move_target") != changed_attributes.end() || changed_attributes.find("size_target") != changed_attributes.end() || + changed_attributes.find("edge_margin") != changed_attributes.end()) + { + initialised = false; + move_target = nullptr; + size_target = nullptr; + } +} + +void ElementHandle::ProcessDefaultAction(Event& event) +{ + Element::ProcessDefaultAction(event); + + if (event.GetTargetElement() == this) + { + if (!initialised && GetOwnerDocument()) + { + const String move_target_name = GetAttribute("move_target", ""); + if (!move_target_name.empty()) + move_target = GetElementById(move_target_name); + + const String size_target_name = GetAttribute("size_target", ""); + if (!size_target_name.empty()) + size_target = GetElementById(size_target_name); + + const String edge_margin_str = GetAttribute("edge_margin", "0px"); + edge_margin = {}; + if (edge_margin_str != "none") + { + HandleEdgeMarginParser parser; + if (!parser.Parse(edge_margin_str, edge_margin)) + Log::Message(Log::LT_WARNING, "Failed to parse 'edge_margin' attribute for element '%s'.", GetAddress().c_str()); + } + + initialised = true; + } + + if (event == EventId::Dragstart) + { + using namespace Style; + Context* context = GetContext(); + drag_start = event.GetUnprojectedMouseScreenPos(); + drag_delta_min = {-FLT_MAX, -FLT_MAX}; + drag_delta_max = {FLT_MAX, FLT_MAX}; + + if (move_target && context) + { + ElementHandleTargetData move_target_data(move_target, context, edge_margin); + move_data = move_target_data.GetMoveData(drag_delta_min, drag_delta_max); + } + + if (size_target && context) + { + ElementHandleTargetData size_target_data(size_target, context, edge_margin); + size_data = size_target_data.GetSizeData(drag_delta_min, drag_delta_max); + } + + drag_delta_min = Math::Min(drag_delta_min, Vector2f{0, 0}); + drag_delta_max = Math::Max(drag_delta_max, Vector2f{0, 0}); + } + else if (event == EventId::Drag) + { + const Vector2f delta = Math::Clamp(event.GetUnprojectedMouseScreenPos() - drag_start, drag_delta_min, drag_delta_max); + + if (move_target) + { + const Vector2f new_position_top_left = (move_data.original_position_top_left + delta).Round(); + const Vector2f new_position_bottom_right = (move_data.original_position_bottom_right - delta).Round(); + + if (move_data.top_left.x) + move_target->SetProperty(PropertyId::Left, Property(new_position_top_left.x, Unit::PX)); + if (move_data.top_left.y) + move_target->SetProperty(PropertyId::Top, Property(new_position_top_left.y, Unit::PX)); + if (move_data.bottom_right.x) + move_target->SetProperty(PropertyId::Right, Property(new_position_bottom_right.x, Unit::PX)); + if (move_data.bottom_right.y) + move_target->SetProperty(PropertyId::Bottom, Property(new_position_bottom_right.y, Unit::PX)); + } + + if (size_target) + { + const Vector2f new_size = Math::Max((size_data.original_size + delta).Round(), Vector2f(0.f)); + const Vector2f new_position_bottom_right = (size_data.original_position_bottom_right - delta).Round(); + + if (size_data.width_height.x) + size_target->SetProperty(PropertyId::Width, Property(new_size.x, Unit::PX)); + if (size_data.width_height.y) + size_target->SetProperty(PropertyId::Height, Property(new_size.y, Unit::PX)); + if (size_data.bottom_right.x) + size_target->SetProperty(PropertyId::Right, Property(new_position_bottom_right.x, Unit::PX)); + if (size_data.bottom_right.y) + size_target->SetProperty(PropertyId::Bottom, Property(new_position_bottom_right.y, Unit::PX)); + } + + Dictionary parameters; + parameters["handle_x"] = delta.x; + parameters["handle_y"] = delta.y; + DispatchEvent(EventId::Handledrag, parameters); + } + } +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementHandle.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementHandle.h new file mode 100644 index 00000000..374a14ef --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementHandle.h @@ -0,0 +1,51 @@ +#pragma once + +#include "../../Include/RmlUi/Core/Element.h" +#include "../../Include/RmlUi/Core/Header.h" + +namespace Rml { + +/** + A derivation of an element for use as a mouse drag handle. It responds to drag events, and can be configured to move + or resize specified target elements. + */ + +class RMLUICORE_API ElementHandle : public Element { +public: + RMLUI_RTTI_DefineWithParent(ElementHandle, Element) + + ElementHandle(const String& tag); + virtual ~ElementHandle(); + + struct MoveData { + Vector2f original_position_top_left; + Vector2f original_position_bottom_right; + Vector2 top_left; + Vector2 bottom_right; + }; + + struct SizeData { + Vector2f original_size; + Vector2f original_position_bottom_right; + Vector2 width_height; + Vector2 bottom_right; + }; + +protected: + void OnAttributeChange(const ElementAttributes& changed_attributes) override; + void ProcessDefaultAction(Event& event) override; + + bool initialised; + Element* move_target; + Element* size_target; + Array edge_margin = {}; + + Vector2f drag_start; + Vector2f drag_delta_min; + Vector2f drag_delta_max; + + MoveData move_data; + SizeData size_data; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementInstancer.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementInstancer.cpp new file mode 100644 index 00000000..8ea92a8a --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementInstancer.cpp @@ -0,0 +1,68 @@ +#include "../../Include/RmlUi/Core/ElementInstancer.h" +#include "../../Include/RmlUi/Core/ElementText.h" +#include "ControlledLifetimeResource.h" +#include "Pool.h" +#include "XMLParseTools.h" + +namespace Rml { + +ElementInstancer::~ElementInstancer() {} + +struct ElementInstancerPools { + Pool pool_element{200, true}; + Pool pool_text_default{200, true}; + + bool IsEmpty() const { return pool_element.GetNumAllocatedObjects() == 0 && pool_text_default.GetNumAllocatedObjects() == 0; } +}; +static ControlledLifetimeResource element_instancer_pools; + +ElementPtr ElementInstancerElement::InstanceElement(Element* /*parent*/, const String& tag, const XMLAttributes& /*attributes*/) +{ + Element* ptr = element_instancer_pools->pool_element.AllocateAndConstruct(tag); + return ElementPtr(ptr); +} + +void ElementInstancerElement::ReleaseElement(Element* element) +{ + element_instancer_pools->pool_element.DestroyAndDeallocate(element); +} + +ElementInstancerElement::~ElementInstancerElement() +{ + int num_elements = element_instancer_pools->pool_element.GetNumAllocatedObjects(); + if (num_elements > 0) + { + Log::Message(Log::LT_WARNING, "--- Found %d leaked element(s) ---", num_elements); + + for (auto it = element_instancer_pools->pool_element.Begin(); it; ++it) + Log::Message(Log::LT_WARNING, " %s", it->GetAddress().c_str()); + + Log::Message(Log::LT_WARNING, "------"); + } +} + +ElementPtr ElementInstancerText::InstanceElement(Element* /*parent*/, const String& tag, const XMLAttributes& /*attributes*/) +{ + ElementText* ptr = element_instancer_pools->pool_text_default.AllocateAndConstruct(tag); + return ElementPtr(static_cast(ptr)); +} + +void ElementInstancerText::ReleaseElement(Element* element) +{ + element_instancer_pools->pool_text_default.DestroyAndDeallocate(rmlui_static_cast(element)); +} + +void Detail::InitializeElementInstancerPools() +{ + element_instancer_pools.InitializeIfEmpty(); +} + +void Detail::ShutdownElementInstancerPools() +{ + if (element_instancer_pools->IsEmpty()) + element_instancer_pools.Shutdown(); + else + element_instancer_pools.Leak(); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementMeta.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementMeta.cpp new file mode 100644 index 00000000..414abc26 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementMeta.cpp @@ -0,0 +1,30 @@ +#include "ElementMeta.h" + +namespace Rml { + +ControlledLifetimeResource ElementMetaPool::element_meta_pool; + +void ElementMetaPool::Initialize() +{ + element_meta_pool.InitializeIfEmpty(); +} + +void ElementMetaPool::Shutdown() +{ + const int num_objects = element_meta_pool->pool.GetNumAllocatedObjects(); + if (num_objects == 0) + { + element_meta_pool.Shutdown(); + } + else + { + Log::Message(Log::LT_ERROR, + "Element meta pool not empty on shutdown, %d object(s) leaked. This will likely lead to a crash when element is destroyed. Ensure that " + "no Rml::Element objects are kept alive in user space at the end of Rml::Shutdown.", + num_objects); + RMLUI_ERROR; + element_meta_pool.Leak(); + } +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementMeta.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementMeta.h new file mode 100644 index 00000000..112b6dda --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementMeta.h @@ -0,0 +1,37 @@ +#pragma once + +#include "../../Include/RmlUi/Core/ComputedValues.h" +#include "../../Include/RmlUi/Core/Element.h" +#include "../../Include/RmlUi/Core/ElementScroll.h" +#include "../../Include/RmlUi/Core/Traits.h" +#include "../../Include/RmlUi/Core/Types.h" +#include "ControlledLifetimeResource.h" +#include "ElementBackgroundBorder.h" +#include "ElementEffects.h" +#include "ElementStyle.h" +#include "EventDispatcher.h" +#include "Pool.h" + +namespace Rml { + +// Meta objects for element collected in a single struct to reduce memory allocations +struct ElementMeta { + explicit ElementMeta(Element* el) : event_dispatcher(el), style(el), background_border(), effects(el), scroll(el), computed_values(el) {} + SmallUnorderedMap attribute_event_listeners; + EventDispatcher event_dispatcher; + ElementStyle style; + ElementBackgroundBorder background_border; + ElementEffects effects; + ElementScroll scroll; + Style::ComputedValues computed_values; +}; + +struct ElementMetaPool { + Pool pool{50, true}; + + static ControlledLifetimeResource element_meta_pool; + static void Initialize(); + static void Shutdown(); +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementScroll.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementScroll.cpp new file mode 100644 index 00000000..42e3ffc5 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementScroll.cpp @@ -0,0 +1,241 @@ +#include "../../Include/RmlUi/Core/ElementScroll.h" +#include "../../Include/RmlUi/Core/ComputedValues.h" +#include "../../Include/RmlUi/Core/Context.h" +#include "../../Include/RmlUi/Core/Element.h" +#include "../../Include/RmlUi/Core/ElementUtilities.h" +#include "../../Include/RmlUi/Core/Event.h" +#include "../../Include/RmlUi/Core/Factory.h" +#include "Layout/LayoutDetails.h" +#include "WidgetScroll.h" + +namespace Rml { + +ElementScroll::ElementScroll(Element* _element) +{ + element = _element; + corner = nullptr; +} + +ElementScroll::~ElementScroll() {} + +void ElementScroll::Update() +{ + for (int i = 0; i < 2; i++) + { + if (scrollbars[i].widget != nullptr) + scrollbars[i].widget->Update(); + } +} + +void ElementScroll::EnableScrollbar(Orientation orientation, float element_width) +{ + if (!scrollbars[orientation].enabled) + { + CreateScrollbar(orientation); + scrollbars[orientation].element->SetProperty(PropertyId::Visibility, Property(Style::Visibility::Visible)); + scrollbars[orientation].enabled = true; + } + + // Determine the size of the scrollbar. + Box box; + LayoutDetails::BuildBox(box, Vector2f(element_width, element_width), scrollbars[orientation].element); + + if (orientation == VERTICAL) + scrollbars[orientation].size = box.GetSize(BoxArea::Margin).x; + if (orientation == HORIZONTAL) + { + if (box.GetSize().y < 0) + scrollbars[orientation].size = box.GetCumulativeEdge(BoxArea::Content, BoxEdge::Left) + + box.GetCumulativeEdge(BoxArea::Content, BoxEdge::Right) + + ResolveValue(scrollbars[orientation].element->GetComputedValues().height(), element_width); + else + scrollbars[orientation].size = box.GetSize(BoxArea::Margin).y; + } +} + +void ElementScroll::DisableScrollbar(Orientation orientation) +{ + if (scrollbars[orientation].enabled) + { + scrollbars[orientation].element->SetProperty(PropertyId::Visibility, Property(Style::Visibility::Hidden)); + scrollbars[orientation].enabled = false; + + if (corner) + corner->SetProperty(PropertyId::Visibility, Property(Style::Visibility::Hidden)); + } +} + +void ElementScroll::UpdateScrollbar(Orientation orientation) +{ + float bar_position; + float traversable_track; + if (orientation == VERTICAL) + { + bar_position = element->GetScrollTop(); + traversable_track = element->GetScrollHeight() - element->GetClientHeight(); + } + else + { + bar_position = element->GetScrollLeft(); + traversable_track = element->GetScrollWidth() - element->GetClientWidth(); + } + + if (traversable_track <= 0) + bar_position = 0; + else + bar_position /= traversable_track; + + if (scrollbars[orientation].widget != nullptr) + { + bar_position = Math::Clamp(bar_position, 0.0f, 1.0f); + + if (scrollbars[orientation].widget->GetBarPosition() != bar_position) + scrollbars[orientation].widget->SetBarPosition(bar_position); + } +} + +Element* ElementScroll::GetScrollbar(Orientation orientation) +{ + return scrollbars[orientation].element; +} + +float ElementScroll::GetScrollbarSize(Orientation orientation) +{ + if (!scrollbars[orientation].enabled) + return 0; + + return scrollbars[orientation].size; +} + +void ElementScroll::FormatScrollbars() +{ + const Box& element_box = element->GetBox(); + const Vector2f containing_block = element_box.GetSize(BoxArea::Padding); + + for (int i = 0; i < 2; i++) + { + if (!scrollbars[i].enabled) + continue; + + if (i == VERTICAL) + { + scrollbars[i].widget->SetBarLength(element->GetClientHeight()); + scrollbars[i].widget->SetTrackLength(element->GetScrollHeight()); + + float traversable_track = element->GetScrollHeight() - element->GetClientHeight(); + if (traversable_track > 0) + scrollbars[i].widget->SetBarPosition(element->GetScrollTop() / traversable_track); + else + scrollbars[i].widget->SetBarPosition(0); + } + else + { + scrollbars[i].widget->SetBarLength(element->GetClientWidth()); + scrollbars[i].widget->SetTrackLength(element->GetScrollWidth()); + + float traversable_track = element->GetScrollWidth() - element->GetClientWidth(); + if (traversable_track > 0) + scrollbars[i].widget->SetBarPosition(element->GetScrollLeft() / traversable_track); + else + scrollbars[i].widget->SetBarPosition(0); + } + + float slider_length = containing_block[1 - i]; + float user_scrollbar_margin = scrollbars[i].element->GetComputedValues().scrollbar_margin(); + float min_scrollbar_margin = GetScrollbarSize(i == VERTICAL ? HORIZONTAL : VERTICAL); + slider_length -= Math::Max(user_scrollbar_margin, min_scrollbar_margin); + + scrollbars[i].widget->FormatElements(containing_block, slider_length); + + int variable_axis = i == VERTICAL ? 0 : 1; + Vector2f offset = element_box.GetPosition(BoxArea::Padding); + offset[variable_axis] += containing_block[variable_axis] - + (scrollbars[i].element->GetBox().GetSize(BoxArea::Border)[variable_axis] + + scrollbars[i].element->GetBox().GetEdge(BoxArea::Margin, i == VERTICAL ? BoxEdge::Right : BoxEdge::Bottom)); + // Add the top or left margin (as appropriate) onto the scrollbar's position. + offset[1 - variable_axis] += scrollbars[i].element->GetBox().GetEdge(BoxArea::Margin, i == VERTICAL ? BoxEdge::Top : BoxEdge::Left); + scrollbars[i].element->SetOffset(offset, element, true); + } + + // Format the corner, if it is necessary. + if (scrollbars[0].enabled && scrollbars[1].enabled) + { + CreateCorner(); + + Box corner_box; + LayoutDetails::BuildBox(corner_box, Vector2f(containing_block.x), corner); + + corner_box.SetContent(Vector2f(scrollbars[VERTICAL].size, scrollbars[HORIZONTAL].size)); + corner->SetBox(corner_box); + corner->SetOffset(containing_block + element_box.GetPosition(BoxArea::Padding) - + Vector2f(scrollbars[VERTICAL].size, scrollbars[HORIZONTAL].size) - corner_box.GetPosition(BoxArea::Margin), + element, true); + + corner->SetProperty(PropertyId::Visibility, Property(Style::Visibility::Visible)); + } +} + +void ElementScroll::UpdateProperties() +{ + for (Element* scroll_element : {scrollbars[VERTICAL].element, scrollbars[HORIZONTAL].element, corner}) + { + if (scroll_element) + UpdateScrollElementProperties(scroll_element); + } +} + +bool ElementScroll::CreateScrollbar(Orientation orientation) +{ + if (scrollbars[orientation].element && scrollbars[orientation].widget) + return true; + + ElementPtr scrollbar_element = + Factory::InstanceElement(element, "*", orientation == VERTICAL ? "scrollbarvertical" : "scrollbarhorizontal", XMLAttributes()); + scrollbars[orientation].element = scrollbar_element.get(); + scrollbars[orientation].element->SetProperty(PropertyId::Clip, Property(1, Unit::NUMBER)); + scrollbars[orientation].element->SetProperty(PropertyId::Drag, Property(Style::Drag::Block)); + + scrollbars[orientation].widget = MakeUnique(scrollbars[orientation].element); + scrollbars[orientation].widget->Initialise(orientation == VERTICAL ? WidgetScroll::VERTICAL : WidgetScroll::HORIZONTAL); + + Element* child = element->AppendChild(std::move(scrollbar_element), false); + + UpdateScrollElementProperties(child); + + return true; +} + +bool ElementScroll::CreateCorner() +{ + if (corner != nullptr) + return true; + + ElementPtr corner_element = Factory::InstanceElement(element, "*", "scrollbarcorner", XMLAttributes()); + corner = corner_element.get(); + corner->SetProperty(PropertyId::Clip, Property(1, Unit::NUMBER)); + corner->SetProperty(PropertyId::Drag, Property(Style::Drag::Block)); + + Element* child = element->AppendChild(std::move(corner_element), false); + UpdateScrollElementProperties(child); + + return true; +} + +void ElementScroll::UpdateScrollElementProperties(Element* scroll_element) +{ + // The construction of scrollbars can occur during layouting, then we need some properties and computed values straight away. + // In particular their size. Furthermore, updating these properties straight away avoids dirtying the document after layouting, + // which may result in one less additional and unnecessary layouting procedure. + + Context* context = element->GetContext(); + + const float dp_ratio = (context ? context->GetDensityIndependentPixelRatio() : 1.0f); + const Vector2f vp_dimensions = (context ? Vector2f(context->GetDimensions()) : Vector2f(1.0f)); + scroll_element->Update(dp_ratio, vp_dimensions); +} + +ElementScroll::Scrollbar::Scrollbar() {} + +ElementScroll::Scrollbar::~Scrollbar() {} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementStyle.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementStyle.cpp new file mode 100644 index 00000000..1d301550 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementStyle.cpp @@ -0,0 +1,910 @@ +#include "ElementStyle.h" +#include "../../Include/RmlUi/Core/ComputedValues.h" +#include "../../Include/RmlUi/Core/Context.h" +#include "../../Include/RmlUi/Core/Core.h" +#include "../../Include/RmlUi/Core/ElementDocument.h" +#include "../../Include/RmlUi/Core/ElementUtilities.h" +#include "../../Include/RmlUi/Core/FontEngineInterface.h" +#include "../../Include/RmlUi/Core/Log.h" +#include "../../Include/RmlUi/Core/Math.h" +#include "../../Include/RmlUi/Core/Profiling.h" +#include "../../Include/RmlUi/Core/Property.h" +#include "../../Include/RmlUi/Core/PropertyDefinition.h" +#include "../../Include/RmlUi/Core/PropertyDictionary.h" +#include "../../Include/RmlUi/Core/PropertyIdSet.h" +#include "../../Include/RmlUi/Core/StyleSheet.h" +#include "../../Include/RmlUi/Core/StyleSheetSpecification.h" +#include "../../Include/RmlUi/Core/TransformPrimitive.h" +#include "ComputeProperty.h" +#include "ElementDefinition.h" +#include "PropertiesIterator.h" +#include + +namespace Rml { + +inline PseudoClassState operator|(PseudoClassState lhs, PseudoClassState rhs) +{ + return PseudoClassState(int(lhs) | int(rhs)); +} +inline PseudoClassState operator&(PseudoClassState lhs, PseudoClassState rhs) +{ + return PseudoClassState(int(lhs) & int(rhs)); +} + +ElementStyle::ElementStyle(Element* _element) +{ + element = _element; +} + +const Property* ElementStyle::GetLocalProperty(PropertyId id, const PropertyDictionary& inline_properties, const ElementDefinition* definition) +{ + // Check for overriding local properties. + const Property* property = inline_properties.GetProperty(id); + if (property) + return property; + + // Check for a property defined in an RCSS rule. + if (definition) + return definition->GetProperty(id); + + return nullptr; +} + +const Property* ElementStyle::GetProperty(PropertyId id, const Element* element, const PropertyDictionary& inline_properties, + const ElementDefinition* definition) +{ + const Property* local_property = GetLocalProperty(id, inline_properties, definition); + if (local_property) + return local_property; + + // Fetch the property specification. + const PropertyDefinition* property = StyleSheetSpecification::GetProperty(id); + if (!property) + return nullptr; + + // If we can inherit this property, return our parent's property. + if (property->IsInherited()) + { + Element* parent = element->GetParentNode(); + while (parent) + { + const Property* parent_property = parent->GetStyle()->GetLocalProperty(id); + if (parent_property) + return parent_property; + + parent = parent->GetParentNode(); + } + } + + // No property available! Return the default value. + return property->GetDefaultValue(); +} + +void ElementStyle::TransitionPropertyChanges(Element* element, PropertyIdSet& properties, const PropertyDictionary& inline_properties, + const ElementDefinition* old_definition, const ElementDefinition* new_definition) +{ + // Apply transition to relevant properties if a transition is defined on element. + // Properties that are part of a transition are removed from the properties list. + + RMLUI_ASSERT(element); + if (!old_definition || !new_definition || properties.Empty()) + return; + + // We get the local property instead of the computed value here, because we want to intercept property changes even before the computed values are + // ready. Now that we have the concept of computed values, we may want do this operation directly on them instead. + if (const Property* transition_property = GetLocalProperty(PropertyId::Transition, inline_properties, new_definition)) + { + if (transition_property->value.GetType() != Variant::TRANSITIONLIST) + return; + + const TransitionList& transition_list = transition_property->value.GetReference(); + + if (!transition_list.none) + { + static const PropertyDictionary empty_properties; + + auto add_transition = [&](const Transition& transition) { + bool transition_added = false; + const Property* start_value = GetProperty(transition.id, element, inline_properties, old_definition); + const Property* target_value = GetProperty(transition.id, element, empty_properties, new_definition); + if (start_value && target_value && (*start_value != *target_value)) + transition_added = element->StartTransition(transition, *start_value, *target_value); + return transition_added; + }; + + if (transition_list.all) + { + Transition transition = transition_list.transitions[0]; + for (auto it = properties.begin(); it != properties.end();) + { + transition.id = *it; + if (add_transition(transition)) + it = properties.Erase(it); + else + ++it; + } + } + else + { + for (const Transition& transition : transition_list.transitions) + { + if (properties.Contains(transition.id)) + { + if (add_transition(transition)) + properties.Erase(transition.id); + } + } + } + } + } +} + +void ElementStyle::UpdateDefinition() +{ + RMLUI_ZoneScoped; + + SharedPtr new_definition; + + if (const StyleSheet* style_sheet = element->GetStyleSheet()) + { + new_definition = style_sheet->GetElementDefinition(element); + } + + // Switch the property definitions if the definition has changed. + if (new_definition != definition) + { + PropertyIdSet changed_properties; + + if (definition) + changed_properties = definition->GetPropertyIds(); + + if (new_definition) + changed_properties |= new_definition->GetPropertyIds(); + + if (definition && new_definition) + { + // Remove properties that compare equal from the changed list. + const PropertyIdSet properties_in_both_definitions = (definition->GetPropertyIds() & new_definition->GetPropertyIds()); + + for (PropertyId id : properties_in_both_definitions) + { + const Property* p0 = definition->GetProperty(id); + const Property* p1 = new_definition->GetProperty(id); + if (p0 && p1 && *p0 == *p1) + changed_properties.Erase(id); + } + + // Transition changed properties if transition property is set + TransitionPropertyChanges(element, changed_properties, inline_properties, definition.get(), new_definition.get()); + } + + definition = new_definition; + + DirtyProperties(changed_properties); + } +} + +bool ElementStyle::SetPseudoClass(const String& pseudo_class, bool activate, bool override_class) +{ + bool changed = false; + + if (activate) + { + PseudoClassState& state = pseudo_classes[pseudo_class]; + changed = (state == PseudoClassState::Clear); + state = (state | (override_class ? PseudoClassState::Override : PseudoClassState::Set)); + } + else + { + auto it = pseudo_classes.find(pseudo_class); + if (it != pseudo_classes.end()) + { + PseudoClassState& state = it->second; + state = (state & (override_class ? PseudoClassState::Set : PseudoClassState::Override)); + if (state == PseudoClassState::Clear) + { + pseudo_classes.erase(it); + changed = true; + } + } + } + + return changed; +} + +bool ElementStyle::IsPseudoClassSet(const String& pseudo_class) const +{ + return (pseudo_classes.count(pseudo_class) == 1); +} + +const PseudoClassMap& ElementStyle::GetActivePseudoClasses() const +{ + return pseudo_classes; +} + +bool ElementStyle::SetClass(const String& class_name, bool activate) +{ + const auto class_location = std::find(classes.begin(), classes.end(), class_name); + + bool changed = false; + if (activate) + { + if (class_location == classes.end()) + { + classes.push_back(class_name); + changed = true; + } + } + else + { + if (class_location != classes.end()) + { + classes.erase(class_location); + changed = true; + } + } + + return changed; +} + +bool ElementStyle::IsClassSet(const String& class_name) const +{ + return std::find(classes.begin(), classes.end(), class_name) != classes.end(); +} + +void ElementStyle::SetClassNames(const String& class_names) +{ + classes.clear(); + StringUtilities::ExpandString(classes, class_names, ' '); +} + +String ElementStyle::GetClassNames() const +{ + String class_names; + for (size_t i = 0; i < classes.size(); i++) + { + if (i != 0) + { + class_names += " "; + } + class_names += classes[i]; + } + + return class_names; +} + +const StringList& ElementStyle::GetClassNameList() const +{ + return classes; +} + +bool ElementStyle::SetProperty(PropertyId id, const Property& property) +{ + Property new_property = property; + + new_property.definition = StyleSheetSpecification::GetProperty(id); + if (!new_property.definition) + return false; + + inline_properties.SetProperty(id, new_property); + DirtyProperty(id); + + return true; +} + +void ElementStyle::RemoveProperty(PropertyId id) +{ + int size_before = inline_properties.GetNumProperties(); + inline_properties.RemoveProperty(id); + + if (inline_properties.GetNumProperties() != size_before) + DirtyProperty(id); +} + +const Property* ElementStyle::GetProperty(PropertyId id) const +{ + return GetProperty(id, element, inline_properties, definition.get()); +} + +const Property* ElementStyle::GetLocalProperty(PropertyId id) const +{ + return GetLocalProperty(id, inline_properties, definition.get()); +} + +const PropertyMap& ElementStyle::GetLocalStyleProperties() const +{ + return inline_properties.GetProperties(); +} + +static float ComputeLength(NumericValue value, Element* element) +{ + float font_size = 0.f; + float doc_font_size = 0.f; + float dp_ratio = 1.0f; + Vector2f vp_dimensions(1.0f); + + if (Any(value.unit & Unit::DP_SCALABLE_LENGTH)) + { + if (Context* context = element->GetContext()) + dp_ratio = context->GetDensityIndependentPixelRatio(); + } + + switch (value.unit) + { + case Unit::EM: font_size = element->GetComputedValues().font_size(); break; + case Unit::REM: + if (ElementDocument* document = element->GetOwnerDocument()) + doc_font_size = document->GetComputedValues().font_size(); + else + doc_font_size = DefaultComputedValues().font_size(); + break; + case Unit::VW: + case Unit::VH: + if (Context* context = element->GetContext()) + vp_dimensions = Vector2f(context->GetDimensions()); + break; + default: break; + } + + const float result = ComputeLength(value, font_size, doc_font_size, dp_ratio, vp_dimensions); + return result; +} + +float ElementStyle::ResolveNumericValue(NumericValue value, float base_value) const +{ + if (value.unit == Unit::PX) + return value.number; + else if (Any(value.unit & Unit::LENGTH)) + return ComputeLength(value, element); + + switch (value.unit) + { + case Unit::NUMBER: return value.number * base_value; + case Unit::PERCENT: return value.number * base_value * 0.01f; + case Unit::X: return value.number; + case Unit::DEG: + case Unit::RAD: return ComputeAngle(value); + default: break; + } + + RMLUI_ERROR; + return 0.f; +} + +float ElementStyle::ResolveRelativeLength(NumericValue value, RelativeTarget relative_target) const +{ + // There is an exception on font-size properties, as 'em' units here refer to parent font size instead + if (Any(value.unit & Unit::LENGTH) && !(value.unit == Unit::EM && relative_target == RelativeTarget::ParentFontSize)) + { + const float result = ComputeLength(value, element); + return result; + } + + float base_value = 0.0f; + + switch (relative_target) + { + case RelativeTarget::None: base_value = 1.0f; break; + case RelativeTarget::ContainingBlockWidth: base_value = element->GetContainingBlock().x; break; + case RelativeTarget::ContainingBlockHeight: base_value = element->GetContainingBlock().y; break; + case RelativeTarget::FontSize: base_value = element->GetComputedValues().font_size(); break; + case RelativeTarget::ParentFontSize: + { + auto p = element->GetParentNode(); + base_value = (p ? p->GetComputedValues().font_size() : DefaultComputedValues().font_size()); + } + break; + case RelativeTarget::LineHeight: base_value = element->GetLineHeight(); break; + } + + float scale_value = 0.0f; + + switch (value.unit) + { + case Unit::EM: + case Unit::NUMBER: scale_value = value.number; break; + case Unit::PERCENT: scale_value = value.number * 0.01f; break; + default: break; + } + + return base_value * scale_value; +} + +void ElementStyle::DirtyInheritedProperties() +{ + dirty_properties |= StyleSheetSpecification::GetRegisteredInheritedProperties(); +} + +void ElementStyle::DirtyPropertiesWithUnits(Units units) +{ + // Dirty all the properties of this element that use the unit(s). + for (auto it = Iterate(); !it.AtEnd(); ++it) + { + auto name_property_pair = *it; + PropertyId id = name_property_pair.first; + const Property& property = name_property_pair.second; + if (Any(property.unit & units)) + DirtyProperty(id); + } +} + +void ElementStyle::DirtyPropertiesWithUnitsRecursive(Units units) +{ + DirtyPropertiesWithUnits(units); + + // Now dirty all of our descendant's properties that use the unit(s). + int num_children = element->GetNumChildren(true); + for (int i = 0; i < num_children; ++i) + element->GetChild(i)->GetStyle()->DirtyPropertiesWithUnitsRecursive(units); +} + +bool ElementStyle::AnyPropertiesDirty() const +{ + return !dirty_properties.Empty(); +} + +PropertiesIterator ElementStyle::Iterate() const +{ + const PropertyMap& property_map = inline_properties.GetProperties(); + auto it_style_begin = property_map.begin(); + auto it_style_end = property_map.end(); + + PropertyMap::const_iterator it_definition{}, it_definition_end{}; + if (definition) + { + const PropertyMap& definition_properties = definition->GetProperties().GetProperties(); + it_definition = definition_properties.begin(); + it_definition_end = definition_properties.end(); + } + return PropertiesIterator(it_style_begin, it_style_end, it_definition, it_definition_end); +} + +void ElementStyle::DirtyProperty(PropertyId id) +{ + dirty_properties.Insert(id); +} + +void ElementStyle::DirtyProperties(const PropertyIdSet& properties) +{ + dirty_properties |= properties; +} + +PropertyIdSet ElementStyle::ComputeValues(Style::ComputedValues& values, const Style::ComputedValues* parent_values, + const Style::ComputedValues* document_values, bool values_are_default_initialized, float dp_ratio, Vector2f vp_dimensions) +{ + if (dirty_properties.Empty()) + return PropertyIdSet(); + + RMLUI_ZoneScopedC(0xFF7F50); + + // Generally, this is how it works: + // 1. Assign default values (clears any removed properties) + // 2. Inherit inheritable values from parent + // 3. Assign any local properties (from inline style or stylesheet) + // 4. Dirty properties in children that are inherited + + const float font_size_before = values.font_size(); + const Style::LineHeight line_height_before = values.line_height(); + + // The next flag is just a small optimization, if the element was just created we don't need to copy all the default values. + if (!values_are_default_initialized) + { + // This needs to be done in case some properties were removed and thus not in our local style anymore. + // If we skipped this, the old dirty value would be unmodified, instead, now it is set to its default value. + // Strictly speaking, we only really need to do this for the dirty, non-inherited values. However, in most + // cases it seems simply assigning all non-inherited values is faster than iterating the dirty properties. + values.CopyNonInherited(DefaultComputedValues()); + } + + if (parent_values) + values.CopyInherited(*parent_values); + else if (!values_are_default_initialized) + values.CopyInherited(DefaultComputedValues()); + + bool dirty_em_properties = false; + + // Always do font-size first if dirty, because of em-relative values + if (dirty_properties.Contains(PropertyId::FontSize)) + { + if (auto p = GetLocalProperty(PropertyId::FontSize)) + values.font_size(ComputeFontsize(p->GetNumericValue(), values, parent_values, document_values, dp_ratio, vp_dimensions)); + else if (parent_values) + values.font_size(parent_values->font_size()); + + if (font_size_before != values.font_size()) + { + dirty_em_properties = true; + dirty_properties.Insert(PropertyId::LineHeight); + } + } + else + { + values.font_size(font_size_before); + } + + const float font_size = values.font_size(); + const float document_font_size = (document_values ? document_values->font_size() : DefaultComputedValues().font_size()); + + // Since vertical-align depends on line-height we compute this before iteration + if (dirty_properties.Contains(PropertyId::LineHeight)) + { + if (auto p = GetLocalProperty(PropertyId::LineHeight)) + { + values.line_height(ComputeLineHeight(p, font_size, document_font_size, dp_ratio, vp_dimensions)); + } + else if (parent_values) + { + // Line height has a special inheritance case for numbers/percent: they inherit them directly instead of computed length, but for lengths, + // they inherit the length. See CSS specs for details. Percent is already converted to number. + if (parent_values->line_height().inherit_type == Style::LineHeight::Number) + values.line_height(Style::LineHeight(font_size * parent_values->line_height().inherit_value, Style::LineHeight::Number, + parent_values->line_height().inherit_value)); + else + values.line_height(parent_values->line_height()); + } + + if (line_height_before.value != values.line_height().value || line_height_before.inherit_value != values.line_height().inherit_value) + dirty_properties.Insert(PropertyId::VerticalAlign); + } + else + { + values.line_height(line_height_before); + } + + bool dirty_font_face_handle = false; + + for (auto it = Iterate(); !it.AtEnd(); ++it) + { + auto name_property_pair = *it; + const PropertyId id = name_property_pair.first; + const Property* p = &name_property_pair.second; + + if (dirty_em_properties && p->unit == Unit::EM) + dirty_properties.Insert(id); + + using namespace Style; + + // clang-format off + switch (id) + { + case PropertyId::MarginTop: + values.margin_top(ComputeLengthPercentageAuto(p, font_size, document_font_size, dp_ratio, vp_dimensions)); + break; + case PropertyId::MarginRight: + values.margin_right(ComputeLengthPercentageAuto(p, font_size, document_font_size, dp_ratio, vp_dimensions)); + break; + case PropertyId::MarginBottom: + values.margin_bottom(ComputeLengthPercentageAuto(p, font_size, document_font_size, dp_ratio, vp_dimensions)); + break; + case PropertyId::MarginLeft: + values.margin_left(ComputeLengthPercentageAuto(p, font_size, document_font_size, dp_ratio, vp_dimensions)); + break; + + case PropertyId::PaddingTop: + values.padding_top(ComputeLengthPercentage(p, font_size, document_font_size, dp_ratio, vp_dimensions)); + break; + case PropertyId::PaddingRight: + values.padding_right(ComputeLengthPercentage(p, font_size, document_font_size, dp_ratio, vp_dimensions)); + break; + case PropertyId::PaddingBottom: + values.padding_bottom(ComputeLengthPercentage(p, font_size, document_font_size, dp_ratio, vp_dimensions)); + break; + case PropertyId::PaddingLeft: + values.padding_left(ComputeLengthPercentage(p, font_size, document_font_size, dp_ratio, vp_dimensions)); + break; + + case PropertyId::BorderTopWidth: + values.border_top_width(ComputeBorderWidth(ComputeLength(p->GetNumericValue(), font_size, document_font_size, dp_ratio, vp_dimensions))); + break; + case PropertyId::BorderRightWidth: + values.border_right_width( + ComputeBorderWidth(ComputeLength(p->GetNumericValue(), font_size, document_font_size, dp_ratio, vp_dimensions))); + break; + case PropertyId::BorderBottomWidth: + values.border_bottom_width( + ComputeBorderWidth(ComputeLength(p->GetNumericValue(), font_size, document_font_size, dp_ratio, vp_dimensions))); + break; + case PropertyId::BorderLeftWidth: + values.border_left_width(ComputeBorderWidth(ComputeLength(p->GetNumericValue(), font_size, document_font_size, dp_ratio, vp_dimensions))); + break; + + case PropertyId::BorderTopColor: + values.border_top_color(p->Get()); + break; + case PropertyId::BorderRightColor: + values.border_right_color(p->Get()); + break; + case PropertyId::BorderBottomColor: + values.border_bottom_color(p->Get()); + break; + case PropertyId::BorderLeftColor: + values.border_left_color(p->Get()); + break; + + case PropertyId::BorderTopLeftRadius: + values.border_top_left_radius(ComputeLength(p->GetNumericValue(), font_size, document_font_size, dp_ratio, vp_dimensions)); + break; + case PropertyId::BorderTopRightRadius: + values.border_top_right_radius(ComputeLength(p->GetNumericValue(), font_size, document_font_size, dp_ratio, vp_dimensions)); + break; + case PropertyId::BorderBottomRightRadius: + values.border_bottom_right_radius(ComputeLength(p->GetNumericValue(), font_size, document_font_size, dp_ratio, vp_dimensions)); + break; + case PropertyId::BorderBottomLeftRadius: + values.border_bottom_left_radius(ComputeLength(p->GetNumericValue(), font_size, document_font_size, dp_ratio, vp_dimensions)); + break; + + case PropertyId::Display: + values.display((Display)p->Get()); + break; + case PropertyId::Position: + values.position((Position)p->Get()); + break; + + case PropertyId::Top: + values.top(ComputeLengthPercentageAuto(p, font_size, document_font_size, dp_ratio, vp_dimensions)); + break; + case PropertyId::Right: + values.right(ComputeLengthPercentageAuto(p, font_size, document_font_size, dp_ratio, vp_dimensions)); + break; + case PropertyId::Bottom: + values.bottom(ComputeLengthPercentageAuto(p, font_size, document_font_size, dp_ratio, vp_dimensions)); + break; + case PropertyId::Left: + values.left(ComputeLengthPercentageAuto(p, font_size, document_font_size, dp_ratio, vp_dimensions)); + break; + + case PropertyId::Float: + values.float_((Float)p->Get()); + break; + case PropertyId::Clear: + values.clear((Clear)p->Get()); + break; + case PropertyId::BoxSizing: + values.box_sizing((BoxSizing)p->Get()); + break; + + case PropertyId::ZIndex: + values.z_index((p->unit == Unit::KEYWORD ? ZIndex(ZIndex::Auto) : ZIndex(ZIndex::Number, p->Get()))); + break; + + case PropertyId::Width: + values.width(ComputeLengthPercentageAuto(p, font_size, document_font_size, dp_ratio, vp_dimensions)); + break; + case PropertyId::MinWidth: + values.min_width(ComputeLengthPercentage(p, font_size, document_font_size, dp_ratio, vp_dimensions)); + break; + case PropertyId::MaxWidth: + values.max_width(ComputeMaxSize(p, font_size, document_font_size, dp_ratio, vp_dimensions)); + break; + + case PropertyId::Height: + values.height(ComputeLengthPercentageAuto(p, font_size, document_font_size, dp_ratio, vp_dimensions)); + break; + case PropertyId::MinHeight: + values.min_height(ComputeLengthPercentage(p, font_size, document_font_size, dp_ratio, vp_dimensions)); + break; + case PropertyId::MaxHeight: + values.max_height(ComputeMaxSize(p, font_size, document_font_size, dp_ratio, vp_dimensions)); + break; + + case PropertyId::LineHeight: + // (Line-height computed above) + break; + case PropertyId::VerticalAlign: + values.vertical_align(ComputeVerticalAlign(p, values.line_height().value, font_size, document_font_size, dp_ratio, vp_dimensions)); + break; + + case PropertyId::OverflowX: + values.overflow_x((Overflow)p->Get< int >()); + break; + case PropertyId::OverflowY: + values.overflow_y((Overflow)p->Get< int >()); + break; + case PropertyId::Clip: + values.clip(ComputeClip(p)); + break; + case PropertyId::Visibility: + values.visibility((Visibility)p->Get< int >()); + break; + case PropertyId::TextOverflow: + values.text_overflow(p->unit == Unit::KEYWORD ? p->Get() : TextOverflow::String); + break; + + case PropertyId::BackgroundColor: + values.background_color(p->Get()); + break; + case PropertyId::Color: + values.color(p->Get()); + break; + case PropertyId::ImageColor: + values.image_color(p->Get()); + break; + case PropertyId::Opacity: + values.opacity(p->Get()); + break; + + case PropertyId::FontFamily: + // Fetched from element's properties. + dirty_font_face_handle = true; + break; + case PropertyId::FontStyle: + values.font_style((FontStyle)p->Get< int >()); + dirty_font_face_handle = true; + break; + case PropertyId::FontWeight: + values.font_weight((FontWeight)p->Get< int >()); + dirty_font_face_handle = true; + break; + case PropertyId::FontSize: + // (font-size computed above) + dirty_font_face_handle = true; + break; + case PropertyId::FontKerning: + values.font_kerning((FontKerning)p->Get()); + dirty_font_face_handle = true; + break; + case PropertyId::LetterSpacing: + values.has_letter_spacing(p->unit != Unit::KEYWORD); + dirty_font_face_handle = true; + break; + + case PropertyId::TextAlign: + values.text_align((TextAlign)p->Get()); + break; + case PropertyId::TextDecoration: + values.text_decoration((TextDecoration)p->Get()); + break; + case PropertyId::TextTransform: + values.text_transform((TextTransform)p->Get()); + break; + case PropertyId::WhiteSpace: + values.white_space((WhiteSpace)p->Get()); + break; + case PropertyId::WordBreak: + values.word_break((WordBreak)p->Get()); + break; + + case PropertyId::RowGap: + values.row_gap(ComputeLengthPercentage(p, font_size, document_font_size, dp_ratio, vp_dimensions)); + break; + case PropertyId::ColumnGap: + values.column_gap(ComputeLengthPercentage(p, font_size, document_font_size, dp_ratio, vp_dimensions)); + break; + + case PropertyId::Drag: + values.drag((Drag)p->Get< int >()); + break; + case PropertyId::TabIndex: + values.tab_index((TabIndex)p->Get< int >()); + break; + case PropertyId::Focus: + values.focus((Focus)p->Get()); + break; + case PropertyId::ScrollbarMargin: + values.scrollbar_margin(ComputeLength(p->GetNumericValue(), font_size, document_font_size, dp_ratio, vp_dimensions)); + break; + case PropertyId::OverscrollBehavior: + values.overscroll_behavior((OverscrollBehavior)p->Get()); + break; + case PropertyId::PointerEvents: + values.pointer_events((PointerEvents)p->Get()); + break; + + case PropertyId::Perspective: + values.perspective(p->unit == Unit::KEYWORD ? 0.f : ComputeLength(p->GetNumericValue(), font_size, document_font_size, dp_ratio, vp_dimensions)); + values.has_local_perspective(values.perspective() > 0.f); + break; + case PropertyId::PerspectiveOriginX: + values.perspective_origin_x(ComputeOrigin(p, font_size, document_font_size, dp_ratio, vp_dimensions)); + break; + case PropertyId::PerspectiveOriginY: + values.perspective_origin_y(ComputeOrigin(p, font_size, document_font_size, dp_ratio, vp_dimensions)); + break; + + case PropertyId::Transform: + values.has_local_transform(p->Get() != nullptr); + break; + case PropertyId::TransformOriginX: + values.transform_origin_x(ComputeOrigin(p, font_size, document_font_size, dp_ratio, vp_dimensions)); + break; + case PropertyId::TransformOriginY: + values.transform_origin_y(ComputeOrigin(p, font_size, document_font_size, dp_ratio, vp_dimensions)); + break; + case PropertyId::TransformOriginZ: + values.transform_origin_z(ComputeLength(p->GetNumericValue(), font_size, document_font_size, dp_ratio, vp_dimensions)); + break; + + case PropertyId::Decorator: + values.has_decorator(p->unit == Unit::DECORATOR && p->value.GetType() == Variant::DECORATORSPTR && p->value.GetReference()); + break; + case PropertyId::MaskImage: + values.has_mask_image(p->unit == Unit::DECORATOR && p->value.GetType() == Variant::DECORATORSPTR && p->value.GetReference()); + break; + case PropertyId::FontEffect: + values.has_font_effect(p->unit == Unit::FONTEFFECT && p->value.GetType() == Variant::FONTEFFECTSPTR && p->value.GetReference()); + break; + case PropertyId::Filter: + values.has_filter(p->unit == Unit::FILTER && p->value.GetType() == Variant::FILTERSPTR && p->value.GetReference()); + break; + case PropertyId::BackdropFilter: + values.has_backdrop_filter(p->unit == Unit::FILTER && p->value.GetType() == Variant::FILTERSPTR && p->value.GetReference()); + break; + case PropertyId::BoxShadow: + values.has_box_shadow(p->unit == Unit::BOXSHADOWLIST && p->value.GetType() == Variant::BOXSHADOWLIST && !p->value.GetReference().empty()); + break; + + case PropertyId::FlexBasis: + values.flex_basis(ComputeLengthPercentageAuto(p, font_size, document_font_size, dp_ratio, vp_dimensions)); + break; + + case PropertyId::RmlUi_Language: + values.language(p->Get()); + break; + case PropertyId::RmlUi_Direction: + values.direction(p->Get()); + break; + + // Fetched from element's properties. + case PropertyId::Cursor: + case PropertyId::Transition: + case PropertyId::Animation: + case PropertyId::AlignContent: + case PropertyId::AlignItems: + case PropertyId::AlignSelf: + case PropertyId::FlexDirection: + case PropertyId::FlexGrow: + case PropertyId::FlexShrink: + case PropertyId::FlexWrap: + case PropertyId::JustifyContent: + break; + // Navigation properties. Must be manually retrieved with 'GetProperty()'. + case PropertyId::NavUp: + case PropertyId::NavDown: + case PropertyId::NavLeft: + case PropertyId::NavRight: + break; + // Unhandled properties. Must be manually retrieved with 'GetProperty()'. + case PropertyId::FillImage: + case PropertyId::CaretColor: + break; + // Invalid properties + case PropertyId::Invalid: + case PropertyId::NumDefinedIds: + case PropertyId::MaxNumIds: + break; + } + // clang-format on + } + + // The font-face handle is nulled when local font properties are set. In that case we need to retrieve a new handle. + if (dirty_font_face_handle) + { + RMLUI_ZoneScopedN("FontFaceHandle"); + values.font_face_handle( + GetFontEngineInterface()->GetFontFaceHandle(values.font_family(), values.font_style(), values.font_weight(), (int)values.font_size())); + } + + // Next, pass inheritable dirty properties onto our children + PropertyIdSet dirty_inherited_properties = (dirty_properties & StyleSheetSpecification::GetRegisteredInheritedProperties()); + + // Special case for text-overflow: It's not really inherited, but the value is used by the element's children. Insert + // it here so they are notified of a change. + if (dirty_properties.Contains(PropertyId::TextOverflow)) + dirty_inherited_properties.Insert(PropertyId::TextOverflow); + + if (!dirty_inherited_properties.Empty()) + { + for (int i = 0; i < element->GetNumChildren(true); i++) + { + auto child = element->GetChild(i); + child->GetStyle()->dirty_properties |= dirty_inherited_properties; + } + } + + PropertyIdSet result(std::move(dirty_properties)); + dirty_properties.Clear(); + return result; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementStyle.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementStyle.h new file mode 100644 index 00000000..279e1496 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementStyle.h @@ -0,0 +1,142 @@ +#pragma once + +#include "../../Include/RmlUi/Core/ComputedValues.h" +#include "../../Include/RmlUi/Core/PropertyDictionary.h" +#include "../../Include/RmlUi/Core/PropertyIdSet.h" +#include "../../Include/RmlUi/Core/Types.h" + +namespace Rml { + +class ElementDefinition; +class PropertiesIterator; +enum class RelativeTarget; + +enum class PseudoClassState : uint8_t { Clear = 0, Set = 1, Override = 2 }; +using PseudoClassMap = SmallUnorderedMap; + +/** + Manages an element's style and property information. + */ + +class ElementStyle { +public: + /// Constructor + /// @param[in] element The element this structure belongs to. + ElementStyle(Element* element); + + /// Update this definition if required + void UpdateDefinition(); + + /// Sets or removes a pseudo-class on the element. + /// @param[in] pseudo_class The pseudo class to activate or deactivate. + /// @param[in] activate True if the pseudo class is to be activated, false to be deactivated. + /// @param[in] override_class True to activate or deactivate the override state of the pseudo class, for advanced use cases. + /// @note An overridden pseudo class means that it will act as if activated even when it has been cleared the normal way. + /// @return True if the pseudo class was changed. + bool SetPseudoClass(const String& pseudo_class, bool activate, bool override_class = false); + /// Checks if a specific pseudo-class has been set on the element. + /// @param[in] pseudo_class The name of the pseudo-class to check for. + /// @return True if the pseudo-class is set on the element, false if not. + bool IsPseudoClassSet(const String& pseudo_class) const; + /// Gets a list of the current active pseudo classes + const PseudoClassMap& GetActivePseudoClasses() const; + + /// Sets or removes a class on the element. + /// @param[in] class_name The name of the class to add or remove from the class list. + /// @param[in] activate True if the class is to be added, false to be removed. + /// @return True if the class was changed, false otherwise. + bool SetClass(const String& class_name, bool activate); + /// Checks if a class is set on the element. + /// @param[in] class_name The name of the class to check for. + /// @return True if the class is set on the element, false otherwise. + bool IsClassSet(const String& class_name) const; + /// Specifies the entire list of classes for this element. This will replace any others specified. + /// @param[in] class_names The list of class names to set on the style, separated by spaces. + void SetClassNames(const String& class_names); + /// Return the active class list. + /// @return A string containing all the classes on the element, separated by spaces. + String GetClassNames() const; + /// Return the active class list. + const StringList& GetClassNameList() const; + + /// Sets a local property override on the element to a pre-parsed value. + /// @param[in] id The ID of the new property. + /// @param[in] property The parsed property to set. + bool SetProperty(PropertyId id, const Property& property); + /// Removes a local property override on the element; its value will revert to that defined in + /// the style sheet. + /// @param[in] id The ID of the local property definition to remove. + void RemoveProperty(PropertyId id); + /// Returns one of this element's properties. If this element is not defined this property, or a parent cannot + /// be found that we can inherit the property from, the default value will be returned. + /// @param[in] id The ID of the property to fetch the value for. + /// @return The value of this property for this element, or nullptr if no property exists with the given name. + const Property* GetProperty(PropertyId id) const; + /// Returns one of this element's properties. If this element is not defined this property, nullptr will be + /// returned. + /// @param[in] id The ID of the property to fetch the value for. + /// @return The value of this property for this element, or nullptr if this property has not been explicitly defined for this element. + const Property* GetLocalProperty(PropertyId id) const; + /// Returns the local style properties, excluding any properties from local class. + const PropertyMap& GetLocalStyleProperties() const; + + /// Resolves a numeric value with units of number, percentage, length, or angle to their canonical unit (unit-less, 'px', or 'rad'). + /// @param[in] value The value to be resolved. + /// @param[in] base_value The value that is scaled by the number or percentage value, if applicable. + /// @return The resolved value in their canonical unit, or zero if it could not be resolved. + float ResolveNumericValue(NumericValue value, float base_value) const; + /// Resolves a property with units of number, length, or percentage to a length in 'px' units. + /// Numbers and percentages are resolved by scaling the size of the specified target. + float ResolveRelativeLength(NumericValue value, RelativeTarget relative_target) const; + + /// Mark inherited properties dirty. + /// Inherited properties will automatically be set when parent inherited properties are changed. However, + /// some operations may require to dirty these manually, such as when moving an element into another. + void DirtyInheritedProperties(); + + // Sets a single property as dirty. + void DirtyProperty(PropertyId id); + /// Dirties all properties with any of the given units (OR-ed together) on the current element (*not* recursive). + void DirtyPropertiesWithUnits(Units units); + /// Dirties all properties with any of the given units (OR-ed together) on the current element and recursively on all children. + void DirtyPropertiesWithUnitsRecursive(Units units); + + /// Returns true if any properties are dirty such that computed values need to be recomputed + bool AnyPropertiesDirty() const; + + /// Turns the local and inherited properties into computed values for this element. These values can in turn be used during the layout procedure. + /// Must be called in correct order, always parent before its children. + PropertyIdSet ComputeValues(Style::ComputedValues& values, const Style::ComputedValues* parent_values, + const Style::ComputedValues* document_values, bool values_are_default_initialized, float dp_ratio, Vector2f vp_dimensions); + + /// Returns an iterator for iterating the local properties of this element. + /// Note: Modifying the element's style invalidates its iterator. + PropertiesIterator Iterate() const; + +private: + // Sets a list of properties as dirty. + void DirtyProperties(const PropertyIdSet& properties); + + static const Property* GetLocalProperty(PropertyId id, const PropertyDictionary& inline_properties, const ElementDefinition* definition); + static const Property* GetProperty(PropertyId id, const Element* element, const PropertyDictionary& inline_properties, + const ElementDefinition* definition); + static void TransitionPropertyChanges(Element* element, PropertyIdSet& properties, const PropertyDictionary& inline_properties, + const ElementDefinition* old_definition, const ElementDefinition* new_definition); + + // Element these properties belong to + Element* element; + + // The list of classes applicable to this object. + StringList classes; + // This element's current pseudo-classes. + PseudoClassMap pseudo_classes; + + // Any properties that have been overridden in this element. + PropertyDictionary inline_properties; + // The definition of this element, provides applicable properties from the stylesheet. + SharedPtr definition; + + PropertyIdSet dirty_properties; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementText.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementText.cpp new file mode 100644 index 00000000..51c52889 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementText.cpp @@ -0,0 +1,722 @@ +#include "../../Include/RmlUi/Core/ElementText.h" +#include "../../Include/RmlUi/Core/Context.h" +#include "../../Include/RmlUi/Core/Core.h" +#include "../../Include/RmlUi/Core/ElementDocument.h" +#include "../../Include/RmlUi/Core/ElementUtilities.h" +#include "../../Include/RmlUi/Core/Event.h" +#include "../../Include/RmlUi/Core/FontEngineInterface.h" +#include "../../Include/RmlUi/Core/MeshUtilities.h" +#include "../../Include/RmlUi/Core/Profiling.h" +#include "../../Include/RmlUi/Core/Property.h" +#include "../../Include/RmlUi/Core/RenderManager.h" +#include "../../Include/RmlUi/Core/TextShapingContext.h" +#include "ComputeProperty.h" +#include "ElementDefinition.h" +#include "ElementStyle.h" +#include "TransformState.h" +#include + +namespace Rml { + +static bool BuildToken(String& token, const char*& token_begin, const char* string_end, bool first_token, bool collapse_white_space, + bool break_at_endline, Style::TextTransform text_transformation, bool decode_escape_characters); +static bool LastToken(const char* token_begin, const char* string_end, bool collapse_white_space, bool break_at_endline); + +static int RoundDownToIntegerClamped(float value) +{ + constexpr int clamp = (1 << std::numeric_limits::digits); + constexpr float clamp_f = float{clamp}; + if (value >= clamp_f) + return clamp; + if (value <= -clamp_f) + return -clamp; + return static_cast(value); +} + +struct TextOverflowResolved { + bool enabled = false; + float overflow_width = 0.f; + String overflow_text; +}; + +static TextOverflowResolved ResolveTextOverflow(Element* parent, FontFaceHandle font_face_handle) +{ + if (!parent) + return {}; + + const auto& parent_computed = parent->GetComputedValues(); + if (parent_computed.overflow_x() == Style::Overflow::Visible && parent_computed.overflow_y() == Style::Overflow::Visible) + return {}; + + const Style::TextOverflow text_overflow = parent_computed.text_overflow(); + if (text_overflow == Style::TextOverflow::Clip) + return {}; + + const Box& box = parent->GetBox(); + const BoxArea clip_area = parent->GetClipArea(); + + auto AccumulateRightSideEdgesUpTo = [](const Box& box, BoxArea up_to_area) -> float { + float result = 0; + for (int i = (int)up_to_area; i < int(BoxArea::Content); i++) + result += box.GetEdge((BoxArea)i, BoxEdge::Right); + return result; + }; + + const float overflow_width = parent->GetScrollLeft() + box.GetSize().x + AccumulateRightSideEdgesUpTo(box, clip_area); + + constexpr char ellipsis_chars[] = "\xE2\x80\xA6"; // U+2026 + constexpr char dots_chars[] = "..."; + + String overflow_text = (text_overflow == Style::TextOverflow::String + ? parent->GetComputedValues().text_overflow_string() + : (GetFontEngineInterface()->GetFontMetrics(font_face_handle).has_ellipsis ? ellipsis_chars : dots_chars)); + + return TextOverflowResolved{ + true, + overflow_width, + std::move(overflow_text), + }; +} + +void LogMissingFontFace(Element* element) +{ + const String font_family_property = element->GetProperty("font-family"); + if (font_family_property.empty()) + { + Log::Message(Log::LT_WARNING, "No font face defined. Missing 'font-family' property. On element %s", element->GetAddress().c_str()); + } + else + { + const ComputedValues& computed = element->GetComputedValues(); + const String font_face_description = GetFontFaceDescription(font_family_property, computed.font_style(), computed.font_weight()); + Log::Message(Log::LT_WARNING, + "No font face defined. Ensure (1) that Context::Update is run after new elements are constructed, before Context::Render, " + "and (2) that the specified font face %s has been successfully loaded. " + "Please see previous log messages for all successfully loaded fonts. On element %s", + font_face_description.c_str(), element->GetAddress().c_str()); + } +} + +ElementText::ElementText(const String& tag) : + Element(tag), colour(255, 255, 255), opacity(1), font_handle_version(0), geometry_dirty(true), dirty_layout_on_change(true), + generated_decoration(Style::TextDecoration::None), decoration_property(Style::TextDecoration::None), font_effects_dirty(true), + font_effects_handle(0) +{} + +ElementText::~ElementText() {} + +void ElementText::SetText(const String& _text) +{ + if (text != _text) + { + text = _text; + + if (dirty_layout_on_change) + DirtyLayout(); + } +} + +const String& ElementText::GetText() const +{ + return text; +} + +void ElementText::OnRender() +{ + RMLUI_ZoneScoped; + + FontFaceHandle font_face_handle = GetFontFaceHandle(); + if (font_face_handle == 0) + return; + + RenderManager& render_manager = GetContext()->GetRenderManager(); + + // If our font effects have potentially changed, update it and force a geometry generation if necessary. + if (font_effects_dirty && UpdateFontEffects()) + geometry_dirty = true; + + // Dirty geometry if font version has changed. + int new_version = GetFontEngineInterface()->GetVersion(font_face_handle); + if (new_version != font_handle_version) + { + font_handle_version = new_version; + geometry_dirty = true; + } + + // Regenerate the geometry if the colour or font configuration has altered. + if (geometry_dirty) + GenerateGeometry(render_manager, font_face_handle); + + // Regenerate text decoration if necessary. + if (decoration_property != generated_decoration) + { + if (decoration_property == Style::TextDecoration::None) + { + decoration.reset(); + } + else + { + Mesh mesh; + if (decoration) + mesh = decoration->Release(Geometry::ReleaseMode::ClearMesh); + else + decoration = MakeUnique(); + + GenerateDecoration(mesh, font_face_handle); + *decoration = GetRenderManager()->MakeGeometry(std::move(mesh)); + } + + generated_decoration = decoration_property; + } + + const Vector2f translation = GetAbsoluteOffset(); + + bool render = true; + + // Do a visibility test against the scissor region to avoid unnecessary render calls. Instead of handling + // culling in complicated transform cases, for simplicity we always proceed to render if one is detected. + Rectanglei scissor_region = render_manager.GetScissorRegion(); + if (!scissor_region.Valid()) + scissor_region = Rectanglei::FromSize(render_manager.GetViewport()); + + if (!GetTransformState() || !GetTransformState()->GetTransform()) + { + const FontMetrics& font_metrics = GetFontEngineInterface()->GetFontMetrics(GetFontFaceHandle()); + const int ascent = Math::RoundUpToInteger(font_metrics.ascent); + const int descent = Math::RoundUpToInteger(font_metrics.descent); + + render = false; + for (const Line& line : lines) + { + const Vector2i baseline = Vector2i(translation + line.position); + const Rectanglei line_region = Rectanglei::FromCorners(baseline - Vector2i(0, ascent), baseline + Vector2i(line.width, descent)); + + if (line_region.Valid() && scissor_region.Intersects(line_region)) + { + render = true; + break; + } + } + } + + if (render) + { + for (size_t i = 0; i < geometry.size(); ++i) + geometry[i].geometry.Render(translation, geometry[i].texture); + } + + if (decoration) + decoration->Render(translation); +} + +bool ElementText::GenerateLine(String& line, int& line_length, float& line_width, int line_begin, float maximum_line_width, float right_spacing_width, + bool trim_whitespace_prefix, bool decode_escape_characters, bool allow_empty) +{ + RMLUI_ZoneScoped; + RMLUI_ASSERT(maximum_line_width >= 0.f); + + FontFaceHandle font_face_handle = GetFontFaceHandle(); + + // Initialise the output variables. + line.clear(); + line_length = 0; + line_width = 0; + + // Bail if we don't have a valid font face. + if (font_face_handle == 0) + { + LogMissingFontFace(GetParentNode() ? GetParentNode() : this); + return true; + } + + // Determine how we are processing white-space while formatting the text. + using namespace Style; + const auto& computed = GetComputedValues(); + const WhiteSpace white_space_property = computed.white_space(); + const bool collapse_white_space = + (white_space_property == WhiteSpace::Normal || white_space_property == WhiteSpace::Nowrap || white_space_property == WhiteSpace::Preline); + const bool break_at_line = + (white_space_property == WhiteSpace::Normal || white_space_property == WhiteSpace::Prewrap || white_space_property == WhiteSpace::Preline); + const bool break_at_endline = + (white_space_property == WhiteSpace::Pre || white_space_property == WhiteSpace::Prewrap || white_space_property == WhiteSpace::Preline); + + const TextShapingContext text_shaping_context{computed.language(), computed.direction(), computed.font_kerning(), computed.letter_spacing()}; + TextTransform text_transform_property = computed.text_transform(); + WordBreak word_break = computed.word_break(); + + FontEngineInterface* font_engine_interface = GetFontEngineInterface(); + + // Starting at the line_begin character, we generate sections of the text (we'll call them tokens) depending on the + // white-space parsing parameters. Each section is then appended to the line if it can fit. If not, or if an + // endline is found (and we're processing them), then the line is ended. kthxbai! + const char* token_begin = text.c_str() + line_begin; + const char* string_end = text.c_str() + text.size(); + while (token_begin != string_end) + { + String token; + const char* next_token_begin = token_begin; + Character previous_codepoint = Character::Null; + if (!line.empty()) + previous_codepoint = + StringUtilities::ToCharacter(StringUtilities::SeekBackwardUTF8(&line.back(), line.data()), line.data() + line.size()); + + // Generate the next token and determine its pixel-length. + bool break_line = BuildToken(token, next_token_begin, string_end, line.empty() && trim_whitespace_prefix, collapse_white_space, + break_at_endline, text_transform_property, decode_escape_characters); + int token_width = font_engine_interface->GetStringWidth(font_face_handle, token, text_shaping_context, previous_codepoint); + + // If we're breaking to fit a line box, check if the token can fit on the line before we add it. + if (break_at_line) + { + const bool is_last_token = LastToken(next_token_begin, string_end, collapse_white_space, break_at_endline); + int max_token_width = RoundDownToIntegerClamped(maximum_line_width - (is_last_token ? line_width + right_spacing_width : line_width)); + + if (token_width > max_token_width) + { + if (word_break == WordBreak::BreakAll || (word_break == WordBreak::BreakWord && line.empty())) + { + // Try to break up the word + max_token_width = RoundDownToIntegerClamped(maximum_line_width - line_width); + const int token_max_size = int(next_token_begin - token_begin); + const char* partial_string_end = token_begin + token_max_size; + + // @performance: Can be made much faster. Use string width heuristics and logarithmic search. + while (true) + { + partial_string_end = StringUtilities::SeekBackwardUTF8(partial_string_end - 1, token_begin); + + bool force_loop_break_at_end = false; + if (partial_string_end == token_begin) + { + // Not even the first character of the token fits. Let it overflow onto the next line if we can. + if (allow_empty || !line.empty()) + return false; + + // Continue by forcing the first character to be consumed, even though it will overflow. + partial_string_end = StringUtilities::SeekForwardUTF8(token_begin + 1, token_begin + token_max_size); + force_loop_break_at_end = true; + } + + token.clear(); + next_token_begin = token_begin; + BuildToken(token, next_token_begin, partial_string_end, line.empty() && trim_whitespace_prefix, collapse_white_space, + break_at_endline, text_transform_property, decode_escape_characters); + token_width = font_engine_interface->GetStringWidth(font_face_handle, token, text_shaping_context, previous_codepoint); + + if (force_loop_break_at_end || token_width <= max_token_width) + break; + } + + break_line = true; + } + else if (allow_empty || !line.empty()) + { + // Let the token overflow into the next line. + return false; + } + } + } + + // The token can fit on the end of the line, so add it onto the end and increment our width and length counters. + line += token; + line_length += (int)(next_token_begin - token_begin); + line_width += token_width; + + // Break out of the loop if an endline was forced. + if (break_line && (allow_empty || !line.empty())) + return false; + + // Set the beginning of the next token. + token_begin = next_token_begin; + } + + return true; +} + +void ElementText::ClearLines() +{ + RMLUI_ZoneScoped; + lines.clear(); + generated_decoration = Style::TextDecoration::None; + geometry_dirty = true; +} + +void ElementText::AddLine(Vector2f line_position, String line) +{ + if (font_effects_dirty) + UpdateFontEffects(); + + lines.emplace_back(std::move(line), line_position); + + geometry_dirty = true; +} + +void ElementText::SuppressAutoLayout() +{ + dirty_layout_on_change = false; +} + +void ElementText::OnPropertyChange(const PropertyIdSet& changed_properties) +{ + RMLUI_ZoneScoped; + + Element::OnPropertyChange(changed_properties); + + bool colour_changed = false; + bool font_face_changed = false; + auto& computed = GetComputedValues(); + + if (changed_properties.Contains(PropertyId::Color) || changed_properties.Contains(PropertyId::Opacity)) + { + const float new_opacity = computed.opacity(); + const bool opacity_changed = opacity != new_opacity; + + ColourbPremultiplied new_colour = computed.color().ToPremultiplied(new_opacity); + colour_changed = colour != new_colour; + + if (colour_changed) + { + colour = new_colour; + } + if (opacity_changed) + { + opacity = new_opacity; + font_effects_dirty = true; + geometry_dirty = true; + } + } + + if (changed_properties.Contains(PropertyId::FontFamily) || // + changed_properties.Contains(PropertyId::FontWeight) || // + changed_properties.Contains(PropertyId::FontStyle) || // + changed_properties.Contains(PropertyId::FontSize) || // + changed_properties.Contains(PropertyId::FontKerning) || // + changed_properties.Contains(PropertyId::LetterSpacing) || // + changed_properties.Contains(PropertyId::RmlUi_Language) || // + changed_properties.Contains(PropertyId::RmlUi_Direction) || // + changed_properties.Contains(PropertyId::TextOverflow)) + { + font_face_changed = true; + geometry_dirty = true; + + font_effects_handle = 0; + font_effects_dirty = true; + font_handle_version = 0; + } + + if (changed_properties.Contains(PropertyId::FontEffect)) + { + font_effects_dirty = true; + } + + if (changed_properties.Contains(PropertyId::TextDecoration)) + { + decoration_property = computed.text_decoration(); + if (decoration && decoration_property == Style::TextDecoration::None) + decoration.reset(); + } + + if (font_face_changed) + { + // We have to let our document know we need to be regenerated. + if (dirty_layout_on_change) + DirtyLayout(); + } + else if (colour_changed) + { + // Force the geometry to be regenerated. + geometry_dirty = true; + + // Re-colour the decoration geometry. + if (decoration) + { + Mesh mesh = decoration->Release(); + for (Vertex& vertex : mesh.vertices) + vertex.colour = colour; + + if (RenderManager* render_manager = GetRenderManager()) + *decoration = render_manager->MakeGeometry(std::move(mesh)); + } + } +} + +void ElementText::GetRML(String& content) +{ + content += StringUtilities::EncodeRml(text); +} + +bool ElementText::UpdateFontEffects() +{ + RMLUI_ZoneScoped; + + if (GetFontFaceHandle() == 0) + return false; + + font_effects_dirty = false; + + static const FontEffectList empty_font_effects; + + // Fetch the font-effect for this text element + const FontEffectList* font_effects = &empty_font_effects; + if (GetComputedValues().has_font_effect()) + { + if (const Property* p = GetProperty(PropertyId::FontEffect)) + if (FontEffectsPtr effects = p->Get()) + font_effects = &effects->list; + } + + // Request a font layer configuration to match this set of effects. If this is different from + // our old configuration, then return true to indicate we'll need to regenerate geometry. + FontEffectsHandle new_font_effects_handle = GetFontEngineInterface()->PrepareFontEffects(GetFontFaceHandle(), *font_effects); + if (new_font_effects_handle != font_effects_handle) + { + font_effects_handle = new_font_effects_handle; + return true; + } + + return false; +} + +void ElementText::GenerateGeometry(RenderManager& render_manager, const FontFaceHandle font_face_handle) +{ + RMLUI_ZoneScopedC(0xD2691E); + + const TextOverflowResolved text_overflow = ResolveTextOverflow(GetParentNode(), font_face_handle); + + const auto& computed = GetComputedValues(); + const TextShapingContext text_shaping_context{computed.language(), computed.direction(), computed.font_kerning(), computed.letter_spacing()}; + + TexturedMeshList mesh_list; + mesh_list.reserve(geometry.size()); + + for (Line& line : lines) + { + line.width = GetFontEngineInterface()->GenerateString(render_manager, font_face_handle, font_effects_handle, line.text, line.position, colour, + opacity, text_shaping_context, mesh_list); + } + + const auto text_overflows_on_line = [&](const Line& line) { return line.position.x + line.width > text_overflow.overflow_width; }; + if (text_overflow.enabled && std::any_of(lines.begin(), lines.end(), text_overflows_on_line)) + { + mesh_list.clear(); + + for (Line& line : lines) + { + if (line.text.empty()) + continue; + + String abbreviated_text; + StringView text_submit_view = line.text; + StringIteratorU8 view(line.text, line.text.size()); + --view; + + // If we have text overflow, reduce the string one character at a time, append the ellipsis or custom + // string, and try again until it fits. @performance Can be improved by e.g. logarithmic search. Consider + // combining it with the word-breaking algorithm of 'GenerateLine'. + for (; text_overflows_on_line(line) && view && view.get() != line.text.c_str(); --view) + { + abbreviated_text.reserve(line.text.size() + text_overflow.overflow_text.size()); + abbreviated_text.assign(line.text.c_str(), view.get()); + abbreviated_text.append(text_overflow.overflow_text); + line.width = GetFontEngineInterface()->GetStringWidth(font_face_handle, abbreviated_text, text_shaping_context); + text_submit_view = abbreviated_text; + } + + line.width = GetFontEngineInterface()->GenerateString(render_manager, font_face_handle, font_effects_handle, text_submit_view, + line.position, colour, opacity, text_shaping_context, mesh_list); + } + } + + // Apply the new geometry and textures. Reuse the old geometry if the mesh matches, which can be relatively common + // where the layout is changed in a way that does not visually affect this element. + geometry.resize(mesh_list.size()); + for (size_t i = 0; i < mesh_list.size(); i++) + { + if (!geometry[i].geometry || geometry[i].geometry.GetMesh() != mesh_list[i].mesh) + geometry[i].geometry = render_manager.MakeGeometry(std::move(mesh_list[i].mesh)); + + geometry[i].texture = mesh_list[i].texture; + } + + generated_decoration = Style::TextDecoration::None; + geometry_dirty = false; +} + +void ElementText::GenerateDecoration(Mesh& mesh, const FontFaceHandle font_face_handle) +{ + RMLUI_ZoneScopedC(0xA52A2A); + RMLUI_ASSERT(decoration); + + const FontMetrics& metrics = GetFontEngineInterface()->GetFontMetrics(font_face_handle); + + float offset = 0.f; + switch (decoration_property) + { + case Style::TextDecoration::Underline: offset = metrics.underline_position; break; + case Style::TextDecoration::Overline: offset = -1.1f * metrics.ascent; break; + case Style::TextDecoration::LineThrough: offset = -0.65f * metrics.x_height; break; + case Style::TextDecoration::None: return; + } + + for (const Line& line : lines) + { + const Vector2f position = {line.position.x, line.position.y + offset}; + const Vector2f size = {(float)line.width, metrics.underline_thickness}; + MeshUtilities::GenerateLine(mesh, position, size, colour); + } +} + +static bool BuildToken(String& token, const char*& token_begin, const char* string_end, bool first_token, bool collapse_white_space, + bool break_at_endline, Style::TextTransform text_transformation, bool decode_escape_characters) +{ + RMLUI_ASSERT(token_begin != string_end); + + token.reserve(string_end - token_begin + token.size()); + + // Check what the first character of the token is; all we need to know is if it is white-space or not. + bool parsing_white_space = StringUtilities::IsWhitespace(*token_begin); + + // Loop through the string from the token's beginning until we find an end to the token. This can occur in various + // places, depending on the white-space processing; + // - at the end of a section of non-white-space characters, + // - at the end of a section of white-space characters, if we're not collapsing white-space, + // - at an endline token, if we're breaking on endlines. + while (token_begin != string_end) + { + bool force_non_whitespace = false; + char character = *token_begin; + + const char* escape_begin = token_begin; + + // Check for an ampersand; if we find one, we've got an HTML escaped character. + if (decode_escape_characters && character == '&') + { + // Find the terminating ';'. + while (token_begin != string_end && *token_begin != ';') + ++token_begin; + + // If we couldn't find the ';', print the token like normal text. + if (token_begin == string_end) + { + token_begin = escape_begin; + } + // We could find a ';', parse the escape code. If the escape code is recognised, set the parsed character + // to the appropriate one. If it is a non-breaking space, prevent it being picked up as whitespace. If it + // is not recognised, print the token like normal text. + else + { + String escape_code(escape_begin + 1, token_begin); + + if (escape_code == "lt") + character = '<'; + else if (escape_code == "gt") + character = '>'; + else if (escape_code == "amp") + character = '&'; + else if (escape_code == "quot") + character = '"'; + else if (escape_code == "nbsp") + { + character = ' '; + force_non_whitespace = true; + } + else + token_begin = escape_begin; + } + } + + // Check for an endline token; if we're breaking on endlines and we find one, then return true to indicate a + // forced break. + if (break_at_endline && character == '\n') + { + token += '\n'; + token_begin++; + return true; + } + + // If we've transitioned from white-space characters to non-white-space characters, or vice-versa, then check + // if should terminate the token; if we're not collapsing white-space, then yes (as sections of white-space are + // non-breaking), otherwise only if we've transitioned from characters to white-space. + bool white_space = !force_non_whitespace && StringUtilities::IsWhitespace(character); + if (white_space != parsing_white_space) + { + if (!collapse_white_space) + { + // Restore pointer to the beginning of the escaped token, if we processed an escape code. + token_begin = escape_begin; + return false; + } + + // We're collapsing white-space; we only tokenise words, not white-space, so we're only done tokenising + // once we've begun parsing non-white-space and then found white-space. + if (!parsing_white_space) + { + // However, if we are the last non-whitespace character in the string, and there are trailing + // whitespace characters after this token, then we need to append a single space to the end of this + // token. + if (token_begin != string_end && LastToken(token_begin, string_end, collapse_white_space, break_at_endline)) + token += ' '; + + return false; + } + + // We've transitioned from white-space to non-white-space, so we append a single white-space character. + if (!first_token) + token += ' '; + + parsing_white_space = false; + } + + // If the current character is white-space, we'll append a space character to the token if we're not collapsing + // sections of white-space. + if (white_space) + { + if (!collapse_white_space) + token += ' '; + } + else + { + if (text_transformation == Style::TextTransform::Uppercase) + { + if (character >= 'a' && character <= 'z') + character += ('A' - 'a'); + } + else if (text_transformation == Style::TextTransform::Lowercase) + { + if (character >= 'A' && character <= 'Z') + character -= ('A' - 'a'); + } + + token += character; + } + + ++token_begin; + } + + return false; +} + +static bool LastToken(const char* token_begin, const char* string_end, bool collapse_white_space, bool break_at_endline) +{ + bool last_token = (token_begin == string_end); + if (collapse_white_space && !last_token) + { + last_token = true; + const char* character = token_begin; + + while (character != string_end) + { + if (!StringUtilities::IsWhitespace(*character) || (break_at_endline && *character == '\n')) + { + last_token = false; + break; + } + + character++; + } + } + + return last_token; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementUtilities.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementUtilities.cpp new file mode 100644 index 00000000..ae3280b3 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ElementUtilities.cpp @@ -0,0 +1,490 @@ +#include "../../Include/RmlUi/Core/ElementUtilities.h" +#include "../../Include/RmlUi/Core/ComputedValues.h" +#include "../../Include/RmlUi/Core/Context.h" +#include "../../Include/RmlUi/Core/Core.h" +#include "../../Include/RmlUi/Core/DecorationTypes.h" +#include "../../Include/RmlUi/Core/Element.h" +#include "../../Include/RmlUi/Core/ElementScroll.h" +#include "../../Include/RmlUi/Core/Factory.h" +#include "../../Include/RmlUi/Core/FontEngineInterface.h" +#include "../../Include/RmlUi/Core/Math.h" +#include "../../Include/RmlUi/Core/RenderManager.h" +#include "../../Include/RmlUi/Core/TextShapingContext.h" +#include "DataController.h" +#include "DataModel.h" +#include "DataView.h" +#include "ElementBackgroundBorder.h" +#include "Layout/LayoutDetails.h" +#include "Layout/LayoutEngine.h" +#include "TransformState.h" +#include + +namespace Rml { + +Element* ElementUtilities::GetElementById(Element* root_element, const String& id) +{ + // Breadth first search on elements for the corresponding id + typedef Queue SearchQueue; + SearchQueue search_queue; + search_queue.push(root_element); + + while (!search_queue.empty()) + { + Element* element = search_queue.front(); + search_queue.pop(); + + if (element->GetId() == id) + { + return element; + } + + // Add all children to search + for (int i = 0; i < element->GetNumChildren(); i++) + search_queue.push(element->GetChild(i)); + } + + return nullptr; +} + +void ElementUtilities::GetElementsByTagName(ElementList& elements, Element* root_element, const String& tag, const String& stop_tag) +{ + // Breadth first search on elements for the corresponding id + typedef Queue SearchQueue; + SearchQueue search_queue; + for (int i = 0; i < root_element->GetNumChildren(); ++i) + search_queue.push(root_element->GetChild(i)); + + while (!search_queue.empty()) + { + Element* element = search_queue.front(); + search_queue.pop(); + + if (element->GetTagName() == tag) + elements.push_back(element); + + if (stop_tag.empty() || element->GetTagName() != stop_tag) + { + // Add all children to search. + for (int i = 0; i < element->GetNumChildren(); i++) + search_queue.push(element->GetChild(i)); + } + } +} + +void ElementUtilities::GetElementsByClassName(ElementList& elements, Element* root_element, const String& class_name) +{ + // Breadth first search on elements for the corresponding id + typedef Queue SearchQueue; + SearchQueue search_queue; + for (int i = 0; i < root_element->GetNumChildren(); ++i) + search_queue.push(root_element->GetChild(i)); + + while (!search_queue.empty()) + { + Element* element = search_queue.front(); + search_queue.pop(); + + if (element->IsClassSet(class_name)) + elements.push_back(element); + + // Add all children to search. + for (int i = 0; i < element->GetNumChildren(); i++) + search_queue.push(element->GetChild(i)); + } +} + +float ElementUtilities::GetDensityIndependentPixelRatio(Element* element) +{ + Context* context = element->GetContext(); + if (context == nullptr) + return 1.0f; + + return context->GetDensityIndependentPixelRatio(); +} + +int ElementUtilities::GetStringWidth(Element* element, StringView string, Character prior_character) +{ + const auto& computed = element->GetComputedValues(); + const TextShapingContext text_shaping_context{computed.language(), computed.direction(), computed.font_kerning(), computed.letter_spacing()}; + + FontFaceHandle font_face_handle = element->GetFontFaceHandle(); + if (font_face_handle == 0) + return 0; + + return GetFontEngineInterface()->GetStringWidth(font_face_handle, string, text_shaping_context, prior_character); +} + +bool ElementUtilities::GetClippingRegion(Element* element, Rectanglei& out_clip_region, ClipMaskGeometryList* out_clip_mask_list, + bool force_clip_self) +{ + using Style::Clip; + Clip target_element_clip = element->GetComputedValues().clip(); + if (target_element_clip == Clip::Type::None && !force_clip_self) + return false; + + int num_ignored_clips = target_element_clip.GetNumber(); + + // Search through the element's ancestors, finding all elements that clip their overflow and have overflow to clip. + // For each that we find, we combine their clipping region with the existing clipping region, and so build up a + // complete clipping region for the element. + Element* clipping_element = (force_clip_self ? element : element->GetOffsetParent()); + + Rectanglef clip_region = Rectanglef::MakeInvalid(); + + while (clipping_element) + { + const bool force_clip_current_element = (force_clip_self && clipping_element == element); + const ComputedValues& clip_computed = clipping_element->GetComputedValues(); + const bool clip_enabled = (clip_computed.overflow_x() != Style::Overflow::Visible || clip_computed.overflow_y() != Style::Overflow::Visible); + const bool clip_always = (clip_computed.clip() == Clip::Type::Always); + const bool clip_none = (clip_computed.clip() == Clip::Type::None); + const int clip_number = clip_computed.clip().GetNumber(); + + // Merge the existing clip region with the current clip region, unless we are ignoring clip regions. + if (((clip_always || clip_enabled) && num_ignored_clips == 0) || force_clip_current_element) + { + const BoxArea clip_area = (force_clip_current_element ? BoxArea::Border : clipping_element->GetClipArea()); + const bool has_clipping_content = + (clip_always || force_clip_current_element || clipping_element->GetClientWidth() < clipping_element->GetScrollWidth() - 0.5f || + clipping_element->GetClientHeight() < clipping_element->GetScrollHeight() - 0.5f); + bool disable_scissor_clipping = false; + + if (out_clip_mask_list) + { + const TransformState* transform_state = clipping_element->GetTransformState(); + const Matrix4f* transform = (transform_state ? transform_state->GetTransform() : nullptr); + const bool has_border_radius = (clip_computed.border_top_left_radius() > 0.f || clip_computed.border_top_right_radius() > 0.f || + clip_computed.border_bottom_right_radius() > 0.f || clip_computed.border_bottom_left_radius() > 0.f); + + // If the element has border-radius we always use a clip mask, since we can't easily predict if content is located on the curved + // region to be clipped. If the element has a transform we only use a clip mask when the content clips. + if (has_border_radius || (transform && has_clipping_content)) + { + Geometry* clip_geometry = clipping_element->GetElementBackgroundBorder()->GetClipGeometry(clipping_element, clip_area); + const ClipMaskOperation clip_operation = (out_clip_mask_list->empty() ? ClipMaskOperation::Set : ClipMaskOperation::Intersect); + const Vector2f absolute_offset = clipping_element->GetAbsoluteOffset(BoxArea::Border).Round(); + out_clip_mask_list->push_back(ClipMaskGeometry{clip_operation, clip_geometry, absolute_offset, transform}); + } + + // If we only have border-radius then we add this element to the scissor region as well as the clip mask. This may help with e.g. + // culling text render calls. However, when we have a transform, the element cannot be added to the scissor region since its geometry + // may be projected entirely elsewhere. + if (transform) + disable_scissor_clipping = true; + } + + if (has_clipping_content && !disable_scissor_clipping) + { + // Shrink the scissor region to the element's client area. + Vector2f element_offset = clipping_element->GetAbsoluteOffset(clip_area).Round(); + Vector2f element_size = clipping_element->GetRenderBox(clip_area).GetFillSize(); + Rectanglef element_region = Rectanglef::FromPositionSize(element_offset, element_size); + + clip_region = element_region.IntersectIfValid(clip_region); + } + } + + // If this region is meant to clip and we're skipping regions, update the counter. + if (num_ignored_clips > 0 && clip_enabled) + num_ignored_clips--; + + // Inherit how many clip regions this ancestor ignores. + num_ignored_clips = Math::Max(num_ignored_clips, clip_number); + + // If this region ignores all clipping regions, then we do too. + if (clip_none) + break; + + // Climb the tree to this region's parent. + clipping_element = clipping_element->GetOffsetParent(); + } + + if (clip_region.Valid()) + { + Math::SnapToPixelGrid(clip_region); + out_clip_region = Rectanglei(clip_region); + } + + return clip_region.Valid(); +} + +bool ElementUtilities::SetClippingRegion(Element* element, bool force_clip_self) +{ + Context* context = element->GetContext(); + if (!context) + return false; + + RenderManager& render_manager = context->GetRenderManager(); + + Rectanglei clip_region; + ClipMaskGeometryList clip_mask_list; + + const bool scissoring_enabled = GetClippingRegion(element, clip_region, &clip_mask_list, force_clip_self); + if (scissoring_enabled) + render_manager.SetScissorRegion(clip_region); + else + render_manager.DisableScissorRegion(); + + render_manager.SetClipMask(std::move(clip_mask_list)); + + return true; +} + +bool ElementUtilities::GetBoundingBox(Rectanglef& out_rectangle, Element* element, BoxArea box_area) +{ + RMLUI_ASSERT(element); + + Vector2f shadow_extent_top_left, shadow_extent_bottom_right; + if (box_area == BoxArea::Auto) + { + // 'Auto' acts like border box extended to encompass any ink overflow, including the element's box-shadow. + // Note: Does not currently include ink overflow due to filters, as that is handled manually in ElementEffects. + box_area = BoxArea::Border; + + if (const Property* p_box_shadow = element->GetLocalProperty(PropertyId::BoxShadow)) + { + RMLUI_ASSERT(p_box_shadow->value.GetType() == Variant::BOXSHADOWLIST); + const BoxShadowList& shadow_list = p_box_shadow->value.GetReference(); + + for (const BoxShadow& shadow : shadow_list) + { + if (!shadow.inset) + { + const float extent = 1.5f * element->ResolveLength(shadow.blur_radius) + element->ResolveLength(shadow.spread_distance); + const Vector2f offset = {element->ResolveLength(shadow.offset_x), element->ResolveLength(shadow.offset_y)}; + + shadow_extent_top_left = Math::Max(shadow_extent_top_left, -offset + Vector2f(extent)); + shadow_extent_bottom_right = Math::Max(shadow_extent_bottom_right, offset + Vector2f(extent)); + } + } + } + } + + // Element bounds in non-transformed space. + Rectanglef bounds = Rectanglef::FromPositionSize(element->GetAbsoluteOffset(box_area), element->GetBox().GetSize(box_area)); + bounds = bounds.Extend(shadow_extent_top_left, shadow_extent_bottom_right); + + const TransformState* transform_state = element->GetTransformState(); + const Matrix4f* transform = (transform_state ? transform_state->GetTransform() : nullptr); + + // Early exit in the common case of no transform. + if (!transform) + { + out_rectangle = bounds; + return true; + } + + Context* context = element->GetContext(); + if (!context) + return false; + + constexpr int num_corners = 4; + Vector2f corners[num_corners] = { + bounds.TopLeft(), + bounds.TopRight(), + bounds.BottomRight(), + bounds.BottomLeft(), + }; + + // Transform and project corners to window coordinates. + constexpr float z_clip = 10'000.f; + const Vector2f window_size = Vector2f(context->GetDimensions()); + const Matrix4f project = Matrix4f::ProjectOrtho(0.f, window_size.x, 0.f, window_size.y, -z_clip, z_clip); + const Matrix4f project_transform = project * (*transform); + bool any_vertex_depth_clipped = false; + + for (int i = 0; i < num_corners; i++) + { + const Vector4f pos_clip_space = project_transform * Vector4f(corners[i].x, corners[i].y, 0, 1); + const Vector2f pos_ndc = Vector2f(pos_clip_space.x, pos_clip_space.y) / pos_clip_space.w; + const Vector2f pos_viewport = 0.5f * window_size * (pos_ndc + Vector2f(1)); + corners[i] = pos_viewport; + any_vertex_depth_clipped |= !(-pos_clip_space.w <= pos_clip_space.z && pos_clip_space.z <= pos_clip_space.w); + } + + // If any part of the box area is outside the depth clip planes we give up finding the bounding box. In this situation a renderer would normally + // clip the underlying triangles against the clip planes. We could in principle do the same, but the added complexity does not seem worthwhile for + // our use cases. + if (any_vertex_depth_clipped) + return false; + + // Find the rectangle covering the projected corners. + out_rectangle = Rectanglef::FromPosition(corners[0]); + for (int i = 1; i < num_corners; i++) + out_rectangle = out_rectangle.Join(corners[i]); + + return true; +} + +void ElementUtilities::FormatElement(Element* element, Vector2f containing_block) +{ + LayoutEngine::FormatElement(element, containing_block); +} + +void ElementUtilities::BuildBox(Box& box, Vector2f containing_block, Element* element, bool inline_element) +{ + LayoutDetails::BuildBox(box, containing_block, element, inline_element ? BuildBoxMode::Inline : BuildBoxMode::Block); +} + +bool ElementUtilities::PositionElement(Element* element, Vector2f offset, PositionAnchor anchor) +{ + Element* parent = element->GetParentNode(); + if (!parent) + return false; + + const Box& parent_box = parent->GetBox(); + Vector2f containing_block = parent_box.GetSize(); + containing_block.x -= parent->GetElementScroll()->GetScrollbarSize(ElementScroll::VERTICAL); + containing_block.y -= parent->GetElementScroll()->GetScrollbarSize(ElementScroll::HORIZONTAL); + + Box box; + LayoutDetails::BuildBox(box, containing_block, element); + if (box.GetSize().y < 0.f) + box.SetContent(Vector2f(box.GetSize().x, containing_block.y)); + element->SetBox(box); + + Vector2f element_block = box.GetSize(BoxArea::Margin); + Vector2f resolved_offset = offset; + + if (anchor & RIGHT) + resolved_offset.x = containing_block.x - (element_block.x + offset.x); + if (anchor & BOTTOM) + resolved_offset.y = containing_block.y - (element_block.y + offset.y); + + // Position element relative to its parent. + Vector2f relative_offset = parent_box.GetPosition(BoxArea::Content); + relative_offset += resolved_offset; + relative_offset.x += box.GetEdge(BoxArea::Margin, BoxEdge::Left); + relative_offset.y += box.GetEdge(BoxArea::Margin, BoxEdge::Top); + + element->SetOffset(relative_offset, parent); + + return true; +} + +bool ElementUtilities::ApplyTransform(Element& element) +{ + Context* context = element.GetContext(); + if (!context) + return false; + + RenderManager& render_manager = context->GetRenderManager(); + + const Matrix4f* new_transform = nullptr; + if (const TransformState* state = element.GetTransformState()) + new_transform = state->GetTransform(); + + render_manager.SetTransform(new_transform); + + return true; +} + +bool ElementUtilities::ApplyDataViewsControllers(Element* element) +{ + RMLUI_ASSERT(element); + + // If we have an active data model, check the attributes for any data bindings + DataModel* data_model = element->GetDataModel(); + if (!data_model) + return false; + + struct ViewControllerInitializer { + String type; + String modifier; + String expression; + DataViewPtr view; + DataControllerPtr controller; + explicit operator bool() const { return view || controller; } + }; + + // Since data views and controllers may modify the element's attributes during initialization, we + // need to iterate over all the attributes _before_ initializing any views or controllers. We store + // the information needed to initialize them in the following container. + Vector initializer_list; + + for (auto& attribute : element->GetAttributes()) + { + // Data views and controllers are declared by the following element attribute: + // data-[type]-[modifier]="[expression]" + + constexpr size_t data_str_length = sizeof("data-") - 1; + + const String& name = attribute.first; + + if (name.size() > data_str_length && name[0] == 'd' && name[1] == 'a' && name[2] == 't' && name[3] == 'a' && name[4] == '-') + { + const size_t type_end = name.find('-', data_str_length); + const size_t type_size = (type_end == String::npos ? String::npos : type_end - data_str_length); + const String type_name = name.substr(data_str_length, type_size); + + ViewControllerInitializer initializer; + + const size_t modifier_offset = data_str_length + type_name.size() + 1; + if (modifier_offset < name.size()) + initializer.modifier = name.substr(modifier_offset); + + if (DataViewPtr view = Factory::InstanceDataView(type_name, element)) + initializer.view = std::move(view); + + if (DataControllerPtr controller = Factory::InstanceDataController(type_name, element)) + initializer.controller = std::move(controller); + + if (initializer) + { + initializer.type = type_name; + initializer.expression = attribute.second.Get(); + + if (Factory::IsStructuralDataView(type_name)) + { + // Structural data views should cancel all other non-structural data views and controllers. + // Clear all other views, and only initialize this one. + // E.g. in elements with a 'data-for' attribute, the data views should be constructed on the + // generated children elements and not on the current element generating the 'for' view. + initializer_list.clear(); + initializer_list.push_back(std::move(initializer)); + break; + } + else + { + initializer_list.push_back(std::move(initializer)); + } + } + } + } + + bool result = false; + + // Now, we can safely initialize the data views and controllers, even modifying the element's attributes when desired. + for (ViewControllerInitializer& initializer : initializer_list) + { + DataViewPtr& view = initializer.view; + DataControllerPtr& controller = initializer.controller; + + if (view) + { + if (view->Initialize(*data_model, element, initializer.expression, initializer.modifier)) + { + data_model->AddView(std::move(view)); + result = true; + } + else + Log::Message(Log::LT_WARNING, "Could not add data-%s view to element: %s", initializer.type.c_str(), element->GetAddress().c_str()); + } + + if (controller) + { + if (controller->Initialize(*data_model, element, initializer.expression, initializer.modifier)) + { + data_model->AddController(std::move(controller)); + result = true; + } + else + Log::Message(Log::LT_WARNING, "Could not add data-%s controller to element: %s", initializer.type.c_str(), + element->GetAddress().c_str()); + } + } + + return result; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/CMakeLists.txt b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/CMakeLists.txt new file mode 100644 index 00000000..3a10c3b2 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/CMakeLists.txt @@ -0,0 +1,50 @@ +# RMLUI_CMAKE_MINIMUM_VERSION_RAISE_NOTICE: +# Using absolute paths to prevent improper interpretation of relative paths. Relative paths can be used once the minimum +# CMake version is greater or equal than CMake 3.13 +target_sources(rmlui_core PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/ElementFormControl.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/ElementFormControlInput.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/ElementFormControlSelect.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/ElementFormControlTextArea.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/ElementForm.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/ElementImage.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/ElementImage.h" + "${CMAKE_CURRENT_SOURCE_DIR}/ElementLabel.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/ElementLabel.h" + "${CMAKE_CURRENT_SOURCE_DIR}/ElementProgress.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/ElementTabSet.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/ElementTextSelection.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/ElementTextSelection.h" + "${CMAKE_CURRENT_SOURCE_DIR}/InputTypeButton.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/InputTypeButton.h" + "${CMAKE_CURRENT_SOURCE_DIR}/InputTypeCheckbox.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/InputTypeCheckbox.h" + "${CMAKE_CURRENT_SOURCE_DIR}/InputType.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/InputType.h" + "${CMAKE_CURRENT_SOURCE_DIR}/InputTypeRadio.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/InputTypeRadio.h" + "${CMAKE_CURRENT_SOURCE_DIR}/InputTypeRange.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/InputTypeRange.h" + "${CMAKE_CURRENT_SOURCE_DIR}/InputTypeSubmit.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/InputTypeSubmit.h" + "${CMAKE_CURRENT_SOURCE_DIR}/InputTypeText.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/InputTypeText.h" + "${CMAKE_CURRENT_SOURCE_DIR}/WidgetDropDown.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/WidgetDropDown.h" + "${CMAKE_CURRENT_SOURCE_DIR}/WidgetSlider.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/WidgetSlider.h" + "${CMAKE_CURRENT_SOURCE_DIR}/WidgetTextInput.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/WidgetTextInput.h" + "${CMAKE_CURRENT_SOURCE_DIR}/WidgetTextInputMultiLine.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/WidgetTextInputMultiLine.h" + "${CMAKE_CURRENT_SOURCE_DIR}/WidgetTextInputSingleLine.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/WidgetTextInputSingleLine.h" + "${CMAKE_CURRENT_SOURCE_DIR}/WidgetTextInputSingleLinePassword.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/WidgetTextInputSingleLinePassword.h" + "${CMAKE_CURRENT_SOURCE_DIR}/XMLNodeHandlerSelect.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/XMLNodeHandlerSelect.h" + "${CMAKE_CURRENT_SOURCE_DIR}/XMLNodeHandlerTabSet.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/XMLNodeHandlerTabSet.h" + "${CMAKE_CURRENT_SOURCE_DIR}/XMLNodeHandlerTextArea.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/XMLNodeHandlerTextArea.h" +) diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/ElementForm.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/ElementForm.cpp new file mode 100644 index 00000000..89f0a177 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/ElementForm.cpp @@ -0,0 +1,57 @@ +#include "../../../Include/RmlUi/Core/Elements/ElementForm.h" +#include "../../../Include/RmlUi/Core/Dictionary.h" +#include "../../../Include/RmlUi/Core/ElementUtilities.h" +#include "../../../Include/RmlUi/Core/Elements/ElementFormControl.h" + +namespace Rml { + +ElementForm::ElementForm(const String& tag) : Element(tag) {} + +ElementForm::~ElementForm() {} + +void ElementForm::Submit(const String& name, const String& submit_value) +{ + Dictionary values; + if (name.empty()) + values["submit"] = submit_value; + else + values[name] = submit_value; + + ElementList form_controls; + ElementUtilities::GetElementsByTagName(form_controls, this, "input"); + ElementUtilities::GetElementsByTagName(form_controls, this, "textarea"); + ElementUtilities::GetElementsByTagName(form_controls, this, "select"); + + for (size_t i = 0; i < form_controls.size(); i++) + { + ElementFormControl* control = rmlui_dynamic_cast(form_controls[i]); + if (!control) + continue; + + // Skip disabled controls. + if (control->IsDisabled()) + continue; + + // Only process controls that should be submitted. + if (!control->IsSubmitted()) + continue; + + String control_name = control->GetName(); + String control_value = control->GetValue(); + + // Skip over unnamed form controls. + if (control_name.empty()) + continue; + + // If the item already exists, append to it. + Variant* value = GetIf(values, control_name); + if (value != nullptr) + *value = value->Get() + ", " + control_value; + else + values[control_name] = control_value; + } + + DispatchEvent(EventId::Submit, values); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/ElementFormControl.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/ElementFormControl.cpp new file mode 100644 index 00000000..48d648c9 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/ElementFormControl.cpp @@ -0,0 +1,62 @@ +#include "../../../Include/RmlUi/Core/Elements/ElementFormControl.h" +#include "../../../Include/RmlUi/Core/ComputedValues.h" + +namespace Rml { + +ElementFormControl::ElementFormControl(const String& tag) : Element(tag) +{ + SetProperty(PropertyId::TabIndex, Property(Style::TabIndex::Auto)); +} + +ElementFormControl::~ElementFormControl() {} + +String ElementFormControl::GetName() const +{ + return GetAttribute("name", ""); +} + +void ElementFormControl::SetName(const String& name) +{ + SetAttribute("name", name); +} + +bool ElementFormControl::IsSubmitted() +{ + return true; +} + +bool ElementFormControl::IsDisabled() const +{ + return HasAttribute("disabled"); +} + +void ElementFormControl::SetDisabled(bool disable) +{ + if (disable) + SetAttribute("disabled", ""); + else + RemoveAttribute("disabled"); +} + +void ElementFormControl::OnAttributeChange(const ElementAttributes& changed_attributes) +{ + Element::OnAttributeChange(changed_attributes); + + if (changed_attributes.find("disabled") != changed_attributes.end()) + { + bool is_disabled = IsDisabled(); + SetPseudoClass("disabled", is_disabled); + + // Disable focus when element is disabled. This will also prevent click + // events (when originating from user inputs, see Context) to reach the element. + if (is_disabled) + { + SetProperty(PropertyId::Focus, Property(Style::Focus::None)); + Blur(); + } + else + RemoveProperty(PropertyId::Focus); + } +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/ElementFormControlInput.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/ElementFormControlInput.cpp new file mode 100644 index 00000000..82ed4fa4 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/ElementFormControlInput.cpp @@ -0,0 +1,170 @@ +#include "../../../Include/RmlUi/Core/Elements/ElementFormControlInput.h" +#include "../../../Include/RmlUi/Core/Event.h" +#include "InputTypeButton.h" +#include "InputTypeCheckbox.h" +#include "InputTypeRadio.h" +#include "InputTypeRange.h" +#include "InputTypeSubmit.h" +#include "InputTypeText.h" + +namespace Rml { + +ElementFormControlInput::ElementFormControlInput(const String& tag) : ElementFormControl(tag) +{ + // OnAttributeChange will be called right after this, possible with a non-default type. Thus, + // creating the default InputTypeText here may result in it being destroyed in just a few moments. + // Instead, we create the InputTypeText in OnAttributeChange in the case where the type attribute has not been set. +} + +ElementFormControlInput::~ElementFormControlInput() {} + +String ElementFormControlInput::GetValue() const +{ + RMLUI_ASSERT(type); + return type->GetValue(); +} + +void ElementFormControlInput::SetValue(const String& value) +{ + SetAttribute("value", value); +} + +bool ElementFormControlInput::IsSubmitted() +{ + RMLUI_ASSERT(type); + return type->IsSubmitted(); +} + +void ElementFormControlInput::Select() +{ + RMLUI_ASSERT(type); + type->Select(); +} + +void ElementFormControlInput::SetSelectionRange(int selection_start, int selection_end) +{ + RMLUI_ASSERT(type); + type->SetSelectionRange(selection_start, selection_end); +} + +void ElementFormControlInput::GetSelection(int* selection_start, int* selection_end, String* selected_text) const +{ + RMLUI_ASSERT(type); + type->GetSelection(selection_start, selection_end, selected_text); +} + +void ElementFormControlInput::SetCompositionRange(int range_start, int range_end) +{ + RMLUI_ASSERT(type); + type->SetCompositionRange(range_start, range_end); +} + +void ElementFormControlInput::OnUpdate() +{ + RMLUI_ASSERT(type); + type->OnUpdate(); +} + +void ElementFormControlInput::OnRender() +{ + RMLUI_ASSERT(type); + type->OnRender(); +} + +void ElementFormControlInput::OnResize() +{ + RMLUI_ASSERT(type); + type->OnResize(); +} + +void ElementFormControlInput::OnLayout() +{ + RMLUI_ASSERT(type); + type->OnLayout(); +} + +void ElementFormControlInput::OnAttributeChange(const ElementAttributes& changed_attributes) +{ + ElementFormControl::OnAttributeChange(changed_attributes); + + String new_type_name; + + auto it_type = changed_attributes.find("type"); + if (it_type != changed_attributes.end()) + { + new_type_name = it_type->second.Get("text"); + } + + if (!type || (!new_type_name.empty() && new_type_name != type_name)) + { + // Reset the existing type before constructing a new one. This ensures the old type removes properties and event + // listeners attached to this element, so it does not interfere with new ones being attached by the new type. + type.reset(); + + if (new_type_name == "password") + type = MakeUnique(this, InputTypeText::OBSCURED); + else if (new_type_name == "radio") + type = MakeUnique(this); + else if (new_type_name == "checkbox") + type = MakeUnique(this); + else if (new_type_name == "range") + type = MakeUnique(this); + else if (new_type_name == "submit") + type = MakeUnique(this); + else if (new_type_name == "button") + type = MakeUnique(this); + else + { + new_type_name = "text"; + type = MakeUnique(this); + } + + if (!type_name.empty()) + SetClass(type_name, false); + SetClass(new_type_name, true); + type_name = new_type_name; + + DirtyLayout(); + } + + RMLUI_ASSERT(type); + + if (!type->OnAttributeChange(changed_attributes)) + DirtyLayout(); +} + +void ElementFormControlInput::OnPropertyChange(const PropertyIdSet& changed_properties) +{ + ElementFormControl::OnPropertyChange(changed_properties); + + if (type) + type->OnPropertyChange(changed_properties); +} + +void ElementFormControlInput::OnChildAdd(Element* child) +{ + if (child == this && type) + type->OnChildAdd(); +} + +void ElementFormControlInput::OnChildRemove(Element* child) +{ + if (child == this && type) + type->OnChildRemove(); +} + +void ElementFormControlInput::ProcessDefaultAction(Event& event) +{ + ElementFormControl::ProcessDefaultAction(event); + if (type) + type->ProcessDefaultAction(event); +} + +bool ElementFormControlInput::GetIntrinsicDimensions(Vector2f& dimensions, float& ratio) +{ + if (type) + return type->GetIntrinsicDimensions(dimensions, ratio); + return false; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/ElementFormControlSelect.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/ElementFormControlSelect.cpp new file mode 100644 index 00000000..41b6395b --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/ElementFormControlSelect.cpp @@ -0,0 +1,166 @@ +#include "../../../Include/RmlUi/Core/Elements/ElementFormControlSelect.h" +#include "../../../Include/RmlUi/Core/ElementText.h" +#include "../../../Include/RmlUi/Core/ElementUtilities.h" +#include "../../../Include/RmlUi/Core/Event.h" +#include "WidgetDropDown.h" + +namespace Rml { + +ElementFormControlSelect::ElementFormControlSelect(const String& tag) : ElementFormControl(tag), widget(nullptr) +{ + widget = new WidgetDropDown(this); +} + +ElementFormControlSelect::~ElementFormControlSelect() +{ + delete widget; +} + +String ElementFormControlSelect::GetValue() const +{ + return GetAttribute("value", String()); +} + +void ElementFormControlSelect::SetValue(const String& value) +{ + MoveChildren(); + + SetAttribute("value", value); +} + +void ElementFormControlSelect::SetSelection(int selection) +{ + MoveChildren(); + + widget->SetSelection(widget->GetOption(selection)); +} + +int ElementFormControlSelect::GetSelection() const +{ + return widget->GetSelection(); +} + +Element* ElementFormControlSelect::GetOption(int index) +{ + MoveChildren(); + + return widget->GetOption(index); +} + +int ElementFormControlSelect::GetNumOptions() +{ + MoveChildren(); + + return widget->GetNumOptions(); +} + +int ElementFormControlSelect::Add(const String& rml, const String& value, int before, bool selectable) +{ + MoveChildren(); + + return widget->AddOption(rml, value, before, false, selectable); +} + +int ElementFormControlSelect::Add(ElementPtr element, int before) +{ + MoveChildren(); + + return widget->AddOption(std::move(element), before); +} + +void ElementFormControlSelect::Remove(int index) +{ + MoveChildren(); + + widget->RemoveOption(index); +} + +void ElementFormControlSelect::RemoveAll() +{ + MoveChildren(); + + widget->ClearOptions(); +} + +void ElementFormControlSelect::ShowSelectBox() +{ + widget->ShowSelectBox(); +} + +void ElementFormControlSelect::HideSelectBox() +{ + widget->HideSelectBox(); +} +void ElementFormControlSelect::CancelSelectBox() +{ + widget->CancelSelectBox(); +} + +bool ElementFormControlSelect::IsSelectBoxVisible() +{ + return widget->IsSelectBoxVisible(); +} + +void ElementFormControlSelect::OnUpdate() +{ + ElementFormControl::OnUpdate(); + + MoveChildren(); + + widget->OnUpdate(); +} + +void ElementFormControlSelect::OnRender() +{ + ElementFormControl::OnRender(); + + widget->OnRender(); +} + +void ElementFormControlSelect::OnLayout() +{ + widget->OnLayout(); +} + +void ElementFormControlSelect::OnChildAdd(Element* child) +{ + if (widget) + widget->OnChildAdd(child); +} + +void ElementFormControlSelect::OnChildRemove(Element* child) +{ + if (widget) + widget->OnChildRemove(child); +} + +void ElementFormControlSelect::MoveChildren() +{ + // Move any child elements into the widget (except for the three functional elements). + while (Element* raw_child = GetFirstChild()) + { + ElementPtr child = RemoveChild(raw_child); + widget->AddOption(std::move(child), -1); + } +} + +bool ElementFormControlSelect::GetIntrinsicDimensions(Vector2f& intrinsic_dimensions, float& /*ratio*/) +{ + intrinsic_dimensions.x = 128 * ElementUtilities::GetDensityIndependentPixelRatio(this); + intrinsic_dimensions.y = GetLineHeight(); + + return true; +} + +void ElementFormControlSelect::OnAttributeChange(const ElementAttributes& changed_attributes) +{ + RMLUI_ASSERT(widget); + + ElementFormControl::OnAttributeChange(changed_attributes); + + auto it = changed_attributes.find("value"); + if (it != changed_attributes.end()) + widget->OnValueChange(it->second.Get()); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/ElementFormControlTextArea.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/ElementFormControlTextArea.cpp new file mode 100644 index 00000000..6c24de41 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/ElementFormControlTextArea.cpp @@ -0,0 +1,177 @@ +#include "../../../Include/RmlUi/Core/Elements/ElementFormControlTextArea.h" +#include "../../../Include/RmlUi/Core/ElementText.h" +#include "../../../Include/RmlUi/Core/ElementUtilities.h" +#include "../../../Include/RmlUi/Core/Math.h" +#include "../../../Include/RmlUi/Core/PropertyIdSet.h" +#include "../../../Include/RmlUi/Core/StyleSheetSpecification.h" +#include "WidgetTextInputMultiLine.h" + +namespace Rml { + +ElementFormControlTextArea::ElementFormControlTextArea(const String& tag) : ElementFormControl(tag) +{ + widget = MakeUnique(this); + SetWordWrapProperties(); +} + +ElementFormControlTextArea::~ElementFormControlTextArea() {} + +String ElementFormControlTextArea::GetValue() const +{ + return GetAttribute("value", ""); +} + +void ElementFormControlTextArea::SetValue(const String& value) +{ + SetAttribute("value", value); +} + +void ElementFormControlTextArea::SetNumColumns(int num_columns) +{ + SetAttribute("cols", Math::Max(1, num_columns)); +} + +int ElementFormControlTextArea::GetNumColumns() const +{ + return GetAttribute("cols", 20); +} + +void ElementFormControlTextArea::SetNumRows(int num_rows) +{ + SetAttribute("rows", Math::Max(1, num_rows)); +} + +int ElementFormControlTextArea::GetNumRows() const +{ + return GetAttribute("rows", 2); +} + +void ElementFormControlTextArea::SetMaxLength(int max_length) +{ + SetAttribute("maxlength", max_length); +} + +int ElementFormControlTextArea::GetMaxLength() const +{ + return GetAttribute("maxlength", -1); +} + +void ElementFormControlTextArea::SetWordWrap(bool word_wrap) +{ + if (word_wrap != GetWordWrap()) + { + if (word_wrap) + RemoveAttribute("wrap"); + else + SetAttribute("wrap", "nowrap"); + } +} + +bool ElementFormControlTextArea::GetWordWrap() +{ + String attribute = GetAttribute("wrap", ""); + return attribute != "nowrap"; +} + +void ElementFormControlTextArea::Select() +{ + widget->Select(); +} + +void ElementFormControlTextArea::SetSelectionRange(int selection_start, int selection_end) +{ + widget->SetSelectionRange(selection_start, selection_end); +} + +void ElementFormControlTextArea::GetSelection(int* selection_start, int* selection_end, String* selected_text) const +{ + widget->GetSelection(selection_start, selection_end, selected_text); +} + +void ElementFormControlTextArea::SetCompositionRange(int range_start, int range_end) +{ + widget->SetCompositionRange(range_start, range_end); +} + +bool ElementFormControlTextArea::GetIntrinsicDimensions(Vector2f& dimensions, float& /*ratio*/) +{ + dimensions.x = (float)(GetNumColumns() * ElementUtilities::GetStringWidth(this, "m")); + dimensions.y = GetNumRows() * widget->GetLineHeight(); + return true; +} + +void ElementFormControlTextArea::OnUpdate() +{ + widget->OnUpdate(); +} + +void ElementFormControlTextArea::OnRender() +{ + widget->OnRender(); +} + +void ElementFormControlTextArea::OnResize() +{ + widget->OnResize(); +} + +void ElementFormControlTextArea::OnLayout() +{ + widget->OnLayout(); +} + +void ElementFormControlTextArea::OnAttributeChange(const ElementAttributes& changed_attributes) +{ + ElementFormControl::OnAttributeChange(changed_attributes); + + if (changed_attributes.find("wrap") != changed_attributes.end()) + SetWordWrapProperties(); + + if (changed_attributes.find("rows") != changed_attributes.end() || changed_attributes.find("cols") != changed_attributes.end()) + DirtyLayout(); + + auto it = changed_attributes.find("maxlength"); + if (it != changed_attributes.end()) + widget->SetMaxLength(it->second.Get(-1)); + + it = changed_attributes.find("value"); + if (it != changed_attributes.end()) + widget->OnValueAttributeChanged(it->second.Get()); + + it = changed_attributes.find("placeholder"); + if (it != changed_attributes.end()) + widget->OnPlaceholderAttributeChanged(it->second.Get()); +} + +void ElementFormControlTextArea::OnPropertyChange(const PropertyIdSet& changed_properties) +{ + ElementFormControl::OnPropertyChange(changed_properties); + + // Some inherited properties require text formatting update, mainly font and line-height properties. + const PropertyIdSet changed_inherited_layout_properties = changed_properties & + (StyleSheetSpecification::GetRegisteredInheritedProperties() & StyleSheetSpecification::GetRegisteredPropertiesForcingLayout()); + + if (!changed_inherited_layout_properties.Empty()) + widget->ForceFormattingOnNextLayout(); + + if (changed_properties.Contains(PropertyId::Color) || changed_properties.Contains(PropertyId::BackgroundColor)) + widget->UpdateSelectionColours(); + + if (changed_properties.Contains(PropertyId::CaretColor)) + widget->GenerateCursor(); +} + +void ElementFormControlTextArea::GetInnerRML(String& content) const +{ + content = GetValue(); +} + +void ElementFormControlTextArea::SetWordWrapProperties() +{ + const bool word_wrap = GetWordWrap(); + SetProperty(PropertyId::OverflowX, Property(word_wrap ? Style::Overflow::Hidden : Style::Overflow::Auto)); + SetProperty(PropertyId::OverflowY, Property(Style::Overflow::Auto)); + SetProperty(PropertyId::WhiteSpace, Property(word_wrap ? Style::WhiteSpace::Prewrap : Style::WhiteSpace::Pre)); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/ElementImage.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/ElementImage.cpp new file mode 100644 index 00000000..42fd9010 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/ElementImage.cpp @@ -0,0 +1,297 @@ +#include "ElementImage.h" +#include "../../../Include/RmlUi/Core/ComputedValues.h" +#include "../../../Include/RmlUi/Core/ElementDocument.h" +#include "../../../Include/RmlUi/Core/ElementUtilities.h" +#include "../../../Include/RmlUi/Core/MeshUtilities.h" +#include "../../../Include/RmlUi/Core/PropertyIdSet.h" +#include "../../../Include/RmlUi/Core/StyleSheet.h" +#include "../../../Include/RmlUi/Core/Texture.h" +#include "../../../Include/RmlUi/Core/URL.h" +#include "../TextureDatabase.h" + +namespace Rml { + +ElementImage::ElementImage(const String& tag) : Element(tag), dimensions(-1, -1), rect_source(RectSource::None) +{ + dimensions_scale = 1.0f; + geometry_dirty = false; + texture_dirty = true; +} + +ElementImage::~ElementImage() {} + +bool ElementImage::GetIntrinsicDimensions(Vector2f& _dimensions, float& _ratio) +{ + EnsureSourceLoaded(); + + if (rect_source == RectSource::None) + { + dimensions = Vector2f(texture.GetDimensions()); + } + else + { + dimensions = rect.Size(); + } + + if (dimensions.y > 0) + { + _ratio = dimensions.x / dimensions.y; + + // Scale intrinsic size based on attributes. CSS properties can in turn override these attributes, see LayoutDetails. + auto requested_width = GetAttribute("width", -1); + auto requested_height = GetAttribute("height", -1); + if (requested_height > 0 && requested_width > 0) + { + // If both a height and width are set update the ratio to match + _ratio = requested_width / requested_height; + dimensions.x = requested_width; + dimensions.y = requested_height; + } + else if (requested_height > 0) + { + dimensions.x = requested_height * _ratio; + dimensions.y = requested_height; + } + else if (requested_width > 0) + { + dimensions.x = requested_width; + dimensions.y = requested_width / _ratio; + } + } + + dimensions *= dimensions_scale; + + // Return the calculated dimensions. If this changes the size of the element, it will result in + // a call to 'onresize' below which will regenerate the geometry. + _dimensions = dimensions; + + return true; +} +void ElementImage::EnsureSourceLoaded() +{ + if (texture_dirty) + LoadTexture(); +} + +void ElementImage::OnRender() +{ + // Regenerate the geometry if required (this will be set if 'rect' changes but does not result in a resize). + if (geometry_dirty) + GenerateGeometry(); + + geometry.Render(GetAbsoluteOffset(BoxArea::Border), texture); +} + +void ElementImage::OnAttributeChange(const ElementAttributes& changed_attributes) +{ + // Call through to the base element's OnAttributeChange(). + Element::OnAttributeChange(changed_attributes); + + bool dirty_layout = false; + + // Check for a changed 'src' attribute. If this changes, the old texture handle is released, + // forcing a reload when the layout is regenerated. + if (changed_attributes.find("src") != changed_attributes.end() || changed_attributes.find("sprite") != changed_attributes.end()) + { + texture_dirty = true; + dirty_layout = true; + } + + // Check for a changed 'width' attribute. If this changes, a layout is forced which will + // recalculate the dimensions. + if (changed_attributes.find("width") != changed_attributes.end() || changed_attributes.find("height") != changed_attributes.end()) + { + dirty_layout = true; + } + + // Check for a change to the 'rect' attribute. If this changes, the coordinates are + // recomputed and a layout forced. If a sprite is set to source, then that will override any attribute. + if (changed_attributes.find("rect") != changed_attributes.end()) + { + UpdateRect(); + + // Rectangle has changed; this will most likely result in a size change, so we need to force a layout. + dirty_layout = true; + } + + if (dirty_layout) + DirtyLayout(); +} + +void ElementImage::OnPropertyChange(const PropertyIdSet& changed_properties) +{ + Element::OnPropertyChange(changed_properties); + + if (changed_properties.Contains(PropertyId::ImageColor) || changed_properties.Contains(PropertyId::Opacity)) + geometry_dirty = true; +} + +void ElementImage::OnChildAdd(Element* child) +{ + // Load the texture once we have attached to the document so that it can immediately be found during the call to `Rml::GetTextureSourceList`. The + // texture won't actually be loaded from the backend before it is shown. However, only do this if we have an active context so that the dp-ratio + // can be retrieved. If there is no context now the texture loading will be deferred until the next layout update. + if (child == this && texture_dirty && GetContext()) + LoadTexture(); +} + +void ElementImage::OnResize() +{ + geometry_dirty = true; +} + +void ElementImage::OnDpRatioChange() +{ + texture_dirty = true; + DirtyLayout(); +} + +void ElementImage::OnStyleSheetChange() +{ + if (HasAttribute("sprite")) + { + texture_dirty = true; + DirtyLayout(); + } +} + +void ElementImage::GenerateGeometry() +{ + // Release the old geometry before specifying the new vertices. + Mesh mesh = geometry.Release(Geometry::ReleaseMode::ClearMesh); + + // Generate the texture coordinates. + Vector2f texcoords[2]; + if (rect_source != RectSource::None) + { + Vector2f texture_dimensions = Vector2f(Math::Max(texture.GetDimensions(), Vector2i(1))); + texcoords[0] = rect.TopLeft() / texture_dimensions; + texcoords[1] = rect.BottomRight() / texture_dimensions; + } + else + { + texcoords[0] = Vector2f(0, 0); + texcoords[1] = Vector2f(1, 1); + } + + const ComputedValues& computed = GetComputedValues(); + const ColourbPremultiplied quad_colour = computed.image_color().ToPremultiplied(computed.opacity()); + const RenderBox render_box = GetRenderBox(BoxArea::Content); + + MeshUtilities::GenerateQuad(mesh, render_box.GetFillOffset(), render_box.GetFillSize(), quad_colour, texcoords[0], texcoords[1]); + + if (RenderManager* render_manager = GetRenderManager()) + geometry = render_manager->MakeGeometry(std::move(mesh)); + + geometry_dirty = false; +} + +bool ElementImage::LoadTexture() +{ + texture_dirty = false; + geometry_dirty = true; + dimensions_scale = 1.0f; + + RenderManager* render_manager = GetRenderManager(); + if (!render_manager) + { + texture = {}; + return false; + } + + const float dp_ratio = ElementUtilities::GetDensityIndependentPixelRatio(this); + + // Check for a sprite first, this takes precedence. + const String sprite_name = GetAttribute("sprite", ""); + if (!sprite_name.empty()) + { + // Load sprite. + bool valid_sprite = false; + + if (ElementDocument* document = GetOwnerDocument()) + { + if (const StyleSheet* style_sheet = document->GetStyleSheet()) + { + if (const Sprite* sprite = style_sheet->GetSprite(sprite_name)) + { + rect = sprite->rectangle; + rect_source = RectSource::Sprite; + texture = sprite->sprite_sheet->texture_source.GetTexture(*render_manager); + dimensions_scale = sprite->sprite_sheet->display_scale * dp_ratio; + valid_sprite = true; + } + } + } + + if (!valid_sprite) + { + texture = {}; + rect_source = RectSource::None; + UpdateRect(); + Log::Message(Log::LT_WARNING, "Could not find sprite '%s' specified in img element %s", sprite_name.c_str(), GetAddress().c_str()); + return false; + } + } + else + { + // Load image from source URL. + const String source_name = GetAttribute("src", ""); + if (source_name.empty()) + { + texture = {}; + rect_source = RectSource::None; + return false; + } + + URL source_url; + + if (ElementDocument* document = GetOwnerDocument()) + source_url.SetURL(document->GetSourceURL()); + + texture = render_manager->LoadTexture(source_name, source_url.GetPath()); + + dimensions_scale = dp_ratio; + } + + return true; +} + +void ElementImage::UpdateRect() +{ + if (rect_source != RectSource::Sprite) + { + bool valid_rect = false; + + String rect_string = GetAttribute("rect", ""); + if (!rect_string.empty()) + { + StringList coords_list; + StringUtilities::ExpandString(coords_list, rect_string, ' '); + + if (coords_list.size() != 4) + { + Log::Message(Log::LT_WARNING, "Element '%s' has an invalid 'rect' attribute; rect requires 4 space-separated values, found %zu.", + GetAddress().c_str(), coords_list.size()); + } + else + { + const Vector2f position = {FromString(coords_list[0], 0.f), FromString(coords_list[1], 0.f)}; + const Vector2f size = {FromString(coords_list[2], 0.f), FromString(coords_list[3], 0.f)}; + rect = Rectanglef::FromPositionSize(position, size); + + // We have new, valid coordinates; force the geometry to be regenerated. + valid_rect = true; + geometry_dirty = true; + rect_source = RectSource::Attribute; + } + } + + if (!valid_rect) + { + rect = {}; + rect_source = RectSource::None; + } + } +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/ElementImage.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/ElementImage.h new file mode 100644 index 00000000..d8e7c468 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/ElementImage.h @@ -0,0 +1,101 @@ +#pragma once + +#include "../../../Include/RmlUi/Core/Element.h" +#include "../../../Include/RmlUi/Core/Geometry.h" +#include "../../../Include/RmlUi/Core/Header.h" +#include "../../../Include/RmlUi/Core/Spritesheet.h" +#include "../../../Include/RmlUi/Core/Texture.h" + +namespace Rml { + +/** + The 'img' element can render images and sprites. + + The 'src' attribute is used to specify an image url. If instead the `sprite` attribute is set, + it will load a sprite and ignore the `src` and `rect` attributes. + + The 'rect' attribute takes four space-separated integer values, specifying a rectangle + using 'x y width height' in pixel coordinates inside the image. No clamping to the + dimensions of the source image will occur; rendered results in this case will + depend on the user's texture addressing mode. + + The intrinsic dimensions of the image can now come from three different sources. They are + used in the following order: + + 1) 'width' / 'height' attributes if present + 2) pixel width / height of the sprite + 3) pixel width / height given by the 'rect' attribute + 4) width / height of the image texture + + This has the result of sizing the element to the pixel-size of the rendered image, unless + overridden by the 'width' or 'height' attributes. + */ + +class RMLUICORE_API ElementImage : public Element { +public: + RMLUI_RTTI_DefineWithParent(ElementImage, Element) + + /// Constructs a new ElementImage. This should not be called directly; use the Factory instead. + /// @param[in] tag The tag the element was declared as in RML. + ElementImage(const String& tag); + virtual ~ElementImage(); + + /// Returns the element's inherent size. + bool GetIntrinsicDimensions(Vector2f& dimensions, float& ratio) override; + + /// Loads the current source file if needed. This normally happens automatically during layouting. + void EnsureSourceLoaded(); + +protected: + /// Renders the image. + void OnRender() override; + + /// Regenerates the element's geometry. + void OnResize() override; + + /// Our intrinsic dimensions may change with the dp-ratio. + void OnDpRatioChange() override; + + /// The sprite may have changed when the style sheet is recompiled. + void OnStyleSheetChange() override; + + /// Checks for changes to the image's source or dimensions. + /// @param[in] changed_attributes A list of attributes changed on the element. + void OnAttributeChange(const ElementAttributes& changed_attributes) override; + + /// Called when properties on the element are changed. + /// @param[in] changed_properties The properties changed on the element. + void OnPropertyChange(const PropertyIdSet& changed_properties) override; + + /// Detect when we have been added to the document. + void OnChildAdd(Element* child) override; + +private: + // Generates the element's geometry. + void GenerateGeometry(); + // Loads the element's texture, as specified by the 'src' attribute. + bool LoadTexture(); + // Loads the rect value from the element's attribute, but only if we're not a sprite. + void UpdateRect(); + + // The texture this element is rendering from. + Texture texture; + // True if we need to refetch the texture's source from the element's attributes. + bool texture_dirty; + // A factor which scales the intrinsic dimensions based on the dp-ratio and image scale. + float dimensions_scale; + // The element's computed intrinsic dimensions. If either of these values are set to -1, then + // that dimension has not been computed yet. + Vector2f dimensions; + + // The rectangle extracted from the sprite or 'rect' attribute. The rect_source will be None if + // these have not been specified or are invalid. + Rectanglef rect; + enum class RectSource { None, Attribute, Sprite } rect_source; + + // The geometry used to render this element. + Geometry geometry; + bool geometry_dirty; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/ElementLabel.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/ElementLabel.cpp new file mode 100644 index 00000000..17ea62e4 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/ElementLabel.cpp @@ -0,0 +1,96 @@ +#include "ElementLabel.h" + +namespace Rml { + +ElementLabel::ElementLabel(const String& tag) : Element(tag) +{ + AddEventListener(EventId::Click, this, true); +} + +ElementLabel::~ElementLabel() +{ + RemoveEventListener(EventId::Click, this, true); +} + +void ElementLabel::OnPseudoClassChange(const String& pseudo_class, bool activate) +{ + if (pseudo_class == "active" || pseudo_class == "hover") + { + if (Element* target = GetTarget()) + { + OverridePseudoClass(target, pseudo_class, activate); + } + } +} + +void ElementLabel::ProcessEvent(Event& event) +{ + // Forward clicks to the target. + if (event == EventId::Click && !disable_click) + { + if (event.GetPhase() == EventPhase::Capture || event.GetPhase() == EventPhase::Target) + { + if (Element* label_target = GetTarget()) + { + Element* event_target = event.GetTargetElement(); + // If the event is already on the way to the label target, there is no reason to intervene with the + // click. Just let the event move down the chain without interrupting it. Otherwise, intervene to + // manually redirect the click to the label target. + if (!label_target->Contains(event_target)) + { + // Temporarily disable click captures to avoid infinite recursion in case this element is on the path to the target element. + disable_click = true; + event.StopPropagation(); + label_target->Focus(); + label_target->Click(); + disable_click = false; + } + } + } + } +} + +// Get the first descending element whose tag name matches one of tags. +static Element* TagMatchRecursive(const StringList& tags, Element* element) +{ + const int num_children = element->GetNumChildren(); + + for (int i = 0; i < num_children; i++) + { + Element* child = element->GetChild(i); + + for (const String& tag : tags) + { + if (child->GetTagName() == tag) + return child; + } + + Element* matching_element = TagMatchRecursive(tags, child); + if (matching_element) + return matching_element; + } + + return nullptr; +} + +Element* ElementLabel::GetTarget() +{ + const String target_id = GetAttribute("for", ""); + + if (target_id.empty()) + { + const StringList matching_tags = {"button", "input", "textarea", "progress", "progressbar", "select"}; + + return TagMatchRecursive(matching_tags, this); + } + else + { + Element* target = GetElementById(target_id); + if (target != this) + return target; + } + + return nullptr; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/ElementLabel.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/ElementLabel.h new file mode 100644 index 00000000..ba7c5251 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/ElementLabel.h @@ -0,0 +1,32 @@ +#pragma once + +#include "../../../Include/RmlUi/Core/Element.h" +#include "../../../Include/RmlUi/Core/EventListener.h" +#include "../../../Include/RmlUi/Core/Header.h" + +namespace Rml { + +/** + A specialisation of the generic Core::Element representing a label element. + + */ + +class ElementLabel : public Element, public EventListener { +public: + RMLUI_RTTI_DefineWithParent(ElementLabel, Element) + + ElementLabel(const String& tag); + virtual ~ElementLabel(); + +protected: + void OnPseudoClassChange(const String& pseudo_class, bool activate) override; + + void ProcessEvent(Event& event) override; + +private: + Element* GetTarget(); + + bool disable_click = false; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/ElementProgress.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/ElementProgress.cpp new file mode 100644 index 00000000..0aa9ad26 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/ElementProgress.cpp @@ -0,0 +1,363 @@ +#include "../../../Include/RmlUi/Core/Elements/ElementProgress.h" +#include "../../../Include/RmlUi/Core/ComputedValues.h" +#include "../../../Include/RmlUi/Core/ElementDocument.h" +#include "../../../Include/RmlUi/Core/ElementUtilities.h" +#include "../../../Include/RmlUi/Core/Factory.h" +#include "../../../Include/RmlUi/Core/Math.h" +#include "../../../Include/RmlUi/Core/MeshUtilities.h" +#include "../../../Include/RmlUi/Core/PropertyIdSet.h" +#include "../../../Include/RmlUi/Core/StyleSheet.h" +#include "../../../Include/RmlUi/Core/URL.h" +#include + +namespace Rml { + +ElementProgress::ElementProgress(const String& tag) : + Element(tag), direction(DefaultDirection), start_edge(DefaultStartEdge), fill(nullptr), rect_set(false) +{ + if (tag == "progressbar") + Log::Message(Log::LT_WARNING, "Deprecation notice: Element '' renamed to '', please adjust RML tags accordingly."); + + geometry_dirty = false; + + // Add the fill element as a non-DOM element. + ElementPtr fill_element = Factory::InstanceElement(this, "*", "fill", XMLAttributes()); + RMLUI_ASSERT(fill_element); + fill = AppendChild(std::move(fill_element), false); +} + +ElementProgress::~ElementProgress() {} + +float ElementProgress::GetValue() const +{ + const float max_value = GetMax(); + return Math::Clamp(GetAttribute("value", 0.0f), 0.0f, max_value); +} + +float ElementProgress::GetMax() const +{ + const float max_value = GetAttribute("max", 1.0f); + return max_value <= 0.0f ? 1.0f : max_value; +} + +void ElementProgress::SetMax(float max_value) +{ + SetAttribute("max", max_value); +} + +void ElementProgress::SetValue(float in_value) +{ + SetAttribute("value", in_value); +} + +bool ElementProgress::GetIntrinsicDimensions(Vector2f& dimensions, float& /*ratio*/) +{ + dimensions.x = 256 * ElementUtilities::GetDensityIndependentPixelRatio(this); + dimensions.y = GetLineHeight(); + + return true; +} + +void ElementProgress::OnRender() +{ + // Some properties may change geometry without dirtying the layout, eg. opacity. + if (geometry_dirty) + GenerateGeometry(); + + // Render the geometry at the fill element's content region. + geometry.Render(fill->GetAbsoluteOffset(), texture); +} + +void ElementProgress::OnAttributeChange(const ElementAttributes& changed_attributes) +{ + Element::OnAttributeChange(changed_attributes); + + if (changed_attributes.find("value") != changed_attributes.end() || changed_attributes.find("max") != changed_attributes.end()) + { + geometry_dirty = true; + } + + if (changed_attributes.find("direction") != changed_attributes.end()) + { + using DirectionNameList = Array; + static const DirectionNameList names = {"top", "right", "bottom", "left", "clockwise", "counter-clockwise"}; + + direction = DefaultDirection; + + String name = StringUtilities::ToLower(GetAttribute("direction", "")); + auto it = std::find(names.begin(), names.end(), name); + + size_t index = size_t(it - names.begin()); + if (index < size_t(Direction::Count)) + direction = Direction(index); + + geometry_dirty = true; + } + + if (changed_attributes.find("start-edge") != changed_attributes.end()) + { + using StartEdgeNameList = Array; + static const StartEdgeNameList names = {"top", "right", "bottom", "left"}; + + start_edge = DefaultStartEdge; + + String name = StringUtilities::ToLower(GetAttribute("start-edge", "")); + auto it = std::find(names.begin(), names.end(), name); + + size_t index = size_t(it - names.begin()); + if (index < size_t(StartEdge::Count)) + start_edge = StartEdge(index); + + geometry_dirty = true; + } +} + +void ElementProgress::OnPropertyChange(const PropertyIdSet& changed_properties) +{ + Element::OnPropertyChange(changed_properties); + + if (changed_properties.Contains(PropertyId::ImageColor) || changed_properties.Contains(PropertyId::Opacity)) + { + geometry_dirty = true; + } + + if (changed_properties.Contains(PropertyId::FillImage)) + { + LoadTexture(); + } +} + +void ElementProgress::OnResize() +{ + const Vector2f element_size = GetBox().GetSize(); + + // Build and set the 'fill' element's box. Here we are mainly interested in all the edge sizes set by the user. + // The content size of the box is here scaled to fit inside the progress bar. Then, during 'CreateGeometry()', + // the 'fill' element's content size is further shrunk according to 'value' along the proper direction. + Box fill_box; + + ElementUtilities::BuildBox(fill_box, element_size, fill); + + const Vector2f margin_top_left(fill_box.GetEdge(BoxArea::Margin, BoxEdge::Left), fill_box.GetEdge(BoxArea::Margin, BoxEdge::Top)); + const Vector2f edge_size = fill_box.GetSize(BoxArea::Margin) - fill_box.GetSize(BoxArea::Content); + + fill_offset = GetBox().GetPosition() + margin_top_left; + fill_size = element_size - edge_size; + + fill_box.SetContent(fill_size); + fill->SetBox(fill_box); + + geometry_dirty = true; +} + +void ElementProgress::GenerateGeometry() +{ + geometry_dirty = false; + + // Warn the user when using the old approach of adding the 'fill-image' property to the 'fill' element. + if (fill->GetLocalProperty(PropertyId::FillImage)) + Log::Message(Log::LT_WARNING, + "Breaking change: The 'fill-image' property now needs to be set on the element, instead of its inner element. Please " + "update your RCSS source to fix progress bars in this document."); + + const float normalized_value = GetValue() / GetMax(); + Vector2f render_size = fill_size; + + { + // Size and offset the fill element depending on the progress value. + Vector2f offset = fill_offset; + + switch (direction) + { + case Direction::Top: + render_size.y = fill_size.y * normalized_value; + offset.y = fill_offset.y + fill_size.y - render_size.y; + break; + case Direction::Right: render_size.x = fill_size.x * normalized_value; break; + case Direction::Bottom: render_size.y = fill_size.y * normalized_value; break; + case Direction::Left: + render_size.x = fill_size.x * normalized_value; + offset.x = fill_offset.x + fill_size.x - render_size.x; + break; + case Direction::Clockwise: + case Direction::CounterClockwise: + // Circular progress bars cannot use a box to shape the fill element, instead we need to manually create the geometry from the image + // texture. Thus, we leave the size and offset untouched as a canvas for the manual geometry. + break; + case Direction::Count: break; + } + + Box fill_box = fill->GetBox(); + fill_box.SetContent(render_size); + fill->SetBox(fill_box); + fill->SetOffset(offset, this); + } + + Mesh mesh = geometry.Release(Geometry::ReleaseMode::ClearMesh); + + // If we don't have a fill texture, then there is no need to generate manual geometry, and we are done here. + // Instead, users can style the fill element eg. by decorators. + if (!texture) + return; + + // Otherwise, the 'fill-image' property is set, let's generate its geometry. + Vector& vertices = mesh.vertices; + Vector& indices = mesh.indices; + + Vector2f texcoords[2]; + if (rect_set) + { + Vector2f texture_dimensions = Vector2f(Math::Max(texture.GetDimensions(), Vector2i(1))); + texcoords[0] = rect.TopLeft() / texture_dimensions; + texcoords[1] = rect.BottomRight() / texture_dimensions; + } + else + { + texcoords[0] = Vector2f(0, 0); + texcoords[1] = Vector2f(1, 1); + } + + const ComputedValues& computed = GetComputedValues(); + const ColourbPremultiplied quad_colour = computed.image_color().ToPremultiplied(computed.opacity()); + + switch (direction) + { + // For the top, right, bottom, left directions the fill element already describes where we should draw the fill, + // we only need to generate the final texture coordinates here. + case Direction::Top: texcoords[0].y = texcoords[0].y + (1.0f - normalized_value) * (texcoords[1].y - texcoords[0].y); break; + case Direction::Right: texcoords[1].x = texcoords[0].x + normalized_value * (texcoords[1].x - texcoords[0].x); break; + case Direction::Bottom: texcoords[1].y = texcoords[0].y + normalized_value * (texcoords[1].y - texcoords[0].y); break; + case Direction::Left: texcoords[0].x = texcoords[0].x + (1.0f - normalized_value) * (texcoords[1].x - texcoords[0].x); break; + + case Direction::Clockwise: + case Direction::CounterClockwise: + { + // The circular directions require custom geometry as a box is insufficient. + // We divide the "circle" into eight parts, here called octants, such that each part can be represented by a triangle. + // 'num_octants' tells us how many of these are completely or partially filled. + const int num_octants = Math::Clamp(Math::RoundUpToInteger(8.f * normalized_value), 0, 8); + const int num_vertices = 2 + num_octants; + const int num_triangles = num_octants; + const bool cw = (direction == Direction::Clockwise); + + if (num_octants == 0) + break; + + vertices.resize(num_vertices); + indices.resize(3 * num_triangles); + + RMLUI_ASSERT(int(start_edge) >= int(StartEdge::Top) && int(start_edge) <= int(StartEdge::Left)); + + // The octant our "circle" expands from. + const int start_octant = 2 * int(start_edge); + + // Positions along the unit square (clockwise, index 0 on top) + const float x[8] = {0, 1, 1, 1, 0, -1, -1, -1}; + const float y[8] = {-1, -1, 0, 1, 1, 1, 0, -1}; + + // Set the position of the octant vertices to be rendered. + for (int i = 0; i <= num_octants; i++) + { + int j = (cw ? i : 8 - i); + j = ((j + start_octant) % 8); + vertices[i].position = Vector2f(x[j], y[j]); + } + + // Find the position of the vertex representing the partially filled triangle. + if (normalized_value < 1.f) + { + using namespace Math; + const float angle_offset = float(start_octant) / 8.f * 2.f * RMLUI_PI; + const float angle = angle_offset + (cw ? 1.f : -1.f) * normalized_value * 2.f * RMLUI_PI; + Vector2f pos(Sin(angle), -Cos(angle)); + // Project it from the circle towards the surrounding unit square. + pos = pos / Max(Absolute(pos.x), Absolute(pos.y)); + vertices[num_octants].position = pos; + } + + const int i_center = num_vertices - 1; + vertices[i_center].position = Vector2f(0, 0); + + for (int i = 0; i < num_triangles; i++) + { + indices[i * 3 + 0] = i_center; + indices[i * 3 + 2] = i; + indices[i * 3 + 1] = i + 1; + } + + for (int i = 0; i < num_vertices; i++) + { + // Transform position from [-1, 1] to [0, 1] and then to [0, size] + const Vector2f pos = (Vector2f(1, 1) + vertices[i].position) * 0.5f; + vertices[i].position = pos * render_size; + vertices[i].tex_coord = texcoords[0] + pos * (texcoords[1] - texcoords[0]); + vertices[i].colour = quad_colour; + } + } + break; + + case Direction::Count: break; + } + + const bool is_circular = (direction == Direction::Clockwise || direction == Direction::CounterClockwise); + + if (!is_circular) + { + MeshUtilities::GenerateQuad(mesh, Vector2f(0), render_size, quad_colour, texcoords[0], texcoords[1]); + } + + geometry = GetRenderManager()->MakeGeometry(std::move(mesh)); +} + +bool ElementProgress::LoadTexture() +{ + geometry_dirty = true; + rect_set = false; + + String name; + + if (const Property* property = GetLocalProperty(PropertyId::FillImage)) + name = property->Get(); + + RenderManager* render_manager = GetRenderManager(); + if (!render_manager) + return false; + + ElementDocument* document = GetOwnerDocument(); + + bool texture_set = false; + + if (!name.empty() && document) + { + // Check for a sprite first, this takes precedence. + if (const StyleSheet* style_sheet = document->GetStyleSheet()) + { + if (const Sprite* sprite = style_sheet->GetSprite(name)) + { + rect = sprite->rectangle; + rect_set = true; + texture = sprite->sprite_sheet->texture_source.GetTexture(*render_manager); + texture_set = true; + } + } + + // Otherwise, treat it as a path + if (!texture_set) + { + URL source_url; + source_url.SetURL(document->GetSourceURL()); + texture = render_manager->LoadTexture(name, source_url.GetPath()); + texture_set = true; + } + } + + if (!texture_set) + { + texture = {}; + rect = {}; + } + + return true; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/ElementTabSet.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/ElementTabSet.cpp new file mode 100644 index 00000000..e8835f46 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/ElementTabSet.cpp @@ -0,0 +1,185 @@ +#include "../../../Include/RmlUi/Core/Elements/ElementTabSet.h" +#include "../../../Include/RmlUi/Core/Factory.h" +#include "../../../Include/RmlUi/Core/Math.h" + +namespace Rml { + +ElementTabSet::ElementTabSet(const String& tag) : Element(tag) +{ + active_tab = 0; +} + +ElementTabSet::~ElementTabSet() {} + +void ElementTabSet::SetTab(int tab_index, const String& rml) +{ + ElementPtr element = Factory::InstanceElement(nullptr, "*", "tab", XMLAttributes()); + Factory::InstanceElementText(element.get(), rml); + SetTab(tab_index, std::move(element)); +} + +void ElementTabSet::SetPanel(int tab_index, const String& rml) +{ + ElementPtr element = Factory::InstanceElement(nullptr, "*", "panel", XMLAttributes()); + Factory::InstanceElementText(element.get(), rml); + SetPanel(tab_index, std::move(element)); +} + +void ElementTabSet::SetTab(int tab_index, ElementPtr element) +{ + Element* tabs = GetChildByTag("tabs"); + if (tab_index >= 0 && tab_index < tabs->GetNumChildren()) + tabs->ReplaceChild(std::move(element), tabs->GetChild(tab_index)); + else + tabs->AppendChild(std::move(element)); +} + +void ElementTabSet::SetPanel(int tab_index, ElementPtr element) +{ + // append the window + Element* windows = GetChildByTag("panels"); + if (tab_index >= 0 && tab_index < windows->GetNumChildren()) + windows->ReplaceChild(std::move(element), windows->GetChild(tab_index)); + else + windows->AppendChild(std::move(element)); +} + +void ElementTabSet::RemoveTab(int tab_index) +{ + if (tab_index < 0) + return; + + Element* panels = GetChildByTag("panels"); + if (panels->GetNumChildren() > tab_index) + { + panels->RemoveChild(panels->GetChild(tab_index)); + } + + Element* tabs = GetChildByTag("tabs"); + if (tabs->GetNumChildren() > tab_index) + { + tabs->RemoveChild(tabs->GetChild(tab_index)); + } +} + +int ElementTabSet::GetNumTabs() +{ + return GetChildByTag("tabs")->GetNumChildren(); +} + +void ElementTabSet::SetActiveTab(int tab_index) +{ + // Update display if the tab has changed + if (tab_index != active_tab) + { + Element* tabs = GetChildByTag("tabs"); + Element* old_tab = tabs->GetChild(active_tab); + Element* new_tab = tabs->GetChild(tab_index); + + if (old_tab) + old_tab->SetPseudoClass("selected", false); + if (new_tab) + new_tab->SetPseudoClass("selected", true); + + Element* windows = GetChildByTag("panels"); + Element* old_window = windows->GetChild(active_tab); + Element* new_window = windows->GetChild(tab_index); + + if (old_window) + { + old_window->SetPseudoClass("selected", false); + old_window->SetProperty(PropertyId::Display, Property(Style::Display::None)); + } + if (new_window) + { + new_window->SetPseudoClass("selected", true); + new_window->RemoveProperty(PropertyId::Display); + } + + active_tab = tab_index; + + Dictionary parameters; + parameters["tab_index"] = active_tab; + DispatchEvent(EventId::Tabchange, parameters); + } +} + +int ElementTabSet::GetActiveTab() const +{ + return active_tab; +} + +void ElementTabSet::ProcessDefaultAction(Event& event) +{ + Element::ProcessDefaultAction(event); + + if (event == EventId::Click) + { + // Find the tab that this click occured on + Element* tabs = GetChildByTag("tabs"); + Element* tab = event.GetTargetElement(); + while (tab && tab != this && tab->GetParentNode() != tabs) + tab = tab->GetParentNode(); + + // Abort if we couldn't find the tab the click occured on + if (!tab || tab == this) + return; + + // Determine the new active tab index + int new_active_tab = active_tab; + for (int i = 0; i < tabs->GetNumChildren(); i++) + { + if (tabs->GetChild(i) == tab) + { + new_active_tab = i; + break; + } + } + + SetActiveTab(new_active_tab); + } +} + +void ElementTabSet::OnChildAdd(Element* child) +{ + Element::OnChildAdd(child); + + if (child->GetParentNode() == GetChildByTag("tabs")) + { + // Set up the new button and append it + child->RemoveProperty(PropertyId::Display); + + if (child->GetParentNode()->GetChild(active_tab) == child) + child->SetPseudoClass("selected", true); + } + + if (child->GetParentNode() == GetChildByTag("panels")) + { + // Hide the new tab window + child->SetProperty(PropertyId::Display, Property(Style::Display::None)); + + // Make the new element visible if its the active tab + if (child->GetParentNode()->GetChild(active_tab) == child) + { + child->SetPseudoClass("selected", true); + child->RemoveProperty(PropertyId::Display); + } + } +} + +Element* ElementTabSet::GetChildByTag(const String& tag) +{ + // Look for the existing child + for (int i = 0; i < GetNumChildren(); i++) + { + if (GetChild(i)->GetTagName() == tag) + return GetChild(i); + } + + // If it doesn't exist, create it + ElementPtr element = Factory::InstanceElement(this, "*", tag, XMLAttributes()); + Element* result = AppendChild(std::move(element)); + return result; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/ElementTextSelection.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/ElementTextSelection.cpp new file mode 100644 index 00000000..128777af --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/ElementTextSelection.cpp @@ -0,0 +1,32 @@ +#include "ElementTextSelection.h" +#include "../../../Include/RmlUi/Core/PropertyIdSet.h" +#include "WidgetTextInput.h" + +namespace Rml { + +ElementTextSelection::ElementTextSelection(const String& tag) : Element(tag) +{ + widget = nullptr; +} + +ElementTextSelection::~ElementTextSelection() {} + +void ElementTextSelection::SetWidget(WidgetTextInput* _widget) +{ + widget = _widget; +} + +void ElementTextSelection::OnPropertyChange(const PropertyIdSet& changed_properties) +{ + Element::OnPropertyChange(changed_properties); + + if (widget == nullptr) + return; + + if (changed_properties.Contains(PropertyId::Color) || changed_properties.Contains(PropertyId::BackgroundColor)) + { + widget->UpdateSelectionColours(); + } +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/ElementTextSelection.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/ElementTextSelection.h new file mode 100644 index 00000000..e5868453 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/ElementTextSelection.h @@ -0,0 +1,33 @@ +#pragma once + +#include "../../../Include/RmlUi/Core/Element.h" + +namespace Rml { + +class WidgetTextInput; + +/** + A stub element used by the WidgetTextInput to query the RCSS-specified text colour and + background colour for selected text. + */ + +class ElementTextSelection : public Element { +public: + RMLUI_RTTI_DefineWithParent(ElementTextSelection, Element) + + ElementTextSelection(const String& tag); + virtual ~ElementTextSelection(); + + /// Set the widget that this selection element was created for. This is the widget that will be + /// notified when this element's properties are altered. + void SetWidget(WidgetTextInput* widget); + +protected: + /// Processes 'color' and 'background-color' property changes. + void OnPropertyChange(const PropertyIdSet& changed_properties) override; + +private: + WidgetTextInput* widget; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputType.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputType.cpp new file mode 100644 index 00000000..8b663055 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputType.cpp @@ -0,0 +1,47 @@ +#include "InputType.h" +#include "../../../Include/RmlUi/Core/Elements/ElementFormControlInput.h" + +namespace Rml { + +InputType::InputType(ElementFormControlInput* element) : element(element) {} + +InputType::~InputType() {} + +String InputType::GetValue() const +{ + return element->GetAttribute("value", ""); +} + +bool InputType::IsSubmitted() +{ + return true; +} + +void InputType::OnUpdate() {} + +void InputType::OnRender() {} + +void InputType::OnResize() {} + +void InputType::OnLayout() {} + +bool InputType::OnAttributeChange(const ElementAttributes& /*changed_attributes*/) +{ + return true; +} + +void InputType::OnPropertyChange(const PropertyIdSet& /*changed_properties*/) {} + +void InputType::OnChildAdd() {} + +void InputType::OnChildRemove() {} + +void InputType::Select() {} + +void InputType::SetSelectionRange(int /*selection_start*/, int /*selection_end*/) {} + +void InputType::GetSelection(int* /*selection_start*/, int* /*selection_end*/, String* /*selected_text*/) const {} + +void InputType::SetCompositionRange(int /*range_start*/, int /*range_end*/) {} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputType.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputType.h new file mode 100644 index 00000000..1a308b44 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputType.h @@ -0,0 +1,73 @@ +#pragma once + +#include "../../../Include/RmlUi/Core/Event.h" +#include "../../../Include/RmlUi/Core/Types.h" + +namespace Rml { + +class ElementFormControlInput; + +/** + An interface for a input type handler used by ElementFormControlInput. A concrete InputType object handles the + functionality of an input element. + */ + +class InputType { +public: + InputType(ElementFormControlInput* element); + virtual ~InputType(); + + /// Returns a string representation of the current value of the form control. + /// @return The value of the form control. + virtual String GetValue() const; + /// Returns if this value should be submitted with the form. + /// @return True if the form control is to be submitted, false otherwise. + virtual bool IsSubmitted(); + + /// Called every update from the host element. + virtual void OnUpdate(); + + /// Called every render from the host element. + virtual void OnRender(); + + /// Called every time the host element's size changes. + virtual void OnResize(); + + /// Called every time the host element is layed out. + virtual void OnLayout(); + + /// Checks for necessary functional changes in the control as a result of changed attributes. + /// @param[in] changed_attributes The list of changed attributes. + /// @return True if no layout is required, false if the layout needs to be dirtied. + virtual bool OnAttributeChange(const ElementAttributes& changed_attributes); + /// Called when properties on the control are changed. + /// @param[in] changed_properties The properties changed on the element. + virtual void OnPropertyChange(const PropertyIdSet& changed_properties); + + /// Called when the element is added into a hierarchy. + virtual void OnChildAdd(); + /// Called when the element is removed from a hierarchy. + virtual void OnChildRemove(); + + /// Checks for necessary functional changes in the control as a result of the event. + /// @param[in] event The event to process. + virtual void ProcessDefaultAction(Event& event) = 0; + + /// Sizes the dimensions to the element's inherent size. + virtual bool GetIntrinsicDimensions(Vector2f& dimensions, float& ratio) = 0; + + /// Selects all text. + virtual void Select(); + /// Selects the text in the given character range. + virtual void SetSelectionRange(int selection_start, int selection_end); + /// Retrieves the selection range and text. + virtual void GetSelection(int* selection_start, int* selection_end, String* selected_text) const; + + /// Sets visual feedback for the IME composition in the given character range. + virtual void SetCompositionRange(int range_start, int range_end); + +protected: + ElementFormControlInput* element; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputTypeButton.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputTypeButton.cpp new file mode 100644 index 00000000..b0a0dbc6 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputTypeButton.cpp @@ -0,0 +1,42 @@ +#include "InputTypeButton.h" +#include "../../../Include/RmlUi/Core/Elements/ElementForm.h" +#include "../../../Include/RmlUi/Core/Elements/ElementFormControlInput.h" +#include "../../../Include/RmlUi/Core/Factory.h" + +namespace Rml { + +InputTypeButton::InputTypeButton(ElementFormControlInput* element) : InputType(element) {} + +InputTypeButton::~InputTypeButton() {} + +bool InputTypeButton::IsSubmitted() +{ + // Buttons are never submitted. + return false; +} + +bool InputTypeButton::OnAttributeChange(const ElementAttributes& changed_attributes) +{ + if (changed_attributes.find("value") != changed_attributes.end()) + { + auto value = element->GetAttribute("value", ""); + if (!value.empty() && !value_element) + value_element = + rmlui_static_cast(element->AppendChild(Factory::InstanceElement(element, "#text", "", XMLAttributes()), true)); + + if (value_element) + value_element->SetText(value); + + return false; + } + return true; +} + +void InputTypeButton::ProcessDefaultAction(Event& /*event*/) {} + +bool InputTypeButton::GetIntrinsicDimensions(Vector2f& /*dimensions*/, float& /*ratio*/) +{ + return false; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputTypeButton.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputTypeButton.h new file mode 100644 index 00000000..9e088802 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputTypeButton.h @@ -0,0 +1,41 @@ +#pragma once + +#include "../../../Include/RmlUi/Core/ElementDocument.h" +#include "../../../Include/RmlUi/Core/ElementText.h" +#include "../../../Include/RmlUi/Core/EventListener.h" +#include "InputType.h" + +namespace Rml { + +/** + A button input type handler. The only functionality a button provides over a normal element is the ability + to be disabled. This prevents 'click' events on this element and the ability to receive focus. + */ + +class InputTypeButton : public InputType { +public: + InputTypeButton(ElementFormControlInput* element); + virtual ~InputTypeButton(); + + /// Returns if this value should be submitted with the form. + /// @return True if the form control is to be submitted, false otherwise. + bool IsSubmitted() override; + + /// Called when an attribute of the element has changed. + /// @param[in] changed_attributes The attributes that have changed. + /// @return True if no layout is required, false if the layout needs to be dirtied. + bool OnAttributeChange(const ElementAttributes& changed_attributes) override; + + /// Checks for necessary functional changes in the control as a result of the event. + /// @param[in] event The event to process. + void ProcessDefaultAction(Event& event) override; + + /// Sizes the dimensions to the element's inherent size. + /// @return False. + bool GetIntrinsicDimensions(Vector2f& dimensions, float& ratio) override; + +private: + ElementText* value_element = nullptr; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputTypeCheckbox.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputTypeCheckbox.cpp new file mode 100644 index 00000000..3a9f6cab --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputTypeCheckbox.cpp @@ -0,0 +1,60 @@ +#include "InputTypeCheckbox.h" +#include "../../../Include/RmlUi/Core/ElementUtilities.h" +#include "../../../Include/RmlUi/Core/Elements/ElementFormControlInput.h" + +namespace Rml { + +InputTypeCheckbox::InputTypeCheckbox(ElementFormControlInput* element) : InputType(element) {} + +InputTypeCheckbox::~InputTypeCheckbox() {} + +String InputTypeCheckbox::GetValue() const +{ + auto value = InputType::GetValue(); + return value.empty() ? "on" : value; +} + +bool InputTypeCheckbox::IsSubmitted() +{ + return element->HasAttribute("checked"); +} + +bool InputTypeCheckbox::OnAttributeChange(const ElementAttributes& changed_attributes) +{ + if (changed_attributes.count("checked")) + { + const bool checked = element->HasAttribute("checked"); + element->SetPseudoClass("checked", checked); + element->DispatchEvent(EventId::Change, + { + {"data-binding-override-value", Variant(checked)}, + {"value", Variant(checked ? GetValue() : "")}, + {"checked", Variant(checked)}, + }); + } + + return true; +} + +void InputTypeCheckbox::ProcessDefaultAction(Event& event) +{ + if (event == EventId::Click && !element->IsDisabled()) + { + if (element->HasAttribute("checked")) + element->RemoveAttribute("checked"); + else + element->SetAttribute("checked", ""); + } +} + +bool InputTypeCheckbox::GetIntrinsicDimensions(Vector2f& dimensions, float& ratio) +{ + dimensions.x = 16; + dimensions.y = 16; + dimensions *= ElementUtilities::GetDensityIndependentPixelRatio(element); + ratio = 1; + + return true; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputTypeCheckbox.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputTypeCheckbox.h new file mode 100644 index 00000000..b37ea665 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputTypeCheckbox.h @@ -0,0 +1,38 @@ +#pragma once + +#include "InputType.h" + +namespace Rml { + +/** + A checkbox input type handler. + */ + +class InputTypeCheckbox : public InputType { +public: + InputTypeCheckbox(ElementFormControlInput* element); + virtual ~InputTypeCheckbox(); + + /// Returns a string representation of the current value of the form control. + /// @return The value of the form control. + String GetValue() const override; + + /// Returns if this value should be submitted with the form. + /// @return True if the form control is to be submitted, false otherwise. + bool IsSubmitted() override; + + /// Checks for necessary functional changes in the control as a result of changed attributes. + /// @param[in] changed_attributes The list of changed attributes. + /// @return True if no layout is required, false if the layout needs to be dirtied. + bool OnAttributeChange(const ElementAttributes& changed_attributes) override; + + /// Checks for necessary functional changes in the control as a result of the event. + /// @param[in] event The event to process. + void ProcessDefaultAction(Event& event) override; + + /// Sizes the dimensions to the element's inherent size. + /// @return True. + bool GetIntrinsicDimensions(Vector2f& dimensions, float& ratio) override; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputTypeRadio.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputTypeRadio.cpp new file mode 100644 index 00000000..19290ba2 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputTypeRadio.cpp @@ -0,0 +1,104 @@ +#include "InputTypeRadio.h" +#include "../../../Include/RmlUi/Core/ElementDocument.h" +#include "../../../Include/RmlUi/Core/ElementUtilities.h" +#include "../../../Include/RmlUi/Core/Elements/ElementForm.h" +#include "../../../Include/RmlUi/Core/Elements/ElementFormControlInput.h" + +namespace Rml { + +InputTypeRadio::InputTypeRadio(ElementFormControlInput* element) : InputType(element) +{ + if (element->HasAttribute("checked")) + PopRadioSet(); +} + +InputTypeRadio::~InputTypeRadio() {} + +String InputTypeRadio::GetValue() const +{ + auto value = InputType::GetValue(); + return value.empty() ? "on" : value; +} + +bool InputTypeRadio::IsSubmitted() +{ + return element->HasAttribute("checked"); +} + +bool InputTypeRadio::OnAttributeChange(const ElementAttributes& changed_attributes) +{ + if (changed_attributes.count("checked")) + { + const bool checked = element->HasAttribute("checked"); + element->SetPseudoClass("checked", checked); + + if (checked) + PopRadioSet(); + + const auto perceived_value = Variant(checked ? GetValue() : ""); + element->DispatchEvent(EventId::Change, + { + {"data-binding-override-value", checked ? perceived_value : Variant()}, + {"value", perceived_value}, + {"checked", Variant(checked)}, + }); + } + + return true; +} + +void InputTypeRadio::OnChildAdd() +{ + if (element->HasAttribute("checked")) + PopRadioSet(); +} + +void InputTypeRadio::ProcessDefaultAction(Event& event) +{ + if (event == EventId::Click && !element->IsDisabled()) + element->SetAttribute("checked", ""); +} + +bool InputTypeRadio::GetIntrinsicDimensions(Vector2f& dimensions, float& ratio) +{ + dimensions.x = 16; + dimensions.y = 16; + dimensions *= ElementUtilities::GetDensityIndependentPixelRatio(element); + ratio = 1; + + return true; +} + +void InputTypeRadio::PopRadioSet() +{ + // Uncheck all other radio buttons with our name in the form. + String stop_tag; + Element* parent = element->GetParentNode(); + while (parent != nullptr && rmlui_dynamic_cast(parent) == nullptr) + parent = parent->GetParentNode(); + + // If no containing form was found, use the containing document as the parent + if (parent == nullptr) + { + parent = element->GetOwnerDocument(); + stop_tag = "form"; // Don't include any radios that are inside form elements + } + + if (parent != nullptr) + { + ElementList form_controls; + ElementUtilities::GetElementsByTagName(form_controls, parent, "input", stop_tag); + + for (size_t i = 0; i < form_controls.size(); ++i) + { + ElementFormControlInput* radio_control = rmlui_dynamic_cast(form_controls[i]); + if (radio_control != nullptr && element != radio_control && radio_control->GetAttribute("type", "text") == "radio" && + radio_control->GetName() == element->GetName()) + { + radio_control->RemoveAttribute("checked"); + } + } + } +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputTypeRadio.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputTypeRadio.h new file mode 100644 index 00000000..29794335 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputTypeRadio.h @@ -0,0 +1,45 @@ +#pragma once + +#include "InputType.h" + +namespace Rml { + +/** + A radio button input type handler. + */ + +class InputTypeRadio : public InputType { +public: + InputTypeRadio(ElementFormControlInput* element); + virtual ~InputTypeRadio(); + + /// Returns a string representation of the current value of the form control. + /// @return The value of the form control. + String GetValue() const override; + + /// Returns if this value should be submitted with the form. + /// @return True if the form control is to be submitted, false otherwise. + bool IsSubmitted() override; + + /// Checks for necessary functional changes in the control as a result of changed attributes. + /// @param[in] changed_attributes The list of changed attributes. + /// @return True if no layout is required, false if the layout needs to be dirtied. + bool OnAttributeChange(const ElementAttributes& changed_attributes) override; + + /// Pops the element's radio set if we are checked. + void OnChildAdd() override; + + /// Checks for necessary functional changes in the control as a result of the event. + /// @param[in] event The event to process. + void ProcessDefaultAction(Event& event) override; + + /// Sizes the dimensions to the element's inherent size. + /// @return True. + bool GetIntrinsicDimensions(Vector2f& dimensions, float& ratio) override; + +private: + /// Pops all other radio buttons in our form that share our name. + void PopRadioSet(); +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputTypeRange.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputTypeRange.cpp new file mode 100644 index 00000000..e76ae82b --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputTypeRange.cpp @@ -0,0 +1,74 @@ +#include "InputTypeRange.h" +#include "../../../Include/RmlUi/Core/ElementUtilities.h" +#include "../../../Include/RmlUi/Core/Elements/ElementFormControlInput.h" +#include "WidgetSlider.h" + +namespace Rml { + +InputTypeRange::InputTypeRange(ElementFormControlInput* element) : InputType(element) +{ + widget = new WidgetSlider(element); + widget->Initialise(); +} + +InputTypeRange::~InputTypeRange() +{ + delete widget; +} + +String InputTypeRange::GetValue() const +{ + return CreateString("%f", widget->GetValue()); +} + +void InputTypeRange::OnUpdate() +{ + widget->Update(); +} + +void InputTypeRange::OnResize() +{ + widget->FormatElements(); +} + +bool InputTypeRange::OnAttributeChange(const ElementAttributes& changed_attributes) +{ + bool dirty_layout = false; + + auto it_orientation = changed_attributes.find("orientation"); + if (it_orientation != changed_attributes.end()) + { + bool is_vertical = (it_orientation->second.Get() == "vertical"); + widget->SetOrientation(is_vertical ? WidgetSlider::VERTICAL : WidgetSlider::HORIZONTAL); + dirty_layout = true; + } + + auto it_step = changed_attributes.find("step"); + if (it_step != changed_attributes.end()) + widget->SetStep(it_step->second.Get(1.0f)); + + auto it_min = changed_attributes.find("min"); + if (it_min != changed_attributes.end()) + widget->SetMinValue(it_min->second.Get(0.0f)); + + auto it_max = changed_attributes.find("max"); + if (it_max != changed_attributes.end()) + widget->SetMaxValue(it_max->second.Get(100.f)); + + auto it_value = changed_attributes.find("value"); + if (it_value != changed_attributes.end()) + widget->SetValue(it_value->second.Get(0.0f)); + + return !dirty_layout; +} + +void InputTypeRange::ProcessDefaultAction(Event& /*event*/) {} + +bool InputTypeRange::GetIntrinsicDimensions(Vector2f& dimensions, float& /*ratio*/) +{ + widget->GetDimensions(dimensions); + dimensions *= ElementUtilities::GetDensityIndependentPixelRatio(element); + return true; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputTypeRange.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputTypeRange.h new file mode 100644 index 00000000..832eabba --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputTypeRange.h @@ -0,0 +1,45 @@ +#pragma once + +#include "InputType.h" + +namespace Rml { + +class WidgetSlider; + +/** + A range input type handler. + */ + +class InputTypeRange : public InputType { +public: + InputTypeRange(ElementFormControlInput* element); + virtual ~InputTypeRange(); + + /// Returns a string representation of the current value of the form control. + /// @return The value of the form control. + String GetValue() const override; + + /// Called every update from the host element. + void OnUpdate() override; + + /// Called every time the host element's size changes. + void OnResize() override; + + /// Checks for necessary functional changes in the control as a result of changed attributes. + /// @param[in] changed_attributes The list of changed attributes. + /// @return True if no layout is required, false if the layout needs to be dirtied. + bool OnAttributeChange(const ElementAttributes& changed_attributes) override; + + /// Checks for necessary functional changes in the control as a result of the event. + /// @param[in] event The event to process. + void ProcessDefaultAction(Event& event) override; + + /// Sizes the dimensions to the element's inherent size. + /// @return True. + bool GetIntrinsicDimensions(Vector2f& dimensions, float& ratio) override; + +private: + WidgetSlider* widget; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputTypeSubmit.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputTypeSubmit.cpp new file mode 100644 index 00000000..00b8410d --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputTypeSubmit.cpp @@ -0,0 +1,60 @@ +#include "InputTypeSubmit.h" +#include "../../../Include/RmlUi/Core/Elements/ElementForm.h" +#include "../../../Include/RmlUi/Core/Elements/ElementFormControlInput.h" +#include "../../../Include/RmlUi/Core/Factory.h" + +namespace Rml { + +InputTypeSubmit::InputTypeSubmit(ElementFormControlInput* element) : InputType(element) {} + +InputTypeSubmit::~InputTypeSubmit() {} +bool InputTypeSubmit::IsSubmitted() +{ + // Submit buttons are never submitted; they submit themselves if appropriate. + return false; +} + +bool InputTypeSubmit::OnAttributeChange(const ElementAttributes& changed_attributes) +{ + if (changed_attributes.find("value") != changed_attributes.end()) + { + auto value = element->GetAttribute("value", ""); + if (!value.empty() && !value_element) + value_element = + rmlui_static_cast(element->AppendChild(Factory::InstanceElement(element, "#text", "", XMLAttributes()), true)); + + if (value_element) + value_element->SetText(value); + + return false; + } + return true; +} + +void InputTypeSubmit::ProcessDefaultAction(Event& event) +{ + if (event == EventId::Click && !element->IsDisabled()) + { + Element* parent = element->GetParentNode(); + while (parent) + { + ElementForm* form = rmlui_dynamic_cast(parent); + if (form != nullptr) + { + form->Submit(element->GetAttribute("name", ""), element->GetAttribute("value", "")); + return; + } + else + { + parent = parent->GetParentNode(); + } + } + } +} + +bool InputTypeSubmit::GetIntrinsicDimensions(Vector2f& /*dimensions*/, float& /*ratio*/) +{ + return false; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputTypeSubmit.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputTypeSubmit.h new file mode 100644 index 00000000..29487842 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputTypeSubmit.h @@ -0,0 +1,38 @@ +#pragma once + +#include "../../../Include/RmlUi/Core/ElementText.h" +#include "InputType.h" + +namespace Rml { + +/** + A submit input type handler. + */ + +class InputTypeSubmit : public InputType { +public: + InputTypeSubmit(ElementFormControlInput* element); + virtual ~InputTypeSubmit(); + + /// Returns if this value should be submitted with the form. + /// @return True if the form control is to be submitted, false otherwise. + bool IsSubmitted() override; + + /// Called when an attribute of the element has changed. + /// @param[in] changed_attributes The attributes that have changed. + /// @return True if no layout is required, false if the layout needs to be dirtied. + bool OnAttributeChange(const ElementAttributes& changed_attributes) override; + + /// Checks for necessary functional changes in the control as a result of the event. + /// @param[in] event The event to process. + void ProcessDefaultAction(Event& event) override; + + /// Sizes the dimensions to the element's inherent size. + /// @return False. + bool GetIntrinsicDimensions(Vector2f& dimensions, float& ratio) override; + +private: + ElementText* value_element = nullptr; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputTypeText.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputTypeText.cpp new file mode 100644 index 00000000..b1d1f0ba --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputTypeText.cpp @@ -0,0 +1,112 @@ +#include "InputTypeText.h" +#include "../../../Include/RmlUi/Core/ElementUtilities.h" +#include "../../../Include/RmlUi/Core/Elements/ElementFormControlInput.h" +#include "../../../Include/RmlUi/Core/PropertyIdSet.h" +#include "../../../Include/RmlUi/Core/StyleSheetSpecification.h" +#include "WidgetTextInputSingleLine.h" +#include "WidgetTextInputSingleLinePassword.h" + +namespace Rml { + +InputTypeText::InputTypeText(ElementFormControlInput* element, Visibility visibility) : InputType(element) +{ + if (visibility == VISIBLE) + widget = MakeUnique(element); + else + widget = MakeUnique(element); +} + +InputTypeText::~InputTypeText() {} + +void InputTypeText::OnUpdate() +{ + widget->OnUpdate(); +} + +void InputTypeText::OnRender() +{ + widget->OnRender(); +} + +void InputTypeText::OnResize() +{ + widget->OnResize(); +} + +void InputTypeText::OnLayout() +{ + widget->OnLayout(); +} + +bool InputTypeText::OnAttributeChange(const ElementAttributes& changed_attributes) +{ + bool dirty_layout = false; + + auto it = changed_attributes.find("maxlength"); + if (it != changed_attributes.end()) + widget->SetMaxLength(it->second.Get(-1)); + + it = changed_attributes.find("size"); + if (it != changed_attributes.end()) + { + size = it->second.Get(20); + dirty_layout = true; + } + + it = changed_attributes.find("value"); + if (it != changed_attributes.end()) + widget->OnValueAttributeChanged(it->second.Get()); + + it = changed_attributes.find("placeholder"); + if (it != changed_attributes.end()) + widget->OnPlaceholderAttributeChanged(it->second.Get()); + + return !dirty_layout; +} + +void InputTypeText::OnPropertyChange(const PropertyIdSet& changed_properties) +{ + // Some inherited properties require text formatting update, mainly font and line-height properties. + const PropertyIdSet changed_inherited_layout_properties = changed_properties & + (StyleSheetSpecification::GetRegisteredInheritedProperties() & StyleSheetSpecification::GetRegisteredPropertiesForcingLayout()); + + if (!changed_inherited_layout_properties.Empty()) + widget->ForceFormattingOnNextLayout(); + + if (changed_properties.Contains(PropertyId::Color) || changed_properties.Contains(PropertyId::BackgroundColor)) + widget->UpdateSelectionColours(); + + if (changed_properties.Contains(PropertyId::CaretColor)) + widget->GenerateCursor(); +} + +void InputTypeText::ProcessDefaultAction(Event& /*event*/) {} + +bool InputTypeText::GetIntrinsicDimensions(Vector2f& dimensions, float& /*ratio*/) +{ + dimensions.x = (float)(size * ElementUtilities::GetStringWidth(element, "m")); + dimensions.y = widget->GetLineHeight(); + return true; +} + +void InputTypeText::Select() +{ + widget->Select(); +} + +void InputTypeText::SetSelectionRange(int selection_start, int selection_end) +{ + widget->SetSelectionRange(selection_start, selection_end); +} + +void InputTypeText::GetSelection(int* selection_start, int* selection_end, String* selected_text) const +{ + widget->GetSelection(selection_start, selection_end, selected_text); +} + +void InputTypeText::SetCompositionRange(int range_start, int range_end) +{ + widget->SetCompositionRange(range_start, range_end); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputTypeText.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputTypeText.h new file mode 100644 index 00000000..515c0215 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/InputTypeText.h @@ -0,0 +1,64 @@ +#pragma once + +#include "InputType.h" + +namespace Rml { + +class WidgetTextInput; + +/** + A single-line input type handler. + */ + +class InputTypeText : public InputType { +public: + enum Visibility { VISIBLE, OBSCURED }; + + InputTypeText(ElementFormControlInput* element, Visibility visibility = VISIBLE); + virtual ~InputTypeText(); + + /// Called every update from the host element. + void OnUpdate() override; + + /// Called every render from the host element. + void OnRender() override; + + /// Called when the parent element's size changes. + void OnResize() override; + + /// Called when the parent element is layed out. + void OnLayout() override; + + /// Checks for necessary functional changes in the control as a result of changed attributes. + /// @param[in] changed_attributes The list of changed attributes. + /// @return True if no layout is required, false if the layout needs to be dirtied. + bool OnAttributeChange(const ElementAttributes& changed_attributes) override; + /// Called when properties on the control are changed. + /// @param[in] changed_properties The properties changed on the element. + void OnPropertyChange(const PropertyIdSet& changed_properties) override; + + /// Checks for necessary functional changes in the control as a result of the event. + /// @param[in] event The event to process. + void ProcessDefaultAction(Event& event) override; + + /// Sizes the dimensions to the element's inherent size. + /// @return True. + bool GetIntrinsicDimensions(Vector2f& dimensions, float& ratio) override; + + /// Selects all text. + void Select() override; + /// Selects the text in the given character range. + void SetSelectionRange(int selection_start, int selection_end) override; + /// Retrieves the selection range and text. + void GetSelection(int* selection_start, int* selection_end, String* selected_text) const override; + + /// Sets visual feedback for the IME composition in the given character range. + void SetCompositionRange(int range_start, int range_end) override; + +private: + int size = 20; + + UniquePtr widget; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/WidgetDropDown.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/WidgetDropDown.cpp new file mode 100644 index 00000000..678c0f19 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/WidgetDropDown.cpp @@ -0,0 +1,647 @@ +#include "WidgetDropDown.h" +#include "../../../Include/RmlUi/Core/ComputedValues.h" +#include "../../../Include/RmlUi/Core/Context.h" +#include "../../../Include/RmlUi/Core/ElementDocument.h" +#include "../../../Include/RmlUi/Core/ElementUtilities.h" +#include "../../../Include/RmlUi/Core/Elements/ElementFormControl.h" +#include "../../../Include/RmlUi/Core/Event.h" +#include "../../../Include/RmlUi/Core/Factory.h" +#include "../../../Include/RmlUi/Core/Input.h" +#include "../../../Include/RmlUi/Core/Math.h" +#include "../../../Include/RmlUi/Core/Profiling.h" +#include "../../../Include/RmlUi/Core/Property.h" +#include "../DataModel.h" + +namespace Rml { + +WidgetDropDown::WidgetDropDown(ElementFormControl* element) +{ + parent_element = element; + + // Create the button and selection elements. + button_element = parent_element->AppendChild(Factory::InstanceElement(parent_element, "*", "selectarrow", XMLAttributes()), false); + value_element = parent_element->AppendChild(Factory::InstanceElement(parent_element, "*", "selectvalue", XMLAttributes()), false); + selection_element = parent_element->AppendChild(Factory::InstanceElement(parent_element, "*", "selectbox", XMLAttributes()), false); + + value_element->SetProperty(PropertyId::OverflowX, Property(Style::Overflow::Hidden)); + value_element->SetProperty(PropertyId::OverflowY, Property(Style::Overflow::Hidden)); + + selection_element->SetProperty(PropertyId::Visibility, Property(Style::Visibility::Hidden)); + selection_element->SetProperty(PropertyId::ZIndex, Property(1.0f, Unit::NUMBER)); + selection_element->SetProperty(PropertyId::Clip, Property(Style::Clip::Type::None)); + selection_element->SetProperty(PropertyId::OverflowY, Property(Style::Overflow::Auto)); + + // Prevent scrolling in the parent document when the mouse is inside the selection box. + selection_element->SetProperty(PropertyId::OverscrollBehavior, Property(Style::OverscrollBehavior::Contain)); + + parent_element->AddEventListener(EventId::Click, this, true); + parent_element->AddEventListener(EventId::Blur, this); + parent_element->AddEventListener(EventId::Focus, this); + parent_element->AddEventListener(EventId::Keydown, this, true); +} + +WidgetDropDown::~WidgetDropDown() +{ + // We shouldn't clear the options ourselves, as removing the element will automatically clear children. + // However, we do need to remove events of children. + const int num_options = selection_element->GetNumChildren(); + for (int i = 0; i < num_options; i++) + selection_element->GetChild(i)->RemoveEventListener(EventId::Click, this); + + parent_element->RemoveEventListener(EventId::Click, this, true); + parent_element->RemoveEventListener(EventId::Blur, this); + parent_element->RemoveEventListener(EventId::Focus, this); + parent_element->RemoveEventListener(EventId::Keydown, this, true); + + DetachScrollEvent(); +} + +void WidgetDropDown::OnUpdate() +{ + if (selection_dirty) + { + // Find the best option element to select in the following priority: + // 1. First option with 'selected' attribute. + // 2. An option whose 'value' attribute matches the select 'value' attribute. + // 3. The first option. + // The select element's value may change as a result of this. + const String select_value = parent_element->GetAttribute("value", String()); + Element* select_option = selection_element->GetFirstChild(); + + const int num_options = selection_element->GetNumChildren(); + for (int i = 0; i < num_options; i++) + { + Element* option = selection_element->GetChild(i); + if (option->HasAttribute("selected")) + { + select_option = option; + break; + } + else if (!select_value.empty() && select_value == option->GetAttribute("value", String())) + { + select_option = option; + } + } + + if (select_option) + SetSelection(select_option); + + selection_dirty = false; + } + + if (value_rml_dirty) + { + String value_rml; + const int selection = GetSelection(); + + if (Element* option = selection_element->GetChild(selection)) + { + option->GetInnerRML(value_rml); + if (auto model = value_element->GetDataModel()) + model->CopyAliases(option, value_element); + } + else + { + if (auto model = value_element->GetDataModel()) + model->EraseAliases(value_element); + value_rml = parent_element->GetValue(); + } + + value_element->SetInnerRML(value_rml); + + value_rml_dirty = false; + value_layout_dirty = true; + } +} + +void WidgetDropDown::OnRender() +{ + if (box_visible && box_layout_dirty) + { + // Layout the selection box. + // The following procedure should ensure that the selection box is never (partly) outside of the context's window. + // This is achieved by positioning the box either above or below the 'select' element, and possibly shrinking + // the element's height. + // We try to respect user values of 'height', 'min-height', and 'max-height'. However, when we need to shrink the box + // we will override the 'height' property. + + const float initial_used_height = selection_element->GetBox().GetSize().y; + const Vector2f initial_scroll_offset = {selection_element->GetScrollLeft(), selection_element->GetScrollTop()}; + + // Previously set 'height' property from this procedure must be removed for the calculations below to work as intended. + if (selection_element->GetLocalStyleProperties().count(PropertyId::Height) == 1) + { + selection_element->RemoveProperty(PropertyId::Height); + selection_element->GetOwnerDocument()->UpdateDocument(); + } + + Box box; + ElementUtilities::BuildBox(box, parent_element->GetBox().GetSize(), selection_element); + + // The user can use 'margin-left/top/bottom' to offset the box away from the 'select' element, respectively + // horizontally, vertically when box below, and vertically when box above. + const float offset_x = box.GetEdge(BoxArea::Margin, BoxEdge::Left); + const float offset_y_below = parent_element->GetBox().GetSize(BoxArea::Border).y + box.GetEdge(BoxArea::Margin, BoxEdge::Top); + const float offset_y_above = -box.GetEdge(BoxArea::Margin, BoxEdge::Bottom); + + float window_height = 100'000.f; + if (Context* context = parent_element->GetContext()) + window_height = float(context->GetDimensions().y); + + const float absolute_y = parent_element->GetAbsoluteOffset(BoxArea::Border).y; + + const float height_below = window_height - absolute_y - offset_y_below; + const float height_above = absolute_y + offset_y_above; + + // Format the selection box and retrieve the 'native' height occupied by all the options, while respecting + // the 'min/max-height' properties. + ElementUtilities::FormatElement(selection_element, parent_element->GetBox().GetSize(BoxArea::Border)); + const float content_height = selection_element->GetOffsetHeight(); + + if (content_height < height_below) + { + // Position box below + selection_element->SetOffset(Vector2f(offset_x, offset_y_below), parent_element); + } + else if (content_height < height_above) + { + // Position box above + selection_element->SetOffset(Vector2f(offset_x, -content_height + offset_y_above), parent_element); + } + else + { + // Shrink box and position either below or above + const float padding_border_size = box.GetSizeAcross(BoxDirection::Vertical, BoxArea::Border, BoxArea::Padding); + + float height = 0.f; + float offset_y = 0.f; + + if (height_below > height_above) + { + // Position below + height = height_below - padding_border_size; + offset_y = offset_y_below; + } + else + { + // Position above + height = height_above - padding_border_size; + offset_y = offset_y_above - height_above; + } + + // Set the height and re-format the selection box. + // @performance: This causes a re-layout of the document since we're setting the height and then updating + // the document. However, the re-layout is not really needed, since the document's layout is independent of + // the selection element's size, and we do all the formatting for the element here. We really only call + // `UpdateDocument` to update the properties. See also `RemoveProperty` for height above. + selection_element->SetProperty(PropertyId::Height, Property(height, Unit::PX)); + selection_element->GetOwnerDocument()->UpdateDocument(); + ElementUtilities::FormatElement(selection_element, parent_element->GetBox().GetSize(BoxArea::Border)); + + // Set the scroll offset back, since it may have been clamped during the first element formatting. + selection_element->SetScrollLeft(initial_scroll_offset.x); + selection_element->SetScrollTop(initial_scroll_offset.y); + + selection_element->SetOffset(Vector2f(offset_x, offset_y), parent_element); + } + + const float new_used_height = selection_element->GetBox().GetSize().y; + const bool should_scroll_into_view = + (box_opened_since_last_format || value_changed_since_last_box_format || initial_used_height != new_used_height); + + const int selection = GetSelection(); + + if (should_scroll_into_view && selection != -1) + { + ScrollIntoViewOptions scroll_options = { + box_opened_since_last_format ? ScrollAlignment::Center : ScrollAlignment::Nearest, + ScrollAlignment::Nearest, + ScrollBehavior::Instant, + ScrollParentage::Closest, + }; + GetOption(selection)->ScrollIntoView(scroll_options); + } + + box_opened_since_last_format = false; + value_changed_since_last_box_format = false; + box_layout_dirty = false; + } + + if (value_layout_dirty) + { + ElementUtilities::FormatElement(value_element, parent_element->GetBox().GetSize(BoxArea::Border)); + value_element->SetOffset(parent_element->GetBox().GetPosition(BoxArea::Content), parent_element); + + value_layout_dirty = false; + } +} + +void WidgetDropDown::OnLayout() +{ + RMLUI_ZoneScopedNC("DropDownLayout", 0x7FFF00); + + if (parent_element->IsDisabled()) + { + // Propagate disabled state to selectvalue and selectarrow + value_element->SetPseudoClass("disabled", true); + button_element->SetPseudoClass("disabled", true); + } + + // Layout the button box. The selection element layout is deferred until it is opened. + ElementUtilities::PositionElement(button_element, Vector2f(0, 0), ElementUtilities::TOP_RIGHT); + + box_layout_dirty = true; + value_layout_dirty = true; +} + +void WidgetDropDown::OnValueChange(const String& value) +{ + if (!lock_selection) + { + Element* select_option = nullptr; + const int num_options = selection_element->GetNumChildren(); + for (int i = 0; i < num_options; i++) + { + Element* option = selection_element->GetChild(i); + Variant* variant = option->GetAttribute("value"); + if (variant && variant->Get() == value) + { + select_option = option; + break; + } + } + + if (select_option && !select_option->HasAttribute("selected")) + SetSelection(select_option); + } + + Dictionary parameters; + parameters["value"] = value; + parent_element->DispatchEvent(EventId::Change, parameters); + + value_rml_dirty = true; + value_changed_since_last_box_format = true; +} + +void WidgetDropDown::SetSelection(Element* select_option, bool force) +{ + const String old_value = parent_element->GetAttribute("value", String()); + const String new_value = select_option ? select_option->GetAttribute("value", String()) : String(); + + bool newly_selected = false; + const int num_options = selection_element->GetNumChildren(); + for (int i = 0; i < num_options; i++) + { + Element* option = selection_element->GetChild(i); + + if (select_option == option) + { + if (!option->IsPseudoClassSet("checked")) + newly_selected = true; + option->SetAttribute("selected", String()); + option->SetPseudoClass("checked", true); + } + else + { + option->RemoveAttribute("selected"); + option->SetPseudoClass("checked", false); + } + } + + if (force || newly_selected || (old_value != new_value)) + { + lock_selection = true; + parent_element->SetAttribute("value", new_value); + lock_selection = false; + } + + value_rml_dirty = true; +} + +void WidgetDropDown::SeekSelection(bool seek_forward) +{ + const int selected_option = GetSelection(); + const int num_options = selection_element->GetNumChildren(); + const int seek_direction = (seek_forward ? 1 : -1); + + for (int i = selected_option + seek_direction; i >= 0 && i < num_options; i += seek_direction) + { + Element* element = selection_element->GetChild(i); + + if (!element->HasAttribute("disabled") && element->IsVisible()) + { + SetSelection(element); + return; + } + } + + // No valid option found, leave selection unchanged. +} + +int WidgetDropDown::GetSelection() const +{ + const int num_options = selection_element->GetNumChildren(); + for (int i = 0; i < num_options; i++) + { + if (selection_element->GetChild(i)->HasAttribute("selected")) + return i; + } + + return -1; +} + +int WidgetDropDown::AddOption(const String& rml, const String& option_value, int before, bool select, bool selectable) +{ + ElementPtr element = Factory::InstanceElement(selection_element, "*", "option", XMLAttributes()); + element->SetInnerRML(rml); + + element->SetAttribute("value", option_value); + + if (select) + element->SetAttribute("selected", String()); + if (!selectable) + element->SetAttribute("disabled", String()); + + int result = AddOption(std::move(element), before); + + return result; +} + +int WidgetDropDown::AddOption(ElementPtr element, int before) +{ + if (element->GetTagName() != "option") + { + Log::Message(Log::LT_WARNING, "A child of '%s' must be of type 'option' but '%s' was given. See element '%s'.", + parent_element->GetTagName().c_str(), element->GetTagName().c_str(), parent_element->GetAddress().c_str()); + return -1; + } + + const int num_children_before = selection_element->GetNumChildren(); + int option_index; + if (before < 0 || before >= num_children_before) + { + selection_element->AppendChild(std::move(element)); + option_index = num_children_before; + } + else + { + selection_element->InsertBefore(std::move(element), selection_element->GetChild(before)); + option_index = before; + } + + return option_index; +} + +void WidgetDropDown::RemoveOption(int index) +{ + Element* element = selection_element->GetChild(index); + if (!element) + return; + + selection_element->RemoveChild(element); +} + +void WidgetDropDown::ClearOptions() +{ + while (Element* element = selection_element->GetLastChild()) + selection_element->RemoveChild(element); +} + +Element* WidgetDropDown::GetOption(int index) +{ + return selection_element->GetChild(index); +} + +int WidgetDropDown::GetNumOptions() const +{ + return selection_element->GetNumChildren(); +} + +void WidgetDropDown::OnChildAdd(Element* element) +{ + // We have a special case for 'data-for' here, since that element must remain hidden. + if (element->GetParentNode() != selection_element || element->HasAttribute("data-for") || element->GetTagName() != "option") + return; + + // Force to block display. Register a click handler so we can be notified of selection. + element->SetProperty(PropertyId::Display, Property(Style::Display::Block)); + element->SetProperty(PropertyId::Clip, Property(Style::Clip::Type::Auto)); + element->AddEventListener(EventId::Click, this); + + // Select the option if appropriate. + if (element->HasAttribute("selected")) + SetSelection(element, true); + + selection_dirty = true; + box_layout_dirty = true; +} + +void WidgetDropDown::OnChildRemove(Element* element) +{ + if (element->GetParentNode() != selection_element) + return; + + element->RemoveEventListener(EventId::Click, this); + + if (element->HasAttribute("selected")) + SetSelection(nullptr); + + selection_dirty = true; + box_layout_dirty = true; +} + +void WidgetDropDown::AttachScrollEvent() +{ + if (ElementDocument* document = parent_element->GetOwnerDocument()) + document->AddEventListener(EventId::Scroll, this, true); +} + +void WidgetDropDown::DetachScrollEvent() +{ + if (ElementDocument* document = parent_element->GetOwnerDocument()) + document->RemoveEventListener(EventId::Scroll, this, true); +} + +void WidgetDropDown::ProcessEvent(Event& event) +{ + if (parent_element->IsDisabled()) + return; + + // Process the button onclick + switch (event.GetId()) + { + case EventId::Click: + { + if (event.GetCurrentElement()->GetParentNode() == selection_element) + { + const int num_options = selection_element->GetNumChildren(); + // Find the element in the options and fire the selection event + for (int i = 0; i < num_options; i++) + { + Element* current_element = event.GetCurrentElement(); + if (selection_element->GetChild(i) == current_element) + { + if (!event.GetCurrentElement()->HasAttribute("disabled")) + { + SetSelection(current_element); + event.StopPropagation(); + + HideSelectBox(); + parent_element->Focus(); + } + } + } + } + else + { + // We have to check that this event isn't targeted to an element + // inside the selection box as we'll get all events coming from our + // root level select element as well as the ones coming from options (which + // get caught in the above if) + Element* element = event.GetTargetElement(); + while (element && element != parent_element) + { + if (element == selection_element) + return; + element = element->GetParentNode(); + } + + if (selection_element->GetComputedValues().visibility() == Style::Visibility::Hidden) + ShowSelectBox(); + else + HideSelectBox(); + } + } + break; + case EventId::Focus: + { + if (event.GetTargetElement() == parent_element) + { + value_element->SetPseudoClass("focus", true); + button_element->SetPseudoClass("focus", true); + } + } + break; + case EventId::Blur: + { + if (event.GetTargetElement() == parent_element) + { + HideSelectBox(); + value_element->SetPseudoClass("focus", false); + button_element->SetPseudoClass("focus", false); + } + } + break; + case EventId::Keydown: + { + Input::KeyIdentifier key_identifier = (Input::KeyIdentifier)event.GetParameter("key_identifier", 0); + + auto HasVerticalNavigation = [this](PropertyId id) { + if (const Property* p = parent_element->GetProperty(id)) + { + if (p->unit != Unit::KEYWORD) + return true; + const Style::Nav nav = static_cast(p->Get()); + if (nav == Style::Nav::Auto || nav == Style::Nav::Vertical) + return true; + } + return false; + }; + + switch (key_identifier) + { + case Input::KI_UP: + if (!box_visible && HasVerticalNavigation(PropertyId::NavUp)) + break; + SeekSelection(false); + event.StopPropagation(); + break; + case Input::KI_DOWN: + if (!box_visible && HasVerticalNavigation(PropertyId::NavDown)) + break; + SeekSelection(true); + event.StopPropagation(); + break; + case Input::KI_RETURN: + case Input::KI_NUMPADENTER: + parent_element->Click(); + event.StopPropagation(); + break; + default: break; + } + } + break; + case EventId::Scroll: + { + if (box_visible) + { + // Close the select box if we scroll outside the select box. + bool scrolls_selection_box = false; + + for (Element* element = event.GetTargetElement(); element; element = element->GetParentNode()) + { + if (element == selection_element) + { + scrolls_selection_box = true; + break; + } + } + + if (!scrolls_selection_box) + HideSelectBox(); + } + } + break; + default: break; + } +} + +void WidgetDropDown::ShowSelectBox() +{ + if (box_visible) + return; + + selected_value_on_box_open = parent_element->GetAttribute("value", ""); + selection_element->SetProperty(PropertyId::Visibility, Property(Style::Visibility::Visible)); + selection_element->SetPseudoClass("checked", true); + value_element->SetPseudoClass("checked", true); + button_element->SetPseudoClass("checked", true); + box_layout_dirty = true; + box_opened_since_last_format = true; + AttachScrollEvent(); + + box_visible = true; +} + +void WidgetDropDown::HideSelectBox() +{ + if (!box_visible) + return; + + selection_element->SetProperty(PropertyId::Visibility, Property(Style::Visibility::Hidden)); + selection_element->RemoveProperty(PropertyId::Height); + selection_element->SetPseudoClass("checked", false); + value_element->SetPseudoClass("checked", false); + button_element->SetPseudoClass("checked", false); + DetachScrollEvent(); + + box_visible = false; +} + +void WidgetDropDown::CancelSelectBox() +{ + if (!box_visible) + return; + + if (parent_element->GetAttribute("value", "") != selected_value_on_box_open) + parent_element->SetAttribute("value", selected_value_on_box_open); + + HideSelectBox(); +} + +bool WidgetDropDown::IsSelectBoxVisible() +{ + return box_visible; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/WidgetDropDown.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/WidgetDropDown.h new file mode 100644 index 00000000..828d90c2 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/WidgetDropDown.h @@ -0,0 +1,106 @@ +#pragma once + +#include "../../../Include/RmlUi/Core/EventListener.h" + +namespace Rml { + +class ElementFormControl; + +/** + Widget for drop-down functionality. + */ + +class WidgetDropDown : public EventListener { +public: + WidgetDropDown(ElementFormControl* element); + virtual ~WidgetDropDown(); + + /// Updates the select value rml if necessary. + void OnUpdate(); + /// Updates the selection box layout if necessary. + void OnRender(); + /// Positions the drop-down's internal elements. + void OnLayout(); + + /// Sets the value of the widget. + void OnValueChange(const String& value); + + /// Sets the option element as the new selection. + /// @param[in] option_element The option element to select. + /// @param[in] force Forces the new selection, even if the widget believes the selection to not have changed. + void SetSelection(Element* option_element, bool force = false); + /// Seek to the next or previous valid (visible and not disabled) option. + /// @param[in] seek_forward True to select the next valid option, false to select the previous valid option. + void SeekSelection(bool seek_forward = true); + /// Returns the index of the currently selected item. + int GetSelection() const; + + /// Adds a new option to the select control. + /// @param[in] rml The RML content used to represent the option. + /// @param[in] value The value of the option. + /// @param[in] before The index of the element to insert the new option before. + /// @param[in] select True to select the new option. + /// @param[in] selectable If true this option can be selected. If false, this option is not selectable. + /// @return The index of the new option. + int AddOption(const String& rml, const String& value, int before, bool select, bool selectable = true); + /// Moves an option element to the select control. + /// @param[in] element Element to move. + /// @param[in] before The index of the element to insert the new option before. + /// @return The index of the new option, or -1 if invalid. + int AddOption(ElementPtr element, int before); + /// Removes an option from the select control. + /// @param[in] index The index of the option to remove. + void RemoveOption(int index); + /// Removes all options from the list. + void ClearOptions(); + + /// Returns one of the widget's options. + /// @param[in] index The index of the desired option. + /// @return The option element or nullptr if the index was out of bounds. + Element* GetOption(int index); + /// Returns the number of options in the widget. + /// @return The number of options. + int GetNumOptions() const; + + // Handle newly added option elements. + void OnChildAdd(Element* element); + // Handle newly removed option elements. + void OnChildRemove(Element* element); + + /// Processes the incoming event. + void ProcessEvent(Event& event) override; + + /// Shows the selection box. + void ShowSelectBox(); + /// Hides the selection box. + void HideSelectBox(); + /// Revert to the value selected when the selection box was opened, then hide the box. + void CancelSelectBox(); + /// Check whether the select box is visible or not. + bool IsSelectBoxVisible(); + +private: + void AttachScrollEvent(); + void DetachScrollEvent(); + + // Parent element that holds this widget + ElementFormControl* parent_element; + + // The elements making up the drop-down process. + Element* button_element; + Element* selection_element; + Element* value_element; + + String selected_value_on_box_open; + + bool lock_selection = false; + bool selection_dirty = false; + bool value_rml_dirty = false; + bool value_layout_dirty = false; + bool value_changed_since_last_box_format = false; + bool box_layout_dirty = false; + bool box_opened_since_last_format = false; + bool box_visible = false; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/WidgetSlider.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/WidgetSlider.cpp new file mode 100644 index 00000000..0a01d4a2 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/WidgetSlider.cpp @@ -0,0 +1,616 @@ +#include "WidgetSlider.h" +#include "../../../Include/RmlUi/Core/ComputedValues.h" +#include "../../../Include/RmlUi/Core/Context.h" +#include "../../../Include/RmlUi/Core/ElementUtilities.h" +#include "../../../Include/RmlUi/Core/Elements/ElementFormControl.h" +#include "../../../Include/RmlUi/Core/Factory.h" +#include "../../../Include/RmlUi/Core/Input.h" +#include "../../../Include/RmlUi/Core/Profiling.h" +#include "../Clock.h" + +namespace Rml { + +static const float DEFAULT_REPEAT_DELAY = 0.5f; +static const float DEFAULT_REPEAT_PERIOD = 0.1f; + +WidgetSlider::WidgetSlider(ElementFormControl* _parent) +{ + parent = _parent; + + orientation = HORIZONTAL; + + track = nullptr; + bar = nullptr; + progress = nullptr; + arrows[0] = nullptr; + arrows[1] = nullptr; + + bar_position = 0; + bar_drag_anchor = 0; + + arrow_timers[0] = -1; + arrow_timers[1] = -1; + last_update_time = 0; + + value = 0; + min_value = 0; + max_value = 100; + step = 1; +} + +WidgetSlider::~WidgetSlider() +{ + if (bar) + parent->RemoveChild(bar); + if (track && progress) + track->RemoveChild(progress); + if (track) + parent->RemoveChild(track); + + parent->RemoveEventListener(EventId::Blur, this); + parent->RemoveEventListener(EventId::Focus, this); + parent->RemoveEventListener(EventId::Keydown, this, true); + parent->RemoveEventListener(EventId::Mousedown, this); + parent->RemoveEventListener(EventId::Mouseup, this); + parent->RemoveEventListener(EventId::Mouseout, this); + parent->RemoveEventListener(EventId::Drag, this); + parent->RemoveEventListener(EventId::Dragstart, this); + parent->RemoveEventListener(EventId::Dragend, this); + + for (int i = 0; i < 2; i++) + { + if (arrows[i]) + parent->RemoveChild(arrows[i]); + } +} + +bool WidgetSlider::Initialise() +{ + // Create all of our child elements as standard elements, and abort if we can't create them. + ElementPtr track_element = Factory::InstanceElement(parent, "*", "slidertrack", XMLAttributes()); + ElementPtr bar_element = Factory::InstanceElement(parent, "*", "sliderbar", XMLAttributes()); + ElementPtr progress_element = Factory::InstanceElement(parent, "*", "sliderprogress", XMLAttributes()); + ElementPtr arrow0_element = Factory::InstanceElement(parent, "*", "sliderarrowdec", XMLAttributes()); + ElementPtr arrow1_element = Factory::InstanceElement(parent, "*", "sliderarrowinc", XMLAttributes()); + + if (!track_element || !bar_element || !progress_element || !arrow0_element || !arrow1_element) + return false; + + // Add them as non-DOM elements. + track = parent->AppendChild(std::move(track_element), false); + progress = track->AppendChild(std::move(progress_element), false); + bar = parent->AppendChild(std::move(bar_element), false); + arrows[0] = parent->AppendChild(std::move(arrow0_element), false); + arrows[1] = parent->AppendChild(std::move(arrow1_element), false); + + const Property drag_property = Property(Style::Drag::Drag); + track->SetProperty(PropertyId::Drag, drag_property); + progress->SetProperty(PropertyId::Drag, drag_property); + bar->SetProperty(PropertyId::Drag, drag_property); + + // Attach the listeners + // All listeners are attached to parent, ensuring that we don't get duplicate events when it bubbles from child to parent + parent->AddEventListener(EventId::Blur, this); + parent->AddEventListener(EventId::Focus, this); + parent->AddEventListener(EventId::Keydown, this, true); + parent->AddEventListener(EventId::Mousedown, this); + parent->AddEventListener(EventId::Mouseup, this); + parent->AddEventListener(EventId::Mouseout, this); + parent->AddEventListener(EventId::Drag, this); + parent->AddEventListener(EventId::Dragstart, this); + parent->AddEventListener(EventId::Dragend, this); + + return true; +} + +void WidgetSlider::Update() +{ + if (!std::any_of(std::begin(arrow_timers), std::end(arrow_timers), [](float timer) { return timer > 0; })) + return; + + const double current_time = Clock::GetElapsedTime(); + const float delta_time = float(current_time - last_update_time); + last_update_time = current_time; + + for (int i = 0; i < 2; i++) + { + if (arrow_timers[i] > 0) + { + arrow_timers[i] -= delta_time; + while (arrow_timers[i] <= 0) + { + arrow_timers[i] += DEFAULT_REPEAT_PERIOD; + SetBarPosition(i == 0 ? OnLineDecrement() : OnLineIncrement()); + } + + if (Context* ctx = parent->GetContext()) + ctx->RequestNextUpdate(arrow_timers[i]); + } + } +} + +void WidgetSlider::SetBarPosition(float _bar_position) +{ + bar_position = Math::Clamp(_bar_position, 0.0f, 1.0f); + PositionBar(); + ResizeProgress(); +} + +float WidgetSlider::GetBarPosition() +{ + return bar_position; +} + +void WidgetSlider::SetOrientation(Orientation _orientation) +{ + orientation = _orientation; +} + +WidgetSlider::Orientation WidgetSlider::GetOrientation() const +{ + return orientation; +} + +void WidgetSlider::GetDimensions(Vector2f& dimensions) const +{ + switch (orientation) + { + case VERTICAL: + dimensions.x = 16; + dimensions.y = 256; + break; + case HORIZONTAL: + dimensions.x = 256; + dimensions.y = 16; + break; + } +} + +void WidgetSlider::SetValue(float target_value) +{ + float num_steps = (target_value - min_value) / step; + float new_value = min_value + Math::Round(num_steps) * step; + + if (new_value != value) + SetBarPosition(SetValueInternal(new_value)); +} + +float WidgetSlider::GetValue() const +{ + return value; +} + +void WidgetSlider::SetMinValue(float _min_value) +{ + if (_min_value != min_value) + { + min_value = _min_value; + SetBarPosition(SetValueInternal(value, false)); + } +} + +void WidgetSlider::SetMaxValue(float _max_value) +{ + if (_max_value != max_value) + { + max_value = _max_value; + SetBarPosition(SetValueInternal(value, false)); + } +} + +void WidgetSlider::SetStep(float _step) +{ + // Can't have a zero step! + if (_step == 0) + return; + + step = _step; +} + +void WidgetSlider::FormatElements() +{ + RMLUI_ZoneScopedNC("RangeOnResize", 0x228044); + + Vector2f box = GetParent()->GetBox().GetSize(); + WidgetSlider::FormatElements(box, GetOrientation() == VERTICAL ? box.y : box.x); +} + +void WidgetSlider::FormatElements(const Vector2f containing_block, float slider_length) +{ + int length_axis = orientation == VERTICAL ? 1 : 0; + + // Build the box for the containing slider element. + Box parent_box; + ElementUtilities::BuildBox(parent_box, containing_block, parent); + + // Set the length of the slider. + Vector2f content = parent_box.GetSize(); + content[length_axis] = slider_length; + parent_box.SetContent(content); + + // Generate the initial dimensions for the track. It'll need to be cut down to fit the arrows. + Box track_box; + ElementUtilities::BuildBox(track_box, parent_box.GetSize(), track); + content = track_box.GetSize(); + content[length_axis] = slider_length -= orientation == VERTICAL + ? (track_box.GetCumulativeEdge(BoxArea::Content, BoxEdge::Top) + track_box.GetCumulativeEdge(BoxArea::Content, BoxEdge::Bottom)) + : (track_box.GetCumulativeEdge(BoxArea::Content, BoxEdge::Left) + track_box.GetCumulativeEdge(BoxArea::Content, BoxEdge::Right)); + // If no height has been explicitly specified for the track, it'll be initialised to -1 as per normal block + // elements. We'll fix that up here. + if (orientation == HORIZONTAL && content.y < 0) + content.y = parent_box.GetSize().y; + + // Now we size the arrows. + for (int i = 0; i < 2; i++) + { + Box arrow_box; + ElementUtilities::BuildBox(arrow_box, parent_box.GetSize(), arrows[i]); + + // Clamp the size to (0, 0). + Vector2f arrow_size = arrow_box.GetSize(); + if (arrow_size.x < 0 || arrow_size.y < 0) + arrow_box.SetContent(Vector2f(0, 0)); + + arrows[i]->SetBox(arrow_box); + + // Shrink the track length by the arrow size. + content[length_axis] -= arrow_box.GetSize(BoxArea::Margin)[length_axis]; + } + + // Now the track has been sized, we can fix everything into position. + track_box.SetContent(content); + track->SetBox(track_box); + + if (orientation == VERTICAL) + { + Vector2f offset(arrows[0]->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Left), arrows[0]->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Top)); + arrows[0]->SetOffset(offset, parent); + + offset.x = track->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Left); + offset.y += arrows[0]->GetBox().GetSize(BoxArea::Border).y + arrows[0]->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Bottom) + + track->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Top); + track->SetOffset(offset, parent); + + offset.x = arrows[1]->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Left); + offset.y += track->GetBox().GetSize(BoxArea::Border).y + track->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Bottom) + + arrows[1]->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Top); + arrows[1]->SetOffset(offset, parent); + } + else + { + Vector2f offset(arrows[0]->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Left), arrows[0]->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Top)); + arrows[0]->SetOffset(offset, parent); + + offset.x += arrows[0]->GetBox().GetSize(BoxArea::Border).x + arrows[0]->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Right) + + track->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Left); + offset.y = track->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Top); + track->SetOffset(offset, parent); + + offset.x += track->GetBox().GetSize(BoxArea::Border).x + track->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Right) + + arrows[1]->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Left); + offset.y = arrows[1]->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Top); + arrows[1]->SetOffset(offset, parent); + } + + FormatBar(); + FormatProgress(); + + if (parent->IsDisabled()) + { + // Propagate disabled state to child elements + bar->SetPseudoClass("disabled", true); + track->SetPseudoClass("disabled", true); + progress->SetPseudoClass("disabled", true); + arrows[0]->SetPseudoClass("disabled", true); + arrows[1]->SetPseudoClass("disabled", true); + } +} + +void WidgetSlider::FormatBar() +{ + Box bar_box; + ElementUtilities::BuildBox(bar_box, parent->GetBox().GetSize(), bar); + auto& computed = bar->GetComputedValues(); + + Vector2f bar_box_content = bar_box.GetSize(); + if (orientation == HORIZONTAL) + { + if (computed.height().type == Style::Height::Auto) + bar_box_content.y = parent->GetBox().GetSize().y; + } + + // Set the new dimensions on the bar to re-decorate it. + bar_box.SetContent(bar_box_content); + bar->SetBox(bar_box); + + // Now that it's been resized, re-position it. + PositionBar(); +} + +void WidgetSlider::FormatProgress() +{ + Box progress_box; + ElementUtilities::BuildBox(progress_box, parent->GetBox().GetSize(), progress); + auto& computed = progress->GetComputedValues(); + + Vector2f progress_box_content = progress_box.GetSize(); + + if (orientation == HORIZONTAL) + { + if (computed.height().type == Style::Height::Auto) + progress_box_content.y = track->GetBox().GetSize().y; + } + + // Set the new dimensions on the progress element to re-decorate it. + progress_box.SetContent(progress_box_content); + progress->SetBox(progress_box); + + Vector2f offset = track->GetRelativeOffset(); + offset.x += progress->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Left); + offset.y += progress->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Top); + + progress->SetOffset(offset, parent); + + ResizeProgress(); +} + +Element* WidgetSlider::GetParent() const +{ + return parent; +} + +void WidgetSlider::ProcessEvent(Event& event) +{ + if (parent->IsDisabled()) + return; + + switch (event.GetId()) + { + case EventId::Mousedown: + { + // Only respond to primary mouse button. + if (event.GetParameter("button", -1) != 0) + break; + + if (event.GetTargetElement() == track || event.GetTargetElement() == progress) + { + float mouse_position, bar_halfsize; + + if (orientation == HORIZONTAL) + { + mouse_position = event.GetParameter("mouse_x", 0); + bar_halfsize = 0.5f * bar->GetBox().GetSize(BoxArea::Border).x; + } + else + { + mouse_position = event.GetParameter("mouse_y", 0); + bar_halfsize = 0.5f * bar->GetBox().GetSize(BoxArea::Border).y; + } + + float new_bar_position = AbsolutePositionToBarPosition(mouse_position - bar_halfsize); + SetBarPosition(OnBarChange(new_bar_position)); + } + else if (event.GetTargetElement() == arrows[0]) + { + arrow_timers[0] = DEFAULT_REPEAT_DELAY; + last_update_time = Clock::GetElapsedTime(); + SetBarPosition(OnLineDecrement()); + } + else if (event.GetTargetElement() == arrows[1]) + { + arrow_timers[1] = DEFAULT_REPEAT_DELAY; + last_update_time = Clock::GetElapsedTime(); + SetBarPosition(OnLineIncrement()); + } + } + break; + + case EventId::Mouseup: + case EventId::Mouseout: + { + if (event.GetTargetElement() == arrows[0]) + arrow_timers[0] = -1; + else if (event.GetTargetElement() == arrows[1]) + arrow_timers[1] = -1; + } + break; + + case EventId::Dragstart: + { + if (event.GetTargetElement() == bar || event.GetTargetElement() == track || event.GetTargetElement() == progress) + { + bar->SetPseudoClass("active", true); + + if (orientation == HORIZONTAL) + bar_drag_anchor = event.GetParameter("mouse_x", 0) - bar->GetAbsoluteOffset().x; + else + bar_drag_anchor = event.GetParameter("mouse_y", 0) - bar->GetAbsoluteOffset().y; + } + } + break; + case EventId::Drag: + { + if (event.GetTargetElement() == bar || event.GetTargetElement() == track || event.GetTargetElement() == progress) + { + float new_bar_offset = event.GetParameter((orientation == HORIZONTAL ? "mouse_x" : "mouse_y"), 0) - bar_drag_anchor; + float new_bar_position = AbsolutePositionToBarPosition(new_bar_offset); + + SetBarPosition(OnBarChange(new_bar_position)); + } + } + break; + case EventId::Dragend: + { + if (event.GetTargetElement() == bar || event.GetTargetElement() == track || event.GetTargetElement() == progress) + { + bar->SetPseudoClass("active", false); + } + } + break; + + case EventId::Keydown: + { + const Input::KeyIdentifier key_identifier = (Input::KeyIdentifier)event.GetParameter("key_identifier", 0); + + const bool increment = + (key_identifier == Input::KI_RIGHT && orientation == HORIZONTAL) || (key_identifier == Input::KI_DOWN && orientation == VERTICAL); + const bool decrement = + (key_identifier == Input::KI_LEFT && orientation == HORIZONTAL) || (key_identifier == Input::KI_UP && orientation == VERTICAL); + + if (increment || decrement) + { + SetBarPosition(decrement ? OnLineDecrement() : OnLineIncrement()); + event.StopPropagation(); + } + } + break; + + case EventId::Focus: + { + if (event.GetTargetElement() == parent) + bar->SetPseudoClass("focus", true); + } + break; + case EventId::Blur: + { + if (event.GetTargetElement() == parent) + bar->SetPseudoClass("focus", false); + } + break; + + default: break; + } +} + +float WidgetSlider::OnBarChange(float bar_position) +{ + float new_value = min_value + bar_position * (max_value - min_value); + int num_steps = Math::RoundToInteger((new_value - value) / step); + + return SetValueInternal(value + num_steps * step); +} + +float WidgetSlider::OnLineIncrement() +{ + return SetValueInternal(value + step); +} + +float WidgetSlider::OnLineDecrement() +{ + return SetValueInternal(value - step); +} + +float WidgetSlider::AbsolutePositionToBarPosition(float absolute_position) const +{ + float new_bar_position = bar_position; + + if (orientation == HORIZONTAL) + { + const float edge_left = bar->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Left); + const float edge_right = bar->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Right); + + float traversable_track_length = + track->GetBox().GetSize(BoxArea::Content).x - bar->GetBox().GetSize(BoxArea::Border).x - edge_left - edge_right; + if (traversable_track_length > 0) + { + float traversable_track_origin = track->GetAbsoluteOffset().x + edge_left; + new_bar_position = (absolute_position - traversable_track_origin) / traversable_track_length; + new_bar_position = Math::Clamp(new_bar_position, 0.0f, 1.0f); + } + } + else + { + const float edge_top = bar->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Top); + const float edge_bottom = bar->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Bottom); + + float traversable_track_length = + track->GetBox().GetSize(BoxArea::Content).y - bar->GetBox().GetSize(BoxArea::Border).y - edge_top - edge_bottom; + if (traversable_track_length > 0) + { + float traversable_track_origin = track->GetAbsoluteOffset().y + edge_top; + new_bar_position = (absolute_position - traversable_track_origin) / traversable_track_length; + new_bar_position = Math::Clamp(new_bar_position, 0.0f, 1.0f); + } + } + + return new_bar_position; +} + +void WidgetSlider::PositionBar() +{ + const Vector2f track_dimensions = track->GetBox().GetSize(); + const Vector2f bar_dimensions = bar->GetBox().GetSize(BoxArea::Border); + + if (orientation == VERTICAL) + { + const float edge_top = bar->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Top); + const float edge_bottom = bar->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Bottom); + + const float traversable_track_length = track_dimensions.y - bar_dimensions.y - edge_top - edge_bottom; + const Vector2f offset = { + bar->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Left), + track->GetRelativeOffset().y + edge_top + traversable_track_length * bar_position, + }; + bar->SetOffset(offset.Round(), parent); + } + else + { + const float edge_left = bar->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Left); + const float edge_right = bar->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Right); + + const float traversable_track_length = track_dimensions.x - bar_dimensions.x - edge_left - edge_right; + const Vector2f offset = { + track->GetRelativeOffset().x + edge_left + traversable_track_length * bar_position, + bar->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Top), + }; + bar->SetOffset(offset.Round(), parent); + } +} + +void WidgetSlider::ResizeProgress() +{ + Box progress_box = progress->GetBox(); + Vector2f new_size = progress_box.GetSize(); + + if (orientation == VERTICAL) { + new_size.y = bar->GetOffsetTop(); + } else { + new_size.x = bar->GetOffsetLeft(); + } + + progress_box.SetContent(new_size); + progress->SetBox(progress_box); +} + +float WidgetSlider::SetValueInternal(float new_value, bool force_submit_change_event) +{ + if (min_value < max_value) + { + value = Math::Clamp(new_value, min_value, max_value); + } + else if (min_value > max_value) + { + value = Math::Clamp(new_value, max_value, min_value); + } + else + { + value = min_value; + return 0; + } + + const bool value_changed = (value != GetParent()->GetAttribute("value", 0.0f)); + + if (force_submit_change_event || value_changed) + { + Dictionary parameters; + parameters["value"] = value; + GetParent()->DispatchEvent(EventId::Change, parameters); + } + + if (value_changed) + GetParent()->SetAttribute("value", value); + + return (value - min_value) / (max_value - min_value); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/WidgetSlider.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/WidgetSlider.h new file mode 100644 index 00000000..ae7393ca --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/WidgetSlider.h @@ -0,0 +1,126 @@ +#pragma once + +#include "../../../Include/RmlUi/Core/EventListener.h" + +namespace Rml { + +class ElementFormControl; + +/** + A generic widget for incorporating sliding functionality into an element. + */ + +class WidgetSlider final : public EventListener { +public: + enum Orientation { VERTICAL, HORIZONTAL }; + + WidgetSlider(ElementFormControl* parent); + virtual ~WidgetSlider(); + + /// Initialises the slider's hidden elements. + bool Initialise(); + + /// Updates the key repeats for the increment / decrement arrows. + void Update(); + + /// Sets the position of the bar. + /// @param[in] bar_position The new position of the bar (0 representing the start of the track, 1 representing the end). + void SetBarPosition(float bar_position); + /// Returns the current position of the bar. + /// @return The current position of the bar (0 representing the start of the track, 1 representing the end). + float GetBarPosition(); + + /// Sets the orientation of the slider. + void SetOrientation(Orientation orientation); + /// Returns the slider's orientation. + Orientation GetOrientation() const; + + /// Sets the dimensions to the size of the slider. + void GetDimensions(Vector2f& dimensions) const; + + /// Sets a new value on the slider, clamped to the min and max values, and rounded to the nearest increment. + void SetValue(float value); + /// Returns the current value of the slider. + float GetValue() const; + + /// Sets the minimum value of the slider. + void SetMinValue(float min_value); + /// Sets the maximum value of the slider. + void SetMaxValue(float max_value); + /// Sets the slider's value increment. + void SetStep(float step); + + /// Formats the slider's elements. + void FormatElements(); + +private: + /// Lays out and resizes the slider's internal elements. + /// @param[in] containing_block The padded box containing the slider. This is used to resolve relative properties. + /// @param[in] slider_length The total length, in pixels, of the slider widget. + void FormatElements(Vector2f containing_block, float slider_length); + /// Lays out and positions the bar element. + void FormatBar(); + + /// Lays out and positions the progress element. + void FormatProgress(); + + /// Returns the widget's parent element. + Element* GetParent() const; + + /// Handles events coming through from the slider's components. + void ProcessEvent(Event& event) override; + + /// Called when the slider's bar position is set or dragged. + /// @param[in] bar_position The new position of the bar (0 representing the start of the track, 1 representing the end). + /// @return The new position of the bar. + float OnBarChange(float bar_position); + /// Called when the slider is incremented by one 'line', either by the down / right key or a mouse-click on the + /// increment arrow. + /// @return The new position of the bar. + float OnLineIncrement(); + /// Called when the slider is decremented by one 'line', either by the up / left key or a mouse-click on the + /// decrement arrow. + /// @return The new position of the bar. + float OnLineDecrement(); + + /// Determine the normalized bar position given an absolute position coordinate. + /// @param[in] absolute_position Absolute position along the axis determined by 'orientation'. + /// @return The normalized bar position [0, 1] + float AbsolutePositionToBarPosition(float absolute_position) const; + + void PositionBar(); + void ResizeProgress(); + + // Clamps the new value, sets it on the slider and returns it as a normalized number from 0 to 1. + float SetValueInternal(float new_value, bool force_submit_change_event = true); + + ElementFormControl* parent; + + Orientation orientation; + + // The background track element, across which the bar slides. + Element* track; + // The bar element. This is the element that is dragged across the trough. + Element* bar; + // Element that renders the progress area of the slider. + Element* progress; + // The two (optional) buttons for incrementing and decrementing the slider. + Element* arrows[2]; + + // A number from 0 to 1, indicating how far along the track the bar is. + float bar_position; + // If the bar is being dragged, this is the pixel offset from the start of the bar to where it was picked up. + float bar_drag_anchor; + + // Set to the auto-repeat timer if either of the arrow buttons have been pressed, -1 if they haven't. + float arrow_timers[2]; + double last_update_time; + + float value; + float min_value; + float max_value; + + float step; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/WidgetTextInput.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/WidgetTextInput.cpp new file mode 100644 index 00000000..7fec9577 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/WidgetTextInput.cpp @@ -0,0 +1,1640 @@ +#include "WidgetTextInput.h" +#include "../../../Include/RmlUi/Core/ComputedValues.h" +#include "../../../Include/RmlUi/Core/Context.h" +#include "../../../Include/RmlUi/Core/Core.h" +#include "../../../Include/RmlUi/Core/ElementScroll.h" +#include "../../../Include/RmlUi/Core/ElementText.h" +#include "../../../Include/RmlUi/Core/ElementUtilities.h" +#include "../../../Include/RmlUi/Core/Elements/ElementFormControl.h" +#include "../../../Include/RmlUi/Core/Factory.h" +#include "../../../Include/RmlUi/Core/FontEngineInterface.h" +#include "../../../Include/RmlUi/Core/Input.h" +#include "../../../Include/RmlUi/Core/Math.h" +#include "../../../Include/RmlUi/Core/MeshUtilities.h" +#include "../../../Include/RmlUi/Core/StringUtilities.h" +#include "../../../Include/RmlUi/Core/SystemInterface.h" +#include "../../../Include/RmlUi/Core/TextInputContext.h" +#include "../../../Include/RmlUi/Core/TextInputHandler.h" +#include "../Clock.h" +#include "ElementTextSelection.h" +#include +#include + +namespace Rml { + +static constexpr float CURSOR_BLINK_TIME = 0.7f; // [s] +static constexpr float OVERFLOW_TOLERANCE = 0.5f; // [px] +static constexpr float COMPOSITION_UNDERLINE_WIDTH = 2.f; // [px] + +enum class CharacterClass { Word, Punctuation, Newline, Whitespace, Undefined }; +static CharacterClass GetCharacterClass(char c) +{ + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' || ((unsigned char)c >= 128)) + return CharacterClass::Word; + if ((c >= '!' && c <= '/') || (c >= ':' && c <= '@') || (c >= '[' && c <= '`') || (c >= '{' && c <= '~')) + return CharacterClass::Punctuation; + if (c == '\n') + return CharacterClass::Newline; + return CharacterClass::Whitespace; +} + +// Clamps the value to the given maximum number of unicode code points. Returns true if the value was changed. +static bool ClampValue(String& value, int max_length) +{ + if (max_length >= 0) + { + int max_byte_length = StringUtilities::ConvertCharacterOffsetToByteOffset(value, max_length); + if (max_byte_length < (int)value.size()) + { + value.erase((size_t)max_byte_length); + return true; + } + } + return false; +} + +class WidgetTextInputContext final : public TextInputContext { +public: + WidgetTextInputContext(TextInputHandler* handler, WidgetTextInput* _owner, ElementFormControl* _element); + ~WidgetTextInputContext(); + + bool GetBoundingBox(Rectanglef& out_rectangle) const override; + void GetSelectionRange(int& start, int& end) const override; + void SetSelectionRange(int start, int end) override; + void SetCursorPosition(int position) override; + void SetText(StringView text, int start, int end) override; + void SetCompositionRange(int start, int end) override; + void CommitComposition(StringView composition) override; + +private: + TextInputHandler* handler; + WidgetTextInput* owner; + ElementFormControl* element; +}; + +WidgetTextInputContext::WidgetTextInputContext(TextInputHandler* handler, WidgetTextInput* owner, ElementFormControl* element) : + handler(handler), owner(owner), element(element) +{} + +WidgetTextInputContext::~WidgetTextInputContext() +{ + handler->OnDestroy(this); +} + +bool WidgetTextInputContext::GetBoundingBox(Rectanglef& out_rectangle) const +{ + return ElementUtilities::GetBoundingBox(out_rectangle, element, BoxArea::Border); +} + +void WidgetTextInputContext::GetSelectionRange(int& start, int& end) const +{ + owner->GetSelection(&start, &end, nullptr); +} + +void WidgetTextInputContext::SetSelectionRange(int start, int end) +{ + owner->SetSelectionRange(start, end); +} + +void WidgetTextInputContext::SetCursorPosition(int position) +{ + SetSelectionRange(position, position); +} + +void WidgetTextInputContext::SetText(StringView text, int start, int end) +{ + String value = owner->GetAttributeValue(); + + start = StringUtilities::ConvertCharacterOffsetToByteOffset(value, start); + end = StringUtilities::ConvertCharacterOffsetToByteOffset(value, end); + + RMLUI_ASSERTMSG(end >= start, "Invalid end character offset."); + value.replace(start, end - start, text.begin(), text.size()); + + element->SetValue(value); +} + +void WidgetTextInputContext::SetCompositionRange(int start, int end) +{ + owner->SetCompositionRange(start, end); +} + +void WidgetTextInputContext::CommitComposition(StringView composition) +{ + int start_byte, end_byte; + owner->GetCompositionRange(start_byte, end_byte); + + // No composition to commit. + if (start_byte == 0 && end_byte == 0) + return; + + String value = owner->GetAttributeValue(); + + // If the text input has a length restriction, we have to shorten the composition string. + if (owner->GetMaxLength() >= 0) + { + int start = StringUtilities::ConvertByteOffsetToCharacterOffset(value, start_byte); + int end = StringUtilities::ConvertByteOffsetToCharacterOffset(value, end_byte); + + int value_length = (int)StringUtilities::LengthUTF8(value); + int composition_length = (int)StringUtilities::LengthUTF8(composition); + + // The requested text value would exceed the length restriction after replacing the original value. + if (value_length + composition_length - (start - end) > owner->GetMaxLength()) + { + int new_length = owner->GetMaxLength() - (value_length - composition_length); + int new_length_byte = StringUtilities::ConvertCharacterOffsetToByteOffset(composition, new_length); + composition = StringView(composition.begin(), composition.begin() + new_length_byte); + } + } + + RMLUI_ASSERTMSG(end_byte >= start_byte, "Invalid end character offset."); + value.replace(start_byte, end_byte - start_byte, composition.begin(), composition.size()); + + element->SetValue(value); +} + +WidgetTextInput::WidgetTextInput(ElementFormControl* _parent) +{ + keyboard_showed = false; + + parent = _parent; + parent->SetProperty(PropertyId::WhiteSpace, Property(Style::WhiteSpace::Pre)); + parent->SetProperty(PropertyId::OverflowX, Property(Style::Overflow::Hidden)); + parent->SetProperty(PropertyId::OverflowY, Property(Style::Overflow::Hidden)); + parent->SetProperty(PropertyId::Drag, Property(Style::Drag::Drag)); + parent->SetProperty(PropertyId::WordBreak, Property(Style::WordBreak::BreakWord)); + parent->SetProperty(PropertyId::TextTransform, Property(Style::TextTransform::None)); + parent->SetProperty(PropertyId::Clip, Property(Style::Clip::Type::Auto)); + + parent->AddEventListener(EventId::Keydown, this, true); + parent->AddEventListener(EventId::Textinput, this, true); + parent->AddEventListener(EventId::Focus, this, true); + parent->AddEventListener(EventId::Blur, this, true); + parent->AddEventListener(EventId::Mousedown, this, true); + parent->AddEventListener(EventId::Dblclick, this, true); + parent->AddEventListener(EventId::Drag, this, true); + + ElementPtr unique_text = Factory::InstanceElement(parent, "#text", "#text", XMLAttributes()); + text_element = rmlui_dynamic_cast(unique_text.get()); + ElementPtr unique_selected_text = Factory::InstanceElement(parent, "#text", "#text", XMLAttributes()); + selected_text_element = rmlui_dynamic_cast(unique_selected_text.get()); + if (text_element) + { + text_element->SuppressAutoLayout(); + parent->AppendChild(std::move(unique_text), false); + + selected_text_element->SuppressAutoLayout(); + parent->AppendChild(std::move(unique_selected_text), false); + } + + // Create the dummy selection element. + ElementPtr unique_selection = Factory::InstanceElement(parent, "#selection", "selection", XMLAttributes()); + if (ElementTextSelection* text_selection_element = rmlui_dynamic_cast(unique_selection.get())) + { + selection_element = text_selection_element; + text_selection_element->SetWidget(this); + parent->AppendChild(std::move(unique_selection), false); + } + + absolute_cursor_index = 0; + cursor_wrap_down = false; + ideal_cursor_position_to_the_right_of_cursor = true; + cancel_next_drag = false; + force_formatting_on_next_layout = false; + + ideal_cursor_position = 0; + + max_length = -1; + + selection_anchor_index = 0; + selection_begin_index = 0; + selection_length = 0; + + ime_composition_begin_index = 0; + ime_composition_end_index = 0; + + last_update_time = 0; + ink_overflow = false; + + ShowCursor(false); +} + +WidgetTextInput::~WidgetTextInput() +{ + parent->RemoveEventListener(EventId::Keydown, this, true); + parent->RemoveEventListener(EventId::Textinput, this, true); + parent->RemoveEventListener(EventId::Focus, this, true); + parent->RemoveEventListener(EventId::Blur, this, true); + parent->RemoveEventListener(EventId::Mousedown, this, true); + parent->RemoveEventListener(EventId::Dblclick, this, true); + parent->RemoveEventListener(EventId::Drag, this, true); + + // This widget might be parented by an input element, which may now be constructing a completely different type. + // Thus, remove all properties set by this widget so they don't affect the new type. + parent->RemoveProperty(PropertyId::WhiteSpace); + parent->RemoveProperty(PropertyId::OverflowX); + parent->RemoveProperty(PropertyId::OverflowY); + parent->RemoveProperty(PropertyId::Drag); + parent->RemoveProperty(PropertyId::WordBreak); + parent->RemoveProperty(PropertyId::TextTransform); + + // Remove all the children added by the text widget. + parent->RemoveChild(text_element); + parent->RemoveChild(selected_text_element); + parent->RemoveChild(selection_element); +} + +void WidgetTextInput::OnValueAttributeChanged(String value) +{ + const size_t initial_size = value.size(); + SanitizeValue(value); + if (initial_size != value.size()) + { + // Sanitizer changed the value. Set the attribute with the new value, the current function will be re-entered. + parent->SetAttribute("value", value); + DispatchChangeEvent(); + return; + } + + TransformValue(value); + + text_element->SetText(value); + + // Reset the IME composition range when the value changes. + ime_composition_begin_index = 0; + ime_composition_end_index = 0; + + SetValueOrPlaceholder(value, GetAttributePlaceholder()); +} + +String WidgetTextInput::GetAttributeValue() const +{ + return parent->GetAttribute("value", String()); +} + +void WidgetTextInput::OnPlaceholderAttributeChanged(const String& placeholder) +{ + SetValueOrPlaceholder(GetAttributeValue(), placeholder); +} + +String WidgetTextInput::GetAttributePlaceholder() const +{ + return parent->GetAttribute("placeholder", String()); +} + +void WidgetTextInput::TransformValue(String& /*value*/) {} + +int WidgetTextInput::DisplayIndexToAttributeIndex(int display_index, const String& /*attribute_value*/) +{ + return display_index; +} + +int WidgetTextInput::AttributeIndexToDisplayIndex(int attribute_index, const String& /*attribute_value*/) +{ + return attribute_index; +} + +void WidgetTextInput::SetMaxLength(int _max_length) +{ + if (max_length != _max_length) + { + max_length = _max_length; + + String value = GetValue(); + if (ClampValue(value, max_length)) + GetElement()->SetAttribute("value", value); + } +} + +int WidgetTextInput::GetMaxLength() const +{ + return max_length; +} + +int WidgetTextInput::GetLength() const +{ + size_t result = StringUtilities::LengthUTF8(GetValue()); + return (int)result; +} + +void WidgetTextInput::Select() +{ + SetSelectionRange(0, INT_MAX); +} + +void WidgetTextInput::SetSelectionRange(int selection_start, int selection_end) +{ + if (!IsFocused()) + return; + + const String& value = GetValue(); + const int byte_start = StringUtilities::ConvertCharacterOffsetToByteOffset(value, selection_start); + const int byte_end = StringUtilities::ConvertCharacterOffsetToByteOffset(value, selection_end); + const bool is_selecting = (byte_start != byte_end); + + cursor_wrap_down = true; + absolute_cursor_index = byte_end; + + bool selection_changed = false; + if (is_selecting) + { + selection_anchor_index = byte_start; + selection_changed = UpdateSelection(true); + } + else + { + selection_changed = UpdateSelection(false); + } + + UpdateCursorPosition(true); + MoveToCursor(); + ShowCursor(true); + + if (selection_changed) + FormatText(); +} + +void WidgetTextInput::GetSelection(int* selection_start, int* selection_end, String* selected_text) const +{ + const String& value = GetValue(); + if (selection_start) + *selection_start = StringUtilities::ConvertByteOffsetToCharacterOffset(value, selection_begin_index); + if (selection_end) + *selection_end = StringUtilities::ConvertByteOffsetToCharacterOffset(value, selection_begin_index + selection_length); + if (selected_text) + *selected_text = value.substr(Math::Min((size_t)selection_begin_index, (size_t)value.size()), (size_t)selection_length); +} + +void WidgetTextInput::SetCompositionRange(int range_start, int range_end) +{ + const String& value = GetValue(); + const int byte_start = StringUtilities::ConvertCharacterOffsetToByteOffset(value, range_start); + const int byte_end = StringUtilities::ConvertCharacterOffsetToByteOffset(value, range_end); + + if (byte_end > byte_start) + { + ime_composition_begin_index = byte_start; + ime_composition_end_index = byte_end; + } + else + { + ime_composition_begin_index = 0; + ime_composition_end_index = 0; + } + + FormatText(); +} + +void WidgetTextInput::GetCompositionRange(int& range_start, int& range_end) const +{ + range_start = ime_composition_begin_index; + range_end = ime_composition_end_index; +} + +void WidgetTextInput::UpdateSelectionColours() +{ + // Determine what the colour of the selected text is. If our 'selection' element has the 'color' + // attribute set, then use that. Otherwise, use the inverse of our own text colour. + Colourb colour; + const Property* colour_property = selection_element->GetLocalProperty(PropertyId::Color); + if (colour_property) + colour = colour_property->Get(); + else + { + colour = parent->GetComputedValues().color(); + colour.red = 255 - colour.red; + colour.green = 255 - colour.green; + colour.blue = 255 - colour.blue; + } + + // Set the computed text colour on the element holding the selected text. + selected_text_element->SetProperty(PropertyId::Color, Property(colour, Unit::COLOUR)); + + // If the 'background-color' property has been set on the 'selection' element, use that as the + // background colour for the selected text. Otherwise, use the inverse of the selected text + // colour. + colour_property = selection_element->GetLocalProperty(PropertyId::BackgroundColor); + if (colour_property) + colour = colour_property->Get(); + else + colour = Colourb(255 - colour.red, 255 - colour.green, 255 - colour.blue, colour.alpha); + + selection_colour = colour.ToPremultiplied(); + + // Color may have changed, so we update the cursor geometry. + GenerateCursor(); +} + +void WidgetTextInput::OnUpdate() +{ + if (cursor_timer > 0) + { + double current_time = Clock::GetElapsedTime(); + cursor_timer -= float(current_time - last_update_time); + last_update_time = current_time; + + while (cursor_timer <= 0) + { + cursor_timer += CURSOR_BLINK_TIME; + cursor_visible = !cursor_visible; + } + + if (parent->IsVisible(true)) + { + if (Context* ctx = parent->GetContext()) + ctx->RequestNextUpdate(cursor_timer); + } + } +} + +void WidgetTextInput::OnResize() +{ + GenerateCursor(); + + Vector2f text_position = parent->GetBox().GetPosition(BoxArea::Content); + text_element->SetOffset(text_position, parent); + selected_text_element->SetOffset(text_position, parent); + + ForceFormattingOnNextLayout(); +} + +void WidgetTextInput::OnRender() +{ + // In some cases, the widget may need formatting even with no document layout step (e.g. like value attribute changes), do it now instead. + if (force_formatting_on_next_layout) + OnLayout(); + + ElementUtilities::SetClippingRegion(text_element); + + Vector2f text_translation = parent->GetAbsoluteOffset() - Vector2f(parent->GetScrollLeft(), parent->GetScrollTop()); + selection_composition_geometry.Render(text_translation); + + if (cursor_visible && selection_length <= 0 && !parent->IsDisabled()) + { + cursor_geometry.Render(text_translation + cursor_position); + } +} + +void WidgetTextInput::OnLayout() +{ + if (force_formatting_on_next_layout) + { + internal_dimensions = parent->GetBox().GetSize(BoxArea::Content); + FormatElement(); + UpdateCursorPosition(true); + force_formatting_on_next_layout = false; + } +} + +Element* WidgetTextInput::GetElement() const +{ + return parent; +} + +TextInputHandler* WidgetTextInput::GetTextInputHandler() const +{ + if (Context* context = parent->GetContext()) + return context->GetTextInputHandler(); + return nullptr; +} + +bool WidgetTextInput::IsFocused() const +{ + return cursor_timer > 0; +} + +void WidgetTextInput::DispatchChangeEvent(bool linebreak) +{ + Dictionary parameters; + parameters["value"] = GetAttributeValue(); + parameters["linebreak"] = Variant(linebreak); + GetElement()->DispatchEvent(EventId::Change, parameters); +} + +void WidgetTextInput::ProcessEvent(Event& event) +{ + if (parent->IsDisabled()) + return; + + switch (event.GetId()) + { + case EventId::Keydown: + { + Input::KeyIdentifier key_identifier = (Input::KeyIdentifier)event.GetParameter("key_identifier", 0); + bool numlock = event.GetParameter("num_lock_key", 0) > 0; + bool shift = event.GetParameter("shift_key", 0) > 0; + bool ctrl = event.GetParameter("ctrl_key", 0) > 0; + bool alt = event.GetParameter("alt_key", 0) > 0; + bool selection_changed = false; + bool out_of_bounds = false; + + switch (key_identifier) + { + // clang-format off + case Input::KI_NUMPAD4: if (numlock) break; //-fallthrough + case Input::KI_LEFT: selection_changed = MoveCursorHorizontal(ctrl ? CursorMovement::PreviousWord : CursorMovement::Left, shift, out_of_bounds); break; + + case Input::KI_NUMPAD6: if (numlock) break; //-fallthrough + case Input::KI_RIGHT: selection_changed = MoveCursorHorizontal(ctrl ? CursorMovement::NextWord : CursorMovement::Right, shift, out_of_bounds); break; + + case Input::KI_NUMPAD8: if (numlock) break; //-fallthrough + case Input::KI_UP: selection_changed = MoveCursorVertical(-1, shift, out_of_bounds); break; + + case Input::KI_NUMPAD2: if (numlock) break; //-fallthrough + case Input::KI_DOWN: selection_changed = MoveCursorVertical(1, shift, out_of_bounds); break; + + case Input::KI_NUMPAD7: if (numlock) break; //-fallthrough + case Input::KI_HOME: selection_changed = MoveCursorHorizontal(ctrl ? CursorMovement::Begin : CursorMovement::BeginLine, shift, out_of_bounds); break; + + case Input::KI_NUMPAD1: if (numlock) break; //-fallthrough + case Input::KI_END: selection_changed = MoveCursorHorizontal(ctrl ? CursorMovement::End : CursorMovement::EndLine, shift, out_of_bounds); break; + + case Input::KI_NUMPAD9: if (numlock) break; //-fallthrough + case Input::KI_PRIOR: selection_changed = MoveCursorVertical(-int(internal_dimensions.y / GetLineHeight()) + 1, shift, out_of_bounds); break; + + case Input::KI_NUMPAD3: if (numlock) break; //-fallthrough + case Input::KI_NEXT: selection_changed = MoveCursorVertical(int(internal_dimensions.y / GetLineHeight()) - 1, shift, out_of_bounds); break; + + case Input::KI_BACK: + { + CursorMovement direction = (ctrl ? CursorMovement::PreviousWord : CursorMovement::Left); + if (DeleteCharacters(direction)) + OnLayout(); + MoveToCursor(); + ShowCursor(true); + } + break; + + case Input::KI_DECIMAL: if (numlock) break; //-fallthrough + case Input::KI_DELETE: + { + CursorMovement direction = (ctrl ? CursorMovement::NextWord : CursorMovement::Right); + if (DeleteCharacters(direction)) + OnLayout(); + MoveToCursor(); + ShowCursor(true); + } + break; + // clang-format on + + case Input::KI_NUMPADENTER: + case Input::KI_RETURN: + { + LineBreak(); + } + break; + + case Input::KI_A: + { + if (ctrl && !alt) + Select(); + } + break; + + case Input::KI_C: + { + if (ctrl && selection_length > 0) + CopySelection(); + } + break; + + case Input::KI_X: + { + if (ctrl && selection_length > 0) + { + CopySelection(); + DeleteSelection(); + DispatchChangeEvent(); + OnLayout(); + MoveToCursor(); + ShowCursor(true); + } + } + break; + + case Input::KI_V: + { + if (ctrl && !alt) + { + String clipboard_text; + GetSystemInterface()->GetClipboardText(clipboard_text); + + if (AddCharacters(clipboard_text)) + OnLayout(); + MoveToCursor(); + ShowCursor(true); + } + } + break; + + // Ignore tabs so input fields can be navigated through with keys. + case Input::KI_TAB: return; + + default: break; + } + + if (shift || ctrl || !out_of_bounds || selection_changed) + event.StopPropagation(); + if (selection_changed) + FormatText(); + } + break; + + case EventId::Textinput: + { + // Only process the text if no modifier keys are pressed. + if (event.GetParameter("ctrl_key", 0) == 0 && event.GetParameter("alt_key", 0) == 0 && event.GetParameter("meta_key", 0) == 0) + { + String text = event.GetParameter("text", String{}); + if (AddCharacters(text)) + OnLayout(); + } + + MoveToCursor(); + ShowCursor(true); + event.StopPropagation(); + } + break; + case EventId::Focus: + { + if (event.GetTargetElement() == parent) + { + parent->SetPseudoClass("focus-visible", true); + if (UpdateSelection(false)) + FormatText(); + ShowCursor(true); + + if (TextInputHandler* handler = GetTextInputHandler()) + { + // Lazily instance the text input context for this widget. + if (!text_input_context) + text_input_context = MakeUnique(handler, this, parent); + + handler->OnActivate(text_input_context.get()); + } + } + } + break; + case EventId::Blur: + { + if (event.GetTargetElement() == parent) + { + if (TextInputHandler* handler = GetTextInputHandler()) + handler->OnDeactivate(text_input_context.get()); + if (ClearSelection()) + FormatText(); + ShowCursor(false); + } + } + break; + case EventId::Drag: + if (cancel_next_drag) + { + // We currently ignore drag events right after a double click. They would need to be handled + // specially by selecting whole words at a time, which is not yet implemented. + break; + } + //-fallthrough + case EventId::Mousedown: + { + if (event.GetTargetElement() == parent) + { + Vector2f mouse_position = Vector2f(event.GetParameter("mouse_x", 0), event.GetParameter("mouse_y", 0)); + mouse_position -= text_element->GetAbsoluteOffset(); + + const int cursor_line_index = CalculateLineIndex(mouse_position.y); + const int cursor_character_index = CalculateCharacterIndex(cursor_line_index, mouse_position.x); + + SetCursorFromRelativeIndices(cursor_line_index, cursor_character_index); + + MoveCursorToCharacterBoundaries(false); + UpdateCursorPosition(true); + + if (UpdateSelection(event == EventId::Drag || event.GetParameter("shift_key", 0) > 0)) + FormatText(); + + if (event == EventId::Drag) + MoveToCursor(); + ShowCursor(true); + cancel_next_drag = false; + } + } + break; + case EventId::Dblclick: + { + if (event.GetTargetElement() == parent) + { + ExpandSelection(); + cancel_next_drag = true; + } + } + break; + + default: break; + } +} + +bool WidgetTextInput::AddCharacters(String string) +{ + SanitizeValue(string); + + if (selection_length > 0) + DeleteSelection(); + + if (max_length >= 0) + ClampValue(string, Math::Max(max_length - GetLength(), 0)); + + if (string.empty()) + return false; + + String value = GetAttributeValue(); + const int attribute_insert_index = DisplayIndexToAttributeIndex(absolute_cursor_index, value); + value.insert(std::min((size_t)attribute_insert_index, value.size()), string); + + const int new_cursor_attribute_index = AttributeIndexToDisplayIndex(attribute_insert_index + (int)string.size(), value); + absolute_cursor_index = new_cursor_attribute_index; + parent->SetAttribute("value", value); + + if (UpdateSelection(false)) + FormatText(); + + DispatchChangeEvent(); + return true; +} + +bool WidgetTextInput::DeleteCharacters(CursorMovement direction) +{ + bool out_of_bounds; + // We set a selection of characters according to direction, and then delete it. + // If we already have a selection, we delete that first. + if (selection_length <= 0) + MoveCursorHorizontal(direction, true, out_of_bounds); + + if (selection_length > 0) + { + DeleteSelection(); + DispatchChangeEvent(); + + return true; + } + + return false; +} + +void WidgetTextInput::CopySelection() +{ + const String& value = GetValue(); + const String snippet = value.substr(Math::Min((size_t)selection_begin_index, (size_t)value.size()), (size_t)selection_length); + GetSystemInterface()->SetClipboardText(snippet); +} + +bool WidgetTextInput::MoveCursorHorizontal(CursorMovement movement, bool select, bool& out_of_bounds) +{ + out_of_bounds = false; + + const String& value = GetValue(); + + int cursor_line_index = 0, cursor_character_index = 0; + GetRelativeCursorIndices(cursor_line_index, cursor_character_index); + + // By default the cursor wraps down when located on softbreaks. This may be overridden by setting the cursor using relative indices. + cursor_wrap_down = true; + + // Whether to seek forward or back to align to utf8 boundaries later. + bool seek_forward = false; + + switch (movement) + { + case CursorMovement::Begin: absolute_cursor_index = 0; break; + case CursorMovement::BeginLine: SetCursorFromRelativeIndices(cursor_line_index, 0); break; + case CursorMovement::PreviousWord: + { + // First skip whitespace, then skip all characters of the same class as the first non-whitespace character. + CharacterClass skip_character_class = CharacterClass::Whitespace; + const char* p_rend = value.data(); + const char* p_rbegin = p_rend + absolute_cursor_index; + const char* p = p_rbegin - 1; + for (; p > p_rend; --p) + { + const CharacterClass character_class = GetCharacterClass(*p); + if (character_class != skip_character_class) + { + if (skip_character_class == CharacterClass::Whitespace) + skip_character_class = character_class; + else + break; + } + } + if (p != p_rend) + ++p; + absolute_cursor_index += int(p - p_rbegin); + } + break; + case CursorMovement::Left: + if (!select && selection_length > 0) + absolute_cursor_index = selection_begin_index; + else + absolute_cursor_index -= 1; + break; + case CursorMovement::Right: + seek_forward = true; + if (!select && selection_length > 0) + absolute_cursor_index = selection_begin_index + selection_length; + else + absolute_cursor_index += 1; + break; + case CursorMovement::NextWord: + { + // First skip all characters of the same class as the first character, then skip any whitespace. + CharacterClass skip_character_class = CharacterClass::Undefined; + const char* p_begin = value.data() + absolute_cursor_index; + const char* p_end = value.data() + value.size(); + const char* p = p_begin; + for (; p < p_end; ++p) + { + const CharacterClass character_class = GetCharacterClass(*p); + if (skip_character_class == CharacterClass::Undefined) + skip_character_class = character_class; + + if (character_class != skip_character_class) + { + if (character_class == CharacterClass::Whitespace) + skip_character_class = CharacterClass::Whitespace; + else + break; + } + } + absolute_cursor_index += int(p - p_begin); + } + break; + case CursorMovement::EndLine: SetCursorFromRelativeIndices(cursor_line_index, lines[cursor_line_index].editable_length); break; + case CursorMovement::End: absolute_cursor_index = (int)GetValue().size(); break; + } + + const int unclamped_absolute_cursor_index = absolute_cursor_index; + absolute_cursor_index = Math::Clamp(absolute_cursor_index, 0, (int)GetValue().size()); + out_of_bounds = (unclamped_absolute_cursor_index != absolute_cursor_index); + + MoveCursorToCharacterBoundaries(seek_forward); + UpdateCursorPosition(true); + + bool selection_changed = UpdateSelection(select); + MoveToCursor(); + ShowCursor(true); + + return selection_changed; +} + +bool WidgetTextInput::MoveCursorVertical(int distance, bool select, bool& out_of_bounds) +{ + int cursor_line_index = 0, cursor_character_index = 0; + out_of_bounds = false; + GetRelativeCursorIndices(cursor_line_index, cursor_character_index); + + cursor_line_index += distance; + + if (cursor_line_index < 0) + { + out_of_bounds = true; + cursor_line_index = 0; + cursor_character_index = 0; + } + else if (cursor_line_index >= (int)lines.size()) + { + out_of_bounds = true; + cursor_line_index = (int)lines.size() - 1; + cursor_character_index = (int)lines[cursor_line_index].editable_length; + } + else + cursor_character_index = CalculateCharacterIndex(cursor_line_index, ideal_cursor_position); + + SetCursorFromRelativeIndices(cursor_line_index, cursor_character_index); + + MoveCursorToCharacterBoundaries(false); + UpdateCursorPosition(false); + + bool selection_changed = UpdateSelection(select); + MoveToCursor(); + ShowCursor(true); + + return selection_changed; +} + +void WidgetTextInput::MoveCursorToCharacterBoundaries(bool forward) +{ + const String& value = GetValue(); + absolute_cursor_index = Math::Min(absolute_cursor_index, (int)value.size()); + + const char* p_begin = value.data(); + const char* p_end = p_begin + value.size(); + const char* p_cursor = p_begin + absolute_cursor_index; + const char* p = p_cursor; + + if (forward) + p = StringUtilities::SeekForwardUTF8(p_cursor, p_end); + else + p = StringUtilities::SeekBackwardUTF8(p_cursor, p_begin); + + if (p != p_cursor) + absolute_cursor_index += int(p - p_cursor); +} + +void WidgetTextInput::ExpandSelection() +{ + const String& value = GetValue(); + const char* const p_begin = value.data(); + const char* const p_end = p_begin + value.size(); + const char* const p_index = p_begin + absolute_cursor_index; + + // The first character encountered defines the character class to expand. + CharacterClass expanding_character_class = CharacterClass::Undefined; + + auto character_is_wrong_type = [&expanding_character_class](const char* p) -> bool { + const CharacterClass character_class = GetCharacterClass(*p); + if (expanding_character_class == CharacterClass::Undefined) + expanding_character_class = character_class; + else if (character_class != expanding_character_class) + return true; + return false; + }; + + auto search_left = [&]() -> const char* { + const char* p = p_index; + for (; p > p_begin; p--) + if (character_is_wrong_type(p - 1)) + break; + return p; + }; + auto search_right = [&]() -> const char* { + const char* p = p_index; + for (; p < p_end; p++) + if (character_is_wrong_type(p)) + break; + return p; + }; + + const char* p_left = p_index; + const char* p_right = p_index; + + if (ideal_cursor_position_to_the_right_of_cursor) + { + p_right = search_right(); + p_left = search_left(); + } + else + { + p_left = search_left(); + p_right = search_right(); + } + + cursor_wrap_down = true; + absolute_cursor_index -= int(p_index - p_left); + MoveCursorToCharacterBoundaries(false); + bool selection_changed = UpdateSelection(false); + + absolute_cursor_index += int(p_right - p_left); + MoveCursorToCharacterBoundaries(true); + selection_changed |= UpdateSelection(true); + + if (selection_changed) + FormatText(); + + UpdateCursorPosition(true); +} + +const String& WidgetTextInput::GetValue() const +{ + static const String empty_value; + if (parent->IsPseudoClassSet("placeholder-shown")) + return empty_value; + + return text_element->GetText(); +} + +void WidgetTextInput::SetValueOrPlaceholder(const String& value, const String& placeholder) +{ + const bool showing_placeholder = value.empty() && !placeholder.empty(); + if (showing_placeholder) + { + absolute_cursor_index = 0; + UpdateSelection(false); + } + + parent->SetPseudoClass("placeholder-shown", showing_placeholder); + text_element->SetText(showing_placeholder ? placeholder : value); + + ForceFormattingOnNextLayout(); +} + +void WidgetTextInput::GetRelativeCursorIndices(int& out_cursor_line_index, int& out_cursor_character_index) const +{ + int line_begin = 0; + + for (size_t i = 0; i < lines.size(); i++) + { + const int cursor_relative_line_end = absolute_cursor_index - (line_begin + lines[i].editable_length); + + // Test if the absolute index is located on the editable part of this line, otherwise we wrap down to the next line. We may have additional + // characters after the editable length, such as the newline character '\n'. We also wrap down if the cursor is located to the right of any + // such characters. + if (cursor_relative_line_end <= 0) + { + const bool soft_wrapped_line = (lines[i].editable_length == lines[i].size); + + // If we are located exactly on a soft break (due to word wrapping) then the cursor wrap state determines whether or not we wrap down. + if (cursor_relative_line_end == 0 && soft_wrapped_line && cursor_wrap_down && (int)i + 1 < (int)lines.size()) + { + out_cursor_line_index = (int)i + 1; + out_cursor_character_index = 0; + } + else + { + out_cursor_line_index = (int)i; + out_cursor_character_index = Math::Max(absolute_cursor_index - line_begin, 0); + } + return; + } + + line_begin += lines[i].size; + } + + // We shouldn't ever get here; this means we actually couldn't find where the absolute cursor said it was. So we'll + // just set the relative cursors to the very end of the text field. + out_cursor_line_index = (int)lines.size() - 1; + out_cursor_character_index = lines[out_cursor_line_index].editable_length; +} + +void WidgetTextInput::SetCursorFromRelativeIndices(int cursor_line_index, int cursor_character_index) +{ + RMLUI_ASSERT(cursor_line_index < (int)lines.size()) + + absolute_cursor_index = cursor_character_index; + + for (int i = 0; i < cursor_line_index; i++) + absolute_cursor_index += lines[i].size; + + // Only wrap down if we're not located at the end of the line. + cursor_wrap_down = (cursor_character_index < lines[cursor_line_index].editable_length); +} + +int WidgetTextInput::CalculateLineIndex(float position) const +{ + int line_index = int(position / GetLineHeight()); + return Math::Clamp(line_index, 0, (int)(lines.size() - 1)); +} + +float WidgetTextInput::GetAlignmentSpecificTextOffset(const Line& line) const +{ + // Callback to avoid expensive calculation in the cases where it is not needed. + auto RemainingWidth = [this](StringView editable_line_string) { + const float total_width = (float)ElementUtilities::GetStringWidth(text_element, editable_line_string); + return GetAvailableWidth() - total_width; + }; + + const String& value = text_element->GetText(); + StringView editable_line_string(value, line.value_offset, line.editable_length); + + switch (parent->GetComputedValues().text_align()) + { + case Style::TextAlign::Left: return 0; + case Style::TextAlign::Right: + { + // For right alignment with soft-wrapped newlines, remove up to a single space to align the last word to the right edge. + const bool is_last_line = (line.value_offset + line.size == (int)value.size()); + const bool is_soft_wrapped = (!is_last_line && line.editable_length == line.size); + if (is_soft_wrapped && !editable_line_string.empty() && *(editable_line_string.end() - 1) == ' ') + { + editable_line_string = StringView(editable_line_string.begin(), editable_line_string.end() - 1); + } + return Math::Max(0.0f, RemainingWidth(editable_line_string)); + } + case Style::TextAlign::Center: return Math::Max(0.0f, 0.5f * RemainingWidth(editable_line_string)); + case Style::TextAlign::Justify: return 0; + } + return 0; +} + +int WidgetTextInput::CalculateCharacterIndex(int line_index, float position) +{ + int prev_offset = 0; + float prev_line_width = 0; + + ideal_cursor_position_to_the_right_of_cursor = true; + + const Line& line = lines[line_index]; + const char* p_begin = GetValue().data() + line.value_offset; + const char* p_end = p_begin + line.editable_length; + + position -= GetAlignmentSpecificTextOffset(line); + + for (auto it = StringIteratorU8(p_begin, p_begin, p_end); it;) + { + ++it; + const int offset = (int)it.offset(); + + const float line_width = (float)ElementUtilities::GetStringWidth(text_element, StringView(p_begin, p_begin + offset)); + if (line_width > position) + { + if (position - prev_line_width < line_width - position) + { + return prev_offset; + } + else + { + ideal_cursor_position_to_the_right_of_cursor = false; + return offset; + } + } + + prev_line_width = line_width; + prev_offset = offset; + } + + return prev_offset; +} + +void WidgetTextInput::ShowCursor(bool show) +{ + if (show) + { + cursor_visible = true; + cursor_timer = CURSOR_BLINK_TIME; + last_update_time = GetSystemInterface()->GetElapsedTime(); + } + else + { + cursor_visible = false; + cursor_timer = -1; + last_update_time = 0; + } + + SetKeyboardActive(show); +} + +void WidgetTextInput::MoveToCursor() +{ + const float minimum_scroll_top = Math::Min((cursor_position.y + cursor_size.y) - GetAvailableHeight(), cursor_position.y); + if (parent->GetScrollTop() < minimum_scroll_top) + parent->SetScrollTop(minimum_scroll_top); + else if (parent->GetScrollTop() > cursor_position.y) + parent->SetScrollTop(cursor_position.y); + + const bool word_wrap = parent->GetComputedValues().white_space() == Style::WhiteSpace::Prewrap; + float minimum_scroll_left = Math::Min((cursor_position.x + cursor_size.x) - GetAvailableWidth(), cursor_position.x); + if (word_wrap) + parent->SetScrollLeft(0.f); + else if (parent->GetScrollLeft() < minimum_scroll_left) + parent->SetScrollLeft(minimum_scroll_left); + else if (parent->GetScrollLeft() > cursor_position.x) + parent->SetScrollLeft(cursor_position.x); +} + +void WidgetTextInput::FormatElement() +{ + using namespace Style; + ElementScroll* scroll = parent->GetElementScroll(); + float width = parent->GetBox().GetSize(BoxArea::Padding).x; + + const Overflow x_overflow_property = parent->GetComputedValues().overflow_x(); + const Overflow y_overflow_property = parent->GetComputedValues().overflow_y(); + const bool word_wrap = (parent->GetComputedValues().white_space() == WhiteSpace::Prewrap); + + if (x_overflow_property == Overflow::Scroll) + scroll->EnableScrollbar(ElementScroll::HORIZONTAL, width); + else + scroll->DisableScrollbar(ElementScroll::HORIZONTAL); + + if (y_overflow_property == Overflow::Scroll) + scroll->EnableScrollbar(ElementScroll::VERTICAL, width); + else + scroll->DisableScrollbar(ElementScroll::VERTICAL); + + // If the formatting produces scrollbars we need to format again later, this constraint enables early exit for the first formatting round. + const float formatting_height_constraint = (y_overflow_property == Overflow::Auto ? GetAvailableHeight() : FLT_MAX); + + // Format the text and determine its total area. + Vector2f content_area = FormatText(formatting_height_constraint); + + // If we're set to automatically generate horizontal scrollbars, check for that now. + if (!word_wrap && x_overflow_property == Overflow::Auto && content_area.x > GetAvailableWidth() + OVERFLOW_TOLERANCE) + scroll->EnableScrollbar(ElementScroll::HORIZONTAL, width); + + // Now check for vertical overflow. If we do turn on the scrollbar, this will cause a reflow. + if (y_overflow_property == Overflow::Auto && content_area.y > GetAvailableHeight() + OVERFLOW_TOLERANCE) + { + scroll->EnableScrollbar(ElementScroll::VERTICAL, width); + content_area = FormatText(); + + if (!word_wrap && x_overflow_property == Overflow::Auto && content_area.x > GetAvailableWidth() + OVERFLOW_TOLERANCE) + scroll->EnableScrollbar(ElementScroll::HORIZONTAL, width); + } + + // For text elements, make the content and padding on all sides reachable by scrolling. + const Vector2f padding_size = parent->GetBox().GetFrameSize(BoxArea::Padding); + parent->SetScrollableOverflowRectangle(content_area + padding_size, true); + scroll->FormatScrollbars(); +} + +Vector2f WidgetTextInput::FormatText(float height_constraint) +{ + Vector2f content_area(0, 0); + + const FontFaceHandle font_handle = parent->GetFontFaceHandle(); + if (!font_handle) + return content_area; + + const FontMetrics& font_metrics = GetFontEngineInterface()->GetFontMetrics(font_handle); + + // Clear the old lines, and all the lines in the text elements. + lines.clear(); + text_element->ClearLines(); + selected_text_element->ClearLines(); + + // Determine the line-height of the text element. + const float line_height = GetLineHeight(); + + const float half_leading = 0.5f * (line_height - (font_metrics.ascent + font_metrics.descent)); + const float top_to_baseline = font_metrics.ascent + half_leading; + + // When the selection contains endlines, we expand the selection area by this width. + const int endline_font_width = int(0.4f * parent->GetComputedValues().font_size()); + + const float available_width = GetAvailableWidth(); + int line_begin = 0; + Vector2f line_position = {0, top_to_baseline}; + bool last_line = false; + + float max_selection_right_edge = 0; + + // Clear the selection background and IME composition geometry, and get the vertices and indices so the new geometry can be generated. + Mesh selection_composition_mesh = selection_composition_geometry.Release(Geometry::ReleaseMode::ClearMesh); + + // Keep generating lines until all the text content is placed. + do + { + const float maximum_line_width = available_width - cursor_size.x; + if (maximum_line_width <= 0.f) + { + lines.push_back(Line{}); + break; + } + + Line line = {}; + line.value_offset = line_begin; + float line_width; + String line_content; + + // Generate the next line. + last_line = text_element->GenerateLine(line_content, line.size, line_width, line_begin, maximum_line_width, 0, false, false, false); + + // Check if the editable length needs to be truncated to dodge a trailing endline. + line.editable_length = (int)line_content.size(); + if (!line_content.empty() && line_content.back() == '\n') + line.editable_length -= 1; + + // Include all spaces at the end of this line, if they were not included due to soft-wrapping in `GenerateLine`. + // This helps prevent sudden shifts when whitespace wraps down to the next line. + { + const String& text = text_element->GetText(); + size_t i_space_begin = size_t(line_begin + line.editable_length); + size_t i_space_end = Math::Min(text.find_first_not_of(' ', i_space_begin), text.size()); + size_t count = i_space_end - i_space_begin; + if (count > 0) + { + line_content.append(count, ' '); + line_width += ElementUtilities::GetStringWidth(text_element, " ") * (int)count; + line.editable_length += (int)count; + line.size += (int)count; + // Consume the hard wrap if we have one on this line, so that it doesn't make its own, empty line. + if (text[i_space_end] == '\n') + line.size += 1; + // If the spaces extend all the way to the end, we have consumed all the lines. + if (i_space_end == text.size()) + last_line = true; + } + } + + // Now that we have the string of characters appearing on the new line, we split it into + // three parts; the unselected text appearing before any selected text on the line, the + // selected text on the line, and any unselected text after the selection. + StringView pre_selection, selection, post_selection; + GetLineSelection(pre_selection, selection, post_selection, line_content, line_begin); + + // The pre-selected text is placed, if there is any (if the selection starts on or before + // the beginning of this line, then this will be empty). + if (!pre_selection.empty()) + { + const int width = ElementUtilities::GetStringWidth(text_element, pre_selection); + text_element->AddLine(line_position + Vector2f{GetAlignmentSpecificTextOffset(line), 0}, String(pre_selection)); + line_position.x += width; + } + + // Return the extra kerning that would result in joining two strings. + auto GetKerningBetween = [this](StringView left, StringView right) -> float { + if (left.empty() || right.empty()) + return 0.0f; + // We could join the whole string, and compare the result of the joined width to the individual widths of each string. Instead, we take + // the two neighboring characters from each string and compare the string width with and without kerning, which should be much faster. + const Character left_back = StringUtilities::ToCharacter(StringUtilities::SeekBackwardUTF8(left.end() - 1, left.begin()), left.end()); + const StringView right_front_u8 = StringView(right.begin(), StringUtilities::SeekForwardUTF8(right.begin() + 1, right.end())); + const int width_kerning = ElementUtilities::GetStringWidth(text_element, right_front_u8, left_back); + const int width_no_kerning = ElementUtilities::GetStringWidth(text_element, right_front_u8, Character::Null); + return float(width_kerning - width_no_kerning); + }; + + // If there is any selected text on this line, place it in the selected text element and + // generate the geometry for its background. + if (!selection.empty()) + { + line_position.x += GetKerningBetween(pre_selection, selection); + + const int selection_width = ElementUtilities::GetStringWidth(selected_text_element, selection); + const bool selection_contains_endline = (selection_begin_index + selection_length > line_begin + line.editable_length); + const Vector2f selection_size = {float(selection_width + (selection_contains_endline ? endline_font_width : 0)), line_height}; + const Vector2f aligned_position = line_position + Vector2f{GetAlignmentSpecificTextOffset(line), 0}; + + MeshUtilities::GenerateQuad(selection_composition_mesh, aligned_position - Vector2f(0, top_to_baseline), selection_size, + selection_colour); + selected_text_element->AddLine(aligned_position, String(selection)); + + max_selection_right_edge = Math::Max(max_selection_right_edge, aligned_position.x + selection_size.x); + line_position.x += selection_width; + } + + // If there is any unselected text after the selection on this line, place it in the + // standard text element after the selected text. + if (!post_selection.empty()) + { + line_position.x += GetKerningBetween(selection, post_selection); + text_element->AddLine(line_position + Vector2f{GetAlignmentSpecificTextOffset(line), 0}, String(post_selection)); + } + + // We fetch the IME composition on the new line to highlight it. + StringView ime_pre_composition, ime_composition; + GetLineIMEComposition(ime_pre_composition, ime_composition, line_content, line_begin); + + // If there is any IME composition string on the line, create a segment for its underline. + if (!ime_composition.empty()) + { + const bool composition_contains_endline = (ime_composition_end_index > line_begin + line.editable_length); + const int composition_width = ElementUtilities::GetStringWidth(text_element, ime_composition); + const Vector2f composition_position = { + float(ElementUtilities::GetStringWidth(text_element, ime_pre_composition)) + GetAlignmentSpecificTextOffset(line), + line_position.y - top_to_baseline + line_height - COMPOSITION_UNDERLINE_WIDTH, + }; + Vector2f line_size = {float(composition_width + (composition_contains_endline ? endline_font_width : 0)), COMPOSITION_UNDERLINE_WIDTH}; + + MeshUtilities::GenerateLine(selection_composition_mesh, composition_position, line_size, + parent->GetComputedValues().color().ToPremultiplied()); + } + + // Update variables for the next line. + line_begin += line.size; + line_position.x = 0; + line_position.y += line_height; + + // Grow the content area width-wise if this line is the longest so far, and push the height out. + content_area.x = Math::Max(content_area.x, line_width + cursor_size.x); + content_area.y = line_position.y - top_to_baseline; + + // Finally, push the new line into our array of lines. + lines.push_back(std::move(line)); + + } while (!last_line && content_area.y <= height_constraint + OVERFLOW_TOLERANCE); + + // Clamp the cursor to a valid range. + absolute_cursor_index = Math::Min(absolute_cursor_index, (int)GetValue().size()); + + selection_composition_geometry = parent->GetRenderManager()->MakeGeometry(std::move(selection_composition_mesh)); + + // Overflow is automatically caught by any text overflowing the content area. However, sometimes it is possible that + // the selection box extends beyond the text and outside the content area. This can even overflow the element + // itself. In particular, when the selection includes newlines near the right edge. We don't want the selection box + // to take part in the scrollable region of the element, which would be one way to ensure that it is always clipped. + // Instead, we here detect such possible overflow manually and force the element to clip. This will clip any parts + // of the selection box that is overflowing. Maybe in the future we'll have a better way to specify ink overflow and + // have that automatically clipped. + const bool new_ink_overflow = (max_selection_right_edge > available_width + parent->GetBox().GetEdge(BoxArea::Padding, BoxEdge::Right)); + if (new_ink_overflow != ink_overflow) + { + ink_overflow = new_ink_overflow; + parent->SetProperty(PropertyId::Clip, Property(ink_overflow ? Style::Clip::Type::Always : Style::Clip::Type::Auto)); + } + + return content_area; +} + +void WidgetTextInput::GenerateCursor() +{ + cursor_size.x = Math::Round(ElementUtilities::GetDensityIndependentPixelRatio(text_element)); + cursor_size.y = GetLineHeight(); + + Colourb color = parent->GetComputedValues().color(); + + if (const Property* property = parent->GetProperty(PropertyId::CaretColor)) + { + if (property->unit == Unit::COLOUR) + color = property->Get(); + } + + Mesh mesh = cursor_geometry.Release(Geometry::ReleaseMode::ClearMesh); + MeshUtilities::GenerateQuad(mesh, Vector2f(0, 0), cursor_size, color.ToPremultiplied()); + cursor_geometry = parent->GetRenderManager()->MakeGeometry(std::move(mesh)); +} + +void WidgetTextInput::ForceFormattingOnNextLayout() +{ + force_formatting_on_next_layout = true; +} + +void WidgetTextInput::UpdateCursorPosition(bool update_ideal_cursor_position) +{ + if (text_element->GetFontFaceHandle() == 0 || lines.empty()) + return; + + int cursor_line_index = 0, cursor_character_index = 0; + GetRelativeCursorIndices(cursor_line_index, cursor_character_index); + + const auto& line = lines[cursor_line_index]; + const int string_width_pre_cursor = + ElementUtilities::GetStringWidth(text_element, StringView(text_element->GetText(), line.value_offset, cursor_character_index)); + + auto AlignmentOffsetForPlaceholder = [this]() { + switch (parent->GetComputedValues().text_align()) + { + case Style::TextAlign::Left: return 0.f; + case Style::TextAlign::Justify: return 0.f; + case Style::TextAlign::Right: return Math::Max(0.0f, GetAvailableWidth()); + case Style::TextAlign::Center: return Math::Max(0.0f, 0.5f * GetAvailableWidth()); + } + return 0.f; + }; + + float alignment_offset; + if (parent->IsPseudoClassSet("placeholder-shown")) + alignment_offset = AlignmentOffsetForPlaceholder(); + else + alignment_offset = GetAlignmentSpecificTextOffset(line); + + cursor_position = { + (float)string_width_pre_cursor + alignment_offset, + (float)cursor_line_index * GetLineHeight(), + }; + + const bool word_wrap = parent->GetComputedValues().white_space() == Style::WhiteSpace::Prewrap; + if (word_wrap) + cursor_position.x = Math::Min(cursor_position.x, GetAvailableWidth() - cursor_size.x); + + if (update_ideal_cursor_position) + ideal_cursor_position = cursor_position.x; +} + +bool WidgetTextInput::UpdateSelection(bool selecting) +{ + bool selection_changed = false; + if (!selecting) + { + selection_anchor_index = selection_begin_index = absolute_cursor_index; + selection_changed = ClearSelection(); + } + else + { + int new_begin_index; + int new_end_index; + + if (absolute_cursor_index > selection_anchor_index) + { + new_begin_index = selection_anchor_index; + new_end_index = absolute_cursor_index; + } + else + { + new_begin_index = absolute_cursor_index; + new_end_index = selection_anchor_index; + } + + if (new_begin_index != selection_begin_index || new_end_index - new_begin_index != selection_length) + { + selection_begin_index = new_begin_index; + selection_length = new_end_index - new_begin_index; + + selection_changed = true; + } + } + + return selection_changed; +} + +bool WidgetTextInput::ClearSelection() +{ + if (selection_length > 0) + { + selection_length = 0; + return true; + } + return false; +} + +void WidgetTextInput::DeleteSelection() +{ + if (selection_length > 0) + { + String new_value = GetAttributeValue(); + const int selection_begin_index_attribute = DisplayIndexToAttributeIndex(selection_begin_index, new_value); + const int selection_end_index_attribute = DisplayIndexToAttributeIndex(selection_begin_index + selection_length, new_value); + RMLUI_ASSERT(selection_end_index_attribute >= selection_begin_index_attribute); + + const size_t selection_begin = std::min((size_t)selection_begin_index_attribute, (size_t)new_value.size()); + const size_t attribute_selection_length = size_t(selection_end_index_attribute - selection_begin_index_attribute); + new_value.erase(selection_begin, (size_t)attribute_selection_length); + + // Move the cursor to the beginning of the old selection. + absolute_cursor_index = selection_begin_index; + + GetElement()->SetAttribute("value", new_value); + + // Erase our record of the selection. + if (UpdateSelection(false)) + FormatText(); + } +} + +void WidgetTextInput::GetLineSelection(StringView& pre_selection, StringView& selection, StringView& post_selection, const String& line, + int line_begin) const +{ + const int selection_end = selection_begin_index + selection_length; + if (selection_length <= 0 || selection_end < line_begin || selection_begin_index > line_begin + (int)line.size()) + { + pre_selection = line; + return; + } + + const int line_length = (int)line.size(); + using namespace Math; + + // Split the line up into its three parts, depending on the size and placement of the selection. + pre_selection = StringView(line, 0, Max(0, selection_begin_index - line_begin)); + selection = StringView(line, Clamp(selection_begin_index - line_begin, 0, line_length), + Max(0, selection_length + Min(0, selection_begin_index - line_begin))); + post_selection = StringView(line, Clamp(selection_end - line_begin, 0, line_length)); +} + +void WidgetTextInput::GetLineIMEComposition(StringView& pre_composition, StringView& ime_composition, const String& line, int line_begin) const +{ + const int composition_length = ime_composition_end_index - ime_composition_begin_index; + + // Check if the line has any text in the IME composition range at all. + if (composition_length <= 0 || ime_composition_end_index < line_begin || ime_composition_begin_index > line_begin + (int)line.size()) + { + pre_composition = line; + return; + } + + const int line_length = (int)line.size(); + + pre_composition = StringView(line, 0, Math::Max(0, ime_composition_begin_index - line_begin)); + ime_composition = StringView(line, Math::Clamp(ime_composition_begin_index - line_begin, 0, line_length), + Math::Max(0, composition_length + Math::Min(0, ime_composition_begin_index - line_begin))); +} + +void WidgetTextInput::SetKeyboardActive(bool active) +{ + if (!keyboard_showed && !active) + return; + + SystemInterface* system = GetSystemInterface(); + if (!system) + return; + + if (active) + { + // Activate the keyboard and submit the cursor position and line height to enable clients to adjust the input method editor (IME). + const Vector2f element_offset = parent->GetAbsoluteOffset() - Vector2f{parent->GetScrollLeft(), parent->GetScrollTop()}; + const Vector2f absolute_cursor_position = element_offset + cursor_position; + system->ActivateKeyboard(absolute_cursor_position, cursor_size.y); + } + else + { + system->DeactivateKeyboard(); + } +} + +float WidgetTextInput::GetLineHeight() const +{ + // This is rounded to improve the navigation experience in a multi-line text field. If it's not rounded then lines + // may move a single pixel for every line, which looks a bit jaggedy when moving up and down. + return Math::Round(parent->GetLineHeight()); +} + +float WidgetTextInput::GetAvailableWidth() const +{ + return parent->GetClientWidth() - parent->GetBox().GetFrameSize(BoxArea::Padding).x; +} + +float WidgetTextInput::GetAvailableHeight() const +{ + return parent->GetClientHeight() - parent->GetBox().GetFrameSize(BoxArea::Padding).y; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/WidgetTextInput.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/WidgetTextInput.h new file mode 100644 index 00000000..ff04868c --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/WidgetTextInput.h @@ -0,0 +1,300 @@ +#pragma once + +#include "../../../Include/RmlUi/Core/EventListener.h" +#include "../../../Include/RmlUi/Core/Geometry.h" +#include "../../../Include/RmlUi/Core/Vertex.h" +#include + +namespace Rml { + +class ElementText; +class ElementFormControl; +class TextInputHandler; +class WidgetTextInputContext; + +/** + An abstract widget for editing and navigating around a text field. + */ + +class WidgetTextInput : public EventListener { +public: + WidgetTextInput(ElementFormControl* parent); + virtual ~WidgetTextInput(); + + /// Handle changes to the parent element's value. + /// @param[in] value The new value set on the text field. + /// @note The attribute is considered the source of truth for the value. + /// @note The value will be sanitized and synchronized back to the element's value attribute. + void OnValueAttributeChanged(String value); + /// Returns the underlying text from the element's value attribute. + String GetAttributeValue() const; + + /// Sets the placeholder text of the text field. + /// @param[in] placeholder The new placeholder text. + /// @note The attribute is considered the source of truth for the placeholder. + void OnPlaceholderAttributeChanged(const String& placeholder); + /// Returns the placeholder text of the text field. + String GetAttributePlaceholder() const; + + /// Sets the maximum length (in characters) of this text field. + /// @param[in] max_length The new maximum length of the text field. A number lower than zero will mean infinite characters. + void SetMaxLength(int max_length); + /// Returns the maximum length (in characters) of this text field. + /// @return The maximum number of characters allowed in this text field. + int GetMaxLength() const; + /// Returns the current length (in characters) of this text field. + int GetLength() const; + + /// Selects all text. + void Select(); + /// Selects the text in the given character range. + /// @param[in] selection_start The first character to be selected. + /// @param[in] selection_end The first character *after* the selection. + void SetSelectionRange(int selection_start, int selection_end); + /// Retrieves the selection range and text. + /// @param[out] selection_start The first character selected. + /// @param[out] selection_end The first character *after* the selection. + /// @param[out] selected_text The selected text. + void GetSelection(int* selection_start, int* selection_end, String* selected_text) const; + + /// Sets visual feedback used for the IME composition in the range. + /// @param[in] range_start The first character to be selected. + /// @param[in] range_end The first character *after* the selection. + void SetCompositionRange(int range_start, int range_end); + /// Obtains the IME composition byte range relative to the current value. + void GetCompositionRange(int& range_start, int& range_end) const; + + /// Update the colours of the selected text. + void UpdateSelectionColours(); + /// Generates the text cursor. + void GenerateCursor(); + /// Force text formatting on the next layout update. + void ForceFormattingOnNextLayout(); + + /// Returns the used line height. + float GetLineHeight() const; + + /// Updates the cursor, if necessary. + void OnUpdate(); + /// Renders the cursor, if it is visible. + void OnRender(); + /// Formats the widget's internal content. + void OnLayout(); + /// Called when the parent element's size changes. + void OnResize(); + +protected: + enum class CursorMovement { Begin = -4, BeginLine = -3, PreviousWord = -2, Left = -1, Right = 1, NextWord = 2, EndLine = 3, End = 4 }; + + /// Processes the "keydown" and "textinput" event to write to the input field, and the "focus" and + /// "blur" to set the state of the cursor. + void ProcessEvent(Event& event) override; + + /// Adds new characters to the string at the cursor position. + /// @param[in] string The characters to add. + /// @return True if at least one character was successfully added, false otherwise. + bool AddCharacters(String string); + /// Deletes characters from the string. + /// @param[in] direction Movement of cursor for deletion. + /// @return True if a character was deleted, false otherwise. + bool DeleteCharacters(CursorMovement direction); + + /// Removes any invalid characters from the string. + virtual void SanitizeValue(String& value) = 0; + /// Transforms the displayed value of the text box, typically used for password fields. + /// @note Only use this for transforming characters, do not modify the length of the string. + virtual void TransformValue(String& value); + /// Converts a display index to an attribute index. + /// @param[in] display_index Absolute index into the displayed value (see GetValue). + /// @param[in] attribute_value The attribute value to be considered. + /// @return Absolute index into the attribute value (see GetAttributeValue). + /// @note All indices stored in this class refer to the displayed value, unless otherwise specified. The display and + /// attribute indices are typically identical, but may differ when the transformed value (see TransformValue) has + /// modified the contents to be displayed. + virtual int DisplayIndexToAttributeIndex(int display_index, const String& attribute_value); + /// Converts an attribute index to a display index. + /// @param[in] attribute_index Absolute index into the attribute value (see GetAttributeValue). + /// @param[in] attribute_value The attribute value to be considered. + /// @return Absolute index into the displayed value (see GetValue). + virtual int AttributeIndexToDisplayIndex(int attribute_index, const String& attribute_value); + /// Called when the user pressed enter. + virtual void LineBreak() = 0; + + /// Gets the parent element containing the widget. + Element* GetElement() const; + + /// Obtains the text input handler of the parent element's context. + TextInputHandler* GetTextInputHandler() const; + + /// Returns true if the text input element is currently focused. + bool IsFocused() const; + + /// Dispatches a change event to the widget's element. + void DispatchChangeEvent(bool linebreak = false); + +private: + struct Line { + // Offset into the text field's value. + int value_offset; + // The size of the contents of the line (including the trailing endline, if that terminated the line). + int size; + // The length of the editable characters on the line (excluding any trailing endline). + int editable_length; + }; + + /// Returns the displayed value of the text field. + /// @note For password fields this would only return the displayed asterisks '****', while the attribute value below contains the underlying text. + const String& GetValue() const; + /// Sets the text element either to the value or placeholder, and updates the placeholder pseudo class. + void SetValueOrPlaceholder(const String& value, const String& placeholder); + + /// Moves the cursor along the current line. + /// @param[in] movement Cursor movement operation. + /// @param[in] select True if the movement will also move the selection cursor, false if not. + /// @param[out] out_of_bounds Set to true if the resulting line position is out of bounds, false if not. + /// @return True if selection was changed. + bool MoveCursorHorizontal(CursorMovement movement, bool select, bool& out_of_bounds); + /// Moves the cursor up and down the text field. + /// @param[in] distance How far to move the cursor. + /// @param[in] select True if the movement will also move the selection cursor, false if not. + /// @param[out] out_of_bounds Set to true if the resulting line position is out of bounds, false if not. + /// @return True if selection was changed. + bool MoveCursorVertical(int distance, bool select, bool& out_of_bounds); + // Move the cursor to utf-8 boundaries, in case it was moved into the middle of a multibyte character. + /// @param[in] forward True to seek forward, else back. + void MoveCursorToCharacterBoundaries(bool forward); + // Expands the cursor, selecting the current word or nearby whitespace. + void ExpandSelection(); + + /// Returns the relative indices from the current absolute index. + void GetRelativeCursorIndices(int& out_cursor_line_index, int& out_cursor_character_index) const; + /// Sets the absolute cursor index from the given relative indices. + void SetCursorFromRelativeIndices(int line_index, int character_index); + + /// Calculates the line index under a specific vertical position. + /// @param[in] position The position to query. + /// @return The index of the line under the mouse cursor. + int CalculateLineIndex(float position) const; + /// Calculates the character index along a line under a specific horizontal position. + /// @param[in] line_index The line to query. + /// @param[in] position The position to query. + /// @return The index of the character under the mouse cursor. + int CalculateCharacterIndex(int line_index, float position); + + /// Shows or hides the cursor. + /// @param[in] show True to show the cursor, false to hide it. + void ShowCursor(bool show); + + /// Scroll the view to make the cursor visible. + void MoveToCursor(); + + /// Formats the element, laying out the text and inserting scrollbars as appropriate. + void FormatElement(); + /// Formats the input element's text field. + /// @param[in] height_constraint Abort formatting when the formatted size grows larger than this height. + /// @return The content area of the element. + Vector2f FormatText(float height_constraint = FLT_MAX); + + /// Updates the position to render the cursor. + /// @param[in] update_ideal_cursor_position Generally should be true on horizontal movement and false on vertical movement. + void UpdateCursorPosition(bool update_ideal_cursor_position); + + /// Expand or shrink the text selection to the position of the cursor. + /// @param[in] selecting True if the new position of the cursor should expand / contract the selection area, false if it should only set the + /// anchor for future selections. + /// @return True if selection was changed. + bool UpdateSelection(bool selecting); + /// Removes the selection of text. + /// @return True if selection was changed. + bool ClearSelection(); + /// Deletes all selected text and removes the selection. + void DeleteSelection(); + /// Copies the selection (if any) to the clipboard. + void CopySelection(); + + /// Split one line of text into three parts, based on the current selection. + /// @param[out] pre_selection The section of unselected text before any selected text on the line. + /// @param[out] selection The section of selected text on the line. + /// @param[out] post_selection The section of unselected text after any selected text on the line. If there is no selection on the line, then this + /// will be empty. + /// @param[in] line The text making up the line. + /// @param[in] line_begin The absolute index at the beginning of the line. + /// @lifetime The returned string views are tied to the lifetime of the line's data. + void GetLineSelection(StringView& pre_selection, StringView& selection, StringView& post_selection, const String& line, int line_begin) const; + /// Fetch the IME composition range on the line. + /// @param[out] pre_composition The section of text before the IME composition string on the line. + /// @param[out] ime_composition The IME composition string on the line. + /// @param[in] line The text making up the line. + /// @param[in] line_begin The absolute index at the beginning of the line. + void GetLineIMEComposition(StringView& pre_composition, StringView& ime_composition, const String& line, int line_begin) const; + + /// Returns the offset that aligns the contents of the line according to the 'text-align' property. + float GetAlignmentSpecificTextOffset(const Line& line) const; + + /// Returns the width available for the text contents without overflowing, that is, the content area subtracted by any scrollbar. + float GetAvailableWidth() const; + /// Returns the height available for the text contents without overflowing, that is, the content area subtracted by any scrollbar. + float GetAvailableHeight() const; + + ElementFormControl* parent; + + ElementText* text_element; + ElementText* selected_text_element; + Vector2f internal_dimensions; + + using LineList = Vector; + LineList lines; + + // Length in number of characters. + int max_length; + + // -- All indices are in bytes: Should always be moved along UTF-8 start bytes. -- + + // Absolute cursor index. Byte index into the text field's value. + int absolute_cursor_index; + // When the cursor is located at the very end of a word-wrapped line there are two valid positions for the same absolute index: at the end of the + // line and at the beginning of the next line. This state determines which of these lines the cursor is placed on visually. + bool cursor_wrap_down; + + bool ideal_cursor_position_to_the_right_of_cursor; + bool cancel_next_drag; + bool force_formatting_on_next_layout; + + // Selection. The start and end indices of the selection are in absolute coordinates. + Element* selection_element; + int selection_anchor_index; + int selection_begin_index; + int selection_length; + + // The colour of the background of selected text. + ColourbPremultiplied selection_colour; + // The selection background. + Geometry selection_composition_geometry; + + // IME composition range. The start and end indices are in absolute coordinates. + int ime_composition_begin_index; + int ime_composition_end_index; + + // The IME context for this widget. + UniquePtr text_input_context; + + // Cursor visibility and timings. + float cursor_timer; + bool cursor_visible; + bool keyboard_showed; + /// Activate or deactivate keyboard (for touchscreen devices) + /// @param[in] active True if need activate keyboard, false if need deactivate. + void SetKeyboardActive(bool active); + + bool ink_overflow; + + double last_update_time; + + // The cursor geometry. + float ideal_cursor_position; + Vector2f cursor_position; + Vector2f cursor_size; + Geometry cursor_geometry; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/WidgetTextInputMultiLine.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/WidgetTextInputMultiLine.cpp new file mode 100644 index 00000000..aa5b322b --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/WidgetTextInputMultiLine.cpp @@ -0,0 +1,19 @@ +#include "WidgetTextInputMultiLine.h" +#include "../../../Include/RmlUi/Core/Dictionary.h" +#include "../../../Include/RmlUi/Core/ElementText.h" +#include + +namespace Rml { + +WidgetTextInputMultiLine::WidgetTextInputMultiLine(ElementFormControl* parent) : WidgetTextInput(parent) {} + +WidgetTextInputMultiLine::~WidgetTextInputMultiLine() {} + +void WidgetTextInputMultiLine::SanitizeValue(String& value) +{ + value.erase(std::remove_if(value.begin(), value.end(), [](char c) { return c == '\r' || c == '\t'; }), value.end()); +} + +void WidgetTextInputMultiLine::LineBreak() {} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/WidgetTextInputMultiLine.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/WidgetTextInputMultiLine.h new file mode 100644 index 00000000..f50ffd94 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/WidgetTextInputMultiLine.h @@ -0,0 +1,23 @@ +#pragma once + +#include "WidgetTextInput.h" + +namespace Rml { + +/** + A specialisation of the text input widget for multi-line text fields. + */ + +class WidgetTextInputMultiLine : public WidgetTextInput { +public: + WidgetTextInputMultiLine(ElementFormControl* parent); + virtual ~WidgetTextInputMultiLine(); + +protected: + /// Removes any invalid characters from the string. + void SanitizeValue(String& value) override; + /// Called when the user pressed enter. + void LineBreak() override; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/WidgetTextInputSingleLine.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/WidgetTextInputSingleLine.cpp new file mode 100644 index 00000000..deb6e2cb --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/WidgetTextInputSingleLine.cpp @@ -0,0 +1,25 @@ +#include "WidgetTextInputSingleLine.h" +#include "../../../Include/RmlUi/Core/Dictionary.h" +#include "../../../Include/RmlUi/Core/ElementText.h" +#include "../../../Include/RmlUi/Core/Elements/ElementFormControl.h" +#include + +namespace Rml { + +WidgetTextInputSingleLine::WidgetTextInputSingleLine(ElementFormControl* parent) : WidgetTextInput(parent) +{ + // Single line text controls should clip to the content area, see visual test: text_input_overflow.rml + parent->SetClipArea(BoxArea::Content); +} + +void WidgetTextInputSingleLine::SanitizeValue(String& value) +{ + value.erase(std::remove_if(value.begin(), value.end(), [](char c) { return c == '\r' || c == '\n' || c == '\t'; }), value.end()); +} + +void WidgetTextInputSingleLine::LineBreak() +{ + DispatchChangeEvent(true); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/WidgetTextInputSingleLine.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/WidgetTextInputSingleLine.h new file mode 100644 index 00000000..48c013f5 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/WidgetTextInputSingleLine.h @@ -0,0 +1,22 @@ +#pragma once + +#include "WidgetTextInput.h" + +namespace Rml { + +/** + A specialisation of the text input widget for single-line input fields. + */ + +class WidgetTextInputSingleLine : public WidgetTextInput { +public: + WidgetTextInputSingleLine(ElementFormControl* parent); + +protected: + /// Removes any invalid characters from the string. + void SanitizeValue(String& value) override; + /// Called when the user pressed enter. + void LineBreak() override; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/WidgetTextInputSingleLinePassword.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/WidgetTextInputSingleLinePassword.cpp new file mode 100644 index 00000000..0fcd9245 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/WidgetTextInputSingleLinePassword.cpp @@ -0,0 +1,26 @@ +#include "WidgetTextInputSingleLinePassword.h" +#include "../../../Include/RmlUi/Core/ElementText.h" + +namespace Rml { + +WidgetTextInputSingleLinePassword::WidgetTextInputSingleLinePassword(ElementFormControl* parent) : WidgetTextInputSingleLine(parent) {} + +void WidgetTextInputSingleLinePassword::TransformValue(String& value) +{ + const size_t character_length = StringUtilities::LengthUTF8(value); + value.replace(0, value.length(), character_length, '*'); +} + +int WidgetTextInputSingleLinePassword::DisplayIndexToAttributeIndex(int display_index, const String& attribute_value) +{ + // Transforming from the attribute value to the display value (above) essentially strips away all continuation + // bytes. Thus, here we effectively count them back in up to the offset. + return StringUtilities::ConvertCharacterOffsetToByteOffset(attribute_value, display_index); +} + +int WidgetTextInputSingleLinePassword::AttributeIndexToDisplayIndex(int attribute_index, const String& attribute_value) +{ + return (int)StringUtilities::LengthUTF8(StringView(attribute_value, 0, (size_t)attribute_index)); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/WidgetTextInputSingleLinePassword.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/WidgetTextInputSingleLinePassword.h new file mode 100644 index 00000000..d8971eb2 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/WidgetTextInputSingleLinePassword.h @@ -0,0 +1,19 @@ +#pragma once + +#include "WidgetTextInputSingleLine.h" + +namespace Rml { + +class WidgetTextInputSingleLinePassword : public WidgetTextInputSingleLine { +public: + WidgetTextInputSingleLinePassword(ElementFormControl* parent); + +protected: + void TransformValue(String& value) override; + + int DisplayIndexToAttributeIndex(int display_index, const String& attribute_value) override; + + int AttributeIndexToDisplayIndex(int attribute_index, const String& attribute_value) override; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/XMLNodeHandlerSelect.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/XMLNodeHandlerSelect.cpp new file mode 100644 index 00000000..979c2ff4 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/XMLNodeHandlerSelect.cpp @@ -0,0 +1,57 @@ +#include "XMLNodeHandlerSelect.h" +#include "../../../Include/RmlUi/Core/Elements/ElementFormControlSelect.h" +#include "../../../Include/RmlUi/Core/Factory.h" +#include "../../../Include/RmlUi/Core/Log.h" +#include "../../../Include/RmlUi/Core/XMLParser.h" + +namespace Rml { + +XMLNodeHandlerSelect::XMLNodeHandlerSelect() {} + +XMLNodeHandlerSelect::~XMLNodeHandlerSelect() {} + +Element* XMLNodeHandlerSelect::ElementStart(XMLParser* parser, const String& name, const XMLAttributes& attributes) +{ + RMLUI_ASSERT(name == "select" || name == "option"); + + if (name == "select") + { + // Call this node handler for all children + parser->PushHandler("select"); + + // Attempt to instance the tabset + ElementPtr element = Factory::InstanceElement(parser->GetParseFrame()->element, name, name, attributes); + ElementFormControlSelect* select_element = rmlui_dynamic_cast(element.get()); + if (!select_element) + { + Log::Message(Log::LT_ERROR, "Instancer failed to create element for tag %s.", name.c_str()); + return nullptr; + } + + // Add the Select element into the document + Element* result = parser->GetParseFrame()->element->AppendChild(std::move(element)); + + return result; + } + else if (name == "option") + { + // Call default element handler for all children. + parser->PushDefaultHandler(); + + ElementPtr option_element = Factory::InstanceElement(parser->GetParseFrame()->element, name, name, attributes); + Element* result = nullptr; + + ElementFormControlSelect* select_element = rmlui_dynamic_cast(parser->GetParseFrame()->element); + if (select_element) + { + result = option_element.get(); + select_element->Add(std::move(option_element)); + } + + return result; + } + + return nullptr; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/XMLNodeHandlerSelect.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/XMLNodeHandlerSelect.h new file mode 100644 index 00000000..a97d1bde --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/XMLNodeHandlerSelect.h @@ -0,0 +1,20 @@ +#pragma once + +#include "../XMLNodeHandlerDefault.h" + +namespace Rml { + +/** + XML node handler for processing the select and option tags. + */ + +class XMLNodeHandlerSelect : public XMLNodeHandlerDefault { +public: + XMLNodeHandlerSelect(); + virtual ~XMLNodeHandlerSelect(); + + /// Called when a new element start is opened + Element* ElementStart(XMLParser* parser, const String& name, const XMLAttributes& attributes) override; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/XMLNodeHandlerTabSet.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/XMLNodeHandlerTabSet.cpp new file mode 100644 index 00000000..b55e42b6 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/XMLNodeHandlerTabSet.cpp @@ -0,0 +1,103 @@ +#include "XMLNodeHandlerTabSet.h" +#include "../../../Include/RmlUi/Core/Elements/ElementTabSet.h" +#include "../../../Include/RmlUi/Core/Factory.h" +#include "../../../Include/RmlUi/Core/Log.h" +#include "../../../Include/RmlUi/Core/XMLParser.h" + +namespace Rml { + +XMLNodeHandlerTabSet::XMLNodeHandlerTabSet() {} + +XMLNodeHandlerTabSet::~XMLNodeHandlerTabSet() {} + +Element* XMLNodeHandlerTabSet::ElementStart(XMLParser* parser, const String& name, const XMLAttributes& attributes) +{ + RMLUI_ASSERT(name == "tabset" || name == "tabs" || name == "tab" || name == "panels" || name == "panel"); + + if (name == "tabset") + { + // Call this node handler for all children + parser->PushHandler("tabset"); + + // Attempt to instance the tabset + ElementPtr element = Factory::InstanceElement(parser->GetParseFrame()->element, name, name, attributes); + ElementTabSet* tabset = rmlui_dynamic_cast(element.get()); + if (!tabset) + { + Log::Message(Log::LT_ERROR, "Instancer failed to create element for tag %s.", name.c_str()); + return nullptr; + } + + // Add the TabSet into the document + Element* result = parser->GetParseFrame()->element->AppendChild(std::move(element)); + + return result; + } + else if (name == "tab") + { + // Call default element handler for all children. + parser->PushDefaultHandler(); + + ElementPtr tab_element = Factory::InstanceElement(parser->GetParseFrame()->element, "*", "tab", attributes); + Element* result = nullptr; + + ElementTabSet* tabset = rmlui_dynamic_cast(parser->GetParseFrame()->element); + if (tabset) + { + result = tab_element.get(); + tabset->SetTab(-1, std::move(tab_element)); + } + + return result; + } + else if (name == "panel") + { + // Call default element handler for all children. + parser->PushDefaultHandler(); + + ElementPtr panel_element = Factory::InstanceElement(parser->GetParseFrame()->element, "*", "panel", attributes); + Element* result = nullptr; + + ElementTabSet* tabset = rmlui_dynamic_cast(parser->GetParseFrame()->element); + if (tabset) + { + result = panel_element.get(); + tabset->SetPanel(-1, std::move(panel_element)); + } + + return result; + } + else if (name == "tabs" || name == "panels") + { + // Use the element handler to add the tabs and panels elements to the the tabset (this allows users to + // style them nicely), but don't return the new element, as we still want the tabset to be the top of the + // parser's node stack. + + Element* parent = parser->GetParseFrame()->element; + + ElementPtr element = Factory::InstanceElement(parent, name, name, attributes); + if (!element) + { + Log::Message(Log::LT_ERROR, "Instancer failed to create element for tag %s.", name.c_str()); + return nullptr; + } + + parent->AppendChild(std::move(element)); + + return nullptr; + } + + return nullptr; +} + +bool XMLNodeHandlerTabSet::ElementEnd(XMLParser* /*parser*/, const String& /*name*/) +{ + return true; +} + +bool XMLNodeHandlerTabSet::ElementData(XMLParser* parser, const String& data, XMLDataType /*type*/) +{ + return Factory::InstanceElementText(parser->GetParseFrame()->element, data); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/XMLNodeHandlerTabSet.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/XMLNodeHandlerTabSet.h new file mode 100644 index 00000000..edaf5c59 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/XMLNodeHandlerTabSet.h @@ -0,0 +1,24 @@ +#pragma once + +#include "../../../Include/RmlUi/Core/XMLNodeHandler.h" + +namespace Rml { + +/** + XML node handler for processing the tabset tags. + */ + +class XMLNodeHandlerTabSet : public XMLNodeHandler { +public: + XMLNodeHandlerTabSet(); + virtual ~XMLNodeHandlerTabSet(); + + /// Called when a new element start is opened + Element* ElementStart(XMLParser* parser, const String& name, const XMLAttributes& attributes) override; + /// Called when an element is closed + bool ElementEnd(XMLParser* parser, const String& name) override; + /// Called for element data + bool ElementData(XMLParser* parser, const String& data, XMLDataType type) override; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/XMLNodeHandlerTextArea.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/XMLNodeHandlerTextArea.cpp new file mode 100644 index 00000000..ec18c708 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/XMLNodeHandlerTextArea.cpp @@ -0,0 +1,51 @@ +#include "XMLNodeHandlerTextArea.h" +#include "../../../Include/RmlUi/Core/Core.h" +#include "../../../Include/RmlUi/Core/Elements/ElementFormControlTextArea.h" +#include "../../../Include/RmlUi/Core/Factory.h" +#include "../../../Include/RmlUi/Core/SystemInterface.h" +#include "../../../Include/RmlUi/Core/XMLParser.h" + +namespace Rml { + +XMLNodeHandlerTextArea::XMLNodeHandlerTextArea() {} + +XMLNodeHandlerTextArea::~XMLNodeHandlerTextArea() {} + +Element* XMLNodeHandlerTextArea::ElementStart(XMLParser* parser, const String& name, const XMLAttributes& attributes) +{ + ElementFormControlTextArea* text_area = rmlui_dynamic_cast(parser->GetParseFrame()->element); + if (!text_area) + { + ElementPtr new_element = Factory::InstanceElement(parser->GetParseFrame()->element, name, name, attributes); + if (!new_element) + return nullptr; + + Element* result = parser->GetParseFrame()->element->AppendChild(std::move(new_element)); + + return result; + } + + return nullptr; +} + +bool XMLNodeHandlerTextArea::ElementEnd(XMLParser* /*parser*/, const String& /*name*/) +{ + return true; +} + +bool XMLNodeHandlerTextArea::ElementData(XMLParser* parser, const String& data, XMLDataType /*type*/) +{ + ElementFormControlTextArea* text_area = rmlui_dynamic_cast(parser->GetParseFrame()->element); + if (text_area != nullptr) + { + // Do any necessary translation. + String translated_data; + GetSystemInterface()->TranslateString(translated_data, data); + + text_area->SetValue(translated_data); + } + + return true; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/XMLNodeHandlerTextArea.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/XMLNodeHandlerTextArea.h new file mode 100644 index 00000000..d8ac5f71 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Elements/XMLNodeHandlerTextArea.h @@ -0,0 +1,24 @@ +#pragma once + +#include "../../../Include/RmlUi/Core/XMLNodeHandler.h" + +namespace Rml { + +/** + Node handler that processes the contents of the textarea tag. + */ + +class XMLNodeHandlerTextArea : public XMLNodeHandler { +public: + XMLNodeHandlerTextArea(); + virtual ~XMLNodeHandlerTextArea(); + + /// Called when a new element is opened. + Element* ElementStart(XMLParser* parser, const String& name, const XMLAttributes& attributes) override; + /// Called when an element is closed. + bool ElementEnd(XMLParser* parser, const String& name) override; + /// Called for element data. + bool ElementData(XMLParser* parser, const String& data, XMLDataType type) override; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Event.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Event.cpp new file mode 100644 index 00000000..2f47757c --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Event.cpp @@ -0,0 +1,157 @@ +#include "../../Include/RmlUi/Core/Event.h" +#include "../../Include/RmlUi/Core/Element.h" +#include "../../Include/RmlUi/Core/EventInstancer.h" + +namespace Rml { + +Event::Event() {} + +Event::Event(Element* _target_element, EventId id, const String& type, const Dictionary& _parameters, bool interruptible) : + parameters(_parameters), target_element(_target_element), type(type), id(id), interruptible(interruptible) +{ + const Variant* mouse_x = GetIf(parameters, "mouse_x"); + const Variant* mouse_y = GetIf(parameters, "mouse_y"); + if (mouse_x && mouse_y) + { + has_mouse_position = true; + mouse_x->GetInto(mouse_screen_position.x); + mouse_y->GetInto(mouse_screen_position.y); + } +} + +Event::~Event() {} + +void Event::SetCurrentElement(Element* element) +{ + current_element = element; + if (has_mouse_position) + { + ProjectMouse(element); + } +} + +Element* Event::GetCurrentElement() const +{ + return current_element; +} + +Element* Event::GetTargetElement() const +{ + return target_element; +} + +const String& Event::GetType() const +{ + return type; +} + +bool Event::operator==(const String& _type) const +{ + return type == _type; +} + +bool Event::operator==(EventId _id) const +{ + return id == _id; +} + +void Event::SetPhase(EventPhase _phase) +{ + phase = _phase; +} + +EventPhase Event::GetPhase() const +{ + return phase; +} + +bool Event::IsPropagating() const +{ + return !interrupted; +} + +void Event::StopPropagation() +{ + if (interruptible) + { + interrupted = true; + } +} + +bool Event::IsImmediatePropagating() const +{ + return !interrupted_immediate; +} + +bool Event::IsInterruptible() const +{ + return interruptible; +} + +void Event::StopImmediatePropagation() +{ + if (interruptible) + { + interrupted_immediate = true; + interrupted = true; + } +} + +const Dictionary& Event::GetParameters() const +{ + return parameters; +} + +Vector2f Event::GetUnprojectedMouseScreenPos() const +{ + return mouse_screen_position; +} + +void Event::Release() +{ + if (instancer) + instancer->ReleaseEvent(this); + else + Log::Message(Log::LT_WARNING, "Leak detected: Event %s not instanced via RmlUi Factory. Unable to release.", type.c_str()); +} + +EventId Event::GetId() const +{ + return id; +} + +void Event::ProjectMouse(Element* element) +{ + if (!element) + { + parameters["mouse_x"] = mouse_screen_position.x; + parameters["mouse_y"] = mouse_screen_position.y; + return; + } + + // Only need to project mouse position if element has a transform state + if (element->GetTransformState()) + { + // Project mouse from parent (previous 'mouse_x/y' property) to child (element) + Variant* mouse_x = GetIf(parameters, "mouse_x"); + Variant* mouse_y = GetIf(parameters, "mouse_y"); + if (!mouse_x || !mouse_y) + { + RMLUI_ERROR; + return; + } + + Vector2f projected_position = mouse_screen_position; + + // Not sure how best to handle the case where the projection fails. + if (element->Project(projected_position)) + { + *mouse_x = projected_position.x; + *mouse_y = projected_position.y; + } + else + StopPropagation(); + } +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/EventDispatcher.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/EventDispatcher.cpp new file mode 100644 index 00000000..c621de50 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/EventDispatcher.cpp @@ -0,0 +1,257 @@ +#include "EventDispatcher.h" +#include "../../Include/RmlUi/Core/Element.h" +#include "../../Include/RmlUi/Core/Event.h" +#include "../../Include/RmlUi/Core/EventListener.h" +#include "../../Include/RmlUi/Core/Factory.h" +#include "EventSpecification.h" +#include +#include + +namespace Rml { + +bool operator==(EventListenerEntry a, EventListenerEntry b) +{ + return a.id == b.id && a.in_capture_phase == b.in_capture_phase && a.listener == b.listener; +} +bool operator!=(EventListenerEntry a, EventListenerEntry b) +{ + return !(a == b); +} + +struct CompareId { + bool operator()(EventListenerEntry a, EventListenerEntry b) const { return a.id < b.id; } +}; +struct CompareIdPhase { + bool operator()(EventListenerEntry a, EventListenerEntry b) const + { + return std::tie(a.id, a.in_capture_phase) < std::tie(b.id, b.in_capture_phase); + } +}; + +EventDispatcher::EventDispatcher(Element* _element) : element(_element) {} + +EventDispatcher::~EventDispatcher() +{ + // Detach from all event dispatchers + for (const auto& event : listeners) + event.listener->OnDetach(element); +} + +void EventDispatcher::AttachEvent(const EventId id, EventListener* listener, const bool in_capture_phase) +{ + const EventListenerEntry entry(id, listener, in_capture_phase); + + // The entries are sorted by (id,phase). Find the bounds of this sort, then find the entry. + const auto range = std::equal_range(listeners.cbegin(), listeners.cend(), entry, CompareIdPhase()); + const auto matching_entry_it = std::find(range.first, range.second, entry); + if (matching_entry_it == range.second) + { + listeners.emplace(range.second, entry); + listener->OnAttach(element); + } +} + +void EventDispatcher::DetachEvent(const EventId id, EventListener* listener, const bool in_capture_phase) +{ + const auto listenerIt = std::find(listeners.cbegin(), listeners.cend(), EventListenerEntry(id, listener, in_capture_phase)); + if (listenerIt != listeners.cend()) + { + listeners.erase(listenerIt); + listener->OnDetach(element); + } +} + +void EventDispatcher::DetachAllEvents() +{ + for (const auto& event : listeners) + event.listener->OnDetach(element); + + listeners.clear(); + + for (int i = 0; i < element->GetNumChildren(true); ++i) + element->GetChild(i)->GetEventDispatcher()->DetachAllEvents(); +} + +/* + CollectedListener + + When dispatching an event we collect all possible event listeners to execute. + They are stored in observer pointers, so that we can safely check if they have been destroyed since the previous listener execution. +*/ +struct CollectedListener { + CollectedListener(Element* _element, EventListener* _listener, int dom_distance_from_target, bool in_capture_phase) : + element(_element->GetObserverPtr()), listener(_listener->GetObserverPtr()) + { + sort = dom_distance_from_target * (in_capture_phase ? -1 : 1); + } + + // The sort value is determined by the distance of the element to the target element in the DOM. + // Capture phase is given negative values. + int sort = 0; + + ObserverPtr element; + ObserverPtr listener; + + // Default actions are returned by EventPhase::None. + EventPhase GetPhase() const { return sort < 0 ? EventPhase::Capture : (sort == 0 ? EventPhase::Target : EventPhase::Bubble); } + + bool operator<(const CollectedListener& other) const { return sort < other.sort; } +}; + +bool EventDispatcher::DispatchEvent(Element* target_element, const EventId id, const String& type, const Dictionary& parameters, + const bool interruptible, const bool bubbles, const DefaultActionPhase default_action_phase) +{ + RMLUI_ASSERTMSG(!((int)default_action_phase & (int)EventPhase::Capture), + "We assume here that the default action phases cannot include capture phase."); + + Vector listeners; + Vector> default_action_elements; + + const EventPhase phases_to_execute = EventPhase((int)EventPhase::Capture | (int)EventPhase::Target | (bubbles ? (int)EventPhase::Bubble : 0)); + + // Walk the DOM tree from target to root, collecting all possible listeners and elements with default actions in the process. + int dom_distance_from_target = 0; + Element* walk_element = target_element; + while (walk_element) + { + EventDispatcher* dispatcher = walk_element->GetEventDispatcher(); + dispatcher->CollectListeners(dom_distance_from_target, id, phases_to_execute, listeners); + + if (dom_distance_from_target == 0) + { + if ((int)default_action_phase & (int)EventPhase::Target) + default_action_elements.push_back(walk_element->GetObserverPtr()); + } + else if ((int)default_action_phase & (int)EventPhase::Bubble) + { + default_action_elements.push_back(walk_element->GetObserverPtr()); + } + + walk_element = walk_element->GetParentNode(); + dom_distance_from_target += 1; + } + + if (listeners.empty() && default_action_elements.empty()) + return true; + + // Use stable_sort so that the order of the listeners in a given element is maintained. + std::stable_sort(listeners.begin(), listeners.end()); + + // Instance event + EventPtr event = Factory::InstanceEvent(target_element, id, type, parameters, interruptible); + if (!event) + return false; + + auto previous_sort_value = std::numeric_limits::max(); + + // Process the event in each listener. + for (const auto& listener_desc : listeners) + { + Element* element = listener_desc.element.get(); + EventListener* listener = listener_desc.listener.get(); + + if (listener_desc.sort != previous_sort_value) + { + // New sort values represent a new level in the DOM, thus, set the new element and possibly new phase. + if (!event->IsPropagating()) + break; + event->SetCurrentElement(element); + event->SetPhase(listener_desc.GetPhase()); + previous_sort_value = listener_desc.sort; + } + + // We only submit the event if both the current element and listener are still alive. + if (element && listener) + { + listener->ProcessEvent(*event); + } + + if (!event->IsImmediatePropagating()) + break; + } + + // Process the default actions. + for (auto& element_ptr : default_action_elements) + { + if (!event->IsPropagating()) + break; + + if (Element* element = element_ptr.get()) + { + event->SetCurrentElement(element); + event->SetPhase(element == target_element ? EventPhase::Target : EventPhase::Bubble); + element->ProcessDefaultAction(*event); + } + } + + bool propagating = event->IsPropagating(); + + return propagating; +} + +void EventDispatcher::CollectListeners(int dom_distance_from_target, const EventId event_id, const EventPhase event_executes_in_phases, + Vector& collect_listeners) +{ + // Find all the entries with a matching id, given that listeners are sorted by id first. + Listeners::iterator begin, end; + std::tie(begin, end) = std::equal_range(listeners.begin(), listeners.end(), EventListenerEntry(event_id, nullptr, false), CompareId()); + + const bool in_target_phase = (dom_distance_from_target == 0); + + if (in_target_phase) + { + // Listeners always attach to target phase, but make sure the event can actually execute in target phase. + if ((int)event_executes_in_phases & (int)EventPhase::Target) + { + for (auto it = begin; it != end; ++it) + collect_listeners.emplace_back(element, it->listener, dom_distance_from_target, false); + } + } + else + { + // Iterate through all the listeners and collect those matching the event execution phase. + for (auto it = begin; it != end; ++it) + { + // Listeners will either attach to capture or bubble phase, make sure the event can execute in the same phase. + const EventPhase listener_executes_in_phase = (it->in_capture_phase ? EventPhase::Capture : EventPhase::Bubble); + if ((int)event_executes_in_phases & (int)listener_executes_in_phase) + collect_listeners.emplace_back(element, it->listener, dom_distance_from_target, it->in_capture_phase); + } + } +} + +String EventDispatcher::ToString() const +{ + String result; + + if (listeners.empty()) + return result; + + auto add_to_result = [&result](EventId id, int count) { + const EventSpecification& specification = EventSpecificationInterface::Get(id); + result += CreateString("%s (%d), ", specification.type.c_str(), count); + }; + + EventId previous_id = listeners[0].id; + int count = 0; + for (const auto& listener : listeners) + { + if (listener.id != previous_id) + { + add_to_result(previous_id, count); + previous_id = listener.id; + count = 0; + } + count++; + } + + if (count > 0) + add_to_result(previous_id, count); + + if (result.size() > 2) + result.resize(result.size() - 2); + + return result; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/EventDispatcher.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/EventDispatcher.h new file mode 100644 index 00000000..d887b51b --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/EventDispatcher.h @@ -0,0 +1,79 @@ +#pragma once + +#include "../../Include/RmlUi/Core/Event.h" +#include "../../Include/RmlUi/Core/Types.h" + +namespace Rml { + +class Element; +class EventListener; +struct CollectedListener; + +struct EventListenerEntry { + EventListenerEntry(const EventId id, EventListener* listener, const bool in_capture_phase) : + id(id), in_capture_phase(in_capture_phase), listener(listener) + {} + + EventId id; + bool in_capture_phase; + EventListener* listener; +}; + +/** + The Event Dispatcher manages a list of event listeners and triggers the events via EventHandlers + whenever requested. +*/ + +class EventDispatcher { +public: + /// Constructor + /// @param element Element this dispatcher acts on + EventDispatcher(Element* element); + + /// Destructor + ~EventDispatcher(); + + /// Attaches a new listener to the specified event name. + /// @param[in] type Type of the event to attach to. + /// @param[in] event_listener The event listener to be notified when the event fires. + /// @param[in] in_capture_phase Should the listener be notified in the capture phase. + void AttachEvent(EventId id, EventListener* event_listener, bool in_capture_phase); + + /// Detaches a listener from the specified event name + /// @param[in] type Type of the event to attach to + /// @param[in] event_listener The event listener to be notified when the event fires + /// @param[in] in_capture_phase Should the listener be notified in the capture phase + void DetachEvent(EventId id, EventListener* listener, bool in_capture_phase); + + /// Detaches all events from this dispatcher and all child dispatchers. + void DetachAllEvents(); + + /// Dispatches the specified event. + /// @param[in] target_element The element to target + /// @param[in] id The id of the event + /// @param[in] type The type of the event + /// @param[in] parameters The event parameters + /// @param[in] interruptible Can the event propagation be stopped + /// @param[in] bubbles True if the event should execute the bubble phase + /// @param[in] default_action_phase The phases to execute default actions in + /// @return True if the event was not consumed (ie, was prevented from propagating by an element), false if it was. + static bool DispatchEvent(Element* target_element, EventId id, const String& type, const Dictionary& parameters, bool interruptible, bool bubbles, + DefaultActionPhase default_action_phase); + + /// Returns event types with number of listeners for debugging. + /// @return Summary of attached listeners. + String ToString() const; + +private: + Element* element; + + // Listeners are sorted first by (id, phase) and then by the order in which the listener was inserted. + // All listeners added are unique. + typedef Vector Listeners; + Listeners listeners; + + // Collect all the listeners from this dispatcher that are allowed to execute given the input arguments. + void CollectListeners(int dom_distance_from_target, EventId event_id, EventPhase phases_to_execute, Vector& collect_listeners); +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/EventInstancer.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/EventInstancer.cpp new file mode 100644 index 00000000..65dbaa58 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/EventInstancer.cpp @@ -0,0 +1,7 @@ +#include "../../Include/RmlUi/Core/EventInstancer.h" + +namespace Rml { + +EventInstancer::~EventInstancer() {} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/EventInstancerDefault.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/EventInstancerDefault.cpp new file mode 100644 index 00000000..cdfd9864 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/EventInstancerDefault.cpp @@ -0,0 +1,25 @@ +#include "EventInstancerDefault.h" +#include "../../Include/RmlUi/Core/Event.h" + +namespace Rml { + +EventInstancerDefault::EventInstancerDefault() {} + +EventInstancerDefault::~EventInstancerDefault() {} + +EventPtr EventInstancerDefault::InstanceEvent(Element* target, EventId id, const String& type, const Dictionary& parameters, bool interruptible) +{ + return EventPtr(new Event(target, id, type, parameters, interruptible)); +} + +void EventInstancerDefault::ReleaseEvent(Event* event) +{ + delete event; +} + +void EventInstancerDefault::Release() +{ + delete this; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/EventInstancerDefault.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/EventInstancerDefault.h new file mode 100644 index 00000000..480e3a42 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/EventInstancerDefault.h @@ -0,0 +1,33 @@ +#pragma once + +#include "../../Include/RmlUi/Core/EventInstancer.h" +#include "../../Include/RmlUi/Core/Types.h" + +namespace Rml { + +/** + Default instancer for instancing events. + */ + +class EventInstancerDefault : public EventInstancer { +public: + EventInstancerDefault(); + virtual ~EventInstancerDefault(); + + /// Instance and event object + /// @param[in] target Target element of this event. + /// @param[in] id ID of this event. + /// @param[in] type Name of this event type. + /// @param[in] parameters Additional parameters for this event. + /// @param[in] interruptible If the event propagation can be stopped. + EventPtr InstanceEvent(Element* target, EventId id, const String& type, const Dictionary& parameters, bool interruptible) override; + + /// Releases an event instanced by this instancer. + /// @param[in] event The event to release. + void ReleaseEvent(Event* event) override; + + /// Releases this event instancer. + void Release() override; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/EventListenerInstancer.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/EventListenerInstancer.cpp new file mode 100644 index 00000000..fe690def --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/EventListenerInstancer.cpp @@ -0,0 +1,7 @@ +#include "../../Include/RmlUi/Core/EventListenerInstancer.h" + +namespace Rml { + +EventListenerInstancer::~EventListenerInstancer() {} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/EventSpecification.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/EventSpecification.cpp new file mode 100644 index 00000000..295415bc --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/EventSpecification.cpp @@ -0,0 +1,166 @@ +#include "EventSpecification.h" +#include "../../Include/RmlUi/Core/ID.h" +#include "ControlledLifetimeResource.h" + +namespace Rml { + +struct EventSpecificationData { + // An EventId is an index into the specifications vector, must be listed in the same order as the EventId values. + Vector specifications = { + // clang-format off + // id type interruptible bubbles default_action + {EventId::Invalid , "invalid" , false , false , DefaultActionPhase::None}, + {EventId::Mousedown , "mousedown" , true , true , DefaultActionPhase::TargetAndBubble}, + {EventId::Mousescroll , "mousescroll" , true , true , DefaultActionPhase::None}, + {EventId::Mouseover , "mouseover" , true , true , DefaultActionPhase::Target}, + {EventId::Mouseout , "mouseout" , true , true , DefaultActionPhase::Target}, + {EventId::Focus , "focus" , false , false , DefaultActionPhase::Target}, + {EventId::Blur , "blur" , false , false , DefaultActionPhase::Target}, + {EventId::Keydown , "keydown" , true , true , DefaultActionPhase::TargetAndBubble}, + {EventId::Keyup , "keyup" , true , true , DefaultActionPhase::TargetAndBubble}, + {EventId::Textinput , "textinput" , true , true , DefaultActionPhase::TargetAndBubble}, + {EventId::Mouseup , "mouseup" , true , true , DefaultActionPhase::TargetAndBubble}, + {EventId::Click , "click" , true , true , DefaultActionPhase::TargetAndBubble}, + {EventId::Dblclick , "dblclick" , true , true , DefaultActionPhase::TargetAndBubble}, + {EventId::Load , "load" , false , false , DefaultActionPhase::None}, + {EventId::Unload , "unload" , false , false , DefaultActionPhase::None}, + {EventId::Show , "show" , false , false , DefaultActionPhase::None}, + {EventId::Hide , "hide" , false , false , DefaultActionPhase::None}, + {EventId::Mousemove , "mousemove" , true , true , DefaultActionPhase::None}, + {EventId::Dragmove , "dragmove" , true , true , DefaultActionPhase::None}, + {EventId::Drag , "drag" , false , true , DefaultActionPhase::Target}, + {EventId::Dragstart , "dragstart" , false , true , DefaultActionPhase::Target}, + {EventId::Dragover , "dragover" , true , true , DefaultActionPhase::None}, + {EventId::Dragdrop , "dragdrop" , true , true , DefaultActionPhase::None}, + {EventId::Dragout , "dragout" , true , true , DefaultActionPhase::None}, + {EventId::Dragend , "dragend" , true , true , DefaultActionPhase::None}, + {EventId::Handledrag , "handledrag" , false , true , DefaultActionPhase::None}, + {EventId::Resize , "resize" , false , false , DefaultActionPhase::None}, + {EventId::Scroll , "scroll" , false , true , DefaultActionPhase::None}, + {EventId::Animationend , "animationend" , false , true , DefaultActionPhase::None}, + {EventId::Transitionend , "transitionend" , false , true , DefaultActionPhase::None}, + {EventId::Change , "change" , false , true , DefaultActionPhase::None}, + {EventId::Submit , "submit" , true , true , DefaultActionPhase::None}, + {EventId::Tabchange , "tabchange" , false , true , DefaultActionPhase::None}, + // clang-format on + }; + + // Reverse lookup map from event type to id. + UnorderedMap type_lookup; +}; + +static ControlledLifetimeResource event_specification_data; + +namespace EventSpecificationInterface { + + void Initialize() + { + event_specification_data.Initialize(); + + auto& specifications = event_specification_data->specifications; + auto& type_lookup = event_specification_data->type_lookup; + + type_lookup.reserve(specifications.size()); + for (auto& specification : specifications) + type_lookup.emplace(specification.type, specification.id); + +#ifdef RMLUI_DEBUG + // Verify that all event ids are specified + RMLUI_ASSERT((int)specifications.size() == (int)EventId::NumDefinedIds); + + for (int i = 0; i < (int)specifications.size(); i++) + { + // Verify correct order + RMLUI_ASSERT(i == (int)specifications[i].id); + } +#endif + } + + void Shutdown() + { + event_specification_data.Shutdown(); + } + + static EventSpecification& GetMutable(EventId id) + { + auto& specifications = event_specification_data->specifications; + size_t i = static_cast(id); + if (i < specifications.size()) + return specifications[i]; + return specifications[0]; + } + + // Get event specification for the given type. + // If not found: Inserts a new entry with given values. + static EventSpecification& GetOrInsert(const String& event_type, bool interruptible, bool bubbles, DefaultActionPhase default_action_phase) + { + auto& specifications = event_specification_data->specifications; + auto& type_lookup = event_specification_data->type_lookup; + + auto it = type_lookup.find(event_type); + + if (it != type_lookup.end()) + return GetMutable(it->second); + + const size_t new_id_num = specifications.size(); + if (new_id_num >= size_t(EventId::MaxNumIds)) + { + Log::Message(Log::LT_ERROR, "Error while registering event type '%s': Maximum number of allowed events exceeded.", event_type.c_str()); + RMLUI_ERROR; + return specifications.front(); + } + + // No specification found for this name, insert a new entry with default values + EventId new_id = static_cast(new_id_num); + specifications.push_back(EventSpecification{new_id, event_type, interruptible, bubbles, default_action_phase}); + type_lookup.emplace(event_type, new_id); + return specifications.back(); + } + + const EventSpecification& Get(EventId id) + { + return GetMutable(id); + } + + const EventSpecification& GetOrInsert(const String& event_type) + { + // Default values for new event types defined as follows: + constexpr bool interruptible = true; + constexpr bool bubbles = true; + constexpr DefaultActionPhase default_action_phase = DefaultActionPhase::None; + + return GetOrInsert(event_type, interruptible, bubbles, default_action_phase); + } + + EventId GetIdOrInsert(const String& event_type) + { + auto& type_lookup = event_specification_data->type_lookup; + + auto it = type_lookup.find(event_type); + if (it != type_lookup.end()) + return it->second; + + return GetOrInsert(event_type).id; + } + + EventId InsertOrReplaceCustom(const String& event_type, bool interruptible, bool bubbles, DefaultActionPhase default_action_phase) + { + auto& specifications = event_specification_data->specifications; + + const size_t size_before = specifications.size(); + EventSpecification& specification = GetOrInsert(event_type, interruptible, bubbles, default_action_phase); + bool got_existing_entry = (size_before == specifications.size()); + + // If we found an existing entry of same type, replace it, but only if it is a custom event id. + if (got_existing_entry && (int)specification.id >= (int)EventId::FirstCustomId) + { + specification.interruptible = interruptible; + specification.bubbles = bubbles; + specification.default_action_phase = default_action_phase; + } + + return specification.id; + } + +} // namespace EventSpecificationInterface +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/EventSpecification.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/EventSpecification.h new file mode 100644 index 00000000..03d8588a --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/EventSpecification.h @@ -0,0 +1,39 @@ +#pragma once + +#include "../../Include/RmlUi/Core/Event.h" +#include "../../Include/RmlUi/Core/Header.h" +#include "../../Include/RmlUi/Core/ID.h" + +namespace Rml { + +struct EventSpecification { + EventId id; + String type; + bool interruptible; + bool bubbles; + DefaultActionPhase default_action_phase; +}; + +namespace EventSpecificationInterface { + + void Initialize(); + void Shutdown(); + + // Get event specification for the given id. + // Returns the 'invalid' event type if no specification exists for id. + const EventSpecification& Get(EventId id); + + // Get event specification for the given type. + // If not found: Inserts a new entry with default values. + const EventSpecification& GetOrInsert(const String& event_type); + + // Get event id for the given name. + // If not found: Inserts a new entry with default values. + EventId GetIdOrInsert(const String& event_type); + + // Insert a new specification for the given event_type. + // If the type already exists, it will be replaced if and only if the event type is not an internal type. + EventId InsertOrReplaceCustom(const String& event_type, bool interruptible, bool bubbles, DefaultActionPhase default_action_phase); +} // namespace EventSpecificationInterface + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Factory.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Factory.cpp new file mode 100644 index 00000000..4690e354 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Factory.cpp @@ -0,0 +1,608 @@ +#include "../../Include/RmlUi/Core/Factory.h" +#include "../../Include/RmlUi/Core/Context.h" +#include "../../Include/RmlUi/Core/ContextInstancer.h" +#include "../../Include/RmlUi/Core/Core.h" +#include "../../Include/RmlUi/Core/ElementDocument.h" +#include "../../Include/RmlUi/Core/ElementInstancer.h" +#include "../../Include/RmlUi/Core/ElementText.h" +#include "../../Include/RmlUi/Core/ElementUtilities.h" +#include "../../Include/RmlUi/Core/Elements/ElementForm.h" +#include "../../Include/RmlUi/Core/Elements/ElementFormControlInput.h" +#include "../../Include/RmlUi/Core/Elements/ElementFormControlSelect.h" +#include "../../Include/RmlUi/Core/Elements/ElementFormControlTextArea.h" +#include "../../Include/RmlUi/Core/Elements/ElementProgress.h" +#include "../../Include/RmlUi/Core/Elements/ElementTabSet.h" +#include "../../Include/RmlUi/Core/EventListenerInstancer.h" +#include "../../Include/RmlUi/Core/StreamMemory.h" +#include "../../Include/RmlUi/Core/StyleSheet.h" +#include "../../Include/RmlUi/Core/StyleSheetContainer.h" +#include "../../Include/RmlUi/Core/SystemInterface.h" +#include "ContextInstancerDefault.h" +#include "ControlledLifetimeResource.h" +#include "DataControllerDefault.h" +#include "DataViewDefault.h" +#include "DecoratorGradient.h" +#include "DecoratorNinePatch.h" +#include "DecoratorShader.h" +#include "DecoratorText.h" +#include "DecoratorTiledBox.h" +#include "DecoratorTiledHorizontal.h" +#include "DecoratorTiledImage.h" +#include "DecoratorTiledVertical.h" +#include "ElementHandle.h" +#include "Elements/ElementImage.h" +#include "Elements/ElementLabel.h" +#include "Elements/ElementTextSelection.h" +#include "Elements/XMLNodeHandlerSelect.h" +#include "Elements/XMLNodeHandlerTabSet.h" +#include "Elements/XMLNodeHandlerTextArea.h" +#include "EventInstancerDefault.h" +#include "FilterBasic.h" +#include "FilterBlur.h" +#include "FilterDropShadow.h" +#include "FontEffectBlur.h" +#include "FontEffectGlow.h" +#include "FontEffectOutline.h" +#include "FontEffectShadow.h" +#include "PluginRegistry.h" +#include "StreamFile.h" +#include "StyleSheetFactory.h" +#include "TemplateCache.h" +#include "XMLNodeHandlerBody.h" +#include "XMLNodeHandlerDefault.h" +#include "XMLNodeHandlerHead.h" +#include "XMLNodeHandlerTemplate.h" +#include "XMLParseTools.h" +#include + +namespace Rml { + +// Default instancers are constructed and destroyed on Initialise and Shutdown, respectively. +struct DefaultInstancers { + UniquePtr context_default; + UniquePtr event_default; + + // Basic elements + ElementInstancerElement element_default; + ElementInstancerText element_text; + ElementInstancerGeneric element_img; + ElementInstancerGeneric element_handle; + ElementInstancerGeneric element_body; + + // Control elements + ElementInstancerGeneric form; + ElementInstancerGeneric input; + ElementInstancerGeneric select; + ElementInstancerGeneric element_label; + + ElementInstancerGeneric textarea; + ElementInstancerGeneric selection; + ElementInstancerGeneric tabset; + + ElementInstancerGeneric progress; + + // Decorators + DecoratorTextInstancer decorator_text; + DecoratorTiledHorizontalInstancer decorator_tiled_horizontal; + DecoratorTiledVerticalInstancer decorator_tiled_vertical; + DecoratorTiledBoxInstancer decorator_tiled_box; + DecoratorTiledImageInstancer decorator_image; + DecoratorNinePatchInstancer decorator_ninepatch; + DecoratorShaderInstancer decorator_shader; + DecoratorStraightGradientInstancer decorator_straight_gradient; + DecoratorLinearGradientInstancer decorator_linear_gradient; + DecoratorRadialGradientInstancer decorator_radial_gradient; + DecoratorConicGradientInstancer decorator_conic_gradient; + + // Filters + FilterBasicInstancer filter_hue_rotate = {FilterBasicInstancer::ValueType::Angle, "0rad"}; + FilterBasicInstancer filter_basic_d0 = {FilterBasicInstancer::ValueType::NumberPercent, "0"}; + FilterBasicInstancer filter_basic_d1 = {FilterBasicInstancer::ValueType::NumberPercent, "1"}; + FilterBlurInstancer filter_blur; + FilterDropShadowInstancer filter_drop_shadow; + + // Font effects + FontEffectBlurInstancer font_effect_blur; + FontEffectGlowInstancer font_effect_glow; + FontEffectOutlineInstancer font_effect_outline; + FontEffectShadowInstancer font_effect_shadow; + + // Data binding views + DataViewInstancerDefault data_view_attribute; + DataViewInstancerDefault data_view_attribute_if; + DataViewInstancerDefault data_view_class; + DataViewInstancerDefault data_view_if; + DataViewInstancerDefault data_view_visible; + DataViewInstancerDefault data_view_rml; + DataViewInstancerDefault data_view_style; + DataViewInstancerDefault data_view_text; + DataViewInstancerDefault data_view_value; + DataViewInstancerDefault data_view_checked; + DataViewInstancerDefault data_view_alias; + + DataViewInstancerDefault structural_data_view_for; + + // Data binding controllers + DataControllerInstancerDefault data_controller_event; + DataControllerInstancerDefault data_controller_value; +}; + +struct FactoryData { + DefaultInstancers default_instancers; + UnorderedMap element_instancers; + UnorderedMap decorator_instancers; + UnorderedMap filter_instancers; + UnorderedMap font_effect_instancers; + UnorderedMap data_view_instancers; + UnorderedMap data_controller_instancers; + SmallUnorderedSet structural_data_view_attribute_names; +}; + +static ControlledLifetimeResource factory_data; + +static ContextInstancer* context_instancer = nullptr; +static EventInstancer* event_instancer = nullptr; +static EventListenerInstancer* event_listener_instancer = nullptr; + +Factory::Factory() {} + +Factory::~Factory() {} + +void Factory::Initialise() +{ + factory_data.Initialize(); + + DefaultInstancers& default_instancers = factory_data->default_instancers; + + // Default context instancer + if (!context_instancer) + { + default_instancers.context_default = MakeUnique(); + context_instancer = default_instancers.context_default.get(); + } + + // Default event instancer + if (!event_instancer) + { + default_instancers.event_default = MakeUnique(); + event_instancer = default_instancers.event_default.get(); + } + + // No default event listener instancer + if (!event_listener_instancer) + event_listener_instancer = nullptr; + + // Basic element instancers + RegisterElementInstancer("*", &default_instancers.element_default); + RegisterElementInstancer("img", &default_instancers.element_img); + RegisterElementInstancer("#text", &default_instancers.element_text); + RegisterElementInstancer("handle", &default_instancers.element_handle); + RegisterElementInstancer("body", &default_instancers.element_body); + + // Control element instancers + RegisterElementInstancer("form", &default_instancers.form); + RegisterElementInstancer("input", &default_instancers.input); + RegisterElementInstancer("select", &default_instancers.select); + RegisterElementInstancer("label", &default_instancers.element_label); + + RegisterElementInstancer("textarea", &default_instancers.textarea); + RegisterElementInstancer("#selection", &default_instancers.selection); + RegisterElementInstancer("tabset", &default_instancers.tabset); + + RegisterElementInstancer("progress", &default_instancers.progress); + RegisterElementInstancer("progressbar", &default_instancers.progress); + + // Decorator instancers + RegisterDecoratorInstancer("text", &default_instancers.decorator_text); + RegisterDecoratorInstancer("tiled-horizontal", &default_instancers.decorator_tiled_horizontal); + RegisterDecoratorInstancer("tiled-vertical", &default_instancers.decorator_tiled_vertical); + RegisterDecoratorInstancer("tiled-box", &default_instancers.decorator_tiled_box); + RegisterDecoratorInstancer("image", &default_instancers.decorator_image); + RegisterDecoratorInstancer("ninepatch", &default_instancers.decorator_ninepatch); + RegisterDecoratorInstancer("shader", &default_instancers.decorator_shader); + + RegisterDecoratorInstancer("gradient", &default_instancers.decorator_straight_gradient); + RegisterDecoratorInstancer("horizontal-gradient", &default_instancers.decorator_straight_gradient); + RegisterDecoratorInstancer("vertical-gradient", &default_instancers.decorator_straight_gradient); + + RegisterDecoratorInstancer("linear-gradient", &default_instancers.decorator_linear_gradient); + RegisterDecoratorInstancer("repeating-linear-gradient", &default_instancers.decorator_linear_gradient); + RegisterDecoratorInstancer("radial-gradient", &default_instancers.decorator_radial_gradient); + RegisterDecoratorInstancer("repeating-radial-gradient", &default_instancers.decorator_radial_gradient); + RegisterDecoratorInstancer("conic-gradient", &default_instancers.decorator_conic_gradient); + RegisterDecoratorInstancer("repeating-conic-gradient", &default_instancers.decorator_conic_gradient); + + // Filter instancers + RegisterFilterInstancer("hue-rotate", &default_instancers.filter_hue_rotate); + RegisterFilterInstancer("brightness", &default_instancers.filter_basic_d1); + RegisterFilterInstancer("contrast", &default_instancers.filter_basic_d1); + RegisterFilterInstancer("grayscale", &default_instancers.filter_basic_d0); + RegisterFilterInstancer("invert", &default_instancers.filter_basic_d0); + RegisterFilterInstancer("opacity", &default_instancers.filter_basic_d1); + RegisterFilterInstancer("saturate", &default_instancers.filter_basic_d1); + RegisterFilterInstancer("sepia", &default_instancers.filter_basic_d0); + + RegisterFilterInstancer("blur", &default_instancers.filter_blur); + RegisterFilterInstancer("drop-shadow", &default_instancers.filter_drop_shadow); + + // Font effect instancers + RegisterFontEffectInstancer("blur", &default_instancers.font_effect_blur); + RegisterFontEffectInstancer("glow", &default_instancers.font_effect_glow); + RegisterFontEffectInstancer("outline", &default_instancers.font_effect_outline); + RegisterFontEffectInstancer("shadow", &default_instancers.font_effect_shadow); + + // Data binding views + // clang-format off + RegisterDataViewInstancer(&default_instancers.data_view_attribute, "attr", false); + RegisterDataViewInstancer(&default_instancers.data_view_attribute_if, "attrif", false); + RegisterDataViewInstancer(&default_instancers.data_view_class, "class", false); + RegisterDataViewInstancer(&default_instancers.data_view_if, "if", false); + RegisterDataViewInstancer(&default_instancers.data_view_visible, "visible", false); + RegisterDataViewInstancer(&default_instancers.data_view_rml, "rml", false); + RegisterDataViewInstancer(&default_instancers.data_view_style, "style", false); + RegisterDataViewInstancer(&default_instancers.data_view_text, "text", false); + RegisterDataViewInstancer(&default_instancers.data_view_value, "value", false); + RegisterDataViewInstancer(&default_instancers.data_view_checked, "checked", false); + RegisterDataViewInstancer(&default_instancers.data_view_alias, "alias", false); + RegisterDataViewInstancer(&default_instancers.structural_data_view_for, "for", true ); + // clang-format on + + // Data binding controllers + RegisterDataControllerInstancer(&default_instancers.data_controller_value, "checked"); + RegisterDataControllerInstancer(&default_instancers.data_controller_event, "event"); + RegisterDataControllerInstancer(&default_instancers.data_controller_value, "value"); + + // XML nodes that only contain CDATA + XMLParser::RegisterPersistentCDATATag("script"); + XMLParser::RegisterPersistentCDATATag("style"); + + // XML node handlers + XMLParser::RegisterNodeHandler("", MakeShared()); + XMLParser::RegisterNodeHandler("body", MakeShared()); + XMLParser::RegisterNodeHandler("head", MakeShared()); + XMLParser::RegisterNodeHandler("template", MakeShared()); + + // XML node handlers for control elements + XMLParser::RegisterNodeHandler("tabset", MakeShared()); + XMLParser::RegisterNodeHandler("textarea", MakeShared()); + XMLParser::RegisterNodeHandler("select", MakeShared()); +} + +void Factory::Shutdown() +{ + context_instancer = nullptr; + event_listener_instancer = nullptr; + event_instancer = nullptr; + + XMLParser::ReleaseHandlers(); + + factory_data.Shutdown(); +} + +void Factory::RegisterContextInstancer(ContextInstancer* instancer) +{ + context_instancer = instancer; +} + +ContextPtr Factory::InstanceContext(const String& name, RenderManager* render_manager, TextInputHandler* text_input_handler) +{ + ContextPtr new_context = context_instancer->InstanceContext(name, render_manager, text_input_handler); + if (new_context) + new_context->SetInstancer(context_instancer); + return new_context; +} + +void Factory::RegisterElementInstancer(const String& name, ElementInstancer* instancer) +{ + factory_data->element_instancers[StringUtilities::ToLower(name)] = instancer; +} + +ElementInstancer* Factory::GetElementInstancer(const String& tag) +{ + auto instancer_iterator = factory_data->element_instancers.find(tag); + if (instancer_iterator == factory_data->element_instancers.end()) + { + instancer_iterator = factory_data->element_instancers.find("*"); + if (instancer_iterator == factory_data->element_instancers.end()) + return nullptr; + } + + return instancer_iterator->second; +} + +ElementPtr Factory::InstanceElement(Element* parent, const String& instancer_name, const String& tag, const XMLAttributes& attributes) +{ + if (ElementInstancer* instancer = GetElementInstancer(instancer_name)) + { + if (ElementPtr element = instancer->InstanceElement(parent, tag, attributes)) + { + element->SetInstancer(instancer); + element->SetAttributes(attributes); + + PluginRegistry::NotifyElementCreate(element.get()); + return element; + } + } + + return nullptr; +} + +bool Factory::InstanceElementText(Element* parent, const String& in_text) +{ + RMLUI_ASSERT(parent); + + String text; + if (SystemInterface* system_interface = GetSystemInterface()) + system_interface->TranslateString(text, in_text); + + // If this text node only contains white-space we don't want to construct it. + const bool only_white_space = std::all_of(text.begin(), text.end(), &StringUtilities::IsWhitespace); + if (only_white_space) + return true; + + // See if we need to parse it as RML, and whether the text contains data expressions (curly brackets). + bool parse_as_rml = false; + bool has_data_expression = false; + + bool inside_brackets = false; + bool inside_string = false; + char previous = 0; + for (const char c : text) + { + const char* error_str = XMLParseTools::ParseDataBrackets(inside_brackets, inside_string, c, previous); + if (error_str) + { + Log::Message(Log::LT_WARNING, "Failed to instance text element '%s'. %s", text.c_str(), error_str); + return false; + } + + if (inside_brackets) + has_data_expression = true; + else if (c == '<') + parse_as_rml = true; + + previous = c; + } + + // If the text contains RML elements then run it through the XML parser again. + if (parse_as_rml) + { + RMLUI_ZoneScopedNC("InstanceStream", 0xDC143C); + auto stream = MakeUnique(text.size() + 32); + Context* context = parent->GetContext(); + String tag = context ? context->GetDocumentsBaseTag() : "body"; + String open_tag = "<" + tag + ">"; + String close_tag = ""; + stream->Write(open_tag.c_str(), open_tag.size()); + stream->Write(text); + stream->Write(close_tag.c_str(), close_tag.size()); + stream->Seek(0, SEEK_SET); + + InstanceElementStream(parent, stream.get()); + } + else + { + RMLUI_ZoneScopedNC("InstanceText", 0x8FBC8F); + + // Attempt to instance the element. + XMLAttributes attributes; + + // If we have curly brackets in the text, we tag the element so that the appropriate data view (DataViewText) is constructed. + if (has_data_expression) + attributes.emplace("data-text", Variant()); + + ElementPtr element = Factory::InstanceElement(parent, "#text", "#text", attributes); + if (!element) + { + Log::Message(Log::LT_ERROR, "Failed to instance text element '%s', instancer returned nullptr.", text.c_str()); + return false; + } + + // Assign the element its text value. + ElementText* text_element = rmlui_dynamic_cast(element.get()); + if (!text_element) + { + Log::Message(Log::LT_ERROR, "Failed to instance text element '%s'. Found type '%s', was expecting a derivative of ElementText.", + text.c_str(), rmlui_type_name(*element)); + return false; + } + + // Unescape any escaped entities or unicode symbols + text = StringUtilities::DecodeRml(text); + + text_element->SetText(text); + + // Add to active node. + parent->AppendChild(std::move(element)); + } + + return true; +} + +bool Factory::InstanceElementStream(Element* parent, Stream* stream) +{ + XMLParser parser(parent); + parser.Parse(stream); + return true; +} + +ElementPtr Factory::InstanceDocumentStream(Context* context, Stream* stream, const String& document_base_tag) +{ + RMLUI_ZoneScoped; + + ElementPtr element = Factory::InstanceElement(nullptr, document_base_tag, document_base_tag, XMLAttributes()); + if (!element) + { + Log::Message(Log::LT_ERROR, "Failed to instance document, instancer returned nullptr."); + return nullptr; + } + + ElementDocument* document = rmlui_dynamic_cast(element.get()); + if (!document) + { + Log::Message(Log::LT_ERROR, "Failed to instance document element. Found type '%s', was expecting derivative of ElementDocument.", + rmlui_type_name(*element)); + return nullptr; + } + + document->context = context; + + XMLParser parser(element.get()); + parser.Parse(stream); + + return element; +} + +void Factory::RegisterDecoratorInstancer(const String& name, DecoratorInstancer* instancer) +{ + RMLUI_ASSERT(instancer); + factory_data->decorator_instancers[StringUtilities::ToLower(name)] = instancer; +} + +DecoratorInstancer* Factory::GetDecoratorInstancer(const String& name) +{ + auto iterator = factory_data->decorator_instancers.find(name); + if (iterator == factory_data->decorator_instancers.end()) + return nullptr; + + return iterator->second; +} + +void Factory::RegisterFilterInstancer(const String& name, FilterInstancer* instancer) +{ + RMLUI_ASSERT(instancer); + factory_data->filter_instancers[StringUtilities::ToLower(name)] = instancer; +} + +FilterInstancer* Factory::GetFilterInstancer(const String& name) +{ + auto iterator = factory_data->filter_instancers.find(name); + if (iterator == factory_data->filter_instancers.end()) + return nullptr; + + return iterator->second; +} + +void Factory::RegisterFontEffectInstancer(const String& name, FontEffectInstancer* instancer) +{ + RMLUI_ASSERT(instancer); + factory_data->font_effect_instancers[StringUtilities::ToLower(name)] = instancer; +} + +FontEffectInstancer* Factory::GetFontEffectInstancer(const String& name) +{ + auto iterator = factory_data->font_effect_instancers.find(name); + if (iterator == factory_data->font_effect_instancers.end()) + return nullptr; + + return iterator->second; +} + +SharedPtr Factory::InstanceStyleSheetString(const String& string) +{ + auto memory_stream = MakeUnique((const byte*)string.c_str(), string.size()); + return InstanceStyleSheetStream(memory_stream.get()); +} + +SharedPtr Factory::InstanceStyleSheetFile(const String& file_name) +{ + auto file_stream = MakeUnique(); + file_stream->Open(file_name); + return InstanceStyleSheetStream(file_stream.get()); +} + +SharedPtr Factory::InstanceStyleSheetStream(Stream* stream) +{ + SharedPtr style_sheet_container = MakeShared(); + if (style_sheet_container->LoadStyleSheetContainer(stream)) + { + return style_sheet_container; + } + return nullptr; +} + +void Factory::ClearStyleSheetCache() +{ + StyleSheetFactory::ClearStyleSheetCache(); +} + +void Factory::ClearTemplateCache() +{ + TemplateCache::Clear(); +} + +void Factory::RegisterEventInstancer(EventInstancer* instancer) +{ + event_instancer = instancer; +} + +EventPtr Factory::InstanceEvent(Element* target, EventId id, const String& type, const Dictionary& parameters, bool interruptible) +{ + EventPtr event = event_instancer->InstanceEvent(target, id, type, parameters, interruptible); + if (event) + event->instancer = event_instancer; + return event; +} + +void Factory::RegisterEventListenerInstancer(EventListenerInstancer* instancer) +{ + event_listener_instancer = instancer; +} + +EventListener* Factory::InstanceEventListener(const String& value, Element* element) +{ + // If we have an event listener instancer, use it + if (event_listener_instancer) + return event_listener_instancer->InstanceEventListener(value, element); + + return nullptr; +} + +void Factory::RegisterDataViewInstancer(DataViewInstancer* instancer, const String& name, bool is_structural_view) +{ + const bool inserted = factory_data->data_view_instancers.emplace(name, instancer).second; + if (!inserted) + { + Log::Message(Log::LT_WARNING, "Could not register data view instancer '%s'. The given name is already registered.", name.c_str()); + return; + } + if (is_structural_view) + factory_data->structural_data_view_attribute_names.emplace("data-" + name); +} + +void Factory::RegisterDataControllerInstancer(DataControllerInstancer* instancer, const String& name) +{ + bool inserted = factory_data->data_controller_instancers.emplace(name, instancer).second; + if (!inserted) + Log::Message(Log::LT_WARNING, "Could not register data controller instancer '%s'. The given name is already registered.", name.c_str()); +} + +DataViewPtr Factory::InstanceDataView(const String& type_name, Element* element) +{ + RMLUI_ASSERT(element); + const auto it = factory_data->data_view_instancers.find(type_name); + if (it != factory_data->data_view_instancers.end()) + return it->second->InstanceView(element); + return nullptr; +} + +DataControllerPtr Factory::InstanceDataController(const String& type_name, Element* element) +{ + const auto it = factory_data->data_controller_instancers.find(type_name); + if (it != factory_data->data_controller_instancers.end()) + return it->second->InstanceController(element); + return nullptr; +} + +bool Factory::IsStructuralDataView(const String& type_name) +{ + const String attribute = "data-" + type_name; + return factory_data->structural_data_view_attribute_names.find(attribute) != factory_data->structural_data_view_attribute_names.end(); +} + +const SmallUnorderedSet& Factory::GetStructuralDataViewAttributeNames() +{ + return factory_data->structural_data_view_attribute_names; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FileInterface.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FileInterface.cpp new file mode 100644 index 00000000..a1d4ec27 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FileInterface.cpp @@ -0,0 +1,41 @@ +#include "../../Include/RmlUi/Core/FileInterface.h" +#include "../../Include/RmlUi/Core/Log.h" + +namespace Rml { + +FileInterface::FileInterface() {} + +FileInterface::~FileInterface() {} + +size_t FileInterface::Length(FileHandle file) +{ + size_t current_position = Tell(file); + Seek(file, 0, SEEK_END); + size_t length = Tell(file); + Seek(file, (long)current_position, SEEK_SET); + return length; +} + +bool FileInterface::LoadFile(const String& path, String& out_data) +{ + FileHandle handle = Open(path); + if (!handle) + return false; + + const size_t length = Length(handle); + + out_data = String(length, 0); + + const size_t read_length = Read(&out_data[0], length, handle); + + if (length != read_length) + { + Log::Message(Log::LT_WARNING, "Could only read %zu of %zu bytes from file %s", read_length, length, path.c_str()); + } + + Close(handle); + + return true; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FileInterfaceDefault.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FileInterfaceDefault.cpp new file mode 100644 index 00000000..4ed602ff --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FileInterfaceDefault.cpp @@ -0,0 +1,35 @@ +#include "FileInterfaceDefault.h" + +#ifndef RMLUI_NO_FILE_INTERFACE_DEFAULT + +namespace Rml { + +FileInterfaceDefault::~FileInterfaceDefault() {} + +FileHandle FileInterfaceDefault::Open(const String& path) +{ + return (FileHandle)fopen(path.c_str(), "rb"); +} + +void FileInterfaceDefault::Close(FileHandle file) +{ + fclose((FILE*)file); +} + +size_t FileInterfaceDefault::Read(void* buffer, size_t size, FileHandle file) +{ + return fread(buffer, 1, size, (FILE*)file); +} + +bool FileInterfaceDefault::Seek(FileHandle file, long offset, int origin) +{ + return fseek((FILE*)file, offset, origin) == 0; +} + +size_t FileInterfaceDefault::Tell(FileHandle file) +{ + return ftell((FILE*)file); +} + +} // namespace Rml +#endif /*RMLUI_NO_FILE_INTERFACE_DEFAULT*/ diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FileInterfaceDefault.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FileInterfaceDefault.h new file mode 100644 index 00000000..ba3e102d --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FileInterfaceDefault.h @@ -0,0 +1,45 @@ +#pragma once + +#include "../../Include/RmlUi/Core/FileInterface.h" + +#ifndef RMLUI_NO_FILE_INTERFACE_DEFAULT + +namespace Rml { + +/** + Implementation of the RmlUi file interface using the Standard C file functions. + */ + +class FileInterfaceDefault : public FileInterface { +public: + virtual ~FileInterfaceDefault(); + + /// Opens a file. + /// @param path The path of the file to open. + /// @return A valid file handle, or nullptr on failure + FileHandle Open(const String& path) override; + /// Closes a previously opened file. + /// @param file The file handle previously opened through Open(). + void Close(FileHandle file) override; + + /// Reads data from a previously opened file. + /// @param buffer The buffer to be read into. + /// @param size The number of bytes to read into the buffer. + /// @param file The handle of the file. + /// @return The total number of bytes read into the buffer. + size_t Read(void* buffer, size_t size, FileHandle file) override; + /// Seeks to a point in a previously opened file. + /// @param file The handle of the file to seek. + /// @param offset The number of bytes to seek. + /// @param origin One of either SEEK_SET (seek from the beginning of the file), SEEK_END (seek from the end of the file) or SEEK_CUR (seek from + /// the current file position). + /// @return True if the operation completed successfully, false otherwise. + bool Seek(FileHandle file, long offset, int origin) override; + /// Returns the current position of the file pointer. + /// @param file The handle of the file to be queried. + /// @return The number of bytes from the origin of the file. + size_t Tell(FileHandle file) override; +}; + +} // namespace Rml +#endif /*RMLUI_NO_FILE_INTERFACE_DEFAULT*/ diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Filter.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Filter.cpp new file mode 100644 index 00000000..058c9455 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Filter.cpp @@ -0,0 +1,16 @@ +#include "../../Include/RmlUi/Core/Filter.h" +#include "../../Include/RmlUi/Core/RenderManager.h" + +namespace Rml { + +Filter::Filter() {} + +Filter::~Filter() {} + +void Filter::ExtendInkOverflow(Element* /*element*/, Rectanglef& /*scissor_region*/) const {} + +FilterInstancer::FilterInstancer() {} + +FilterInstancer::~FilterInstancer() {} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FilterBasic.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FilterBasic.cpp new file mode 100644 index 00000000..af192576 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FilterBasic.cpp @@ -0,0 +1,52 @@ +#include "FilterBasic.h" +#include "../../Include/RmlUi/Core/CompiledFilterShader.h" +#include "../../Include/RmlUi/Core/Element.h" +#include "../../Include/RmlUi/Core/PropertyDefinition.h" +#include "../../Include/RmlUi/Core/PropertyDictionary.h" +#include "../../Include/RmlUi/Core/RenderManager.h" + +namespace Rml { + +bool FilterBasic::Initialise(const String& in_name, float in_value) +{ + name = in_name; + value = in_value; + return true; +} + +CompiledFilter FilterBasic::CompileFilter(Element* element) const +{ + return element->GetRenderManager()->CompileFilter(name, Dictionary{{"value", Variant(value)}}); +} + +FilterBasicInstancer::FilterBasicInstancer(ValueType value_type, const char* default_value) +{ + switch (value_type) + { + case ValueType::NumberPercent: ids.value = RegisterProperty("value", default_value).AddParser("number_percent").GetId(); break; + case ValueType::Angle: ids.value = RegisterProperty("value", default_value).AddParser("angle").GetId(); break; + } + + RegisterShorthand("filter", "value", ShorthandType::FallThrough); +} + +SharedPtr FilterBasicInstancer::InstanceFilter(const String& name, const PropertyDictionary& properties) +{ + const Property* p_value = properties.GetProperty(ids.value); + if (!p_value) + return nullptr; + + float value = p_value->Get(); + if (p_value->unit == Unit::PERCENT) + value *= 0.01f; + else if (p_value->unit == Unit::DEG) + value = Rml::Math::DegreesToRadians(value); + + auto filter = MakeShared(); + if (filter->Initialise(name, value)) + return filter; + + return nullptr; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FilterBasic.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FilterBasic.h new file mode 100644 index 00000000..e90c33a1 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FilterBasic.h @@ -0,0 +1,34 @@ +#pragma once + +#include "../../Include/RmlUi/Core/Filter.h" +#include "../../Include/RmlUi/Core/ID.h" + +namespace Rml { + +class FilterBasic : public Filter { +public: + bool Initialise(const String& name, float value); + + CompiledFilter CompileFilter(Element* element) const override; + +private: + String name; + float value = 0.f; +}; + +class FilterBasicInstancer : public FilterInstancer { +public: + enum class ValueType { NumberPercent, Angle }; + + FilterBasicInstancer(ValueType value_type, const char* default_value); + + SharedPtr InstanceFilter(const String& name, const PropertyDictionary& properties) override; + +private: + struct PropertyIds { + PropertyId value; + }; + PropertyIds ids = {}; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FilterBlur.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FilterBlur.cpp new file mode 100644 index 00000000..1f0b716d --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FilterBlur.cpp @@ -0,0 +1,48 @@ +#include "FilterBlur.h" +#include "../../Include/RmlUi/Core/CompiledFilterShader.h" +#include "../../Include/RmlUi/Core/Element.h" +#include "../../Include/RmlUi/Core/PropertyDefinition.h" +#include "../../Include/RmlUi/Core/PropertyDictionary.h" +#include "../../Include/RmlUi/Core/RenderManager.h" + +namespace Rml { + +bool FilterBlur::Initialise(NumericValue in_sigma) +{ + sigma_value = in_sigma; + return Any(in_sigma.unit & Unit::LENGTH); +} + +CompiledFilter FilterBlur::CompileFilter(Element* element) const +{ + const float radius = element->ResolveLength(sigma_value); + return element->GetRenderManager()->CompileFilter("blur", Dictionary{{"sigma", Variant(radius)}}); +} + +void FilterBlur::ExtendInkOverflow(Element* element, Rectanglef& scissor_region) const +{ + const float sigma = element->ResolveLength(sigma_value); + const float blur_extent = 3.0f * Math::Max(sigma, 1.f); + scissor_region = scissor_region.Extend(blur_extent); +} + +FilterBlurInstancer::FilterBlurInstancer() +{ + ids.sigma = RegisterProperty("sigma", "0px").AddParser("length").GetId(); + RegisterShorthand("filter", "sigma", ShorthandType::FallThrough); +} + +SharedPtr FilterBlurInstancer::InstanceFilter(const String& /*name*/, const PropertyDictionary& properties) +{ + const Property* p_radius = properties.GetProperty(ids.sigma); + if (!p_radius) + return nullptr; + + auto decorator = MakeShared(); + if (decorator->Initialise(p_radius->GetNumericValue())) + return decorator; + + return nullptr; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FilterBlur.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FilterBlur.h new file mode 100644 index 00000000..597eae5a --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FilterBlur.h @@ -0,0 +1,34 @@ +#pragma once + +#include "../../Include/RmlUi/Core/Filter.h" +#include "../../Include/RmlUi/Core/ID.h" +#include "../../Include/RmlUi/Core/NumericValue.h" + +namespace Rml { + +class FilterBlur : public Filter { +public: + bool Initialise(NumericValue sigma); + + CompiledFilter CompileFilter(Element* element) const override; + + void ExtendInkOverflow(Element* element, Rectanglef& scissor_region) const override; + +private: + NumericValue sigma_value; +}; + +class FilterBlurInstancer : public FilterInstancer { +public: + FilterBlurInstancer(); + + SharedPtr InstanceFilter(const String& name, const PropertyDictionary& properties) override; + +private: + struct PropertyIds { + PropertyId sigma; + }; + PropertyIds ids; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FilterDropShadow.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FilterDropShadow.cpp new file mode 100644 index 00000000..74f74b18 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FilterDropShadow.cpp @@ -0,0 +1,72 @@ +#include "FilterDropShadow.h" +#include "../../Include/RmlUi/Core/CompiledFilterShader.h" +#include "../../Include/RmlUi/Core/Element.h" +#include "../../Include/RmlUi/Core/PropertyDefinition.h" +#include "../../Include/RmlUi/Core/PropertyDictionary.h" +#include "../../Include/RmlUi/Core/RenderManager.h" + +namespace Rml { + +bool FilterDropShadow::Initialise(Colourb in_color, NumericValue in_offset_x, NumericValue in_offset_y, NumericValue in_sigma) +{ + color = in_color; + value_offset_x = in_offset_x; + value_offset_y = in_offset_y; + value_sigma = in_sigma; + return Any(in_offset_x.unit & Unit::LENGTH) && Any(in_offset_y.unit & Unit::LENGTH) && Any(in_sigma.unit & Unit::LENGTH); +} + +CompiledFilter FilterDropShadow::CompileFilter(Element* element) const +{ + const float sigma = element->ResolveLength(value_sigma); + const Vector2f offset = { + element->ResolveLength(value_offset_x), + element->ResolveLength(value_offset_y), + }; + + CompiledFilter filter = element->GetRenderManager()->CompileFilter("drop-shadow", + Dictionary{{"color", Variant(color)}, {"offset", Variant(offset)}, {"sigma", Variant(sigma)}}); + + return filter; +} + +void FilterDropShadow::ExtendInkOverflow(Element* element, Rectanglef& scissor_region) const +{ + // Expand the ink overflow area to cover both the native element *and* its offset shadow w/blur. + const float sigma = element->ResolveLength(value_sigma); + const Vector2f offset = { + element->ResolveLength(value_offset_x), + element->ResolveLength(value_offset_y), + }; + + const float blur_extent = 3.f * sigma; + scissor_region = + scissor_region.Extend(Math::Max(-offset, Vector2f(0.f)) + Vector2f(blur_extent), Math::Max(offset, Vector2f(0.f)) + Vector2f(blur_extent)); +} + +FilterDropShadowInstancer::FilterDropShadowInstancer() +{ + ids.color = RegisterProperty("color", "transparent").AddParser("color").GetId(); + ids.offset_x = RegisterProperty("offset-x", "0px").AddParser("length").GetId(); + ids.offset_y = RegisterProperty("offset-y", "0px").AddParser("length").GetId(); + ids.sigma = RegisterProperty("sigma", "0px").AddParser("length").GetId(); + RegisterShorthand("filter", "color, offset-x, offset-y, sigma", ShorthandType::FallThrough); +} + +SharedPtr FilterDropShadowInstancer::InstanceFilter(const String& /*name*/, const PropertyDictionary& properties) +{ + const Property* p_color = properties.GetProperty(ids.color); + const Property* p_offset_x = properties.GetProperty(ids.offset_x); + const Property* p_offset_y = properties.GetProperty(ids.offset_y); + const Property* p_sigma = properties.GetProperty(ids.sigma); + if (!p_color || !p_offset_x || !p_offset_y || !p_sigma) + return nullptr; + + auto decorator = MakeShared(); + if (decorator->Initialise(p_color->Get(), p_offset_x->GetNumericValue(), p_offset_y->GetNumericValue(), p_sigma->GetNumericValue())) + return decorator; + + return nullptr; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FilterDropShadow.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FilterDropShadow.h new file mode 100644 index 00000000..8ad56f3a --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FilterDropShadow.h @@ -0,0 +1,35 @@ +#pragma once + +#include "../../Include/RmlUi/Core/Filter.h" +#include "../../Include/RmlUi/Core/ID.h" +#include "../../Include/RmlUi/Core/NumericValue.h" + +namespace Rml { + +class FilterDropShadow : public Filter { +public: + bool Initialise(Colourb color, NumericValue offset_x, NumericValue offset_y, NumericValue sigma); + + CompiledFilter CompileFilter(Element* element) const override; + + void ExtendInkOverflow(Element* element, Rectanglef& scissor_region) const override; + +private: + Colourb color; + NumericValue value_offset_x, value_offset_y, value_sigma; +}; + +class FilterDropShadowInstancer : public FilterInstancer { +public: + FilterDropShadowInstancer(); + + SharedPtr InstanceFilter(const String& name, const PropertyDictionary& properties) override; + +private: + struct PropertyIds { + PropertyId color, offset_x, offset_y, sigma; + }; + PropertyIds ids; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEffect.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEffect.cpp new file mode 100644 index 00000000..cece6e93 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEffect.cpp @@ -0,0 +1,67 @@ +#include "../../Include/RmlUi/Core/FontEffect.h" +#include "../../Include/RmlUi/Core/FontEffectInstancer.h" + +namespace Rml { + +FontEffect::FontEffect() : layer(Layer::Back), colour(255, 255, 255), fingerprint(0) {} + +FontEffect::~FontEffect() {} + +bool FontEffect::HasUniqueTexture() const +{ + return false; +} + +bool FontEffect::GetGlyphMetrics(Vector2i& /*origin*/, Vector2i& /*dimensions*/, const FontGlyph& /*glyph*/) const +{ + return false; +} + +void FontEffect::GenerateGlyphTexture(byte* /*destination_data*/, Vector2i /*destination_dimensions*/, int /*destination_stride*/, + const FontGlyph& /*glyph*/) const +{} + +void FontEffect::SetColour(const Colourb _colour) +{ + colour = _colour; +} + +Colourb FontEffect::GetColour() const +{ + return colour; +} + +FontEffect::Layer FontEffect::GetLayer() const +{ + return layer; +} + +void FontEffect::SetLayer(Layer _layer) +{ + layer = _layer; +} + +size_t FontEffect::GetFingerprint() const +{ + return fingerprint; +} + +void FontEffect::SetFingerprint(size_t _fingerprint) +{ + fingerprint = _fingerprint; +} + +void FontEffect::FillColorValuesFromAlpha(byte* destination, Vector2i dimensions, int stride) +{ + for (int y = 0; y < dimensions.y; ++y) + { + for (int x = 0; x < dimensions.x; ++x) + { + const int i = y * stride + x * 4; + const byte alpha = destination[i + 3]; + destination[i + 0] = destination[i + 1] = destination[i + 2] = alpha; + } + } +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEffectBlur.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEffectBlur.cpp new file mode 100644 index 00000000..199b86e0 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEffectBlur.cpp @@ -0,0 +1,113 @@ +#include "FontEffectBlur.h" +#include "../../Include/RmlUi/Core/PropertyDefinition.h" +#include "Memory.h" + +namespace Rml { + +FontEffectBlur::FontEffectBlur() +{ + width = 0; + SetLayer(Layer::Back); +} + +FontEffectBlur::~FontEffectBlur() {} + +bool FontEffectBlur::HasUniqueTexture() const +{ + return true; +} + +bool FontEffectBlur::Initialise(int _width) +{ + if (_width <= 0) + return false; + + width = _width; + + const float std_dev = .4f * float(width); + const float two_variance = 2.f * std_dev * std_dev; + const float gain = 1.f / Math::SquareRoot(Math::RMLUI_PI * two_variance); + + float sum_weight = 0.f; + + // We separate the blur filter into two passes, horizontal and vertical, for performance reasons. + filter_x.Initialise(Vector2i(width, 0), FilterOperation::Sum); + filter_y.Initialise(Vector2i(0, width), FilterOperation::Sum); + + for (int x = -width; x <= width; ++x) + { + float weight = gain * Math::Exp(-Math::SquareRoot(float(x * x) / two_variance)); + + filter_x[0][x + width] = weight; + filter_y[x + width][0] = weight; + sum_weight += weight; + } + + // Normalize the kernels + for (int x = -width; x <= width; ++x) + { + filter_x[0][x + width] /= sum_weight; + filter_y[x + width][0] /= sum_weight; + } + + return true; +} + +bool FontEffectBlur::GetGlyphMetrics(Vector2i& origin, Vector2i& dimensions, const FontGlyph& /*glyph*/) const +{ + if (dimensions.x * dimensions.y > 0) + { + origin.x -= width; + origin.y -= width; + + dimensions.y += 2 * width; + dimensions.x += 2 * width; + + return true; + } + + return false; +} + +void FontEffectBlur::GenerateGlyphTexture(byte* destination_data, const Vector2i destination_dimensions, int destination_stride, + const FontGlyph& glyph) const +{ + const Vector2i buf_dimensions = destination_dimensions; + const int buf_stride = buf_dimensions.x; + const int buf_size = buf_dimensions.x * buf_dimensions.y; + DynamicArray> x_output(buf_size); + + filter_x.Run(x_output.data(), buf_dimensions, buf_stride, ColorFormat::A8, glyph.bitmap_data, glyph.bitmap_dimensions, Vector2i(width), + glyph.color_format); + + filter_y.Run(destination_data, destination_dimensions, destination_stride, ColorFormat::RGBA8, x_output.data(), buf_dimensions, Vector2i(0), + ColorFormat::A8); + + FillColorValuesFromAlpha(destination_data, destination_dimensions, destination_stride); +} + +FontEffectBlurInstancer::FontEffectBlurInstancer() : id_width(PropertyId::Invalid), id_color(PropertyId::Invalid) +{ + id_width = RegisterProperty("width", "1px", true).AddParser("length").GetId(); + id_color = RegisterProperty("color", "white", false).AddParser("color").GetId(); + RegisterShorthand("font-effect", "width, color", ShorthandType::FallThrough); +} + +FontEffectBlurInstancer::~FontEffectBlurInstancer() {} + +SharedPtr FontEffectBlurInstancer::InstanceFontEffect(const String& /*name*/, const PropertyDictionary& properties) +{ + float width = properties.GetProperty(id_width)->Get(); + Colourb color = properties.GetProperty(id_color)->Get(); + + auto font_effect = MakeShared(); + if (font_effect->Initialise(int(width))) + { + font_effect->SetColour(color); + return font_effect; + } + + return nullptr; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEffectBlur.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEffectBlur.h new file mode 100644 index 00000000..c59f1d22 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEffectBlur.h @@ -0,0 +1,46 @@ +#pragma once + +#include "../../Include/RmlUi/Core/ConvolutionFilter.h" +#include "../../Include/RmlUi/Core/FontEffect.h" +#include "../../Include/RmlUi/Core/FontEffectInstancer.h" + +namespace Rml { + +/** + A concrete font effect for rendering Gaussian blurred text. + */ + +class FontEffectBlur : public FontEffect { +public: + FontEffectBlur(); + virtual ~FontEffectBlur(); + + bool Initialise(int width); + + bool HasUniqueTexture() const override; + + bool GetGlyphMetrics(Vector2i& origin, Vector2i& dimensions, const FontGlyph& glyph) const override; + + void GenerateGlyphTexture(byte* destination_data, Vector2i destination_dimensions, int destination_stride, const FontGlyph& glyph) const override; + +private: + int width; + ConvolutionFilter filter_x, filter_y; +}; + +/** + A concrete font effect instancer for the blur effect. + */ + +class FontEffectBlurInstancer : public FontEffectInstancer { +public: + FontEffectBlurInstancer(); + virtual ~FontEffectBlurInstancer(); + + SharedPtr InstanceFontEffect(const String& name, const PropertyDictionary& properties) override; + +private: + PropertyId id_width, id_color; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEffectGlow.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEffectGlow.cpp new file mode 100644 index 00000000..18deb806 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEffectGlow.cpp @@ -0,0 +1,154 @@ +#include "FontEffectGlow.h" +#include "../../Include/RmlUi/Core/PropertyDefinition.h" +#include "Memory.h" + +namespace Rml { + +FontEffectGlow::FontEffectGlow() +{ + width_blur = 0; + width_outline = 0; + combined_width = 0; + SetLayer(Layer::Back); +} + +FontEffectGlow::~FontEffectGlow() {} + +bool FontEffectGlow::HasUniqueTexture() const +{ + return true; +} + +bool FontEffectGlow::Initialise(int _width_outline, int _width_blur, Vector2i _offset) +{ + if (_width_outline < 0 || _width_blur < 0) + return false; + + width_outline = _width_outline; + width_blur = _width_blur; + combined_width = width_blur + width_outline; + offset = _offset; + + // Outline filter. + filter_outline.Initialise(width_outline, FilterOperation::Dilation); + for (int x = -width_outline; x <= width_outline; ++x) + { + for (int y = -width_outline; y <= width_outline; ++y) + { + float weight = 1; + + float distance = Math::SquareRoot(float(x * x + y * y)); + if (distance > width_outline) + { + weight = (width_outline + 1) - distance; + weight = Math::Max(weight, 0.0f); + } + + filter_outline[x + width_outline][y + width_outline] = weight; + } + } + + // Gaussian blur filter + const float std_dev = (width_blur == 0 ? 1.f : .4f * float(width_blur)); + const float two_variance = 2.f * std_dev * std_dev; + const float gain = 1.f / Math::SquareRoot(Math::RMLUI_PI * two_variance); + + float sum_weight = 0.f; + + // We separate the blur filter into two passes, horizontal and vertical, for performance reasons. + filter_blur_x.Initialise(Vector2i(width_blur, 0), FilterOperation::Sum); + filter_blur_y.Initialise(Vector2i(0, width_blur), FilterOperation::Sum); + + for (int x = -width_blur; x <= width_blur; ++x) + { + float weight = gain * Math::Exp(-Math::SquareRoot(float(x * x) / two_variance)); + + filter_blur_x[0][x + width_blur] = weight; + filter_blur_y[x + width_blur][0] = weight; + sum_weight += weight; + } + + // Normalize the kernels + for (int x = -width_blur; x <= width_blur; ++x) + { + filter_blur_x[0][x + width_blur] /= sum_weight; + filter_blur_y[x + width_blur][0] /= sum_weight; + } + + return true; +} + +bool FontEffectGlow::GetGlyphMetrics(Vector2i& origin, Vector2i& dimensions, const FontGlyph& /*glyph*/) const +{ + if (dimensions.x * dimensions.y > 0) + { + origin.x += offset.x - combined_width; + origin.y += offset.y - combined_width; + + dimensions.x += 2 * combined_width; + dimensions.y += 2 * combined_width; + + return true; + } + + return false; +} + +void FontEffectGlow::GenerateGlyphTexture(byte* destination_data, const Vector2i destination_dimensions, int destination_stride, + const FontGlyph& glyph) const +{ + const Vector2i buf_dimensions = destination_dimensions; + const int buf_stride = buf_dimensions.x; + const int buf_size = buf_dimensions.x * buf_dimensions.y; + + DynamicArray> outline_output(buf_size); + DynamicArray> blur_x_output(buf_size); + + filter_outline.Run(outline_output.data(), buf_dimensions, buf_stride, ColorFormat::A8, glyph.bitmap_data, glyph.bitmap_dimensions, + Vector2i(combined_width), glyph.color_format); + + filter_blur_x.Run(blur_x_output.data(), buf_dimensions, buf_stride, ColorFormat::A8, outline_output.data(), buf_dimensions, Vector2i(0), + ColorFormat::A8); + + filter_blur_y.Run(destination_data, destination_dimensions, destination_stride, ColorFormat::RGBA8, blur_x_output.data(), buf_dimensions, + Vector2i(0), ColorFormat::A8); + + FillColorValuesFromAlpha(destination_data, destination_dimensions, destination_stride); +} + +FontEffectGlowInstancer::FontEffectGlowInstancer() : + id_width_outline(PropertyId::Invalid), id_width_blur(PropertyId::Invalid), id_color(PropertyId::Invalid) +{ + id_width_outline = RegisterProperty("width-outline", "1px", true).AddParser("length").GetId(); + id_width_blur = RegisterProperty("width-blur", "-1px", true).AddParser("length").GetId(); + id_offset_x = RegisterProperty("offset-x", "0px", true).AddParser("length").GetId(); + id_offset_y = RegisterProperty("offset-y", "0px", true).AddParser("length").GetId(); + id_color = RegisterProperty("color", "white", false).AddParser("color").GetId(); + RegisterShorthand("font-effect", "width-outline, width-blur, offset-x, offset-y, color", ShorthandType::FallThrough); +} + +FontEffectGlowInstancer::~FontEffectGlowInstancer() {} + +SharedPtr FontEffectGlowInstancer::InstanceFontEffect(const String& /*name*/, const PropertyDictionary& properties) +{ + Vector2i offset; + int width_outline = properties.GetProperty(id_width_outline)->Get(); + int width_blur = properties.GetProperty(id_width_blur)->Get(); + offset.x = properties.GetProperty(id_offset_x)->Get(); + offset.y = properties.GetProperty(id_offset_y)->Get(); + Colourb color = properties.GetProperty(id_color)->Get(); + + if (width_blur < 0) + width_blur = width_outline; + + auto font_effect = MakeShared(); + if (font_effect->Initialise(width_outline, width_blur, offset)) + { + font_effect->SetColour(color); + return font_effect; + } + + return nullptr; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEffectGlow.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEffectGlow.h new file mode 100644 index 00000000..f16f2d96 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEffectGlow.h @@ -0,0 +1,50 @@ +#pragma once + +#include "../../Include/RmlUi/Core/ConvolutionFilter.h" +#include "../../Include/RmlUi/Core/FontEffect.h" +#include "../../Include/RmlUi/Core/FontEffectInstancer.h" + +namespace Rml { + +/** + A font effect for rendering glow around text. + + Glow consists of an outline pass followed by a Gaussian blur pass. + + */ + +class FontEffectGlow : public FontEffect { +public: + FontEffectGlow(); + virtual ~FontEffectGlow(); + + bool Initialise(int width_outline, int width_blur, Vector2i offset); + + bool HasUniqueTexture() const override; + + bool GetGlyphMetrics(Vector2i& origin, Vector2i& dimensions, const FontGlyph& glyph) const override; + + void GenerateGlyphTexture(byte* destination_data, Vector2i destination_dimensions, int destination_stride, const FontGlyph& glyph) const override; + +private: + int width_outline, width_blur, combined_width; + Vector2i offset; + ConvolutionFilter filter_outline, filter_blur_x, filter_blur_y; +}; + +/** + A concrete font effect instancer for the glow effect. + */ + +class FontEffectGlowInstancer : public FontEffectInstancer { +public: + FontEffectGlowInstancer(); + virtual ~FontEffectGlowInstancer(); + + SharedPtr InstanceFontEffect(const String& name, const PropertyDictionary& properties) override; + +private: + PropertyId id_width_outline, id_width_blur, id_offset_x, id_offset_y, id_color; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEffectInstancer.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEffectInstancer.cpp new file mode 100644 index 00000000..2af24ca5 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEffectInstancer.cpp @@ -0,0 +1,29 @@ +#include "../../Include/RmlUi/Core/FontEffectInstancer.h" +#include "../../Include/RmlUi/Core/PropertyDefinition.h" + +namespace Rml { + +FontEffectInstancer::FontEffectInstancer() : properties(10, 10) {} + +FontEffectInstancer::~FontEffectInstancer() {} + +const PropertySpecification& FontEffectInstancer::GetPropertySpecification() const +{ + return properties; +} + +PropertyDefinition& FontEffectInstancer::RegisterProperty(const String& property_name, const String& default_value, bool affects_generation) +{ + PropertyDefinition& definition = properties.RegisterProperty(property_name, default_value, false, false); + if (affects_generation) + volatile_properties.insert(definition.GetId()); + + return definition; +} + +ShorthandId FontEffectInstancer::RegisterShorthand(const String& shorthand_name, const String& property_names, ShorthandType type) +{ + return properties.RegisterShorthand(shorthand_name, property_names, type); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEffectOutline.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEffectOutline.cpp new file mode 100644 index 00000000..b24abf6d --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEffectOutline.cpp @@ -0,0 +1,96 @@ +#include "FontEffectOutline.h" +#include "../../Include/RmlUi/Core/PropertyDefinition.h" + +namespace Rml { + +FontEffectOutline::FontEffectOutline() +{ + width = 0; + SetLayer(Layer::Back); +} + +FontEffectOutline::~FontEffectOutline() {} + +bool FontEffectOutline::HasUniqueTexture() const +{ + return true; +} + +bool FontEffectOutline::Initialise(int _width) +{ + if (_width <= 0) + return false; + + width = _width; + + filter.Initialise(width, FilterOperation::Dilation); + for (int x = -width; x <= width; ++x) + { + for (int y = -width; y <= width; ++y) + { + float weight = 1; + + float distance = Math::SquareRoot(float(x * x + y * y)); + if (distance > width) + { + weight = (width + 1) - distance; + weight = Math::Max(weight, 0.0f); + } + + filter[x + width][y + width] = weight; + } + } + + return true; +} + +bool FontEffectOutline::GetGlyphMetrics(Vector2i& origin, Vector2i& dimensions, const FontGlyph& /*glyph*/) const +{ + if (dimensions.x * dimensions.y > 0) + { + origin.x -= width; + origin.y -= width; + + dimensions.x += 2 * width; + dimensions.y += 2 * width; + + return true; + } + + return false; +} + +void FontEffectOutline::GenerateGlyphTexture(byte* destination_data, const Vector2i destination_dimensions, int destination_stride, + const FontGlyph& glyph) const +{ + filter.Run(destination_data, destination_dimensions, destination_stride, ColorFormat::RGBA8, glyph.bitmap_data, glyph.bitmap_dimensions, + Vector2i(width), glyph.color_format); + + FillColorValuesFromAlpha(destination_data, destination_dimensions, destination_stride); +} + +FontEffectOutlineInstancer::FontEffectOutlineInstancer() : id_width(PropertyId::Invalid), id_color(PropertyId::Invalid) +{ + id_width = RegisterProperty("width", "1px", true).AddParser("length").GetId(); + id_color = RegisterProperty("color", "white", false).AddParser("color").GetId(); + RegisterShorthand("font-effect", "width, color", ShorthandType::FallThrough); +} + +FontEffectOutlineInstancer::~FontEffectOutlineInstancer() {} + +SharedPtr FontEffectOutlineInstancer::InstanceFontEffect(const String& /*name*/, const PropertyDictionary& properties) +{ + float width = properties.GetProperty(id_width)->Get(); + Colourb color = properties.GetProperty(id_color)->Get(); + + auto font_effect = MakeShared(); + if (font_effect->Initialise(int(width))) + { + font_effect->SetColour(color); + return font_effect; + } + + return nullptr; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEffectOutline.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEffectOutline.h new file mode 100644 index 00000000..6b163fe7 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEffectOutline.h @@ -0,0 +1,46 @@ +#pragma once + +#include "../../Include/RmlUi/Core/ConvolutionFilter.h" +#include "../../Include/RmlUi/Core/FontEffect.h" +#include "../../Include/RmlUi/Core/FontEffectInstancer.h" + +namespace Rml { + +/** + A concrete font effect for rendering outlines around text. + */ + +class FontEffectOutline : public FontEffect { +public: + FontEffectOutline(); + virtual ~FontEffectOutline(); + + bool Initialise(int width); + + bool HasUniqueTexture() const override; + + bool GetGlyphMetrics(Vector2i& origin, Vector2i& dimensions, const FontGlyph& glyph) const override; + + void GenerateGlyphTexture(byte* destination_data, Vector2i destination_dimensions, int destination_stride, const FontGlyph& glyph) const override; + +private: + int width; + ConvolutionFilter filter; +}; + +/** + A concrete font effect instancer for the outline effect. + */ + +class FontEffectOutlineInstancer : public FontEffectInstancer { +public: + FontEffectOutlineInstancer(); + virtual ~FontEffectOutlineInstancer(); + + SharedPtr InstanceFontEffect(const String& name, const PropertyDictionary& properties) override; + +private: + PropertyId id_width, id_color; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEffectShadow.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEffectShadow.cpp new file mode 100644 index 00000000..70e649d0 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEffectShadow.cpp @@ -0,0 +1,62 @@ +#include "FontEffectShadow.h" +#include "../../Include/RmlUi/Core/PropertyDefinition.h" + +namespace Rml { + +FontEffectShadow::FontEffectShadow() : offset(0, 0) +{ + SetLayer(Layer::Back); +} + +FontEffectShadow::~FontEffectShadow() {} + +bool FontEffectShadow::Initialise(const Vector2i _offset) +{ + offset = _offset; + return true; +} + +bool FontEffectShadow::HasUniqueTexture() const +{ + return false; +} + +bool FontEffectShadow::GetGlyphMetrics(Vector2i& origin, Vector2i& /*dimensions*/, const FontGlyph& glyph) const +{ + if (glyph.color_format == ColorFormat::RGBA8) + return false; + + origin += offset; + return true; +} + +FontEffectShadowInstancer::FontEffectShadowInstancer() : + id_offset_x(PropertyId::Invalid), id_offset_y(PropertyId::Invalid), id_color(PropertyId::Invalid) +{ + id_offset_x = RegisterProperty("offset-x", "0px", true).AddParser("length").GetId(); + id_offset_y = RegisterProperty("offset-y", "0px", true).AddParser("length").GetId(); + id_color = RegisterProperty("color", "white", false).AddParser("color").GetId(); + RegisterShorthand("offset", "offset-x, offset-y", ShorthandType::FallThrough); + RegisterShorthand("font-effect", "offset-x, offset-y, color", ShorthandType::FallThrough); +} + +FontEffectShadowInstancer::~FontEffectShadowInstancer() {} + +SharedPtr FontEffectShadowInstancer::InstanceFontEffect(const String& /*name*/, const PropertyDictionary& properties) +{ + Vector2i offset; + offset.x = properties.GetProperty(id_offset_x)->Get(); + offset.y = properties.GetProperty(id_offset_y)->Get(); + Colourb color = properties.GetProperty(id_color)->Get(); + + auto font_effect = MakeShared(); + if (font_effect->Initialise(offset)) + { + font_effect->SetColour(color); + return font_effect; + } + + return nullptr; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEffectShadow.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEffectShadow.h new file mode 100644 index 00000000..c0315a10 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEffectShadow.h @@ -0,0 +1,42 @@ +#pragma once + +#include "../../Include/RmlUi/Core/FontEffect.h" +#include "../../Include/RmlUi/Core/FontEffectInstancer.h" + +namespace Rml { + +/** + A concrete font effect for rendering text shadows. + */ + +class FontEffectShadow : public FontEffect { +public: + FontEffectShadow(); + virtual ~FontEffectShadow(); + + bool Initialise(Vector2i offset); + + bool HasUniqueTexture() const override; + + bool GetGlyphMetrics(Vector2i& origin, Vector2i& dimensions, const FontGlyph& glyph) const override; + +private: + Vector2i offset; +}; + +/** + A concrete font effect instancer for the shadow effect. + */ + +class FontEffectShadowInstancer : public FontEffectInstancer { +public: + FontEffectShadowInstancer(); + virtual ~FontEffectShadowInstancer(); + + SharedPtr InstanceFontEffect(const String& name, const PropertyDictionary& properties) override; + +private: + PropertyId id_offset_x, id_offset_y, id_color; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/CMakeLists.txt b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/CMakeLists.txt new file mode 100644 index 00000000..934bdcf4 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/CMakeLists.txt @@ -0,0 +1,22 @@ +# RMLUI_CMAKE_MINIMUM_VERSION_RAISE_NOTICE: +# Using absolute paths to prevent improper interpretation of relative paths Relative paths can be used once the minimum +# CMake version is greater or equal than CMake 3.13 +target_sources(rmlui_core PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/FontEngineInterfaceDefault.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/FontEngineInterfaceDefault.h" + "${CMAKE_CURRENT_SOURCE_DIR}/FontFace.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/FontFace.h" + "${CMAKE_CURRENT_SOURCE_DIR}/FontFaceHandleDefault.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/FontFaceHandleDefault.h" + "${CMAKE_CURRENT_SOURCE_DIR}/FontFaceLayer.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/FontFaceLayer.h" + "${CMAKE_CURRENT_SOURCE_DIR}/FontFamily.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/FontFamily.h" + "${CMAKE_CURRENT_SOURCE_DIR}/FontProvider.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/FontProvider.h" + "${CMAKE_CURRENT_SOURCE_DIR}/FontTypes.h" + "${CMAKE_CURRENT_SOURCE_DIR}/FreeTypeInterface.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/FreeTypeInterface.h" +) + +target_compile_definitions(rmlui_core PRIVATE "RMLUI_FONT_ENGINE_FREETYPE") diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FontEngineInterfaceDefault.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FontEngineInterfaceDefault.cpp new file mode 100644 index 00000000..7f7f9119 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FontEngineInterfaceDefault.cpp @@ -0,0 +1,80 @@ +#include "FontEngineInterfaceDefault.h" +#include "../../../Include/RmlUi/Core/StringUtilities.h" +#include "FontFaceHandleDefault.h" +#include "FontProvider.h" + +namespace Rml { + +void FontEngineInterfaceDefault::Initialize() +{ + FontProvider::Initialise(); +} + +void FontEngineInterfaceDefault::Shutdown() +{ + FontProvider::Shutdown(); +} + +bool FontEngineInterfaceDefault::LoadFontFace(const String& file_name, int face_index, bool fallback_face, Style::FontWeight weight) +{ + return FontProvider::LoadFontFace(file_name, face_index, fallback_face, weight); +} + +bool FontEngineInterfaceDefault::LoadFontFace(const String& file_name, int face_index, const String& font_family, Style::FontStyle style, + Style::FontWeight weight, bool fallback_face) +{ + return FontProvider::LoadFontFace(file_name, face_index, font_family, style, weight, fallback_face); +} + +bool FontEngineInterfaceDefault::LoadFontFace(Span data, int face_index, const String& font_family, Style::FontStyle style, + Style::FontWeight weight, bool fallback_face) +{ + return FontProvider::LoadFontFace(data, face_index, font_family, style, weight, fallback_face); +} + +FontFaceHandle FontEngineInterfaceDefault::GetFontFaceHandle(const String& family, Style::FontStyle style, Style::FontWeight weight, int size) +{ + auto handle = FontProvider::GetFontFaceHandle(family, style, weight, size); + return reinterpret_cast(handle); +} + +FontEffectsHandle FontEngineInterfaceDefault::PrepareFontEffects(FontFaceHandle handle, const FontEffectList& font_effects) +{ + auto handle_default = reinterpret_cast(handle); + return (FontEffectsHandle)handle_default->GenerateLayerConfiguration(font_effects); +} + +const FontMetrics& FontEngineInterfaceDefault::GetFontMetrics(FontFaceHandle handle) +{ + auto handle_default = reinterpret_cast(handle); + return handle_default->GetFontMetrics(); +} + +int FontEngineInterfaceDefault::GetStringWidth(FontFaceHandle handle, StringView string, const TextShapingContext& text_shaping_context, + Character prior_character) +{ + auto handle_default = reinterpret_cast(handle); + return handle_default->GetStringWidth(string, text_shaping_context, prior_character); +} + +int FontEngineInterfaceDefault::GenerateString(RenderManager& render_manager, FontFaceHandle handle, FontEffectsHandle font_effects_handle, + StringView string, Vector2f position, ColourbPremultiplied colour, float opacity, const TextShapingContext& text_shaping_context, + TexturedMeshList& mesh_list) +{ + auto handle_default = reinterpret_cast(handle); + return handle_default->GenerateString(render_manager, mesh_list, string, position, colour, opacity, text_shaping_context, + (int)font_effects_handle); +} + +int FontEngineInterfaceDefault::GetVersion(FontFaceHandle handle) +{ + auto handle_default = reinterpret_cast(handle); + return handle_default->GetVersion(); +} + +void FontEngineInterfaceDefault::ReleaseFontResources() +{ + FontProvider::ReleaseFontResources(); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FontEngineInterfaceDefault.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FontEngineInterfaceDefault.h new file mode 100644 index 00000000..701ac952 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FontEngineInterfaceDefault.h @@ -0,0 +1,52 @@ +#pragma once + +#include "../../../Include/RmlUi/Core/FontEngineInterface.h" + +namespace Rml { + +class RMLUICORE_API FontEngineInterfaceDefault : public FontEngineInterface { +public: + /// Called when RmlUi is being initialized. + void Initialize() override; + + /// Called when RmlUi is being shut down. + void Shutdown() override; + + /// Adds a new font face to the database. The face's family, style and weight will be determined from the face itself. + bool LoadFontFace(const String& file_name, int face_index, bool fallback_face, Style::FontWeight weight) override; + + /// Adds a new font face to the database using the provided family, style and weight. + bool LoadFontFace(const String& file_name, int face_index, const String& font_family, Style::FontStyle style, Style::FontWeight weight, + bool fallback_face) override; + + /// Adds a new font face to the database using the provided family, style and weight. + bool LoadFontFace(Span data, int face_index, const String& font_family, Style::FontStyle style, Style::FontWeight weight, + bool fallback_face) override; + + /// Returns a handle to a font face that can be used to position and render text. This will return the closest match + /// it can find, but in the event a font family is requested that does not exist, NULL will be returned instead of a + /// valid handle. + FontFaceHandle GetFontFaceHandle(const String& family, Style::FontStyle style, Style::FontWeight weight, int size) override; + + /// Prepares for font effects by configuring a new, or returning an existing, layer configuration. + FontEffectsHandle PrepareFontEffects(FontFaceHandle handle, const FontEffectList& font_effects) override; + + /// Returns the font metrics of the given font face. + const FontMetrics& GetFontMetrics(FontFaceHandle handle) override; + + /// Returns the width a string will take up if rendered with this handle. + int GetStringWidth(FontFaceHandle handle, StringView string, const TextShapingContext& text_shaping_context, Character prior_character) override; + + /// Generates the geometry required to render a single line of text. + int GenerateString(RenderManager& render_manager, FontFaceHandle face_handle, FontEffectsHandle effects_handle, StringView string, + Vector2f position, ColourbPremultiplied colour, float opacity, const TextShapingContext& text_shaping_context, + TexturedMeshList& mesh_list) override; + + /// Returns the current version of the font face. + int GetVersion(FontFaceHandle handle) override; + + /// Releases resources owned by sized font faces, including their textures and rendered glyphs. + void ReleaseFontResources() override; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FontFace.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FontFace.cpp new file mode 100644 index 00000000..fec89083 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FontFace.cpp @@ -0,0 +1,65 @@ +#include "FontFace.h" +#include "../../../Include/RmlUi/Core/Log.h" +#include "FontFaceHandleDefault.h" +#include "FreeTypeInterface.h" + +namespace Rml { + +FontFace::FontFace(FontFaceHandleFreetype _face, Style::FontStyle _style, Style::FontWeight _weight) +{ + style = _style; + weight = _weight; + face = _face; +} + +FontFace::~FontFace() +{ + if (face) + FreeType::ReleaseFace(face); +} + +Style::FontStyle FontFace::GetStyle() const +{ + return style; +} + +Style::FontWeight FontFace::GetWeight() const +{ + return weight; +} + +FontFaceHandleDefault* FontFace::GetHandle(int size, bool load_default_glyphs) +{ + auto it = handles.find(size); + if (it != handles.end()) + return it->second.get(); + + // See if this face has been released. + if (!face) + { + Log::Message(Log::LT_WARNING, "Font face has been released, unable to generate new handle."); + return nullptr; + } + + // Construct and initialise the new handle. + auto handle = MakeUnique(); + if (!handle->Initialize(face, size, load_default_glyphs)) + { + handles[size] = nullptr; + return nullptr; + } + + FontFaceHandleDefault* result = handle.get(); + + // Save the new handle to the font face + handles[size] = std::move(handle); + + return result; +} + +void FontFace::ReleaseFontResources() +{ + HandleMap().swap(handles); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FontFace.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FontFace.h new file mode 100644 index 00000000..369eda52 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FontFace.h @@ -0,0 +1,38 @@ +#pragma once + +#include "../../../Include/RmlUi/Core/StyleTypes.h" +#include "FontTypes.h" + +namespace Rml { + +class FontFaceHandleDefault; + +class FontFace { +public: + FontFace(FontFaceHandleFreetype face, Style::FontStyle style, Style::FontWeight weight); + ~FontFace(); + + Style::FontStyle GetStyle() const; + Style::FontWeight GetWeight() const; + + /// Returns a handle for positioning and rendering this face at the given size. + /// @param[in] size The size of the desired handle, in points. + /// @param[in] load_default_glyphs True to load the default set of glyph (ASCII range). + /// @return The font handle. + FontFaceHandleDefault* GetHandle(int size, bool load_default_glyphs); + + /// Releases resources owned by sized font faces, including their textures and rendered glyphs. + void ReleaseFontResources(); + +private: + Style::FontStyle style; + Style::FontWeight weight; + + // Key is font size + using HandleMap = UnorderedMap>; + HandleMap handles; + + FontFaceHandleFreetype face; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FontFaceHandleDefault.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FontFaceHandleDefault.cpp new file mode 100644 index 00000000..7be3327a --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FontFaceHandleDefault.cpp @@ -0,0 +1,466 @@ +#include "FontFaceHandleDefault.h" +#include "../../../Include/RmlUi/Core/Profiling.h" +#include "../../../Include/RmlUi/Core/StringUtilities.h" +#include "../../../Include/RmlUi/Core/StyleTypes.h" +#include "../TextureLayout.h" +#include "FontFaceLayer.h" +#include "FontProvider.h" +#include "FreeTypeInterface.h" +#include +#include + +namespace Rml { + +static constexpr char32_t KerningCache_AsciiSubsetBegin = 32; +static constexpr char32_t KerningCache_AsciiSubsetLast = 126; + +FontFaceHandleDefault::FontFaceHandleDefault() +{ + base_layer = nullptr; + metrics = {}; + ft_face = 0; +} + +FontFaceHandleDefault::~FontFaceHandleDefault() +{ + glyphs.clear(); + layers.clear(); +} + +bool FontFaceHandleDefault::Initialize(FontFaceHandleFreetype face, int font_size, bool load_default_glyphs) +{ + ft_face = face; + + RMLUI_ASSERTMSG(layer_configurations.empty(), "Initialize must only be called once."); + + if (!FreeType::InitialiseFaceHandle(ft_face, font_size, glyphs, metrics, load_default_glyphs)) + return false; + + has_kerning = FreeType::HasKerning(ft_face); + FillKerningPairCache(); + + // Generate the default layer and layer configuration. + base_layer = GetOrCreateLayer(nullptr); + layer_configurations.push_back(LayerConfiguration{base_layer}); + + return true; +} + +const FontMetrics& FontFaceHandleDefault::GetFontMetrics() const +{ + return metrics; +} + +const FontGlyphMap& FontFaceHandleDefault::GetGlyphs() const +{ + return glyphs; +} + +int FontFaceHandleDefault::GetStringWidth(StringView string, const TextShapingContext& text_shaping_context, Character prior_character) +{ + RMLUI_ZoneScoped; + + bool has_set_size = false; + bool is_kerning_enabled = IsKerningEnabled(text_shaping_context); + int width = 0; + for (auto it_string = StringIteratorU8(string); it_string; ++it_string) + { + Character character = *it_string; + + const FontGlyph* glyph = GetOrAppendGlyph(character); + if (!glyph) + continue; + + // Adjust the cursor for the kerning between this character and the previous one. + if (is_kerning_enabled) + width += GetKerning(prior_character, character, has_set_size); + + // Adjust the cursor for this character's advance. + width += glyph->advance; + width += (int)text_shaping_context.letter_spacing; + + prior_character = character; + } + + return Math::Max(width, 0); +} + +int FontFaceHandleDefault::GenerateLayerConfiguration(const FontEffectList& font_effects) +{ + if (font_effects.empty()) + return 0; + + // Check each existing configuration for a match with this arrangement of effects. + int configuration_index = 1; + for (; configuration_index < (int)layer_configurations.size(); ++configuration_index) + { + const LayerConfiguration& configuration = layer_configurations[configuration_index]; + + // Check the size is correct. For a match, there should be one layer in the configuration + // plus an extra for the base layer. + if (configuration.size() != font_effects.size() + 1) + continue; + + // Check through each layer, checking it was created by the same effect as the one we're + // checking. + size_t effect_index = 0; + for (size_t i = 0; i < configuration.size(); ++i) + { + // Skip the base layer ... + if (configuration[i]->GetFontEffect() == nullptr) + continue; + + // If the ith layer's effect doesn't match the equivalent effect, then this + // configuration can't match. + if (configuration[i]->GetFontEffect() != font_effects[effect_index].get()) + break; + + // Check the next one ... + ++effect_index; + } + + if (effect_index == font_effects.size()) + return configuration_index; + } + + // No match, so we have to generate a new layer configuration. + layer_configurations.push_back(LayerConfiguration()); + LayerConfiguration& layer_configuration = layer_configurations.back(); + + bool added_base_layer = false; + + for (size_t i = 0; i < font_effects.size(); ++i) + { + if (!added_base_layer && font_effects[i]->GetLayer() == FontEffect::Layer::Front) + { + layer_configuration.push_back(base_layer); + added_base_layer = true; + } + + FontFaceLayer* new_layer = GetOrCreateLayer(font_effects[i]); + layer_configuration.push_back(new_layer); + } + + // Add the base layer now if we still haven't added it. + if (!added_base_layer) + layer_configuration.push_back(base_layer); + + return (int)(layer_configurations.size() - 1); +} + +bool FontFaceHandleDefault::GenerateLayerTexture(Vector& texture_data, Vector2i& texture_dimensions, const FontEffect* font_effect, + int texture_id, int handle_version) const +{ + if (handle_version != version) + { + RMLUI_ERRORMSG("While generating font layer texture: Handle version mismatch in texture vs font-face."); + return false; + } + + auto it = std::find_if(layers.begin(), layers.end(), [font_effect](const EffectLayerPair& pair) { return pair.font_effect == font_effect; }); + + if (it == layers.end()) + { + RMLUI_ERRORMSG("While generating font layer texture: Layer id not found."); + return false; + } + + return it->layer->GenerateTexture(texture_data, texture_dimensions, texture_id, glyphs); +} + +int FontFaceHandleDefault::GenerateString(RenderManager& render_manager, TexturedMeshList& mesh_list, StringView string, const Vector2f position, + const ColourbPremultiplied colour, const float opacity, const TextShapingContext& text_shaping_context, const int layer_configuration_index) +{ + RMLUI_ASSERT(layer_configuration_index >= 0); + RMLUI_ASSERT(layer_configuration_index < (int)layer_configurations.size()); + + int geometry_index = 0; + int line_width = 0; + bool has_set_size = false; + bool is_kerning_enabled = IsKerningEnabled(text_shaping_context); + + UpdateLayersOnDirty(); + + // Fetch the requested configuration and generate the geometry for each one. + const LayerConfiguration& layer_configuration = layer_configurations[layer_configuration_index]; + + // Each texture represents one geometry. + const int num_geometries = std::accumulate(layer_configuration.begin(), layer_configuration.end(), 0, + [](int sum, const FontFaceLayer* layer) { return sum + layer->GetNumTextures(); }); + + mesh_list.resize(num_geometries); + + for (size_t layer_index = 0; layer_index < layer_configuration.size(); ++layer_index) + { + FontFaceLayer* layer = layer_configuration[layer_index]; + + ColourbPremultiplied layer_colour; + if (layer == base_layer) + layer_colour = colour; + else + layer_colour = layer->GetColour(opacity); + + const int num_textures = layer->GetNumTextures(); + if (num_textures == 0) + continue; + + RMLUI_ASSERT(geometry_index + num_textures <= (int)mesh_list.size()); + + line_width = 0; + Character prior_character = Character::Null; + + // Set the mesh and textures to the geometries. + for (int tex_index = 0; tex_index < num_textures; ++tex_index) + mesh_list[geometry_index + tex_index].texture = layer->GetTexture(render_manager, tex_index); + + mesh_list[geometry_index].mesh.indices.reserve(string.size() * 6); + mesh_list[geometry_index].mesh.vertices.reserve(string.size() * 4); + + for (auto it_string = StringIteratorU8(string); it_string; ++it_string) + { + Character character = *it_string; + + const FontGlyph* glyph = GetOrAppendGlyph(character); + if (!glyph) + continue; + + // Adjust the cursor for the kerning between this character and the previous one. + if (is_kerning_enabled) + line_width += GetKerning(prior_character, character, has_set_size); + + ColourbPremultiplied glyph_color = layer_colour; + // Use white vertex colors on RGB glyphs. + if (layer == base_layer && glyph->color_format == ColorFormat::RGBA8) + glyph_color = ColourbPremultiplied(layer_colour.alpha, layer_colour.alpha); + + layer->GenerateGeometry(&mesh_list[geometry_index], character, Vector2f(position.x + line_width, position.y), glyph_color); + + line_width += glyph->advance; + line_width += (int)text_shaping_context.letter_spacing; + prior_character = character; + } + + geometry_index += num_textures; + } + + return Math::Max(line_width, 0); +} + +bool FontFaceHandleDefault::UpdateLayersOnDirty() +{ + bool result = false; + + // If we are dirty, regenerate all the layers and increment the version + if (is_layers_dirty && base_layer) + { + is_layers_dirty = false; + ++version; + + // Regenerate all the layers. + // Note: The layer regeneration needs to happen in the order in which the layers were created, + // otherwise we may end up cloning a layer which has not yet been regenerated. This means trouble! + for (auto& pair : layers) + { + GenerateLayer(pair.layer.get()); + } + + result = true; + } + + return result; +} + +int FontFaceHandleDefault::GetVersion() const +{ + return version; +} + +bool FontFaceHandleDefault::AppendGlyph(Character character) +{ + bool result = FreeType::AppendGlyph(ft_face, metrics.size, character, glyphs); + return result; +} + +void FontFaceHandleDefault::FillKerningPairCache() +{ + if (!has_kerning) + return; + + for (char32_t i = KerningCache_AsciiSubsetBegin; i <= KerningCache_AsciiSubsetLast; i++) + { + for (char32_t j = KerningCache_AsciiSubsetBegin; j <= KerningCache_AsciiSubsetLast; j++) + { + const bool first_iteration = (i == KerningCache_AsciiSubsetBegin && j == KerningCache_AsciiSubsetBegin); + + // Fetch the kerning from the font face. Submit zero font size on subsequent iterations for performance reasons. + const int kerning = FreeType::GetKerning(ft_face, first_iteration ? metrics.size : 0, Character(i), Character(j)); + if (kerning != 0) + { + kerning_pair_cache.emplace(AsciiPair((i << 8) | j), KerningIntType(kerning)); + } + } + } +} + +int FontFaceHandleDefault::GetKerning(Character lhs, Character rhs, bool& has_set_size) const +{ + static_assert(' ' == 32, "Only ASCII/UTF8 character set supported."); + + // Check if we have no kerning, or if we have a control character. + if (!has_kerning || char32_t(lhs) < ' ' || char32_t(rhs) < ' ') + return 0; + + // See if the kerning pair has been cached. + const bool lhs_in_cache = (char32_t(lhs) >= KerningCache_AsciiSubsetBegin && char32_t(lhs) <= KerningCache_AsciiSubsetLast); + const bool rhs_in_cache = (char32_t(rhs) >= KerningCache_AsciiSubsetBegin && char32_t(rhs) <= KerningCache_AsciiSubsetLast); + + if (lhs_in_cache && rhs_in_cache) + { + const auto it = kerning_pair_cache.find(AsciiPair((int(lhs) << 8) | int(rhs))); + + if (it != kerning_pair_cache.end()) + { + return it->second; + } + + return 0; + } + + // Fetch it from the font face instead. + const int result = FreeType::GetKerning(ft_face, has_set_size ? 0 : metrics.size, lhs, rhs); + + // This is purely an optimization to avoid repeatedly setting the font size in FreeType, which can be a measurable performance hit. + has_set_size = true; + + return result; +} + +bool FontFaceHandleDefault::IsKerningEnabled(const TextShapingContext& text_shaping_context) const +{ + return text_shaping_context.font_kerning != Style::FontKerning::None; +} + +const FontGlyph* FontFaceHandleDefault::GetOrAppendGlyph(Character& character, bool look_in_fallback_fonts) +{ + // Don't try to render control characters + if ((char32_t)character < (char32_t)' ') + return nullptr; + + auto it_glyph = glyphs.find(character); + if (it_glyph == glyphs.end()) + { + bool result = AppendGlyph(character); + + if (result) + { + it_glyph = glyphs.find(character); + if (it_glyph == glyphs.end()) + { + RMLUI_ERROR; + return nullptr; + } + + is_layers_dirty = true; + } + else if (look_in_fallback_fonts) + { + const int num_fallback_faces = FontProvider::CountFallbackFontFaces(); + for (int i = 0; i < num_fallback_faces; i++) + { + FontFaceHandleDefault* fallback_face = FontProvider::GetFallbackFontFace(i, metrics.size); + if (!fallback_face || fallback_face == this) + continue; + + const FontGlyph* glyph = fallback_face->GetOrAppendGlyph(character, false); + if (glyph) + { + // Insert the new glyph into our own set of glyphs + auto pair = glyphs.emplace(character, glyph->WeakCopy()); + it_glyph = pair.first; + if (pair.second) + is_layers_dirty = true; + break; + } + } + + // If we still have not found a glyph, use the replacement character. + if (it_glyph == glyphs.end()) + { + character = Character::Replacement; + it_glyph = glyphs.find(character); + if (it_glyph == glyphs.end()) + return nullptr; + } + } + else + { + return nullptr; + } + } + + const FontGlyph* glyph = &it_glyph->second; + return glyph; +} + +FontFaceLayer* FontFaceHandleDefault::GetOrCreateLayer(const SharedPtr& font_effect) +{ + // Search for the font effect layer first, it may have been instanced before as part of a different configuration. + const FontEffect* font_effect_ptr = font_effect.get(); + auto it = + std::find_if(layers.begin(), layers.end(), [font_effect_ptr](const EffectLayerPair& pair) { return pair.font_effect == font_effect_ptr; }); + + if (it != layers.end()) + return it->layer.get(); + + // No existing effect matches, generate a new layer for the effect. + layers.push_back(EffectLayerPair{font_effect_ptr, nullptr}); + auto& layer = layers.back().layer; + + layer = MakeUnique(font_effect); + GenerateLayer(layer.get()); + + return layer.get(); +} + +bool FontFaceHandleDefault::GenerateLayer(FontFaceLayer* layer) +{ + RMLUI_ASSERT(layer); + const FontEffect* font_effect = layer->GetFontEffect(); + bool result = false; + + if (!font_effect) + { + result = layer->Generate(this); + } + else + { + // Determine which, if any, layer the new layer should copy its geometry and textures from. + FontFaceLayer* clone = nullptr; + bool clone_glyph_origins = true; + String generation_key; + size_t fingerprint = font_effect->GetFingerprint(); + + if (!font_effect->HasUniqueTexture()) + { + clone = base_layer; + clone_glyph_origins = false; + } + else + { + auto cache_iterator = layer_cache.find(fingerprint); + if (cache_iterator != layer_cache.end() && cache_iterator->second != layer) + clone = cache_iterator->second; + } + + // Create a new layer. + result = layer->Generate(this, clone, clone_glyph_origins); + + // Cache the layer in the layer cache if it generated its own textures (ie, didn't clone). + if (!clone) + layer_cache[fingerprint] = layer; + } + + return result; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FontFaceHandleDefault.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FontFaceHandleDefault.h new file mode 100644 index 00000000..cf051810 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FontFaceHandleDefault.h @@ -0,0 +1,127 @@ +#pragma once + +#include "../../../Include/RmlUi/Core/FontEffect.h" +#include "../../../Include/RmlUi/Core/FontGlyph.h" +#include "../../../Include/RmlUi/Core/FontMetrics.h" +#include "../../../Include/RmlUi/Core/Geometry.h" +#include "../../../Include/RmlUi/Core/TextShapingContext.h" +#include "../../../Include/RmlUi/Core/Texture.h" +#include "../../../Include/RmlUi/Core/Traits.h" +#include "FontTypes.h" + +namespace Rml { + +class FontFaceLayer; + +class FontFaceHandleDefault final : public NonCopyMoveable { +public: + FontFaceHandleDefault(); + ~FontFaceHandleDefault(); + + bool Initialize(FontFaceHandleFreetype face, int font_size, bool load_default_glyphs); + + const FontMetrics& GetFontMetrics() const; + + const FontGlyphMap& GetGlyphs() const; + + /// Returns the width a string will take up if rendered with this handle. + /// @param[in] string The string to measure. + /// @param[in] text_shaping_context Extra parameters that provide context for text shaping. + /// @param[in] prior_character The optionally-specified character that immediately precedes the string. This may have an impact on the string + /// width due to kerning. + /// @return The width, in pixels, this string will occupy if rendered with this handle. + int GetStringWidth(StringView string, const TextShapingContext& text_shaping_context, Character prior_character = Character::Null); + + /// Generates, if required, the layer configuration for a given list of font effects. + /// @param[in] font_effects The list of font effects to generate the configuration for. + /// @return The index to use when generating geometry using this configuration. + int GenerateLayerConfiguration(const FontEffectList& font_effects); + /// Generates the texture data for a layer (for the texture database). + /// @param[out] texture_data The generated texture data. + /// @param[out] texture_dimensions The dimensions of the texture. + /// @param[in] font_effect The font effect used for the layer. + /// @param[in] texture_id The index of the texture within the layer to generate. + /// @param[in] handle_version The version of the handle data. Function returns false if out of date. + bool GenerateLayerTexture(Vector& texture_data, Vector2i& texture_dimensions, const FontEffect* font_effect, int texture_id, + int handle_version) const; + + /// Generates the geometry required to render a single line of text. + /// @param[in] render_manager The render manager responsible for rendering the string. + /// @param[out] mesh_list A list to place the new meshes into. + /// @param[in] string The string to render. + /// @param[in] position The position of the baseline of the first character to render. + /// @param[in] colour The colour to render the text. + /// @param[in] opacity The opacity of the text, should be applied to font effects. + /// @param[in] text_shaping_context Extra parameters that provide context for text shaping. + /// @param[in] layer_configuration Face configuration index to use for generating string. + /// @return The width, in pixels, of the string geometry. + int GenerateString(RenderManager& render_manager, TexturedMeshList& mesh_list, StringView string, Vector2f position, ColourbPremultiplied colour, + float opacity, const TextShapingContext& text_shaping_context, int layer_configuration); + + /// Version is changed whenever the layers are dirtied, requiring regeneration of string geometry. + int GetVersion() const; + +private: + // Build and append glyph to 'glyphs' + bool AppendGlyph(Character character); + + // Build a kerning cache for common characters. + void FillKerningPairCache(); + + // Return the kerning for a character pair. + int GetKerning(Character lhs, Character rhs, bool& has_set_size) const; + + // Returns whether kerning is enabled based on the text-shaping context. + bool IsKerningEnabled(const TextShapingContext& text_shaping_context) const; + + /// Retrieve a glyph from the given code point, building and appending a new glyph if not already built. + /// @param[in-out] character The character, can be changed e.g. to the replacement character if no glyph is found. + /// @param[in] look_in_fallback_fonts Look for the glyph in fallback fonts if not found locally, adding it to our glyphs. + /// @return The font glyph for the returned code point. + const FontGlyph* GetOrAppendGlyph(Character& character, bool look_in_fallback_fonts = true); + + // Regenerate layers if dirty, such as after adding new glyphs. + bool UpdateLayersOnDirty(); + + // Create a new layer from the given font effect if it does not already exist. + FontFaceLayer* GetOrCreateLayer(const SharedPtr& font_effect); + + // (Re-)generate a layer in this font face handle. + bool GenerateLayer(FontFaceLayer* layer); + + FontGlyphMap glyphs; + + struct EffectLayerPair { + const FontEffect* font_effect; + UniquePtr layer; + }; + using FontLayerMap = Vector; + using FontLayerCache = SmallUnorderedMap; + using LayerConfiguration = Vector; + using LayerConfigurationList = Vector; + + // The list of all font layers, index by the effect that instanced them. + FontFaceLayer* base_layer; + FontLayerMap layers; + // Each font layer that generated geometry or textures, indexed by the font-effect's fingerprint key. + FontLayerCache layer_cache; + + // Pre-cache kerning pairs for some ascii subset of all characters. + using AsciiPair = uint16_t; + using KerningIntType = int16_t; + using KerningPairs = UnorderedMap; + KerningPairs kerning_pair_cache; + + bool has_kerning = false; + bool is_layers_dirty = false; + int version = 0; + + // All configurations currently in use on this handle. New configurations will be generated as required. + LayerConfigurationList layer_configurations; + + FontMetrics metrics; + + FontFaceHandleFreetype ft_face; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FontFaceLayer.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FontFaceLayer.cpp new file mode 100644 index 00000000..87ba603c --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FontFaceLayer.cpp @@ -0,0 +1,247 @@ +#include "FontFaceLayer.h" +#include "../../../Include/RmlUi/Core/RenderManager.h" +#include "FontFaceHandleDefault.h" +#include +#include + +namespace Rml { + +FontFaceLayer::FontFaceLayer(const SharedPtr& _effect) : colour(255, 255, 255) +{ + effect = _effect; + if (effect) + colour = effect->GetColour(); +} + +FontFaceLayer::~FontFaceLayer() {} + +bool FontFaceLayer::Generate(const FontFaceHandleDefault* handle, const FontFaceLayer* clone, bool clone_glyph_origins) +{ + // Clear the old layout if it exists. + { + // @performance: We could be much smarter about this, e.g. such as adding new glyphs to the existing texture layout and textures. + // Right now we re-generate the whole thing, including textures. + texture_layout = TextureLayout{}; + character_boxes.clear(); + textures_owned.clear(); + textures_ptr = &textures_owned; + } + + const FontGlyphMap& glyphs = handle->GetGlyphs(); + + // Generate the new layout. + if (clone) + { + // Clone the geometry and textures from the clone layer. + character_boxes = clone->character_boxes; + + // Point our textures to the cloned layer's textures. + textures_ptr = clone->textures_ptr; + + // Request the effect (if we have one) and adjust the origins as appropriate. + if (effect && !clone_glyph_origins) + { + for (auto& pair : glyphs) + { + Character character = pair.first; + const FontGlyph& glyph = pair.second; + + auto it = character_boxes.find(character); + if (it == character_boxes.end()) + { + // This can happen if the layers have been dirtied in FontHandleDefault. We will + // probably be regenerated soon, just skip the character for now. + continue; + } + + TextureBox& box = it->second; + + Vector2i glyph_origin = Vector2i(box.origin); + Vector2i glyph_dimensions = Vector2i(box.dimensions); + + if (effect->GetGlyphMetrics(glyph_origin, glyph_dimensions, glyph)) + box.origin = Vector2f(glyph_origin); + else + box.texture_index = -1; + } + } + } + else + { + // Initialise the texture layout for the glyphs. + character_boxes.reserve(glyphs.size()); + for (auto& pair : glyphs) + { + Character character = pair.first; + const FontGlyph& glyph = pair.second; + + Vector2i glyph_origin(0, 0); + Vector2i glyph_dimensions = glyph.bitmap_dimensions; + + // Adjust glyph origin / dimensions for the font effect. + if (effect) + { + if (!effect->GetGlyphMetrics(glyph_origin, glyph_dimensions, glyph)) + continue; + } + + TextureBox box; + box.origin = Vector2f(float(glyph_origin.x + glyph.bearing.x), float(glyph_origin.y - glyph.bearing.y)); + box.dimensions = Vector2f(glyph_dimensions); + + RMLUI_ASSERT(box.dimensions.x >= 0 && box.dimensions.y >= 0); + + character_boxes[character] = box; + + // Add the character's dimensions into the texture layout engine. + texture_layout.AddRectangle((int)character, glyph_dimensions); + } + + constexpr int max_texture_dimensions = 1024; + + // Generate the texture layout; this will position the glyph rectangles efficiently and + // allocate the texture data ready for writing. + if (!texture_layout.GenerateLayout(max_texture_dimensions)) + return false; + + // Iterate over each rectangle in the layout, copying the glyph data into the rectangle as + // appropriate and generating geometry. + for (int i = 0; i < texture_layout.GetNumRectangles(); ++i) + { + TextureLayoutRectangle& rectangle = texture_layout.GetRectangle(i); + const TextureLayoutTexture& texture = texture_layout.GetTexture(rectangle.GetTextureIndex()); + Character character = (Character)rectangle.GetId(); + RMLUI_ASSERT(character_boxes.find(character) != character_boxes.end()); + TextureBox& box = character_boxes[character]; + + // Set the character's texture index. + box.texture_index = rectangle.GetTextureIndex(); + + // Generate the character's texture coordinates. + box.texcoords[0].x = float(rectangle.GetPosition().x) / float(texture.GetDimensions().x); + box.texcoords[0].y = float(rectangle.GetPosition().y) / float(texture.GetDimensions().y); + box.texcoords[1].x = float(rectangle.GetPosition().x + rectangle.GetDimensions().x) / float(texture.GetDimensions().x); + box.texcoords[1].y = float(rectangle.GetPosition().y + rectangle.GetDimensions().y) / float(texture.GetDimensions().y); + } + + const FontEffect* effect_ptr = effect.get(); + const int handle_version = handle->GetVersion(); + + // Generate the textures. + for (int i = 0; i < texture_layout.GetNumTextures(); ++i) + { + const int texture_id = i; + + CallbackTextureFunction texture_callback = [handle, effect_ptr, texture_id, handle_version]( + const CallbackTextureInterface& texture_interface) -> bool { + Vector2i dimensions; + Vector data; + if (!handle->GenerateLayerTexture(data, dimensions, effect_ptr, texture_id, handle_version) || data.empty()) + return false; + if (!texture_interface.GenerateTexture(data, dimensions)) + return false; + return true; + }; + + static_assert(std::is_nothrow_move_constructible::value, + "CallbackTextureSource must be nothrow move constructible so that it can be placed in the vector below."); + + textures_owned.emplace_back(std::move(texture_callback)); + } + } + + return true; +} + +bool FontFaceLayer::GenerateTexture(Vector& texture_data, Vector2i& texture_dimensions, int texture_id, const FontGlyphMap& glyphs) +{ + if (texture_id < 0 || texture_id > texture_layout.GetNumTextures()) + return false; + + // Generate the texture data. + texture_data = texture_layout.GetTexture(texture_id).AllocateTexture(); + texture_dimensions = texture_layout.GetTexture(texture_id).GetDimensions(); + + for (int i = 0; i < texture_layout.GetNumRectangles(); ++i) + { + TextureLayoutRectangle& rectangle = texture_layout.GetRectangle(i); + Character character = (Character)rectangle.GetId(); + RMLUI_ASSERT(character_boxes.find(character) != character_boxes.end()); + + TextureBox& box = character_boxes[character]; + + if (box.texture_index != texture_id) + continue; + + auto it = glyphs.find((Character)rectangle.GetId()); + if (it == glyphs.end()) + continue; + + const FontGlyph& glyph = it->second; + + if (effect == nullptr) + { + // Copy the glyph's bitmap data into its allocated texture. + if (glyph.bitmap_data) + { + byte* destination = rectangle.GetTextureData(); + const byte* source = glyph.bitmap_data; + const int num_bytes_per_line = glyph.bitmap_dimensions.x * (glyph.color_format == ColorFormat::RGBA8 ? 4 : 1); + + for (int j = 0; j < glyph.bitmap_dimensions.y; ++j) + { + switch (glyph.color_format) + { + case ColorFormat::A8: + { + // We use premultiplied alpha, so copy the alpha into all four channels. + for (int k = 0; k < num_bytes_per_line; ++k) + for (int c = 0; c < 4; ++c) + destination[k * 4 + c] = source[k]; + } + break; + case ColorFormat::RGBA8: + { + memcpy(destination, source, num_bytes_per_line); + } + break; + } + + destination += rectangle.GetTextureStride(); + source += num_bytes_per_line; + } + } + } + else + { + effect->GenerateGlyphTexture(rectangle.GetTextureData(), Vector2i(box.dimensions), rectangle.GetTextureStride(), glyph); + } + } + + return true; +} + +const FontEffect* FontFaceLayer::GetFontEffect() const +{ + return effect.get(); +} + +Texture FontFaceLayer::GetTexture(RenderManager& render_manager, int index) +{ + RMLUI_ASSERT(index >= 0); + RMLUI_ASSERT(index < GetNumTextures()); + + return (*textures_ptr)[index].GetTexture(render_manager); +} + +int FontFaceLayer::GetNumTextures() const +{ + return (int)textures_ptr->size(); +} + +ColourbPremultiplied FontFaceLayer::GetColour(float opacity) const +{ + return colour.ToPremultiplied(opacity); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FontFaceLayer.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FontFaceLayer.h new file mode 100644 index 00000000..5810b627 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FontFaceLayer.h @@ -0,0 +1,97 @@ +#pragma once + +#include "../../../Include/RmlUi/Core/CallbackTexture.h" +#include "../../../Include/RmlUi/Core/FontGlyph.h" +#include "../../../Include/RmlUi/Core/Geometry.h" +#include "../../../Include/RmlUi/Core/MeshUtilities.h" +#include "../TextureLayout.h" + +namespace Rml { + +class FontEffect; +class FontFaceHandleDefault; + +/** + A textured layer stored as part of a font face handle. Each handle will have at least a base + layer for the standard font. Further layers can be added to allow rendering of text effects. + */ + +class FontFaceLayer { +public: + FontFaceLayer(const SharedPtr& _effect); + ~FontFaceLayer(); + + /// Generates or re-generates the character and texture data for the layer. + /// @param[in] handle The handle generating this layer. + /// @param[in] clone The layer to optionally clone geometry and texture data from. + /// @param[in] clone_glyph_origins True to keep the character origins from the cloned layer, false to generate new ones. + /// @return True if the layer was generated successfully, false if not. + bool Generate(const FontFaceHandleDefault* handle, const FontFaceLayer* clone = nullptr, bool clone_glyph_origins = false); + + /// Generates the texture data for a layer (for the texture database). + /// @param[out] texture_data The generated texture data. + /// @param[out] texture_dimensions The dimensions of the texture. + /// @param[in] texture_id The index of the texture within the layer to generate. + /// @param[in] glyphs The glyphs required by the font face handle. + bool GenerateTexture(Vector& texture_data, Vector2i& texture_dimensions, int texture_id, const FontGlyphMap& glyphs); + + /// Generates the geometry required to render a single character. + /// @param[out] mesh_list An array of meshes this layer will write to. It must be at least as big as the number of textures in this layer. + /// @param[in] character_code The character to generate geometry for. + /// @param[in] position The position of the baseline. + /// @param[in] colour The colour of the string. + inline void GenerateGeometry(TexturedMesh* mesh_list, const Character character_code, const Vector2f position, + const ColourbPremultiplied colour) const + { + auto it = character_boxes.find(character_code); + if (it == character_boxes.end()) + return; + + const TextureBox& box = it->second; + + if (box.texture_index < 0) + return; + + // Generate the geometry for the character. + Mesh& mesh = mesh_list[box.texture_index].mesh; + MeshUtilities::GenerateQuad(mesh, (position + box.origin).Round(), box.dimensions, colour, box.texcoords[0], box.texcoords[1]); + } + + /// Returns the effect used to generate the layer. + const FontEffect* GetFontEffect() const; + + /// Returns one of the layer's textures. + Texture GetTexture(RenderManager& render_manager, int index); + /// Returns the number of textures employed by this layer. + int GetNumTextures() const; + + /// Returns the layer's colour after applying the given opacity. + ColourbPremultiplied GetColour(float opacity) const; + +private: + struct TextureBox { + // The offset, in pixels, of the baseline from the start of this character's geometry. + Vector2f origin; + // The width and height, in pixels, of this character's geometry. + Vector2f dimensions; + // The texture coordinates for the character's geometry. + Vector2f texcoords[2]; + + // The texture this character renders from. + int texture_index = -1; + }; + + using CharacterMap = UnorderedMap; + using TextureList = Vector; + + SharedPtr effect; + + TextureList textures_owned; + TextureList* textures_ptr = &textures_owned; + + TextureLayout texture_layout; + CharacterMap character_boxes; + Colourb colour; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FontFamily.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FontFamily.cpp new file mode 100644 index 00000000..53134ef9 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FontFamily.cpp @@ -0,0 +1,77 @@ +#include "FontFamily.h" +#include "../../../Include/RmlUi/Core/ComputedValues.h" +#include "../../../Include/RmlUi/Core/Math.h" +#include "FontFace.h" +#include + +namespace Rml { + +FontFamily::FontFamily(const String& name) : name(name) {} + +FontFamily::~FontFamily() +{ + // Multiple face entries may share memory within a single font family, although only one of them owns it. Here we make sure that all the face + // destructors are run before all the memory is released. This way we don't leave any hanging references to invalidated memory. + for (FontFaceEntry& entry : font_faces) + entry.face.reset(); +} + +FontFaceHandleDefault* FontFamily::GetFaceHandle(Style::FontStyle style, Style::FontWeight weight, int size) +{ + int best_dist = INT_MAX; + FontFace* matching_face = nullptr; + for (size_t i = 0; i < font_faces.size(); i++) + { + FontFace* face = font_faces[i].face.get(); + + if (face->GetStyle() == style) + { + const int dist = Math::Absolute((int)face->GetWeight() - (int)weight); + if (dist == 0) + { + // Direct match for weight, break the loop early. + matching_face = face; + break; + } + else if (dist < best_dist) + { + // Best match so far for weight, store the face and dist. + matching_face = face; + best_dist = dist; + } + } + } + + if (!matching_face) + return nullptr; + + return matching_face->GetHandle(size, true); +} + +auto FontFamily::AddFace(FontFaceHandleFreetype ft_face, Style::FontStyle style, Style::FontWeight weight, UniquePtr face_memory) + -> AddFaceResult +{ + for (auto& face : font_faces) + { + if (face.face->GetStyle() == style && face.face->GetWeight() == weight) + { + return {FontProvider::FontFaceLoadResult::Duplicate, nullptr}; + } + } + + auto face = MakeUnique(ft_face, style, weight); + + AddFaceResult result{FontProvider::FontFaceLoadResult::Success, face.get()}; + + font_faces.push_back(FontFaceEntry{std::move(face), std::move(face_memory)}); + + return result; +} + +void FontFamily::ReleaseFontResources() +{ + for (auto& entry : font_faces) + entry.face->ReleaseFontResources(); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FontFamily.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FontFamily.h new file mode 100644 index 00000000..b2510294 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FontFamily.h @@ -0,0 +1,52 @@ +#pragma once + +#include "FontProvider.h" +#include "FontTypes.h" + +namespace Rml { + +class FontFace; +class FontFaceHandleDefault; + +class FontFamily { +public: + struct AddFaceResult { + FontProvider::FontFaceLoadResult result = FontProvider::FontFaceLoadResult::Success; + FontFace* face = nullptr; + }; + + FontFamily(const String& name); + ~FontFamily(); + + /// Returns a handle to the most appropriate font in the family, at the correct size. + /// @param[in] style The style of the desired handle. + /// @param[in] weight The weight of the desired handle. + /// @param[in] size The size of the desired handle, in points. + /// @return A valid handle if a matching (or closely matching) font face was found, nullptr otherwise. + FontFaceHandleDefault* GetFaceHandle(Style::FontStyle style, Style::FontWeight weight, int size); + + /// Adds a new face to the family. + /// @param[in] ft_face The previously loaded FreeType face. + /// @param[in] style The style of the new face. + /// @param[in] weight The weight of the new face. + /// @param[in] face_memory Optionally pass ownership of the face's memory to the face itself, automatically releasing it on destruction. + /// @return A result flag and a pointer to the new font face on success. + AddFaceResult AddFace(FontFaceHandleFreetype ft_face, Style::FontStyle style, Style::FontWeight weight, UniquePtr face_memory); + + /// Releases resources owned by sized font faces, including their textures and rendered glyphs. + void ReleaseFontResources(); + +protected: + String name; + + struct FontFaceEntry { + UniquePtr face; + // Only filled if we own the memory used by the face's FreeType handle. May be shared with other faces in this family. + UniquePtr face_memory; + }; + + using FontFaceList = Vector; + FontFaceList font_faces; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FontProvider.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FontProvider.cpp new file mode 100644 index 00000000..b5d9772b --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FontProvider.cpp @@ -0,0 +1,250 @@ +#include "FontProvider.h" +#include "../../../Include/RmlUi/Core/Core.h" +#include "../../../Include/RmlUi/Core/FileInterface.h" +#include "../../../Include/RmlUi/Core/Log.h" +#include "../../../Include/RmlUi/Core/Math.h" +#include "../../../Include/RmlUi/Core/StringUtilities.h" +#include "../ComputeProperty.h" +#include "FontFace.h" +#include "FontFamily.h" +#include "FreeTypeInterface.h" +#include + +namespace Rml { + +static FontProvider* g_font_provider = nullptr; + +FontProvider::FontProvider() +{ + RMLUI_ASSERT(!g_font_provider); +} + +FontProvider::~FontProvider() +{ + RMLUI_ASSERT(g_font_provider == this); +} + +bool FontProvider::Initialise() +{ + RMLUI_ASSERT(!g_font_provider); + if (!FreeType::Initialise()) + return false; + g_font_provider = new FontProvider; + return true; +} + +void FontProvider::Shutdown() +{ + RMLUI_ASSERT(g_font_provider); + delete g_font_provider; + g_font_provider = nullptr; + FreeType::Shutdown(); +} + +FontProvider& FontProvider::Get() +{ + RMLUI_ASSERT(g_font_provider); + return *g_font_provider; +} + +FontFaceHandleDefault* FontProvider::GetFontFaceHandle(const String& family, Style::FontStyle style, Style::FontWeight weight, int size) +{ + RMLUI_ASSERTMSG(family == StringUtilities::ToLower(family), "Font family name must be converted to lowercase before entering here."); + + FontFamilyMap& families = Get().font_families; + + auto it = families.find(family); + if (it == families.end()) + return nullptr; + + return it->second->GetFaceHandle(style, weight, size); +} + +int FontProvider::CountFallbackFontFaces() +{ + return (int)Get().fallback_font_faces.size(); +} + +FontFaceHandleDefault* FontProvider::GetFallbackFontFace(int index, int font_size) +{ + auto& faces = FontProvider::Get().fallback_font_faces; + + if (index >= 0 && index < (int)faces.size()) + return faces[index]->GetHandle(font_size, false); + + return nullptr; +} + +void FontProvider::ReleaseFontResources() +{ + RMLUI_ASSERT(g_font_provider); + for (auto& name_family : g_font_provider->font_families) + name_family.second->ReleaseFontResources(); +} + +bool FontProvider::LoadFontFace(const String& file_name, int face_index, bool fallback_face, Style::FontWeight weight) +{ + return LoadFontFace(file_name, face_index, {}, Style::FontStyle::Normal, weight, fallback_face); +} + +bool FontProvider::LoadFontFace(const String& file_name, int face_index, const String& font_family, Style::FontStyle style, Style::FontWeight weight, + bool fallback_face) +{ + FileInterface* file_interface = GetFileInterface(); + FileHandle handle = file_interface->Open(file_name); + + if (!handle) + { + Log::Message(Log::LT_ERROR, "Failed to load font face from %s, could not open file.", file_name.c_str()); + return false; + } + + size_t length = file_interface->Length(handle); + + auto buffer_ptr = UniquePtr(new byte[length]); + byte* buffer = buffer_ptr.get(); + file_interface->Read(buffer, length, handle); + file_interface->Close(handle); + + bool result = Get().LoadFontFace({buffer, length}, face_index, fallback_face, std::move(buffer_ptr), file_name, font_family, style, weight); + + return result; +} + +bool FontProvider::LoadFontFace(Span data, int face_index, const String& font_family, Style::FontStyle style, Style::FontWeight weight, + bool fallback_face) +{ + const String source = "memory"; + + bool result = Get().LoadFontFace(data, face_index, fallback_face, nullptr, source, font_family, style, weight); + + return result; +} + +bool FontProvider::LoadFontFace(Span data, int face_index, bool fallback_face, UniquePtr face_memory, const String& source, + String font_family, Style::FontStyle style, Style::FontWeight weight) +{ + using Style::FontWeight; + + Vector face_variations; + if (!FreeType::GetFaceVariations(data, face_variations, face_index)) + { + Log::Message(Log::LT_ERROR, "Failed to load font face from '%s': Invalid or unsupported font face file format.", source.c_str()); + return false; + } + + Vector load_variations; + if (face_variations.empty()) + { + load_variations.push_back(FaceVariation{Style::FontWeight::Auto, 0, 0}); + } + else + { + // Iterate through all the face variations and pick the ones to load. The list is already sorted by (weight, width). When weight is set to + // 'auto' we load all the weights of the face. However, we only want to load one width for each weight. + for (auto it = face_variations.begin(); it != face_variations.end();) + { + if (weight != FontWeight::Auto && it->weight != weight) + { + ++it; + continue; + } + + // We don't currently have any way for users to select widths, so we search for a regular (medium) value here. + constexpr int search_width = 100; + const FontWeight current_weight = it->weight; + + int best_width_distance = Math::Absolute((int)it->width - search_width); + auto it_best_width = it; + + // Search forward to find the best 'width' with the same weight. + for (++it; it != face_variations.end(); ++it) + { + if (it->weight != current_weight) + break; + + const int width_distance = Math::Absolute((int)it->width - search_width); + if (width_distance < best_width_distance) + { + best_width_distance = width_distance; + it_best_width = it; + } + } + + load_variations.push_back(*it_best_width); + } + } + + if (load_variations.empty()) + { + Log::Message(Log::LT_ERROR, "Failed to load font face from '%s': Could not locate face with weight %d.", source.c_str(), (int)weight); + return false; + } + + for (const FaceVariation& variation : load_variations) + { + FontFaceHandleFreetype ft_face = FreeType::LoadFace(data, source, face_index, variation.named_instance_index); + if (!ft_face) + return false; + + if (font_family.empty()) + FreeType::GetFaceStyle(ft_face, &font_family, &style, nullptr); + if (weight == FontWeight::Auto) + FreeType::GetFaceStyle(ft_face, nullptr, nullptr, &weight); + + const FontWeight variation_weight = (variation.weight == FontWeight::Auto ? weight : variation.weight); + const String font_face_description = GetFontFaceDescription(font_family, style, variation_weight); + + if (auto result = AddFace(ft_face, font_family, style, variation_weight, fallback_face, std::move(face_memory)); + result != FontFaceLoadResult::Success) + { + if (result == FontFaceLoadResult::Error) + { + Log::Message(Log::LT_ERROR, "Failed to load font face %s from '%s'.", font_face_description.c_str(), source.c_str()); + } + return false; + } + + Log::Message(Log::LT_INFO, "Loaded font face %s from '%s'.", font_face_description.c_str(), source.c_str()); + } + + return true; +} + +auto FontProvider::AddFace(FontFaceHandleFreetype face, const String& family, Style::FontStyle style, Style::FontWeight weight, bool fallback_face, + UniquePtr face_memory) -> FontFaceLoadResult +{ + if (family.empty() || weight == Style::FontWeight::Auto) + return FontFaceLoadResult::Error; + + String family_lower = StringUtilities::ToLower(family); + FontFamily* font_family = nullptr; + auto it = font_families.find(family_lower); + if (it != font_families.end()) + { + font_family = (FontFamily*)it->second.get(); + } + else + { + auto font_family_ptr = MakeUnique(family_lower); + font_family = font_family_ptr.get(); + font_families[family_lower] = std::move(font_family_ptr); + } + + const auto [result, face_ptr] = font_family->AddFace(face, style, weight, std::move(face_memory)); + if (result != FontFaceLoadResult::Success) + return result; + + if (face_ptr && fallback_face) + { + auto it_fallback_face = std::find(fallback_font_faces.begin(), fallback_font_faces.end(), face_ptr); + if (it_fallback_face == fallback_font_faces.end()) + { + fallback_font_faces.push_back(face_ptr); + } + } + + return result; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FontProvider.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FontProvider.h new file mode 100644 index 00000000..7b2c1392 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FontProvider.h @@ -0,0 +1,79 @@ +#pragma once + +#include "../../../Include/RmlUi/Core/StyleTypes.h" +#include "../../../Include/RmlUi/Core/Types.h" +#include "FontTypes.h" + +namespace Rml { + +class FontFace; +class FontFamily; +class FontFaceHandleDefault; + +/** + The font provider contains all font families currently in use by RmlUi. + */ + +class FontProvider { +public: + enum class FontFaceLoadResult { + Success, /// A new font was added to the global font face list. + Duplicate, /// The font is valid but is already in the font face list. + Error /// An error occurred while trying to load this font face. + }; + + static bool Initialise(); + static void Shutdown(); + + /// Returns a handle to a font face that can be used to position and render text. This will return the closest match + /// it can find, but in the event a font family is requested that does not exist, nullptr will be returned instead of a + /// valid handle. + /// @param[in] family The family of the desired font handle. + /// @param[in] style The style of the desired font handle. + /// @param[in] weight The weight of the desired font handle. + /// @param[in] size The size of desired handle, in points. + /// @return A valid handle if a matching (or closely matching) font face was found, nullptr otherwise. + static FontFaceHandleDefault* GetFontFaceHandle(const String& family, Style::FontStyle style, Style::FontWeight weight, int size); + + /// Adds a new font face to the database. The face's family, style and weight will be determined from the face itself. + static bool LoadFontFace(const String& file_name, int face_index, bool fallback_face, Style::FontWeight weight = Style::FontWeight::Auto); + + /// Adds a new font face from a file. + static bool LoadFontFace(const String& file_name, int face_index, const String& font_family, Style::FontStyle style, Style::FontWeight weight, + bool fallback_face); + + /// Adds a new font face from memory. + static bool LoadFontFace(Span data, int face_index, const String& font_family, Style::FontStyle style, Style::FontWeight weight, + bool fallback_face); + + /// Return the number of fallback font faces. + static int CountFallbackFontFaces(); + + /// Return a font face handle with the given index, at the given font size. + static FontFaceHandleDefault* GetFallbackFontFace(int index, int font_size); + + /// Releases resources owned by sized font faces, including their textures and rendered glyphs. + static void ReleaseFontResources(); + +private: + FontProvider(); + ~FontProvider(); + + static FontProvider& Get(); + + bool LoadFontFace(Span data, int face_index, bool fallback_face, UniquePtr face_memory, const String& source, + String font_family, Style::FontStyle style, Style::FontWeight weight); + + FontFaceLoadResult AddFace(FontFaceHandleFreetype face, const String& family, Style::FontStyle style, Style::FontWeight weight, + bool fallback_face, UniquePtr face_memory); + + using FontFaceList = Vector; + using FontFamilyMap = UnorderedMap>; + + FontFamilyMap font_families; + FontFaceList fallback_font_faces; + + static const String debugger_font_family_name; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FontTypes.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FontTypes.h new file mode 100644 index 00000000..2f6c9d07 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FontTypes.h @@ -0,0 +1,24 @@ +#pragma once + +#include "../../../Include/RmlUi/Core/FontGlyph.h" +#include "../../../Include/RmlUi/Core/StyleTypes.h" +#include "../../../Include/RmlUi/Core/Types.h" + +namespace Rml { + +using FontFaceHandleFreetype = uintptr_t; + +struct FaceVariation { + Style::FontWeight weight; + uint16_t width; + int named_instance_index; +}; + +inline bool operator<(const FaceVariation& a, const FaceVariation& b) +{ + if (a.weight == b.weight) + return a.width < b.width; + return a.weight < b.weight; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FreeTypeInterface.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FreeTypeInterface.cpp new file mode 100644 index 00000000..a46ce1d9 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FreeTypeInterface.cpp @@ -0,0 +1,578 @@ +#include "FreeTypeInterface.h" +#include "../../../Include/RmlUi/Core/ComputedValues.h" +#include "../../../Include/RmlUi/Core/FontMetrics.h" +#include "../../../Include/RmlUi/Core/Log.h" +#include +#include +#include +#include +#include FT_FREETYPE_H +#include FT_MULTIPLE_MASTERS_H +#include FT_TRUETYPE_TABLES_H + +namespace Rml { + +static FT_Library ft_library = nullptr; + +static bool BuildGlyph(FT_Face ft_face, Character character, FontGlyphMap& glyphs, float bitmap_scaling_factor); +static void BuildGlyphMap(FT_Face ft_face, int size, FontGlyphMap& glyphs, float bitmap_scaling_factor, bool load_default_glyphs); +static void GenerateMetrics(FT_Face ft_face, FontMetrics& metrics, float bitmap_scaling_factor); +static bool SetFontSize(FT_Face ft_face, int font_size, float& out_bitmap_scaling_factor); +static void BitmapDownscale(byte* bitmap_new, int new_width, int new_height, const byte* bitmap_source, int width, int height, int pitch, + ColorFormat color_format); + +static int ConvertFixed16_16ToInt(int32_t fx) +{ + return fx / 0x10000; +} + +#ifdef RMLUI_DEBUG +#define FT_ERROR_START_LIST switch ( error_code ) { +#define FT_ERRORDEF( e, v, s ) case v: return s; +#define FT_ERROR_END_LIST } +// https://freetype.org/freetype2/docs/reference/ft2-error_enumerations.html +static const char* GetFreeTypeErrorString(FT_Error error_code) +{ +#undef FTERRORS_H_ +#include "freetype/fterrors.h" + return ""; +} +#endif + +bool FreeType::Initialise() +{ + RMLUI_ASSERT(!ft_library); + + FT_Error result = FT_Init_FreeType(&ft_library); + if (result != 0) + { + Log::Message(Log::LT_ERROR, "Failed to initialise FreeType, error %d.", result); + Shutdown(); + return false; + } + + return true; +} + +void FreeType::Shutdown() +{ + if (ft_library != nullptr) + { + FT_Done_FreeType(ft_library); + ft_library = nullptr; + } +} + +bool FreeType::GetFaceVariations(Span data, Vector& out_face_variations, int face_index) +{ + RMLUI_ASSERT(ft_library); + + FT_Face face = nullptr; + FT_Error error = FT_New_Memory_Face(ft_library, static_cast(data.data()), static_cast(data.size()), face_index, &face); + if (error) + return false; + + FT_MM_Var* var = nullptr; + error = FT_Get_MM_Var(face, &var); + if (error) + return true; + + unsigned int axis_index_weight = 0; + unsigned int axis_index_width = 1; + + const unsigned int num_axis = var->num_axis; + for (unsigned int i = 0; i < num_axis; i++) + { + switch (var->axis[i].tag) + { + case 0x77676874: // 'wght' + axis_index_weight = i; + break; + case 0x77647468: // 'wdth' + axis_index_width = i; + break; + } + } + + if (num_axis > 0) + { + for (unsigned int i = 0; i < var->num_namedstyles; i++) + { + uint16_t weight = (axis_index_weight < num_axis ? (uint16_t)ConvertFixed16_16ToInt(var->namedstyle[i].coords[axis_index_weight]) : 0); + uint16_t width = (axis_index_width < num_axis ? (uint16_t)ConvertFixed16_16ToInt(var->namedstyle[i].coords[axis_index_width]) : 0); + int named_instance_index = (i + 1); + + out_face_variations.push_back(FaceVariation{weight == 0 ? Style::FontWeight::Normal : (Style::FontWeight)weight, + width == 0 ? (uint16_t)100 : width, named_instance_index}); + } + } + + std::sort(out_face_variations.begin(), out_face_variations.end()); + +#if FREETYPE_MAJOR >= 2 && FREETYPE_MINOR >= 9 + FT_Done_MM_Var(ft_library, var); +#endif + + FT_Done_Face(face); + + return true; +} + +FontFaceHandleFreetype FreeType::LoadFace(Span data, const String& source, int face_index, int named_style_index) +{ + RMLUI_ASSERT(ft_library); + + FT_Face face = nullptr; + FT_Error error = FT_New_Memory_Face(ft_library, static_cast(data.data()), static_cast(data.size()), + (named_style_index << 16) | face_index, &face); + + if (error) + { + Log::Message(Log::LT_ERROR, "FreeType error %d while loading face from %s.", error, source.c_str()); + return 0; + } + + // Initialise the character mapping on the face. + if (face->charmap == nullptr) + { + FT_Select_Charmap(face, FT_ENCODING_APPLE_ROMAN); + if (face->charmap == nullptr) + { + Log::Message(Log::LT_ERROR, "Font face (from %s) does not contain a Unicode or Apple Roman character map.", source.c_str()); + FT_Done_Face(face); + return 0; + } + } + + return (FontFaceHandleFreetype)face; +} + +bool FreeType::ReleaseFace(FontFaceHandleFreetype in_face) +{ + FT_Face face = (FT_Face)in_face; + FT_Error error = FT_Done_Face(face); + + return (error == 0); +} + +void FreeType::GetFaceStyle(FontFaceHandleFreetype in_face, String* font_family, Style::FontStyle* style, Style::FontWeight* weight) +{ + FT_Face face = (FT_Face)in_face; + + if (font_family) + *font_family = face->family_name; + if (style) + *style = face->style_flags & FT_STYLE_FLAG_ITALIC ? Style::FontStyle::Italic : Style::FontStyle::Normal; + + if (weight) + { + TT_OS2* font_table = (TT_OS2*)FT_Get_Sfnt_Table(face, FT_SFNT_OS2); + if (font_table && font_table->usWeightClass != 0) + *weight = (Style::FontWeight)font_table->usWeightClass; + else + *weight = (face->style_flags & FT_STYLE_FLAG_BOLD) == FT_STYLE_FLAG_BOLD ? Style::FontWeight::Bold : Style::FontWeight::Normal; + } +} + +bool FreeType::InitialiseFaceHandle(FontFaceHandleFreetype face, int font_size, FontGlyphMap& glyphs, FontMetrics& metrics, bool load_default_glyphs) +{ + FT_Face ft_face = (FT_Face)face; + + metrics.size = font_size; + + float bitmap_scaling_factor = 1.0f; + if (!SetFontSize(ft_face, font_size, bitmap_scaling_factor)) + return false; + + // Construct the initial list of glyphs. + BuildGlyphMap(ft_face, font_size, glyphs, bitmap_scaling_factor, load_default_glyphs); + + // Generate the metrics for the handle. + GenerateMetrics(ft_face, metrics, bitmap_scaling_factor); + + return true; +} + +bool FreeType::AppendGlyph(FontFaceHandleFreetype face, int font_size, Character character, FontGlyphMap& glyphs) +{ + FT_Face ft_face = (FT_Face)face; + + RMLUI_ASSERT(glyphs.find(character) == glyphs.end()); + RMLUI_ASSERT(ft_face); + + // Set face size again in case it was used at another size in another font face handle. + float bitmap_scaling_factor = 1.0f; + if (!SetFontSize(ft_face, font_size, bitmap_scaling_factor)) + return false; + + if (!BuildGlyph(ft_face, character, glyphs, bitmap_scaling_factor)) + return false; + + return true; +} + +int FreeType::GetKerning(FontFaceHandleFreetype face, int font_size, Character lhs, Character rhs) +{ + FT_Face ft_face = (FT_Face)face; + + RMLUI_ASSERT(FT_HAS_KERNING(ft_face)); + + // Set face size again in case it was used at another size in another font face handle. + // Font size value of zero assumes it is already set. + if (font_size > 0) + { + float bitmap_scaling_factor = 1.0f; + if (!SetFontSize(ft_face, font_size, bitmap_scaling_factor) || bitmap_scaling_factor != 1.0f) + return 0; + } + + FT_Vector ft_kerning; + + FT_Error ft_error = FT_Get_Kerning(ft_face, FT_Get_Char_Index(ft_face, (FT_ULong)lhs), FT_Get_Char_Index(ft_face, (FT_ULong)rhs), + FT_KERNING_DEFAULT, &ft_kerning); + + if (ft_error) + return 0; + + int kerning = ft_kerning.x >> 6; + return kerning; +} + +bool FreeType::HasKerning(FontFaceHandleFreetype face) +{ + FT_Face ft_face = (FT_Face)face; + + return FT_HAS_KERNING(ft_face); +} + +static void BuildGlyphMap(FT_Face ft_face, int size, FontGlyphMap& glyphs, const float bitmap_scaling_factor, const bool load_default_glyphs) +{ + if (load_default_glyphs) + { + glyphs.reserve(128); + + // Add the ASCII characters now. Other characters are added later as needed. + FT_ULong code_min = 32; + FT_ULong code_max = 126; + + for (FT_ULong character_code = code_min; character_code <= code_max; ++character_code) + BuildGlyph(ft_face, (Character)character_code, glyphs, bitmap_scaling_factor); + } + + // Add a replacement character for rendering unknown characters. + Character replacement_character = Character::Replacement; + auto it = glyphs.find(replacement_character); + if (it == glyphs.end()) + { + FontGlyph glyph; + glyph.bitmap_dimensions = {size / 3, (size * 2) / 3}; + glyph.advance = glyph.bitmap_dimensions.x + 2; + glyph.bearing = {1, glyph.bitmap_dimensions.y}; + + glyph.bitmap_owned_data.reset(new byte[glyph.bitmap_dimensions.x * glyph.bitmap_dimensions.y]); + glyph.bitmap_data = glyph.bitmap_owned_data.get(); + + for (int y = 0; y < glyph.bitmap_dimensions.y; y++) + { + for (int x = 0; x < glyph.bitmap_dimensions.x; x++) + { + constexpr int stroke = 1; + int i = y * glyph.bitmap_dimensions.x + x; + bool near_edge = (x < stroke || x >= glyph.bitmap_dimensions.x - stroke || y < stroke || y >= glyph.bitmap_dimensions.y - stroke); + glyph.bitmap_owned_data[i] = (near_edge ? 0xdd : 0); + } + } + + glyphs[replacement_character] = std::move(glyph); + } +} + +static bool BuildGlyph(FT_Face ft_face, const Character character, FontGlyphMap& glyphs, const float bitmap_scaling_factor) +{ + FT_UInt index = FT_Get_Char_Index(ft_face, (FT_ULong)character); + if (index == 0) + return false; + + FT_Error error = FT_Load_Glyph(ft_face, index, FT_LOAD_COLOR); + if (error != 0) + { +#ifdef RMLUI_DEBUG + auto error_message = GetFreeTypeErrorString(error); + Rml::Log::Message(Rml::Log::LT_WARNING, "Unable to load glyph at index '%u' in font face '%s %s'; FreeType error 0x%x: %s.", + (unsigned int)index, ft_face->family_name, ft_face->style_name, error, error_message); +#else + Rml::Log::Message(Rml::Log::LT_WARNING, "Unable to load glyph at index '%u' in font face '%s %s'; FreeType error 0x%x.", + (unsigned int)index, ft_face->family_name, ft_face->style_name, error); +#endif + } + + error = FT_Render_Glyph(ft_face->glyph, FT_RENDER_MODE_NORMAL); + if (error != 0) + { +#ifdef RMLUI_DEBUG + auto error_message = GetFreeTypeErrorString(error); + Rml::Log::Message(Rml::Log::LT_WARNING, "Unable to render glyph at index '%u' in font face '%s %s'; FreeType error 0x%x: %s.", + (unsigned int)index, ft_face->family_name, ft_face->style_name, error, error_message); +#else + Rml::Log::Message(Rml::Log::LT_WARNING, "Unable to render glyph at index '%u' in font face '%s %s'; FreeType error 0x%x.", + (unsigned int)index, ft_face->family_name, ft_face->style_name, error); +#endif + } + + auto result = glyphs.emplace(character, FontGlyph{}); + if (!result.second) + { + Log::Message(Log::LT_WARNING, "Glyph character '%u' is already loaded in the font face '%s %s'.", (unsigned int)character, + ft_face->family_name, ft_face->style_name); + return false; + } + + FontGlyph& glyph = result.first->second; + + FT_GlyphSlot ft_glyph = ft_face->glyph; + + glyph.bearing.x = ft_glyph->bitmap_left; + glyph.bearing.y = ft_glyph->bitmap_top; + glyph.advance = ft_glyph->metrics.horiAdvance >> 6; + glyph.bitmap_dimensions.x = ft_glyph->bitmap.width; + glyph.bitmap_dimensions.y = ft_glyph->bitmap.rows; + + // Determine new metrics if we need to scale the bitmap received from FreeType. Only allow bitmap downscaling. + const bool scale_bitmap = (bitmap_scaling_factor < 1.f); + if (scale_bitmap) + { + glyph.bearing = Vector2i(Vector2f(glyph.bearing) * bitmap_scaling_factor); + glyph.advance = int(float(glyph.advance) * bitmap_scaling_factor); + glyph.bitmap_dimensions = Vector2i(Vector2f(glyph.bitmap_dimensions) * bitmap_scaling_factor); + } + + // Copy the glyph's bitmap data from the FreeType glyph handle to our glyph handle. + if (glyph.bitmap_dimensions.x * glyph.bitmap_dimensions.y != 0) + { + // Check if the pixel mode is supported. + if (ft_glyph->bitmap.pixel_mode != FT_PIXEL_MODE_MONO && ft_glyph->bitmap.pixel_mode != FT_PIXEL_MODE_GRAY && + ft_glyph->bitmap.pixel_mode != FT_PIXEL_MODE_BGRA) + { + Log::Message(Log::LT_WARNING, "Unable to render glyph on the font face '%s %s': unsupported pixel mode (%d).", + ft_glyph->face->family_name, ft_glyph->face->style_name, ft_glyph->bitmap.pixel_mode); + } + else if (ft_glyph->bitmap.pixel_mode == FT_PIXEL_MODE_MONO && scale_bitmap) + { + Log::Message(Log::LT_WARNING, "Unable to render glyph on the font face '%s %s': bitmap scaling unsupported in mono pixel mode.", + ft_glyph->face->family_name, ft_glyph->face->style_name); + } + else + { + const int num_bytes_per_pixel = (ft_glyph->bitmap.pixel_mode == FT_PIXEL_MODE_BGRA ? 4 : 1); + glyph.color_format = (ft_glyph->bitmap.pixel_mode == FT_PIXEL_MODE_BGRA ? ColorFormat::RGBA8 : ColorFormat::A8); + + glyph.bitmap_owned_data.reset(new byte[glyph.bitmap_dimensions.x * glyph.bitmap_dimensions.y * num_bytes_per_pixel]); + glyph.bitmap_data = glyph.bitmap_owned_data.get(); + byte* destination_bitmap = glyph.bitmap_owned_data.get(); + const byte* source_bitmap = ft_glyph->bitmap.buffer; + + // Copy the bitmap data into the newly-allocated space on our glyph. + switch (ft_glyph->bitmap.pixel_mode) + { + case FT_PIXEL_MODE_MONO: + { + // Unpack 1-bit data into 8-bit. + for (int i = 0; i < glyph.bitmap_dimensions.y; ++i) + { + int mask = 0x80; + const byte* source_byte = source_bitmap; + for (int j = 0; j < glyph.bitmap_dimensions.x; ++j) + { + if ((*source_byte & mask) == mask) + destination_bitmap[j] = 255; + else + destination_bitmap[j] = 0; + + mask >>= 1; + if (mask <= 0) + { + mask = 0x80; + ++source_byte; + } + } + + destination_bitmap += glyph.bitmap_dimensions.x; + source_bitmap += ft_glyph->bitmap.pitch; + } + } + break; + case FT_PIXEL_MODE_GRAY: + case FT_PIXEL_MODE_BGRA: + { + if (scale_bitmap) + { + // Resize the glyph data to the new dimensions. + BitmapDownscale(destination_bitmap, glyph.bitmap_dimensions.x, glyph.bitmap_dimensions.y, source_bitmap, + (int)ft_glyph->bitmap.width, (int)ft_glyph->bitmap.rows, ft_glyph->bitmap.pitch, glyph.color_format); + } + else + { + // Copy the glyph data directly. + const int num_bytes_per_line = glyph.bitmap_dimensions.x * num_bytes_per_pixel; + for (int i = 0; i < glyph.bitmap_dimensions.y; ++i) + { + memcpy(destination_bitmap, source_bitmap, num_bytes_per_line); + destination_bitmap += num_bytes_per_line; + source_bitmap += ft_glyph->bitmap.pitch; + } + } + + if (glyph.color_format == ColorFormat::RGBA8) + { + // Swizzle channels (BGRA -> RGBA) + destination_bitmap = glyph.bitmap_owned_data.get(); + + for (int k = 0; k < glyph.bitmap_dimensions.x * glyph.bitmap_dimensions.y * num_bytes_per_pixel; k += 4) + { + std::swap(destination_bitmap[k], destination_bitmap[k + 2]); +#ifdef RMLUI_DEBUG + const byte alpha = destination_bitmap[k + 3]; + for (int c = 0; c < 3; c++) + RMLUI_ASSERTMSG(destination_bitmap[k + c] <= alpha, + "Glyph data is assumed to be encoded in premultiplied alpha, but that is not the case."); +#endif + } + } + } + break; + } + } + } + + return true; +} + +static void GenerateMetrics(FT_Face ft_face, FontMetrics& metrics, float bitmap_scaling_factor) +{ + metrics.ascent = ft_face->size->metrics.ascender * bitmap_scaling_factor / float(1 << 6); + metrics.descent = -ft_face->size->metrics.descender * bitmap_scaling_factor / float(1 << 6); + metrics.line_spacing = ft_face->size->metrics.height * bitmap_scaling_factor / float(1 << 6); + + metrics.underline_position = FT_MulFix(-ft_face->underline_position, ft_face->size->metrics.y_scale) * bitmap_scaling_factor / float(1 << 6); + metrics.underline_thickness = FT_MulFix(ft_face->underline_thickness, ft_face->size->metrics.y_scale) * bitmap_scaling_factor / float(1 << 6); + metrics.underline_thickness = Math::Max(metrics.underline_thickness, 1.0f); + + // Determine the x-height of this font face. + FT_UInt index = FT_Get_Char_Index(ft_face, 'x'); + if (index != 0 && FT_Load_Glyph(ft_face, index, 0) == 0) + metrics.x_height = ft_face->glyph->metrics.height * bitmap_scaling_factor / float(1 << 6); + else + metrics.x_height = 0.5f * metrics.line_spacing; + + FT_UInt ellipsis_index = FT_Get_Char_Index(ft_face, 0x2026); + metrics.has_ellipsis = (ellipsis_index != 0); +} + +static bool SetFontSize(FT_Face ft_face, int font_size, float& out_bitmap_scaling_factor) +{ + RMLUI_ASSERT(out_bitmap_scaling_factor == 1.f); + + FT_Error error = 0; + + // Set the character size on the font face. + error = FT_Set_Char_Size(ft_face, 0, font_size << 6, 0, 0); + + // If setting char size fails, try to select a bitmap strike instead when available. + if (error != 0 && FT_HAS_FIXED_SIZES(ft_face)) + { + constexpr int a_big_number = INT_MAX / 2; + int heuristic_min = INT_MAX; + int index_min = -1; + + // Select the bitmap strike with the smallest size *above* font_size, or else the largest size. + for (int i = 0; i < ft_face->num_fixed_sizes; i++) + { + const int size_diff = ft_face->available_sizes[i].height - font_size; + const int heuristic = (size_diff < 0 ? a_big_number - size_diff : size_diff); + + if (heuristic < heuristic_min) + { + index_min = i; + heuristic_min = heuristic; + } + } + + if (index_min >= 0) + { + out_bitmap_scaling_factor = float(font_size) / ft_face->available_sizes[index_min].height; + + // Add some tolerance to the scaling factor to avoid unnecessary scaling. Only allow downscaling. + constexpr float bitmap_scaling_factor_threshold = 0.95f; + if (out_bitmap_scaling_factor >= bitmap_scaling_factor_threshold) + out_bitmap_scaling_factor = 1.f; + + error = FT_Select_Size(ft_face, index_min); + } + } + + if (error != 0) + { + Log::Message(Log::LT_ERROR, "Unable to set the character size '%d' on the font face '%s %s'.", font_size, ft_face->family_name, + ft_face->style_name); + return false; + } + + return true; +} + +static void BitmapDownscale(byte* bitmap_new, const int new_width, const int new_height, const byte* bitmap_source, const int width, const int height, + const int pitch, const ColorFormat color_format) +{ + // Average filter for downscaling bitmap images, based on https://stackoverflow.com/a/9571580 + constexpr int max_num_channels = 4; + const int num_channels = (color_format == ColorFormat::RGBA8 ? 4 : 1); + + const float xscale = float(new_width) / width; + const float yscale = float(new_height) / height; + const float sumscale = xscale * yscale; + + float yend = 0.0f; + for (int f = 0; f < new_height; f++) // y on output + { + const float ystart = yend; + yend = (f + 1) * (1.f / yscale); + if (yend >= height) + yend = height - 0.001f; + + float xend = 0.0; + for (int g = 0; g < new_width; g++) // x on output + { + float xstart = xend; + xend = (g + 1) * (1.f / xscale); + if (xend >= width) + xend = width - 0.001f; + + float sum[max_num_channels] = {}; + for (int y = (int)ystart; y <= (int)yend; ++y) + { + float yportion = 1.0f; + if (y == (int)ystart) + yportion -= ystart - y; + if (y == (int)yend) + yportion -= y + 1 - yend; + + for (int x = (int)xstart; x <= (int)xend; ++x) + { + float xportion = 1.0f; + if (x == (int)xstart) + xportion -= xstart - x; + if (x == (int)xend) + xportion -= x + 1 - xend; + + for (int i = 0; i < num_channels; i++) + sum[i] += bitmap_source[y * pitch + x * num_channels + i] * yportion * xportion; + } + } + + for (int i = 0; i < num_channels; i++) + bitmap_new[(f * new_width + g) * num_channels + i] = (byte)Math::Min(sum[i] * sumscale, 255.f); + } + } +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FreeTypeInterface.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FreeTypeInterface.h new file mode 100644 index 00000000..68a3a726 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineDefault/FreeTypeInterface.h @@ -0,0 +1,41 @@ +#pragma once + +#include "../../../Include/RmlUi/Core/FontMetrics.h" +#include "FontTypes.h" + +namespace Rml { + +namespace FreeType { + + // Initialize FreeType library. + bool Initialise(); + // Shutdown FreeType library. + void Shutdown(); + + // Returns a sorted list of available font variations for the font face located in memory. + bool GetFaceVariations(Span data, Vector& out_face_variations, int face_index); + + // Loads a FreeType face from memory, 'source' is only used for logging. + FontFaceHandleFreetype LoadFace(Span data, const String& source, int face_index, int named_instance_index = 0); + + // Releases the FreeType face. + bool ReleaseFace(FontFaceHandleFreetype face); + + // Retrieves the font family, style and weight of the given font face. Use nullptr to ignore a property. + void GetFaceStyle(FontFaceHandleFreetype face, String* font_family, Style::FontStyle* style, Style::FontWeight* weight); + + // Initializes a face for a given font size. Glyphs are filled with the ASCII subset, and the font face metrics are set. + bool InitialiseFaceHandle(FontFaceHandleFreetype face, int font_size, FontGlyphMap& glyphs, FontMetrics& metrics, bool load_default_glyphs); + + // Build a new glyph representing the given code point and append to 'glyphs'. + bool AppendGlyph(FontFaceHandleFreetype face, int font_size, Character character, FontGlyphMap& glyphs); + + // Returns the kerning between two characters. + // 'font_size' value of zero assumes the font size is already set on the face, and skips this step for performance reasons. + int GetKerning(FontFaceHandleFreetype face, int font_size, Character lhs, Character rhs); + + // Returns true if the font face has kerning. + bool HasKerning(FontFaceHandleFreetype face); + +} // namespace FreeType +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineInterface.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineInterface.cpp new file mode 100644 index 00000000..f66c4884 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/FontEngineInterface.cpp @@ -0,0 +1,68 @@ +#include "../../Include/RmlUi/Core/FontEngineInterface.h" +#include "../../Include/RmlUi/Core/StringUtilities.h" + +namespace Rml { + +FontEngineInterface::FontEngineInterface() {} + +FontEngineInterface::~FontEngineInterface() {} + +void FontEngineInterface::Initialize() {} + +void FontEngineInterface::Shutdown() {} + +bool FontEngineInterface::LoadFontFace(const String& /*file_path*/, int /*face_index*/, bool /*fallback_face*/, Style::FontWeight /*weight*/) +{ + return false; +} + +bool FontEngineInterface::LoadFontFace(const String& /*file_path*/, int /*face_index*/, const String& /*family*/, Style::FontStyle /*style*/, + Style::FontWeight /*weight*/, bool /*fallback_face*/) +{ + return false; +} + +bool FontEngineInterface::LoadFontFace(Span /*data*/, int /*face_index*/, const String& /*family*/, Style::FontStyle /*style*/, + Style::FontWeight /*weight*/, bool /*fallback_face*/) +{ + return false; +} + +FontFaceHandle FontEngineInterface::GetFontFaceHandle(const String& /*family*/, Style::FontStyle /*style*/, Style::FontWeight /*weight*/, + int /*size*/) +{ + return 0; +} + +FontEffectsHandle FontEngineInterface::PrepareFontEffects(FontFaceHandle /*handle*/, const FontEffectList& /*font_effects*/) +{ + return 0; +} + +const FontMetrics& FontEngineInterface::GetFontMetrics(FontFaceHandle /*handle*/) +{ + static const FontMetrics metrics = {}; + return metrics; +} + +int FontEngineInterface::GetStringWidth(FontFaceHandle /*handle*/, StringView /*string*/, const TextShapingContext& /*text_shaping_context*/, + Character /*prior_character*/) +{ + return 0; +} + +int FontEngineInterface::GenerateString(RenderManager& /*render_manager*/, FontFaceHandle /*face_handle*/, FontEffectsHandle /*font_effects_handle*/, + StringView /*string*/, Vector2f /*position*/, ColourbPremultiplied /*colour*/, float /*opacity*/, + const TextShapingContext& /*text_shaping_context*/, TexturedMeshList& /*mesh_list*/) +{ + return 0; +} + +int FontEngineInterface::GetVersion(FontFaceHandle /*handle*/) +{ + return 0; +} + +void FontEngineInterface::ReleaseFontResources() {} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Geometry.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Geometry.cpp new file mode 100644 index 00000000..f83e64e9 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Geometry.cpp @@ -0,0 +1,39 @@ +#include "../../Include/RmlUi/Core/Geometry.h" +#include "RenderManagerAccess.h" + +namespace Rml { + +Geometry::Geometry(RenderManager* render_manager, StableVectorIndex resource_handle) : UniqueRenderResource(render_manager, resource_handle) {} + +void Geometry::Render(Vector2f translation, Texture texture, const CompiledShader& shader) const +{ + if (resource_handle == StableVectorIndex::Invalid) + return; + + translation = translation.Round(); + + RenderManagerAccess::Render(render_manager, *this, translation, texture, shader); +} + +Mesh Geometry::Release(ReleaseMode mode) +{ + if (resource_handle == StableVectorIndex::Invalid) + return Mesh(); + + Mesh mesh = RenderManagerAccess::ReleaseResource(render_manager, *this); + Clear(); + if (mode == ReleaseMode::ClearMesh) + { + mesh.vertices.clear(); + mesh.indices.clear(); + } + return mesh; +} + +const Mesh& Geometry::GetMesh() const +{ + RMLUI_ASSERT(resource_handle != StableVectorIndex::Invalid); + return RenderManagerAccess::GetMesh(render_manager, *this); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/GeometryBackgroundBorder.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/GeometryBackgroundBorder.cpp new file mode 100644 index 00000000..6106b49b --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/GeometryBackgroundBorder.cpp @@ -0,0 +1,330 @@ +#include "GeometryBackgroundBorder.h" +#include "../../Include/RmlUi/Core/Box.h" +#include "../../Include/RmlUi/Core/Math.h" +#include +#include + +namespace Rml { + +GeometryBackgroundBorder::GeometryBackgroundBorder(Vector& vertices, Vector& indices) : vertices(vertices), indices(indices) {} + +BorderMetrics GeometryBackgroundBorder::ComputeBorderMetrics(Vector2f outer_position, EdgeSizes edge_sizes, Vector2f inner_size, + CornerSizes outer_radii_def) +{ + BorderMetrics metrics = {}; + + // -- Find the corner positions -- + + const Vector2f inner_position = outer_position + Vector2f(edge_sizes[LEFT], edge_sizes[TOP]); + const Vector2f outer_size = inner_size + Vector2f(edge_sizes[LEFT] + edge_sizes[RIGHT], edge_sizes[TOP] + edge_sizes[BOTTOM]); + + metrics.positions_outer = { + outer_position, + outer_position + Vector2f(outer_size.x, 0), + outer_position + outer_size, + outer_position + Vector2f(0, outer_size.y), + }; + + metrics.positions_inner = { + inner_position, + inner_position + Vector2f(inner_size.x, 0), + inner_position + inner_size, + inner_position + Vector2f(0, inner_size.y), + }; + + // -- For curved borders, find the positions to draw ellipses around, and the scaled outer and inner radii -- + + const float sum_radius = (outer_radii_def[TOP_LEFT] + outer_radii_def[TOP_RIGHT] + outer_radii_def[BOTTOM_RIGHT] + outer_radii_def[BOTTOM_LEFT]); + const bool has_radius = (sum_radius > 1.f); + + if (has_radius) + { + auto& outer_radii = metrics.outer_radii; + outer_radii = outer_radii_def; + + // Scale the radii such that we have no overlapping curves. + float scale_factor = FLT_MAX; + scale_factor = Math::Min(scale_factor, inner_size.x / (outer_radii[TOP_LEFT] + outer_radii[TOP_RIGHT])); // Top + scale_factor = Math::Min(scale_factor, inner_size.y / (outer_radii[TOP_RIGHT] + outer_radii[BOTTOM_RIGHT])); // Right + scale_factor = Math::Min(scale_factor, inner_size.x / (outer_radii[BOTTOM_RIGHT] + outer_radii[BOTTOM_LEFT])); // Bottom + scale_factor = Math::Min(scale_factor, inner_size.y / (outer_radii[BOTTOM_LEFT] + outer_radii[TOP_LEFT])); // Left + scale_factor = Math::Min(1.0f, scale_factor); + + for (float& radius : outer_radii) + radius = Math::Round(radius * scale_factor); + + // Place the circle/ellipse centers + metrics.positions_circle_center = { + metrics.positions_outer[TOP_LEFT] + Vector2f(1, 1) * outer_radii[TOP_LEFT], + metrics.positions_outer[TOP_RIGHT] + Vector2f(-1, 1) * outer_radii[TOP_RIGHT], + metrics.positions_outer[BOTTOM_RIGHT] + Vector2f(-1, -1) * outer_radii[BOTTOM_RIGHT], + metrics.positions_outer[BOTTOM_LEFT] + Vector2f(1, -1) * outer_radii[BOTTOM_LEFT], + }; + + metrics.inner_radii = { + Vector2f(outer_radii[TOP_LEFT]) - Vector2f(edge_sizes[LEFT], edge_sizes[TOP]), + Vector2f(outer_radii[TOP_RIGHT]) - Vector2f(edge_sizes[RIGHT], edge_sizes[TOP]), + Vector2f(outer_radii[BOTTOM_RIGHT]) - Vector2f(edge_sizes[RIGHT], edge_sizes[BOTTOM]), + Vector2f(outer_radii[BOTTOM_LEFT]) - Vector2f(edge_sizes[LEFT], edge_sizes[BOTTOM]), + }; + } + + return metrics; +} + +void GeometryBackgroundBorder::DrawBackground(const BorderMetrics& metrics, ColourbPremultiplied color) +{ + const int offset_vertices = (int)vertices.size(); + + for (int corner = 0; corner < 4; corner++) + DrawBackgroundCorner(Corner(corner), metrics.positions_inner[corner], metrics.positions_circle_center[corner], metrics.outer_radii[corner], + metrics.inner_radii[corner], color); + + FillBackground(offset_vertices); +} + +void GeometryBackgroundBorder::DrawBorder(const BorderMetrics& metrics, EdgeSizes edge_sizes, const ColourbPremultiplied border_colors[4]) +{ + RMLUI_ASSERT(border_colors); + + const int offset_vertices = (int)vertices.size(); + + const bool draw_edge[4] = { + edge_sizes[TOP] > 0 && border_colors[TOP].alpha > 0, + edge_sizes[RIGHT] > 0 && border_colors[RIGHT].alpha > 0, + edge_sizes[BOTTOM] > 0 && border_colors[BOTTOM].alpha > 0, + edge_sizes[LEFT] > 0 && border_colors[LEFT].alpha > 0, + }; + + const bool draw_corner[4] = { + draw_edge[TOP] || draw_edge[LEFT], + draw_edge[TOP] || draw_edge[RIGHT], + draw_edge[BOTTOM] || draw_edge[RIGHT], + draw_edge[BOTTOM] || draw_edge[LEFT], + }; + + for (int corner = 0; corner < 4; corner++) + { + const Edge edge0 = Edge((corner + 3) % 4); + const Edge edge1 = Edge(corner); + + if (draw_corner[corner]) + { + DrawBorderCorner(Corner(corner), metrics.positions_outer[corner], metrics.positions_inner[corner], + metrics.positions_circle_center[corner], metrics.outer_radii[corner], metrics.inner_radii[corner], border_colors[edge0], + border_colors[edge1]); + } + + if (draw_edge[edge1]) + { + RMLUI_ASSERTMSG(draw_corner[corner] && draw_corner[(corner + 1) % 4], + "Border edges can only be drawn if both of its connected corners are drawn."); + + FillEdge(edge1 == LEFT ? offset_vertices : (int)vertices.size()); + } + } +} + +void GeometryBackgroundBorder::DrawBackgroundCorner(Corner corner, Vector2f pos_inner, Vector2f pos_circle_center, float R, Vector2f r, + ColourbPremultiplied color) +{ + if (R == 0 || r.x <= 0 || r.y <= 0) + { + DrawPoint(pos_inner, color); + } + else if (r.x > 0 && r.y > 0) + { + const float a0 = float((int)corner + 2) * 0.5f * Math::RMLUI_PI; + const float a1 = float((int)corner + 3) * 0.5f * Math::RMLUI_PI; + const int num_points = GetNumPoints(R); + DrawArc(pos_circle_center, r, a0, a1, color, color, num_points); + } +} + +void GeometryBackgroundBorder::DrawPoint(Vector2f pos, ColourbPremultiplied color) +{ + const int offset_vertices = (int)vertices.size(); + + vertices.resize(offset_vertices + 1); + + vertices[offset_vertices].position = pos; + vertices[offset_vertices].colour = color; +} + +void GeometryBackgroundBorder::DrawArc(Vector2f pos_center, Vector2f r, float a0, float a1, ColourbPremultiplied color0, ColourbPremultiplied color1, + int num_points) +{ + RMLUI_ASSERT(num_points >= 2 && r.x > 0 && r.y > 0); + + const int offset_vertices = (int)vertices.size(); + + vertices.resize(offset_vertices + num_points); + + for (int i = 0; i < num_points; i++) + { + const float t = float(i) / float(num_points - 1); + + const float a = Math::Lerp(t, a0, a1); + + const Vector2f unit_vector(Math::Cos(a), Math::Sin(a)); + + vertices[offset_vertices + i].position = unit_vector * r + pos_center; + vertices[offset_vertices + i].colour = Math::RoundedLerp(t, color0, color1); + } +} + +void GeometryBackgroundBorder::FillBackground(int index_start) +{ + const int num_added_vertices = (int)vertices.size() - index_start; + const int offset_indices = (int)indices.size(); + + const int num_triangles = (num_added_vertices - 2); + + indices.resize(offset_indices + 3 * num_triangles); + + for (int i = 0; i < num_triangles; i++) + { + indices[offset_indices + 3 * i] = index_start; + indices[offset_indices + 3 * i + 1] = index_start + i + 2; + indices[offset_indices + 3 * i + 2] = index_start + i + 1; + } +} + +void GeometryBackgroundBorder::DrawBorderCorner(Corner corner, Vector2f pos_outer, Vector2f pos_inner, Vector2f pos_circle_center, float R, + Vector2f r, ColourbPremultiplied color0, ColourbPremultiplied color1) +{ + const float a0 = float((int)corner + 2) * 0.5f * Math::RMLUI_PI; + const float a1 = float((int)corner + 3) * 0.5f * Math::RMLUI_PI; + + if (R == 0) + { + DrawPointPoint(pos_outer, pos_inner, color0, color1); + } + else if (r.x > 0 && r.y > 0) + { + DrawArcArc(pos_circle_center, R, r, a0, a1, color0, color1, GetNumPoints(R)); + } + else + { + DrawArcPoint(pos_circle_center, pos_inner, R, a0, a1, color0, color1, GetNumPoints(R)); + } +} + +void GeometryBackgroundBorder::DrawPointPoint(Vector2f pos_outer, Vector2f pos_inner, ColourbPremultiplied color0, ColourbPremultiplied color1) +{ + const bool different_color = (color0 != color1); + + vertices.reserve((int)vertices.size() + (different_color ? 4 : 2)); + + DrawPoint(pos_inner, color0); + DrawPoint(pos_outer, color0); + + if (different_color) + { + DrawPoint(pos_inner, color1); + DrawPoint(pos_outer, color1); + } +} + +void GeometryBackgroundBorder::DrawArcArc(Vector2f pos_center, float R, Vector2f r, float a0, float a1, ColourbPremultiplied color0, + ColourbPremultiplied color1, int num_points) +{ + RMLUI_ASSERT(num_points >= 2 && R > 0 && r.x > 0 && r.y > 0); + + const int num_triangles = 2 * (num_points - 1); + + const int offset_vertices = (int)vertices.size(); + const int offset_indices = (int)indices.size(); + + vertices.resize(offset_vertices + 2 * num_points); + indices.resize(offset_indices + 3 * num_triangles); + + for (int i = 0; i < num_points; i++) + { + const float t = float(i) / float(num_points - 1); + + const float a = Math::Lerp(t, a0, a1); + const ColourbPremultiplied color = Math::RoundedLerp(t, color0, color1); + const Vector2f unit_vector(Math::Cos(a), Math::Sin(a)); + + vertices[offset_vertices + 2 * i].position = unit_vector * r + pos_center; + vertices[offset_vertices + 2 * i].colour = color; + vertices[offset_vertices + 2 * i + 1].position = unit_vector * R + pos_center; + vertices[offset_vertices + 2 * i + 1].colour = color; + } + + for (int i = 0; i < num_triangles; i += 2) + { + indices[offset_indices + 3 * i + 0] = offset_vertices + i + 0; + indices[offset_indices + 3 * i + 1] = offset_vertices + i + 2; + indices[offset_indices + 3 * i + 2] = offset_vertices + i + 1; + + indices[offset_indices + 3 * i + 3] = offset_vertices + i + 1; + indices[offset_indices + 3 * i + 4] = offset_vertices + i + 2; + indices[offset_indices + 3 * i + 5] = offset_vertices + i + 3; + } +} + +void GeometryBackgroundBorder::DrawArcPoint(Vector2f pos_center, Vector2f pos_inner, float R, float a0, float a1, ColourbPremultiplied color0, + ColourbPremultiplied color1, int num_points) +{ + RMLUI_ASSERT(R > 0 && num_points >= 2); + + const int offset_vertices = (int)vertices.size(); + vertices.reserve(offset_vertices + num_points + 2); + + // Generate the vertices. We could also split the arc mid-way to create a sharp color transition. + DrawPoint(pos_inner, color0); + DrawArc(pos_center, Vector2f(R), a0, a1, color0, color1, num_points); + DrawPoint(pos_inner, color1); + + RMLUI_ASSERT((int)vertices.size() - offset_vertices == num_points + 2); + + // Swap the last two vertices such that the outer edge vertex is last, see the comment for the border drawing functions. Their colors should + // already be the same. + const int last_vertex = (int)vertices.size() - 1; + std::swap(vertices[last_vertex - 1].position, vertices[last_vertex].position); + + // Generate the indices + const int num_triangles = (num_points - 1); + + const int i_vertex_inner0 = offset_vertices; + const int i_vertex_inner1 = last_vertex - 1; + + const int offset_indices = (int)indices.size(); + indices.resize(offset_indices + 3 * num_triangles); + + for (int i = 0; i < num_triangles; i++) + { + indices[offset_indices + 3 * i + 0] = (i > num_triangles / 2 ? i_vertex_inner1 : i_vertex_inner0); + indices[offset_indices + 3 * i + 1] = offset_vertices + i + 2; + indices[offset_indices + 3 * i + 2] = offset_vertices + i + 1; + } + + // Since we swapped the last two vertices we also need to change the last triangle. + indices[offset_indices + 3 * (num_triangles - 1) + 1] = last_vertex; +} + +void GeometryBackgroundBorder::FillEdge(int index_next_corner) +{ + const int offset_indices = (int)indices.size(); + const int num_vertices = (int)vertices.size(); + RMLUI_ASSERT(num_vertices >= 2); + + indices.resize(offset_indices + 6); + + indices[offset_indices + 0] = num_vertices - 2; + indices[offset_indices + 1] = index_next_corner; + indices[offset_indices + 2] = num_vertices - 1; + + indices[offset_indices + 3] = num_vertices - 1; + indices[offset_indices + 4] = index_next_corner; + indices[offset_indices + 5] = index_next_corner + 1; +} + +int GeometryBackgroundBorder::GetNumPoints(float R) const +{ + return Math::Clamp(3 + Math::RoundToInteger(R / 6.f), 2, 100); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/GeometryBackgroundBorder.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/GeometryBackgroundBorder.h new file mode 100644 index 00000000..a0808439 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/GeometryBackgroundBorder.h @@ -0,0 +1,109 @@ +#pragma once + +#include "../../Include/RmlUi/Core/Types.h" +#include "../../Include/RmlUi/Core/Vertex.h" + +namespace Rml { + +class Box; + +// Ordered by top, right, bottom, left. +using EdgeSizes = Array; + +// Ordered by top-left, top-right, bottom-right, bottom-left. +using CornerSizes = Array; +using CornerSizes2 = Array; +using CornerPositions = Array; + +// The background-border metrics specify an inner and an outer rectangular area, whose corners can be rounded. +struct BorderMetrics { + // Outer corner positions (e.g. at border edge) + CornerPositions positions_outer; + // Inner corner positions (e.g. at padding edge) + CornerPositions positions_inner; + // Curved borders are drawn as circles (outer border) and ellipses (inner border) around the centers. + CornerPositions positions_circle_center; + + // Radii of the outer edges, always circles. + CornerSizes outer_radii; + // Radii of the inner edges, 2-dimensional as these can be elliptic. + // The inner radii is effectively the (signed) distance from the circle center to the inner edge. + // They can also be zero or negative, in which case a sharp corner should be drawn instead of an arc. + CornerSizes2 inner_radii; +}; + +class GeometryBackgroundBorder { +public: + // Construct the background-border geometry drawer, passing in the target vertex and index lists to be filled by later draw operations. + GeometryBackgroundBorder(Vector& vertices, Vector& indices); + + /// Compute background-border metrics used by later draw operations. + /// @param outer_position Top-left position of the outer edge. + /// @param edge_sizes Widths of the border. + /// @param inner_size Size of the inner area. + /// @param outer_radii The radius of the outer edge at each corner. + /// @return The computed metrics. + static BorderMetrics ComputeBorderMetrics(Vector2f outer_position, EdgeSizes edge_sizes, Vector2f inner_size, CornerSizes outer_radii); + + // Generate geometry for the background, defined by the inner area of the border metrics. + void DrawBackground(const BorderMetrics& metrics, ColourbPremultiplied color); + + /// Generate geometry for the border, defined by the intersection of the outer and inner areas of the border metrics. + void DrawBorder(const BorderMetrics& metrics, EdgeSizes edge_sizes, const ColourbPremultiplied border_colors[4]); + +private: + enum Edge { TOP, RIGHT, BOTTOM, LEFT }; + enum Corner { TOP_LEFT, TOP_RIGHT, BOTTOM_RIGHT, BOTTOM_LEFT }; + + // -- Background -- + // All draw operations place vertices in clockwise order. + + // Draw the corner, delegate to the specific corner shape drawing function. + void DrawBackgroundCorner(Corner corner, Vector2f pos_inner, Vector2f pos_circle_center, float R, Vector2f r, ColourbPremultiplied color); + + // Add a single point. + void DrawPoint(Vector2f pos, ColourbPremultiplied color); + + // Draw an arc by placing vertices along the ellipse formed by the two-axis radius r, spaced evenly between angles a0,a1 (inclusive). Colors are + // interpolated. + void DrawArc(Vector2f pos_center, Vector2f r, float a0, float a1, ColourbPremultiplied color0, ColourbPremultiplied color1, int num_points); + + // Generates triangles by connecting the added vertices. + void FillBackground(int index_start); + + // -- Border -- + // All draw operations place the first and last vertices in the following manner. + // Let N be the number of vertices placed, and the numbers be indices into the newly placed vertices: + // 0: Inner edge, aligned with the previous corner + // 1: Outer edge, aligned with the previous corner + // N-2: Inner edge, aligned with the next corner + // N-1: Outer edge, aligned with the next corner + // Where 'next' corner means along the clockwise direction. This way we can easily fill the triangles of the edges in FillEdge(). + + // Draw the corner, delegate to the specific corner shape drawing function. + void DrawBorderCorner(Corner corner, Vector2f pos_outer, Vector2f pos_inner, Vector2f pos_circle_center, float R, Vector2f r, + ColourbPremultiplied color0, ColourbPremultiplied color1); + + // Draw a sharp border corner, ie. no border-radius. Does not produce any triangles. + void DrawPointPoint(Vector2f pos_outer, Vector2f pos_inner, ColourbPremultiplied color0, ColourbPremultiplied color1); + + // Draw an arc along the outer edge (radius R), and an arc along the inner edge (two-axis radius r), + // spaced evenly between angles a0,a1 (inclusive). Connect them by triangles. Colors are interpolated. + void DrawArcArc(Vector2f pos_center, float R, Vector2f r, float a0, float a1, ColourbPremultiplied color0, ColourbPremultiplied color1, + int num_points); + + // Draw an arc along the outer edge, and connect them by triangles to a point on the inner edge. + void DrawArcPoint(Vector2f pos_center, Vector2f pos_inner, float R, float a0, float a1, ColourbPremultiplied color0, ColourbPremultiplied color1, + int num_points); + + // Add triangles between the previous corner to another one specified by the index (possibly yet-to-be-drawn). + void FillEdge(int index_next_corner); + + // -- Tools -- + int GetNumPoints(float R) const; + + Vector& vertices; + Vector& indices; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/GeometryBoxShadow.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/GeometryBoxShadow.cpp new file mode 100644 index 00000000..e729a75f --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/GeometryBoxShadow.cpp @@ -0,0 +1,244 @@ +#include "GeometryBoxShadow.h" +#include "../../Include/RmlUi/Core/Box.h" +#include "../../Include/RmlUi/Core/CompiledFilterShader.h" +#include "../../Include/RmlUi/Core/DecorationTypes.h" +#include "../../Include/RmlUi/Core/Element.h" +#include "../../Include/RmlUi/Core/Geometry.h" +#include "../../Include/RmlUi/Core/Math.h" +#include "../../Include/RmlUi/Core/MeshUtilities.h" +#include "../../Include/RmlUi/Core/Profiling.h" +#include "../../Include/RmlUi/Core/RenderManager.h" + +namespace Rml { + +BoxShadowGeometryInfo GeometryBoxShadow::Resolve(Element* element, const CornerSizes& border_radius, ColourbPremultiplied background_color, + const Array& border_colors, float opacity) +{ + RMLUI_ZoneScoped; + + // Find the box-shadow texture dimension and offset required to cover all box-shadows and element boxes combined. + Vector2f element_offset_in_texture; + Vector2i texture_dimensions; + + const Property* p_box_shadow = element->GetLocalProperty(PropertyId::BoxShadow); + RMLUI_ASSERT(p_box_shadow->value.GetType() == Variant::BOXSHADOWLIST); + BoxShadowList shadow_list = p_box_shadow->value.Get(); + + // Resolve all lengths to px units. + for (BoxShadow& shadow : shadow_list) + { + shadow.blur_radius = NumericValue(element->ResolveLength(shadow.blur_radius), Unit::PX); + shadow.spread_distance = NumericValue(element->ResolveLength(shadow.spread_distance), Unit::PX); + shadow.offset_x = NumericValue(element->ResolveLength(shadow.offset_x), Unit::PX); + shadow.offset_y = NumericValue(element->ResolveLength(shadow.offset_y), Unit::PX); + } + + { + Vector2f extend_min; + Vector2f extend_max; + + // Extend the render-texture to encompass box-shadow blur and spread. + for (const BoxShadow& shadow : shadow_list) + { + if (!shadow.inset) + { + const float extend = 1.5f * shadow.blur_radius.number + shadow.spread_distance.number; + const Vector2f offset = {shadow.offset_x.number, shadow.offset_y.number}; + extend_min = Math::Min(extend_min, offset - Vector2f(extend)); + extend_max = Math::Max(extend_max, offset + Vector2f(extend)); + } + } + + Rectanglef texture_region; + + // Extend the render-texture further to cover all the element's boxes. + for (int i = 0; i < element->GetNumBoxes(); i++) + { + const RenderBox box = element->GetRenderBox(BoxArea::Border, i); + texture_region = texture_region.Join(Rectanglef::FromPositionSize(box.GetBorderOffset(), box.GetFillSize())); + } + + texture_region = texture_region.Extend(-extend_min, extend_max); + Math::ExpandToPixelGrid(texture_region); + + element_offset_in_texture = -texture_region.TopLeft(); + texture_dimensions = Vector2i(texture_region.Size()); + } + + // Since we can reuse textures across multiple box shadows with the same properties, + // we need to copy the element's box shadow list and the background and border geometry. + RenderBoxList padding_render_boxes{}; + RenderBoxList border_render_boxes{}; + + for (int i = 0; i < element->GetNumBoxes(); i++) + { + padding_render_boxes.push_back(element->GetRenderBox(BoxArea::Padding, i)); + border_render_boxes.push_back(element->GetRenderBox(BoxArea::Border, i)); + } + + // Finally, create cache information + BoxShadowGeometryInfo geometry_info; + geometry_info.background_color = background_color; + geometry_info.border_colors = border_colors; + geometry_info.border_radius = border_radius; + geometry_info.texture_dimensions = texture_dimensions; + geometry_info.element_offset_in_texture = element_offset_in_texture; + geometry_info.padding_render_boxes = std::move(padding_render_boxes); + geometry_info.border_render_boxes = std::move(border_render_boxes); + geometry_info.shadow_list = std::move(shadow_list); + geometry_info.opacity = opacity; + return geometry_info; +} + +void GeometryBoxShadow::GenerateTexture(CallbackTexture& out_shadow_texture, Geometry& out_background_border_geometry, RenderManager& render_manager, + const BoxShadowGeometryInfo& info) +{ + RMLUI_ZoneScoped; + + Mesh mesh = out_background_border_geometry.Release(Geometry::ReleaseMode::ClearMesh); + for (size_t i = 0; i < info.padding_render_boxes.size(); i++) + MeshUtilities::GenerateBackgroundBorder(mesh, info.padding_render_boxes[i], info.background_color, info.border_colors.data()); + out_background_border_geometry = render_manager.MakeGeometry(std::move(mesh)); + + // Callback for generating the box-shadow texture. Using a callback ensures that the texture can be regenerated at any time, for example if the + // device loses its GPU context and the client calls Rml::ReleaseTextures(). + auto texture_callback = [&info, &out_background_border_geometry](const CallbackTextureInterface& texture_interface) -> bool { + RMLUI_ASSERT(info.border_render_boxes.size() == info.padding_render_boxes.size()); + RMLUI_ZoneScopedN("BoxShadow::GenerateTexture::Callback"); + size_t num_boxes = info.border_render_boxes.size(); + + RenderManager& render_manager = texture_interface.GetRenderManager(); + + Mesh mesh_padding; // Render geometry for inner box-shadow. + Mesh mesh_padding_border; // Clipping mask for outer box-shadow. + + bool has_inner_shadow = false; + bool has_outer_shadow = false; + for (const BoxShadow& shadow : info.shadow_list) + { + if (shadow.inset) + has_inner_shadow = true; + else + has_outer_shadow = true; + } + + // Generate the geometry for all the element's boxes and extend the render-texture further to cover all of them. + for (size_t i = 0; i < num_boxes; i++) + { + ColourbPremultiplied white(255); + if (has_inner_shadow) + MeshUtilities::GenerateBackground(mesh_padding, info.padding_render_boxes[i], white); + if (has_outer_shadow) + MeshUtilities::GenerateBackground(mesh_padding_border, info.border_render_boxes[i], white); + } + + const RenderState initial_render_state = render_manager.GetState(); + render_manager.ResetState(); + render_manager.SetScissorRegion(Rectanglei::FromSize(info.texture_dimensions)); + + // The scissor region will be clamped to the current window size, check the resulting scissor region. + const Rectanglei scissor_region = render_manager.GetScissorRegion(); + if (scissor_region.Width() <= 0 || scissor_region.Height() <= 0) + { + // The window may become zero-sized for example when minimized. Just skip the texture generation for now, we + // expect to be called again later when the window is restored. + render_manager.SetState(initial_render_state); + return false; + } + if (scissor_region != Rectanglei::FromSize(info.texture_dimensions)) + { + Log::Message(Log::LT_INFO, + "The desired box-shadow texture dimensions (%d, %d) are larger than the current window region (%d, %d). " + "Results may be clipped.", + info.texture_dimensions.x, info.texture_dimensions.y, scissor_region.Width(), scissor_region.Height()); + } + + render_manager.PushLayer(); + out_background_border_geometry.Render(info.element_offset_in_texture); + + for (int shadow_index = (int)info.shadow_list.size() - 1; shadow_index >= 0; shadow_index--) + { + const BoxShadow& shadow = info.shadow_list[shadow_index]; + const Vector2f shadow_offset = {shadow.offset_x.number, shadow.offset_y.number}; + const bool inset = shadow.inset; + const float spread_distance = shadow.spread_distance.number; + const float blur_radius = shadow.blur_radius.number; + + CornerSizes spread_radii = info.border_radius; + for (int i = 0; i < 4; i++) + { + float& radius = spread_radii[i]; + float spread_factor = (inset ? -1.f : 1.f); + if (radius < spread_distance) + { + const float ratio_minus_one = (radius / spread_distance) - 1.f; + spread_factor *= 1.f + ratio_minus_one * ratio_minus_one * ratio_minus_one; + } + radius = Math::Max(radius + spread_factor * spread_distance, 0.f); + } + + Mesh mesh_shadow; + + // Generate the shadow geometry. For outer box-shadows it is rendered normally, while for inset box-shadows it is used as a clipping mask. + for (size_t i = 0; i < num_boxes; i++) + { + const float signed_spread_distance = (inset ? -spread_distance : spread_distance); + RenderBox render_box = (inset ? info.padding_render_boxes : info.border_render_boxes)[i]; + render_box.SetFillSize(Math::Max(render_box.GetFillSize() + Vector2f(2.f * signed_spread_distance), Vector2f{0.001f})); + render_box.SetBorderRadius(spread_radii); + render_box.SetBorderOffset(render_box.GetBorderOffset() - Vector2f(signed_spread_distance)); + MeshUtilities::GenerateBackground(mesh_shadow, render_box, shadow.color); + } + + CompiledFilter blur; + if (blur_radius >= 0.5f) + { + blur = render_manager.CompileFilter("blur", Dictionary{{"sigma", Variant(0.5f * blur_radius)}}); + if (blur) + render_manager.PushLayer(); + } + + Geometry geometry_shadow = render_manager.MakeGeometry(std::move(mesh_shadow)); + + if (inset) + { + render_manager.SetClipMask(ClipMaskOperation::SetInverse, &geometry_shadow, shadow_offset + info.element_offset_in_texture); + + for (Rml::Vertex& vertex : mesh_padding.vertices) + vertex.colour = shadow.color; + + // @performance: Don't need to copy the mesh if this is the last use of it. + Geometry geometry_padding = render_manager.MakeGeometry(Mesh(mesh_padding)); + geometry_padding.Render(info.element_offset_in_texture); + + render_manager.SetClipMask(ClipMaskOperation::Set, &geometry_padding, info.element_offset_in_texture); + } + else + { + Mesh mesh = mesh_padding_border; + Geometry geometry_padding_border = render_manager.MakeGeometry(std::move(mesh)); + render_manager.SetClipMask(ClipMaskOperation::SetInverse, &geometry_padding_border, info.element_offset_in_texture); + geometry_shadow.Render(shadow_offset + info.element_offset_in_texture); + } + + if (blur) + { + FilterHandleList filters; + blur.AddHandleTo(filters); + render_manager.CompositeLayers(render_manager.GetTopLayer(), render_manager.GetNextLayer(), BlendMode::Blend, filters); + render_manager.PopLayer(); + blur.Release(); + } + } + + texture_interface.SaveLayerAsTexture(); + + render_manager.PopLayer(); + render_manager.SetState(initial_render_state); + + return true; + }; + + out_shadow_texture = render_manager.MakeCallbackTexture(std::move(texture_callback)); +} +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/GeometryBoxShadow.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/GeometryBoxShadow.h new file mode 100644 index 00000000..e9c17442 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/GeometryBoxShadow.h @@ -0,0 +1,57 @@ +#pragma once + +#include "../../Include/RmlUi/Core/DecorationTypes.h" +#include "../../Include/RmlUi/Core/RenderBox.h" +#include "../../Include/RmlUi/Core/Types.h" + +namespace Rml { +using RenderBoxList = Vector; +struct BoxShadowGeometryInfo { + ColourbPremultiplied background_color; + Array border_colors; + CornerSizes border_radius; + Vector2i texture_dimensions; + Vector2f element_offset_in_texture; + RenderBoxList padding_render_boxes; + RenderBoxList border_render_boxes; + BoxShadowList shadow_list; + float opacity; +}; +inline bool operator==(const BoxShadowGeometryInfo& a, const BoxShadowGeometryInfo& b) +{ + return a.background_color == b.background_color && a.border_colors == b.border_colors && a.border_radius == b.border_radius && + a.texture_dimensions == b.texture_dimensions && a.element_offset_in_texture == b.element_offset_in_texture && + a.padding_render_boxes == b.padding_render_boxes && a.border_render_boxes == b.border_render_boxes && a.shadow_list == b.shadow_list && + a.opacity == b.opacity; +} +inline bool operator!=(const BoxShadowGeometryInfo& a, const BoxShadowGeometryInfo& b) +{ + return !(a == b); +} + +class Geometry; +class CallbackTexture; +class RenderManager; + +class GeometryBoxShadow { +public: + /// Resolve the element's properties into its box geometry info. + /// @param[in] element The element to resolve. + /// @param[in] border_radius The border radius of the element. + /// @param[in] background_color The background colour of the element. + /// @param[in] border_colors The border colours of the element. + /// @param[in] opacity The computed opacity of the element. + static BoxShadowGeometryInfo Resolve(Element* element, const CornerSizes& border_radius, ColourbPremultiplied background_color, + const Array& border_colors, float opacity); + + /// Generate the texture and geometry for a box shadow and including the element's background and border. + /// @param[out] out_shadow_texture The target texture, assumes pointer stability during the lifetime of the shadow geometry. + /// @param[out] out_background_border_geometry The generated geometry for the element's background and border. + /// @param[in] render_manager The render manager to generate the shadow for. + /// @param[in] shadow_geometry_info The resolved box shadow geometry of a given element. + /// @see GeometryBoxShadow::Resolve() + static void GenerateTexture(CallbackTexture& out_shadow_texture, Geometry& out_background_border_geometry, RenderManager& render_manager, + const BoxShadowGeometryInfo& shadow_geometry_info); +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/IdNameMap.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/IdNameMap.h new file mode 100644 index 00000000..ad1fd6f3 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/IdNameMap.h @@ -0,0 +1,85 @@ +#pragma once + +#include "../../Include/RmlUi/Core/Header.h" +#include "../../Include/RmlUi/Core/Types.h" +#include + +namespace Rml { + +template +class IdNameMap { + Vector name_map; // IDs are indices into the name_map + UnorderedMap reverse_map; + +protected: + IdNameMap(size_t num_ids_to_reserve) + { + static_assert((int)ID::Invalid == 0, "Invalid id must be zero"); + name_map.reserve(num_ids_to_reserve); + reverse_map.reserve(num_ids_to_reserve); + AddPair(ID::Invalid, "invalid"); + } + +public: + void AddPair(ID id, const String& name) + { + // Should only be used for defined IDs + if ((size_t)id >= name_map.size()) + name_map.resize(1 + (size_t)id); + name_map[(size_t)id] = name; + bool inserted = reverse_map.emplace(name, id).second; + RMLUI_ASSERT(inserted); + (void)inserted; + } + + bool AssertAllInserted(ID number_of_defined_ids) const + { + std::ptrdiff_t cnt = std::count_if(name_map.begin(), name_map.end(), [](const String& name) { return !name.empty(); }); + return cnt == (std::ptrdiff_t)number_of_defined_ids && reverse_map.size() == (size_t)number_of_defined_ids; + } + + ID GetId(const String& name) const + { + auto it = reverse_map.find(name); + if (it != reverse_map.end()) + return it->second; + return ID::Invalid; + } + const String& GetName(ID id) const + { + if (static_cast(id) < name_map.size()) + return name_map[static_cast(id)]; + return name_map[static_cast(ID::Invalid)]; + } + + ID GetOrCreateId(const String& name) + { + // All predefined properties must be set before possibly adding custom properties here + RMLUI_ASSERT(name_map.size() == reverse_map.size()); + + ID next_id = static_cast(name_map.size()); + + // Only insert if not already in list + auto pair = reverse_map.emplace(name, next_id); + const auto& it = pair.first; + bool inserted = pair.second; + + if (inserted) + name_map.push_back(name); + + // Return the property id that already existed, or the new one if inserted + return it->second; + } +}; + +class PropertyIdNameMap : public IdNameMap { +public: + PropertyIdNameMap(size_t reserve_num_properties) : IdNameMap(reserve_num_properties) {} +}; + +class ShorthandIdNameMap : public IdNameMap { +public: + ShorthandIdNameMap(size_t reserve_num_shorthands) : IdNameMap(reserve_num_shorthands) {} +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/BlockContainer.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/BlockContainer.cpp new file mode 100644 index 00000000..f153f78f --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/BlockContainer.cpp @@ -0,0 +1,535 @@ +#include "BlockContainer.h" +#include "../../../Include/RmlUi/Core/ComputedValues.h" +#include "../../../Include/RmlUi/Core/Element.h" +#include "../../../Include/RmlUi/Core/ElementScroll.h" +#include "../../../Include/RmlUi/Core/Profiling.h" +#include "FloatedBoxSpace.h" +#include "InlineContainer.h" +#include "LayoutDetails.h" +#include "LineBox.h" + +namespace Rml { + +BlockContainer::BlockContainer(ContainerBox* _parent_container, FloatedBoxSpace* _space, Element* _element, const Box& _box, float _min_height, + float _max_height) : + ContainerBox(Type::BlockContainer, _element, _parent_container), box(_box), min_height(_min_height), max_height(_max_height), space(_space) +{ + RMLUI_ASSERT(element); + RMLUI_ASSERT(box.GetSize().x >= 0.f); + + if (!space) + { + // We are the root of the formatting context, establish a new space for floated boxes. + root_space = MakeUnique(); + space = root_space.get(); + } +} + +BlockContainer::~BlockContainer() {} + +bool BlockContainer::Close(BlockContainer* parent_block_container) +{ + // If the last child of this block box is an inline box, then we haven't closed it; close it now! + if (!CloseOpenInlineContainer()) + return false; + + // Set this box's height, if necessary. + if (box.GetSize().y < 0) + { + float content_height = box_cursor; + + if (!parent_block_container) + content_height = Math::Max(content_height, space->GetDimensions(FloatedBoxEdge::Margin).y - (position.y + box.GetPosition().y)); + + content_height = Math::Clamp(content_height, min_height, max_height); + box.SetContent({box.GetSize().x, content_height}); + } + + // Find the size of our content. + const Vector2f space_box = space->GetDimensions(FloatedBoxEdge::Overflow) - (position + box.GetPosition()); + Vector2f content_box = Math::Max(inner_content_size, space_box); + content_box.y = Math::Max(content_box.y, box_cursor); + + if (!SubmitBox(content_box, box, max_height)) + return false; + + // If we are the root of our block formatting context, this will be null. Otherwise increment our parent's cursor to account for this box. + if (parent_block_container) + { + RMLUI_ASSERTMSG(GetParent() == parent_block_container, "Mismatched parent box."); + + // If this close fails, it means this block box has caused our parent box to generate an automatic vertical scrollbar. + if (!parent_block_container->EncloseChildBox(this, position, box.GetSizeAcross(BoxDirection::Vertical, BoxArea::Border), + box.GetEdge(BoxArea::Margin, BoxEdge::Bottom))) + return false; + } + + // Now that we have been sized, we can proceed with formatting and placing positioned elements that this container + // acts as a containing block for. + ClosePositionedElements(); + + // Find the element baseline which is the distance from the margin bottom of the element to its baseline. + float element_baseline = 0; + + // For inline-blocks with visible overflow, this is the baseline of the last line of the element (see CSS2 10.8.1). + if (element->GetDisplay() == Style::Display::InlineBlock && !IsScrollContainer()) + { + float baseline = 0; + bool found_baseline = GetBaselineOfLastLine(baseline); + + // The retrieved baseline is the vertical distance from the top of our root space (the coordinate system of our + // local block formatting context), convert it to the element's local coordinates. + if (found_baseline) + { + const float bottom_position = + position.y + box.GetSizeAcross(BoxDirection::Vertical, BoxArea::Border) + box.GetEdge(BoxArea::Margin, BoxEdge::Bottom); + element_baseline = bottom_position - baseline; + } + } + + SetElementBaseline(element_baseline); + + EnsureEmptyInterruptedLineBox(); + + SubmitElementLayout(); + + return true; +} + +bool BlockContainer::EncloseChildBox(LayoutBox* child, Vector2f child_position, float child_height, float child_margin_bottom) +{ + child_position -= (box.GetPosition() + position); + + box_cursor = child_position.y + child_height + child_margin_bottom; + + // Extend the inner content size. The vertical size can be larger than the box_cursor due to overflow. + inner_content_size = Math::Max(inner_content_size, child_position + child->GetVisibleOverflowSize()); + + const Vector2f content_size = Math::Max(Vector2f{box.GetSize().x, box_cursor}, inner_content_size); + + const bool result = CatchOverflow(content_size, box, max_height); + + return result; +} + +BlockContainer* BlockContainer::OpenBlockBox(Element* child_element, const Box& child_box, float min_height, float max_height) +{ + if (!CloseOpenInlineContainer()) + return nullptr; + + auto child_container_ptr = MakeUnique(this, space, child_element, child_box, min_height, max_height); + BlockContainer* child_container = child_container_ptr.get(); + + child_container->position = NextBoxPosition(child_box, child_element->GetComputedValues().clear()); + child_element->SetOffset(child_container->position - position, element); + + child_container->ResetScrollbars(child_box); + + // Store relatively positioned elements with their containing block so that their offset can be updated after + // their containing block has been sized. + if (child_element->GetPosition() == Style::Position::Relative) + AddRelativeElement(child_element); + + child_boxes.push_back(std::move(child_container_ptr)); + + return child_container; +} + +LayoutBox* BlockContainer::AddBlockLevelBox(UniquePtr block_level_box_ptr, Element* child_element, const Box& child_box) +{ + RMLUI_ASSERT(child_box.GetSize().y >= 0.f); // Assumes child element already formatted and sized. + + if (!CloseOpenInlineContainer()) + return nullptr; + + // Clear any floats to avoid overlapping them. In CSS, it is allowed to instead shrink the box and place it next to + // any floats, but we keep it simple here for now and just clear them. + Vector2f child_position = NextBoxPosition(child_box, Style::Clear::Both); + + child_element->SetOffset(child_position - position, element); + + if (child_element->GetPosition() == Style::Position::Relative) + AddRelativeElement(child_element); + + LayoutBox* block_level_box = block_level_box_ptr.get(); + child_boxes.push_back(std::move(block_level_box_ptr)); + + if (!EncloseChildBox(block_level_box, child_position, child_box.GetSizeAcross(BoxDirection::Vertical, BoxArea::Border), + child_box.GetEdge(BoxArea::Margin, BoxEdge::Bottom))) + return nullptr; + + return block_level_box; +} + +InlineBoxHandle BlockContainer::AddInlineElement(Element* element, const Box& child_box) +{ + RMLUI_ZoneScoped; + + // Inline-level elements need to be added to an inline container, open one if needed. + InlineContainer* inline_container = EnsureOpenInlineContainer(); + + InlineBox* inline_box = inline_container->AddInlineElement(element, child_box); + + if (element->GetPosition() == Style::Position::Relative) + AddRelativeElement(element); + + return {inline_box}; +} + +void BlockContainer::CloseInlineElement(InlineBoxHandle handle) +{ + // If the inline-level element did not generate an inline box, then there is no need to close anything. + if (!handle.inline_box) + return; + + // Usually the inline container the box was placed in is still the open box, and we can just close the inline + // element in it. However, it is possible that an intermediary block-level element was placed, thereby splitting the + // inline element into multiple inline containers around the block-level box. If we don't have an open inline + // container at all, open a new one, even if the sole purpose of the new line is to close this inline element. + EnsureOpenInlineContainer()->CloseInlineElement(handle.inline_box); +} + +void BlockContainer::AddBreak() +{ + const float line_height = element->GetLineHeight(); + + // Check for an inline box as our last child; if so, we can simply end its line and bail. + if (InlineContainer* inline_container = GetOpenInlineContainer()) + { + inline_container->AddBreak(line_height); + return; + } + + // No inline box as our last child; no problem, just increment the cursor by the line height of this element. + box_cursor += line_height; +} + +void BlockContainer::AddFloatElement(Element* element, Vector2f visible_overflow_size) +{ + if (InlineContainer* inline_container = GetOpenInlineContainer()) + { + // Try to add the float to our inline container, placing it next to any open line if possible. Otherwise, queue it for later. + bool float_placed = false; + float line_position_top = 0.f; + Vector2f line_size; + if (queued_float_elements.empty() && inline_container->GetOpenLineBoxDimensions(line_position_top, line_size)) + { + const Vector2f margin_size = element->GetBox().GetSize(BoxArea::Margin); + const Style::Float float_property = element->GetComputedValues().float_(); + const Style::Clear clear_property = element->GetComputedValues().clear(); + + float available_width = 0.f; + const Vector2f float_position = + space->NextFloatPosition(this, available_width, line_position_top, margin_size, float_property, clear_property); + + const float line_position_bottom = line_position_top + line_size.y; + const float line_and_element_width = margin_size.x + line_size.x; + + // If the float can be positioned on the open line, and it can fit next to the line's contents, place it now. + if (float_position.y < line_position_bottom && line_and_element_width <= available_width) + { + PlaceFloat(element, line_position_top, visible_overflow_size); + inline_container->UpdateOpenLineBoxPlacement(); + float_placed = true; + } + } + + if (!float_placed) + queued_float_elements.push_back({element, visible_overflow_size}); + } + else + { + // There is no inline container, so just place it! + const Vector2f box_position = NextBoxPosition(); + PlaceFloat(element, box_position.y, visible_overflow_size); + } + + if (element->GetPosition() == Style::Position::Relative) + AddRelativeElement(element); +} + +Vector2f BlockContainer::GetOpenStaticPosition(Style::Display display) const +{ + // Estimate the next box as if it had static position (10.6.4). If the element is inline-level, position it on the + // open line if we have one. Otherwise, block-level elements are positioned on a hypothetical next line. + Vector2f static_position = NextBoxPosition(); + + if (const InlineContainer* inline_container = GetOpenInlineContainer()) + { + const bool inline_level_element = (display == Style::Display::Inline || display == Style::Display::InlineBlock); + static_position += inline_container->GetStaticPositionEstimate(inline_level_element); + } + + return static_position; +} + +Vector2f BlockContainer::NextBoxPosition() const +{ + Vector2f box_position = position + box.GetPosition(); + box_position.y += box_cursor; + return box_position; +} + +Vector2f BlockContainer::NextBoxPosition(const Box& child_box, Style::Clear clear_property) const +{ + const float child_top_margin = child_box.GetEdge(BoxArea::Margin, BoxEdge::Top); + + Vector2f box_position = NextBoxPosition(); + + box_position.x += child_box.GetEdge(BoxArea::Margin, BoxEdge::Left); + box_position.y += child_top_margin; + + float clear_margin = space->DetermineClearPosition(box_position.y, clear_property) - box_position.y; + if (clear_margin > 0.f) + { + box_position.y += clear_margin; + } + else if (const LayoutBox* block_box = GetOpenLayoutBox()) + { + // Check for a collapsing vertical margin with our last child, which will be vertically adjacent to the new box. + if (const Box* open_box = block_box->GetIfBox()) + { + const float open_bottom_margin = open_box->GetEdge(BoxArea::Margin, BoxEdge::Bottom); + const float margin_sum = open_bottom_margin + child_top_margin; + + // The collapsed margin size depends on the sign of each margin, according to CSS behavior. The margins have + // already been added to the 'box_position', so subtract their sum as needed. + const int num_negative_margins = int(child_top_margin < 0.f) + int(open_bottom_margin < 0.f); + switch (num_negative_margins) + { + case 0: + // Use the largest margin. + box_position.y += Math::Max(child_top_margin, open_bottom_margin) - margin_sum; + break; + case 1: + // Use the sum of the positive and negative margin. These are already added to the position, so do nothing. + break; + case 2: + // Use the most negative margin. + box_position.y += Math::Min(child_top_margin, open_bottom_margin) - margin_sum; + break; + } + } + } + + return box_position; +} + +void BlockContainer::PlaceQueuedFloats(float vertical_position) +{ + if (!queued_float_elements.empty()) + { + for (QueuedFloat entry : queued_float_elements) + PlaceFloat(entry.element, vertical_position, entry.visible_overflow_size); + + queued_float_elements.clear(); + } +} + +float BlockContainer::GetShrinkToFitWidth() const +{ + auto& computed = element->GetComputedValues(); + + float content_width = 0.0f; + if (computed.width().type == Style::Width::Length) + { + // We have a definite width, so use that size. + content_width = box.GetSize().x; + } + else + { + // Nope, then use the largest outer shrink-to-fit width of our children. Percentage sizing would be relative to + // our containing block width and is treated just like 'auto' in this context. + for (const auto& block_box : child_boxes) + { + const float child_inner_width = block_box->GetShrinkToFitWidth(); + float child_edges_width = 0.f; + if (const Box* child_box = block_box->GetIfBox()) + child_edges_width = child_box->GetSizeAcross(BoxDirection::Horizontal, BoxArea::Margin, BoxArea::Padding); + + content_width = Math::Max(content_width, child_edges_width + child_inner_width); + } + } + + if (root_space) + { + // Since we are the root of the block formatting context, add the width contributions of the floated boxes in + // our context. The basic algorithm used can produce overestimates, since floats may not be located next to the + // rest of the content. + const float edge_left = box.GetPosition().x; + const float edge_right = edge_left + box.GetSize().x; + content_width += space->GetShrinkToFitWidth(edge_left, edge_right); + } + + float min_width, max_width; + LayoutDetails::GetMinMaxWidth(min_width, max_width, computed, box, 0.f); + content_width = Math::Clamp(content_width, min_width, max_width); + + return content_width; +} + +const Box* BlockContainer::GetIfBox() const +{ + return &box; +} + +Element* BlockContainer::GetElement() const +{ + return element; +} + +const FloatedBoxSpace* BlockContainer::GetBlockBoxSpace() const +{ + return space; +} + +Vector2f BlockContainer::GetPosition() const +{ + return position; +} + +Box& BlockContainer::GetBox() +{ + return box; +} + +const Box& BlockContainer::GetBox() const +{ + return box; +} + +void BlockContainer::ResetContents() +{ + RMLUI_ZoneScopedC(0xDD3322); + + if (root_space) + root_space->Reset(); + + child_boxes.clear(); + queued_float_elements.clear(); + + box_cursor = 0; + interrupted_line_box.reset(); + + inner_content_size = {}; +} + +String BlockContainer::DebugDumpTree(int depth) const +{ + String value = String(depth * 2, ' ') + "BlockContainer" + " | " + LayoutDetails::GetDebugElementName(element) + '\n'; + + for (auto&& block_box : child_boxes) + value += block_box->DumpLayoutTree(depth + 1); + + return value; +} + +InlineContainer* BlockContainer::GetOpenInlineContainer() +{ + return const_cast(static_cast(*this).GetOpenInlineContainer()); +} + +const InlineContainer* BlockContainer::GetOpenInlineContainer() const +{ + if (!child_boxes.empty() && child_boxes.back()->GetType() == Type::InlineContainer) + return rmlui_static_cast(child_boxes.back().get()); + return nullptr; +} + +InlineContainer* BlockContainer::EnsureOpenInlineContainer() +{ + // First check to see if we already have an open inline container. + InlineContainer* inline_container = GetOpenInlineContainer(); + + // Otherwise, we open a new one. + if (!inline_container) + { + const float scrollbar_width = (IsScrollContainer() ? element->GetElementScroll()->GetScrollbarSize(ElementScroll::VERTICAL) : 0.f); + const float available_width = box.GetSize().x - scrollbar_width; + + auto inline_container_ptr = MakeUnique(this, available_width); + inline_container = inline_container_ptr.get(); + child_boxes.push_back(std::move(inline_container_ptr)); + + if (interrupted_line_box) + { + inline_container->AddChainedBox(std::move(interrupted_line_box)); + interrupted_line_box.reset(); + } + } + + return inline_container; +} + +const LayoutBox* BlockContainer::GetOpenLayoutBox() const +{ + if (!child_boxes.empty()) + return child_boxes.back().get(); + return nullptr; +} + +bool BlockContainer::CloseOpenInlineContainer() +{ + if (InlineContainer* inline_container = GetOpenInlineContainer()) + { + EnsureEmptyInterruptedLineBox(); + + Vector2f child_position; + float child_height = 0.f; + inline_container->Close(&interrupted_line_box, child_position, child_height); + + // Increment our cursor. If this close fails, it means this block container generated an automatic scrollbar. + if (!EncloseChildBox(inline_container, child_position, child_height, 0.f)) + return false; + } + + return true; +} + +void BlockContainer::EnsureEmptyInterruptedLineBox() +{ + if (interrupted_line_box) + { + RMLUI_ERROR; // Internal error: Interrupted line box leaked. + interrupted_line_box.reset(); + } +} + +void BlockContainer::PlaceFloat(Element* element, float vertical_position, Vector2f visible_overflow_size) +{ + const Box& element_box = element->GetBox(); + + const Vector2f border_size = element_box.GetSize(BoxArea::Border); + visible_overflow_size = Math::Max(border_size, visible_overflow_size); + + const Vector2f margin_top_left = {element_box.GetEdge(BoxArea::Margin, BoxEdge::Left), element_box.GetEdge(BoxArea::Margin, BoxEdge::Top)}; + const Vector2f margin_bottom_right = {element_box.GetEdge(BoxArea::Margin, BoxEdge::Right), + element_box.GetEdge(BoxArea::Margin, BoxEdge::Bottom)}; + const Vector2f margin_size = border_size + margin_top_left + margin_bottom_right; + + Style::Float float_property = element->GetComputedValues().float_(); + Style::Clear clear_property = element->GetComputedValues().clear(); + + float unused_box_width = 0.f; + const Vector2f margin_position = space->NextFloatPosition(this, unused_box_width, vertical_position, margin_size, float_property, clear_property); + const Vector2f border_position = margin_position + margin_top_left; + + space->PlaceFloat(float_property, margin_position, margin_size, border_position, visible_overflow_size); + + // Shift the offset into this container's space, which acts as the float element's containing block. + element->SetOffset(border_position - position, GetElement()); +} + +bool BlockContainer::GetBaselineOfLastLine(float& out_baseline) const +{ + // Return the baseline of our last child that itself has a baseline. + for (int i = (int)child_boxes.size() - 1; i >= 0; i--) + { + if (child_boxes[i]->GetBaselineOfLastLine(out_baseline)) + return true; + } + + return false; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/BlockContainer.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/BlockContainer.h new file mode 100644 index 00000000..2c44bf30 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/BlockContainer.h @@ -0,0 +1,165 @@ +#pragma once + +#include "../../../Include/RmlUi/Core/Types.h" +#include "ContainerBox.h" + +namespace Rml { + +class FloatedBoxSpace; +class LineBox; +class InlineBox; +class InlineContainer; +struct InlineBoxHandle { + InlineBox* inline_box; +}; + +/** + A container for block-level boxes. + + Takes part in a block formatting context. Block-level boxes are stacked vertically as they are added. Inline-level + elements create a new inline container if one is not already open. The inline container itself acts as an anonymous + block-level box in this block container. + + Positions are relative to the root of the block formatting context this box takes part in, unless otherwise noted. + However, elements themselves are offset relative to their containing block. We act as the containing block for + static and relative children, and possibly for absolutely positioned descendants. + + */ +class BlockContainer final : public ContainerBox { +public: + /// Creates a new block box for rendering a block element. + /// @param[in] parent The parent of this block box. This will be nullptr for the root element. + /// @param[in] element The element this block box is laying out. + /// @param[in] box The box used for this block box. + /// @param[in] min_height The minimum height of the content box. + /// @param[in] max_height The maximum height of the content box. + BlockContainer(ContainerBox* parent_container, FloatedBoxSpace* space, Element* element, const Box& box, float min_height, float max_height); + ~BlockContainer(); + + /// Closes the box. This will determine this container's height if it was unspecified. + /// @param[in] parent_block_container Our parent which will be sized to contain this box, or nullptr for the root of the block formatting context. + /// @return False if the block box caused an automatic vertical scrollbar to appear, forcing a reformat of the current block formatting context. + bool Close(BlockContainer* parent_block_container); + + /// Creates and opens a new block box, and adds it as a child of this one. + /// @param[in] element The new block element. + /// @param[in] box The dimensions of the new box, whose content height may be indefinite (-1). + /// @param[in] min_height The minimum height of the content box. + /// @param[in] max_height The maximum height of the content box. + /// @return The block box representing the element. Once the element's children have been positioned, Close() must be called on it. + BlockContainer* OpenBlockBox(Element* element, const Box& box, float min_height, float max_height); + + /// Adds a block-level box whose contents have already been formatted in an independent formatting context. + /// @param[in] block_level_box The box to add as a new child of this. + /// @param[in] element The element represented by the new box. + /// @param[in] box The dimensions of the new box. + /// @return A pointer to the added block-level box. + LayoutBox* AddBlockLevelBox(UniquePtr block_level_box, Element* element, const Box& box); + + // Adds an element to this block container to be handled as a floating element. + void AddFloatElement(Element* element, Vector2f visible_overflow_size); + + /// Adds a new inline-level element to this block container. + /// @param[in] element The new inline element. + /// @param[in] box The box defining the element's bounds. + /// @return A handle for the inline element, which must later be submitted to 'CloseInlineElement()'. + /// @note Adds a new inline container to this box if needed, which starts a new inline formatting context. + InlineBoxHandle AddInlineElement(Element* element, const Box& box); + /// Closes a previously added inline element. This must be called after all its children have been added. + /// @param[in] handle A handle previously returned from 'AddInlineElement()'. + void CloseInlineElement(InlineBoxHandle handle); + + // Adds a line-break to this block box. + void AddBreak(); + + // Estimates the static position of a hypothetical next element to be placed. + Vector2f GetOpenStaticPosition(Style::Display display) const; + + /// Returns the position of a new child box to be placed here, relative to the block formatting context space. + Vector2f NextBoxPosition() const; + /// Returns the position of a new child box to be placed here. Collapses adjacent margins and optionally clears floats. + /// @param[in] child_box The dimensions of the new box. + /// @param[in] clear_property Specifies any floated boxes to be cleared (vertically skipped). + /// @return The next position in the block formatting context space. + Vector2f NextBoxPosition(const Box& child_box, Style::Clear clear_property) const; + + // Places all queued floating elements. + void PlaceQueuedFloats(float vertical_position); + + // Resets this box, so that it can be formatted again. + void ResetContents(); + + Vector2f GetPosition() const; + Element* GetElement() const; + const FloatedBoxSpace* GetBlockBoxSpace() const; + + Box& GetBox(); + const Box& GetBox() const; + + // -- Inherited from LayoutBox -- + + const Box* GetIfBox() const override; + float GetShrinkToFitWidth() const override; + bool GetBaselineOfLastLine(float& out_baseline) const override; + +private: + InlineContainer* EnsureOpenInlineContainer(); + InlineContainer* GetOpenInlineContainer(); + const InlineContainer* GetOpenInlineContainer() const; + + const LayoutBox* GetOpenLayoutBox() const; + + /// Increment our cursor and content size, to enclose a sized block-level child box. + /// @param[in] child The child box. + /// @param[in] child_position The border position of the child, relative to the current block formatting context. + /// @param[in] child_size The border size of the child. + /// @param[in] child_margin_bottom The bottom margin width of the child. + /// @return False if this caused an automatic vertical scrollbar to appear, forcing a reformat of the current block formatting context. + bool EncloseChildBox(LayoutBox* child, Vector2f child_position, float child_height, float child_margin_bottom); + + // Closes the inline container if there is one open. Returns false if our formatting context needs to be reformatted. + bool CloseOpenInlineContainer(); + + // Ensures that the interrupted line box is empty, otherwise it indicates an internal error. + void EnsureEmptyInterruptedLineBox(); + + // Positions a floating element within this block box. + void PlaceFloat(Element* element, float vertical_position, Vector2f visible_overflow_size); + + // Debug dump layout tree. + String DebugDumpTree(int depth) const override; + + using LayoutBoxList = Vector>; + struct QueuedFloat { + Element* element; + Vector2f visible_overflow_size; + }; + using QueuedFloatList = Vector; + + // Position of this box, relative to the border area of the root of our block formatting context. + Vector2f position; + + Box box; + float min_height = 0.f; + float max_height = -1.f; + + // The vertical position of the next block box to be added to this box, relative to our box's top content edge. + float box_cursor = 0.f; + + // Stores the floated boxes in the current block formatting context, if we are the root of the formatting context. + UniquePtr root_space; + // Pointer to the floated box space of the current block formatting context. [not-null] + FloatedBoxSpace* space; + // List of block-level boxes contained in this box. + LayoutBoxList child_boxes; + // Stores floated elements that are waiting for a line break to be positioned. + QueuedFloatList queued_float_elements; + // Stores the unplaced part of a line box that was split by a block-level box. + UniquePtr interrupted_line_box; + + // The inner content size (excluding any padding/border/margins). + // This is extended as child block boxes are closed, or from external formatting contexts. + Vector2f inner_content_size; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/BlockFormattingContext.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/BlockFormattingContext.cpp new file mode 100644 index 00000000..526da4bb --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/BlockFormattingContext.cpp @@ -0,0 +1,263 @@ +#include "BlockFormattingContext.h" +#include "../../../Include/RmlUi/Core/ComputedValues.h" +#include "../../../Include/RmlUi/Core/Element.h" +#include "../../../Include/RmlUi/Core/Profiling.h" +#include "../../../Include/RmlUi/Core/PropertyDefinition.h" +#include "../../../Include/RmlUi/Core/StyleSheetSpecification.h" +#include "../../../Include/RmlUi/Core/SystemInterface.h" +#include "BlockContainer.h" +#include "FloatedBoxSpace.h" +#include "LayoutDetails.h" + +namespace Rml { + +// Table elements should be handled within FormatElementTable, log a warning when it seems like we're encountering table parts in the wild. +static void LogUnexpectedFlowElement(Element* element, Style::Display display) +{ + RMLUI_ASSERT(element); + String value = "*unknown"; + StyleSheetSpecification::GetPropertySpecification().GetProperty(PropertyId::Display)->GetValue(value, Property(display)); + + Log::Message(Log::LT_WARNING, "Element has a display type '%s' which cannot be located in normal flow layout. Element will not be formatted: %s", + value.c_str(), element->GetAddress().c_str()); +} + +#ifdef RMLUI_DEBUG +static bool g_debug_dumping_layout_tree = false; +struct DebugDumpLayoutTree { + Element* element; + BlockContainer* block_box; + bool is_printing_tree_root = false; + + DebugDumpLayoutTree(Element* element, BlockContainer* block_box) : element(element), block_box(block_box) + { + // When an element with this ID is encountered, dump the formatted layout tree (including for all descendant formatting contexts). + static const String debug_trigger_id = "rmlui-debug-layout"; + is_printing_tree_root = element->HasAttribute(debug_trigger_id); + if (is_printing_tree_root) + g_debug_dumping_layout_tree = true; + } + ~DebugDumpLayoutTree() + { + if (g_debug_dumping_layout_tree) + { + const String header = ":: " + LayoutDetails::GetDebugElementName(element) + " ::\n"; + const String layout_tree = header + block_box->DumpLayoutTree(); + if (SystemInterface* system_interface = GetSystemInterface()) + system_interface->LogMessage(Log::LT_INFO, layout_tree); + + if (is_printing_tree_root) + g_debug_dumping_layout_tree = false; + } + } +}; +#else +struct DebugDumpLayoutTree { + DebugDumpLayoutTree(Element* /*element*/, BlockContainer* /*block_box*/) {} +}; +#endif + +enum class OuterDisplayType { BlockLevel, InlineLevel, Invalid }; + +static OuterDisplayType GetOuterDisplayType(Style::Display display) +{ + switch (display) + { + case Style::Display::Block: + case Style::Display::FlowRoot: + case Style::Display::Flex: + case Style::Display::Table: return OuterDisplayType::BlockLevel; + + case Style::Display::Inline: + case Style::Display::InlineBlock: + case Style::Display::InlineFlex: + case Style::Display::InlineTable: return OuterDisplayType::InlineLevel; + + case Style::Display::TableRow: + case Style::Display::TableRowGroup: + case Style::Display::TableColumn: + case Style::Display::TableColumnGroup: + case Style::Display::TableCell: + case Style::Display::None: break; + } + + return OuterDisplayType::Invalid; +} + +UniquePtr BlockFormattingContext::Format(ContainerBox* parent_container, Element* element, const Box* override_initial_box) +{ + RMLUI_ASSERT(parent_container && element); + +#ifdef RMLUI_TRACY_PROFILING + RMLUI_ZoneScopedC(0xB22222); + auto name = CreateString("%s %x", element->GetAddress(false, false).c_str(), element); + RMLUI_ZoneName(name.c_str(), name.size()); +#endif + + const Vector2f containing_block = LayoutDetails::GetContainingBlock(parent_container, element->GetPosition()).size; + + Box box; + if (override_initial_box) + box = *override_initial_box; + else + LayoutDetails::BuildBox(box, containing_block, element); + + float min_height, max_height; + LayoutDetails::GetDefiniteMinMaxHeight(min_height, max_height, element->GetComputedValues(), box, containing_block.y); + + UniquePtr container = MakeUnique(parent_container, nullptr, element, box, min_height, max_height); + + DebugDumpLayoutTree debug_dump_tree(element, container.get()); + + container->ResetScrollbars(box); + + // Format the element's children. In rare cases, it is possible that we need three iterations: Once to enable the + // horizontal scrollbar, then to enable the vertical scrollbar, and finally to format with both scrollbars enabled. + for (int layout_iteration = 0; layout_iteration < 3; layout_iteration++) + { + bool all_children_formatted = true; + for (int i = 0; i < element->GetNumChildren() && all_children_formatted; i++) + { + if (!FormatBlockContainerChild(container.get(), element->GetChild(i))) + all_children_formatted = false; + } + + if (all_children_formatted && container->Close(nullptr)) + // Success, break out of the loop. + break; + + // Otherwise, restart formatting now that one or both scrollbars have been enabled. + container->ResetContents(); + } + + return container; +} + +bool BlockFormattingContext::FormatBlockBox(BlockContainer* parent_container, Element* element) +{ + RMLUI_ZoneScopedC(0x2F4F4F); + const Vector2f containing_block = LayoutDetails::GetContainingBlock(parent_container, element->GetPosition()).size; + + Box box; + LayoutDetails::BuildBox(box, containing_block, element); + float min_height, max_height; + LayoutDetails::GetDefiniteMinMaxHeight(min_height, max_height, element->GetComputedValues(), box, containing_block.y); + + BlockContainer* container = parent_container->OpenBlockBox(element, box, min_height, max_height); + if (!container) + return false; + + // Format our children. This may result in scrollbars being added to our formatting context root, then we need to + // bail out and restart formatting for the current block formatting context. + for (int i = 0; i < element->GetNumChildren(); i++) + { + if (!FormatBlockContainerChild(container, element->GetChild(i))) + return false; + } + + if (!container->Close(parent_container)) + return false; + + return true; +} + +bool BlockFormattingContext::FormatInlineBox(BlockContainer* parent_container, Element* element) +{ + RMLUI_ZoneScopedC(0x3F6F6F); + const Vector2f containing_block = LayoutDetails::GetContainingBlock(parent_container, element->GetPosition()).size; + + Box box; + LayoutDetails::BuildBox(box, containing_block, element, BuildBoxMode::Inline); + auto inline_box_handle = parent_container->AddInlineElement(element, box); + + // Format the element's children. + for (int i = 0; i < element->GetNumChildren(); i++) + { + if (!FormatBlockContainerChild(parent_container, element->GetChild(i))) + return false; + } + + parent_container->CloseInlineElement(inline_box_handle); + + return true; +} + +bool BlockFormattingContext::FormatBlockContainerChild(BlockContainer* parent_container, Element* element) +{ +#ifdef RMLUI_TRACY_PROFILING + RMLUI_ZoneScoped; + auto name = CreateString(">%s %x", element->GetAddress(false, false).c_str(), element); + RMLUI_ZoneName(name.c_str(), name.size()); +#endif + + // Check for special formatting tags. + if (element->GetTagName() == "br") + { + parent_container->AddBreak(); + return true; + } + + auto& computed = element->GetComputedValues(); + const Style::Display display = computed.display(); + + // Don't lay this element out if it is set to a display type of none. + if (display == Style::Display::None) + return true; + + // Check for absolutely positioned elements: they are removed from the flow and added to the box representing their + // containing block, to be layed out and positioned once that box has been closed and sized. + const Style::Position position_property = computed.position(); + if (position_property == Style::Position::Absolute || position_property == Style::Position::Fixed) + { + const Vector2f static_position = parent_container->GetOpenStaticPosition(display) - parent_container->GetPosition(); + ContainingBlock containing_block = LayoutDetails::GetContainingBlock(parent_container, position_property); + containing_block.container->AddAbsoluteElement(element, static_position, parent_container->GetElement()); + return true; + } + + const OuterDisplayType outer_display = GetOuterDisplayType(display); + if (outer_display == OuterDisplayType::Invalid) + { + LogUnexpectedFlowElement(element, display); + return true; + } + + // If the element creates an independent formatting context, then format it accordingly. + if (UniquePtr layout_box = FormattingContext::FormatIndependent(parent_container, element, nullptr, FormattingContextType::None)) + { + // If the element is floating, we remove it from the flow. + if (computed.float_() != Style::Float::None) + { + parent_container->AddFloatElement(element, layout_box->GetVisibleOverflowSize()); + } + // Otherwise, check if we have a sized block-level box. + else if (layout_box && outer_display == OuterDisplayType::BlockLevel) + { + if (!parent_container->AddBlockLevelBox(std::move(layout_box), element, element->GetBox())) + return false; + } + // Nope, then this must be an inline-level box. + else + { + RMLUI_ASSERT(outer_display == OuterDisplayType::InlineLevel); + auto inline_box_handle = parent_container->AddInlineElement(element, element->GetBox()); + parent_container->CloseInlineElement(inline_box_handle); + } + + return true; + } + + // The element is an in-flow box participating in this same block formatting context. + switch (display) + { + case Style::Display::Block: return FormatBlockBox(parent_container, element); + case Style::Display::Inline: return FormatInlineBox(parent_container, element); + default: + RMLUI_ERROR; // Should have been handled above. + break; + } + + return true; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/BlockFormattingContext.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/BlockFormattingContext.h new file mode 100644 index 00000000..d1b4fb73 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/BlockFormattingContext.h @@ -0,0 +1,41 @@ +#pragma once + +#include "../../../Include/RmlUi/Core/Types.h" +#include "FormattingContext.h" + +namespace Rml { + +class Box; +class BlockContainer; +class ContainerBox; +class LayoutBox; + +/* + Places boxes according to normal flow, while handling floated boxes. + + A block formatting context (BFC) starts with a root BlockContainer, where child block-level boxes are placed + vertically. Descendant elements take part in the same BFC, unless the element has properties causing it to establish + an independent formatting context. + + Floated boxes are taken out-of-flow and placed inside the current BFC. Floats are only affected by, and affect only, + other boxes within the same BFC. +*/ +class BlockFormattingContext final : public FormattingContext { +public: + static UniquePtr Format(ContainerBox* parent_container, Element* element, const Box* override_initial_box); + +private: + // Format the element as a block box, including its children. + // @return False if the box caused an automatic vertical scrollbar to appear in the block formatting context root, forcing it to be reformatted. + static bool FormatBlockBox(BlockContainer* parent_container, Element* element); + + // Format the element as an inline box, including its children. + // @return False if the box caused an automatic vertical scrollbar to appear in the block formatting context root, forcing it to be reformatted. + static bool FormatInlineBox(BlockContainer* parent_container, Element* element); + + // Determine how to format a child element of a block container, and format it accordingly, possibly including any children. + // @return False if the box caused an automatic vertical scrollbar to appear in the block formatting context root, forcing it to be reformatted. + static bool FormatBlockContainerChild(BlockContainer* parent_container, Element* element); +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/CMakeLists.txt b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/CMakeLists.txt new file mode 100644 index 00000000..42bc9581 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/CMakeLists.txt @@ -0,0 +1,40 @@ +# RMLUI_CMAKE_MINIMUM_VERSION_RAISE_NOTICE: +# Using absolute paths to prevent improper interpretation of relative paths Relative paths can be used once the minimum +# CMake version is greater or equal than CMake 3.13 +target_sources(rmlui_core PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/BlockContainer.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/BlockContainer.h" + "${CMAKE_CURRENT_SOURCE_DIR}/BlockFormattingContext.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/BlockFormattingContext.h" + "${CMAKE_CURRENT_SOURCE_DIR}/ContainerBox.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/ContainerBox.h" + "${CMAKE_CURRENT_SOURCE_DIR}/FlexFormattingContext.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/FlexFormattingContext.h" + "${CMAKE_CURRENT_SOURCE_DIR}/FloatedBoxSpace.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/FloatedBoxSpace.h" + "${CMAKE_CURRENT_SOURCE_DIR}/FormattingContext.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/FormattingContext.h" + "${CMAKE_CURRENT_SOURCE_DIR}/InlineBox.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/InlineBox.h" + "${CMAKE_CURRENT_SOURCE_DIR}/InlineContainer.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/InlineContainer.h" + "${CMAKE_CURRENT_SOURCE_DIR}/InlineLevelBox.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/InlineLevelBox.h" + "${CMAKE_CURRENT_SOURCE_DIR}/InlineTypes.h" + "${CMAKE_CURRENT_SOURCE_DIR}/LayoutBox.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/LayoutBox.h" + "${CMAKE_CURRENT_SOURCE_DIR}/LayoutDetails.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/LayoutDetails.h" + "${CMAKE_CURRENT_SOURCE_DIR}/LayoutEngine.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/LayoutEngine.h" + "${CMAKE_CURRENT_SOURCE_DIR}/LayoutPools.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/LayoutPools.h" + "${CMAKE_CURRENT_SOURCE_DIR}/LineBox.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/LineBox.h" + "${CMAKE_CURRENT_SOURCE_DIR}/ReplacedFormattingContext.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/ReplacedFormattingContext.h" + "${CMAKE_CURRENT_SOURCE_DIR}/TableFormattingContext.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/TableFormattingContext.h" + "${CMAKE_CURRENT_SOURCE_DIR}/TableFormattingDetails.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/TableFormattingDetails.h" +) diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/ContainerBox.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/ContainerBox.cpp new file mode 100644 index 00000000..8d458d95 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/ContainerBox.cpp @@ -0,0 +1,298 @@ +#include "ContainerBox.h" +#include "../../../Include/RmlUi/Core/ComputedValues.h" +#include "../../../Include/RmlUi/Core/Element.h" +#include "../../../Include/RmlUi/Core/ElementScroll.h" +#include "../../../Include/RmlUi/Core/Profiling.h" +#include "FlexFormattingContext.h" +#include "FormattingContext.h" +#include "LayoutDetails.h" +#include +#include + +namespace Rml { + +void ContainerBox::ResetScrollbars(const Box& box) +{ + RMLUI_ASSERT(element); + if (overflow_x == Style::Overflow::Scroll) + element->GetElementScroll()->EnableScrollbar(ElementScroll::HORIZONTAL, box.GetSizeAcross(BoxDirection::Horizontal, BoxArea::Padding)); + else + element->GetElementScroll()->DisableScrollbar(ElementScroll::HORIZONTAL); + + if (overflow_y == Style::Overflow::Scroll) + element->GetElementScroll()->EnableScrollbar(ElementScroll::VERTICAL, box.GetSizeAcross(BoxDirection::Horizontal, BoxArea::Padding)); + else + element->GetElementScroll()->DisableScrollbar(ElementScroll::VERTICAL); +} + +void ContainerBox::AddAbsoluteElement(Element* element, Vector2f static_position, Element* static_relative_offset_parent) +{ + // We may possibly be adding the same element from a previous layout iteration. If so, this ensures it is updated with the latest static position. + absolute_elements[element] = AbsoluteElement{static_position, static_relative_offset_parent}; +} + +void ContainerBox::AddRelativeElement(Element* element) +{ + // The same relative element may be added multiple times during repeated layout iterations, avoid any duplicates. + if (std::find(relative_elements.begin(), relative_elements.end(), element) == relative_elements.end()) + relative_elements.push_back(element); +} + +bool ContainerBox::IsScrollContainer() const +{ + return LayoutDetails::IsScrollContainer(overflow_x, overflow_y); +} + +void ContainerBox::ClosePositionedElements() +{ + // Any relatively positioned elements that we act as containing block for may need to be have their positions + // updated to reflect changes to the size of this block box. Update relative offsets before handling absolute + // elements, as this may affect the resolved static position of the absolute elements. + for (Element* child : relative_elements) + child->UpdateOffset(); + + relative_elements.clear(); + + while (!absolute_elements.empty()) + { + // New absolute elements may be added to this box during formatting below. To avoid invalidated iterators and + // references, move the list to a local copy to iterate over, and repeat if new elements are added. + AbsoluteElementMap absolute_elements_iterate = std::move(absolute_elements); + absolute_elements.clear(); + + for (const auto& absolute_element_pair : absolute_elements_iterate) + { + Element* absolute_element = absolute_element_pair.first; + const Vector2f static_position = absolute_element_pair.second.static_position; + Element* static_position_offset_parent = absolute_element_pair.second.static_position_offset_parent; + + // Find the static position relative to this containing block. First, calculate the offset from ourself to + // the static position's offset parent. Assumes (1) that this container box is part of the containing block + // chain of the static position offset parent, and (2) that all offsets in this chain has been set already. + Vector2f relative_position; + for (Element* ancestor = static_position_offset_parent; ancestor && ancestor != element; ancestor = ancestor->GetOffsetParent()) + relative_position += ancestor->GetRelativeOffset(BoxArea::Border); + + // Now simply add the result to the stored static position to get the static position in our local space. + Vector2f offset = relative_position + static_position; + + // Lay out the element. + FormattingContext::FormatIndependent(this, absolute_element, nullptr, FormattingContextType::Block); + + // Now that the element's box has been built, we can offset the position we determined was appropriate for + // it by the element's margin. This is necessary because the coordinate system for the box begins at the + // border, not the margin. + offset.x += absolute_element->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Left); + offset.y += absolute_element->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Top); + + // Set the offset of the element; the element itself will take care of any RCSS-defined positional offsets. + absolute_element->SetOffset(offset, element); + } + } +} + +void ContainerBox::SetElementBaseline(float element_baseline) +{ + element->SetBaseline(element_baseline); +} + +void ContainerBox::SubmitElementLayout() +{ + element->OnLayout(); +} + +ContainerBox::ContainerBox(Type type, Element* element, ContainerBox* parent_container) : + LayoutBox(type), element(element), parent_container(parent_container) +{ + if (element) + { + const auto& computed = element->GetComputedValues(); + overflow_x = computed.overflow_x(); + overflow_y = computed.overflow_y(); + is_absolute_positioning_containing_block = (computed.position() != Style::Position::Static || computed.has_local_transform() || + computed.has_local_perspective() || computed.has_filter() || computed.has_backdrop_filter() || computed.has_mask_image()); + } +} + +bool ContainerBox::CatchOverflow(const Vector2f content_overflow_size, const Box& box, const float max_height) const +{ + if (!IsScrollContainer()) + return true; + + const Vector2f padding_bottom_right = {box.GetEdge(BoxArea::Padding, BoxEdge::Right), box.GetEdge(BoxArea::Padding, BoxEdge::Bottom)}; + const float padding_width = box.GetSizeAcross(BoxDirection::Horizontal, BoxArea::Padding); + + Vector2f available_space = box.GetSize(); + if (available_space.y < 0.f) + available_space.y = max_height; + if (available_space.y < 0.f) + available_space.y = HUGE_VALF; + + RMLUI_ASSERT(available_space.x >= 0.f && available_space.y >= 0.f); + + // Allow overflow onto the padding area. + available_space += padding_bottom_right; + + ElementScroll* element_scroll = element->GetElementScroll(); + bool scrollbar_size_changed = false; + + // @performance If we have auto-height sizing and the horizontal scrollbar is enabled, then we can in principle + // simply add the scrollbar size to the height instead of formatting the element all over again. + if (overflow_x == Style::Overflow::Auto && content_overflow_size.x > available_space.x + 0.5f) + { + if (element_scroll->GetScrollbarSize(ElementScroll::HORIZONTAL) == 0.f) + { + element_scroll->EnableScrollbar(ElementScroll::HORIZONTAL, padding_width); + const float new_size = element_scroll->GetScrollbarSize(ElementScroll::HORIZONTAL); + scrollbar_size_changed = (new_size != 0.f); + available_space.y -= new_size; + } + } + + // If we're auto-scrolling and our height is fixed, we have to check if this box has exceeded our client height. + if (overflow_y == Style::Overflow::Auto && content_overflow_size.y > available_space.y + 0.5f) + { + if (element_scroll->GetScrollbarSize(ElementScroll::VERTICAL) == 0.f) + { + element_scroll->EnableScrollbar(ElementScroll::VERTICAL, padding_width); + const float new_size = element_scroll->GetScrollbarSize(ElementScroll::VERTICAL); + scrollbar_size_changed |= (new_size != 0.f); + } + } + + return !scrollbar_size_changed; +} + +bool ContainerBox::SubmitBox(const Vector2f content_overflow_size, const Box& box, const float max_height) +{ + Vector2f visible_overflow_size; + + // Set the computed box on the element. + if (element) + { + // Calculate the dimensions of the box's scrollable overflow rectangle. This is the union of the tightest- + // fitting box around all of the internal elements, and this element's padding box. We really only care about + // overflow on the bottom-right sides, as these are the only ones allowed to be scrolled to in CSS. + // + // If we are a scroll container (use any other value than 'overflow: visible'), then any overflow outside our + // padding box should be caught here. Otherwise, our overflow should be included in the overflow calculations of + // our nearest scroll container ancestor. + + // If our content is larger than our padding box, we can add scrollbars if we're set to auto-scrollbars. If + // we're set to always use scrollbars, then the scrollbars have already been enabled. + if (!CatchOverflow(content_overflow_size, box, max_height)) + return false; + + const Vector2f padding_top_left = {box.GetEdge(BoxArea::Padding, BoxEdge::Left), box.GetEdge(BoxArea::Padding, BoxEdge::Top)}; + const Vector2f padding_bottom_right = {box.GetEdge(BoxArea::Padding, BoxEdge::Right), box.GetEdge(BoxArea::Padding, BoxEdge::Bottom)}; + const Vector2f padding_size = box.GetSize() + padding_top_left + padding_bottom_right; + + const bool is_scroll_container = IsScrollContainer(); + const Vector2f scrollbar_size = { + is_scroll_container ? element->GetElementScroll()->GetScrollbarSize(ElementScroll::VERTICAL) : 0.f, + is_scroll_container ? element->GetElementScroll()->GetScrollbarSize(ElementScroll::HORIZONTAL) : 0.f, + }; + + element->SetBox(box); + + // Scrollable overflow is the set of things extending our padding area, for which scrolling could be provided. + const Vector2f scrollable_overflow_size = Math::Max(padding_size - scrollbar_size, padding_top_left + content_overflow_size); + // Set the overflow size but defer clamping of the scroll offset, see `LayoutEngine::FormatElement`. + element->SetScrollableOverflowRectangle(scrollable_overflow_size, false); + + const Vector2f border_size = padding_size + box.GetFrameSize(BoxArea::Border); + + // Set the visible overflow size so that ancestors can catch any overflow produced by us. That is, hiding it or + // providing a scrolling mechanism. If this box is a scroll container we catch our own overflow here. Thus, in + // this case, only our border box is visible from our ancestor's perpective. + if (is_scroll_container) + { + visible_overflow_size = border_size; + + // Format any scrollbars in case they were enabled on this element. + element->GetElementScroll()->FormatScrollbars(); + } + else + { + const Vector2f border_top_left = {box.GetEdge(BoxArea::Border, BoxEdge::Left), box.GetEdge(BoxArea::Border, BoxEdge::Top)}; + visible_overflow_size = Math::Max(border_size, content_overflow_size + border_top_left + padding_top_left); + } + } + + SetVisibleOverflowSize(visible_overflow_size); + + return true; +} + +String RootBox::DebugDumpTree(int depth) const +{ + return String(depth * 2, ' ') + "RootBox"; +} + +FlexContainer::FlexContainer(Element* element, ContainerBox* parent_container) : ContainerBox(Type::FlexContainer, element, parent_container) +{ + RMLUI_ASSERT(element); +} + +bool FlexContainer::Close(const Vector2f content_overflow_size, const Box& box, float element_baseline) +{ + if (!SubmitBox(content_overflow_size, box, -1.f)) + return false; + + ClosePositionedElements(); + + SubmitElementLayout(); + SetElementBaseline(element_baseline); + return true; +} + +float FlexContainer::GetShrinkToFitWidth() const +{ + // For the trivial case of a fixed width, we simply return that. + if (element->GetComputedValues().width().type == Style::Width::Type::Length) + return box.GetSize().x; + + // Infer shrink-to-fit width from the intrinsic width of the element. + return FlexFormattingContext::GetMaxContentSize(element).x; +} + +String FlexContainer::DebugDumpTree(int depth) const +{ + return String(depth * 2, ' ') + "FlexContainer" + " | " + LayoutDetails::GetDebugElementName(element); +} + +TableWrapper::TableWrapper(Element* element, ContainerBox* parent_container) : ContainerBox(Type::TableWrapper, element, parent_container) +{ + RMLUI_ASSERT(element); +} + +void TableWrapper::Close(const Vector2f content_overflow_size, const Box& box, float element_baseline) +{ + bool result = SubmitBox(content_overflow_size, box, -1.f); + + // Since the table wrapper cannot generate scrollbars, this should always pass. + RMLUI_ASSERT(result); + (void)result; + + ClosePositionedElements(); + + SubmitElementLayout(); + SetElementBaseline(element_baseline); +} + +float TableWrapper::GetShrinkToFitWidth() const +{ + // We don't currently support shrink-to-fit layout of tables. However, for the trivial case of a fixed width, we + // simply return that. + if (element->GetComputedValues().width().type == Style::Width::Type::Length) + return box.GetSize().x; + + return 0.0f; +} + +String TableWrapper::DebugDumpTree(int depth) const +{ + return String(depth * 2, ' ') + "TableWrapper" + " | " + LayoutDetails::GetDebugElementName(element); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/ContainerBox.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/ContainerBox.h new file mode 100644 index 00000000..0f471a9b --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/ContainerBox.h @@ -0,0 +1,139 @@ +#pragma once + +#include "../../../Include/RmlUi/Core/Box.h" +#include "../../../Include/RmlUi/Core/StyleTypes.h" +#include "../../../Include/RmlUi/Core/Types.h" +#include "LayoutBox.h" + +namespace Rml { + +/** + Abstraction for layout boxes that can act as a containing block. + + Implements functionality for catching overflow, and for handling positioned boxes that the box acts as a containing block for. +*/ +class ContainerBox : public LayoutBox { +public: + // Enable or disable scrollbars for the element we represent, preparing it for the first round of layouting, according to our properties. + void ResetScrollbars(const Box& box); + + // Adds an absolutely positioned element, to be formatted and positioned when closing this container, see 'ClosePositionedElements'. + void AddAbsoluteElement(Element* element, Vector2f static_position, Element* static_relative_offset_parent); + // Adds a relatively positioned element which we act as a containing block for. + void AddRelativeElement(Element* element); + + ContainerBox* GetParent() { return parent_container; } + Element* GetElement() { return element; } + + bool IsScrollContainer() const; + + // Returns true if this box acts as a containing block for absolutely positioned descendants. + bool IsAbsolutePositioningContainingBlock() const { return is_absolute_positioning_containing_block; } + +protected: + ContainerBox(Type type, Element* element, ContainerBox* parent_container); + + /// Checks if we have a new overflow on an auto-scrolling element. If so, our vertical scrollbar will be enabled and + /// our block boxes will be destroyed. All content will need to be re-formatted. + /// @param[in] content_overflow_size The size of the visible content, relative to our content area. + /// @param[in] box The box built for the element, possibly with a non-determinate height. + /// @param[in] max_height Maximum height of the content area, if any. + /// @returns True if no overflow occured, false if it did. + bool CatchOverflow(const Vector2f content_overflow_size, const Box& box, const float max_height) const; + + /// Set the box and scrollable area on our element, possibly catching any overflow. + /// @param[in] content_overflow_size The size of the visible content, relative to our content area. + /// @param[in] box The box to be set on the element. + /// @param[in] max_height Maximum height of the content area, if any. + /// @returns True if no overflow occured, false if it did. + bool SubmitBox(const Vector2f content_overflow_size, const Box& box, const float max_height); + + /// Formats, sizes, and positions all absolute elements whose containing block is this, and offsets relative elements. + void ClosePositionedElements(); + + // Set the element's baseline (proxy for private access to Element). + void SetElementBaseline(float element_baseline); + // Calls Element::OnLayout (proxy for private access to Element). + void SubmitElementLayout(); + + // The element this box represents, if any. + Element* const element; + +private: + struct AbsoluteElement { + Vector2f static_position; // The hypothetical position of the element as if it was placed in normal flow. + Element* static_position_offset_parent; // The element for which the static position is offset from. + }; + using AbsoluteElementMap = SmallUnorderedMap; + + AbsoluteElementMap absolute_elements; // List of absolutely positioned elements that we act as a containing block for. + ElementList relative_elements; // List of relatively positioned elements that we act as a containing block for. + + Style::Overflow overflow_x = Style::Overflow::Visible; + Style::Overflow overflow_y = Style::Overflow::Visible; + bool is_absolute_positioning_containing_block = false; + + ContainerBox* parent_container = nullptr; +}; + +/** + The root of a tree of layout boxes, usually represents the root element or viewport. +*/ +class RootBox final : public ContainerBox { +public: + RootBox(Vector2f containing_block) : ContainerBox(Type::Root, nullptr, nullptr), box(containing_block) {} + RootBox(const Box& box) : ContainerBox(Type::Root, nullptr, nullptr), box(box) {} + + const Box* GetIfBox() const override { return &box; } + String DebugDumpTree(int depth) const override; + +private: + Box box; +}; + +/** + A box where flexbox layout occurs. +*/ +class FlexContainer final : public ContainerBox { +public: + FlexContainer(Element* element, ContainerBox* parent_container); + + // Submits the formatted box to the flex container element, and propagates any uncaught overflow to this box. + // @returns True if it succeeds, otherwise false if it needs to be formatted again because scrollbars were enabled. + bool Close(const Vector2f content_overflow_size, const Box& box, float element_baseline); + + float GetShrinkToFitWidth() const override; + + const Box* GetIfBox() const override { return &box; } + String DebugDumpTree(int depth) const override; + + Box& GetBox() { return box; } + +private: + Box box; +}; + +/** + A box where table formatting occurs. + + As opposed to a flex container, the table wrapper cannot contain overflow or produce scrollbars. +*/ +class TableWrapper final : public ContainerBox { +public: + TableWrapper(Element* element, ContainerBox* parent_container); + + // Submits the formatted box to the table element, and propagates any uncaught overflow to this box. + void Close(const Vector2f content_overflow_size, const Box& box, float element_baseline); + + float GetShrinkToFitWidth() const override; + + const Box* GetIfBox() const override { return &box; } + String DebugDumpTree(int depth) const override; + + Box& GetBox() { return box; } + +private: + Box box; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/FlexFormattingContext.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/FlexFormattingContext.cpp new file mode 100644 index 00000000..a4101f85 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/FlexFormattingContext.cpp @@ -0,0 +1,972 @@ +#include "FlexFormattingContext.h" +#include "../../../Include/RmlUi/Core/ComputedValues.h" +#include "../../../Include/RmlUi/Core/Element.h" +#include "../../../Include/RmlUi/Core/ElementScroll.h" +#include "../../../Include/RmlUi/Core/Profiling.h" +#include "../../../Include/RmlUi/Core/Types.h" +#include "ContainerBox.h" +#include "LayoutDetails.h" +#include +#include +#include + +namespace Rml { + +UniquePtr FlexFormattingContext::Format(ContainerBox* parent_container, Element* element, const Box* override_initial_box) +{ + RMLUI_ZoneScopedC(0xAFAF4F); + auto flex_container_box = MakeUnique(element, parent_container); + + ElementScroll* element_scroll = element->GetElementScroll(); + const ComputedValues& computed = element->GetComputedValues(); + + const Vector2f containing_block = LayoutDetails::GetContainingBlock(parent_container, element->GetPosition()).size; + RMLUI_ASSERT(containing_block.x >= 0.f); + + // Build the initial box as specified by the flex's style, as if it was a normal block element. + Box& box = flex_container_box->GetBox(); + if (override_initial_box) + box = *override_initial_box; + else + LayoutDetails::BuildBox(box, containing_block, element, BuildBoxMode::Block); + + // Start with any auto-scrollbars off. + flex_container_box->ResetScrollbars(box); + + FlexFormattingContext context; + context.flex_container_box = flex_container_box.get(); + context.element_flex = element; + + LayoutDetails::GetMinMaxWidth(context.flex_min_size.x, context.flex_max_size.x, computed, box, containing_block.x); + LayoutDetails::GetMinMaxHeight(context.flex_min_size.y, context.flex_max_size.y, computed, box, containing_block.y); + + const Vector2f box_content_size = box.GetSize(); + const bool auto_height = (box_content_size.y < 0.0f); + + context.flex_content_offset = box.GetPosition(); + + for (int layout_iteration = 0; layout_iteration < 3; layout_iteration++) + { + // One or both scrollbars can be enabled between iterations. + const Vector2f scrollbar_size = { + element_scroll->GetScrollbarSize(ElementScroll::VERTICAL), + element_scroll->GetScrollbarSize(ElementScroll::HORIZONTAL), + }; + + context.flex_available_content_size = Math::Max(box_content_size - scrollbar_size, Vector2f(0.f)); + context.flex_content_containing_block = context.flex_available_content_size; + + if (auto_height) + { + context.flex_available_content_size.y = -1.f; // Negative means infinite space + context.flex_content_containing_block.y = containing_block.y; + } + + // Format the flexbox and all its children. + Vector2f flex_resulting_content_size, content_overflow_size; + float flex_baseline = 0.f; + context.Format(flex_resulting_content_size, content_overflow_size, flex_baseline); + + // Output the size of the formatted flexbox. The width is determined as a normal block box so we don't need to change that. + Vector2f formatted_content_size = box_content_size; + if (auto_height) + formatted_content_size.y = flex_resulting_content_size.y + scrollbar_size.y; + + Box sized_box = box; + sized_box.SetContent(formatted_content_size); + + // Change the flex baseline coordinates to the element baseline, which is defined as the distance from the element's bottom margin edge. + const float element_baseline = + sized_box.GetSizeAcross(BoxDirection::Vertical, BoxArea::Border) + sized_box.GetEdge(BoxArea::Margin, BoxEdge::Bottom) - flex_baseline; + + // Close the box, and break out of the loop if it did not produce any new scrollbars, otherwise continue to format the flexbox again. + if (flex_container_box->Close(content_overflow_size, sized_box, element_baseline)) + break; + } + + return flex_container_box; +} + +Vector2f FlexFormattingContext::GetMaxContentSize(Element* element) +{ + // A large but finite number is used here, since layouting doesn't always work well with infinities. + const Vector2f infinity(10000.0f, 10000.0f); + RootBox root(infinity); + auto flex_container_box = MakeUnique(element, &root); + + FlexFormattingContext context; + context.flex_container_box = flex_container_box.get(); + context.element_flex = element; + context.flex_available_content_size = Vector2f(-1, -1); + context.flex_content_containing_block = infinity; + context.flex_max_size = Vector2f(FLT_MAX, FLT_MAX); + + // Format the flexbox and all its children. + Vector2f flex_resulting_content_size, content_overflow_size; + float flex_baseline = 0.f; + context.Format(flex_resulting_content_size, content_overflow_size, flex_baseline); + return flex_resulting_content_size; +} + +struct FlexItem { + // In the following, suffix '_a' means flex start edge while '_b' means flex end edge. + struct Size { + bool auto_margin_a, auto_margin_b; + bool auto_size; + float margin_a, margin_b; + float sum_edges_a; // Start edge: margin (non-auto) + border + padding + float sum_edges; // Inner->outer size + float min_size, max_size; // Inner size + }; + + Element* element; + Box box; + + // Filled during the build step. + Size main; + Size cross; + float flex_shrink_factor; + float flex_grow_factor; + Style::AlignSelf align_self; // 'Auto' is replaced by container's 'align-items' value + + float inner_flex_base_size; // Inner size + float flex_base_size; // Outer size + float hypothetical_main_size; // Outer size + + // Used for resolving flexible length + enum class Violation : uint8_t { None = 0, Min, Max }; + bool frozen; + Violation violation; + float target_main_size; // Outer size + float used_main_size; // Outer size (without auto margins) + float main_auto_margin_size_a, main_auto_margin_size_b; + float main_offset; + + // Used for resolving cross size + float hypothetical_cross_size; // Outer size + float used_cross_size; // Outer size + float cross_offset; // Offset within line + float cross_baseline_top; // Only used for baseline cross alignment +}; + +struct FlexLine { + FlexLine(Vector&& items) : items(std::move(items)) {} + Vector items; + float accumulated_hypothetical_main_size = 0; + float cross_size = 0; // Excludes line spacing + float cross_spacing_a = 0, cross_spacing_b = 0; + float cross_offset = 0; +}; + +struct FlexLineContainer { + Vector lines; +}; + +static void GetItemSizing(FlexItem::Size& destination, const ComputedAxisSize& computed_size, const float base_value, const bool direction_reverse) +{ + float margin_a, margin_b, padding_border_a, padding_border_b; + LayoutDetails::GetEdgeSizes(margin_a, margin_b, padding_border_a, padding_border_b, computed_size, base_value); + + const float padding_border = padding_border_a + padding_border_b; + const float margin = margin_a + margin_b; + + destination.auto_margin_a = (computed_size.margin_a.type == Style::Margin::Auto); + destination.auto_margin_b = (computed_size.margin_b.type == Style::Margin::Auto); + + destination.auto_size = (computed_size.size.type == Style::LengthPercentageAuto::Auto); + + destination.margin_a = margin_a; + destination.margin_b = margin_b; + destination.sum_edges = padding_border + margin; + destination.sum_edges_a = (direction_reverse ? padding_border_b + margin_b : padding_border_a + margin_a); + + destination.min_size = ResolveValue(computed_size.min_size, base_value); + destination.max_size = ResolveValue(computed_size.max_size, base_value); + + if (computed_size.box_sizing == Style::BoxSizing::BorderBox) + { + destination.min_size = Math::Max(0.0f, destination.min_size - padding_border); + if (destination.max_size < FLT_MAX) + destination.max_size = Math::Max(0.0f, destination.max_size - padding_border); + } + + if (direction_reverse) + { + std::swap(destination.auto_margin_a, destination.auto_margin_b); + std::swap(destination.margin_a, destination.margin_b); + } +} + +static float GetInnerUsedMainSize(const FlexItem& item) +{ + // Due to floating-point precision the outer size may be smaller than `sum_edges`, so clamp the result to zero. + return Math::Max(item.used_main_size - item.main.sum_edges, 0.f); +} + +static float GetInnerUsedCrossSize(const FlexItem& item) +{ + return Math::Max(item.used_cross_size - item.cross.sum_edges, 0.f); +} + +void FlexFormattingContext::Format(Vector2f& flex_resulting_content_size, Vector2f& flex_content_overflow_size, float& flex_baseline) const +{ + // The following procedure is based on the CSS flexible box layout algorithm. + // For details, see https://drafts.csswg.org/css-flexbox/#layout-algorithm + + const ComputedValues& computed_flex = element_flex->GetComputedValues(); + const Style::FlexDirection direction = computed_flex.flex_direction(); + const Style::LengthPercentage row_gap = computed_flex.row_gap(); + const Style::LengthPercentage column_gap = computed_flex.column_gap(); + + const bool main_axis_horizontal = (direction == Style::FlexDirection::Row || direction == Style::FlexDirection::RowReverse); + const bool direction_reverse = (direction == Style::FlexDirection::RowReverse || direction == Style::FlexDirection::ColumnReverse); + const bool flex_single_line = (computed_flex.flex_wrap() == Style::FlexWrap::Nowrap); + const bool wrap_reverse = (computed_flex.flex_wrap() == Style::FlexWrap::WrapReverse); + + const float main_available_size = (main_axis_horizontal ? flex_available_content_size.x : flex_available_content_size.y); + const float cross_available_size = (!main_axis_horizontal ? flex_available_content_size.x : flex_available_content_size.y); + + const float main_min_size = (main_axis_horizontal ? flex_min_size.x : flex_min_size.y); + const float main_max_size = (main_axis_horizontal ? flex_max_size.x : flex_max_size.y); + const float cross_min_size = (main_axis_horizontal ? flex_min_size.y : flex_min_size.x); + const float cross_max_size = (main_axis_horizontal ? flex_max_size.y : flex_max_size.x); + + // For the purpose of placing items we make infinite size a big value. + const float main_wrap_size = Math::Clamp(main_available_size < 0.0f ? FLT_MAX : main_available_size, main_min_size, main_max_size); + + // For the purpose of resolving lengths, infinite main size becomes zero. + const float main_size_base_value = (main_available_size < 0.0f ? 0.0f : main_available_size); + const float cross_size_base_value = (cross_available_size < 0.0f ? 0.0f : cross_available_size); + + const float main_gap_size = ResolveValue(main_axis_horizontal ? column_gap : row_gap, main_size_base_value); + const float cross_gap_size = ResolveValue(main_axis_horizontal ? row_gap : column_gap, cross_size_base_value); + + // -- Build a list of all flex items with base size information -- + const int num_flex_children = element_flex->GetNumChildren(); + Vector items; + items.reserve(num_flex_children); + + for (int i = 0; i < num_flex_children; i++) + { + Element* element = element_flex->GetChild(i); + const ComputedValues& computed = element->GetComputedValues(); + + if (computed.display() == Style::Display::None) + { + continue; + } + else if (computed.position() == Style::Position::Absolute || computed.position() == Style::Position::Fixed) + { + ContainerBox* absolute_containing_block = LayoutDetails::GetContainingBlock(flex_container_box, computed.position()).container; + absolute_containing_block->AddAbsoluteElement(element, {}, element_flex); + continue; + } + else if (computed.position() == Style::Position::Relative) + { + flex_container_box->AddRelativeElement(element); + } + + FlexItem item = {}; + item.element = element; + LayoutDetails::BuildBox(item.box, flex_content_containing_block, element, BuildBoxMode::UnalignedBlock); + + Style::LengthPercentageAuto item_main_size; + + { + const ComputedAxisSize computed_main_size = + main_axis_horizontal ? LayoutDetails::BuildComputedHorizontalSize(computed) : LayoutDetails::BuildComputedVerticalSize(computed); + const ComputedAxisSize computed_cross_size = + !main_axis_horizontal ? LayoutDetails::BuildComputedHorizontalSize(computed) : LayoutDetails::BuildComputedVerticalSize(computed); + + GetItemSizing(item.main, computed_main_size, main_size_base_value, direction_reverse); + GetItemSizing(item.cross, computed_cross_size, cross_size_base_value, wrap_reverse); + + item_main_size = computed_main_size.size; + } + + item.flex_shrink_factor = computed.flex_shrink(); + item.flex_grow_factor = computed.flex_grow(); + item.align_self = computed.align_self(); + + static_assert(int(Style::AlignSelf::FlexStart) == int(Style::AlignItems::FlexStart) + 1 && + int(Style::AlignSelf::Stretch) == int(Style::AlignItems::Stretch) + 1, + "It is assumed below that align items is a shifted version (no auto value) of align self."); + + // Use the container's align-items property if align-self is auto. + if (item.align_self == Style::AlignSelf::Auto) + item.align_self = static_cast(static_cast(computed_flex.align_items()) + 1); + + auto GetMainSize = [&](const Box& box) { return box.GetSize()[main_axis_horizontal ? 0 : 1]; }; + + const float sum_padding_border = item.main.sum_edges - (item.main.margin_a + item.main.margin_b); + + // Find the flex base size (possibly negative when using border box sizing) + if (computed.flex_basis().type != Style::FlexBasis::Auto) + { + item.inner_flex_base_size = ResolveValue(computed.flex_basis(), main_size_base_value); + if (computed.box_sizing() == Style::BoxSizing::BorderBox) + item.inner_flex_base_size -= sum_padding_border; + } + else if (!item.main.auto_size) + { + item.inner_flex_base_size = ResolveValue(item_main_size, main_size_base_value); + if (computed.box_sizing() == Style::BoxSizing::BorderBox) + item.inner_flex_base_size -= sum_padding_border; + } + else if (GetMainSize(item.box) >= 0.f) + { + // The element is auto-sized, and yet its box was given a definite size. This can happen e.g. due to intrinsic sizing or aspect ratios. + item.inner_flex_base_size = GetMainSize(item.box); + } + else if (main_axis_horizontal) + { + item.inner_flex_base_size = LayoutDetails::GetShrinkToFitWidth(element, flex_content_containing_block); + } + else + { + const Vector2f initial_box_size = item.box.GetSize(); + RMLUI_ASSERT(initial_box_size.y < 0.f); + + Box format_box = item.box; + if (initial_box_size.x < 0.f && flex_available_content_size.x >= 0.f) + format_box.SetContent(Vector2f(flex_available_content_size.x - item.cross.sum_edges, initial_box_size.y)); + + FormattingContext::FormatIndependent(flex_container_box, element, (format_box.GetSize().x >= 0 ? &format_box : nullptr), + FormattingContextType::Block); + item.inner_flex_base_size = element->GetBox().GetSize().y; + + // Apply the automatic block size as minimum size (§4.5). Strictly speaking, we should also apply this to + // the other branches in column mode (and inline min-content size in row mode). However, the formatting step + // can be expensive, here we have already done that step so the value is readily accessible to us. + if (item.main.min_size == 0.f && !LayoutDetails::IsScrollContainer(computed.overflow_x(), computed.overflow_y())) + item.main.min_size = Math::Min(item.inner_flex_base_size, item.main.max_size); + } + + // Calculate the hypothetical main size (clamped flex base size). + item.hypothetical_main_size = Math::Clamp(item.inner_flex_base_size, item.main.min_size, item.main.max_size) + item.main.sum_edges; + item.flex_base_size = item.inner_flex_base_size + item.main.sum_edges; + + items.push_back(std::move(item)); + } + + if (items.empty()) + { + return; + } + + // -- Collect the items into lines -- + FlexLineContainer container; + + if (flex_single_line) + { + container.lines.emplace_back(std::move(items)); + } + else + { + float cursor = 0; + + Vector line_items; + + for (FlexItem& item : items) + { + cursor += item.hypothetical_main_size; + + if (!line_items.empty() && cursor > main_wrap_size) + { + // Break into new line. + container.lines.emplace_back(std::move(line_items)); + cursor = item.hypothetical_main_size; + line_items = {std::move(item)}; + } + else + { + // Add item to current line. + line_items.push_back(std::move(item)); + } + + cursor += main_gap_size; + } + + if (!line_items.empty()) + container.lines.emplace_back(std::move(line_items)); + + items.clear(); + items.shrink_to_fit(); + } + + for (FlexLine& line : container.lines) + { + // now that items are in lines, we can add the main gap size to all but the last item + if (main_gap_size > 0.f) + { + for (size_t i = 0; i < line.items.size() - 1; i++) + { + line.items[i].hypothetical_main_size += main_gap_size; + line.items[i].flex_base_size += main_gap_size; + line.items[i].main.margin_b += main_gap_size; + line.items[i].main.sum_edges += main_gap_size; + } + } + + line.accumulated_hypothetical_main_size = std::accumulate(line.items.begin(), line.items.end(), 0.0f, + [](float value, const FlexItem& item) { return value + item.hypothetical_main_size; }); + } + + // If the available main size is infinite, the used main size becomes the accumulated outer size of all items of the widest line. + const float used_main_size_unconstrained = main_available_size >= 0.f + ? main_available_size + : std::max_element(container.lines.begin(), container.lines.end(), [](const FlexLine& a, const FlexLine& b) { + return a.accumulated_hypothetical_main_size < b.accumulated_hypothetical_main_size; + })->accumulated_hypothetical_main_size; + + const float used_main_size = Math::Clamp(used_main_size_unconstrained, main_min_size, main_max_size); + + // -- Determine main size -- + // Resolve flexible lengths to find the used main size of all items. + for (FlexLine& line : container.lines) + { + const float available_flex_space = used_main_size - line.accumulated_hypothetical_main_size; // Possibly negative + + const bool flex_mode_grow = (available_flex_space > 0.f); + + auto FlexFactor = [flex_mode_grow](const FlexItem& item) { return (flex_mode_grow ? item.flex_grow_factor : item.flex_shrink_factor); }; + + // Initialize items and freeze inflexible items. + for (FlexItem& item : line.items) + { + item.target_main_size = item.flex_base_size; + + if (FlexFactor(item) == 0.f || (flex_mode_grow && item.flex_base_size > item.hypothetical_main_size) || + (!flex_mode_grow && item.flex_base_size < item.hypothetical_main_size)) + { + item.frozen = true; + item.target_main_size = item.hypothetical_main_size; + } + } + + auto RemainingFreeSpace = [used_main_size, &line]() { + return used_main_size - std::accumulate(line.items.begin(), line.items.end(), 0.f, [](float value, const FlexItem& item) { + return value + (item.frozen ? item.target_main_size : item.flex_base_size); + }); + }; + + const float initial_free_space = RemainingFreeSpace(); + + // Now iteratively distribute or shrink the size of all the items, until all the items are frozen. + while (!std::all_of(line.items.begin(), line.items.end(), [](const FlexItem& item) { return item.frozen; })) + { + float remaining_free_space = RemainingFreeSpace(); + + const float flex_factor_sum = std::accumulate(line.items.begin(), line.items.end(), 0.f, + [&FlexFactor](float value, const FlexItem& item) { return value + (item.frozen ? 0.0f : FlexFactor(item)); }); + + if (flex_factor_sum < 1.f) + { + const float scaled_initial_free_space = initial_free_space * flex_factor_sum; + if (Math::Absolute(scaled_initial_free_space) < Math::Absolute(remaining_free_space)) + remaining_free_space = scaled_initial_free_space; + } + + if (remaining_free_space != 0.f) + { + // Distribute free space proportionally to flex factors + if (flex_mode_grow) + { + for (FlexItem& item : line.items) + { + if (!item.frozen) + { + const float distribute_ratio = item.flex_grow_factor / flex_factor_sum; + item.target_main_size = item.flex_base_size + distribute_ratio * remaining_free_space; + } + } + } + else + { + const float scaled_flex_shrink_factor_sum = + std::accumulate(line.items.begin(), line.items.end(), 0.f, [](float value, const FlexItem& item) { + return value + (item.frozen ? 0.0f : item.flex_shrink_factor * item.inner_flex_base_size); + }); + const float scaled_flex_shrink_factor_sum_nonzero = (scaled_flex_shrink_factor_sum == 0 ? 1 : scaled_flex_shrink_factor_sum); + + for (FlexItem& item : line.items) + { + if (!item.frozen) + { + const float scaled_flex_shrink_factor = item.flex_shrink_factor * item.inner_flex_base_size; + const float distribute_ratio = scaled_flex_shrink_factor / scaled_flex_shrink_factor_sum_nonzero; + item.target_main_size = item.flex_base_size - distribute_ratio * Math::Absolute(remaining_free_space); + } + } + } + } + + // Clamp min/max violations + float total_minmax_violation = 0.f; + + for (FlexItem& item : line.items) + { + if (!item.frozen) + { + const float inner_target_main_size = Math::Max(0.0f, item.target_main_size - item.main.sum_edges); + const float clamped_target_main_size = + Math::Clamp(inner_target_main_size, item.main.min_size, item.main.max_size) + item.main.sum_edges; + + const float violation_diff = clamped_target_main_size - item.target_main_size; + item.violation = (violation_diff > 0.0f ? FlexItem::Violation::Min + : (violation_diff < 0.f ? FlexItem::Violation::Max : FlexItem::Violation::None)); + item.target_main_size = clamped_target_main_size; + + total_minmax_violation += violation_diff; + } + } + + for (FlexItem& item : line.items) + { + if (total_minmax_violation > 0.0f) + item.frozen |= (item.violation == FlexItem::Violation::Min); + else if (total_minmax_violation < 0.0f) + item.frozen |= (item.violation == FlexItem::Violation::Max); + else + item.frozen = true; + } + } + + // Now, each item's used main size is found! + for (FlexItem& item : line.items) + item.used_main_size = item.target_main_size; + } + + // -- Align main axis (§9.5) -- + // Main alignment is done before cross sizing. Previously, doing it in this order was important due to pixel + // rounding, since changing the main offset could change the main size after rounding, which in turn could influence + // the cross size. However, now we no longer do pixel rounding, so we may be free to do cross sizing first if we + // want to do it in that order for some particular reason. + for (FlexLine& line : container.lines) + { + const float remaining_free_space = used_main_size - + std::accumulate(line.items.begin(), line.items.end(), 0.f, [](float value, const FlexItem& item) { return value + item.used_main_size; }); + + if (remaining_free_space > 0.0f) + { + const int num_auto_margins = std::accumulate(line.items.begin(), line.items.end(), 0, + [](int value, const FlexItem& item) { return value + int(item.main.auto_margin_a) + int(item.main.auto_margin_b); }); + + if (num_auto_margins > 0) + { + // Distribute the remaining space to the auto margins. + const float space_per_auto_margin = remaining_free_space / float(num_auto_margins); + for (FlexItem& item : line.items) + { + if (item.main.auto_margin_a) + item.main_auto_margin_size_a = space_per_auto_margin; + if (item.main.auto_margin_b) + item.main_auto_margin_size_b = space_per_auto_margin; + } + } + else + { + // Distribute the remaining space based on the 'justify-content' property. + using Style::JustifyContent; + const int num_items = int(line.items.size()); + + switch (computed_flex.justify_content()) + { + case JustifyContent::SpaceBetween: + if (num_items > 1) + { + const float space_per_edge = remaining_free_space / float(2 * num_items - 2); + for (int i = 0; i < num_items; i++) + { + FlexItem& item = line.items[i]; + if (i > 0) + item.main_auto_margin_size_a = space_per_edge; + if (i < num_items - 1) + item.main_auto_margin_size_b = space_per_edge; + } + break; + } + //-fallthrough + case JustifyContent::FlexStart: line.items.back().main_auto_margin_size_b = remaining_free_space; break; + case JustifyContent::FlexEnd: line.items.front().main_auto_margin_size_a = remaining_free_space; break; + case JustifyContent::Center: + line.items.front().main_auto_margin_size_a = 0.5f * remaining_free_space; + line.items.back().main_auto_margin_size_b = 0.5f * remaining_free_space; + break; + case JustifyContent::SpaceAround: + { + const float space_per_edge = remaining_free_space / float(2 * num_items); + for (FlexItem& item : line.items) + { + item.main_auto_margin_size_a = space_per_edge; + item.main_auto_margin_size_b = space_per_edge; + } + } + break; + case JustifyContent::SpaceEvenly: + { + const float space_per_edge = remaining_free_space / float(2 * (num_items + 1)); + for (int i = 0; i < num_items; i++) + { + FlexItem& item = line.items[i]; + item.main_auto_margin_size_a = space_per_edge; + item.main_auto_margin_size_b = space_per_edge; + if (i == 0) + item.main_auto_margin_size_a *= 2.0f; + else if (i == num_items - 1) + item.main_auto_margin_size_b *= 2.0f; + } + } + break; + } + } + } + + // Now find the offset for each item. + float cursor = 0.0f; + for (FlexItem& item : line.items) + { + if (direction_reverse) + item.main_offset = used_main_size - (cursor + item.used_main_size + item.main_auto_margin_size_a - item.main.margin_b); + else + item.main_offset = cursor + item.main.margin_a + item.main_auto_margin_size_a; + + cursor += item.used_main_size + item.main_auto_margin_size_a + item.main_auto_margin_size_b; + } + } + + // Apply cross axis gaps to every item in every line except the last line. + if (cross_gap_size > 0.f) + { + for (size_t i = 0; i < container.lines.size() - 1; i++) + { + FlexLine& line = container.lines[i]; + for (FlexItem& item : line.items) + { + item.cross.margin_b += cross_gap_size; + item.cross.sum_edges += cross_gap_size; + } + } + } + + auto CanSkipHypotheticalCrossSize = [=](const FlexItem& item) { + // If the following conditions are met, the hypothetical cross size will never be used. This allows us to skip a + // potentially slow step with content-based sizing. + const bool stretch_item = (item.align_self == Style::AlignSelf::Stretch); + const bool stretched = (stretch_item && item.cross.auto_size && !item.cross.auto_margin_a && !item.cross.auto_margin_b); + const bool single_line_definite_cross_size = (cross_available_size >= 0.f && flex_single_line); + return stretched && single_line_definite_cross_size; + }; + + // -- Determine cross size (§9.4) -- + // First, determine the cross size of each item, format it if necessary. + for (FlexLine& line : container.lines) + { + for (FlexItem& item : line.items) + { + if (CanSkipHypotheticalCrossSize(item)) + continue; + + const Vector2f content_size = item.box.GetSize(); + + if (main_axis_horizontal) + { + if (content_size.y < 0.0f) + { + item.box.SetContent(Vector2f(GetInnerUsedMainSize(item), content_size.y)); + FormattingContext::FormatIndependent(flex_container_box, item.element, &item.box, FormattingContextType::Block); + item.hypothetical_cross_size = item.element->GetBox().GetSize().y + item.cross.sum_edges; + } + else + { + item.hypothetical_cross_size = content_size.y + item.cross.sum_edges; + } + } + else + { + if (content_size.x < 0.0f) + { + item.box.SetContent(Vector2f(content_size.x, GetInnerUsedMainSize(item))); + item.hypothetical_cross_size = + LayoutDetails::GetShrinkToFitWidth(item.element, flex_content_containing_block) + item.cross.sum_edges; + } + else + { + item.hypothetical_cross_size = content_size.x + item.cross.sum_edges; + } + } + } + } + + // Determine cross size of each line. + if (cross_available_size >= 0.f && flex_single_line) + { + RMLUI_ASSERT(container.lines.size() == 1); + container.lines[0].cross_size = cross_available_size; + } + else + { + for (FlexLine& line : container.lines) + { + RMLUI_ASSERT(std::none_of(line.items.begin(), line.items.end(), [&](const auto& item) { return CanSkipHypotheticalCrossSize(item); })); + + const float largest_hypothetical_cross_size = + std::max_element(line.items.begin(), line.items.end(), [](const FlexItem& a, const FlexItem& b) { + return a.hypothetical_cross_size < b.hypothetical_cross_size; + })->hypothetical_cross_size; + + // Currently, we don't handle the case where baseline alignment could extend the line's cross size, see CSS specs 9.4.8. + line.cross_size = Math::Max(0.0f, largest_hypothetical_cross_size); + + if (flex_single_line) + line.cross_size = Math::Clamp(line.cross_size, cross_min_size, cross_max_size); + } + } + + // Stretch out the lines if we have extra space. + if (cross_available_size >= 0.f && computed_flex.align_content() == Style::AlignContent::Stretch) + { + int remaining_space = static_cast(cross_available_size - + std::accumulate(container.lines.begin(), container.lines.end(), 0.f, + [](float value, const FlexLine& line) { return value + line.cross_size; })); + + if (remaining_space > 0) + { + // Here we use integer math to ensure all space is distributed to pixel boundaries. + const int num_lines = (int)container.lines.size(); + for (int i = 0; i < num_lines; i++) + { + const int add_space_to_line = remaining_space / (num_lines - i); + remaining_space -= add_space_to_line; + container.lines[i].cross_size += static_cast(add_space_to_line); + } + } + } + + // Determine the used cross size of items. + for (FlexLine& line : container.lines) + { + for (FlexItem& item : line.items) + { + const bool stretch_item = (item.align_self == Style::AlignSelf::Stretch); + if (stretch_item && item.cross.auto_size && !item.cross.auto_margin_a && !item.cross.auto_margin_b) + { + item.used_cross_size = + Math::Clamp(line.cross_size - item.cross.sum_edges, item.cross.min_size, item.cross.max_size) + item.cross.sum_edges; + // Here we are supposed to re-format the item with the new size, so that percentages can be resolved, see CSS specs Sec. 9.4.11. Seems + // very slow, we skip this for now. + } + else + { + RMLUI_ASSERT(!CanSkipHypotheticalCrossSize(item)); + item.used_cross_size = item.hypothetical_cross_size; + } + } + } + + // -- Align cross axis (§9.6) -- + for (FlexLine& line : container.lines) + { + constexpr float UndefinedBaseline = -FLT_MAX; + float max_baseline_edge_distance = UndefinedBaseline; + FlexItem* max_baseline_item = nullptr; + + for (FlexItem& item : line.items) + { + const float remaining_space = line.cross_size - item.used_cross_size; + + item.cross_offset = item.cross.margin_a; + item.cross_baseline_top = UndefinedBaseline; + + const int num_auto_margins = int(item.cross.auto_margin_a) + int(item.cross.auto_margin_b); + if (num_auto_margins > 0) + { + const float space_per_auto_margin = Math::Max(remaining_space, 0.0f) / float(num_auto_margins); + item.cross_offset = item.cross.margin_a + (item.cross.auto_margin_a ? space_per_auto_margin : 0.f); + } + else + { + using Style::AlignSelf; + const AlignSelf align_self = item.align_self; + + switch (align_self) + { + case AlignSelf::Auto: + // Never encountered here: should already have been replaced by container's align-items property. + RMLUI_ERROR; + break; + case AlignSelf::FlexStart: + // Do nothing, cross offset set above with this behavior. + break; + case AlignSelf::FlexEnd: item.cross_offset = item.cross.margin_a + remaining_space; break; + case AlignSelf::Center: item.cross_offset = item.cross.margin_a + 0.5f * remaining_space; break; + case AlignSelf::Baseline: + { + // We don't currently have a good way to get the true baseline here, so we make a very rough zero-effort approximation. + const float baseline_heuristic = 0.5f * item.element->GetLineHeight(); + const float sum_edges_top = (wrap_reverse ? item.cross.sum_edges - item.cross.sum_edges_a : item.cross.sum_edges_a); + + item.cross_baseline_top = sum_edges_top + baseline_heuristic; + + const float baseline_edge_distance = (wrap_reverse ? item.used_cross_size - item.cross_baseline_top : item.cross_baseline_top); + if (baseline_edge_distance > max_baseline_edge_distance) + { + max_baseline_item = &item; + max_baseline_edge_distance = baseline_edge_distance; + } + } + break; + case AlignSelf::Stretch: + // Handled above + break; + } + } + + if (wrap_reverse) + { + const float reverse_offset = line.cross_size - item.used_cross_size + item.cross.margin_a + item.cross.margin_b; + item.cross_offset = reverse_offset - item.cross_offset; + } + } + + if (max_baseline_item) + { + // Align all baseline items such that their baselines are aligned with the one with the max. baseline distance. + // Cross offset for all baseline items are currently set as in 'flex-start'. + const float max_baseline_margin_top = (wrap_reverse ? max_baseline_item->cross.margin_b : max_baseline_item->cross.margin_a); + const float line_top_to_baseline_distance = + max_baseline_item->cross_offset - max_baseline_margin_top + max_baseline_item->cross_baseline_top; + + for (FlexItem& item : line.items) + { + if (item.cross_baseline_top != UndefinedBaseline) + { + const float margin_top = (wrap_reverse ? item.cross.margin_b : item.cross.margin_a); + item.cross_offset = line_top_to_baseline_distance - item.cross_baseline_top + margin_top; + } + } + } + } + + const float accumulated_lines_cross_size = std::accumulate(container.lines.begin(), container.lines.end(), 0.f, + [](float value, const FlexLine& line) { return value + line.cross_size; }); + + // If the available cross size is infinite, the used cross size becomes the accumulated line cross size. + const float used_cross_size_unconstrained = cross_available_size >= 0.f ? cross_available_size : accumulated_lines_cross_size; + const float used_cross_size = Math::Clamp(used_cross_size_unconstrained, cross_min_size, cross_max_size); + + // Align the lines along the cross-axis. + { + const float remaining_free_space = used_cross_size - accumulated_lines_cross_size; + const int num_lines = int(container.lines.size()); + + if (remaining_free_space > 0.f) + { + using Style::AlignContent; + + switch (computed_flex.align_content()) + { + case AlignContent::SpaceBetween: + if (num_lines > 1) + { + const float space_per_edge = remaining_free_space / float(2 * num_lines - 2); + for (int i = 0; i < num_lines; i++) + { + FlexLine& line = container.lines[i]; + if (i > 0) + line.cross_spacing_a = space_per_edge; + if (i < num_lines - 1) + line.cross_spacing_b = space_per_edge; + } + } + //-fallthrough + case AlignContent::FlexStart: container.lines.back().cross_spacing_b = remaining_free_space; break; + case AlignContent::FlexEnd: container.lines.front().cross_spacing_a = remaining_free_space; break; + case AlignContent::Center: + container.lines.front().cross_spacing_a = 0.5f * remaining_free_space; + container.lines.back().cross_spacing_b = 0.5f * remaining_free_space; + break; + case AlignContent::SpaceAround: + { + const float space_per_edge = remaining_free_space / float(2 * num_lines); + for (FlexLine& line : container.lines) + { + line.cross_spacing_a = space_per_edge; + line.cross_spacing_b = space_per_edge; + } + } + break; + case AlignContent::SpaceEvenly: + { + const float space_per_edge = remaining_free_space / float(2 * (num_lines + 1)); + for (int i = 0; i < num_lines; i++) + { + FlexLine& line = container.lines[i]; + line.cross_spacing_a = space_per_edge; + line.cross_spacing_b = space_per_edge; + if (i == 0) + line.cross_spacing_a *= 2.0f; + else if (i == num_lines - 1) + line.cross_spacing_b *= 2.0f; + } + } + break; + case AlignContent::Stretch: + // Handled above. + break; + } + } + + // Now find the offset and snap the line edges to the pixel grid. + float cursor = 0.f; + for (FlexLine& line : container.lines) + { + if (wrap_reverse) + line.cross_offset = used_cross_size - (cursor + line.cross_spacing_a + line.cross_size); + else + line.cross_offset = cursor + line.cross_spacing_a; + + cursor += line.cross_spacing_a + line.cross_size + line.cross_spacing_b; + } + } + + auto MainCrossToVec2 = [main_axis_horizontal](const float v_main, const float v_cross) { + return main_axis_horizontal ? Vector2f(v_main, v_cross) : Vector2f(v_cross, v_main); + }; + + bool baseline_set = false; + + // -- Format items -- + for (FlexLine& line : container.lines) + { + for (FlexItem& item : line.items) + { + const Vector2f item_size = MainCrossToVec2(GetInnerUsedMainSize(item), GetInnerUsedCrossSize(item)); + const Vector2f item_offset = MainCrossToVec2(item.main_offset, line.cross_offset + item.cross_offset); + + item.box.SetContent(item_size); + + UniquePtr item_layout_box = + FormattingContext::FormatIndependent(flex_container_box, item.element, &item.box, FormattingContextType::Block); + + // Set the position of the element within the flex container + item.element->SetOffset(flex_content_offset + item_offset, element_flex); + + // The flex container baseline is simply set to the first flex item that has a baseline. + if (!baseline_set && item_layout_box->GetBaselineOfLastLine(flex_baseline)) + { + flex_baseline += flex_content_offset.y + item_offset.y; + baseline_set = true; + } + + // The cell contents may overflow, propagate this to the flex container. + const Vector2f overflow_size = item_offset + item_layout_box->GetVisibleOverflowSize(); + + flex_content_overflow_size = Math::Max(flex_content_overflow_size, overflow_size); + } + } + + flex_resulting_content_size = MainCrossToVec2(used_main_size, used_cross_size); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/FlexFormattingContext.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/FlexFormattingContext.h new file mode 100644 index 00000000..b359d25b --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/FlexFormattingContext.h @@ -0,0 +1,42 @@ +#pragma once + +#include "../../../Include/RmlUi/Core/Types.h" +#include "FormattingContext.h" + +namespace Rml { + +class LayoutBox; +class ContainerBox; +class FlexContainer; + +/* + Formats a flex container element and its flex items according to flexible box (flexbox) layout rules. +*/ +class FlexFormattingContext final : public FormattingContext { +public: + /// Formats a flex container element and its flex items according to flexbox layout rules. + static UniquePtr Format(ContainerBox* parent_container, Element* element, const Box* override_initial_box); + + /// Computes max-content size for a flex container. + static Vector2f GetMaxContentSize(Element* element); + +private: + FlexFormattingContext() = default; + + /// Format the flexbox and its children. + /// @param[out] flex_resulting_content_size The final content size of the flex container. + /// @param[out] flex_content_overflow_size Overflow size in case flex items or their contents overflow the container. + /// @param[out] flex_baseline The baseline of the flex container, in terms of the vertical distance from its top-left border corner. + void Format(Vector2f& flex_resulting_content_size, Vector2f& flex_content_overflow_size, float& flex_baseline) const; + + Vector2f flex_available_content_size; + Vector2f flex_content_containing_block; + Vector2f flex_content_offset; + Vector2f flex_min_size; + Vector2f flex_max_size; + + Element* element_flex = nullptr; + FlexContainer* flex_container_box = nullptr; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/FloatedBoxSpace.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/FloatedBoxSpace.cpp new file mode 100644 index 00000000..3e9940ea --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/FloatedBoxSpace.cpp @@ -0,0 +1,235 @@ +#include "FloatedBoxSpace.h" +#include "../../../Include/RmlUi/Core/ComputedValues.h" +#include "../../../Include/RmlUi/Core/Element.h" +#include "../../../Include/RmlUi/Core/ElementScroll.h" +#include "BlockContainer.h" +#include "LayoutPools.h" +#include + +namespace Rml { + +FloatedBoxSpace::FloatedBoxSpace() {} + +FloatedBoxSpace::~FloatedBoxSpace() {} + +Vector2f FloatedBoxSpace::NextBoxPosition(const BlockContainer* parent, float& box_width, float cursor, const Vector2f dimensions, bool nowrap) const +{ + return NextBoxPosition(parent, box_width, cursor, dimensions, nowrap, Style::Float::None); +} + +Vector2f FloatedBoxSpace::NextFloatPosition(const BlockContainer* parent, float& out_box_width, float cursor, Vector2f dimensions, + Style::Float float_property, Style::Clear clear_property) const +{ + // Shift the cursor down (if necessary) so it isn't placed any higher than a previously-floated box. + for (int i = 0; i < NUM_ANCHOR_EDGES; ++i) + { + if (!boxes[i].empty()) + cursor = Math::Max(cursor, boxes[i].back().offset.y); + } + + // Shift the cursor down past to clear boxes, if necessary. + cursor = DetermineClearPosition(cursor, clear_property); + + // Find a place to put this box. + const bool nowrap = false; + const Vector2f margin_offset = NextBoxPosition(parent, out_box_width, cursor, dimensions, nowrap, float_property); + + return margin_offset; +} + +void FloatedBoxSpace::PlaceFloat(Style::Float float_property, Vector2f margin_position, Vector2f margin_size, Vector2f overflow_position, + Vector2f overflow_size) +{ + boxes[float_property == Style::Float::Left ? LEFT : RIGHT].push_back(FloatedBox{margin_position, margin_size}); + + // Set our extents so they enclose the new box. + extent_top_left_overflow = Math::Min(extent_top_left_overflow, overflow_position); + extent_bottom_right_overflow = Math::Max(extent_bottom_right_overflow, overflow_position + overflow_size); + extent_bottom_right_margin = Math::Max(extent_bottom_right_margin, margin_position + margin_size); +} + +float FloatedBoxSpace::DetermineClearPosition(float cursor, Style::Clear clear_property) const +{ + using namespace Style; + // Clear left boxes. + if (clear_property == Clear::Left || clear_property == Clear::Both) + { + for (size_t i = 0; i < boxes[LEFT].size(); ++i) + cursor = Math::Max(cursor, boxes[LEFT][i].offset.y + boxes[LEFT][i].dimensions.y); + } + + // Clear right boxes. + if (clear_property == Clear::Right || clear_property == Clear::Both) + { + for (size_t i = 0; i < boxes[RIGHT].size(); ++i) + cursor = Math::Max(cursor, boxes[RIGHT][i].offset.y + boxes[RIGHT][i].dimensions.y); + } + + return cursor; +} + +Vector2f FloatedBoxSpace::NextBoxPosition(const BlockContainer* parent, float& maximum_box_width, const float cursor, const Vector2f dimensions, + const bool nowrap, const Style::Float float_property) const +{ + const float parent_scrollbar_width = parent->GetElement()->GetElementScroll()->GetScrollbarSize(ElementScroll::VERTICAL); + const float parent_edge_left = parent->GetPosition().x + parent->GetBox().GetPosition().x; + const float parent_edge_right = parent_edge_left + parent->GetBox().GetSize().x - parent_scrollbar_width; + + const AnchorEdge box_edge = (float_property == Style::Float::Right ? RIGHT : LEFT); + + Vector2f box_position = {parent_edge_left, cursor}; + + if (box_edge == RIGHT) + box_position.x = parent_edge_right - dimensions.x; + + float next_cursor = FLT_MAX; + + // First up; we iterate through all boxes that share our edge, pushing ourself to the side of them if we intersect + // them. We record the height of the lowest box that gets in our way; in the event we can't be positioned at this + // height, we'll reposition ourselves at that height for the next iteration. + for (const FloatedBox& fixed_box : boxes[box_edge]) + { + // If the fixed box's bottom edge is above our top edge, then we can safely skip it. + if (fixed_box.offset.y + fixed_box.dimensions.y <= box_position.y) + continue; + + // If the fixed box's top edge is below our bottom edge, then we can safely skip it. + if (fixed_box.offset.y >= box_position.y + dimensions.y) + continue; + + // We're intersecting this box vertically, so the box is pushed to the side if necessary. + bool collision = false; + if (box_edge == LEFT) + { + float right_edge = fixed_box.offset.x + fixed_box.dimensions.x; + collision = box_position.x < right_edge; + if (collision) + box_position.x = right_edge; + } + else + { + collision = box_position.x + dimensions.x > fixed_box.offset.x; + if (collision) + box_position.x = fixed_box.offset.x - dimensions.x; + } + + // If there was a collision, then we *might* want to remember the height of this box if it is the earliest- + // terminating box we've collided with so far. + if (collision && !nowrap) + { + next_cursor = Math::Min(next_cursor, fixed_box.offset.y + fixed_box.dimensions.y); + + // Were we pushed out of our containing box? If so, try again at the next cursor position. + if (box_position.x < parent_edge_left || box_position.x + dimensions.x > parent_edge_right) + return NextBoxPosition(parent, maximum_box_width, next_cursor, dimensions, nowrap, float_property); + } + } + + // Second; we go through all of the boxes on the other edge, checking for horizontal collisions and determining the + // maximum width the box can stretch to, if it is placed at this location. + maximum_box_width = (box_edge == LEFT ? parent_edge_right - box_position.x : box_position.x + dimensions.x); + + for (const FloatedBox& fixed_box : boxes[1 - box_edge]) + { + // If the fixed box's bottom edge is above our top edge, then we can safely skip it. + if (fixed_box.offset.y + fixed_box.dimensions.y <= box_position.y) + continue; + + // If the fixed box's top edge is below our bottom edge, then we can safely skip it. + if (fixed_box.offset.y >= box_position.y + dimensions.y) + continue; + + // We intersect this box vertically, so check if it intersects horizontally. + bool collision = false; + if (box_edge == LEFT) + { + maximum_box_width = Math::Min(maximum_box_width, fixed_box.offset.x - box_position.x); + collision = box_position.x + dimensions.x > fixed_box.offset.x; + } + else + { + maximum_box_width = Math::Min(maximum_box_width, (box_position.x + dimensions.x) - (fixed_box.offset.x + fixed_box.dimensions.x)); + collision = box_position.x < fixed_box.offset.x + fixed_box.dimensions.x; + } + + // If we collided with this box ... d'oh! We'll try again lower down the page, at the highest bottom-edge of + // any of the boxes we've been pushed around by so far. + if (collision && !nowrap) + { + next_cursor = Math::Min(next_cursor, fixed_box.offset.y + fixed_box.dimensions.y); + return NextBoxPosition(parent, maximum_box_width, next_cursor, dimensions, nowrap, float_property); + } + } + + // If we are restricted from wrapping the position down, then we are already done now that we've shifted horizontally. + if (nowrap) + return box_position; + + // Third; we go through all of the boxes (on both sides), checking for vertical collisions. + for (int i = 0; i < 2; ++i) + { + for (const FloatedBox& fixed_box : boxes[i]) + { + // If the fixed box's bottom edge is above our top edge, then we can safely skip it. + if (fixed_box.offset.y + fixed_box.dimensions.y <= box_position.y) + continue; + + // If the fixed box's top edge is below our bottom edge, then we can safely skip it. + if (fixed_box.offset.y >= box_position.y + dimensions.y) + continue; + + // We collide vertically; if we also collide horizontally, then we have to try again further down the + // layout. If the fixed box's left edge is to right of our right edge, then we can safely skip it. + if (fixed_box.offset.x >= box_position.x + dimensions.x) + continue; + + // If the fixed box's right edge is to the left of our left edge, then we can safely skip it. + if (fixed_box.offset.x + fixed_box.dimensions.x <= box_position.x) + continue; + + // D'oh! We hit this box. Ah well; we'll try again lower down the page, at the highest bottom-edge of any + // of the boxes we've been pushed around by so far. + next_cursor = Math::Min(next_cursor, fixed_box.offset.y + fixed_box.dimensions.y); + return NextBoxPosition(parent, maximum_box_width, next_cursor, dimensions, nowrap, float_property); + } + } + + // Looks like we've found a winner! + return box_position; +} + +Vector2f FloatedBoxSpace::GetDimensions(FloatedBoxEdge edge) const +{ + // For now, we don't really use the top-left extent, because it is not allowed in CSS to scroll to content located + // to the top or left, and thus we have no use for it currently. We could use it later to help detect overflow on + // the top-left sides. For example so we can hide parts of floats pushing outside the top-left sides of its parent + // which is set to 'overflow: auto'. + return edge == FloatedBoxEdge::Margin ? extent_bottom_right_margin : extent_bottom_right_overflow; +} + +float FloatedBoxSpace::GetShrinkToFitWidth(float edge_left, float edge_right) const +{ + // For the left-anchored boxes: Find the right-most edge of the boxes, relative to our parent's left edge. + float left_shrink_width = 0.f; + for (const FloatedBox& box : boxes[LEFT]) + left_shrink_width = Math::Max(left_shrink_width, box.offset.x - edge_left + box.dimensions.x); + + // Conversely, for the right-anchored boxes: Find the left-most edge, relative to our parent's right edge. + float right_shrink_width = 0.f; + for (const FloatedBox& box : boxes[RIGHT]) + right_shrink_width = Math::Max(right_shrink_width, edge_right - box.offset.x); + + return left_shrink_width + right_shrink_width; +} + +void* FloatedBoxSpace::operator new(size_t size) +{ + return LayoutPools::AllocateLayoutChunk(size); +} + +void FloatedBoxSpace::operator delete(void* chunk, size_t size) +{ + LayoutPools::DeallocateLayoutChunk(chunk, size); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/FloatedBoxSpace.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/FloatedBoxSpace.h new file mode 100644 index 00000000..90d1bfda --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/FloatedBoxSpace.h @@ -0,0 +1,97 @@ +#pragma once + +#include "../../../Include/RmlUi/Core/StyleTypes.h" +#include "../../../Include/RmlUi/Core/Types.h" + +namespace Rml { + +class Element; +class BlockContainer; + +enum class FloatedBoxEdge { + Margin, // The box's margin area, used for layout sizing. + Overflow, // Includes the box's border box plus any visible overflow, used to determine overflow and scrolling area. +}; + +/** + Each block box has a space object for managing the space occupied by its floating elements, and those of its + ancestors as relevant. + */ + +class FloatedBoxSpace { +public: + FloatedBoxSpace(); + ~FloatedBoxSpace(); + + /// Generates the position for a box of a given size within our block box. + /// @param[out] box_width The available width for the box. + /// @param[in] cursor The ideal vertical position for the box. + /// @param[in] dimensions The minimum available space required for the box. + /// @param[in] nowrap Restrict from wrapping down, returned vertical position always placed at ideal cursor. + /// @return The generated position for the box. + Vector2f NextBoxPosition(const BlockContainer* parent, float& box_width, float cursor, Vector2f dimensions, bool nowrap) const; + + /// Determines the position of a floated element within our block box. + /// @param[out] box_width The available width for the box. + /// @param[in] cursor The ideal vertical position for the box. + /// @param[in] dimensions The floated element's margin size. + /// @param[in] float_property The element's computed float property. + /// @param[in] clear_property The element's computed clear property. + /// @return The next placement position for the float at its top-left margin position. + Vector2f NextFloatPosition(const BlockContainer* parent, float& box_width, float cursor, Vector2f dimensions, Style::Float float_property, + Style::Clear clear_property) const; + + /// Add a new entry into our list of floated boxes. + void PlaceFloat(Style::Float float_property, Vector2f margin_position, Vector2f margin_size, Vector2f overflow_position, Vector2f overflow_size); + + /// Determines the appropriate vertical position for an object that is choosing to clear floating elements to + /// the left or right (or both). + /// @param[in] cursor The ideal vertical position. + /// @param[in] clear_property The value of the clear property of the clearing object. + /// @return The appropriate vertical position for the clearing object. + float DetermineClearPosition(float cursor, Style::Clear clear_property) const; + + /// Returns the size of the rectangle encompassing all boxes within the space, relative to the block formatting context space. + /// @param[in] edges Which edge of the boxes to encompass. + Vector2f GetDimensions(FloatedBoxEdge edge) const; + + /// Get the width of the floated boxes for calculating the shrink-to-fit width. + float GetShrinkToFitWidth(float edge_left, float edge_right) const; + + /// Clear all floating boxes placed in this space. + void Reset() + { + for (auto& box_list : boxes) + box_list.clear(); + extent_bottom_right_overflow = {}; + extent_bottom_right_overflow = {}; + extent_bottom_right_margin = {}; + } + + void* operator new(size_t size); + void operator delete(void* chunk, size_t size); + +private: + enum AnchorEdge { LEFT = 0, RIGHT = 1, NUM_ANCHOR_EDGES = 2 }; + + // Generates the position for an arbitrary box within our space layout, floated against either the left or right edge. + Vector2f NextBoxPosition(const BlockContainer* parent, float& maximum_box_width, float cursor, Vector2f dimensions, bool nowrap, + Style::Float float_property) const; + + struct FloatedBox { + Vector2f offset; + Vector2f dimensions; + }; + + using FloatedBoxList = Vector; + + // The boxes floating in our space. + FloatedBoxList boxes[NUM_ANCHOR_EDGES]; + + // The rectangle encompassing all boxes added specifically into this space, relative to our block formatting context space. + Vector2f extent_top_left_overflow; + Vector2f extent_bottom_right_overflow; + Vector2f extent_bottom_right_margin; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/FormattingContext.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/FormattingContext.cpp new file mode 100644 index 00000000..bc80967b --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/FormattingContext.cpp @@ -0,0 +1,52 @@ +#include "FormattingContext.h" +#include "../../../Include/RmlUi/Core/ComputedValues.h" +#include "../../../Include/RmlUi/Core/Element.h" +#include "../../../Include/RmlUi/Core/Profiling.h" +#include "BlockFormattingContext.h" +#include "FlexFormattingContext.h" +#include "LayoutBox.h" +#include "ReplacedFormattingContext.h" +#include "TableFormattingContext.h" + +namespace Rml { + +UniquePtr FormattingContext::FormatIndependent(ContainerBox* parent_container, Element* element, const Box* override_initial_box, + FormattingContextType backup_context) +{ + RMLUI_ZoneScopedC(0xAFAFAF); + using namespace Style; + + if (element->IsReplaced()) + return ReplacedFormattingContext::Format(parent_container, element, override_initial_box); + + FormattingContextType type = backup_context; + + auto& computed = element->GetComputedValues(); + const Display display = computed.display(); + if (display == Display::Flex || display == Display::InlineFlex) + { + type = FormattingContextType::Flex; + } + else if (display == Display::Table || display == Display::InlineTable) + { + type = FormattingContextType::Table; + } + else if (display == Display::InlineBlock || display == Display::FlowRoot || display == Display::TableCell || computed.float_() != Float::None || + computed.position() == Position::Absolute || computed.position() == Position::Fixed || computed.overflow_x() != Overflow::Visible || + computed.overflow_y() != Overflow::Visible || !element->GetParentNode() || element->GetParentNode()->GetDisplay() == Display::Flex) + { + type = FormattingContextType::Block; + } + + switch (type) + { + case FormattingContextType::Block: return BlockFormattingContext::Format(parent_container, element, override_initial_box); + case FormattingContextType::Table: return TableFormattingContext::Format(parent_container, element, override_initial_box); + case FormattingContextType::Flex: return FlexFormattingContext::Format(parent_container, element, override_initial_box); + case FormattingContextType::None: break; + } + + return nullptr; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/FormattingContext.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/FormattingContext.h new file mode 100644 index 00000000..ea36334a --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/FormattingContext.h @@ -0,0 +1,37 @@ +#pragma once + +#include "../../../Include/RmlUi/Core/Types.h" + +namespace Rml { + +class Box; +class ContainerBox; +class LayoutBox; + +enum class FormattingContextType { + Block, + Table, + Flex, + None, +}; + +/* + An environment in which related boxes are layed out. +*/ +class FormattingContext { +public: + /// Format the element in an independent formatting context, generating a new layout box. + /// @param[in] parent_container The container box which should act as the new box's parent. + /// @param[in] element The element to be formatted. + /// @param[in] override_initial_box Optionally set the initial box dimensions, otherwise one will be generated based on the element's properties. + /// @param[in] backup_context If a formatting context can not be determined from the element's properties, use this context. + /// @return A new, fully formatted layout box, or nullptr if its formatting context could not be determined, or if formatting was unsuccessful. + static UniquePtr FormatIndependent(ContainerBox* parent_container, Element* element, const Box* override_initial_box, + FormattingContextType backup_context); + +protected: + FormattingContext() = default; + ~FormattingContext() = default; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/InlineBox.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/InlineBox.cpp new file mode 100644 index 00000000..557e6740 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/InlineBox.cpp @@ -0,0 +1,130 @@ +#include "InlineBox.h" +#include "../../../Include/RmlUi/Core/Box.h" +#include "../../../Include/RmlUi/Core/Element.h" +#include "../../../Include/RmlUi/Core/FontMetrics.h" + +namespace Rml { + +static void ZeroBoxEdge(Box& box, BoxEdge edge) +{ + box.SetEdge(BoxArea::Padding, edge, 0.f); + box.SetEdge(BoxArea::Border, edge, 0.f); + box.SetEdge(BoxArea::Margin, edge, 0.f); +} + +InlineLevelBox* InlineBoxBase::AddChild(UniquePtr child) +{ + auto result = child.get(); + children.push_back(std::move(child)); + return result; +} + +void InlineBoxBase::GetStrut(float& out_total_height_above, float& out_total_depth_below) const +{ + const FontMetrics& font_metrics = GetFontMetrics(); + const float line_height = GetElement()->GetLineHeight(); + + const float half_leading = 0.5f * (line_height - (font_metrics.ascent + font_metrics.descent)); + out_total_height_above = font_metrics.ascent + half_leading; + out_total_depth_below = line_height - out_total_height_above; +} + +String InlineBoxBase::DebugDumpTree(int depth) const +{ + String value = InlineLevelBox::DebugDumpTree(depth); + for (auto&& child : children) + value += child->DebugDumpTree(depth + 1); + return value; +} + +InlineBoxBase::InlineBoxBase(Element* element) : InlineLevelBox(element) {} + +InlineBoxRoot::InlineBoxRoot(Element* element) : InlineBoxBase(element) +{ + float height_above_baseline, depth_below_baseline; + GetStrut(height_above_baseline, depth_below_baseline); + SetHeight(height_above_baseline, depth_below_baseline); +} + +FragmentConstructor InlineBoxRoot::CreateFragment(InlineLayoutMode /*mode*/, float /*available_width*/, float /*right_spacing_width*/, + bool /*first_box*/, LayoutOverflowHandle /*overflow_handle*/) +{ + RMLUI_ERROR; + return {}; +} + +void InlineBoxRoot::Submit(const PlacedFragment& /*placed_fragment*/) +{ + RMLUI_ERROR; +} + +InlineBox::InlineBox(const InlineLevelBox* parent, Element* element, const Box& _box) : InlineBoxBase(element), box(_box) +{ + RMLUI_ASSERT(box.GetSize().x < 0.f && box.GetSize().y < 0.f); + + const FontMetrics& font_metrics = GetFontMetrics(); + + // The inline box content height does not depend on the 'line-height' property, only on the font, and is not exactly + // specified by CSS. Here we choose to size the content height equal to the default line-height for the font-size. + const float inner_height = 1.2f * (float)font_metrics.size; + box.SetContent(Vector2f(-1.f, inner_height)); + + float height_above_baseline, depth_below_baseline; + GetStrut(height_above_baseline, depth_below_baseline); + SetHeightAndVerticalAlignment(height_above_baseline, depth_below_baseline, parent); + + const float edge_left = box.GetCumulativeEdge(BoxArea::Padding, BoxEdge::Left); + const float edge_right = box.GetCumulativeEdge(BoxArea::Padding, BoxEdge::Right); + SetInlineBoxSpacing(edge_left, edge_right); + + // Vertically position the box so that its content box is equally spaced around its font ascent and descent metrics. + const float half_leading = 0.5f * (inner_height - (font_metrics.ascent + font_metrics.descent)); + baseline_to_border_height = + font_metrics.ascent + half_leading + box.GetEdge(BoxArea::Border, BoxEdge::Top) + box.GetEdge(BoxArea::Padding, BoxEdge::Top); +} + +FragmentConstructor InlineBox::CreateFragment(InlineLayoutMode mode, float available_width, float right_spacing_width, bool /*first_box*/, + LayoutOverflowHandle /*overflow_handle*/) +{ + if (mode != InlineLayoutMode::WrapAny || right_spacing_width <= available_width + GetSpacingLeft()) + return FragmentConstructor{FragmentType::InlineBox, -1.f, {}, {}}; + + return {}; +} + +void InlineBox::Submit(const PlacedFragment& placed_fragment) +{ + Element* element = GetElement(); + RMLUI_ASSERT(element && element != placed_fragment.offset_parent); + + Box element_box = box; + element_box.SetContent({placed_fragment.layout_width, element_box.GetSize().y}); + + if (placed_fragment.split_left) + ZeroBoxEdge(element_box, BoxEdge::Left); + if (placed_fragment.split_right) + ZeroBoxEdge(element_box, BoxEdge::Right); + + // In inline layout, fragments are positioned in terms of (x: margin edge, y: baseline), while element offsets are + // specified relative to their border box. Thus, find the offset from the fragment position to the border edge. + const Vector2f border_position = + placed_fragment.position + Vector2f{element_box.GetEdge(BoxArea::Margin, BoxEdge::Left), -baseline_to_border_height}; + + // We can determine the principal fragment based on its left split: Only the principal one has its left side intact, + // subsequent fragments have their left side split. + const bool principal_box = !placed_fragment.split_left; + + if (principal_box) + { + element_offset = border_position; + element->SetOffset(border_position, placed_fragment.offset_parent); + element->SetBox(element_box); + SubmitElementOnLayout(); + } + else + { + element->AddBox(element_box, border_position - element_offset); + } +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/InlineBox.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/InlineBox.h new file mode 100644 index 00000000..45296a4e --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/InlineBox.h @@ -0,0 +1,69 @@ +#pragma once + +#include "../../../Include/RmlUi/Core/Box.h" +#include "InlineLevelBox.h" + +namespace Rml { + +class InlineBoxBase : public InlineLevelBox { +public: + InlineLevelBox* AddChild(UniquePtr child); + + String DebugDumpTree(int depth) const override; + +protected: + InlineBoxBase(Element* element); + + // Get the total height above and depth below the baseline based on this element's line-height and font. + void GetStrut(float& out_total_height_above, float& out_total_depth_below) const; + +private: + using InlineLevelBoxList = Vector>; + + // @performance Use first_child, next_sibling instead to build the tree? + InlineLevelBoxList children; +}; + +/** + Inline boxes are inline-level boxes whose contents (child boxes) participate in the same inline formatting context + as the box itself. + + An inline box initially creates an unsized open fragment, since its width depends on its children. The fragment is + sized and placed later on, either when its line needs to be split or when its element is closed, which happens after + all its children in the element tree have already been placed. + */ +class InlineBox final : public InlineBoxBase { +public: + InlineBox(const InlineLevelBox* parent, Element* element, const Box& box); + + FragmentConstructor CreateFragment(InlineLayoutMode mode, float available_width, float right_spacing_width, bool first_box, + LayoutOverflowHandle overflow_handle) override; + + void Submit(const PlacedFragment& placed_fragment) override; + + String DebugDumpNameValue() const override { return "InlineBox"; } + +private: + float baseline_to_border_height = 0; + Vector2f element_offset; + Box box; +}; + +/** + The root inline box is contained directly within its inline container. + + All content in the current inline formatting context is contained either within the root or one of its decendants. + */ +class InlineBoxRoot final : public InlineBoxBase { +public: + InlineBoxRoot(Element* element); + + FragmentConstructor CreateFragment(InlineLayoutMode mode, float available_width, float right_spacing_width, bool first_box, + LayoutOverflowHandle overflow_handle) override; + + void Submit(const PlacedFragment& placed_fragment) override; + + String DebugDumpNameValue() const override { return "InlineBoxRoot"; } +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/InlineContainer.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/InlineContainer.cpp new file mode 100644 index 00000000..2af53e55 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/InlineContainer.cpp @@ -0,0 +1,288 @@ +#include "InlineContainer.h" +#include "../../../Include/RmlUi/Core/ComputedValues.h" +#include "../../../Include/RmlUi/Core/Element.h" +#include "../../../Include/RmlUi/Core/ElementScroll.h" +#include "../../../Include/RmlUi/Core/ElementText.h" +#include "../../../Include/RmlUi/Core/ElementUtilities.h" +#include "../../../Include/RmlUi/Core/Profiling.h" +#include "../../../Include/RmlUi/Core/Property.h" +#include "BlockContainer.h" +#include "FloatedBoxSpace.h" +#include "InlineLevelBox.h" +#include "LayoutDetails.h" +#include "LineBox.h" + +namespace Rml { + +InlineContainer::InlineContainer(BlockContainer* _parent, float _available_width) : + LayoutBox(Type::InlineContainer), parent(_parent), root_inline_box(_parent->GetElement()) +{ + RMLUI_ASSERT(_parent); + + box_size = {_available_width, -1.f}; + position = parent->NextBoxPosition(); + + const auto& computed = parent->GetElement()->GetComputedValues(); + element_line_height = computed.line_height().value; + wrap_content = (computed.white_space() != Style::WhiteSpace::Nowrap); + text_align = computed.text_align(); +} + +InlineContainer::~InlineContainer() {} + +InlineBox* InlineContainer::AddInlineElement(Element* element, const Box& box) +{ + RMLUI_ASSERT(element); + + InlineBox* inline_box = nullptr; + InlineLevelBox* inline_level_box = nullptr; + InlineBoxBase* parent_box = GetOpenInlineBox(); + + if (auto text_element = rmlui_dynamic_cast(element)) + { + inline_level_box = parent_box->AddChild(MakeUnique(text_element)); + } + else if (box.GetSize().x >= 0.f) + { + inline_level_box = parent_box->AddChild(MakeUnique(parent_box, element, box)); + } + else + { + auto inline_box_ptr = MakeUnique(parent_box, element, box); + inline_box = inline_box_ptr.get(); + inline_level_box = parent_box->AddChild(std::move(inline_box_ptr)); + } + + const float minimum_line_height = + Math::Max(element_line_height, (box.GetSize().y >= 0.f ? box.GetSizeAcross(BoxDirection::Vertical, BoxArea::Margin) : 0.f)); + + LayoutOverflowHandle overflow_handle = {}; + float minimum_width_next = 0.f; + + while (true) + { + LineBox* line_box = EnsureOpenLineBox(); + + UpdateLineBoxPlacement(line_box, minimum_width_next, minimum_line_height); + + InlineLayoutMode layout_mode = InlineLayoutMode::Nowrap; + if (wrap_content) + { + const bool line_shrinked_by_floats = (line_box->GetLineWidth() + 0.5f < box_size.x && minimum_width_next < box_size.x); + const bool can_wrap_any = (line_shrinked_by_floats || line_box->HasContent()); + layout_mode = (can_wrap_any ? InlineLayoutMode::WrapAny : InlineLayoutMode::WrapAfterContent); + } + + const bool add_new_line = line_box->AddBox(inline_level_box, layout_mode, overflow_handle); + if (!add_new_line) + break; + + minimum_width_next = (line_box->HasContent() ? 0.f : line_box->GetLineWidth() + 1.f); + + // Keep adding boxes on a new line, either because the box couldn't fit on the current line at all, or because it had to be split. + CloseOpenLineBox(false); + } + + return inline_box; +} + +void InlineContainer::CloseInlineElement(InlineBox* inline_box) +{ + if (LineBox* line_box = GetOpenLineBox()) + { + line_box->CloseInlineBox(inline_box); + } + else + { + RMLUI_ERROR; + } +} + +void InlineContainer::AddBreak(float line_height) +{ + // Simply end the line if one is open, otherwise increment by the line height. + if (GetOpenLineBox()) + CloseOpenLineBox(true); + else + box_cursor += line_height; +} + +void InlineContainer::AddChainedBox(UniquePtr open_line_box) +{ + RMLUI_ASSERT(line_boxes.empty()); + RMLUI_ASSERT(open_line_box && !open_line_box->IsClosed()); + line_boxes.push_back(std::move(open_line_box)); +} + +void InlineContainer::Close(UniquePtr* out_open_line_box, Vector2f& out_position, float& out_height) +{ + RMLUI_ZoneScoped; + + // The parent container may need the open line box to be split and resumed. + CloseOpenLineBox(true, out_open_line_box); + + // It is possible that floats were queued between closing the last line and closing this container, if so place them now. + parent->PlaceQueuedFloats(position.y + box_cursor); + + // Set this box's height. + box_size.y = Math::Max(box_cursor, 0.f); + + // Find the overflow size for our content, relative to our local space. + Vector2f visible_overflow_size = {0.f, box_size.y}; + + for (const auto& line_box : line_boxes) + { + visible_overflow_size.x = Math::Max(visible_overflow_size.x, line_box->GetPosition().x - position.x + line_box->GetExtentRight()); + } + + visible_overflow_size.x = Math::RoundDown(visible_overflow_size.x); + SetVisibleOverflowSize(visible_overflow_size); + + out_position = position; + out_height = box_size.y; +} + +void InlineContainer::CloseOpenLineBox(bool split_all_open_boxes, UniquePtr* out_split_line) +{ + if (LineBox* line_box = GetOpenLineBox()) + { + float height_of_line = 0.f; + UniquePtr split_line_box = line_box->DetermineVerticalPositioning(&root_inline_box, split_all_open_boxes, height_of_line); + + // If the final height of the line is larger than previously considered, we might need to push the line down to + // clear overlapping floats. + if (height_of_line > line_box->GetLineMinimumHeight()) + UpdateLineBoxPlacement(line_box, 0.f, height_of_line); + + // Now that the line has been given a final position and size, close the line box to submit all the fragments. + // Our parent block container acts as the containing block for our inline boxes. + line_box->Close(parent->GetElement(), parent->GetPosition(), text_align); + + // Move the cursor down, unless we should collapse the line. + if (!line_box->CanCollapseLine()) + box_cursor = (line_box->GetPosition().y - position.y) + height_of_line; + + // If we have any pending floating elements for our parent, then this would be an ideal time to place them. + parent->PlaceQueuedFloats(position.y + box_cursor); + + if (split_line_box) + { + if (out_split_line) + *out_split_line = std::move(split_line_box); + else + line_boxes.push_back(std::move(split_line_box)); + } + } +} + +bool InlineContainer::GetOpenLineBoxDimensions(float& out_vertical_position, Vector2f& out_tentative_size) const +{ + if (LineBox* line_box = GetOpenLineBox()) + { + out_vertical_position = position.y + box_cursor; + out_tentative_size = {line_box->GetBoxCursor(), line_box->GetLineMinimumHeight()}; + return true; + } + return false; +} + +void InlineContainer::UpdateOpenLineBoxPlacement() +{ + if (LineBox* line_box = GetOpenLineBox()) + UpdateLineBoxPlacement(line_box, 0.f, element_line_height); +} + +void InlineContainer::UpdateLineBoxPlacement(LineBox* line_box, float minimum_width, float minimum_height) +{ + RMLUI_ASSERT(line_box); + + Vector2f minimum_dimensions = { + Math::Max(minimum_width, line_box->GetBoxCursor()), + Math::Max(minimum_height, line_box->GetLineMinimumHeight()), + }; + + // @performance: We might benefit from doing this search only when the minimum dimensions change, or if we get new inline floats. + const float ideal_position_y = position.y + box_cursor; + float available_width = 0.f; + const Vector2f line_position = + parent->GetBlockBoxSpace()->NextBoxPosition(parent, available_width, ideal_position_y, minimum_dimensions, !wrap_content); + available_width = Math::Max(available_width, 0.f); + + line_box->SetLineBox(line_position, available_width, minimum_dimensions.y); +} + +float InlineContainer::GetShrinkToFitWidth() const +{ + float content_width = 0.0f; + + // Simply find our widest line. + for (const auto& line_box : line_boxes) + content_width = Math::Max(content_width, line_box->GetBoxCursor()); + + return content_width; +} + +Vector2f InlineContainer::GetStaticPositionEstimate(bool inline_level_box) const +{ + Vector2f result = {0.f, box_cursor}; + + if (const LineBox* line_box = GetOpenLineBox()) + { + if (inline_level_box) + result.x += line_box->GetBoxCursor(); + else + result.y += element_line_height; + } + + return result; +} + +bool InlineContainer::GetBaselineOfLastLine(float& out_baseline) const +{ + if (!line_boxes.empty()) + { + out_baseline = line_boxes.back()->GetPosition().y + line_boxes.back()->GetBaseline(); + return true; + } + return false; +} + +LineBox* InlineContainer::EnsureOpenLineBox() +{ + if (line_boxes.empty() || line_boxes.back()->IsClosed()) + { + line_boxes.push_back(MakeUnique()); + } + return line_boxes.back().get(); +} + +LineBox* InlineContainer::GetOpenLineBox() const +{ + if (line_boxes.empty() || line_boxes.back()->IsClosed()) + return nullptr; + return line_boxes.back().get(); +} + +InlineBoxBase* InlineContainer::GetOpenInlineBox() +{ + if (LineBox* line_box = GetOpenLineBox()) + { + if (InlineBox* inline_box = line_box->GetOpenInlineBox()) + return inline_box; + } + return &root_inline_box; +} + +String InlineContainer::DebugDumpTree(int depth) const +{ + String value = String(depth * 2, ' ') + "InlineContainer" + '\n'; + + value += root_inline_box.DebugDumpTree(depth + 1); + + for (const auto& line_box : line_boxes) + value += line_box->DebugDumpTree(depth + 1); + + return value; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/InlineContainer.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/InlineContainer.h new file mode 100644 index 00000000..931f61d1 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/InlineContainer.h @@ -0,0 +1,105 @@ +#pragma once + +#include "../../../Include/RmlUi/Core/Box.h" +#include "../../../Include/RmlUi/Core/Types.h" +#include "InlineBox.h" +#include "LayoutBox.h" + +namespace Rml { + +class BlockContainer; +class LineBox; + +/** + A container for inline-level boxes. + + Always a direct child of a block container where it acts as a block-level box, and starts a new inline formatting + context. It maintains a stack of line boxes in which fragments generated from inline-level boxes are placed within. + Not a proper CSS term, but effectively a "block container that only contains inline-level boxes". + */ +class InlineContainer final : public LayoutBox { +public: + /// Creates a new block box in an inline context. + InlineContainer(BlockContainer* parent, float available_width); + ~InlineContainer(); + + /// Adds a new inline-level element to this inline-context box. + /// @param[in] element The new inline-level element. + /// @param[in] box The box defining the element's bounds. + /// @return The inline box if one was generated for the elmeent, otherwise nullptr. + /// @note Any non-null return value must be closed with a call to CloseInlineElement(). + InlineBox* AddInlineElement(Element* element, const Box& box); + + /// Closes the previously added inline box. + /// @param[in] inline_box The box to close. + /// @note Calls to this function should be submitted in reverse order to AddInlineElement(). + void CloseInlineElement(InlineBox* inline_box); + + /// Add a break to the last line. + void AddBreak(float line_height); + /// Adds a line box for resuming one that was previously split. + /// @param[in] open_line_box The line box overflowing from a previous inline container. + void AddChainedBox(UniquePtr open_line_box); + + /// Closes the box. This will determine the element's height (if it was unspecified). + /// @param[out] out_open_line_box Optionally, output the open inline box. + /// @param[out] out_position Outputs the position of this container. + /// @param[out] out_height Outputs the height of this container. + void Close(UniquePtr* out_open_line_box, Vector2f& out_position, float& out_height); + + /// Update the placement of the open line box, e.g. to account for newly placed floats. + void UpdateOpenLineBoxPlacement(); + + /// Returns the position and tentative size of the currently open line box, if any. + bool GetOpenLineBoxDimensions(float& out_vertical_position, Vector2f& out_tentative_size) const; + /// Returns an estimate for the position of a hypothetical next box to be placed, relative to the content box of this container. + Vector2f GetStaticPositionEstimate(bool inline_level_box) const; + + // -- Inherited from LayoutBox -- + + float GetShrinkToFitWidth() const override; + bool GetBaselineOfLastLine(float& out_baseline) const override; + String DebugDumpTree(int depth) const override; + +private: + using LineBoxList = Vector>; + + LineBox* EnsureOpenLineBox(); + LineBox* GetOpenLineBox() const; + InlineBoxBase* GetOpenInlineBox(); + + /// Close any open line box. + /// @param[in] split_all_open_boxes Split all open inline boxes, even if they have no content. + /// @param[out] out_split_line Optionally return any resulting split line, otherwise it will be added as a new line box to this container. + void CloseOpenLineBox(bool split_all_open_boxes, UniquePtr* out_split_line = nullptr); + + /// Find and set the position and line width for the currently open line box. + /// @param[in] line_box The currently open line box. + /// @param[in] minimum_width The minimum line width to consider for this search. + /// @param[in] minimum_height The minimum line height to to be considered for this and future searches. + void UpdateLineBoxPlacement(LineBox* line_box, float minimum_width, float minimum_height); + + BlockContainer* parent; // [not-null] + + Vector2f position; + Vector2f box_size; + + // The element's computed line-height. Not necessarily the same as the height of our lines. + float element_line_height = 0.f; + // True if content should wrap instead of overflowing the line box. + bool wrap_content = false; + // The element's text-align property. + Style::TextAlign text_align = {}; + + // The vertical position of the currently open line, or otherwise the next one to be placed, relative to the top of this box. + float box_cursor = 0; + + // The root of the tree of inline boxes located in this inline container. + InlineBoxRoot root_inline_box; + + // The list of line boxes, each of which flows fragments generated by inline boxes. + // @performance Investigate using a list backed by the pools instead, to avoid allocations. + LineBoxList line_boxes; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/InlineLevelBox.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/InlineLevelBox.cpp new file mode 100644 index 00000000..8e18d666 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/InlineLevelBox.cpp @@ -0,0 +1,204 @@ +#include "InlineLevelBox.h" +#include "../../../Include/RmlUi/Core/ComputedValues.h" +#include "../../../Include/RmlUi/Core/Core.h" +#include "../../../Include/RmlUi/Core/ElementText.h" +#include "../../../Include/RmlUi/Core/FontEngineInterface.h" +#include "LayoutDetails.h" +#include "LayoutPools.h" + +namespace Rml { + +void* InlineLevelBox::operator new(size_t size) +{ + return LayoutPools::AllocateLayoutChunk(size); +} + +void InlineLevelBox::operator delete(void* chunk, size_t size) +{ + LayoutPools::DeallocateLayoutChunk(chunk, size); +} + +InlineLevelBox::~InlineLevelBox() {} + +void InlineLevelBox::SubmitElementOnLayout() +{ + element->OnLayout(); +} + +const FontMetrics& InlineLevelBox::GetFontMetrics() const +{ + if (FontFaceHandle handle = element->GetFontFaceHandle()) + return GetFontEngineInterface()->GetFontMetrics(handle); + + // If there is no font face defined then we provide zero'd out font metrics. This situation can affect the layout, + // in particular in terms of inline box sizing and vertical alignment. Thus, this is potentially a situation where + // we might want to log a warning. However, in many cases it will produce the same layout with or without the font, + // so in that sense the warnings can produce false positives. + // + // For now, we wait until we try to actually place text before producing any warnings, since that is a clear + // erroneous situation producing no text. See 'LogMissingFontFace' in ElementText.cpp, which also lists some + // possible reasons for the missing font face. + + static const FontMetrics font_metrics = {}; + return font_metrics; +} + +void InlineLevelBox::SetHeightAndVerticalAlignment(float _height_above_baseline, float _depth_below_baseline, const InlineLevelBox* parent) +{ + RMLUI_ASSERT(parent); + using Style::VerticalAlign; + + SetHeight(_height_above_baseline, _depth_below_baseline); + + const Style::VerticalAlign vertical_align = element->GetComputedValues().vertical_align(); + vertical_align_type = vertical_align.type; + + // Determine the offset from the parent baseline. + float parent_baseline_offset = 0.f; // The anchor on the parent, as an offset from its baseline. + float self_baseline_offset = 0.f; // The offset from the parent anchor to our baseline. + + switch (vertical_align.type) + { + case VerticalAlign::Baseline: parent_baseline_offset = 0.f; break; + case VerticalAlign::Length: parent_baseline_offset = -vertical_align.value; break; + case VerticalAlign::Sub: parent_baseline_offset = (1.f / 5.f) * (float)parent->GetFontMetrics().size; break; + case VerticalAlign::Super: parent_baseline_offset = (-1.f / 3.f) * (float)parent->GetFontMetrics().size; break; + case VerticalAlign::TextTop: + parent_baseline_offset = -parent->GetFontMetrics().ascent; + self_baseline_offset = height_above_baseline; + break; + case VerticalAlign::TextBottom: + parent_baseline_offset = parent->GetFontMetrics().descent; + self_baseline_offset = -depth_below_baseline; + break; + case VerticalAlign::Middle: + parent_baseline_offset = -0.5f * parent->GetFontMetrics().x_height; + self_baseline_offset = 0.5f * (height_above_baseline - depth_below_baseline); + break; + case VerticalAlign::Top: + case VerticalAlign::Center: + case VerticalAlign::Bottom: + // These are relative to the line box and handled later. + break; + } + + vertical_offset_from_parent = parent_baseline_offset + self_baseline_offset; +} + +void InlineLevelBox::SetHeight(float _height_above_baseline, float _depth_below_baseline) +{ + height_above_baseline = _height_above_baseline; + depth_below_baseline = _depth_below_baseline; +} + +void InlineLevelBox::SetInlineBoxSpacing(float _spacing_left, float _spacing_right) +{ + spacing_left = _spacing_left; + spacing_right = _spacing_right; +} + +String InlineLevelBox::DebugDumpTree(int depth) const +{ + String value = String(depth * 2, ' ') + DebugDumpNameValue() + " | " + LayoutDetails::GetDebugElementName(GetElement()) + '\n'; + return value; +} + +InlineLevelBox_Atomic::InlineLevelBox_Atomic(const InlineLevelBox* parent, Element* element, const Box& box) : InlineLevelBox(element), box(box) +{ + RMLUI_ASSERT(parent && element); + RMLUI_ASSERT(box.GetSize().x >= 0.f && box.GetSize().y >= 0.f); + + const float outer_height = box.GetSizeAcross(BoxDirection::Vertical, BoxArea::Margin); + + const float descent = GetElement()->GetBaseline(); + const float ascent = outer_height - descent; + SetHeightAndVerticalAlignment(ascent, descent, parent); +} + +FragmentConstructor InlineLevelBox_Atomic::CreateFragment(InlineLayoutMode mode, float available_width, float right_spacing_width, bool /*first_box*/, + LayoutOverflowHandle /*overflow_handle*/) +{ + const float outer_width = box.GetSizeAcross(BoxDirection::Horizontal, BoxArea::Margin); + + if (mode != InlineLayoutMode::WrapAny || outer_width + right_spacing_width <= available_width) + return FragmentConstructor{FragmentType::SizedBox, outer_width, {}, {}}; + + return {}; +} + +void InlineLevelBox_Atomic::Submit(const PlacedFragment& placed_fragment) +{ + const Vector2f margin_position = {placed_fragment.position.x, placed_fragment.position.y - GetHeightAboveBaseline()}; + const Vector2f margin_edge = {box.GetEdge(BoxArea::Margin, BoxEdge::Left), box.GetEdge(BoxArea::Margin, BoxEdge::Top)}; + const Vector2f border_position = margin_position + margin_edge; + + GetElement()->SetOffset(border_position, placed_fragment.offset_parent); + GetElement()->SetBox(box); + SubmitElementOnLayout(); +} + +InlineLevelBox_Text::InlineLevelBox_Text(ElementText* element) : InlineLevelBox(element) {} + +FragmentConstructor InlineLevelBox_Text::CreateFragment(InlineLayoutMode mode, float available_width, float right_spacing_width, bool first_box, + LayoutOverflowHandle in_overflow_handle) +{ + ElementText* text_element = GetTextElement(); + + const bool allow_empty = (mode == InlineLayoutMode::WrapAny); + const bool decode_escape_characters = true; + + String line_contents; + int line_begin = in_overflow_handle; + int line_length = 0; + float line_width = 0.f; + bool overflow = !text_element->GenerateLine(line_contents, line_length, line_width, line_begin, available_width, right_spacing_width, first_box, + decode_escape_characters, allow_empty); + + if (overflow && line_contents.empty()) + // We couldn't fit anything on this line. + return {}; + + LayoutOverflowHandle out_overflow_handle = {}; + if (overflow) + out_overflow_handle = line_begin + line_length; + + LayoutFragmentHandle fragment_handle = (LayoutFragmentHandle)fragments.size(); + fragments.push_back(std::move(line_contents)); + + return FragmentConstructor{FragmentType::TextRun, line_width, fragment_handle, out_overflow_handle}; +} + +void InlineLevelBox_Text::Submit(const PlacedFragment& placed_fragment) +{ + RMLUI_ASSERT((size_t)placed_fragment.handle < fragments.size()); + + const int fragment_index = (int)placed_fragment.handle; + const bool principal_box = (fragment_index == 0); + + ElementText* text_element = GetTextElement(); + Vector2f line_offset; + + if (principal_box) + { + element_offset = placed_fragment.position; + text_element->SetOffset(placed_fragment.position, placed_fragment.offset_parent); + text_element->ClearLines(); + } + else + { + line_offset = placed_fragment.position - element_offset; + } + + text_element->AddLine(line_offset, std::move(fragments[fragment_index])); +} + +String InlineLevelBox_Text::DebugDumpNameValue() const +{ + return "InlineLevelBox_Text"; +} + +ElementText* InlineLevelBox_Text::GetTextElement() +{ + return rmlui_static_cast(GetElement()); +} +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/InlineLevelBox.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/InlineLevelBox.h new file mode 100644 index 00000000..af1220de --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/InlineLevelBox.h @@ -0,0 +1,115 @@ +#pragma once + +#include "../../../Include/RmlUi/Core/Box.h" +#include "../../../Include/RmlUi/Core/StyleTypes.h" +#include "InlineTypes.h" + +namespace Rml { + +class ElementText; +struct FontMetrics; + +/** + A box that takes part in inline layout. + + The inline-level box is used to generate fragments that are placed within line boxes. + */ +class InlineLevelBox { +public: + virtual ~InlineLevelBox(); + + // Create a fragment from this box, if it can fit within the available width. + virtual FragmentConstructor CreateFragment(InlineLayoutMode mode, float available_width, float right_spacing_width, bool first_box, + LayoutOverflowHandle overflow_handle) = 0; + + // Submit a fragment's position and size to be displayed on the underlying element. + virtual void Submit(const PlacedFragment& placed_fragment) = 0; + + float GetHeightAboveBaseline() const { return height_above_baseline; } + float GetDepthBelowBaseline() const { return depth_below_baseline; } + Style::VerticalAlign::Type GetVerticalAlign() const { return vertical_align_type; } + float GetVerticalOffsetFromParent() const { return vertical_offset_from_parent; } + float GetSpacingLeft() const { return spacing_left; } + float GetSpacingRight() const { return spacing_right; } + + virtual String DebugDumpNameValue() const = 0; + virtual String DebugDumpTree(int depth) const; + + void* operator new(size_t size); + void operator delete(void* chunk, size_t size); + +protected: + InlineLevelBox(Element* element) : element(element) { RMLUI_ASSERT(element); } + + Element* GetElement() const { return element; } + const FontMetrics& GetFontMetrics() const; + + // Set the height used for inline layout, and the vertical offset relative to our parent box. + void SetHeightAndVerticalAlignment(float height_above_baseline, float depth_below_baseline, const InlineLevelBox* parent); + + // Set the height used for inline layout. + void SetHeight(float height_above_baseline, float depth_below_baseline); + + // Set the inner-to-outer spacing (margin + border + padding) for inline boxes. + void SetInlineBoxSpacing(float spacing_left, float spacing_right); + + // Calls Element::OnLayout (proxy for private access to Element). + void SubmitElementOnLayout(); + +private: + float height_above_baseline = 0.f; + float depth_below_baseline = 0.f; + + Style::VerticalAlign::Type vertical_align_type = {}; + float vertical_offset_from_parent = 0.f; + + float spacing_left = 0.f; // Left margin-border-padding for inline boxes. + float spacing_right = 0.f; // Right margin-border-padding for inline boxes. + + Element* element; +}; + +/** + Atomic inline-level boxes are sized boxes that cannot be split. + + This includes inline-block elements, replaced inline-level elements, inline tables, and inline flex containers. + */ +class InlineLevelBox_Atomic final : public InlineLevelBox { +public: + InlineLevelBox_Atomic(const InlineLevelBox* parent, Element* element, const Box& box); + + FragmentConstructor CreateFragment(InlineLayoutMode mode, float available_width, float right_spacing_width, bool first_box, + LayoutOverflowHandle overflow_handle) override; + + void Submit(const PlacedFragment& placed_fragment) override; + + String DebugDumpNameValue() const override { return "InlineLevelBox_Atomic"; } + +private: + Box box; +}; + +/** + Inline-level text boxes represent text nodes. + + Generates fragments to display its text, splitting it up as necessary to fit in the available space. + */ +class InlineLevelBox_Text final : public InlineLevelBox { +public: + InlineLevelBox_Text(ElementText* element); + + FragmentConstructor CreateFragment(InlineLayoutMode mode, float available_width, float right_spacing_width, bool first_box, + LayoutOverflowHandle overflow_handle) override; + + void Submit(const PlacedFragment& placed_fragment) override; + + String DebugDumpNameValue() const override; + +private: + ElementText* GetTextElement(); + + Vector2f element_offset; + StringList fragments; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/InlineTypes.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/InlineTypes.h new file mode 100644 index 00000000..bbf262c1 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/InlineTypes.h @@ -0,0 +1,39 @@ +#pragma once + +#include "../../../Include/RmlUi/Core/Types.h" + +namespace Rml { + +using LayoutOverflowHandle = int; +using LayoutFragmentHandle = int; + +enum class InlineLayoutMode { + WrapAny, // Allow wrapping to avoid overflow, even if nothing is placed. + WrapAfterContent, // Allow wrapping to avoid overflow, but first place at least *some* content on this line. + Nowrap, // Place all content on this line, regardless of overflow. +}; + +enum class FragmentType : byte { + Invalid, // Could not be placed. + InlineBox, // An inline box. + SizedBox, // Sized inline-level boxes that are not inline-boxes. + TextRun, // Text runs. +}; + +struct FragmentConstructor { + FragmentType type = FragmentType::Invalid; + float layout_width = 0.f; + LayoutFragmentHandle fragment_handle = {}; // Handle to enable the inline-level box to reference any fragment-specific data. + LayoutOverflowHandle overflow_handle = {}; // Overflow handle is non-zero when there is another fragment to be layed out. +}; + +struct PlacedFragment { + Element* offset_parent; + LayoutFragmentHandle handle; + Vector2f position; + float layout_width; + bool split_left; + bool split_right; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/LayoutBox.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/LayoutBox.cpp new file mode 100644 index 00000000..641c7289 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/LayoutBox.cpp @@ -0,0 +1,31 @@ +#include "LayoutBox.h" +#include "LayoutPools.h" + +namespace Rml { + +const Box* LayoutBox::GetIfBox() const +{ + return nullptr; +} + +bool LayoutBox::GetBaselineOfLastLine(float& /*out_baseline*/) const +{ + return false; +} + +float LayoutBox::GetShrinkToFitWidth() const +{ + return 0.f; +} +void* LayoutBox::operator new(size_t size) +{ + void* memory = LayoutPools::AllocateLayoutChunk(size); + return memory; +} + +void LayoutBox::operator delete(void* chunk, size_t size) +{ + LayoutPools::DeallocateLayoutChunk(chunk, size); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/LayoutBox.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/LayoutBox.h new file mode 100644 index 00000000..a98cf011 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/LayoutBox.h @@ -0,0 +1,48 @@ +#pragma once + +#include "../../../Include/RmlUi/Core/Box.h" +#include "../../../Include/RmlUi/Core/Types.h" + +namespace Rml { + +/* + A box used to represent the formatting structure of the document, taking part in the box tree. +*/ +class LayoutBox { +public: + enum class Type { Root, BlockContainer, InlineContainer, FlexContainer, TableWrapper, Replaced }; + + virtual ~LayoutBox() = default; + + Type GetType() const { return type; } + Vector2f GetVisibleOverflowSize() const { return visible_overflow_size; } + + // Returns a pointer to the dimensions box if this layout box has one. + virtual const Box* GetIfBox() const; + // Returns the baseline of the last line of this box, if any. Returns true if a baseline was found, otherwise false. + virtual bool GetBaselineOfLastLine(float& out_baseline) const; + // Calculates the box's inner content width, i.e. the size used to calculate the shrink-to-fit width. + virtual float GetShrinkToFitWidth() const; + + // Debug dump layout tree. + String DumpLayoutTree(int depth = 0) const { return DebugDumpTree(depth); } + + void* operator new(size_t size); + void operator delete(void* chunk, size_t size); + +protected: + LayoutBox(Type type) : type(type) {} + + void SetVisibleOverflowSize(Vector2f size) { visible_overflow_size = size; } + + virtual String DebugDumpTree(int depth) const = 0; + +private: + Type type; + + // Visible overflow size is the border size of this box, plus any overflowing content. If this is a scroll + // container, then any overflow should be caught here, and not contribute to our visible overflow size. + Vector2f visible_overflow_size; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/LayoutDetails.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/LayoutDetails.cpp new file mode 100644 index 00000000..c39dbbf0 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/LayoutDetails.cpp @@ -0,0 +1,559 @@ +#include "LayoutDetails.h" +#include "../../../Include/RmlUi/Core/ComputedValues.h" +#include "../../../Include/RmlUi/Core/Element.h" +#include "../../../Include/RmlUi/Core/ElementScroll.h" +#include "../../../Include/RmlUi/Core/ElementText.h" +#include "../../../Include/RmlUi/Core/Math.h" +#include "../../../Include/RmlUi/Core/Profiling.h" +#include "ContainerBox.h" +#include "FormattingContext.h" +#include "LayoutEngine.h" +#include + +namespace Rml { + +// Convert width or height of a border box to the width or height of its corresponding content box. +static inline float BorderSizeToContentSize(float border_size, float border_padding_edges_size) +{ + if (border_size < 0.0f || border_size >= FLT_MAX) + return border_size; + + return Math::Max(0.0f, border_size - border_padding_edges_size); +} + +void LayoutDetails::BuildBox(Box& box, Vector2f containing_block, Element* element, BuildBoxMode box_context) +{ + if (!element) + { + box.SetContent(containing_block); + return; + } + + const ComputedValues& computed = element->GetComputedValues(); + + // Calculate the padding area. + box.SetEdge(BoxArea::Padding, BoxEdge::Top, Math::Max(0.0f, ResolveValue(computed.padding_top(), containing_block.x))); + box.SetEdge(BoxArea::Padding, BoxEdge::Right, Math::Max(0.0f, ResolveValue(computed.padding_right(), containing_block.x))); + box.SetEdge(BoxArea::Padding, BoxEdge::Bottom, Math::Max(0.0f, ResolveValue(computed.padding_bottom(), containing_block.x))); + box.SetEdge(BoxArea::Padding, BoxEdge::Left, Math::Max(0.0f, ResolveValue(computed.padding_left(), containing_block.x))); + + // Calculate the border area. + box.SetEdge(BoxArea::Border, BoxEdge::Top, Math::Max(0.0f, computed.border_top_width())); + box.SetEdge(BoxArea::Border, BoxEdge::Right, Math::Max(0.0f, computed.border_right_width())); + box.SetEdge(BoxArea::Border, BoxEdge::Bottom, Math::Max(0.0f, computed.border_bottom_width())); + box.SetEdge(BoxArea::Border, BoxEdge::Left, Math::Max(0.0f, computed.border_left_width())); + + // Prepare sizing of the content area. + Vector2f content_area(-1, -1); + Vector2f min_size = Vector2f(0, 0); + Vector2f max_size = Vector2f(FLT_MAX, FLT_MAX); + + // Intrinsic size for replaced elements. + Vector2f intrinsic_size(-1, -1); + float intrinsic_ratio = -1; + + const bool replaced_element = element->GetIntrinsicDimensions(intrinsic_size, intrinsic_ratio); + + // Calculate the content area and constraints. 'auto' width and height are handled later. + // For inline non-replaced elements, width and height are ignored, so we can skip the calculations. + if (box_context == BuildBoxMode::Block || box_context == BuildBoxMode::UnalignedBlock || replaced_element) + { + content_area.x = ResolveValueOr(computed.width(), containing_block.x, -1.f); + content_area.y = ResolveValueOr(computed.height(), containing_block.y, -1.f); + + min_size = Vector2f{ + ResolveValueOr(computed.min_width(), containing_block.x, 0.f), + ResolveValueOr(computed.min_height(), containing_block.y, 0.f), + }; + max_size = Vector2f{ + ResolveValueOr(computed.max_width(), containing_block.x, FLT_MAX), + ResolveValueOr(computed.max_height(), containing_block.y, FLT_MAX), + }; + + // Adjust sizes for the given box sizing model. + if (computed.box_sizing() == Style::BoxSizing::BorderBox) + { + const float border_padding_width = box.GetSizeAcross(BoxDirection::Horizontal, BoxArea::Border, BoxArea::Padding); + const float border_padding_height = box.GetSizeAcross(BoxDirection::Vertical, BoxArea::Border, BoxArea::Padding); + + min_size.x = BorderSizeToContentSize(min_size.x, border_padding_width); + max_size.x = BorderSizeToContentSize(max_size.x, border_padding_width); + content_area.x = BorderSizeToContentSize(content_area.x, border_padding_width); + + min_size.y = BorderSizeToContentSize(min_size.y, border_padding_height); + max_size.y = BorderSizeToContentSize(max_size.y, border_padding_height); + content_area.y = BorderSizeToContentSize(content_area.y, border_padding_height); + } + + max_size = Math::Max(min_size, max_size); + + if (content_area.x >= 0) + content_area.x = Math::Clamp(content_area.x, min_size.x, max_size.x); + if (content_area.y >= 0) + content_area.y = Math::Clamp(content_area.y, min_size.y, max_size.y); + + if (replaced_element) + content_area = CalculateSizeForReplacedElement(content_area, min_size, max_size, intrinsic_size, intrinsic_ratio); + } + + box.SetContent(content_area); + + // Evaluate the margins, and width and height if they are auto. + BuildBoxSizeAndMargins(box, min_size, max_size, containing_block, element, box_context, replaced_element); +} + +void LayoutDetails::GetMinMaxWidth(float& min_width, float& max_width, const ComputedValues& computed, const Box& box, float containing_block_width) +{ + min_width = ResolveValueOr(computed.min_width(), containing_block_width, 0.f); + max_width = ResolveValueOr(computed.max_width(), containing_block_width, FLT_MAX); + + if (computed.box_sizing() == Style::BoxSizing::BorderBox) + { + const float border_padding_width = box.GetSizeAcross(BoxDirection::Horizontal, BoxArea::Border, BoxArea::Padding); + min_width = BorderSizeToContentSize(min_width, border_padding_width); + max_width = BorderSizeToContentSize(max_width, border_padding_width); + } +} + +void LayoutDetails::GetMinMaxHeight(float& min_height, float& max_height, const ComputedValues& computed, const Box& box, + float containing_block_height) +{ + min_height = ResolveValueOr(computed.min_height(), containing_block_height, 0.f); + max_height = ResolveValueOr(computed.max_height(), containing_block_height, FLT_MAX); + + if (computed.box_sizing() == Style::BoxSizing::BorderBox) + { + const float border_padding_height = box.GetSizeAcross(BoxDirection::Vertical, BoxArea::Border, BoxArea::Padding); + min_height = BorderSizeToContentSize(min_height, border_padding_height); + max_height = BorderSizeToContentSize(max_height, border_padding_height); + } +} + +void LayoutDetails::GetDefiniteMinMaxHeight(float& min_height, float& max_height, const ComputedValues& computed, const Box& box, + float containing_block_height) +{ + const float box_height = box.GetSize().y; + if (box_height < 0) + { + GetMinMaxHeight(min_height, max_height, computed, box, containing_block_height); + } + else + { + min_height = box_height; + max_height = box_height; + } +} + +ContainingBlock LayoutDetails::GetContainingBlock(ContainerBox* parent_container, const Style::Position position) +{ + RMLUI_ASSERT(parent_container); + using Style::Position; + + ContainerBox* container = parent_container; + BoxArea area = BoxArea::Content; + + // For absolutely positioned boxes we look for the first positioned ancestor. We deviate from the CSS specs by using + // the same rules for fixed boxes, as that is particularly helpful on handles and other widgets that should not + // scroll with the window. This is a common design pattern in target applications for this library, although this + // behavior may be reconsidered in the future. + if (position == Position::Absolute || position == Position::Fixed) + { + area = BoxArea::Padding; + + while (container->GetParent() && !container->IsAbsolutePositioningContainingBlock()) + container = container->GetParent(); + } + + const Box* box = container->GetIfBox(); + if (!box) + { + RMLUI_ERROR; + return {container, {}}; + } + + Vector2f containing_block = box->GetSize(area); + + if (position == Position::Static || position == Position::Relative) + { + // For static elements we subtract the scrollbar size so that elements normally don't overlap their parent's + // scrollbars. In CSS, this would also be done for absolutely positioned elements, we might want to copy that + // behavior in the future. If so, we would also need to change the element offset behavior, and ideally also + // make positioned boxes contribute to the scrollable area. + if (Element* element = container->GetElement()) + { + ElementScroll* element_scroll = element->GetElementScroll(); + if (containing_block.x >= 0.f) + containing_block.x = Math::Max(containing_block.x - element_scroll->GetScrollbarSize(ElementScroll::VERTICAL), 0.f); + if (containing_block.y >= 0.f) + containing_block.y = Math::Max(containing_block.y - element_scroll->GetScrollbarSize(ElementScroll::HORIZONTAL), 0.f); + } + } + + return {container, containing_block}; +} + +void LayoutDetails::BuildBoxSizeAndMargins(Box& box, Vector2f min_size, Vector2f max_size, Vector2f containing_block, Element* element, + BuildBoxMode box_context, bool replaced_element) +{ + const ComputedValues& computed = element->GetComputedValues(); + + if (box_context == BuildBoxMode::Inline || box_context == BuildBoxMode::UnalignedBlock) + { + // For inline elements, their calculations are straightforward. No worrying about auto margins and dimensions, etc. + // Evaluate the margins. Any declared as 'auto' will resolve to 0. + box.SetEdge(BoxArea::Margin, BoxEdge::Top, ResolveValue(computed.margin_top(), containing_block.x)); + box.SetEdge(BoxArea::Margin, BoxEdge::Right, ResolveValue(computed.margin_right(), containing_block.x)); + box.SetEdge(BoxArea::Margin, BoxEdge::Bottom, ResolveValue(computed.margin_bottom(), containing_block.x)); + box.SetEdge(BoxArea::Margin, BoxEdge::Left, ResolveValue(computed.margin_left(), containing_block.x)); + } + else + { + // The element is block, so we need to run the box through the ringer to potentially evaluate auto margins and dimensions. + BuildBoxWidth(box, computed, min_size.x, max_size.x, containing_block, element, replaced_element); + BuildBoxHeight(box, computed, min_size.y, max_size.y, containing_block.y); + } +} + +float LayoutDetails::GetShrinkToFitWidth(Element* element, Vector2f containing_block) +{ + RMLUI_ASSERT(element); + + // @performance Can we lay out the elements directly using a fit-content size mode, instead of fetching the + // shrink-to-fit width first? Use a non-definite placeholder for the box content width, and available width as a + // maximum constraint. + Box box; + float min_height, max_height; + LayoutDetails::BuildBox(box, containing_block, element, BuildBoxMode::UnalignedBlock); + LayoutDetails::GetDefiniteMinMaxHeight(min_height, max_height, element->GetComputedValues(), box, containing_block.y); + + if (box.GetSize().x >= 0.f) + { + return box.GetSize().x; + } + + // Currently we don't support shrink-to-fit width for tables. Just return a zero-sized width. + const Style::Display display = element->GetDisplay(); + if (display == Style::Display::Table || display == Style::Display::InlineTable) + { + return 0.f; + } + + // Use a large size for the box content width, so that it is practically unconstrained. This makes the formatting + // procedure act as if under a maximum content constraint. Children with percentage sizing values may be scaled + // based on this width (such as 'width' or 'margin'), if so, the layout is considered undefined like in CSS 2. + const float max_content_constraint_width = containing_block.x + 10000.f; + box.SetContent({max_content_constraint_width, box.GetSize().y}); + + // First, format the element under the above generated box. Then we ask the resulting box for its shrink-to-fit + // width. For block containers, this is essentially its largest line or child box. + // @performance. Some formatting can be simplified, e.g. absolute elements do not contribute to the shrink-to-fit + // width. Also, children of elements with a fixed width and height don't need to be formatted further. + RootBox root(Math::Max(containing_block, Vector2f(0.f))); + UniquePtr layout_box = FormattingContext::FormatIndependent(&root, element, &box, FormattingContextType::Block); + + float shrink_to_fit_width = layout_box->GetShrinkToFitWidth(); + if (containing_block.x >= 0) + { + const float available_width = + Math::Max(0.f, containing_block.x - box.GetSizeAcross(BoxDirection::Horizontal, BoxArea::Margin, BoxArea::Padding)); + shrink_to_fit_width = Math::Min(shrink_to_fit_width, available_width); + } + return shrink_to_fit_width; +} + +ComputedAxisSize LayoutDetails::BuildComputedHorizontalSize(const ComputedValues& computed) +{ + return ComputedAxisSize{computed.width(), computed.min_width(), computed.max_width(), computed.padding_left(), computed.padding_right(), + computed.margin_left(), computed.margin_right(), computed.border_left_width(), computed.border_right_width(), computed.box_sizing()}; +} + +ComputedAxisSize LayoutDetails::BuildComputedVerticalSize(const ComputedValues& computed) +{ + return ComputedAxisSize{computed.height(), computed.min_height(), computed.max_height(), computed.padding_top(), computed.padding_bottom(), + computed.margin_top(), computed.margin_bottom(), computed.border_top_width(), computed.border_bottom_width(), computed.box_sizing()}; +} + +void LayoutDetails::GetEdgeSizes(float& margin_a, float& margin_b, float& padding_border_a, float& padding_border_b, + const ComputedAxisSize& computed_size, const float base_value) +{ + margin_a = ResolveValue(computed_size.margin_a, base_value); + margin_b = ResolveValue(computed_size.margin_b, base_value); + + padding_border_a = Math::Max(0.0f, ResolveValue(computed_size.padding_a, base_value)) + Math::Max(0.0f, computed_size.border_a); + padding_border_b = Math::Max(0.0f, ResolveValue(computed_size.padding_b, base_value)) + Math::Max(0.0f, computed_size.border_b); +} + +String LayoutDetails::GetDebugElementName(Element* element) +{ + if (!element) + return "nullptr"; + if (!element->GetId().empty()) + return '#' + element->GetId(); + if (auto element_text = rmlui_dynamic_cast(element)) + return '\"' + StringUtilities::StripWhitespace(element_text->GetText()).substr(0, 20) + '\"'; + return element->GetAddress(false, false); +} + +Vector2f LayoutDetails::CalculateSizeForReplacedElement(const Vector2f specified_content_size, const Vector2f min_size, const Vector2f max_size, + const Vector2f intrinsic_size, const float intrinsic_ratio) +{ + // Start with the element's specified width and height. If any of them are auto, use the element's intrinsic + // dimensions and ratio to find a suitable content size. + Vector2f content_size = specified_content_size; + + const bool auto_width = (content_size.x < 0); + const bool auto_height = (content_size.y < 0); + + if (auto_width) + content_size.x = intrinsic_size.x; + + if (auto_height) + content_size.y = intrinsic_size.y; + + // Use a fallback size if we still couldn't determine the size. + if (content_size.x < 0) + content_size.x = 300; + if (content_size.y < 0) + content_size.y = 150; + + // Resolve the size constraints. + const float min_width = min_size.x; + const float max_width = max_size.x; + const float min_height = min_size.y; + const float max_height = max_size.y; + + // If we have an intrinsic ratio and one of the dimensions is 'auto', then scale it such that the ratio is preserved. + if (intrinsic_ratio > 0) + { + if (auto_width && !auto_height) + { + content_size.x = content_size.y * intrinsic_ratio; + } + else if (auto_height && !auto_width) + { + content_size.y = content_size.x / intrinsic_ratio; + } + else if (auto_width && auto_height) + { + // If both width and height are auto, try to preserve the ratio under the respective min/max constraints. + const float w = content_size.x; + const float h = content_size.y; + + if ((w < min_width && h > max_height) || (w > max_width && h < min_height)) + { + // Cannot preserve aspect ratio, let it be clamped. + } + else if (w < min_width && h < min_height) + { + // Increase the size such that both min-constraints are respected. The non-scaled axis will + // be clamped below, preserving the aspect ratio. + if (min_width <= min_height * intrinsic_ratio) + content_size.x = min_height * intrinsic_ratio; + else + content_size.y = min_width / intrinsic_ratio; + } + else if (w > max_width && h > max_height) + { + // Shrink the size such that both max-constraints are respected. The non-scaled axis will + // be clamped below, preserving the aspect ratio. + if (max_width <= max_height * intrinsic_ratio) + content_size.y = max_width / intrinsic_ratio; + else + content_size.x = max_height * intrinsic_ratio; + } + else + { + // Single constraint violations. + if (w < min_width) + content_size.y = min_width / intrinsic_ratio; + else if (w > max_width) + content_size.y = max_width / intrinsic_ratio; + else if (h < min_height) + content_size.x = min_height * intrinsic_ratio; + else if (h > max_height) + content_size.x = max_height * intrinsic_ratio; + } + } + } + + content_size.x = Math::Clamp(content_size.x, min_width, max_width); + content_size.y = Math::Clamp(content_size.y, min_height, max_height); + + return content_size; +} + +void LayoutDetails::BuildBoxWidth(Box& box, const ComputedValues& computed, float min_width, float max_width, Vector2f containing_block, + Element* element, bool replaced_element, float override_shrink_to_fit_width) +{ + RMLUI_ZoneScoped; + + Vector2f content_area = box.GetSize(); + + // Determine if the element has automatic margins. + bool margins_auto[2]; + int num_auto_margins = 0; + + for (int i = 0; i < 2; ++i) + { + const Style::Margin margin_value = (i == 0 ? computed.margin_left() : computed.margin_right()); + if (margin_value.type == Style::Margin::Auto) + { + margins_auto[i] = true; + num_auto_margins++; + box.SetEdge(BoxArea::Margin, i == 0 ? BoxEdge::Left : BoxEdge::Right, 0); + } + else + { + margins_auto[i] = false; + box.SetEdge(BoxArea::Margin, i == 0 ? BoxEdge::Left : BoxEdge::Right, ResolveValue(margin_value, containing_block.x)); + } + } + + const bool absolutely_positioned = (computed.position() == Style::Position::Absolute || computed.position() == Style::Position::Fixed); + const bool inset_auto = (computed.left().type == Style::Left::Auto || computed.right().type == Style::Right::Auto); + const bool width_auto = (content_area.x < 0); + + auto GetInsetWidth = [&] { + // For absolutely positioned elements (and only those), the 'left' and 'right' values are part of the box's width constraint. + if (absolutely_positioned) + return ResolveValue(computed.left(), containing_block.x) + ResolveValue(computed.right(), containing_block.x); + return 0.f; + }; + + // If the width is set to auto, we need to calculate the width. + if (width_auto) + { + // Apply the shrink-to-fit algorithm here to find the width of the element. + // See CSS 2.1 section 10.3.7 for when this should be applied. + const bool shrink_to_fit = !replaced_element && + ((computed.float_() != Style::Float::None) || (absolutely_positioned && inset_auto) || + (computed.display() == Style::Display::InlineBlock || computed.display() == Style::Display::InlineFlex)); + + if (!shrink_to_fit) + { + // The width is set to whatever remains of the containing block. + content_area.x = containing_block.x - (GetInsetWidth() + box.GetSizeAcross(BoxDirection::Horizontal, BoxArea::Margin, BoxArea::Padding)); + content_area.x = Math::Max(0.0f, content_area.x); + } + else if (override_shrink_to_fit_width >= 0) + { + content_area.x = override_shrink_to_fit_width; + } + else + { + content_area.x = GetShrinkToFitWidth(element, containing_block); + } + } + // Otherwise, the margins that are set to auto will pick up the remaining width of the containing block. + else if (num_auto_margins > 0) + { + const float margin = + (containing_block.x - (GetInsetWidth() + box.GetSizeAcross(BoxDirection::Horizontal, BoxArea::Margin))) / float(num_auto_margins); + + if (margins_auto[0]) + box.SetEdge(BoxArea::Margin, BoxEdge::Left, margin); + if (margins_auto[1]) + box.SetEdge(BoxArea::Margin, BoxEdge::Right, margin); + } + + // Clamp the calculated width; if the width is changed by the clamp, then the margins need to be recalculated if + // they were set to auto. + const float clamped_width = Math::Clamp(content_area.x, min_width, max_width); + if (clamped_width != content_area.x) + { + content_area.x = clamped_width; + box.SetContent(content_area); + + if (num_auto_margins > 0) + BuildBoxWidth(box, computed, min_width, max_width, containing_block, element, replaced_element, clamped_width); + } + else + box.SetContent(content_area); +} + +void LayoutDetails::BuildBoxHeight(Box& box, const ComputedValues& computed, float min_height, float max_height, float containing_block_height) +{ + RMLUI_ZoneScoped; + + Vector2f content_area = box.GetSize(); + + // Determine if the element has automatic margins. + bool margins_auto[2]; + int num_auto_margins = 0; + + for (int i = 0; i < 2; ++i) + { + const Style::Margin margin_value = (i == 0 ? computed.margin_top() : computed.margin_bottom()); + if (margin_value.type == Style::Margin::Auto) + { + margins_auto[i] = true; + num_auto_margins++; + box.SetEdge(BoxArea::Margin, i == 0 ? BoxEdge::Top : BoxEdge::Bottom, 0); + } + else + { + margins_auto[i] = false; + box.SetEdge(BoxArea::Margin, i == 0 ? BoxEdge::Top : BoxEdge::Bottom, ResolveValue(margin_value, containing_block_height)); + } + } + + const bool absolutely_positioned = (computed.position() == Style::Position::Absolute || computed.position() == Style::Position::Fixed); + const bool inset_auto = (computed.top().type == Style::Top::Auto || computed.bottom().type == Style::Bottom::Auto); + const bool height_auto = (content_area.y < 0); + + auto GetInsetHeight = [&] { + // For absolutely positioned elements (and only those), the 'top' and 'bottom' values are part of the box's height constraint. + if (absolutely_positioned) + return ResolveValue(computed.top(), containing_block_height) + ResolveValue(computed.bottom(), containing_block_height); + return 0.f; + }; + + // If the height is set to auto, we need to calculate the height. + if (height_auto) + { + // If the height is set to auto for a box in normal flow, the height is set to -1, representing indefinite height. + content_area.y = -1; + + // But if we are dealing with an absolutely positioned element we need to consider if the top and bottom + // properties are set, since the height can be affected. + if (absolutely_positioned && !inset_auto) + { + // The height is set to whatever remains of the containing block. + content_area.y = + containing_block_height - (GetInsetHeight() + box.GetSizeAcross(BoxDirection::Vertical, BoxArea::Margin, BoxArea::Padding)); + content_area.y = Math::Max(0.0f, content_area.y); + } + } + // Otherwise, the margins that are set to auto will pick up the remaining height of the containing block. + else if (num_auto_margins > 0) + { + const float margin = + (containing_block_height - (GetInsetHeight() + box.GetSizeAcross(BoxDirection::Vertical, BoxArea::Margin))) / float(num_auto_margins); + + if (margins_auto[0]) + box.SetEdge(BoxArea::Margin, BoxEdge::Top, margin); + if (margins_auto[1]) + box.SetEdge(BoxArea::Margin, BoxEdge::Bottom, margin); + } + + if (content_area.y >= 0) + { + // Clamp the calculated height; if the height is changed by the clamp, then the margins need to be recalculated if + // they were set to auto. + float clamped_height = Math::Clamp(content_area.y, min_height, max_height); + if (clamped_height != content_area.y) + { + content_area.y = clamped_height; + box.SetContent(content_area); + + if (num_auto_margins > 0) + BuildBoxHeight(box, computed, min_height, max_height, containing_block_height); + + return; + } + } + + box.SetContent(content_area); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/LayoutDetails.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/LayoutDetails.h new file mode 100644 index 00000000..18a9cccd --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/LayoutDetails.h @@ -0,0 +1,126 @@ +#pragma once + +#include "../../../Include/RmlUi/Core/StyleTypes.h" +#include "../../../Include/RmlUi/Core/Types.h" + +namespace Rml { + +class Box; +class BlockContainer; +class ContainerBox; + +/** + ComputedAxisSize is an abstraction of an element's computed size properties along a single axis, either horizontally or vertically, + allowing eg. rows and columns alike to use the same algorithms. Here, 'a' means left or top, 'b' means right or bottom. +*/ +struct ComputedAxisSize { + Style::LengthPercentageAuto size; + Style::LengthPercentage min_size, max_size; + Style::Padding padding_a, padding_b; + Style::Margin margin_a, margin_b; + float border_a, border_b; + Style::BoxSizing box_sizing; +}; + +struct ContainingBlock { + ContainerBox* container; + Vector2f size; +}; + +enum class BuildBoxMode { + Block, // Sets edges and size if available, auto width can result in shrink-to-fit width, auto margins are used for alignment. + Inline, // Sets edges, ignores width, height, and auto margins. + UnalignedBlock, // Like block, but auto width returns -1, and auto margins are resolved to zero. +}; + +/** + Layout functions for sizing elements. + + Some procedures based on the CSS 2.1 specification, 'Section 10. Visual formatting model details'. + */ +class LayoutDetails { +public: + /// Generates the box dimensions for an element. + /// @param[out] box The box to be built. + /// @param[in] containing_block The dimensions of the content area of the block containing the element. + /// @param[in] element The element to build the box for. + /// @param[in] box_context The formatting context in which the box is generated. + static void BuildBox(Box& box, Vector2f containing_block, Element* element, BuildBoxMode box_context = BuildBoxMode::Block); + + // Retrieves the minimum and maximum width from an element's computed values. + static void GetMinMaxWidth(float& min_width, float& max_width, const ComputedValues& computed, const Box& box, float containing_block_width); + + // Retrieves the minimum and maximum height from an element's computed values. + static void GetMinMaxHeight(float& min_height, float& max_height, const ComputedValues& computed, const Box& box, float containing_block_height); + + // Retrieves the minimum and maximum height, set to the box's content height if it is definite (>= 0), otherwise retrieves the minimum and maximum + // heights from an element's computed values. + static void GetDefiniteMinMaxHeight(float& min_height, float& max_height, const ComputedValues& computed, const Box& box, + float containing_block_height); + + /// Returns the containing block for a box. + /// @param[in] parent_container The parent container of the current box. + /// @param[in] position The position property of the current box. + /// @return The containing block box and size, possibly indefinite (represented by negative size) along one or both axes. + static ContainingBlock GetContainingBlock(ContainerBox* parent_container, Style::Position position); + + /// Builds margins of a Box, and resolves any auto width or height for non-inline elements. The height may be left unresolved if it depends on the + /// element's children. + /// @param[in,out] box The box to generate. The padding and borders, in addition to any definite content area, must be set on the box already. + /// Auto width and height are specified by negative content size. + /// @param[in] min_size The element's minimum width and height. + /// @param[in] max_size The element's maximum width and height. + /// @param[in] containing_block The size of the containing block. + /// @param[in] element The element the box is being generated for. + /// @param[in] box_context The formatting context in which the box is generated. + /// @param[in] replaced_element True when the element is a replaced element. + static void BuildBoxSizeAndMargins(Box& box, Vector2f min_size, Vector2f max_size, Vector2f containing_block, Element* element, + BuildBoxMode box_context, bool replaced_element); + + /// Formats the element and returns the width of its contents. + static float GetShrinkToFitWidth(Element* element, Vector2f containing_block); + + /// Build computed axis size along the horizontal direction (width and friends). + static ComputedAxisSize BuildComputedHorizontalSize(const ComputedValues& computed); + /// Build computed axis size along the vertical direction (height and friends). + static ComputedAxisSize BuildComputedVerticalSize(const ComputedValues& computed); + /// Resolve edge sizes from a computed axis size. + static void GetEdgeSizes(float& margin_a, float& margin_b, float& padding_border_a, float& padding_border_b, + const ComputedAxisSize& computed_size, float base_value); + + static String GetDebugElementName(Element* element); + + static bool IsScrollContainer(Style::Overflow overflow_x, Style::Overflow overflow_y) + { + return overflow_x != Style::Overflow::Visible || overflow_y != Style::Overflow::Visible; + } + +private: + /// Calculates and returns the content size for replaced elements. + static Vector2f CalculateSizeForReplacedElement(Vector2f specified_content_size, Vector2f min_size, Vector2f max_size, Vector2f intrinsic_size, + float intrinsic_ratio); + + /// Builds the block-specific width and horizontal margins of a Box. + /// @param[in,out] box The box to generate. The padding and borders must be set on the box already. The content area is used instead of the width + /// property, and -1 means auto width. + /// @param[in] computed The computed values of the element the box is being generated for. + /// @param[in] min_width The minimum content width of the element. + /// @param[in] max_width The maximum content width of the element. + /// @param[in] containing_block The size of the containing block. + /// @param[in] element The element the box is being generated for. + /// @param[in] replaced_element True when the element is a replaced element. + /// @param[in] override_shrink_to_fit_width Provide a fixed shrink-to-fit width instead of formatting the element when its properties allow + /// shrinking. + static void BuildBoxWidth(Box& box, const ComputedValues& computed, float min_width, float max_width, Vector2f containing_block, Element* element, + bool replaced_element, float override_shrink_to_fit_width = -1); + /// Builds the block-specific height and vertical margins of a Box. + /// @param[in,out] box The box to generate. The padding and borders must be set on the box already. The content area is used instead of the height + /// property, and -1 means auto height. + /// @param[in] computed The computed values of the element the box is being generated for. + /// @param[in] min_height The minimum content height of the element. + /// @param[in] max_height The maximum content height of the element. + /// @param[in] containing_block_height The height of the containing block. + static void BuildBoxHeight(Box& box, const ComputedValues& computed, float min_height, float max_height, float containing_block_height); +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/LayoutEngine.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/LayoutEngine.cpp new file mode 100644 index 00000000..ebed838e --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/LayoutEngine.cpp @@ -0,0 +1,33 @@ +#include "LayoutEngine.h" +#include "../../../Include/RmlUi/Core/Element.h" +#include "../../../Include/RmlUi/Core/Log.h" +#include "../../../Include/RmlUi/Core/Profiling.h" +#include "ContainerBox.h" +#include "FormattingContext.h" + +namespace Rml { + +void LayoutEngine::FormatElement(Element* element, Vector2f containing_block) +{ + RMLUI_ASSERT(element && containing_block.x >= 0 && containing_block.y >= 0); + + RootBox root(containing_block); + + auto layout_box = FormattingContext::FormatIndependent(&root, element, nullptr, FormattingContextType::Block); + if (!layout_box) + { + Log::Message(Log::LT_ERROR, "Error while formatting element: %s", element->GetAddress().c_str()); + } + + { + RMLUI_ZoneScopedN("ClampScrollOffsetRecursive"); + // The size of the scrollable area might have changed, so clamp the scroll offset to avoid scrolling outside the + // scrollable area. During layouting, we might be changing the scrollable overflow area of the element several + // times, such as after enabling scrollbars. For this reason, we don't clamp the scroll offset during layouting, + // as that could inadvertently clamp it to a temporary size. Now that we know the final layout, including the + // size of each element's scrollable area, we can finally clamp the scroll offset. + element->ClampScrollOffsetRecursive(); + } +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/LayoutEngine.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/LayoutEngine.h new file mode 100644 index 00000000..60853dfe --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/LayoutEngine.h @@ -0,0 +1,19 @@ +#pragma once + +#include "../../../Include/RmlUi/Core/Types.h" + +namespace Rml { + +/** + See the CSS glossary for terms used in the layout engine: + https://www.w3.org/TR/css-display-3/#glossary + */ +class LayoutEngine { +public: + /// Formats the contents for a root-level element, usually a document, or a replaced element with custom formatting. + /// @param[in] element The element to lay out. + /// @param[in] containing_block The size of the containing block. + static void FormatElement(Element* element, Vector2f containing_block); +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/LayoutPools.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/LayoutPools.cpp new file mode 100644 index 00000000..a58d625a --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/LayoutPools.cpp @@ -0,0 +1,76 @@ +#include "LayoutPools.h" +#include "../../../Include/RmlUi/Core/Element.h" +#include "../ControlledLifetimeResource.h" +#include "../Pool.h" +#include "BlockContainer.h" +#include "FloatedBoxSpace.h" +#include "FormattingContext.h" +#include "InlineBox.h" +#include "InlineContainer.h" +#include "LineBox.h" +#include "ReplacedFormattingContext.h" +#include +#include + +namespace Rml { + +template +struct LayoutChunk { + alignas(std::max_align_t) byte buffer[Size]; +}; + +static constexpr size_t ChunkSizeBig = std::max({sizeof(BlockContainer)}); +static constexpr size_t ChunkSizeMedium = + std::max({sizeof(InlineContainer), sizeof(InlineBox), sizeof(RootBox), sizeof(FlexContainer), sizeof(TableWrapper)}); +static constexpr size_t ChunkSizeSmall = + std::max({sizeof(ReplacedBox), sizeof(InlineLevelBox_Text), sizeof(InlineLevelBox_Atomic), sizeof(LineBox), sizeof(FloatedBoxSpace)}); + +struct LayoutPoolsData { + Pool> layout_chunk_pool_big{50, true}; + Pool> layout_chunk_pool_medium{50, true}; + Pool> layout_chunk_pool_small{50, true}; +}; + +static ControlledLifetimeResource layout_pools_data; + +void LayoutPools::Initialize() +{ + layout_pools_data.Initialize(); +} +void LayoutPools::Shutdown() +{ + layout_pools_data.Shutdown(); +} + +void* LayoutPools::AllocateLayoutChunk(size_t size) +{ + static_assert(ChunkSizeBig > ChunkSizeMedium && ChunkSizeMedium > ChunkSizeSmall, "The following assumes a strict ordering of the chunk sizes."); + + // Note: If any change is made here, make sure a corresponding change is applied to the deallocation procedure below. + if (size <= ChunkSizeSmall) + return layout_pools_data->layout_chunk_pool_small.AllocateAndConstruct(); + else if (size <= ChunkSizeMedium) + return layout_pools_data->layout_chunk_pool_medium.AllocateAndConstruct(); + else if (size <= ChunkSizeBig) + return layout_pools_data->layout_chunk_pool_big.AllocateAndConstruct(); + + RMLUI_ERROR; + return nullptr; +} + +void LayoutPools::DeallocateLayoutChunk(void* chunk, size_t size) +{ + // Note: If any change is made here, make sure a corresponding change is applied to the allocation procedure above. + if (size <= ChunkSizeSmall) + layout_pools_data->layout_chunk_pool_small.DestroyAndDeallocate((LayoutChunk*)chunk); + else if (size <= ChunkSizeMedium) + layout_pools_data->layout_chunk_pool_medium.DestroyAndDeallocate((LayoutChunk*)chunk); + else if (size <= ChunkSizeBig) + layout_pools_data->layout_chunk_pool_big.DestroyAndDeallocate((LayoutChunk*)chunk); + else + { + RMLUI_ERROR; + } +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/LayoutPools.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/LayoutPools.h new file mode 100644 index 00000000..59da6794 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/LayoutPools.h @@ -0,0 +1,17 @@ +#pragma once + +#include "../../../Include/RmlUi/Core/Types.h" + +namespace Rml { + +namespace LayoutPools { + + void Initialize(); + void Shutdown(); + + void* AllocateLayoutChunk(size_t size); + void DeallocateLayoutChunk(void* chunk, size_t size); + +} // namespace LayoutPools + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/LineBox.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/LineBox.cpp new file mode 100644 index 00000000..722575ad --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/LineBox.cpp @@ -0,0 +1,415 @@ +#include "LineBox.h" +#include "../../../Include/RmlUi/Core/Element.h" +#include "../../../Include/RmlUi/Core/StyleTypes.h" +#include "InlineBox.h" +#include "InlineLevelBox.h" +#include "LayoutPools.h" +#include +#include + +namespace Rml { + +LineBox::~LineBox() {} + +void LineBox::SetLineBox(Vector2f _line_position, float _line_width, float _line_minimum_height) +{ + line_position = _line_position; + line_width = _line_width; + line_minimum_height = _line_minimum_height; +} + +bool LineBox::AddBox(InlineLevelBox* box, InlineLayoutMode layout_mode, LayoutOverflowHandle& inout_overflow_handle) +{ + RMLUI_ASSERT(!is_closed); + const bool first_box = !HasContent(); + + // Find the spacing this element must leave on its right side, to account for its parent inline boxes to be closed later. + float open_spacing_right = 0.f; + ForAllOpenFragments([&](const Fragment& fragment) { open_spacing_right += fragment.box->GetSpacingRight(); }); + + const float box_placement_cursor = box_cursor + open_spacing_left; + + float available_width = FLT_MAX; + if (layout_mode != InlineLayoutMode::Nowrap) + { + available_width = Math::RoundUp(line_width - box_placement_cursor); + if (available_width < 0.f) + { + if (layout_mode == InlineLayoutMode::WrapAny) + return true; + else + available_width = 0.f; + } + } + + FragmentConstructor constructor = box->CreateFragment(layout_mode, available_width, open_spacing_right, first_box, inout_overflow_handle); + + if (constructor.type == FragmentType::Invalid) + { + // Could not place fragment on this line, try again on a new line. + RMLUI_ASSERT(layout_mode == InlineLayoutMode::WrapAny); + return true; + } + + const FragmentIndex fragment_index = (FragmentIndex)fragments.size(); + + fragments.push_back(Fragment{box, constructor, box->GetVerticalAlign(), box_placement_cursor, open_fragments_leaf}); + fragments.back().aligned_subtree_root = DetermineAlignedSubtreeRoot(fragment_index); + + inout_overflow_handle = {}; + bool continue_on_new_line = false; + + switch (constructor.type) + { + case FragmentType::InlineBox: + { + RMLUI_ASSERT(constructor.layout_width < 0.f); + RMLUI_ASSERT(rmlui_static_cast(box)); + + open_fragments_leaf = fragment_index; + open_spacing_left += box->GetSpacingLeft(); + } + break; + case FragmentType::SizedBox: + case FragmentType::TextRun: + { + RMLUI_ASSERT(constructor.layout_width >= 0.f); + + box_cursor = box_placement_cursor + constructor.layout_width; + open_spacing_left = 0.f; + + if (constructor.overflow_handle) + { + continue_on_new_line = true; + inout_overflow_handle = constructor.overflow_handle; + } + + // Mark open fragments as having content so we later know whether we should split or move them in case of overflow. + ForAllOpenFragments([&](Fragment& fragment) { fragment.has_content = true; }); + has_content = true; + } + break; + case FragmentType::Invalid: + RMLUI_ERROR; // Handled above; + break; + } + + return continue_on_new_line; +} + +void LineBox::CloseFragment(Fragment& open_fragment, float right_inner_edge_position) +{ + RMLUI_ASSERT(open_fragment.type == FragmentType::InlineBox); + + open_fragment.children_end_index = (int)fragments.size(); + const float spacing_left = (open_fragment.split_left ? 0.f : open_fragment.box->GetSpacingLeft()); + open_fragment.layout_width = Math::Max(right_inner_edge_position - open_fragment.position.x - spacing_left, 0.f); +} + +void LineBox::CloseInlineBox(InlineBox* inline_box) +{ + if (open_fragments_leaf == RootFragmentIndex || fragments[open_fragments_leaf].box != inline_box) + { + RMLUI_ERRORMSG("Inline box open/close mismatch."); + return; + } + + box_cursor += open_spacing_left; + open_spacing_left = 0.f; + + Fragment& fragment = fragments[open_fragments_leaf]; + CloseFragment(fragment, box_cursor); + box_cursor += fragment.box->GetSpacingRight(); + + open_fragments_leaf = fragment.parent; +} + +UniquePtr LineBox::SplitLine(bool split_all_open_boxes) +{ + if (open_fragments_leaf == RootFragmentIndex) + return nullptr; + + int num_open_fragments = 0; + ForAllOpenFragments([&](const Fragment& /*fragment*/) { num_open_fragments += 1; }); + + // Move the box cursor to account for any newly opened inline boxes to be closed. + box_cursor += open_spacing_left; + open_spacing_left = 0.f; + + // Make a new line with the open fragments. + auto new_line = MakeUnique(); + new_line->fragments.resize(num_open_fragments); + + // Copy all open fragments to the next line. Do it in reverse order of iteration, since we iterate from back to front. + FragmentIndex new_index = num_open_fragments; + ForAllOpenFragments([&](Fragment& old_fragment) { + new_index -= 1; + RMLUI_ASSERT((size_t)new_index < new_line->fragments.size() && old_fragment.children_end_index == 0); + + // Copy the old fragment. + Fragment& new_fragment = new_line->fragments[new_index]; + new_fragment = old_fragment; + new_fragment.position.x = 0.f; + new_fragment.parent = new_index - 1; + new_fragment.aligned_subtree_root = new_line->DetermineAlignedSubtreeRoot(new_index); + + // Only fragments with content placed on the previous line is split, otherwise the fragment is moved down. + if (new_fragment.has_content || split_all_open_boxes) + { + new_fragment.split_left = true; + new_fragment.has_content = false; + + CloseFragment(old_fragment, box_cursor); + old_fragment.split_right = true; + } + else + { + const float spacing_left = (new_fragment.split_left ? 0.f : new_fragment.box->GetSpacingLeft()); + new_line->open_spacing_left += spacing_left; + + // The old fragment is not closed here, which ensures that it will not be placed/submitted on the previous + // line. Backtrack the box cursor since this fragment is moved down to the next line. + box_cursor -= spacing_left; + } + }); + + new_line->open_fragments_leaf = (int)new_line->fragments.size() - 1; + open_fragments_leaf = RootFragmentIndex; + +#ifdef RMLUI_DEBUG + // Verify integrity of the fragment tree after split. + for (int i = 0; i < (int)new_line->fragments.size(); i++) + { + const Fragment& fragment = new_line->fragments[i]; + RMLUI_ASSERT(fragment.type == FragmentType::InlineBox); + RMLUI_ASSERT(fragment.parent < i); + RMLUI_ASSERT(fragment.parent == i - 1); + RMLUI_ASSERT(fragment.parent == RootFragmentIndex || new_line->fragments[fragment.parent].type == FragmentType::InlineBox); + RMLUI_ASSERT( + fragment.aligned_subtree_root == RootFragmentIndex || new_line->IsAlignedSubtreeRoot(new_line->fragments[fragment.aligned_subtree_root])); + RMLUI_ASSERT(fragment.children_end_index == 0); + } +#endif + + return new_line; +} + +UniquePtr LineBox::DetermineVerticalPositioning(const InlineBoxRoot* root_inline_box, bool split_all_open_boxes, float& out_height_of_line) +{ + RMLUI_ASSERT(!is_closed && !is_vertically_positioned); + + UniquePtr new_line_box = SplitLine(split_all_open_boxes); + + RMLUI_ASSERT(open_fragments_leaf == RootFragmentIndex); // Ensure all open fragments are either closed or split. + + // Vertical alignment and sizing. + // + // Aligned subtree CSS definition: "The aligned subtree of an inline element contains that element and the aligned + // subtrees of all children inline elements whose computed vertical-align value is not 'top' or 'bottom'." + // + // We have already determined each box's offset relative to its parent baseline, and its layout size above and below + // its baseline. Now, for each aligned subtree, place all fragments belonging to that subtree relative to the + // subtree root baseline. Simultaneously, consider each fragment and keep track of the maximum height above root + // baseline (max_ascent) and maximum depth below root baseline (max_descent). Their sum is the height of that + // aligned subtree. + // + // Next, treat the root inline box like just another aligned subtree. Then the line box height is first determined + // by the height of that subtree. Other aligned subtrees might push out that size to make them fit. After the line + // box size is determined, position each aligned subtree according to its vertical-align, and then position each + // fragment relative to the aligned subtree they belong to. + float max_ascent = root_inline_box->GetHeightAboveBaseline(); + float max_descent = root_inline_box->GetDepthBelowBaseline(); + + { + const int subtree_root_index = RootFragmentIndex; + const int children_end_index = (int)fragments.size(); + VerticallyAlignSubtree(subtree_root_index, children_end_index, max_ascent, max_descent); + } + + // Find all the aligned subtrees, and vertically align each of them independently. + for (int i = 0; i < (int)fragments.size(); i++) + { + Fragment& fragment = fragments[i]; + if (IsAlignedSubtreeRoot(fragment)) + { + fragment.max_ascent = fragment.box->GetHeightAboveBaseline(); + fragment.max_descent = fragment.box->GetDepthBelowBaseline(); + + if (fragment.type == FragmentType::InlineBox) + { + const int subtree_root_index = i; + VerticallyAlignSubtree(subtree_root_index, fragment.children_end_index, fragment.max_ascent, fragment.max_descent); + } + + const float subtree_height = fragment.max_ascent + fragment.max_descent; + + // Increase the line box size to fit all line-relative aligned fragments. + switch (fragment.vertical_align) + { + case VerticalAlignType::Top: max_descent = Math::Max(max_descent, subtree_height - max_ascent); break; + case VerticalAlignType::Bottom: max_ascent = Math::Max(max_ascent, subtree_height - max_descent); break; + case VerticalAlignType::Center: + { + // Distribute the subtree's height equally to the ascent and descent. + const float distribute_height = 0.5f * (subtree_height - (max_ascent + max_descent)); + if (distribute_height > 0.f) + { + max_ascent += distribute_height; + max_descent += distribute_height; + } + } + break; + default: RMLUI_ERROR; break; + } + } + } + + // Size the line. + out_height_of_line = max_ascent + max_descent; + total_height_above_baseline = max_ascent; + + // Now that the line is sized we can set the vertical position of the fragments. + for (Fragment& fragment : fragments) + { + switch (fragment.vertical_align) + { + case VerticalAlignType::Top: fragment.position.y = fragment.max_ascent; break; + case VerticalAlignType::Bottom: fragment.position.y = out_height_of_line - fragment.max_descent; break; + case VerticalAlignType::Center: fragment.position.y = 0.5f * (fragment.max_ascent - fragment.max_descent + out_height_of_line); break; + default: + { + RMLUI_ASSERT(!IsAlignedSubtreeRoot(fragment)); + const float aligned_subtree_baseline = + (fragment.aligned_subtree_root < 0 ? max_ascent : fragments[fragment.aligned_subtree_root].position.y); + fragment.position.y = aligned_subtree_baseline + fragment.baseline_offset; + } + break; + } + } + + is_vertically_positioned = true; + + return new_line_box; +} + +void LineBox::Close(Element* offset_parent, Vector2f offset_parent_position, Style::TextAlign text_align) +{ + RMLUI_ASSERT(is_vertically_positioned && !is_closed); + + // Horizontal alignment using available space on our line. + if (box_cursor < line_width) + { + switch (text_align) + { + case Style::TextAlign::Center: offset_horizontal_alignment = (line_width - box_cursor) * 0.5f; break; + case Style::TextAlign::Right: offset_horizontal_alignment = (line_width - box_cursor); break; + case Style::TextAlign::Left: // Already left-aligned. + case Style::TextAlign::Justify: // Justification occurs at the text level. + break; + } + } + + // Position and size all inline-level boxes, place geometry boxes. + for (const Fragment& fragment : fragments) + { + // Skip inline boxes which have not been closed (moved down to next line). + if (fragment.type == FragmentType::InlineBox && fragment.children_end_index == 0) + continue; + + RMLUI_ASSERT(fragment.layout_width >= 0.f); + + const PlacedFragment placed_fragment = { + offset_parent, + fragment.fragment_handle, + line_position - offset_parent_position + fragment.position + Vector2f(offset_horizontal_alignment, 0.f), + fragment.layout_width, + fragment.split_left, + fragment.split_right, + }; + fragment.box->Submit(placed_fragment); + } + + is_closed = true; +} + +void LineBox::VerticallyAlignSubtree(const int subtree_root_index, const int children_end_index, float& max_ascent, float& max_descent) +{ + const int children_begin_index = subtree_root_index + 1; + + // Iterate all descendant fragments which belong to our aligned subtree. + for (int i = children_begin_index; i < children_end_index; i++) + { + Fragment& fragment = fragments[i]; + if (fragment.aligned_subtree_root != subtree_root_index) + continue; + + // Position the baseline of fragments relative to their subtree root. + const float parent_absolute_baseline = (fragment.parent < 0 ? 0.f : fragments[fragment.parent].baseline_offset); + fragment.baseline_offset = parent_absolute_baseline + fragment.box->GetVerticalOffsetFromParent(); + + // Expand this aligned subtree's height based on the height contributions of its descendants. + if (fragment.type != FragmentType::TextRun) + { + max_ascent = Math::Max(max_ascent, fragment.box->GetHeightAboveBaseline() - fragment.baseline_offset); + max_descent = Math::Max(max_descent, fragment.box->GetDepthBelowBaseline() + fragment.baseline_offset); + } + } +} + +InlineBox* LineBox::GetOpenInlineBox() +{ + if (open_fragments_leaf == RootFragmentIndex) + return nullptr; + + return rmlui_static_cast(fragments[open_fragments_leaf].box); +} + +bool LineBox::CanCollapseLine() const +{ + // Roughly, collapse lines with only empty text fragments or inline boxes not taking up any width or spacing. + for (const Fragment& fragment : fragments) + { + if (fragment.layout_width > 0.f) + return false; + else if (fragment.type == FragmentType::SizedBox) + return false; + else if (fragment.type == FragmentType::InlineBox && fragment.children_end_index > 0) + { + const bool any_spacing_left = (!fragment.split_left && fragment.box->GetSpacingLeft() != 0.f); + const bool any_spacing_right = (!fragment.split_right && fragment.box->GetSpacingRight() != 0.f); + if (any_spacing_left || any_spacing_right) + return false; + } + } + return true; +} + +float LineBox::GetExtentRight() const +{ + RMLUI_ASSERT(is_closed); + return box_cursor + offset_horizontal_alignment; +} + +float LineBox::GetBaseline() const +{ + RMLUI_ASSERT(is_closed); + return total_height_above_baseline; +} + +String LineBox::DebugDumpTree(int depth) const +{ + const String value = String(depth * 2, ' ') + "LineBox (" + ToString(fragments.size()) + " fragment" + (fragments.size() == 1 ? "" : "s") + ")\n"; + return value; +} + +void* LineBox::operator new(size_t size) +{ + return LayoutPools::AllocateLayoutChunk(size); +} + +void LineBox::operator delete(void* chunk, size_t size) +{ + LayoutPools::DeallocateLayoutChunk(chunk, size); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/LineBox.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/LineBox.h new file mode 100644 index 00000000..7eed0cdd --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/LineBox.h @@ -0,0 +1,185 @@ +#pragma once + +#include "../../../Include/RmlUi/Core/StyleTypes.h" +#include "InlineTypes.h" + +namespace Rml { + +class InlineBox; +class InlineBoxRoot; +class InlineLevelBox; + +/* + Horizontally places fragments generated from inline-level boxes. + + Inline boxes can nest other inline-level boxes, thereby creating a tree of inline-level boxes. This tree structure + needs to be considered for the generated fragments as well, since their size and position depend on this tree + structure. + + Note that a single inline-level box can generate multiple fragments, possibly placed in different line boxes. +*/ +class LineBox final { +public: + LineBox() = default; + ~LineBox(); + + // Set the line box position and dimensions. + void SetLineBox(Vector2f line_position, float line_width, float line_minimum_height); + + /// Generates a fragment from a box and places it on this line, if possible. + /// @param[in] box The inline-level box to be placed. + /// @param[in] layout_mode The inline layout mode, which affects the fragment generation. + /// @param[in,out] inout_overflow_handle A handle to resume fragment generation from a partially placed box. + /// @return True if the box should be placed again on a new line, either because the box could not fit or there is more content to be placed. + bool AddBox(InlineLevelBox* box, InlineLayoutMode layout_mode, LayoutOverflowHandle& inout_overflow_handle); + + /// Closes the open inline box. + /// @param[in] inline_box The inline box to be closed. Should match the currently open box, strictly used for verification. + /// @note Only inline boxes need to be closed, not other inline-level boxes since they do not contain child boxes. + void CloseInlineBox(InlineBox* inline_box); + + /// Vertically positions each fragment and sizes the line, after splitting any open inline boxes to be placed on a new line. + /// @param[in] root_inline_box The root inline box of our inline container. + /// @param[in] split_all_open_boxes Split all open inline boxes, even if they have no content. + /// @param[out] out_height_of_line Resulting height of line. This can be different from the element's computed line-height property. + /// @return The next line if any open fragments had to be split or wrapped down. + UniquePtr DetermineVerticalPositioning(const InlineBoxRoot* root_inline_box, bool split_all_open_boxes, float& out_height_of_line); + + // Closes the line and submits all fragments. Thereby positioning, sizing, and placing their corresponding boxes. + // @note The line must have been vertically positioned before closing. + void Close(Element* offset_parent, Vector2f offset_parent_position, Style::TextAlign text_align); + + float GetBoxCursor() const { return box_cursor; } + Vector2f GetPosition() const { return line_position; } + float GetLineWidth() const { return line_width; } + float GetLineMinimumHeight() const { return line_minimum_height; } + + InlineBox* GetOpenInlineBox(); + + bool IsClosed() const { return is_closed; } + bool HasContent() const { return has_content; } + + // Returns true if this line should be collapsed, such that it takes up no height in the inline container. + bool CanCollapseLine() const; + + // Returns the width of the contents of the line, relative to the left edge of the line box. Includes spacing due to horizontal alignment. + // @note Only available after line has been closed. + float GetExtentRight() const; + + // Returns the baseline position, relative to the top of the line box. + // @note Only available after line has been closed. + float GetBaseline() const; + + String DebugDumpTree(int depth) const; + + void* operator new(size_t size); + void operator delete(void* chunk, size_t size); + +private: + using FragmentIndex = int; + using VerticalAlignType = Style::VerticalAlign::Type; + + static constexpr FragmentIndex RootFragmentIndex = -1; + + struct Fragment { + Fragment() = default; + Fragment(InlineLevelBox* box, FragmentConstructor constructor, VerticalAlignType vertical_align, float position_x, FragmentIndex parent) : + box(box), type(constructor.type), fragment_handle(constructor.fragment_handle), vertical_align(vertical_align), position(position_x, 0.f), + layout_width(constructor.layout_width), parent(parent) + {} + + InlineLevelBox* box = nullptr; + FragmentType type = FragmentType::Invalid; + LayoutFragmentHandle fragment_handle = {}; + VerticalAlignType vertical_align = {}; + + // Layout state. + Vector2f position; // Position relative to start of the line, disregarding floats, (x: outer-left edge, y: baseline). + float layout_width = 0.f; // Inner width for inline boxes, otherwise outer width. + + // Vertical alignment state. + FragmentIndex parent = RootFragmentIndex; + FragmentIndex aligned_subtree_root = RootFragmentIndex; // Index of the aligned subtree the fragment belongs to. + float baseline_offset = 0.f; // Vertical offset from aligned subtree root baseline to our baseline. + + // For inline boxes. + bool split_left = false; + bool split_right = false; + bool has_content = false; + FragmentIndex children_end_index = 0; // One-past-last-child of this box, as index into fragment list. + + // For aligned subtree root. + float max_ascent = 0.f; + float max_descent = 0.f; + }; + using FragmentList = Vector; + + // Place an open fragment. + void CloseFragment(Fragment& open_fragment, float right_inner_edge_position); + + // Splits the line, returning a new line if there are any open fragments. + UniquePtr SplitLine(bool split_all_open_boxes); + + // Vertically align all descendants of the subtree. Returns the ascent of the top-most box, and descent of the bottom-most box. + void VerticallyAlignSubtree(int subtree_root_index, int children_end_index, float& max_ascent, float& max_descent); + + // Returns true if the fragment establishes an aligned subtree. + static bool IsAlignedSubtreeRoot(const Fragment& fragment) + { + return (fragment.vertical_align == VerticalAlignType::Top || fragment.vertical_align == VerticalAlignType::Center || + fragment.vertical_align == VerticalAlignType::Bottom); + } + // Returns the aligned subtree root for a given fragment, based on its ancestors. + FragmentIndex DetermineAlignedSubtreeRoot(FragmentIndex index) const + { + while (index != RootFragmentIndex) + { + const Fragment& fragment = fragments[index]; + if (IsAlignedSubtreeRoot(fragment)) + return index; + index = fragment.parent; + } + return index; + } + + template + void ForAllOpenFragments(Func&& func) + { + FragmentIndex index = open_fragments_leaf; + while (index != RootFragmentIndex) + { + Fragment& fragment = fragments[index]; + index = fragment.parent; + func(fragment); + } + } + + // Position of the line, relative to our block formatting context root. + Vector2f line_position; + // Available space for the line. Based on our parent box content width, possibly shrinked due to floating boxes. + float line_width = 0.f; + // Lower-bound estimate for the line box height. + float line_minimum_height = 0.f; + + // The horizontal cursor. This is the outer-right position of the last placed fragment. + float box_cursor = 0.f; + // The contribution of opened inline boxes to the placement of the next fragment, due to their left edges (margin-border-padding). + float open_spacing_left = 0.f; + + // List of fragments in this line box. + FragmentList fragments; + + // Index of the last open fragment. The list of open fragments is this leaf and all of its ancestors up to the root. + FragmentIndex open_fragments_leaf = RootFragmentIndex; + + bool has_content = false; + bool is_vertically_positioned = false; + bool is_closed = false; + + // Content offset due to space distribution from 'text-align'. Available after close. + float offset_horizontal_alignment = 0.f; + // The line box's height above baseline. Available after close. + float total_height_above_baseline = 0.f; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/ReplacedFormattingContext.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/ReplacedFormattingContext.cpp new file mode 100644 index 00000000..ca958e0b --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/ReplacedFormattingContext.cpp @@ -0,0 +1,51 @@ +#include "ReplacedFormattingContext.h" +#include "../../../Include/RmlUi/Core/ComputedValues.h" +#include "../../../Include/RmlUi/Core/Element.h" +#include "BlockFormattingContext.h" +#include "ContainerBox.h" +#include "LayoutDetails.h" + +namespace Rml { + +UniquePtr ReplacedFormattingContext::Format(ContainerBox* parent_container, Element* element, const Box* override_initial_box) +{ + RMLUI_ASSERT(element->IsReplaced()); + + // Replaced elements provide their own rendering, we just set their box here and notify them that the element has been sized. + auto replaced_box = MakeUnique(element); + Box& box = replaced_box->GetBox(); + if (override_initial_box) + box = *override_initial_box; + else + { + const Vector2f containing_block = LayoutDetails::GetContainingBlock(parent_container, element->GetPosition()).size; + LayoutDetails::BuildBox(box, containing_block, element); + } + + // Submit the box and notify the element. + replaced_box->Close(); + + // Usually, replaced elements add children to the hidden DOM. If we happen to have any normal DOM children, e.g. + // added by the user, we format them using normal block formatting rules. Since replaced elements provide their + // own rendering, this could cause conflicting or strange layout results, and is done at the user's own risk. + if (element->HasChildNodes()) + { + RootBox root(box); + BlockFormattingContext::Format(&root, element, &box); + } + + return replaced_box; +} + +void ReplacedBox::Close() +{ + element->SetBox(box); + element->OnLayout(); +} + +String ReplacedBox::DebugDumpTree(int depth) const +{ + return String(depth * 2, ' ') + "ReplacedBox"; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/ReplacedFormattingContext.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/ReplacedFormattingContext.h new file mode 100644 index 00000000..66d9715f --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/ReplacedFormattingContext.h @@ -0,0 +1,35 @@ +#pragma once + +#include "../../../Include/RmlUi/Core/Types.h" +#include "FormattingContext.h" +#include "LayoutBox.h" + +namespace Rml { + +/* + A formatting context that handles replaced elements. + + Replaced elements normally take care of their own layouting, so this is only responsible for setting thei box + dimensions and notifying the element. +*/ +class ReplacedFormattingContext final : public FormattingContext { +public: + static UniquePtr Format(ContainerBox* parent_container, Element* element, const Box* override_initial_box); +}; + +class ReplacedBox : public LayoutBox { +public: + ReplacedBox(Element* element) : LayoutBox(Type::Replaced), element(element) {} + + void Close(); + Box& GetBox() { return box; } + + const Box* GetIfBox() const override { return &box; } + String DebugDumpTree(int depth) const override; + +private: + Element* element; + Box box; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/TableFormattingContext.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/TableFormattingContext.cpp new file mode 100644 index 00000000..521dde5c --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/TableFormattingContext.cpp @@ -0,0 +1,466 @@ +#include "TableFormattingContext.h" +#include "../../../Include/RmlUi/Core/ComputedValues.h" +#include "../../../Include/RmlUi/Core/Element.h" +#include "../../../Include/RmlUi/Core/Types.h" +#include "ContainerBox.h" +#include "LayoutDetails.h" +#include "LayoutEngine.h" +#include "TableFormattingDetails.h" +#include +#include + +namespace Rml { + +UniquePtr TableFormattingContext::Format(ContainerBox* parent_container, Element* element_table, const Box* override_initial_box) +{ + auto table_wrapper_box = MakeUnique(element_table, parent_container); + if (table_wrapper_box->IsScrollContainer()) + { + Log::Message(Log::LT_WARNING, "Table elements can only have 'overflow' property values of 'visible'. Table will not be formatted: %s.", + element_table->GetAddress().c_str()); + return table_wrapper_box; + } + + const Vector2f containing_block = LayoutDetails::GetContainingBlock(parent_container, element_table->GetPosition()).size; + RMLUI_ASSERT(containing_block.x >= 0.f); + const ComputedValues& computed_table = element_table->GetComputedValues(); + + // Build the initial box as specified by the table's style, as if it was a normal block element. + Box& box = table_wrapper_box->GetBox(); + if (override_initial_box) + box = *override_initial_box; + else + LayoutDetails::BuildBox(box, containing_block, element_table, BuildBoxMode::Block); + + TableFormattingContext context; + context.element_table = element_table; + context.table_wrapper_box = table_wrapper_box.get(); + + LayoutDetails::GetMinMaxWidth(context.table_min_size.x, context.table_max_size.x, computed_table, box, containing_block.x); + LayoutDetails::GetMinMaxHeight(context.table_min_size.y, context.table_max_size.y, computed_table, box, containing_block.y); + + // Format the table, this may adjust the box content size. + const Vector2f initial_content_size = box.GetSize(); + context.table_auto_height = (initial_content_size.y < 0.0f); + + context.table_content_offset = box.GetPosition(); + context.table_initial_content_size = Vector2f(initial_content_size.x, Math::Max(0.0f, initial_content_size.y)); + + // When width or height is set, they act as minimum width or height, just as in CSS. + if (computed_table.width().type != Style::Width::Auto) + context.table_min_size.x = Math::Max(context.table_min_size.x, context.table_initial_content_size.x); + if (computed_table.height().type != Style::Height::Auto) + context.table_min_size.y = Math::Max(context.table_min_size.y, context.table_initial_content_size.y); + + context.table_gap = Vector2f(ResolveValue(computed_table.column_gap(), context.table_initial_content_size.x), + ResolveValue(computed_table.row_gap(), context.table_initial_content_size.y)); + + context.grid.Build(element_table, *table_wrapper_box); + + Vector2f table_content_size, table_overflow_size; + float table_baseline = 0.f; + + // Format the table and its children. + context.FormatTable(table_content_size, table_overflow_size, table_baseline); + + RMLUI_ASSERT(table_content_size.y >= 0); + + // Update the box size based on the new table size. + box.SetContent(table_content_size); + + if (table_content_size != initial_content_size) + { + // Perform this step to re-evaluate any auto margins. + LayoutDetails::BuildBoxSizeAndMargins(box, context.table_min_size, context.table_max_size, containing_block, element_table, + BuildBoxMode::Block, true); + } + + // Change the table baseline coordinates to the element baseline, which is defined as the distance from the element's bottom margin edge. + const float element_baseline = + box.GetSizeAcross(BoxDirection::Vertical, BoxArea::Border) + box.GetEdge(BoxArea::Margin, BoxEdge::Bottom) - table_baseline; + + table_wrapper_box->Close(table_overflow_size, box, element_baseline); + + return table_wrapper_box; +} + +void TableFormattingContext::FormatTable(Vector2f& table_content_size, Vector2f& table_overflow_size, float& table_baseline) const +{ + // Defines the boxes for all columns in this table, one entry per table column (spanning columns will add multiple entries). + TrackBoxList columns; + // Defines the boxes for all rows in this table, one entry per table row. + TrackBoxList rows; + // Defines the boxes for all cells in this table. + BoxList cells; + + DetermineColumnWidths(columns, table_content_size.x); + + InitializeCellBoxes(cells, columns); + + DetermineRowHeights(rows, cells, table_content_size.y); + + FormatRows(rows, table_content_size.x); + + FormatColumns(columns, table_content_size.y); + + FormatCells(cells, table_overflow_size, rows, columns, table_baseline); +} + +void TableFormattingContext::DetermineColumnWidths(TrackBoxList& columns, float& table_content_width) const +{ + // The column widths are determined entirely by any elements preceding the first row, and elements in the first row. + // If has a fixed width, that is used. Otherwise, if has a fixed width, that is used. Otherwise the column is 'flexible' width. + // All flexible widths are then sized to evenly fill the width of the table. + + // Both and can have border/padding which extend beyond the size of and , respectively. + // Margins for , , are merged to produce a single left/right margin for each column, located outside . + + TrackMetricList column_metrics(grid.columns.size()); + TracksSizing sizing(column_metrics, table_initial_content_size.x, table_gap.x); + + // Use the and elements for initializing the respective columns. + for (int i = 0; i < (int)column_metrics.size(); i++) + { + if (Element* element_group = grid.columns[i].element_group) + { + const ComputedAxisSize computed = LayoutDetails::BuildComputedHorizontalSize(element_group->GetComputedValues()); + const int span = grid.columns[i].group_span; + + sizing.ApplyGroupElement(i, span, computed); + } + + if (Element* element_column = grid.columns[i].element_column) + { + const ComputedAxisSize computed = LayoutDetails::BuildComputedHorizontalSize(element_column->GetComputedValues()); + const int span = grid.columns[i].column_span; + + sizing.ApplyTrackElement(i, span, computed); + } + } + + // Next, walk through the cells in the first table row, use their size if the column is still auto-sized. + for (int i = 0; i < (int)grid.columns.size(); i++) + { + if (Element* element_cell = grid.columns[i].element_cell) + { + const ComputedAxisSize computed = LayoutDetails::BuildComputedHorizontalSize(element_cell->GetComputedValues()); + const int colspan = grid.columns[i].cell_span; + + sizing.ApplyCellElement(i, colspan, computed); + } + } + + // Convert any auto widths to flexible width. + for (TrackMetric& metric : column_metrics) + { + if (metric.sizing_mode == TrackSizingMode::Auto) + { + metric.sizing_mode = TrackSizingMode::Flexible; + metric.fixed_size = 0.0f; + metric.flex_size = 1.0f; + } + } + + // Now all widths should be either fixed or flexible, resolve all flexible widths to fixed. + sizing.ResolveFlexibleSize(); + + // Generate the column results based on the metrics. + const float columns_full_width = BuildColumnBoxes(columns, column_metrics, grid.columns, table_gap.x); + + // Adjust the table content width based on the accumulated column widths and spacing. + table_content_width = Math::Clamp(columns_full_width, table_min_size.x, table_max_size.x); +} + +void TableFormattingContext::InitializeCellBoxes(BoxList& cells, const TrackBoxList& columns) const +{ + // Requires that column boxes are already generated. + RMLUI_ASSERT(columns.size() == grid.columns.size()); + + cells.resize(grid.cells.size()); + + for (int i = 0; i < (int)cells.size(); i++) + { + Box& box = cells[i]; + + // Determine the cell's box for formatting later, we may get an indefinite (-1) vertical content size. + LayoutDetails::BuildBox(box, table_initial_content_size, grid.cells[i].element_cell, BuildBoxMode::UnalignedBlock); + + // Determine the cell's content width. Include any spanning columns in the cell width. + const float cell_border_width = GetSpanningCellBorderSize(columns, grid.cells[i].column_begin, grid.cells[i].column_last); + const float content_width = + Math::Max(0.0f, cell_border_width - box.GetSizeAcross(BoxDirection::Horizontal, BoxArea::Border, BoxArea::Padding)); + box.SetContent(Vector2f(content_width, box.GetSize().y)); + } +} + +void TableFormattingContext::DetermineRowHeights(TrackBoxList& rows, BoxList& cells, float& table_content_height) const +{ + /* + The table height algorithm works similar to the table width algorithm. The major difference is that 'auto' row height + will use the height of the largest formatted cell in the row. + + Table row height: + auto: Height of largest cell in row. + length: Fixed size. + percentage < 100%: Fixed size, resolved against table initial height. + percentage >= 100%: Flexible size. + + Table height: + auto: Height is sum of all rows. + length/percentage: Fixed minimum size. If row height sum is larger, increase table size. If row sum is smaller, try to increase + row heights, but respect max-heights. If table is still larger than row-sum, leave empty space. + */ + + // Requires that cell boxes have been initialized. + RMLUI_ASSERT(cells.size() == grid.cells.size()); + + TrackMetricList row_metrics(grid.rows.size()); + TracksSizing sizing(row_metrics, table_initial_content_size.y, table_gap.y); + bool percentage_size_used = false; + + // First look for any and elements preceding any elements, use them for initializing the respective columns. + for (int i = 0; i < (int)grid.rows.size(); i++) + { + if (Element* element_group = grid.rows[i].element_group) + { + // The padding/border/margin of column groups are used, but their widths are ignored. + const ComputedAxisSize computed = LayoutDetails::BuildComputedVerticalSize(element_group->GetComputedValues()); + const int span = grid.rows[i].group_span; + + sizing.ApplyGroupElement(i, span, computed); + } + + if (Element* element_row = grid.rows[i].element_row) + { + // The padding/border/margin and widths of columns are used. + const ComputedAxisSize computed = LayoutDetails::BuildComputedVerticalSize(element_row->GetComputedValues()); + + if (computed.size.type == Style::LengthPercentageAuto::Percentage) + percentage_size_used = true; + + sizing.ApplyTrackElement(i, 1, computed); + } + } + + if (table_auto_height && percentage_size_used) + { + Log::Message(Log::LT_WARNING, + "Table has one or more rows that use percentages for height. However, initial table height is undefined, thus " + "these rows will become flattened. Set a fixed height on the table, or use fixed or 'auto' row heights. In element: %s.", + element_table->GetAddress().c_str()); + } + + // Next, find the height of rows that use auto height. + // Auto height rows set their height according to the largest formatted cell size. This differs from the column width algorithm. + for (int i = 0; i < (int)row_metrics.size(); i++) + { + TrackMetric& row_metric = row_metrics[i]; + + if (row_metric.sizing_mode == TrackSizingMode::Auto) + { + struct CellLastRowComp { + bool operator()(const TableGrid::Cell& cell, int i) const { return cell.row_last < i; } + bool operator()(int i, const TableGrid::Cell& cell) const { return i < cell.row_last; } + }; + + // Determine which cells end at this row. + const auto it_pair = std::equal_range(grid.cells.begin(), grid.cells.end(), i, CellLastRowComp{}); + const int cell_begin = int(it_pair.first - grid.cells.begin()); + const int cell_end = int(it_pair.second - grid.cells.begin()); + + for (int cell_index = cell_begin; cell_index < cell_end; cell_index++) + { + const TableGrid::Cell& grid_cell = grid.cells[cell_index]; + Element* element_cell = grid_cell.element_cell; + Box& box = cells[cell_index]; + + // If both the row and the cell heights are 'auto', we need to format the cell to get its height. + if (box.GetSize().y < 0) + { + FormattingContext::FormatIndependent(table_wrapper_box, element_cell, &box, FormattingContextType::Block); + box.SetContent(element_cell->GetBox().GetSize()); + } + + // Find the height of the cell which applies only to this row. + // In case it spans multiple rows, we must first subtract the height of any previous rows it spans. It is + // unsupported if any spanning rows are flexibly sized, in which case we consider their size to be zero. + const float gap_from_spanning_rows = table_gap.y * float(grid_cell.row_last - grid_cell.row_begin); + + const float height_from_spanning_rows = std::accumulate(row_metrics.begin() + grid_cell.row_begin, + row_metrics.begin() + grid_cell.row_last, gap_from_spanning_rows, [](float acc_height, const TrackMetric& metric) { + return acc_height + metric.fixed_size + metric.column_padding_border_a + metric.column_padding_border_b + + metric.group_padding_border_a + metric.group_padding_border_b + metric.sum_margin_a + metric.sum_margin_b; + }); + + const float cell_inrow_height = box.GetSizeAcross(BoxDirection::Vertical, BoxArea::Border) - height_from_spanning_rows; + + // Now we have the height of the cell, increase the row height to accompany the cell. + row_metric.fixed_size = Math::Max(row_metric.fixed_size, cell_inrow_height); + } + + row_metric.sizing_mode = TrackSizingMode::Fixed; + row_metric.flex_size = 0.0f; + row_metric.fixed_size = Math::Clamp(row_metric.fixed_size, row_metric.min_size, row_metric.max_size); + } + } + + // Now all heights should be either fixed or flexible, resolve all flexible heights to fixed. + sizing.ResolveFlexibleSize(); + + // Generate the row boxes based on the metrics. + const float rows_full_height = BuildRowBoxes(rows, row_metrics, grid.rows, table_gap.y); + + // Adjust the table content height based on the accumulated row heights and spacing. + table_content_height = Math::Clamp(Math::Max(rows_full_height, table_initial_content_size.y), table_min_size.y, table_max_size.y); +} + +void TableFormattingContext::FormatRows(const TrackBoxList& rows, float table_content_width) const +{ + RMLUI_ASSERT(rows.size() == grid.rows.size()); + + // Size and position the row and row group elements. + auto FormatRow = [this, table_content_width](Element* element, float content_height, float offset_y) { + Box box; + // We use inline build mode here because we only care about padding, border, and (non-auto) margin. + LayoutDetails::BuildBox(box, table_initial_content_size, element, BuildBoxMode::Inline); + const Vector2f content_size(table_content_width - box.GetSizeAcross(BoxDirection::Horizontal, BoxArea::Margin, BoxArea::Padding), + content_height); + box.SetContent(content_size); + element->SetBox(box); + + element->SetOffset(table_content_offset + Vector2f(box.GetEdge(BoxArea::Margin, BoxEdge::Left), offset_y), element_table); + }; + + for (int i = 0; i < (int)rows.size(); i++) + { + const TableGrid::Row& grid_row = grid.rows[i]; + const TrackBox& box = rows[i]; + + if (grid_row.element_row) + FormatRow(grid_row.element_row, box.track_size, box.track_offset); + + if (grid_row.element_group) + FormatRow(grid_row.element_group, box.group_size, box.group_offset); + } +} + +void TableFormattingContext::FormatColumns(const TrackBoxList& columns, float table_content_height) const +{ + RMLUI_ASSERT(columns.size() == grid.columns.size()); + + // Size and position the column and column group elements. + auto FormatColumn = [this, table_content_height](Element* element, float content_width, float offset_x) { + Box box; + // We use inline build mode here because we only care about padding, border, and (non-auto) margin. + LayoutDetails::BuildBox(box, table_initial_content_size, element, BuildBoxMode::Inline); + const Vector2f content_size(content_width, + table_content_height - box.GetSizeAcross(BoxDirection::Vertical, BoxArea::Margin, BoxArea::Padding)); + box.SetContent(content_size); + element->SetBox(box); + + element->SetOffset(table_content_offset + Vector2f(offset_x, box.GetEdge(BoxArea::Margin, BoxEdge::Top)), element_table); + }; + + for (int i = 0; i < (int)columns.size(); i++) + { + const TableGrid::Column& grid_column = grid.columns[i]; + const TrackBox& box = columns[i]; + + if (grid_column.element_column) + FormatColumn(grid_column.element_column, box.track_size, box.track_offset); + + if (grid_column.element_group) + FormatColumn(grid_column.element_group, box.group_size, box.group_offset); + } +} + +void TableFormattingContext::FormatCells(BoxList& cells, Vector2f& table_overflow_size, const TrackBoxList& rows, const TrackBoxList& columns, + float& table_baseline) const +{ + RMLUI_ASSERT(cells.size() == grid.cells.size()); + + bool baseline_set = false; + + for (int cell_index = 0; cell_index < (int)cells.size(); cell_index++) + { + const TableGrid::Cell& grid_cell = grid.cells[cell_index]; + Element* element_cell = grid_cell.element_cell; + + Box& box = cells[cell_index]; + Style::VerticalAlign vertical_align = element_cell->GetComputedValues().vertical_align(); + + const float cell_border_height = GetSpanningCellBorderSize(rows, grid_cell.row_begin, grid_cell.row_last); + const Vector2f cell_offset = + table_content_offset + Vector2f(columns[grid_cell.column_begin].cell_offset, rows[grid_cell.row_begin].cell_offset); + + // Determine the height of the cell. + if (box.GetSize().y < 0) + { + const bool is_aligned = (vertical_align.type == Style::VerticalAlign::Middle || vertical_align.type == Style::VerticalAlign::Bottom); + if (is_aligned) + { + // We need to format the cell to know how much padding to add. + FormattingContext::FormatIndependent(table_wrapper_box, element_cell, &box, FormattingContextType::Block); + box.SetContent(element_cell->GetBox().GetSize()); + } + else + { + // We don't need to add any padding and can thus avoid formatting, just set the height to the row height. + box.SetContent(Vector2f(box.GetSize().x, + Math::Max(0.0f, cell_border_height - box.GetSizeAcross(BoxDirection::Vertical, BoxArea::Border, BoxArea::Padding)))); + } + } + + const float available_height = cell_border_height - box.GetSizeAcross(BoxDirection::Vertical, BoxArea::Border); + + if (available_height > 0) + { + // Pad the cell for vertical alignment + float add_padding_top; + float add_padding_bottom; + + switch (vertical_align.type) + { + case Style::VerticalAlign::Bottom: + add_padding_top = available_height; + add_padding_bottom = 0; + break; + case Style::VerticalAlign::Middle: + add_padding_top = 0.5f * available_height; + add_padding_bottom = 0.5f * available_height; + break; + case Style::VerticalAlign::Top: + default: + add_padding_top = 0.0f; + add_padding_bottom = available_height; + break; + } + + box.SetEdge(BoxArea::Padding, BoxEdge::Top, box.GetEdge(BoxArea::Padding, BoxEdge::Top) + add_padding_top); + box.SetEdge(BoxArea::Padding, BoxEdge::Bottom, box.GetEdge(BoxArea::Padding, BoxEdge::Bottom) + add_padding_bottom); + } + + // Format the cell in a new block formatting context. + // @performance: We may have already formatted the element during the above procedures without the extra padding. In that case, we may + // instead set the new box and offset all descending elements whose offset parent is the cell, to account for the new padding box. + // That should be faster than formatting the element again, but there may be edge-cases not accounted for. + auto cell_box = FormattingContext::FormatIndependent(table_wrapper_box, element_cell, &box, FormattingContextType::Block); + Vector2f cell_visible_overflow_size = cell_box->GetVisibleOverflowSize(); + + // Set the position of the element within the table container + element_cell->SetOffset(cell_offset, element_table); + + // The table baseline is simply set to the first cell that has a baseline. + if (!baseline_set && cell_box->GetBaselineOfLastLine(table_baseline)) + { + table_baseline += cell_offset.y; + baseline_set = true; + } + + // The cell contents may overflow, propagate this to the table. + table_overflow_size.x = Math::Max(table_overflow_size.x, cell_offset.x - table_content_offset.x + cell_visible_overflow_size.x); + table_overflow_size.y = Math::Max(table_overflow_size.y, cell_offset.y - table_content_offset.y + cell_visible_overflow_size.y); + } +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/TableFormattingContext.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/TableFormattingContext.h new file mode 100644 index 00000000..33afb3a3 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/TableFormattingContext.h @@ -0,0 +1,65 @@ +#pragma once + +#include "../../../Include/RmlUi/Core/Types.h" +#include "FormattingContext.h" +#include "TableFormattingDetails.h" + +namespace Rml { + +class Box; +class TableGrid; +class TableWrapper; +struct TrackBox; +using TrackBoxList = Vector; + +/* + Formats a table element and its parts according to table layout rules. +*/ +class TableFormattingContext final : public FormattingContext { +public: + static UniquePtr Format(ContainerBox* parent_container, Element* element, const Box* override_initial_box); + +private: + TableFormattingContext() = default; + + using BoxList = Vector; + + /// Format the table and its children. + /// @param[out] table_content_size The final size of the table which will be determined by the size of its columns, rows, and spacing. + /// @param[out] table_overflow_size Overflow size in case the contents of any cells overflow their cell box (without being caught by the cell). + /// @param[out] table_baseline The baseline of the table wrapper, in terms of the vertical distance from its top-left border corner. + /// @note Expects the table grid to have been built, and all table parameters to be set already. + void FormatTable(Vector2f& table_content_size, Vector2f& table_overflow_size, float& table_baseline) const; + + // Determines the column widths, and populates the columns. + void DetermineColumnWidths(TrackBoxList& columns, float& table_content_width) const; + + // Generate the initial boxes for all cells, content height may be indeterminate for now (-1). + void InitializeCellBoxes(BoxList& cells, const TrackBoxList& columns) const; + + // Determines the row heights, and populates the rows. + void DetermineRowHeights(TrackBoxList& rows, BoxList& cells, float& table_content_height) const; + + // Format the table row and row group elements. + void FormatRows(const TrackBoxList& rows, float table_content_width) const; + + // Format the table row and row group elements. + void FormatColumns(const TrackBoxList& columns, float table_content_height) const; + + // Format the table cell elements. + void FormatCells(BoxList& cells, Vector2f& table_overflow_size, const TrackBoxList& rows, const TrackBoxList& columns, + float& table_baseline) const; + + Element* element_table = nullptr; + TableWrapper* table_wrapper_box = nullptr; + + TableGrid grid; + + bool table_auto_height = false; + Vector2f table_min_size, table_max_size; + Vector2f table_gap; + Vector2f table_content_offset; + Vector2f table_initial_content_size; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/TableFormattingDetails.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/TableFormattingDetails.cpp new file mode 100644 index 00000000..2b0d3a9d --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/TableFormattingDetails.cpp @@ -0,0 +1,643 @@ +#include "TableFormattingDetails.h" +#include "../../../Include/RmlUi/Core/ComputedValues.h" +#include "../../../Include/RmlUi/Core/Element.h" +#include "ContainerBox.h" +#include "LayoutDetails.h" +#include +#include + +namespace Rml { + +bool TableGrid::Build(Element* element_table, TableWrapper& table_wrapper) +{ + RMLUI_ASSERT(rows.empty() && columns.empty() && cells.empty() && open_cells.empty()); + ElementList non_parented_cell_elements; + + const int num_table_children = element_table->GetNumChildren(); + for (int i = 0; i < num_table_children; i++) + { + using Display = Style::Display; + + Element* element = element_table->GetChild(i); + const Display display = element->GetDisplay(); + + if (display == Display::None) + continue; + + if (!non_parented_cell_elements.empty() && display != Display::TableCell) + { + PushRow(nullptr, std::move(non_parented_cell_elements), table_wrapper); + non_parented_cell_elements.clear(); + } + + if (display == Display::TableCell) + { + non_parented_cell_elements.push_back(element); + } + else if (display == Display::TableRow) + { + PushRow(element, {}, table_wrapper); + } + else if (display == Display::TableRowGroup) + { + const int num_row_group_children = element->GetNumChildren(); + const int row_group_index = (int)rows.size(); + int num_rows_added = 0; + + for (int j = 0; j < num_row_group_children; j++) + { + Element* element_row = element->GetChild(j); + const Display display_row = element_row->GetDisplay(); + + if (display_row != Display::TableRow) + { + if (display_row != Display::None) + { + Log::Message(Log::LT_WARNING, "Only table rows are valid children of table row groups. Ignoring element %s.", + element_row->GetAddress().c_str()); + } + continue; + } + + PushRow(element_row, {}, table_wrapper); + num_rows_added += 1; + } + + if (num_rows_added > 0) + { + rows[row_group_index].element_group = element; + rows[row_group_index].group_span = num_rows_added; + } + if (element->GetPosition() == Style::Position::Relative) + table_wrapper.AddRelativeElement(element); + } + else if (rows.empty() && display == Display::TableColumn) + { + const int span = Math::Max(1, element->GetAttribute("span", 1)); + PushColumn(element, span); + } + else if (rows.empty() && display == Display::TableColumnGroup) + { + PushColumnGroup(element); + } + else + { + if (display == Display::TableColumn || display == Display::TableColumnGroup) + Log::Message(Log::LT_WARNING, "Table columns and column groups must precede any table rows. Ignoring element %s.", + element->GetAddress().c_str()); + else + Log::Message(Log::LT_WARNING, + "Only table columns, column groups, rows, row groups, and cells are valid children of tables. Ignoring element %s.", + element->GetAddress().c_str()); + } + } + + if (!non_parented_cell_elements.empty()) + { + PushRow(nullptr, std::move(non_parented_cell_elements), table_wrapper); + non_parented_cell_elements.clear(); + } + + // Sort cells by the last row they span. + std::sort(cells.begin(), cells.end(), [](const Cell& a, const Cell& b) { return a.row_last < b.row_last; }); + + if (!open_cells.empty()) + { + Log::Message(Log::LT_WARNING, + "One or more cells span below the last row in table %s. They will not be formatted. Add additional rows, or adjust the rowspan " + "attribute.", + element_table->GetAddress().c_str()); + } + open_cells.clear(); + open_cells.shrink_to_fit(); + + return true; +} + +void TableGrid::PushColumn(Element* element_column, int span) +{ + Column column; + column.element_column = element_column; + column.column_span = span; + + columns.push_back(column); + + for (int j = 1; j < span; j++) + columns.push_back({}); +} + +void TableGrid::PushColumnGroup(Element* element_column_group) +{ + const int column_begin = (int)columns.size(); + int group_span = Math::Max(0, element_column_group->GetAttribute("span", 0)); + + if (group_span == 0) + { + // Look through the column group to find all its column children. + const int num_column_group_children = element_column_group->GetNumChildren(); + for (int j = 0; j < num_column_group_children; j++) + { + Element* child = element_column_group->GetChild(j); + if (child->GetDisplay() == Style::Display::TableColumn) + { + const int column_span = Math::Max(1, child->GetAttribute("span", 1)); + PushColumn(child, column_span); + group_span += column_span; + } + } + } + else + { + // Push empty columns, the group properties are filled below. + for (int j = 0; j < group_span; j++) + columns.push_back({}); + } + + if (group_span > 0) + { + columns[column_begin].element_group = element_column_group; + columns[column_begin].group_span = group_span; + } +} + +void TableGrid::PushOrMergeColumnsFromFirstRow(Element* element_cell, int column_begin, int span) +{ + for (int column_index = column_begin; column_index < column_begin + span; column_index++) + { + Column* column = nullptr; + if (column_index < (int)columns.size()) + { + column = &columns[column_index]; + } + else + { + RMLUI_ASSERT(column_index == (int)columns.size()); + columns.push_back({}); + column = &columns.back(); + } + + if (column_index == column_begin) + { + column->element_cell = element_cell; + column->cell_span = span; + } + } +} + +void TableGrid::PushRow(Element* element_row, ElementList cell_elements, TableWrapper& table_wrapper) +{ + const int row_index = (int)rows.size(); + + if (element_row) + { + RMLUI_ASSERT(cell_elements.empty()); + + const int num_row_children = element_row->GetNumChildren(); + cell_elements.reserve(num_row_children); + + for (int j = 0; j < num_row_children; j++) + { + Element* element_cell = element_row->GetChild(j); + + const Style::Display cell_display = element_cell->GetComputedValues().display(); + if (cell_display == Style::Display::TableCell) + { + cell_elements.push_back(element_cell); + } + else if (cell_display != Style::Display::None) + { + Log::Message(Log::LT_WARNING, "Only table cells are allowed as children of table rows. %s", element_cell->GetAddress().c_str()); + } + } + + if (element_row->GetPosition() == Style::Position::Relative) + table_wrapper.AddRelativeElement(element_row); + } + + rows.push_back(Row{element_row, nullptr, 0}); + + const int num_cells_spanning_this_row = (int)open_cells.size(); + + // For all child cell elements of this row, add them to the list of open cells. + for (int j = 0, column = 0; j < (int)cell_elements.size(); j++) + { + Element* element_cell = cell_elements[j]; + + const int row_span = Math::Max(1, element_cell->GetAttribute("rowspan", 1)); + const int col_span = Math::Max(1, element_cell->GetAttribute("colspan", 1)); + + if (row_index == 0) + { + // This is the first row. The cells of this row along with previous elements define the columns. + PushOrMergeColumnsFromFirstRow(element_cell, column, col_span); + } + + // Offset the column if we have any rowspan elements from previous rows overlapping with the current column. + for (bool continue_offset_column = true; continue_offset_column;) + { + continue_offset_column = false; + for (int k = 0; k < num_cells_spanning_this_row; k++) + { + if (column >= open_cells[k].column_begin && column <= open_cells[k].column_last) + { + column = open_cells[k].column_last + 1; + continue_offset_column = true; + break; + } + } + } + + const int column_last = column + col_span - 1; + + if (column_last >= (int)columns.size()) + { + Log::Message(Log::LT_WARNING, + "Too many columns in table row %d while encountering cell: %s\nThe number of columns is %d, as determined by the table columns or " + "the first table row.", + row_index + 1, element_cell->GetAddress().c_str(), (int)columns.size()); + break; + } + + const Style::Position cell_position = element_cell->GetPosition(); + if (cell_position == Style::Position::Absolute || cell_position == Style::Position::Fixed) + { + ContainerBox* containing_box = LayoutDetails::GetContainingBlock(&table_wrapper, cell_position).container; + containing_box->AddAbsoluteElement(element_cell, {}, table_wrapper.GetElement()); + } + else + { + // Add the new cell to our list. + open_cells.emplace_back(); + Cell& cell = open_cells.back(); + + cell.element_cell = element_cell; + cell.row_begin = row_index; + cell.row_last = row_index + row_span - 1; + cell.column_begin = column; + cell.column_last = column_last; + + if (cell_position == Style::Position::Relative) + table_wrapper.AddRelativeElement(element_cell); + } + + column += col_span; + } + + // Partition the cells to determine those who end at this row. + const auto it_cells_in_row_end = + std::partition(open_cells.begin(), open_cells.end(), [row_index](const Cell& cell) { return cell.row_last == row_index; }); + + // Close cells ending at this row. + cells.insert(cells.end(), open_cells.begin(), it_cells_in_row_end); + open_cells.erase(open_cells.begin(), it_cells_in_row_end); +} + +void TracksSizing::GetEdgeSizes(float& margin_a, float& margin_b, float& padding_border_a, float& padding_border_b, + const ComputedAxisSize& computed) const +{ + LayoutDetails::GetEdgeSizes(margin_a, margin_b, padding_border_a, padding_border_b, computed, table_initial_content_size); +} + +void TracksSizing::ApplyGroupElement(const int index, const int span, const ComputedAxisSize& computed) +{ + RMLUI_ASSERT(span >= 1 && index + span - 1 < (int)metrics.size()); + + float margin_a, margin_b; + float padding_border_a, padding_border_b; + + GetEdgeSizes(margin_a, margin_b, padding_border_a, padding_border_b, computed); + + // Add left/top edges. + TrackMetric& metric_begin = metrics[index]; + metric_begin.group_padding_border_a = padding_border_a; + metric_begin.sum_margin_a = margin_a; + + // Add right/bottom edges. + TrackMetric& metric_last = metrics[index + span - 1]; + metric_last.group_padding_border_b = padding_border_b; + metric_last.sum_margin_b = margin_b; +} + +void TracksSizing::ApplyTrackElement(const int index, const int span, const ComputedAxisSize& computed) +{ + RMLUI_ASSERT(span >= 1 && index + span - 1 < (int)metrics.size()); + + float margin_a, margin_b; + float padding_border_a, padding_border_b; + TrackMetric& metric_begin = metrics[index]; + + // We target the content box because track sizes are defined in terms of the border box of cells, equal to the content size of tracks. + InitializeSize(metric_begin, margin_a, margin_b, padding_border_a, padding_border_b, computed, span, Style::BoxSizing::ContentBox); + + // Add left/top edges. Increment the values because we are merging with any previously set edges. + metric_begin.column_padding_border_a += padding_border_a; + metric_begin.sum_margin_a += margin_a; + + // Add right/bottom edges. + TrackMetric& metric_last = metrics[index + span - 1]; + metric_last.column_padding_border_b += padding_border_b; + metric_last.sum_margin_b += margin_b; + + // The size of all spanning tracks are distributed equally. + for (int j = 1; j < span; j++) + { + TrackMetric& metric = metrics[index + j]; + + metric.sizing_mode = metric_begin.sizing_mode; + metric.fixed_size = metric_begin.fixed_size; + metric.flex_size = metric_begin.flex_size; + metric.min_size = metric_begin.min_size; + metric.max_size = metric_begin.max_size; + } +} + +void TracksSizing::ApplyCellElement(const int index, const int span, const ComputedAxisSize& computed) +{ + // Merge the metrics of the cell with the existing track: If the existing track + // has auto min-/max-/size, we use the cell's min-/max-/size if it has any. + + RMLUI_ASSERT(span >= 1 && index + span - 1 < (int)metrics.size()); + + float margin_a, margin_b; + float padding_border_a, padding_border_b; + TrackMetric cell_metric; + + // We target the border box because track sizes are defined in terms of the border box of cells. + InitializeSize(cell_metric, margin_a, margin_b, padding_border_a, padding_border_b, computed, span, Style::BoxSizing::BorderBox); + + cell_metric.sum_margin_a = margin_a; + + // Merge the size determined by this cell with any existing track sizes. + for (int j = 0; j < span; j++) + { + if (j == 1) + cell_metric.sum_margin_a = 0; + if (j == span - 1) + cell_metric.sum_margin_b = margin_b; + + // Merge the existing track metrics with the cell sizing data. + TrackMetric& destination = metrics[index + j]; + + if (destination.sizing_mode != TrackSizingMode::Fixed && cell_metric.sizing_mode == TrackSizingMode::Fixed) + { + destination.fixed_size = cell_metric.fixed_size; + destination.flex_size = 0; + destination.sizing_mode = TrackSizingMode::Fixed; + } + + if (destination.min_size == 0) + destination.min_size = cell_metric.min_size; + + if (destination.max_size == FLT_MAX) + destination.max_size = cell_metric.max_size; + + destination.sum_margin_a += cell_metric.sum_margin_a; + destination.sum_margin_b += cell_metric.sum_margin_b; + } +} + +void TracksSizing::InitializeSize(TrackMetric& metric, float& margin_a, float& margin_b, float& padding_border_a, float& padding_border_b, + const ComputedAxisSize& computed, const int span, const Style::BoxSizing target_box) const +{ + RMLUI_ASSERT(span >= 1); + + GetEdgeSizes(margin_a, margin_b, padding_border_a, padding_border_b, computed); + + const float padding_border_sum = padding_border_a + padding_border_b; + + // Find the min/max size. + metric.min_size = ResolveValue(computed.min_size, table_initial_content_size); + metric.max_size = ResolveValue(computed.max_size, table_initial_content_size); + + if (target_box == Style::BoxSizing::ContentBox && computed.box_sizing == Style::BoxSizing::BorderBox) + { + metric.min_size = Math::Max(0.0f, metric.min_size - padding_border_sum); + if (metric.max_size < FLT_MAX) + metric.max_size = Math::Max(0.0f, metric.max_size - padding_border_sum); + } + else if (target_box == Style::BoxSizing::BorderBox && computed.box_sizing == Style::BoxSizing::ContentBox) + { + if (metric.min_size > 0) + metric.min_size += padding_border_sum; + if (metric.max_size < FLT_MAX) + metric.max_size += padding_border_sum; + } + + // Find fixed and flexible sizes. + if (computed.size.type == Style::LengthPercentageAuto::Auto) + { + metric.sizing_mode = TrackSizingMode::Auto; + } + else if (computed.size.type == Style::LengthPercentageAuto::Percentage && computed.size.value >= 100.f) + { + // Percentages >= 100% are resolved as flexible size. + metric.sizing_mode = TrackSizingMode::Flexible; + metric.flex_size = Math::Max(0.01f * computed.size.value / float(span), 0.f); + } + else + { + float width = ResolveValue(computed.size, table_initial_content_size); + + if (target_box == Style::BoxSizing::ContentBox && computed.box_sizing == Style::BoxSizing::BorderBox) + width = Math::Max(0.f, width - padding_border_sum); + else if (target_box == Style::BoxSizing::BorderBox && computed.box_sizing == Style::BoxSizing::ContentBox) + width += padding_border_sum; + + metric.sizing_mode = TrackSizingMode::Fixed; + metric.flex_size = 0; + metric.fixed_size = Math::Clamp(width, metric.min_size, metric.max_size); + metric.min_size = metric.fixed_size; + metric.max_size = metric.fixed_size; + } + + if (span > 1) + { + // Account for distribution of fixed size over the tracks we are spanning. + const float width_factor = 1.f / float(span); + metric.fixed_size *= width_factor; + metric.min_size *= width_factor; + if (metric.max_size < FLT_MAX) + metric.max_size *= width_factor; + } +} + +void TracksSizing::ResolveFlexibleSize() +{ + // The fixed spacing includes the table gaps, and the track and track-group elements' padding, border, and margins. + float sum_fixed_spacing = table_gap * float((int)metrics.size() - 1); + + for (const TrackMetric& metric : metrics) + { + // Any auto size must have been resolved to either fixed or flexible before running this algorithm. + RMLUI_ASSERT(metric.sizing_mode == TrackSizingMode::Fixed || metric.sizing_mode == TrackSizingMode::Flexible); + + sum_fixed_spacing += metric.column_padding_border_a + metric.column_padding_border_b; + sum_fixed_spacing += metric.group_padding_border_a + metric.group_padding_border_a; + sum_fixed_spacing += metric.sum_margin_a + metric.sum_margin_b; + } + + float table_available_size = 0.0f; + + // Convert any flexible sizes to fixed sizes by filling up the size of the table. + for (bool continue_iteration = true; continue_iteration;) + { + continue_iteration = false; + float fr_to_px_ratio = 0; + + // Calculate the fr/px-ratio. [fr] is here the unit for flexible width. + { + float sum_fixed_size = sum_fixed_spacing; // [px] + float sum_flex_size = 0; // [fr] + + for (const TrackMetric& metric : metrics) + { + sum_flex_size += metric.flex_size; + sum_fixed_size += (metric.flex_size == 0.f ? metric.fixed_size : 0.0f); + } + + sum_flex_size = Math::Max(1.f, sum_flex_size); + + table_available_size = table_initial_content_size - sum_fixed_size; + fr_to_px_ratio = Math::Max(0.0f, table_available_size) / sum_flex_size; + } + + // Iterate through each track and convert flexible size to fixed size. + for (auto& metric : metrics) + { + if (metric.flex_size > 0) + { + const float fixed_flex_size = metric.flex_size * fr_to_px_ratio; + metric.fixed_size = Math::Clamp(fixed_flex_size, metric.min_size, metric.max_size); + table_available_size -= metric.fixed_size; + + if (metric.fixed_size != fixed_flex_size) + { + // We met a min/max-constraint, fix the size of this track. Start over with the procedure once we are done with all the tracks. + metric.flex_size = 0.0f; + continue_iteration = true; + } + } + } + } + + // If we have distributed all the flexible space, and there is still space available, then distribute the available space over + // the track sizes while respecting max-widths. + if (table_available_size > 0.5f) + { + const int num_tracks = (int)metrics.size(); + + struct TrackAvailableSize { + int track; + float available_size; + }; + Vector track_available_sizes(num_tracks); + + // Find the available size of all tracks. + for (int i = 0; i < num_tracks; i++) + { + track_available_sizes[i].track = i; + track_available_sizes[i].available_size = metrics[i].max_size - metrics[i].fixed_size; + } + + // Sort the tracks by available size, smallest to largest. This lets us "fill up" the most constrained tracks first. + std::sort(track_available_sizes.begin(), track_available_sizes.end(), + [](const TrackAvailableSize& c1, const TrackAvailableSize& c2) { return c1.available_size < c2.available_size; }); + + for (int i = 0; i < num_tracks; i++) + { + const int track = track_available_sizes[i].track; + const int num_tracks_remaining = num_tracks - i; + + const float ideal_add_track_size = table_available_size / float(num_tracks_remaining); + const float add_track_size = Math::Min(ideal_add_track_size, track_available_sizes[i].available_size); + + if (add_track_size > 0) + { + metrics[track].fixed_size += add_track_size; + table_available_size = Math::Max(0.0f, table_available_size - add_track_size); + } + } + } +} + +static float InitializeTrackBoxes(TrackBoxList& boxes, const TrackMetricList& metrics, const float table_gap) +{ + boxes.resize(metrics.size()); + + float cursor = 0; + + // Walk through all the metrics and populate the track box accordingly. + for (size_t i = 0; i < metrics.size(); i++) + { + TrackBox& box = boxes[i]; + const TrackMetric& metric = metrics[i]; + + box.group_offset = cursor + metric.sum_margin_a; + box.track_offset = box.group_offset + metric.group_padding_border_a; + box.cell_offset = box.track_offset + metric.column_padding_border_a; + + // The group and column width will be extended if they span multiple columns (see next loop). + box.group_size = metric.fixed_size + metric.column_padding_border_a + metric.column_padding_border_b; + box.cell_size = metric.fixed_size; + box.track_size = metric.fixed_size; + + cursor = box.cell_offset + metric.fixed_size + metric.column_padding_border_b + metric.group_padding_border_b + metric.sum_margin_b; + if (i != metrics.size() - 1) + cursor += table_gap; + } + + return cursor; +} + +float BuildColumnBoxes(TrackBoxList& column_boxes, const TrackMetricList& column_metrics, const TableGrid::ColumnList& grid_columns, + const float table_gap_x) +{ + const float columns_width = InitializeTrackBoxes(column_boxes, column_metrics, table_gap_x); + const int num_columns = (int)column_metrics.size(); + + // Extend column and column group widths to cover all the columns they span. + for (int i = 0; i < num_columns; i++) + { + const int column_span = grid_columns[i].column_span; + const int group_span = grid_columns[i].group_span; + + if (column_span > 1 && i + column_span - 1 < num_columns) + { + TrackBox& metric = column_boxes[i]; + TrackBox& metric_last_span = column_boxes[i + column_span - 1]; + metric.track_size = metric_last_span.cell_size + (metric_last_span.cell_offset - metric.cell_offset); + } + + if (group_span > 1 && i + group_span - 1 < num_columns) + { + TrackBox& metric = column_boxes[i]; + TrackBox& metric_last_span = column_boxes[i + group_span - 1]; + metric.group_size = metric_last_span.group_size + (metric_last_span.track_offset - metric.track_offset); + } + } + + return columns_width; +} + +float BuildRowBoxes(TrackBoxList& row_boxes, const TrackMetricList& row_metrics, const TableGrid::RowList& grid_rows, const float table_gap_y) +{ + const float rows_height = InitializeTrackBoxes(row_boxes, row_metrics, table_gap_y); + const int num_rows = (int)row_metrics.size(); + + // Extend row group heights to cover the all rows they span. + for (int i = 0; i < num_rows; i++) + { + const int group_span = grid_rows[i].group_span; + + if (group_span > 1 && i + group_span - 1 < num_rows) + { + TrackBox& metric = row_boxes[i]; + TrackBox& metric_last_span = row_boxes[i + group_span - 1]; + metric.group_size = metric_last_span.group_size + (metric_last_span.track_offset - metric.track_offset); + } + } + + return rows_height; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/TableFormattingDetails.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/TableFormattingDetails.h new file mode 100644 index 00000000..2bf67e48 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Layout/TableFormattingDetails.h @@ -0,0 +1,149 @@ +#pragma once + +#include "../../../Include/RmlUi/Core/StyleTypes.h" +#include "../../../Include/RmlUi/Core/Types.h" +#include + +namespace Rml { + +class TableWrapper; +struct ComputedAxisSize; + +/* + TableGrid builds the structure of the table, that is a list of rows, columns, and cells, taking + spanning attributes into account to position cells. +*/ +class TableGrid { +public: + // Build a list of columns, rows, and cells in this table. + bool Build(Element* element_table, TableWrapper& table_wrapper); + + struct Column { + Element* element_column = nullptr; // The '' element which begins at this column, or nullptr if there is no such element or if the column + // is spanned from a previous column. + Element* element_group = nullptr; // The '' element which begins at this column, otherwise nullptr. + Element* element_cell = nullptr; // The '' element of the first row which begins at this column, otherwise nullptr. + int column_span = 0; // The span of the '' element. + int group_span = 0; // The span of the '' element. + int cell_span = 0; // The colspan of the '' element. + }; + struct Row { + Element* element_row = nullptr; // The '' element which begins at this column, or nullptr if there is no such element or if the column is + // spanned from a previous column. + Element* element_group = nullptr; // The '' element which begins at this column, otherwise nullptr. + int group_span = 0; // The span of the '' element. + }; + struct Cell { + Element* element_cell = nullptr; // The element. + int row_begin = 0, row_last = 0; // The first and last rows the cell spans. + int column_begin = 0, column_last = 0; // The first and last columns the cell spans. + }; + + using ColumnList = Vector; + using RowList = Vector; + using CellList = Vector; + + ColumnList columns; + RowList rows; + CellList cells; + +private: + void PushColumn(Element* element_column, int column_span); + + void PushColumnGroup(Element* element_column_group); + + void PushOrMergeColumnsFromFirstRow(Element* element_cell, int column_begin, int span); + + void PushRow(Element* element_row, ElementList cell_elements, TableWrapper& table_wrapper); + + CellList open_cells; +}; + +enum class TrackSizingMode { Auto, Fixed, Flexible }; + +/* + TrackMetric describes the size and the edges of a given track (row or column) in the table. +*/ +struct TrackMetric { + // All sizes are defined in terms of the border size of cells in the row or column. + TrackSizingMode sizing_mode = TrackSizingMode::Auto; + float fixed_size = 0; + float flex_size = 0; + float min_size = 0; + float max_size = FLT_MAX; + // The following are used for row/column elements. + float column_padding_border_a = 0; + float column_padding_border_b = 0; + // The following are used for group elements. + float group_padding_border_a = 0; + float group_padding_border_b = 0; + // The margins are the sum of the margins from all cells, tracks, and group elements. + float sum_margin_a = 0; + float sum_margin_b = 0; +}; + +using TrackMetricList = Vector; + +/* + TracksSizing is a helper class for building the track metrics, with methods applicable to both rows and columns sizing. +*/ +class TracksSizing { +public: + TracksSizing(TrackMetricList& metrics, float table_initial_content_size, float table_gap) : + metrics(metrics), table_initial_content_size(table_initial_content_size), table_gap(table_gap) + {} + + // Apply group element. This sets the initial size of edges. + void ApplyGroupElement(const int index, const int span, const ComputedAxisSize& computed); + // Apply track element. This merges its edges, and sets the initial content size. + void ApplyTrackElement(const int index, const int span, const ComputedAxisSize& computed); + // Apply cell element for column sizing. This merges its content size and margins. + void ApplyCellElement(const int index, const int span, const ComputedAxisSize& computed); + + // Convert flexible size to fixed size for all tracks. + void ResolveFlexibleSize(); + +private: + void GetEdgeSizes(float& margin_a, float& margin_b, float& padding_border_a, float& padding_border_b, const ComputedAxisSize& computed) const; + + // Fill the track metric with fixed, flexible and min/max size, based on the element's computed values. + void InitializeSize(TrackMetric& metric, float& margin_a, float& margin_b, float& padding_border_a, float& padding_border_b, + const ComputedAxisSize& computed, const int span, const Style::BoxSizing target_box) const; + + TrackMetricList& metrics; + const float table_initial_content_size; + const float table_gap; +}; + +/* + TrackBox represents the size and offset of any given track (row or column). + Rows: Sizes == Heights. Offsets along vertical axis. + Columns: Sizes == Widths. Offsets along horizontal axis. +*/ +struct TrackBox { + float cell_size = 0; // The *border* size of cells in this track, does not account for spanning cells. + float cell_offset = 0; // Offset from the table content box to the border box of cells in this track. + float track_size = 0; // The *content* size of the row/column element, which may span multiple tracks. + float track_offset = 0; // Offset from the table content box to the border box of the row/column element. + float group_size = 0; // The *content* size of the group element, which may span multiple tracks. + float group_offset = 0; // Offset from the table content box to the border box of the group element. +}; +using TrackBoxList = Vector; + +// Build a list of column boxes from the provided metrics. +// @return The accumulated width of all columns. +float BuildColumnBoxes(TrackBoxList& column_boxes, const TrackMetricList& column_metrics, const TableGrid::ColumnList& grid_columns, + float table_gap_x); + +// Build a list of row boxes from the provided metrics. +// @return The accumulated height of all rows. +float BuildRowBoxes(TrackBoxList& row_boxes, const TrackMetricList& row_metrics, const TableGrid::RowList& grid_rows, float table_gap_y); + +// Return the border size of a cell spanning one or multiple tracks. +inline float GetSpanningCellBorderSize(const TrackBoxList& boxes, const int index, const int index_last_span) +{ + RMLUI_ASSERT(index < (int)boxes.size() && index_last_span < (int)boxes.size()); + return boxes[index_last_span].cell_size + (boxes[index_last_span].cell_offset - boxes[index].cell_offset); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Log.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Log.cpp new file mode 100644 index 00000000..3203129e --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Log.cpp @@ -0,0 +1,68 @@ +#include "../../Include/RmlUi/Core/Log.h" +#include "../../Include/RmlUi/Core/Core.h" +#include "../../Include/RmlUi/Core/StringUtilities.h" +#include "../../Include/RmlUi/Core/SystemInterface.h" +#include "LogDefault.h" +#include +#include + +namespace Rml { + +void Log::Message(Log::Type type, const char* fmt, ...) +{ + const int buffer_size = 1024; + char buffer[buffer_size]; + va_list argument_list; + + // Print the message to the buffer. + va_start(argument_list, fmt); + int len = vsnprintf(buffer, buffer_size - 2, fmt, argument_list); + if (len < 0 || len > buffer_size - 2) + { + len = buffer_size - 2; + } + buffer[len] = '\0'; + va_end(argument_list); + + if (SystemInterface* system_interface = GetSystemInterface()) + system_interface->LogMessage(type, buffer); + else + LogDefault::LogMessage(type, buffer); +} + +void Log::ParseError(const String& filename, int line_number, const char* fmt, ...) +{ + const int buffer_size = 1024; + char buffer[buffer_size]; + va_list argument_list; + + // Print the message to the buffer. + va_start(argument_list, fmt); + int len = vsnprintf(buffer, buffer_size - 2, fmt, argument_list); + if (len < 0 || len > buffer_size - 2) + { + len = buffer_size - 2; + } + buffer[len] = '\0'; + va_end(argument_list); + + if (line_number >= 0) + Message(Log::LT_ERROR, "%s:%d: %s", filename.c_str(), line_number, buffer); + else + Message(Log::LT_ERROR, "%s: %s", filename.c_str(), buffer); +} + +bool Assert(const char* msg, const char* file, int line) +{ + String message = CreateString("%s\n%s:%d", msg, file, line); + + bool result = true; + if (SystemInterface* system_interface = GetSystemInterface()) + result = system_interface->LogMessage(Log::LT_ASSERT, message); + else + result = LogDefault::LogMessage(Log::LT_ASSERT, message); + + return result; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/LogDefault.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/LogDefault.cpp new file mode 100644 index 00000000..a4405e22 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/LogDefault.cpp @@ -0,0 +1,43 @@ +#include "LogDefault.h" +#include "../../Include/RmlUi/Core/StringUtilities.h" + +#ifdef RMLUI_PLATFORM_WIN32_NATIVE + #include +#else + #include +#endif + +namespace Rml { + +#if defined RMLUI_PLATFORM_WIN32_NATIVE +bool LogDefault::LogMessage(Log::Type type, const String& message) +{ + #if !defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP) + if (type == Log::LT_ASSERT) + { + String message_user = CreateString("%s\nWould you like to interrupt execution?", message.c_str()); + + // Return TRUE if the user presses NO (continue execution) + return (IDNO == MessageBoxA(nullptr, message_user.c_str(), "Assertion Failure", MB_YESNO | MB_ICONSTOP | MB_DEFBUTTON2 | MB_TASKMODAL)); + } + else + #endif + { + OutputDebugStringA(message.c_str()); + OutputDebugStringA("\r\n"); + } + return true; +} +#else +bool LogDefault::LogMessage(Log::Type /*type*/, const String& message) +{ + #ifdef RMLUI_PLATFORM_EMSCRIPTEN + puts(message.c_str()); + #else + fprintf(stderr, "%s\n", message.c_str()); + #endif + return true; +} +#endif + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/LogDefault.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/LogDefault.h new file mode 100644 index 00000000..930b60c5 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/LogDefault.h @@ -0,0 +1,17 @@ +#pragma once + +#include "../../Include/RmlUi/Core/Log.h" +#include "../../Include/RmlUi/Core/Types.h" + +namespace Rml { + +/** + Provides a platform-dependent default implementation for message logging. + */ + +class LogDefault { +public: + static bool LogMessage(Log::Type type, const String& message); +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Math.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Math.cpp new file mode 100644 index 00000000..7faad600 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Math.cpp @@ -0,0 +1,269 @@ +#include "../../Include/RmlUi/Core/Math.h" +#include "../../Include/RmlUi/Core/Types.h" +#include +#include + +namespace Rml { + +namespace Math { + + static constexpr float FZERO = 0.0001f; + + RMLUICORE_API bool IsCloseToZero(float value) + { + return Absolute(value) < FZERO; + } + + RMLUICORE_API float Absolute(float value) + { + return std::abs(value); + } + + RMLUICORE_API int Absolute(int value) + { + return std::abs(value); + } + + RMLUICORE_API Vector2f Absolute(Vector2f value) + { + return {std::abs(value.x), std::abs(value.y)}; + } + + RMLUICORE_API float Cos(float angle) + { + return std::cos(angle); + } + + RMLUICORE_API float ACos(float value) + { + return std::acos(value); + } + + RMLUICORE_API float Sin(float angle) + { + return std::sin(angle); + } + + RMLUICORE_API float ASin(float value) + { + return std::asin(value); + } + + RMLUICORE_API float Tan(float angle) + { + return std::tan(angle); + } + + RMLUICORE_API float ATan2(float y, float x) + { + return std::atan2(y, x); + } + + RMLUICORE_API float Exp(float value) + { + return std::exp(value); + } + + RMLUICORE_API int Log2(int value) + { + int result = 0; + while (value > 1) + { + value >>= 1; + result++; + } + return result; + } + + RMLUICORE_API float RadiansToDegrees(float angle) + { + return angle * (180.0f / RMLUI_PI); + } + + RMLUICORE_API float DegreesToRadians(float angle) + { + return angle * (RMLUI_PI / 180.0f); + } + + RMLUICORE_API float NormaliseAngle(float angle) + { + float result = std::fmod(angle, RMLUI_PI * 2.0f); + if (result < 0.f) + result += RMLUI_PI * 2.0f; + return result; + } + + RMLUICORE_API float SquareRoot(float value) + { + return std::sqrt(value); + } + + RMLUICORE_API float Round(float value) + { + return std::floor(value + 0.5f); + } + + RMLUICORE_API double Round(double value) + { + return std::floor(value + 0.5); + } + + RMLUICORE_API float RoundUp(float value) + { + return std::ceil(value); + } + + RMLUICORE_API float RoundDown(float value) + { + return std::floor(value); + } + + RMLUICORE_API int RoundToInteger(float value) + { + if (value > 0.0f) + return int(value + 0.5f); + + return int(value - 0.5f); + } + + RMLUICORE_API int RoundUpToInteger(float value) + { + return int(std::ceil(value)); + } + + RMLUICORE_API int RoundDownToInteger(float value) + { + return int(std::floor(value)); + } + + RMLUICORE_API float DecomposeFractionalIntegral(float value, float* integral) + { + return std::modf(value, integral); + } + + RMLUICORE_API void SnapToPixelGrid(float& offset, float& width) + { + const float right_edge = offset + width; + offset = Math::Round(offset); + width = Math::Round(right_edge) - offset; + } + + RMLUICORE_API void SnapToPixelGrid(Vector2f& position, Vector2f& size) + { + const Vector2f bottom_right = position + size; + position = position.Round(); + size = bottom_right.Round() - position; + } + + RMLUICORE_API void SnapToPixelGrid(Rectanglef& rectangle) + { + rectangle = Rectanglef::FromCorners(rectangle.TopLeft().Round(), rectangle.BottomRight().Round()); + } + + RMLUICORE_API void ExpandToPixelGrid(Vector2f& position, Vector2f& size) + { + const Vector2f bottom_right = position + size; + position = Vector2f(std::floor(position.x), std::floor(position.y)); + size = Vector2f(std::ceil(bottom_right.x), std::ceil(bottom_right.y)) - position; + } + + RMLUICORE_API void ExpandToPixelGrid(Rectanglef& rectangle) + { + const Vector2f top_left = {std::floor(rectangle.Left()), std::floor(rectangle.Top())}; + const Vector2f bottom_right = {std::ceil(rectangle.Right()), std::ceil(rectangle.Bottom())}; + rectangle = Rectanglef::FromCorners(top_left, bottom_right); + } + + RMLUICORE_API int ToPowerOfTwo(int number) + { + // Check if the number is already a power of two. + if ((number & (number - 1)) == 0) + return number; + + // Assuming 31 useful bits in an int here ... ! + for (int i = 31; i >= 0; i--) + { + if (number & (1 << i)) + { + if (i == 31) + return 1 << 31; + else + return 1 << (i + 1); + } + } + + return 0; + } + + RMLUICORE_API int HexToDecimal(char hex_digit) + { + if (hex_digit >= '0' && hex_digit <= '9') + return hex_digit - '0'; + else if (hex_digit >= 'a' && hex_digit <= 'f') + return 10 + (hex_digit - 'a'); + else if (hex_digit >= 'A' && hex_digit <= 'F') + return 10 + (hex_digit - 'A'); + + return -1; + } + + RMLUICORE_API float RandomReal(float max_value) + { + return (std::rand() / (float)RAND_MAX) * max_value; + } + + RMLUICORE_API int RandomInteger(int max_value) + { + return (std::rand() % max_value); + } + + RMLUICORE_API bool RandomBool() + { + return RandomInteger(2) == 1; + } + + template <> + Vector2f Max(Vector2f a, Vector2f b) + { + return Vector2f(Max(a.x, b.x), Max(a.y, b.y)); + } + template <> + Vector2i Max(Vector2i a, Vector2i b) + { + return Vector2i(Max(a.x, b.x), Max(a.y, b.y)); + } + + template <> + Vector2f Min(Vector2f a, Vector2f b) + { + return Vector2f(Min(a.x, b.x), Min(a.y, b.y)); + } + template <> + Vector2i Min(Vector2i a, Vector2i b) + { + return Vector2i(Min(a.x, b.x), Min(a.y, b.y)); + } + + template <> + Vector2f Clamp(Vector2f value, Vector2f min, Vector2f max) + { + return Vector2f(Clamp(value.x, min.x, max.x), Clamp(value.y, min.y, max.y)); + } + template <> + Vector2i Clamp(Vector2i value, Vector2i min, Vector2i max) + { + return Vector2i(Clamp(value.x, min.x, max.x), Clamp(value.y, min.y, max.y)); + } + + ColourbPremultiplied RoundedLerp(float t, ColourbPremultiplied v0, ColourbPremultiplied v1) + { + return ColourbPremultiplied{ + static_cast(RoundToInteger(Lerp(t, static_cast(v0[0]), static_cast(v1[0])))), + static_cast(RoundToInteger(Lerp(t, static_cast(v0[1]), static_cast(v1[1])))), + static_cast(RoundToInteger(Lerp(t, static_cast(v0[2]), static_cast(v1[2])))), + static_cast(RoundToInteger(Lerp(t, static_cast(v0[3]), static_cast(v1[3])))), + }; + } + +} // namespace Math +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Memory.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Memory.cpp new file mode 100644 index 00000000..5cab60dc --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Memory.cpp @@ -0,0 +1,69 @@ +#include "Memory.h" +#include +#include +#include + +namespace Rml { + +namespace Detail { + + inline void* rmlui_align(size_t alignment, size_t size, void*& ptr, size_t& space) + { +#if defined(_MSC_VER) + return std::align(alignment, size, ptr, space); +#else + // std::align replacement to support compilers missing this feature. + // From https://gcc.gnu.org/bugzilla/show_bug.cgi?id=57350 + + uintptr_t pn = reinterpret_cast(ptr); + uintptr_t aligned = (pn + alignment - 1) & -alignment; + size_t padding = aligned - pn; + if (space < size + padding) + return nullptr; + space -= padding; + return ptr = reinterpret_cast(aligned); +#endif + } + + BasicStackAllocator::BasicStackAllocator(size_t N) : N(N), data((byte*)malloc(N)), p(data) {} + + BasicStackAllocator::~BasicStackAllocator() noexcept + { + RMLUI_ASSERT(p == data); + free(data); + } + + void* BasicStackAllocator::allocate(size_t alignment, size_t byte_size) + { + size_t available_space = N - ((byte*)p - data); + + if (rmlui_align(alignment, byte_size, p, available_space)) + { + void* result = p; + p = (byte*)p + byte_size; + return result; + } + + // Fall back to malloc + return malloc(byte_size); + } + + void BasicStackAllocator::deallocate(void* obj) noexcept + { + if (obj < data || obj >= data + N) + { + free(obj); + return; + } + p = obj; + } + + BasicStackAllocator& GetGlobalBasicStackAllocator() + { + static BasicStackAllocator stack_allocator(10 * 1024); + return stack_allocator; + } + +} // namespace Detail + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Memory.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Memory.h new file mode 100644 index 00000000..2f1894c6 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Memory.h @@ -0,0 +1,107 @@ +#pragma once + +#include "../../Include/RmlUi/Core/Traits.h" +#include "../../Include/RmlUi/Core/Types.h" + +namespace Rml { + +namespace Detail { + + /** + Basic stack allocator. + + A very cheap allocator which only moves a pointer up and down during allocation and deallocation, respectively. + The allocator is initialized with some fixed memory. If it runs out, it falls back to malloc. + + Warning: Using this is dangerous as deallocation must happen in exact reverse order of allocation. + + Do not use this class directly. + */ + class BasicStackAllocator { + public: + BasicStackAllocator(size_t N); + ~BasicStackAllocator() noexcept; + + void* allocate(size_t alignment, size_t byte_size); + void deallocate(void* obj) noexcept; + + private: + const size_t N; + byte* data; + void* p; + }; + + BasicStackAllocator& GetGlobalBasicStackAllocator(); + +} /* namespace Detail */ + +/** + Global stack allocator. + + Can very cheaply allocate memory using the global stack allocator. Memory will be allocated from the + heap on the very first construction of a global stack allocator, and will persist and be re-used after. + Falls back to malloc if there is not enough space left. + + Warning: Using this is dangerous as deallocation must happen in exact reverse order of allocation. + Memory is shared between different global stack allocators. Should only be used for highly localized code, + where memory is allocated and then quickly thrown away. +*/ + +template +class GlobalStackAllocator { +public: + using value_type = T; + + GlobalStackAllocator() = default; + template + constexpr GlobalStackAllocator(const GlobalStackAllocator&) noexcept + {} + + T* allocate(size_t num_objects) { return static_cast(Detail::GetGlobalBasicStackAllocator().allocate(alignof(T), num_objects * sizeof(T))); } + + void deallocate(T* ptr, size_t) noexcept { Detail::GetGlobalBasicStackAllocator().deallocate(ptr); } +}; + +template +bool operator==(const GlobalStackAllocator&, const GlobalStackAllocator&) +{ + return true; +} +template +bool operator!=(const GlobalStackAllocator&, const GlobalStackAllocator&) +{ + return false; +} + +/** + A poor man's dynamic array. + + Constructs N objects on initialization which are default initialized. Can not be resized. +*/ + +template +class DynamicArray : Alloc, NonCopyMoveable { +public: + DynamicArray(size_t N) : N(N) + { + p = Alloc::allocate(N); + for (size_t i = 0; i < N; i++) + new (p + i) T; + } + ~DynamicArray() noexcept + { + for (size_t i = 0; i < N; i++) + p[i].~T(); + Alloc::deallocate(p, N); + } + + T* data() noexcept { return p; } + + T& operator[](size_t i) noexcept { return p[i]; } + +private: + size_t N; + T* p; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/MeshUtilities.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/MeshUtilities.cpp new file mode 100644 index 00000000..3c4abb2c --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/MeshUtilities.cpp @@ -0,0 +1,140 @@ +#include "../../Include/RmlUi/Core/MeshUtilities.h" +#include "../../Include/RmlUi/Core/Box.h" +#include "../../Include/RmlUi/Core/Core.h" +#include "../../Include/RmlUi/Core/FontEngineInterface.h" +#include "../../Include/RmlUi/Core/Types.h" +#include "GeometryBackgroundBorder.h" + +namespace Rml { + +void MeshUtilities::GenerateQuad(Mesh& mesh, Vector2f origin, Vector2f dimensions, ColourbPremultiplied colour) +{ + GenerateQuad(mesh, origin, dimensions, colour, Vector2f(0, 0), Vector2f(1, 1)); +} + +void MeshUtilities::GenerateQuad(Mesh& mesh, Vector2f origin, Vector2f dimensions, ColourbPremultiplied colour, Vector2f top_left_texcoord, + Vector2f bottom_right_texcoord) +{ + const int v0 = (int)mesh.vertices.size(); + const int i0 = (int)mesh.indices.size(); + + mesh.vertices.resize(mesh.vertices.size() + 4); + mesh.indices.resize(mesh.indices.size() + 6); + Vertex* vertices = mesh.vertices.data(); + int* indices = mesh.indices.data(); + + vertices[v0 + 0].position = origin; + vertices[v0 + 0].colour = colour; + vertices[v0 + 0].tex_coord = top_left_texcoord; + + vertices[v0 + 1].position = Vector2f(origin.x + dimensions.x, origin.y); + vertices[v0 + 1].colour = colour; + vertices[v0 + 1].tex_coord = Vector2f(bottom_right_texcoord.x, top_left_texcoord.y); + + vertices[v0 + 2].position = origin + dimensions; + vertices[v0 + 2].colour = colour; + vertices[v0 + 2].tex_coord = bottom_right_texcoord; + + vertices[v0 + 3].position = Vector2f(origin.x, origin.y + dimensions.y); + vertices[v0 + 3].colour = colour; + vertices[v0 + 3].tex_coord = Vector2f(top_left_texcoord.x, bottom_right_texcoord.y); + + indices[i0 + 0] = v0 + 0; + indices[i0 + 1] = v0 + 3; + indices[i0 + 2] = v0 + 1; + + indices[i0 + 3] = v0 + 1; + indices[i0 + 4] = v0 + 3; + indices[i0 + 5] = v0 + 2; +} + +void MeshUtilities::GenerateLine(Mesh& mesh, Vector2f position, Vector2f size, ColourbPremultiplied color) +{ + Math::SnapToPixelGrid(position, size); + MeshUtilities::GenerateQuad(mesh, position, size, color); +} + +void MeshUtilities::GenerateBackgroundBorder(Mesh& out_mesh, const RenderBox& render_box, ColourbPremultiplied background_color, + const ColourbPremultiplied border_colors[4]) +{ + RMLUI_ASSERT(border_colors); + + Vector& vertices = out_mesh.vertices; + Vector& indices = out_mesh.indices; + + const EdgeSizes& border_widths = render_box.GetBorderWidths(); + int num_borders = 0; + for (int i = 0; i < 4; i++) + if (border_colors[i].alpha > 0 && border_widths[i] > 0) + num_borders += 1; + + const Vector2f fill_size = render_box.GetFillSize(); + const bool has_background = (background_color.alpha > 0 && fill_size.x > 0 && fill_size.y > 0); + const bool has_border = (num_borders > 0); + + if (!has_background && !has_border) + return; + + // Reserve geometry. A conservative estimate, does not take border-radii into account and assumes same-colored borders. + const int estimated_num_vertices = 4 * int(has_background) + 2 * num_borders; + const int estimated_num_triangles = 2 * int(has_background) + 2 * num_borders; + vertices.reserve((int)vertices.size() + estimated_num_vertices); + indices.reserve((int)indices.size() + 3 * estimated_num_triangles); + + // Generate the geometry. + GeometryBackgroundBorder geometry(vertices, indices); + const BorderMetrics metrics = + GeometryBackgroundBorder::ComputeBorderMetrics(render_box.GetBorderOffset(), border_widths, fill_size, render_box.GetBorderRadius()); + + if (has_background) + geometry.DrawBackground(metrics, background_color); + + if (has_border) + geometry.DrawBorder(metrics, border_widths, border_colors); + +#if 0 + // Debug draw vertices + if (render_box.border_radius != CornerSizes{}) + { + const int num_vertices = (int)vertices.size(); + const int num_indices = (int)indices.size(); + vertices.reserve(num_vertices + 4 * num_vertices); + indices.reserve(num_indices + 6 * num_indices); + + for (int i = 0; i < num_vertices; i++) + MeshUtilities::GenerateQuad(out_mesh, vertices[i].position, Vector2f(3, 3), ColourbPremultiplied(255, 0, (i % 2) == 0 ? 0 : 255)); + } +#endif + +#ifdef RMLUI_DEBUG + const int num_vertices = (int)vertices.size(); + for (int index : indices) + { + RMLUI_ASSERT(index < num_vertices); + } +#endif +} + +void MeshUtilities::GenerateBackground(Mesh& out_mesh, const RenderBox& render_box, ColourbPremultiplied color) +{ + const Vector2f fill_size = render_box.GetFillSize(); + const bool has_background = (color.alpha > 0 && fill_size.x > 0 && fill_size.y > 0); + if (!has_background) + return; + + const BorderMetrics metrics = GeometryBackgroundBorder::ComputeBorderMetrics(render_box.GetBorderOffset(), render_box.GetBorderWidths(), + fill_size, render_box.GetBorderRadius()); + + Vector& vertices = out_mesh.vertices; + Vector& indices = out_mesh.indices; + + // Reserve geometry. A conservative estimate, does not take border-radii into account. + vertices.reserve((int)vertices.size() + 4); + indices.reserve((int)indices.size() + 6); + + // Generate the geometry + GeometryBackgroundBorder geometry(vertices, indices); + geometry.DrawBackground(metrics, color); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ObserverPtr.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ObserverPtr.cpp new file mode 100644 index 00000000..5035ae5c --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ObserverPtr.cpp @@ -0,0 +1,56 @@ +#include "../../Include/RmlUi/Core/ObserverPtr.h" +#include "Pool.h" + +namespace Rml { + +struct ObserverPtrData { + bool is_shutdown = false; + Pool block_pool{128, true}; +}; +static ObserverPtrData* observer_ptr_data = nullptr; + +void Detail::DeallocateObserverPtrBlockIfEmpty(ObserverPtrBlock* block) +{ + RMLUI_ASSERT(block->num_observers >= 0); + if (block->num_observers == 0 && block->pointed_to_object == nullptr) + { + observer_ptr_data->block_pool.DestroyAndDeallocate(block); + if (observer_ptr_data->is_shutdown && observer_ptr_data->block_pool.GetNumAllocatedObjects() == 0) + { + delete observer_ptr_data; + observer_ptr_data = nullptr; + } + } +} + +void Detail::InitializeObserverPtrPool() +{ + if (!observer_ptr_data) + observer_ptr_data = new ObserverPtrData; + observer_ptr_data->is_shutdown = false; +} + +void Detail::ShutdownObserverPtrPool() +{ + if (observer_ptr_data->block_pool.GetNumAllocatedObjects() == 0) + { + delete observer_ptr_data; + observer_ptr_data = nullptr; + } + else + { + // This pool must outlive all other global variables that derive from EnableObserverPtr. This even includes user + // variables which we have no control over. So if there are any objects still alive, let the pool garbage + // collect itself when all references to it are gone. It is somewhat unreasonable to expect that no observer + // pointers remain, particularly because that means no objects derived from Rml::EventListener can be alive in + // user space, which can be a hassle to ensure and is otherwise pretty innocent. + observer_ptr_data->is_shutdown = true; + } +} + +Detail::ObserverPtrBlock* Detail::AllocateObserverPtrBlock() +{ + return observer_ptr_data->block_pool.AllocateAndConstruct(); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Plugin.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Plugin.cpp new file mode 100644 index 00000000..bb5f4b10 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Plugin.cpp @@ -0,0 +1,34 @@ +#include "../../Include/RmlUi/Core/Plugin.h" + +namespace Rml { + +Plugin::~Plugin() {} + +int Plugin::GetEventClasses() +{ + return EVT_ALL; +} + +void Plugin::OnInitialise() {} + +void Plugin::OnShutdown() {} + +void Plugin::OnContextCreate(Context* /*context*/) {} + +void Plugin::OnContextDestroy(Context* /*context*/) {} + +void Plugin::OnDocumentOpen(Context* /*context*/, const String& /*document_path*/) {} + +void Plugin::OnDocumentLoad(ElementDocument* /*document*/) {} + +void Plugin::OnDocumentUnload(ElementDocument* /*document*/) {} + +void Plugin::OnElementCreate(Element* /*element*/) {} + +void Plugin::OnElementDestroy(Element* /*element*/) {} + +void Plugin::OnDataModelCreate(Context* /*context*/, const String& /*name*/) {} + +void Plugin::OnDataModelDestroy(Context* /*context*/, const String& /*name*/) {} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PluginRegistry.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PluginRegistry.cpp new file mode 100644 index 00000000..23986799 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PluginRegistry.cpp @@ -0,0 +1,137 @@ +#include "PluginRegistry.h" +#include "../../Include/RmlUi/Core/Plugin.h" +#include "ControlledLifetimeResource.h" +#include + +namespace Rml { + +struct PluginVectors { + Vector basic; + Vector document; + Vector element; + Vector data_model; +}; + +static ControlledLifetimeResource plugin_vectors; + +static void EnsurePluginVectorsInitialized() +{ + if (!plugin_vectors) + { + plugin_vectors.Initialize(); + } +} + +void PluginRegistry::RegisterPlugin(Plugin* plugin) +{ + EnsurePluginVectorsInitialized(); + + int event_classes = plugin->GetEventClasses(); + + if (event_classes & Plugin::EVT_BASIC) + plugin_vectors->basic.push_back(plugin); + if (event_classes & Plugin::EVT_DOCUMENT) + plugin_vectors->document.push_back(plugin); + if (event_classes & Plugin::EVT_ELEMENT) + plugin_vectors->element.push_back(plugin); + if (event_classes & Plugin::EVT_DATA_MODEL) + plugin_vectors->data_model.push_back(plugin); +} + +void PluginRegistry::UnregisterPlugin(Plugin* plugin) +{ + // Safe to call after NotifyShutdown() (e.g. from a plugin destructor): the backing storage has been released + // and there is nothing left to unregister. + if (!plugin_vectors) + return; + + auto erase_value = [](Vector& container, Plugin* value) { + container.erase(std::remove(container.begin(), container.end(), value), container.end()); + }; + + int event_classes = plugin->GetEventClasses(); + if (event_classes & Plugin::EVT_BASIC) + erase_value(plugin_vectors->basic, plugin); + if (event_classes & Plugin::EVT_DOCUMENT) + erase_value(plugin_vectors->document, plugin); + if (event_classes & Plugin::EVT_ELEMENT) + erase_value(plugin_vectors->element, plugin); + if (event_classes & Plugin::EVT_DATA_MODEL) + erase_value(plugin_vectors->data_model, plugin); +} + +void PluginRegistry::NotifyInitialise() +{ + EnsurePluginVectorsInitialized(); + + for (Plugin* plugin : plugin_vectors->basic) + plugin->OnInitialise(); +} + +void PluginRegistry::NotifyShutdown() +{ + while (!plugin_vectors->basic.empty()) + { + Plugin* plugin = plugin_vectors->basic.back(); + PluginRegistry::UnregisterPlugin(plugin); + plugin->OnShutdown(); + } + + plugin_vectors.Shutdown(); +} + +void PluginRegistry::NotifyContextCreate(Context* context) +{ + for (Plugin* plugin : plugin_vectors->basic) + plugin->OnContextCreate(context); +} + +void PluginRegistry::NotifyContextDestroy(Context* context) +{ + for (Plugin* plugin : plugin_vectors->basic) + plugin->OnContextDestroy(context); +} + +void PluginRegistry::NotifyDocumentOpen(Context* context, const String& document_path) +{ + for (Plugin* plugin : plugin_vectors->document) + plugin->OnDocumentOpen(context, document_path); +} + +void PluginRegistry::NotifyDocumentLoad(ElementDocument* document) +{ + for (Plugin* plugin : plugin_vectors->document) + plugin->OnDocumentLoad(document); +} + +void PluginRegistry::NotifyDocumentUnload(ElementDocument* document) +{ + for (Plugin* plugin : plugin_vectors->document) + plugin->OnDocumentUnload(document); +} + +void PluginRegistry::NotifyElementCreate(Element* element) +{ + for (Plugin* plugin : plugin_vectors->element) + plugin->OnElementCreate(element); +} + +void PluginRegistry::NotifyElementDestroy(Element* element) +{ + for (Plugin* plugin : plugin_vectors->element) + plugin->OnElementDestroy(element); +} + +void PluginRegistry::NotifyDataModelCreate(Context* context, const String& name) +{ + for (Plugin* plugin : plugin_vectors->data_model) + plugin->OnDataModelCreate(context, name); +} + +void PluginRegistry::NotifyDataModelDestroy(Context* context, const String& name) +{ + for (Plugin* plugin : plugin_vectors->data_model) + plugin->OnDataModelDestroy(context, name); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PluginRegistry.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PluginRegistry.h new file mode 100644 index 00000000..783b6f52 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PluginRegistry.h @@ -0,0 +1,48 @@ +#pragma once + +#include "../../Include/RmlUi/Core/Types.h" + +namespace Rml { + +class Context; +class Element; +class ElementDocument; +class Plugin; + +class PluginRegistry { +public: + static void RegisterPlugin(Plugin* plugin); + static void UnregisterPlugin(Plugin* plugin); + + /// Calls OnInitialise() on all plugins. + static void NotifyInitialise(); + /// Calls OnShutdown() on all plugins. + static void NotifyShutdown(); + + /// Calls OnContextCreate() on all plugins. + static void NotifyContextCreate(Context* context); + /// Calls OnContextDestroy() on all plugins. + static void NotifyContextDestroy(Context* context); + + /// Calls OnDocumentOpen() on all plugins. + static void NotifyDocumentOpen(Context* context, const String& document_path); + /// Calls OnDocumentLoad() on all plugins. + static void NotifyDocumentLoad(ElementDocument* document); + /// Calls OnDocumentUnload() on all plugins. + static void NotifyDocumentUnload(ElementDocument* document); + + /// Calls OnElementCreate() on all plugins. + static void NotifyElementCreate(Element* element); + /// Calls OnElementDestroy() on all plugins. + static void NotifyElementDestroy(Element* element); + + /// Calls OnDataModelCreate() on all plugins. + static void NotifyDataModelCreate(Context* context, const String& name); + /// Calls OnDataModelDestroy() on all plugins. + static void NotifyDataModelDestroy(Context* context, const String& name); + +private: + PluginRegistry() = delete; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Pool.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Pool.h new file mode 100644 index 00000000..72b689d4 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Pool.h @@ -0,0 +1,113 @@ +#pragma once + +#include "../../Include/RmlUi/Core/Debug.h" +#include "../../Include/RmlUi/Core/Header.h" +#include "../../Include/RmlUi/Core/Traits.h" +#include "../../Include/RmlUi/Core/Types.h" + +namespace Rml { + +template +class Pool { +private: + static constexpr size_t N = sizeof(PoolType); + static constexpr size_t A = alignof(PoolType); + + class PoolNode : public NonCopyMoveable { + public: + alignas(A) unsigned char object[N]; + PoolNode* previous; + PoolNode* next; + }; + + class PoolChunk : public NonCopyMoveable { + public: + PoolNode* chunk; + PoolChunk* next; + }; + +public: + /** + Iterator objects are used for safe traversal of the allocated + members of a pool. + */ + class Iterator { + friend class Rml::Pool; + + public: + /// Increments the iterator to reference the next node in the + /// linked list. It is an error to call this function if the + /// node this iterator references is invalid. + inline void operator++() + { + RMLUI_ASSERT(node != nullptr); + node = node->next; + } + /// Returns true if it is OK to deference or increment this + /// iterator. + explicit inline operator bool() { return (node != nullptr); } + + /// Returns the object referenced by the iterator's current + /// node. + inline PoolType& operator*() { return *reinterpret_cast(node->object); } + /// Returns a pointer to the object referenced by the + /// iterator's current node. + inline PoolType* operator->() { return reinterpret_cast(node->object); } + + private: + // Constructs an iterator referencing the given node. + inline Iterator(PoolNode* node) { this->node = node; } + + PoolNode* node; + }; + + Pool(int chunk_size = 0, bool grow = false); + ~Pool(); + + /// Initialises the pool to a given size. + void Initialise(int chunk_size, bool grow = false); + + /// Returns the head of the linked list of allocated objects. + inline Iterator Begin(); + + /// Attempts to allocate an object into a free slot in the memory pool and construct it using the given arguments. + /// If the process is successful, the newly constructed object is returned. Otherwise, if the process fails due to + /// no free objects being available, nullptr is returned. + template + inline PoolType* AllocateAndConstruct(Args&&... args); + + /// Deallocates the object pointed to by the given iterator. + inline void DestroyAndDeallocate(Iterator& iterator); + /// Deallocates the given object. + inline void DestroyAndDeallocate(PoolType* object); + + /// Returns the number of objects in the pool. + inline int GetSize() const; + /// Returns the number of object chunks in the pool. + inline int GetNumChunks() const; + /// Returns the number of allocated objects in the pool. + inline int GetNumAllocatedObjects() const; + +private: + // Creates a new pool chunk and appends its nodes to the beginning of the free list. + void CreateChunk(); + + int chunk_size; + bool grow; + + PoolChunk* pool; + + // The heads of the two linked lists. + PoolNode* first_allocated_node; + PoolNode* first_free_node; + + int num_allocated_objects; + +#ifdef RMLUI_DEBUG + int max_num_allocated_objects = 0; +#endif +}; + +} // namespace Rml + +#include "Pool.inl" diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Pool.inl b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Pool.inl new file mode 100644 index 00000000..03c14ee6 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Pool.inl @@ -0,0 +1,232 @@ +namespace Rml { + +template < typename PoolType > +Pool< PoolType >::Pool(int _chunk_size, bool _grow) +{ + chunk_size = 0; + grow = _grow; + + num_allocated_objects = 0; + + pool = nullptr; + first_allocated_node = nullptr; + first_free_node = nullptr; + + if (_chunk_size > 0) + Initialise(_chunk_size, _grow); +} + +template < typename PoolType > +Pool< PoolType >::~Pool() +{ + RMLUI_ASSERT(num_allocated_objects == 0); + + PoolChunk* chunk = pool; + while (chunk) + { + PoolChunk* next_chunk = chunk->next; + + delete[] chunk->chunk; + delete chunk; + + chunk = next_chunk; + } +} + +// Initialises the pool to a given size. +template < typename PoolType > +void Pool< PoolType >::Initialise(int _chunk_size, bool _grow) +{ + // Should resize the pool here ... ? + if (chunk_size > 0) + return; + + if (_chunk_size <= 0) + return; + + grow = _grow; + chunk_size = _chunk_size; + pool = nullptr; + + // Create the initial chunk. + CreateChunk(); +} + +// Returns the head of the linked list of allocated objects. +template < typename PoolType > +typename Pool< PoolType >::Iterator Pool< PoolType >::Begin() +{ + return typename Pool< PoolType >::Iterator(first_allocated_node); +} + +// Attempts to allocate a deallocated object in the memory pool. +template +template +inline PoolType* Pool::AllocateAndConstruct(Args&&... args) +{ + // We can't allocate a new object if the deallocated list is empty. + if (first_free_node == nullptr) + { + // Attempt to grow the pool first. + if (grow) + { + CreateChunk(); + if (first_free_node == nullptr) + return nullptr; + } + else + return nullptr; + } + + // We're about to allocate an object. + ++num_allocated_objects; + +#ifdef RMLUI_DEBUG + if (num_allocated_objects > max_num_allocated_objects) + max_num_allocated_objects = num_allocated_objects; +#endif + + // This one! + PoolNode* allocated_object = first_free_node; + + // Remove the newly allocated object from the list of deallocated objects. + first_free_node = first_free_node->next; + if (first_free_node != nullptr) + first_free_node->previous = nullptr; + + // Add the newly allocated object to the head of the list of allocated objects. + if (first_allocated_node != nullptr) + { + allocated_object->previous = nullptr; + allocated_object->next = first_allocated_node; + first_allocated_node->previous = allocated_object; + } + else + { + // This object is the only allocated object. + allocated_object->previous = nullptr; + allocated_object->next = nullptr; + } + + first_allocated_node = allocated_object; + + return new (allocated_object->object) PoolType(std::forward(args)...); +} + +// Deallocates the object pointed to by the given iterator. +template < typename PoolType > +void Pool< PoolType >::DestroyAndDeallocate(Iterator& iterator) +{ + // We're about to deallocate an object. + --num_allocated_objects; + + PoolNode* object = iterator.node; + reinterpret_cast(object->object)->~PoolType(); + + // Get the previous and next pointers now, because they will be overwritten + // before we're finished. + PoolNode* previous_object = object->previous; + PoolNode* next_object = object->next; + + if (previous_object != nullptr) + previous_object->next = next_object; + else + { + RMLUI_ASSERT(first_allocated_node == object); + first_allocated_node = next_object; + } + + if (next_object != nullptr) + next_object->previous = previous_object; + + // Insert the freed node at the beginning of the free object list. + if (first_free_node == nullptr) + { + object->previous = nullptr; + object->next = nullptr; + } + else + { + object->previous = nullptr; + object->next = first_free_node; + } + + first_free_node = object; + + // Increment the iterator, so it points to the next active object. + iterator.node = next_object; +} + +// Deallocates the given object. +template < typename PoolType > +void Pool< PoolType >::DestroyAndDeallocate(PoolType* object) +{ + // This assumes the object has the same address as the node, which will be + // true as long as the struct definition does not change. + Iterator iterator((PoolNode*) object); + DestroyAndDeallocate(iterator); +} + +// Returns the number of objects in the pool. +template < typename PoolType > +int Pool< PoolType >::GetSize() const +{ + return chunk_size * GetNumChunks(); +} + +/// Returns the number of object chunks in the pool. +template < typename PoolType > +int Pool< PoolType >::GetNumChunks() const +{ + int num_chunks = 0; + + PoolChunk* chunk = pool; + while (chunk != nullptr) + { + ++num_chunks; + chunk = chunk->next; + } + + return num_chunks; +} + +// Returns the number of allocated objects in the pool. +template < typename PoolType > +int Pool< PoolType >::GetNumAllocatedObjects() const +{ + return num_allocated_objects; +} + +// Creates a new pool chunk and appends its nodes to the beginning of the free list. +template < typename PoolType > +void Pool< PoolType >::CreateChunk() +{ + if (chunk_size <= 0) + return; + + // Create the new chunk and mark it as the first chunk. + PoolChunk* new_chunk = new PoolChunk(); + new_chunk->next = pool; + pool = new_chunk; + + // Create chunk's pool nodes. + new_chunk->chunk = new PoolNode[chunk_size]; + + // Initialise the linked list. + for (int i = 0; i < chunk_size; i++) + { + if (i == 0) + new_chunk->chunk[i].previous = nullptr ; + else + new_chunk->chunk[i].previous = &new_chunk->chunk[i - 1]; + + if (i == chunk_size - 1) + new_chunk->chunk[i].next = first_free_node; + else + new_chunk->chunk[i].next = &new_chunk->chunk[i + 1]; + } + + first_free_node = new_chunk->chunk; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Profiling.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Profiling.cpp new file mode 100644 index 00000000..165098f9 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Profiling.cpp @@ -0,0 +1,21 @@ +#include "../../Include/RmlUi/Core/Profiling.h" + +#ifdef RMLUI_TRACY_MEMORY_PROFILING + #include + #include + +void* operator new(size_t n) +{ + // Overload global new and delete for memory inspection + void* ptr = std::malloc(n); + TracyAlloc(ptr, n); + return ptr; +} + +void operator delete(void* ptr) noexcept +{ + TracyFree(ptr); + std::free(ptr); +} + +#endif diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertiesIterator.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertiesIterator.h new file mode 100644 index 00000000..2252c70b --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertiesIterator.h @@ -0,0 +1,78 @@ +#pragma once + +#include "../../Include/RmlUi/Core/PropertyIdSet.h" +#include "../../Include/RmlUi/Core/Types.h" + +namespace Rml { + +// An iterator for local properties defined on an element. +// Note: Modifying the underlying style invalidates the iterator. +class PropertiesIterator { +public: + using ValueType = Pair; + using PropertyIt = PropertyMap::const_iterator; + + PropertiesIterator(PropertyIt it_style, PropertyIt it_style_end, PropertyIt it_definition, PropertyIt it_definition_end) : + it_style(it_style), it_style_end(it_style_end), it_definition(it_definition), it_definition_end(it_definition_end) + { + ProceedToNextValid(); + } + + PropertiesIterator& operator++() + { + if (it_style != it_style_end) + // We iterate over the local style properties first + ++it_style; + else + // .. and then over the properties given by the element's definition + ++it_definition; + // If we reached the end of one of the iterator pairs, we need to continue iteration on the next pair. + ProceedToNextValid(); + return *this; + } + + ValueType operator*() const + { + if (it_style != it_style_end) + return {it_style->first, it_style->second}; + return {it_definition->first, it_definition->second}; + } + + bool AtEnd() const { return at_end; } + +private: + PropertyIdSet iterated_properties; + PropertyIt it_style, it_style_end; + PropertyIt it_definition, it_definition_end; + bool at_end = false; + + inline bool IsDirtyRemove(PropertyId id) + { + if (!iterated_properties.Contains(id)) + { + iterated_properties.Insert(id); + return true; + } + return false; + } + + inline void ProceedToNextValid() + { + for (; it_style != it_style_end; ++it_style) + { + if (IsDirtyRemove(it_style->first)) + return; + } + + for (; it_definition != it_definition_end; ++it_definition) + { + if (IsDirtyRemove(it_definition->first)) + return; + } + + // All iterators are now at the end + at_end = true; + } +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertiesIteratorView.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertiesIteratorView.cpp new file mode 100644 index 00000000..ccb9d2e7 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertiesIteratorView.cpp @@ -0,0 +1,45 @@ +#include "../../Include/RmlUi/Core/PropertiesIteratorView.h" +#include "../../Include/RmlUi/Core/StyleSheetSpecification.h" +#include "PropertiesIterator.h" + +namespace Rml { + +PropertiesIteratorView::PropertiesIteratorView(UniquePtr ptr) : ptr(std::move(ptr)) {} + +PropertiesIteratorView::PropertiesIteratorView(PropertiesIteratorView&& other) noexcept : ptr(std::move(other.ptr)) {} + +PropertiesIteratorView& PropertiesIteratorView::operator=(PropertiesIteratorView&& other) noexcept +{ + ptr = std::move(other.ptr); + return *this; +} + +PropertiesIteratorView::~PropertiesIteratorView() {} + +PropertiesIteratorView& PropertiesIteratorView::operator++() +{ + ++(*ptr); + return *this; +} + +PropertyId PropertiesIteratorView::GetId() const +{ + return (*(*ptr)).first; +} + +const String& PropertiesIteratorView::GetName() const +{ + return StyleSheetSpecification::GetPropertyName(GetId()); +} + +const Property& PropertiesIteratorView::GetProperty() const +{ + return (*(*ptr)).second; +} + +bool PropertiesIteratorView::AtEnd() const +{ + return ptr->AtEnd(); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Property.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Property.cpp new file mode 100644 index 00000000..c49e239e --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Property.cpp @@ -0,0 +1,33 @@ +#include "../../Include/RmlUi/Core/Property.h" +#include "../../Include/RmlUi/Core/PropertyDefinition.h" + +namespace Rml { + +Property::Property() : unit(Unit::UNKNOWN), specificity(-1) +{ + definition = nullptr; + parser_index = -1; +} + +String Property::ToString() const +{ + if (!definition) + return value.Get() + Rml::ToString(unit); + + String string; + definition->GetValue(string, *this); + return string; +} + +NumericValue Property::GetNumericValue() const +{ + NumericValue result; + if (Any(unit & Unit::NUMERIC)) + { + if (value.GetInto(result.number)) + result.unit = unit; + } + return result; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyDefinition.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyDefinition.cpp new file mode 100644 index 00000000..cce6d3b3 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyDefinition.cpp @@ -0,0 +1,168 @@ +#include "../../Include/RmlUi/Core/PropertyDefinition.h" +#include "../../Include/RmlUi/Core/Log.h" +#include "../../Include/RmlUi/Core/StyleSheetSpecification.h" + +namespace Rml { + +PropertyDefinition::PropertyDefinition(PropertyId id, const String& _default_value, bool _inherited, bool _forces_layout) : + id(id), default_value(_default_value, Unit::UNKNOWN), relative_target(RelativeTarget::None) +{ + inherited = _inherited; + forces_layout = _forces_layout; + default_value.definition = this; +} + +PropertyDefinition::~PropertyDefinition() {} + +PropertyDefinition& PropertyDefinition::AddParser(const String& parser_name, const String& parser_parameters) +{ + ParserState new_parser; + + // Fetch the parser. + new_parser.parser = StyleSheetSpecification::GetParser(parser_name); + if (new_parser.parser == nullptr) + { + Log::Message(Log::LT_ERROR, "Property was registered with invalid parser '%s'.", parser_name.c_str()); + return *this; + } + + // Split the parameter list, and set up the map. + if (!parser_parameters.empty()) + { + StringList parameter_list; + StringUtilities::ExpandString(parameter_list, parser_parameters); + + int parameter_value = 0; + for (const String& parameter : parameter_list) + { + // Look for an optional parameter value such as in "normal=400". + const size_t i_equal = parameter.find('='); + if (i_equal != String::npos) + { + if (!TypeConverter::Convert(parameter.substr(i_equal + 1), parameter_value)) + { + Log::Message(Log::LT_ERROR, "Parser was added with invalid parameter '%s'.", parameter.c_str()); + return *this; + } + } + + new_parser.parameters[parameter.substr(0, i_equal)] = parameter_value; + parameter_value += 1; + } + } + + const int parser_index = (int)parsers.size(); + parsers.push_back(new_parser); + + // If the default value has not been parsed successfully yet, run it through the new parser. + if (default_value.unit == Unit::UNKNOWN) + { + String unparsed_value = default_value.value.Get(); + if (new_parser.parser->ParseValue(default_value, unparsed_value, new_parser.parameters)) + { + default_value.parser_index = parser_index; + } + else + { + default_value.value = unparsed_value; + default_value.unit = Unit::UNKNOWN; + } + } + + return *this; +} + +bool PropertyDefinition::ParseValue(Property& property, const String& value) const +{ + for (size_t i = 0; i < parsers.size(); i++) + { + if (parsers[i].parser->ParseValue(property, value, parsers[i].parameters)) + { + property.definition = this; + property.parser_index = (int)i; + return true; + } + } + + property.unit = Unit::UNKNOWN; + return false; +} + +bool PropertyDefinition::GetValue(String& value, const Property& property) const +{ + value = property.value.Get(); + + switch (property.unit) + { + case Unit::KEYWORD: + { + int parser_index = property.parser_index; + if (parser_index < 0 || parser_index >= (int)parsers.size()) + { + // Look for the keyword parser in the property's list of parsers + const auto* keyword_parser = StyleSheetSpecification::GetParser("keyword"); + for (int i = 0; i < (int)parsers.size(); i++) + { + if (parsers[i].parser == keyword_parser) + { + parser_index = i; + break; + } + } + // If we couldn't find it, exit now + if (parser_index < 0 || parser_index >= (int)parsers.size()) + return false; + } + + int keyword = property.value.Get(); + for (const auto& name_keyword : parsers[parser_index].parameters) + { + if (name_keyword.second == keyword) + { + value = name_keyword.first; + break; + } + } + + return false; + } + break; + + default: value += ToString(property.unit); break; + } + + return true; +} + +bool PropertyDefinition::IsInherited() const +{ + return inherited; +} + +bool PropertyDefinition::IsLayoutForced() const +{ + return forces_layout; +} + +const Property* PropertyDefinition::GetDefaultValue() const +{ + return &default_value; +} + +RelativeTarget PropertyDefinition::GetRelativeTarget() const +{ + return relative_target; +} + +PropertyId PropertyDefinition::GetId() const +{ + return id; +} + +PropertyDefinition& PropertyDefinition::SetRelativeTarget(RelativeTarget relative_target) +{ + this->relative_target = relative_target; + return *this; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyDictionary.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyDictionary.cpp new file mode 100644 index 00000000..ca55e5e3 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyDictionary.cpp @@ -0,0 +1,75 @@ +#include "../../Include/RmlUi/Core/PropertyDictionary.h" +#include "../../Include/RmlUi/Core/ID.h" + +namespace Rml { + +PropertyDictionary::PropertyDictionary() {} + +void PropertyDictionary::SetProperty(PropertyId id, const Property& property) +{ + RMLUI_ASSERT(id != PropertyId::Invalid); + properties[id] = property; +} + +void PropertyDictionary::RemoveProperty(PropertyId id) +{ + RMLUI_ASSERT(id != PropertyId::Invalid); + properties.erase(id); +} + +const Property* PropertyDictionary::GetProperty(PropertyId id) const +{ + PropertyMap::const_iterator iterator = properties.find(id); + if (iterator == properties.end()) + return nullptr; + + return &(*iterator).second; +} + +int PropertyDictionary::GetNumProperties() const +{ + return (int)properties.size(); +} + +const PropertyMap& PropertyDictionary::GetProperties() const +{ + return properties; +} + +void PropertyDictionary::Import(const PropertyDictionary& other, int property_specificity) +{ + for (const auto& pair : other.properties) + { + const PropertyId id = pair.first; + const Property& property = pair.second; + SetProperty(id, property, property_specificity > 0 ? property_specificity : property.specificity); + } +} + +void PropertyDictionary::Merge(const PropertyDictionary& other, int specificity_offset) +{ + for (const auto& pair : other.properties) + { + const PropertyId id = pair.first; + const Property& property = pair.second; + SetProperty(id, property, property.specificity + specificity_offset); + } +} + +void PropertyDictionary::SetSourceOfAllProperties(const SharedPtr& property_source) +{ + for (auto& p : properties) + p.second.source = property_source; +} + +void PropertyDictionary::SetProperty(PropertyId id, const Property& property, int specificity) +{ + PropertyMap::iterator iterator = properties.find(id); + if (iterator != properties.end() && iterator->second.specificity > specificity) + return; + + Property& new_property = (properties[id] = property); + new_property.specificity = specificity; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserAnimation.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserAnimation.cpp new file mode 100644 index 00000000..3398d69d --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserAnimation.cpp @@ -0,0 +1,352 @@ +#include "PropertyParserAnimation.h" +#include "../../Include/RmlUi/Core/PropertyDefinition.h" +#include "../../Include/RmlUi/Core/PropertyIdSet.h" +#include "../../Include/RmlUi/Core/StringUtilities.h" +#include "../../Include/RmlUi/Core/StyleSheetSpecification.h" +#include "PropertyShorthandDefinition.h" + +namespace Rml { + +enum class KeywordType { None, Tween, All, Alternate, Infinite, Paused }; + +struct Keyword { + KeywordType type; + Tween tween; + Keyword(Tween tween) : type(KeywordType::Tween), tween(tween) {} + Keyword(KeywordType type) : type(type) {} + + bool ValidTransition() const { return type == KeywordType::None || type == KeywordType::Tween || type == KeywordType::All; } + bool ValidAnimation() const + { + return type == KeywordType::None || type == KeywordType::Tween || type == KeywordType::Alternate || type == KeywordType::Infinite || + type == KeywordType::Paused; + } +}; + +struct PropertyParserAnimationData { + const UnorderedMap keywords = { + {"none", {KeywordType::None}}, + {"all", {KeywordType::All}}, + {"alternate", {KeywordType::Alternate}}, + {"infinite", {KeywordType::Infinite}}, + {"paused", {KeywordType::Paused}}, + + {"back-in", {Tween{Tween::Back, Tween::In}}}, + {"back-out", {Tween{Tween::Back, Tween::Out}}}, + {"back-in-out", {Tween{Tween::Back, Tween::InOut}}}, + + {"bounce-in", {Tween{Tween::Bounce, Tween::In}}}, + {"bounce-out", {Tween{Tween::Bounce, Tween::Out}}}, + {"bounce-in-out", {Tween{Tween::Bounce, Tween::InOut}}}, + + {"circular-in", {Tween{Tween::Circular, Tween::In}}}, + {"circular-out", {Tween{Tween::Circular, Tween::Out}}}, + {"circular-in-out", {Tween{Tween::Circular, Tween::InOut}}}, + + {"cubic-in", {Tween{Tween::Cubic, Tween::In}}}, + {"cubic-out", {Tween{Tween::Cubic, Tween::Out}}}, + {"cubic-in-out", {Tween{Tween::Cubic, Tween::InOut}}}, + + {"elastic-in", {Tween{Tween::Elastic, Tween::In}}}, + {"elastic-out", {Tween{Tween::Elastic, Tween::Out}}}, + {"elastic-in-out", {Tween{Tween::Elastic, Tween::InOut}}}, + + {"exponential-in", {Tween{Tween::Exponential, Tween::In}}}, + {"exponential-out", {Tween{Tween::Exponential, Tween::Out}}}, + {"exponential-in-out", {Tween{Tween::Exponential, Tween::InOut}}}, + + {"linear-in", {Tween{Tween::Linear, Tween::In}}}, + {"linear-out", {Tween{Tween::Linear, Tween::Out}}}, + {"linear-in-out", {Tween{Tween::Linear, Tween::InOut}}}, + + {"quadratic-in", {Tween{Tween::Quadratic, Tween::In}}}, + {"quadratic-out", {Tween{Tween::Quadratic, Tween::Out}}}, + {"quadratic-in-out", {Tween{Tween::Quadratic, Tween::InOut}}}, + + {"quartic-in", {Tween{Tween::Quartic, Tween::In}}}, + {"quartic-out", {Tween{Tween::Quartic, Tween::Out}}}, + {"quartic-in-out", {Tween{Tween::Quartic, Tween::InOut}}}, + + {"quintic-in", {Tween{Tween::Quintic, Tween::In}}}, + {"quintic-out", {Tween{Tween::Quintic, Tween::Out}}}, + {"quintic-in-out", {Tween{Tween::Quintic, Tween::InOut}}}, + + {"sine-in", {Tween{Tween::Sine, Tween::In}}}, + {"sine-out", {Tween{Tween::Sine, Tween::Out}}}, + {"sine-in-out", {Tween{Tween::Sine, Tween::InOut}}}, + }; +}; + +ControlledLifetimeResource PropertyParserAnimation::parser_data; + +void PropertyParserAnimation::Initialize() +{ + parser_data.Initialize(); +} + +void PropertyParserAnimation::Shutdown() +{ + parser_data.Shutdown(); +} + +PropertyParserAnimation::PropertyParserAnimation(Type type) : type(type) {} + +bool PropertyParserAnimation::ParseValue(Property& property, const String& value, const ParameterMap& /*parameters*/) const +{ + StringList list_of_values; + StringUtilities::ExpandString(list_of_values, value, ','); + + bool result = false; + if (type == ANIMATION_PARSER) + { + result = ParseAnimation(property, list_of_values); + } + else if (type == TRANSITION_PARSER) + { + result = ParseTransition(property, list_of_values); + } + + return result; +} + +bool PropertyParserAnimation::ParseAnimation(Property& property, const StringList& animation_values) +{ + AnimationList animation_list; + + for (const String& single_animation_value : animation_values) + { + Animation animation; + + StringList arguments; + StringUtilities::ExpandString(arguments, single_animation_value, ' '); + + bool duration_found = false; + bool delay_found = false; + bool num_iterations_found = false; + + for (auto& argument : arguments) + { + if (argument.empty()) + continue; + + // See if we have a or specifier as defined in keywords + auto it = parser_data->keywords.find(StringUtilities::ToLower(argument)); + if (it != parser_data->keywords.end() && it->second.ValidAnimation()) + { + switch (it->second.type) + { + case KeywordType::None: + { + if (animation_list.size() > 0) // The none keyword can not be part of multiple definitions + return false; + property = Property{AnimationList{}, Unit::ANIMATION}; + return true; + } + break; + case KeywordType::Tween: animation.tween = it->second.tween; break; + case KeywordType::Alternate: animation.alternate = true; break; + case KeywordType::Infinite: + if (num_iterations_found) + return false; + animation.num_iterations = -1; + num_iterations_found = true; + break; + case KeywordType::Paused: animation.paused = true; break; + default: break; + } + } + else + { + // Either , , or a + float number = 0.0f; + int count = 0; + + if (sscanf(argument.c_str(), "%fs%n", &number, &count) == 1) + { + // Found a number, if there was an 's' unit, count will be positive + if (count > 0) + { + // Duration or delay was assigned + if (!duration_found) + { + duration_found = true; + animation.duration = number; + } + else if (!delay_found) + { + delay_found = true; + animation.delay = number; + } + else + return false; + } + else + { + // No 's' unit means num_iterations was found + if (!num_iterations_found) + { + animation.num_iterations = Math::RoundToInteger(number); + num_iterations_found = true; + } + else + return false; + } + } + else + { + // Must be an animation name + animation.name = argument; + } + } + } + + // Validate the parsed transition + if (animation.name.empty() || animation.duration <= 0.0f || (animation.num_iterations < -1 || animation.num_iterations == 0)) + { + return false; + } + + animation_list.push_back(std::move(animation)); + } + + property.value = std::move(animation_list); + property.unit = Unit::ANIMATION; + + return true; +} + +bool PropertyParserAnimation::ParseTransition(Property& property, const StringList& transition_values) +{ + TransitionList transition_list{false, false, {}}; + + for (const String& single_transition_value : transition_values) + { + Transition transition; + PropertyIdSet target_property_ids; + + StringList arguments; + StringUtilities::ExpandString(arguments, single_transition_value, ' '); + + bool duration_found = false; + bool delay_found = false; + bool reverse_adjustment_factor_found = false; + + for (auto& argument : arguments) + { + if (argument.empty()) + continue; + + // See if we have a or specifier as defined in keywords + auto it = parser_data->keywords.find(argument); + if (it != parser_data->keywords.end() && it->second.ValidTransition()) + { + if (it->second.type == KeywordType::None) + { + if (transition_list.transitions.size() > 0) // The none keyword can not be part of multiple definitions + return false; + property = Property{TransitionList{true, false, {}}, Unit::TRANSITION}; + return true; + } + else if (it->second.type == KeywordType::All) + { + if (transition_list.transitions.size() > 0) // The all keyword can not be part of multiple definitions + return false; + transition_list.all = true; + } + else if (it->second.type == KeywordType::Tween) + { + transition.tween = it->second.tween; + } + } + else + { + // Either , or a + float number = 0.0f; + int count = 0; + + if (sscanf(argument.c_str(), "%fs%n", &number, &count) == 1) + { + // Found a number, if there was an 's' unit, count will be positive + if (count > 0) + { + // Duration or delay was assigned + if (!duration_found) + { + duration_found = true; + transition.duration = number; + } + else if (!delay_found) + { + delay_found = true; + transition.delay = number; + } + else + return false; + } + else + { + // No 's' unit means reverse adjustment factor was found + if (!reverse_adjustment_factor_found) + { + reverse_adjustment_factor_found = true; + transition.reverse_adjustment_factor = number; + } + else + return false; + } + } + else + { + // Must be a property name or shorthand, expand now + if (auto shorthand = StyleSheetSpecification::GetShorthand(argument)) + { + PropertyIdSet underlying_properties = StyleSheetSpecification::GetShorthandUnderlyingProperties(shorthand->id); + target_property_ids |= underlying_properties; + } + else if (auto definition = StyleSheetSpecification::GetProperty(argument)) + { + // Single property + target_property_ids.Insert(definition->GetId()); + } + else + { + // Unknown property name + return false; + } + } + } + } + + // Validate the parsed transition + if ((transition_list.all && !target_property_ids.Empty()) // + || (!transition_list.all && target_property_ids.Empty()) // + || transition.duration <= 0.0f // + || transition.reverse_adjustment_factor < 0.0f // + || transition.reverse_adjustment_factor > 1.0f // + ) + { + return false; + } + + if (transition_list.all) + { + transition.id = PropertyId::Invalid; + transition_list.transitions.push_back(transition); + } + else + { + for (const PropertyId id : target_property_ids) + { + transition.id = id; + transition_list.transitions.push_back(transition); + } + } + } + + property.value = std::move(transition_list); + property.unit = Unit::TRANSITION; + + return true; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserAnimation.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserAnimation.h new file mode 100644 index 00000000..7a3b0c54 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserAnimation.h @@ -0,0 +1,36 @@ +#pragma once + +#include "../../Include/RmlUi/Core/PropertyParser.h" +#include "ControlledLifetimeResource.h" + +namespace Rml { + +/** +Parses the RCSS 'animation' and 'transition' property specifications. +*/ + +class PropertyParserAnimation : public PropertyParser { +public: + enum Type { ANIMATION_PARSER, TRANSITION_PARSER } type; + + /// Constructs the parser for either the animation or the transition type. + PropertyParserAnimation(Type type); + + /// Called to parse a RCSS animation or transition declaration. + /// @param[out] property The property to set the parsed value on. + /// @param[in] value The raw value defined for this property. + /// @param[in] parameters The parameters defined for this property. + /// @return True if the value was validated successfully, false otherwise. + bool ParseValue(Property& property, const String& value, const ParameterMap& parameters) const override; + + static void Initialize(); + static void Shutdown(); + +private: + static bool ParseAnimation(Property& property, const StringList& animation_values); + static bool ParseTransition(Property& property, const StringList& transition_values); + + static ControlledLifetimeResource parser_data; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserBoxShadow.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserBoxShadow.cpp new file mode 100644 index 00000000..71aa263a --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserBoxShadow.cpp @@ -0,0 +1,90 @@ +#include "PropertyParserBoxShadow.h" +#include "../../Include/RmlUi/Core/DecorationTypes.h" +#include "../../Include/RmlUi/Core/StringUtilities.h" + +namespace Rml { + +PropertyParserBoxShadow::PropertyParserBoxShadow(PropertyParser* parser_color, PropertyParser* parser_length) : + parser_color(parser_color), parser_length(parser_length) +{ + RMLUI_ASSERT(parser_color && parser_length); +} + +bool PropertyParserBoxShadow::ParseValue(Property& property, const String& value, const ParameterMap& /*parameters*/) const +{ + if (value.empty() || value == "none") + { + property.unit = Unit::BOXSHADOWLIST; + property.value = Variant(BoxShadowList{}); + return true; + } + + StringList shadows_string_list; + { + auto lowercase_value = StringUtilities::ToLower(value); + StringUtilities::ExpandString(shadows_string_list, lowercase_value, ',', '(', ')'); + } + + if (shadows_string_list.empty()) + return false; + + const ParameterMap empty_parameter_map; + + BoxShadowList shadow_list; + shadow_list.reserve(shadows_string_list.size()); + + for (const String& shadow_str : shadows_string_list) + { + StringList arguments; + StringUtilities::ExpandString(arguments, shadow_str, ' ', '(', ')'); + if (arguments.empty()) + return false; + + shadow_list.push_back({}); + BoxShadow& shadow = shadow_list.back(); + + int length_argument_index = 0; + + for (const String& argument : arguments) + { + if (argument.empty()) + continue; + + Property prop; + if (parser_length->ParseValue(prop, argument, empty_parameter_map)) + { + switch (length_argument_index) + { + case 0: shadow.offset_x = prop.GetNumericValue(); break; + case 1: shadow.offset_y = prop.GetNumericValue(); break; + case 2: shadow.blur_radius = prop.GetNumericValue(); break; + case 3: shadow.spread_distance = prop.GetNumericValue(); break; + default: return false; + } + length_argument_index += 1; + } + else if (argument == "inset") + { + shadow.inset = true; + } + else if (parser_color->ParseValue(prop, argument, empty_parameter_map)) + { + shadow.color = prop.Get().ToPremultiplied(); + } + else + { + return false; + } + } + + if (length_argument_index < 2) + return false; + } + + property.unit = Unit::BOXSHADOWLIST; + property.value = Variant(std::move(shadow_list)); + + return true; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserBoxShadow.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserBoxShadow.h new file mode 100644 index 00000000..a126a04d --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserBoxShadow.h @@ -0,0 +1,27 @@ +#pragma once + +#include "../../Include/RmlUi/Core/PropertyParser.h" + +namespace Rml { + +/** + Parses the RCSS 'box-shadow' property. +*/ + +class PropertyParserBoxShadow : public PropertyParser { +public: + PropertyParserBoxShadow(PropertyParser* parser_color, PropertyParser* parser_length); + + /// Called to parse a RCSS declaration. + /// @param[out] property The property to set the parsed value on. + /// @param[in] value The raw value defined for this property. + /// @param[in] parameters The parameters defined for this property. + /// @return True if the value was validated successfully, false otherwise. + bool ParseValue(Property& property, const String& value, const ParameterMap& parameters) const override; + +private: + PropertyParser* parser_color; + PropertyParser* parser_length; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserColorStopList.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserColorStopList.cpp new file mode 100644 index 00000000..80756e38 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserColorStopList.cpp @@ -0,0 +1,72 @@ +#include "PropertyParserColorStopList.h" +#include "../../Include/RmlUi/Core/ComputedValues.h" +#include "../../Include/RmlUi/Core/DecorationTypes.h" +#include + +namespace Rml { + +PropertyParserColorStopList::PropertyParserColorStopList(PropertyParser* parser_color) : + parser_color(parser_color), parser_length_percent_angle(Unit::LENGTH_PERCENT | Unit::ANGLE, Unit::PERCENT) +{ + RMLUI_ASSERT(parser_color); +} + +PropertyParserColorStopList::~PropertyParserColorStopList() {} + +bool PropertyParserColorStopList::ParseValue(Property& property, const String& value, const ParameterMap& parameters) const +{ + const ParameterMap empty_parameter_map; + + if (value.empty()) + return false; + + StringList color_stop_str_list; + StringUtilities::ExpandString(color_stop_str_list, value, ',', '(', ')'); + + if (color_stop_str_list.empty()) + return false; + + const Unit accepted_units = (parameters.count("angle") ? (Unit::ANGLE | Unit::PERCENT) : Unit::LENGTH_PERCENT); + + ColorStopList color_stops; + color_stops.reserve(color_stop_str_list.size()); + + for (const String& color_stop_str : color_stop_str_list) + { + StringList values; + StringUtilities::ExpandString(values, color_stop_str, ' ', '(', ')', true); + + if (values.empty() || values.size() > 3) + return false; + + Property p_color; + if (!parser_color->ParseValue(p_color, values[0], empty_parameter_map)) + return false; + + ColorStop color_stop = {}; + color_stop.color = p_color.Get().ToPremultiplied(); + + if (values.size() <= 1) + color_stops.push_back(color_stop); + + for (size_t i = 1; i < values.size(); i++) + { + Property p_position(Style::LengthPercentageAuto::Auto); + if (!parser_length_percent_angle.ParseValue(p_position, values[i], empty_parameter_map)) + return false; + + if (Any(p_position.unit & accepted_units)) + color_stop.position = NumericValue(p_position.Get(), p_position.unit); + else if (p_position.unit != Unit::KEYWORD) + return false; + + color_stops.push_back(color_stop); + } + } + + property.value = Variant(std::move(color_stops)); + property.unit = Unit::COLORSTOPLIST; + + return true; +} +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserColorStopList.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserColorStopList.h new file mode 100644 index 00000000..20465823 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserColorStopList.h @@ -0,0 +1,25 @@ +#pragma once + +#include "../../Include/RmlUi/Core/PropertyParser.h" +#include "../../Include/RmlUi/Core/Types.h" +#include "PropertyParserNumber.h" + +namespace Rml { + +/** + A property parser that parses color stop lists, particularly for gradients. + */ + +class PropertyParserColorStopList : public PropertyParser { +public: + PropertyParserColorStopList(PropertyParser* parser_color); + virtual ~PropertyParserColorStopList(); + + bool ParseValue(Property& property, const String& value, const ParameterMap& parameters) const override; + +private: + PropertyParser* parser_color; + PropertyParserNumber parser_length_percent_angle; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserColour.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserColour.cpp new file mode 100644 index 00000000..30c0328a --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserColour.cpp @@ -0,0 +1,548 @@ +#include "PropertyParserColour.h" +#include "ControlledLifetimeResource.h" +#include +#include +#include +#include + +namespace Rml { + +// Helper function for hsl->rgb conversion. +static float HSL_f(float h, float s, float l, float n) +{ + float k = std::fmod((n + h * (1.0f / 30.0f)), 12.0f); + float a = s * std::min(l, 1.0f - l); + return l - a * std::max(-1.0f, std::min({k - 3.0f, 9.0f - k, 1.0f})); +} + +// Reference: https://en.wikipedia.org/wiki/HSL_and_HSV#HSL_to_RGB_alternative +static void HSLAToRGBA(Array& vals) +{ + if (vals[1] == 0.0f) + { + vals[0] = vals[1] = vals[2]; + } + else + { + float h = std::fmod(vals[0], 360.0f); + if (h < 0) + h += 360.0f; + float s = vals[1]; + float l = vals[2]; + vals[0] = HSL_f(h, s, l, 0.0f); + vals[1] = HSL_f(h, s, l, 8.0f); + vals[2] = HSL_f(h, s, l, 4.0f); + } +} + +// Reference: https://en.wikipedia.org/wiki/SRGB#Definition +static float InverseSRGBNonlinearTransfer(float channel) +{ + return channel > 0.0031308f ? 1.055f * std::pow(channel, 1.0f / 2.4f) - 0.055f : 12.92f * channel; +} + +// Reference: https://en.wikipedia.org/wiki/CIELAB_color_space#Converting_between_CIELAB_and_CIE_XYZ_coordinates +static void CIELABToRGBA(Array& values) +{ + float y_double_prime = (values[0] + 16.0f) / 116.0f; + float x_double_prime = (values[1] / 500.0f) + y_double_prime; + float z_double_prime = y_double_prime - (values[2] / 200.0f); + + float x_prime = (x_double_prime * x_double_prime * x_double_prime) > 0.008856f ? (x_double_prime * x_double_prime * x_double_prime) + : (x_double_prime - (16.0f / 116.0f)) / 7.787f; + float y_prime = (y_double_prime * y_double_prime * y_double_prime) > 0.008856f ? (y_double_prime * y_double_prime * y_double_prime) + : (y_double_prime - (16.0f / 116.0f)) / 7.787f; + float z_prime = (z_double_prime * z_double_prime * z_double_prime) > 0.008856f ? (z_double_prime * z_double_prime * z_double_prime) + : (z_double_prime - (16.0f / 116.0f)) / 7.787f; + + static const Vector3f illuminant_d65_multiplicands(0.95047f, 1.0f, 1.08883f); + + float x = x_prime * illuminant_d65_multiplicands.x; + float y = y_prime * illuminant_d65_multiplicands.y; + float z = z_prime * illuminant_d65_multiplicands.z; + + static constexpr Array, 3> xyz_to_srgb_matrix{ + Array{+3.2404548f, -1.5371389f, -0.4985315f}, + Array{-0.9692664f, +1.8760109f, +0.0415561f}, + Array{+0.0556434f, -0.2040259f, +1.0572252f}, + }; + + float r = xyz_to_srgb_matrix[0][0] * x + xyz_to_srgb_matrix[0][1] * y + xyz_to_srgb_matrix[0][2] * z; + float g = xyz_to_srgb_matrix[1][0] * x + xyz_to_srgb_matrix[1][1] * y + xyz_to_srgb_matrix[1][2] * z; + float b = xyz_to_srgb_matrix[2][0] * x + xyz_to_srgb_matrix[2][1] * y + xyz_to_srgb_matrix[2][2] * z; + + values[0] = Math::Clamp(InverseSRGBNonlinearTransfer(r), 0.0f, 1.0f); + values[1] = Math::Clamp(InverseSRGBNonlinearTransfer(g), 0.0f, 1.0f); + values[2] = Math::Clamp(InverseSRGBNonlinearTransfer(b), 0.0f, 1.0f); +} + +// References: https://en.wikipedia.org/wiki/Oklab_color_space#Conversions_between_color_spaces and https://bottosson.github.io/posts/oklab/ +static void OklabToRGBA(Array& values) +{ + static constexpr Array, 3> oklab_to_lms_prime_matrix{ + Array{+1.0f, +0.3963377774f, +0.2158037573f}, + Array{+1.0f, -0.1055613458f, -0.0638541728f}, + Array{+1.0f, -0.0894841775f, -1.2914855480f}, + }; + + float lightness = values[0]; + float a_axis = values[1]; + float b_axis = values[2]; + + float l_prime = oklab_to_lms_prime_matrix[0][0] * lightness + oklab_to_lms_prime_matrix[0][1] * a_axis + oklab_to_lms_prime_matrix[0][2] * b_axis; + float m_prime = oklab_to_lms_prime_matrix[1][0] * lightness + oklab_to_lms_prime_matrix[1][1] * a_axis + oklab_to_lms_prime_matrix[1][2] * b_axis; + float s_prime = oklab_to_lms_prime_matrix[2][0] * lightness + oklab_to_lms_prime_matrix[2][1] * a_axis + oklab_to_lms_prime_matrix[2][2] * b_axis; + + float l = l_prime * l_prime * l_prime; + float m = m_prime * m_prime * m_prime; + float s = s_prime * s_prime * s_prime; + + static constexpr Array, 3> lms_to_srgb_matrix{ + Array{+4.0767416621f, -3.3077115913f, +0.2309699292f}, + Array{-1.2684380046f, +2.6097574011f, -0.3413193965f}, + Array{-0.0041960863f, -0.7034186147f, +1.7076147010f}, + }; + + float r = lms_to_srgb_matrix[0][0] * l + lms_to_srgb_matrix[0][1] * m + lms_to_srgb_matrix[0][2] * s; + float g = lms_to_srgb_matrix[1][0] * l + lms_to_srgb_matrix[1][1] * m + lms_to_srgb_matrix[1][2] * s; + float b = lms_to_srgb_matrix[2][0] * l + lms_to_srgb_matrix[2][1] * m + lms_to_srgb_matrix[2][2] * s; + + values[0] = Math::Clamp(InverseSRGBNonlinearTransfer(r), 0.0f, 1.0f); + values[1] = Math::Clamp(InverseSRGBNonlinearTransfer(g), 0.0f, 1.0f); + values[2] = Math::Clamp(InverseSRGBNonlinearTransfer(b), 0.0f, 1.0f); +} + +struct PropertyParserColourData { + const UnorderedMap html_colours = { + {"black", Colourb(0, 0, 0)}, + {"silver", Colourb(192, 192, 192)}, + {"gray", Colourb(128, 128, 128)}, + {"grey", Colourb(128, 128, 128)}, + {"white", Colourb(255, 255, 255)}, + {"maroon", Colourb(128, 0, 0)}, + {"red", Colourb(255, 0, 0)}, + {"orange", Colourb(255, 165, 0)}, + {"purple", Colourb(128, 0, 128)}, + {"fuchsia", Colourb(255, 0, 255)}, + {"green", Colourb(0, 128, 0)}, + {"lime", Colourb(0, 255, 0)}, + {"olive", Colourb(128, 128, 0)}, + {"yellow", Colourb(255, 255, 0)}, + {"navy", Colourb(0, 0, 128)}, + {"blue", Colourb(0, 0, 255)}, + {"teal", Colourb(0, 128, 128)}, + {"aqua", Colourb(0, 255, 255)}, + {"transparent", Colourb(0, 0, 0, 0)}, + }; +}; + +ControlledLifetimeResource PropertyParserColour::parser_data; + +void PropertyParserColour::Initialize() +{ + parser_data.Initialize(); +} +void PropertyParserColour::Shutdown() +{ + parser_data.Shutdown(); +} + +PropertyParserColour::PropertyParserColour() {} + +PropertyParserColour::~PropertyParserColour() {} + +bool PropertyParserColour::ParseValue(Property& property, const String& value, const ParameterMap& /*parameters*/) const +{ + Colourb colour; + if (!ParseColour(colour, value)) + return false; + + property.value = Variant(colour); + property.unit = Unit::COLOUR; + + return true; +} + +bool PropertyParserColour::ParseColour(Colourb& colour, const String& value) +{ + if (value.empty()) + return false; + + colour = {}; + + if (value[0] == '#') + { + if (!ParseHexColour(colour, value)) + return false; + } + else if (value.substr(0, 3) == "rgb") + { + if (!ParseRGBColour(colour, value)) + return false; + } + else if (value.substr(0, 3) == "hsl") + { + if (!ParseHSLColour(colour, value)) + return false; + } + else if (value.substr(0, 3) == "lab" || value.substr(0, 3) == "lch") + { + if (!ParseCIELABColour(colour, value)) + return false; + } + else if (value.substr(0, 5) == "oklab" || value.substr(0, 5) == "oklch") + { + if (!ParseOklabColour(colour, value)) + return false; + } + else + { + // Check for the specification of an HTML colour. + auto it = parser_data->html_colours.find(StringUtilities::ToLower(value)); + if (it == parser_data->html_colours.end()) + return false; + else + colour = it->second; + } + + return true; +} + +bool PropertyParserColour::ParseHexColour(Colourb& colour, const String& value) +{ + char hex_values[4][2] = {{'f', 'f'}, {'f', 'f'}, {'f', 'f'}, {'f', 'f'}}; + + switch (value.size()) + { + // Single hex digit per channel, RGB and alpha. + case 5: + hex_values[3][0] = hex_values[3][1] = value[4]; + //-fallthrough + // Single hex digit per channel, RGB only. + case 4: + hex_values[0][0] = hex_values[0][1] = value[1]; + hex_values[1][0] = hex_values[1][1] = value[2]; + hex_values[2][0] = hex_values[2][1] = value[3]; + break; + + // Two hex digits per channel, RGB and alpha. + case 9: + hex_values[3][0] = value[7]; + hex_values[3][1] = value[8]; + //-fallthrough + // Two hex digits per channel, RGB only. + case 7: memcpy(hex_values, &value.c_str()[1], sizeof(char) * 6); break; + + default: return false; + } + + // Parse each of the colour elements. + for (int i = 0; i < 4; i++) + { + int tens = Math::HexToDecimal(hex_values[i][0]); + int ones = Math::HexToDecimal(hex_values[i][1]); + if (tens == -1 || ones == -1) + return false; + + colour[i] = (byte)(tens * 16 + ones); + } + + return true; +} + +bool PropertyParserColour::ParseRGBColour(Colourb& colour, const String& value) +{ + StringList values; + values.reserve(4); + if (!GetColourFunctionValues(values, value, true)) + return false; + + // Check if we're parsing an 'rgba' or 'rgb' colour declaration. + if (value.size() > 3 && value[3] == 'a') + { + if (values.size() != 4) + return false; + } + else + { + if (values.size() != 3) + return false; + + values.push_back("255"); + } + + // Parse the RGBA values. + for (int i = 0; i < 4; ++i) + { + int component; + + // We're parsing a percentage value. + if (values[i].size() > 0 && values[i][values[i].size() - 1] == '%') + component = int((float)atof(values[i].substr(0, values[i].size() - 1).c_str()) * (255.0f / 100.0f)); + // We're parsing a 0 -> 255 integer value. + else + component = atoi(values[i].c_str()); + + colour[i] = (byte)(Math::Clamp(component, 0, 255)); + } + + return true; +} + +bool PropertyParserColour::ParseHSLColour(Colourb& colour, const String& value) +{ + StringList values; + values.reserve(4); + if (!GetColourFunctionValues(values, value, true)) + return false; + + // Check if we're parsing an 'hsla' or 'hsl' colour declaration. + if (value.size() > 3 && value[3] == 'a') + { + if (values.size() != 4) + return false; + } + else + { + if (values.size() != 3) + return false; + + values.push_back("1.0"); + } + + // Parse the HSLA values. + Array vals; + // H is a number in degrees, A is a number between 0.0 and 1.0. + for (int i : {0, 3}) + vals[i] = (float)atof(values[i].c_str()); + // S and L are percentage values. + for (int i : {1, 2}) + if (values[i].size() > 0 && values[i][values[i].size() - 1] == '%') + vals[i] = (float)atof(values[i].substr(0, values[i].size() - 1).c_str()) * (1.0f / 100.0f); + else + return false; + + HSLAToRGBA(vals); + for (int i = 0; i < 4; ++i) + colour[i] = (byte)(Math::Clamp((int)(vals[i] * 255.0f), 0, 255)); + + return true; +} + +bool PropertyParserColour::ParseCIELABColour(Colourb& colour, const String& value) +{ + StringList values; + values.reserve(5); + if (!GetColourFunctionValues(values, value, false)) + return false; + + // Check if we have an alpha component. + if (values.size() == 5) + { + if (values[3] != "/") + return false; + + values[3] = std::move(values[4]); + values.pop_back(); + } + else + { + if (values.size() != 3) + return false; + + values.push_back("1.0"); + } + + Array lab_values; + + // Parse lightness and alpha (same for both lab and lch). + for (int i : {0, 3}) + { + // Value can either be 'none' (representing 0.0), a percentage between 0% and 100%, or a number (between 0.0 and 100.0 for lightness and between 0.0 and 1.0 for alpha). + if (values[i] == "none") + lab_values[i] = 0.0f; + else if (values[i][values[i].size() - 1] == '%') + { + lab_values[i] = (float)atof(values[i].substr(0, values[i].size() - 1).c_str()); + if (i == 3) + lab_values[i] /= 100.0f; + } + else + lab_values[i] = (float)atof(values[i].c_str()); + + lab_values[i] = Math::Clamp(lab_values[i], 0.0f, i == 0 ? 100.0f : 1.0f); + } + + // Determine if colour is in CIELAB or CIELCh space. + if (value.substr(0, 3) == "lab") + { + // Parse A-axis (green-to-red) and B-axis (blue-to-yellow). + for (int i : {1, 2}) + { + // Value can either be 'none' (representing 0.0), a percentage between -100% and +100% (representing -125.0 to +125.0), or a number. + if (values[i] == "none") + lab_values[i] = 0.0f; + else if (values[i][values[i].size() - 1] == '%') + { + static constexpr float cielab_axis_percentage_bound = 125.0f; + lab_values[i] = (float)atof(values[i].substr(0, values[i].size() - 1).c_str()) / 100.0f * cielab_axis_percentage_bound; + } + else + lab_values[i] = (float)atof(values[i].c_str()); + + // Whilst the axis values are theoretically unbounded, in practice, they only exist between -160.0 and +160.0. + static constexpr float cielab_axis_bound_limit = 160.0f; + lab_values[i] = Math::Clamp(lab_values[i], -cielab_axis_bound_limit, +cielab_axis_bound_limit); + } + } + else + { + // Parse chroma; value can either be 'none' (representing 0.0), a percentage between 0% and 100% (representing 0.0 to 150.0), or a number. + float chroma = 0.0f; + if (values[1] == "none") + chroma = 0.0f; + else if (values[1][values[1].size() - 1] == '%') + { + static constexpr float cielch_maximum_percentage_chroma = 150.0f; + chroma = (float)atof(values[1].substr(0, values[1].size() - 1).c_str()) / 100.0f * cielch_maximum_percentage_chroma; + } + else + chroma = (float)atof(values[1].c_str()); + + // Whilst the chroma is theoretically unbounded, in practice, it does not exceed 230.0. + static constexpr float cielch_maximum_chroma = 230.0f; + chroma = Math::Clamp(chroma, 0.0f, cielch_maximum_chroma); + + // Parse hue; value can either be 'none' (representing 0.0), or an angle. + float hue = 0.0f; + if (values[2] == "none") + hue = 0.0f; + else + hue = (float)atof(values[2].c_str()); + + // Convert LCh polar coordinates to LAB Cartesian coordinates. + lab_values[1] = chroma * Math::Cos(Math::DegreesToRadians(hue)); + lab_values[2] = chroma * Math::Sin(Math::DegreesToRadians(hue)); + } + + CIELABToRGBA(lab_values); + for (int i = 0; i < 4; ++i) + colour[i] = (byte)(Math::Clamp((int)(lab_values[i] * 255.0f), 0, 255)); + + return true; +} + +bool PropertyParserColour::ParseOklabColour(Colourb& colour, const String& value) +{ + StringList values; + values.reserve(5); + if (!GetColourFunctionValues(values, value, false)) + return false; + + // Check if we have an alpha component. + if (values.size() == 5) + { + if (values[3] != "/") + return false; + + values[3] = std::move(values[4]); + values.pop_back(); + } + else + { + if (values.size() != 3) + return false; + + values.push_back("1.0"); + } + + Array oklab_values; + + // Parse lightness and alpha (same for both Oklab and Oklch). + for (int i : {0, 3}) + { + // Value can either be 'none' (representing 0.0), a percentage between 0% and 100%, or a number between 0.0 and 1.0. + if (values[i] == "none") + oklab_values[i] = 0.0f; + else if (values[i][values[i].size() - 1] == '%') + oklab_values[i] = (float)atof(values[i].substr(0, values[i].size() - 1).c_str()) / 100.0f; + else + oklab_values[i] = (float)atof(values[i].c_str()); + + oklab_values[i] = Math::Clamp(oklab_values[i], 0.0f, 1.0f); + } + + // Determine if colour is in Oklab or Oklch space. + if (value.substr(0, 5) == "oklab") + { + // Parse A-axis (green-to-red) and B-axis (blue-to-yellow). + for (int i : {1, 2}) + { + // Value can either be 'none' (representing 0.0), a percentage between -100% and +100% (representing -0.4 to +0.4), or a number. + if (values[i] == "none") + oklab_values[i] = 0.0f; + else if (values[i][values[i].size() - 1] == '%') + { + static constexpr float oklab_axis_percentage_bound = 0.4f; + oklab_values[i] = (float)atof(values[i].substr(0, values[i].size() - 1).c_str()) / 100.0f * oklab_axis_percentage_bound; + } + else + oklab_values[i] = (float)atof(values[i].c_str()); + + // Whilst the axis values are theoretically unbounded, in practice, they only exist between -0.5 and +0.5. + static constexpr float oklab_axis_bound_limit = 0.5f; + oklab_values[i] = Math::Clamp(oklab_values[i], -oklab_axis_bound_limit, +oklab_axis_bound_limit); + } + } + else + { + // Parse chroma; value can either be 'none' (representing 0.0), a percentage between 0% and 100% (representing 0.0 to 0.4), or a number. + float chroma = 0.0f; + if (values[1] == "none") + chroma = 0.0f; + else if (values[1][values[1].size() - 1] == '%') + { + static constexpr float oklch_maximum_percentage_chroma = 0.4f; + chroma = (float)atof(values[1].substr(0, values[1].size() - 1).c_str()) / 100.0f * oklch_maximum_percentage_chroma; + } + else + chroma = (float)atof(values[1].c_str()); + + // Whilst the chroma is theoretically unbounded, in practice, it does not exceed 0.5. + static constexpr float oklch_maximum_chroma = 0.5f; + chroma = Math::Clamp(chroma, 0.0f, oklch_maximum_chroma); + + // Parse hue; value can either be 'none' (representing 0.0), or an angle. + float hue = 0.0f; + if (values[2] == "none") + hue = 0.0f; + else + hue = (float)atof(values[2].c_str()); + + // Convert Oklch polar coordinates to Oklab Cartesian coordinates. + oklab_values[1] = chroma * Math::Cos(Math::DegreesToRadians(hue)); + oklab_values[2] = chroma * Math::Sin(Math::DegreesToRadians(hue)); + } + + OklabToRGBA(oklab_values); + for (int i = 0; i < 4; ++i) + colour[i] = (byte)(Math::Clamp((int)(oklab_values[i] * 255.0f), 0, 255)); + + return true; +} + +bool PropertyParserColour::GetColourFunctionValues(StringList& values, const String& value, bool is_comma_separated) +{ + size_t find = value.find('('); + if (find == String::npos) + return false; + + size_t begin_values = find + 1; + + StringUtilities::ExpandString(values, value.substr(begin_values, value.rfind(')') - begin_values), is_comma_separated ? ',' : ' ', + !is_comma_separated); + + return true; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserColour.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserColour.h new file mode 100644 index 00000000..28e45749 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserColour.h @@ -0,0 +1,54 @@ +#pragma once + +#include "../../Include/RmlUi/Core/PropertyParser.h" +#include "../../Include/RmlUi/Core/Types.h" +#include "ControlledLifetimeResource.h" + +namespace Rml { + +/** + A property parser that parses a colour value. + */ + +class PropertyParserColour : public PropertyParser { +public: + PropertyParserColour(); + virtual ~PropertyParserColour(); + + /// Called to parse a RCSS colour declaration. + /// @param[out] property The property to set the parsed value on. + /// @param[in] value The raw value defined for this property. + /// @param[in] parameters The parameters defined for this property; not used for this parser. + /// @return True if the value was parsed successfully, false otherwise. + bool ParseValue(Property& property, const String& value, const ParameterMap& parameters) const override; + + /// Parse a colour directly. + static bool ParseColour(Colourb& colour, const String& value); + + static void Initialize(); + static void Shutdown(); + +private: + static ControlledLifetimeResource parser_data; + + // Parse a colour in hex-code form (e.g. #FF00FF or #00FF00FF). + static bool ParseHexColour(Colourb& colour, const String& value); + + // Parse a colour in RGB form (e.g. rgb(255, 0, 255) or rgba(0, 255, 0, 255)). + static bool ParseRGBColour(Colourb& colour, const String& value); + + // Parse a colour in HSL form (e.g. hsl(0, 100%, 50%) or hsla(0, 100%, 50%, 1.0)). + static bool ParseHSLColour(Colourb& colour, const String& value); + + // Parse a colour in CIELAB form (e.g. lab(100.0 0.0 0.0) or lab(50.0 -60.0 60.0 / 0.5) + // or CIELCh form (e.g. lch(100.0 0.0 30) or lch(100.0 0.0 60 / 0.5)). + static bool ParseCIELABColour(Colourb& colour, const String& value); + + // Parse a colour in Oklab form (e.g. oklab(1.0 0.0 0.0) or oklab(0.5 -0.2 0.2 / 0.5)) + // or Oklch form (e.g. oklch(1.0 0.0 30) or oklch(1.0 0.0 60 / 0.5)). + static bool ParseOklabColour(Colourb& colour, const String& value); + + static bool GetColourFunctionValues(StringList& values, const String& value, bool is_comma_separated); +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserDecorator.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserDecorator.cpp new file mode 100644 index 00000000..8249047b --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserDecorator.cpp @@ -0,0 +1,146 @@ +#include "PropertyParserDecorator.h" +#include "../../Include/RmlUi/Core/Decorator.h" +#include "../../Include/RmlUi/Core/Factory.h" +#include "../../Include/RmlUi/Core/Profiling.h" +#include "../../Include/RmlUi/Core/PropertySpecification.h" +#include "../../Include/RmlUi/Core/StyleSheetTypes.h" + +namespace Rml { + +struct PropertyParserDecoratorData { + const SmallUnorderedMap area_keywords = { + {"border-box", BoxArea::Border}, + {"padding-box", BoxArea::Padding}, + {"content-box", BoxArea::Content}, + }; +}; + +ControlledLifetimeResource PropertyParserDecorator::parser_data; + +void PropertyParserDecorator::Initialize() +{ + parser_data.Initialize(); +} +void PropertyParserDecorator::Shutdown() +{ + parser_data.Shutdown(); +} + +PropertyParserDecorator::PropertyParserDecorator() {} + +PropertyParserDecorator::~PropertyParserDecorator() {} + +bool PropertyParserDecorator::ParseValue(Property& property, const String& decorator_string_value, const ParameterMap& /*parameters*/) const +{ + // Decorators are declared as + // decorator: [, ...]; + // Where is either a @decorator name: + // decorator: invader-theme-background ?, ...; + // or is an anonymous decorator with inline properties + // decorator: tiled-box( ) ?, ...; + // where is one of + // border-box, padding-box, content-box + + if (decorator_string_value.empty() || decorator_string_value == "none") + { + property.value = Variant(DecoratorsPtr()); + property.unit = Unit::DECORATOR; + return true; + } + + RMLUI_ZoneScoped; + + // Make sure we don't split inside the parenthesis since they may appear in decorator shorthands. + StringList decorator_string_list; + StringUtilities::ExpandString(decorator_string_list, decorator_string_value, ',', '(', ')'); + + DecoratorDeclarationList decorators; + decorators.value = decorator_string_value; + decorators.list.reserve(decorator_string_list.size()); + + // Get or instance each decorator in the comma-separated string list + for (const String& decorator_string : decorator_string_list) + { + const size_t shorthand_open = decorator_string.find('('); + const size_t shorthand_close = decorator_string.rfind(')'); + const bool invalid_parenthesis = (shorthand_open == String::npos || shorthand_close == String::npos || shorthand_open >= shorthand_close); + const size_t keywords_begin = (invalid_parenthesis ? decorator_string.find(' ') : shorthand_close + 1); + + // Look-up keywords for customized paint area. + BoxArea paint_area = BoxArea::Auto; + + { + StringList keywords; + if (keywords_begin < decorator_string.size()) + StringUtilities::ExpandString(keywords, decorator_string.substr(keywords_begin), ' '); + + for (const String& keyword : keywords) + { + if (keyword.empty()) + continue; + + auto it = parser_data->area_keywords.find(StringUtilities::ToLower(keyword)); + if (it == parser_data->area_keywords.end()) + return false; // Bail out if we have an invalid keyword. + + paint_area = it->second; + } + } + + if (invalid_parenthesis) + { + // We found no parenthesis, that means the value must be a name of a @decorator rule. + decorators.list.emplace_back(DecoratorDeclaration{decorator_string.substr(0, keywords_begin), nullptr, {}, paint_area}); + } + else + { + // Since we have parentheses it must be an anonymous decorator with inline properties + const String type = StringUtilities::StripWhitespace(decorator_string.substr(0, shorthand_open)); + + // Check for valid decorator type + DecoratorInstancer* instancer = Factory::GetDecoratorInstancer(type); + if (!instancer) + { + Log::Message(Log::LT_WARNING, "Decorator type '%s' not found.", type.c_str()); + return false; + } + + const String shorthand = decorator_string.substr(shorthand_open + 1, shorthand_close - shorthand_open - 1); + const PropertySpecification& specification = instancer->GetPropertySpecification(); + + // Parse the shorthand properties given by the 'decorator' shorthand property + PropertyDictionary properties; + if (!specification.ParsePropertyDeclaration(properties, "decorator", shorthand)) + { + // Empty values are allowed in decorators, if the value is not empty we must have encountered a parser error. + if (!StringUtilities::StripWhitespace(shorthand).empty()) + return false; + } + + // Set unspecified values to their defaults + specification.SetPropertyDefaults(properties); + + decorators.list.emplace_back(DecoratorDeclaration{type, instancer, std::move(properties), paint_area}); + } + } + + if (decorators.list.empty()) + return false; + + property.value = Variant(MakeShared(std::move(decorators))); + property.unit = Unit::DECORATOR; + + return true; +} + +String PropertyParserDecorator::ConvertAreaToString(BoxArea area) +{ + for (const auto& it : parser_data->area_keywords) + { + if (it.second == area) + return it.first; + } + return String(); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserDecorator.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserDecorator.h new file mode 100644 index 00000000..b4f55007 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserDecorator.h @@ -0,0 +1,29 @@ +#pragma once + +#include "../../Include/RmlUi/Core/PropertyParser.h" +#include "ControlledLifetimeResource.h" + +namespace Rml { + +/** + A property parser for the decorator property. + */ + +class PropertyParserDecorator : public PropertyParser { +public: + PropertyParserDecorator(); + virtual ~PropertyParserDecorator(); + + /// Called to parse a decorator declaration. + bool ParseValue(Property& property, const String& value, const ParameterMap& parameters) const override; + + static String ConvertAreaToString(BoxArea area); + + static void Initialize(); + static void Shutdown(); + +private: + static ControlledLifetimeResource parser_data; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserFilter.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserFilter.cpp new file mode 100644 index 00000000..97474de7 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserFilter.cpp @@ -0,0 +1,91 @@ +#include "PropertyParserFilter.h" +#include "../../Include/RmlUi/Core/Factory.h" +#include "../../Include/RmlUi/Core/Filter.h" +#include "../../Include/RmlUi/Core/Profiling.h" +#include "../../Include/RmlUi/Core/PropertySpecification.h" +#include "../../Include/RmlUi/Core/StyleSheetTypes.h" + +namespace Rml { + +PropertyParserFilter::PropertyParserFilter() {} + +PropertyParserFilter::~PropertyParserFilter() {} + +bool PropertyParserFilter::ParseValue(Property& property, const String& filter_string_value, const ParameterMap& /*parameters*/) const +{ + // Filters are declared as + // filter: [ ...]; + // Where is specified with inline properties + // filter: brightness( ) ...; + + if (filter_string_value.empty() || filter_string_value == "none") + { + property.value = Variant(FiltersPtr()); + property.unit = Unit::FILTER; + return true; + } + + RMLUI_ZoneScoped; + + // Make sure we don't split inside the parenthesis since they may appear in filter shorthands. + StringList filter_string_list; + StringUtilities::ExpandString(filter_string_list, filter_string_value, ' ', '(', ')', true); + + FilterDeclarationList filters; + filters.value = filter_string_value; + filters.list.reserve(filter_string_list.size()); + + // Get or instance each filter in the comma-separated string list + for (const String& filter_string : filter_string_list) + { + const size_t shorthand_open = filter_string.find('('); + const size_t shorthand_close = filter_string.rfind(')'); + const bool invalid_parenthesis = (shorthand_open == String::npos || shorthand_close == String::npos || shorthand_open >= shorthand_close); + + if (invalid_parenthesis) + { + // We found no parenthesis, filters can only be declared anonymously for now. + Log::Message(Log::LT_WARNING, "Invalid syntax for font-effect '%s'.", filter_string.c_str()); + return false; + } + else + { + const String type = StringUtilities::StripWhitespace(filter_string.substr(0, shorthand_open)); + + // Check for valid filter type + FilterInstancer* instancer = Factory::GetFilterInstancer(type); + if (!instancer) + { + Log::Message(Log::LT_WARNING, "Filter type '%s' not found.", type.c_str()); + return false; + } + + const String shorthand = filter_string.substr(shorthand_open + 1, shorthand_close - shorthand_open - 1); + const PropertySpecification& specification = instancer->GetPropertySpecification(); + + // Parse the shorthand properties given by the 'filter' shorthand property + PropertyDictionary properties; + if (!specification.ParsePropertyDeclaration(properties, "filter", shorthand)) + { + // Empty values are allowed in filters, if the value is not empty we must have encountered a parser error. + if (!StringUtilities::StripWhitespace(shorthand).empty()) + return false; + } + + // Set unspecified values to their defaults + specification.SetPropertyDefaults(properties); + + filters.list.emplace_back(FilterDeclaration{type, instancer, std::move(properties)}); + } + } + + if (filters.list.empty()) + return false; + + property.value = Variant(MakeShared(std::move(filters))); + property.unit = Unit::FILTER; + + return true; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserFilter.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserFilter.h new file mode 100644 index 00000000..f3250004 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserFilter.h @@ -0,0 +1,20 @@ +#pragma once + +#include "../../Include/RmlUi/Core/PropertyParser.h" + +namespace Rml { + +/** + A property parser for the filter property. + */ + +class PropertyParserFilter : public PropertyParser { +public: + PropertyParserFilter(); + virtual ~PropertyParserFilter(); + + /// Called to parse a decorator declaration. + bool ParseValue(Property& property, const String& value, const ParameterMap& parameters) const override; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserFontEffect.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserFontEffect.cpp new file mode 100644 index 00000000..43aa496e --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserFontEffect.cpp @@ -0,0 +1,119 @@ +#include "PropertyParserFontEffect.h" +#include "../../Include/RmlUi/Core/Factory.h" +#include "../../Include/RmlUi/Core/FontEffect.h" +#include "../../Include/RmlUi/Core/FontEffectInstancer.h" +#include "../../Include/RmlUi/Core/Profiling.h" +#include "../../Include/RmlUi/Core/PropertySpecification.h" +#include "../../Include/RmlUi/Core/Utilities.h" +#include + +namespace Rml { + +PropertyParserFontEffect::PropertyParserFontEffect() {} + +PropertyParserFontEffect::~PropertyParserFontEffect() {} + +bool PropertyParserFontEffect::ParseValue(Property& property, const String& font_effect_string_value, const ParameterMap& /*parameters*/) const +{ + // Font-effects are declared as + // font-effect: [, ...]; + // Where is declared with inline properties, e.g. + // font-effect: outline( 1px black ), ...; + + if (font_effect_string_value.empty() || font_effect_string_value == "none") + { + property.value = Variant(); + property.unit = Unit::UNKNOWN; + return true; + } + + RMLUI_ZoneScoped; + + FontEffects font_effects; + + // Make sure we don't split inside the parenthesis since they may appear in decorator shorthands. + StringList font_effect_string_list; + StringUtilities::ExpandString(font_effect_string_list, font_effect_string_value, ',', '(', ')'); + + font_effects.value = font_effect_string_value; + font_effects.list.reserve(font_effect_string_list.size()); + + // Get or instance each decorator in the comma-separated string list + for (const String& font_effect_string : font_effect_string_list) + { + const size_t shorthand_open = font_effect_string.find('('); + const size_t shorthand_close = font_effect_string.rfind(')'); + const bool invalid_parenthesis = (shorthand_open == String::npos || shorthand_close == String::npos || shorthand_open >= shorthand_close); + + if (invalid_parenthesis) + { + // We found no parenthesis, font-effects can only be declared anonymously for now. + Log::Message(Log::LT_WARNING, "Invalid syntax for font-effect '%s'.", font_effect_string.c_str()); + return false; + } + else + { + // Since we have parentheses it must be an anonymous decorator with inline properties + const String type = StringUtilities::StripWhitespace(font_effect_string.substr(0, shorthand_open)); + + // Check for valid font-effect type + FontEffectInstancer* instancer = Factory::GetFontEffectInstancer(type); + if (!instancer) + { + Log::Message(Log::LT_WARNING, "Font-effect type '%s' not found.", type.c_str()); + return false; + } + + const String shorthand = font_effect_string.substr(shorthand_open + 1, shorthand_close - shorthand_open - 1); + const PropertySpecification& specification = instancer->GetPropertySpecification(); + + // Parse the shorthand properties given by the 'font-effect' shorthand property + PropertyDictionary properties; + if (!specification.ParsePropertyDeclaration(properties, "font-effect", shorthand)) + { + // Empty values are allowed in font-effects, if the value is not empty we must have encountered a parser error. + if (!StringUtilities::StripWhitespace(shorthand).empty()) + { + Log::Message(Log::LT_WARNING, "Could not parse font-effect value '%s'.", font_effect_string.c_str()); + return false; + } + } + + // Set unspecified values to their defaults + specification.SetPropertyDefaults(properties); + + RMLUI_ZoneScopedN("InstanceFontEffect"); + SharedPtr font_effect = instancer->InstanceFontEffect(type, properties); + if (font_effect) + { + // Create a unique hash value for the given type and values + size_t fingerprint = Hash{}(type); + for (const auto& id_value : properties.GetProperties()) + Utilities::HashCombine(fingerprint, id_value.second.Get()); + + font_effect->SetFingerprint(fingerprint); + + font_effects.list.emplace_back(std::move(font_effect)); + } + else + { + Log::Message(Log::LT_WARNING, "Font-effect '%s' could not be instanced.", font_effect_string.c_str()); + return false; + } + } + } + + if (font_effects.list.empty()) + return false; + + // Partition the list such that the back layer effects appear before the front layer effects + std::stable_partition(font_effects.list.begin(), font_effects.list.end(), + [](const SharedPtr& effect) { return effect->GetLayer() == FontEffect::Layer::Back; }); + + property.value = Variant(MakeShared(std::move(font_effects))); + property.unit = Unit::FONTEFFECT; + + return true; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserFontEffect.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserFontEffect.h new file mode 100644 index 00000000..6afda388 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserFontEffect.h @@ -0,0 +1,20 @@ +#pragma once + +#include "../../Include/RmlUi/Core/PropertyParser.h" + +namespace Rml { + +/** + A property parser for the font-effect property. + */ + +class PropertyParserFontEffect : public PropertyParser { +public: + PropertyParserFontEffect(); + virtual ~PropertyParserFontEffect(); + + /// Called to parse a font-effect declaration. + bool ParseValue(Property& property, const String& value, const ParameterMap& parameters) const override; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserKeyword.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserKeyword.cpp new file mode 100644 index 00000000..e9b9fbff --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserKeyword.cpp @@ -0,0 +1,21 @@ +#include "PropertyParserKeyword.h" + +namespace Rml { + +PropertyParserKeyword::PropertyParserKeyword() {} + +PropertyParserKeyword::~PropertyParserKeyword() {} + +bool PropertyParserKeyword::ParseValue(Property& property, const String& value, const ParameterMap& parameters) const +{ + ParameterMap::const_iterator iterator = parameters.find(StringUtilities::ToLower(value)); + if (iterator == parameters.end()) + return false; + + property.value = Variant((*iterator).second); + property.unit = Unit::KEYWORD; + + return true; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserKeyword.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserKeyword.h new file mode 100644 index 00000000..2ee01cb6 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserKeyword.h @@ -0,0 +1,24 @@ +#pragma once + +#include "../../Include/RmlUi/Core/PropertyParser.h" + +namespace Rml { + +/** + A property parser that validates a value is part of a specified list of keywords. + */ + +class PropertyParserKeyword : public PropertyParser { +public: + PropertyParserKeyword(); + virtual ~PropertyParserKeyword(); + + /// Called to parse a RCSS keyword declaration. + /// @param[out] property The property to set the parsed value on. + /// @param[in] value The raw value defined for this property. + /// @param[in] parameters The parameters defined for this property. + /// @return True if the value was validated successfully, false otherwise. + bool ParseValue(Property& property, const String& value, const ParameterMap& parameters) const override; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserNumber.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserNumber.cpp new file mode 100644 index 00000000..1983d790 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserNumber.cpp @@ -0,0 +1,99 @@ +#include "PropertyParserNumber.h" +#include + +namespace Rml { + +struct PropertyParserNumberData { + const UnorderedMap unit_string_map = { + {"", Unit::NUMBER}, + {"%", Unit::PERCENT}, + {"px", Unit::PX}, + {"dp", Unit::DP}, + {"x", Unit::X}, + {"vw", Unit::VW}, + {"vh", Unit::VH}, + {"em", Unit::EM}, + {"rem", Unit::REM}, + {"in", Unit::INCH}, + {"cm", Unit::CM}, + {"mm", Unit::MM}, + {"pt", Unit::PT}, + {"pc", Unit::PC}, + {"deg", Unit::DEG}, + {"rad", Unit::RAD}, + }; +}; + +ControlledLifetimeResource PropertyParserNumber::parser_data; + +void PropertyParserNumber::Initialize() +{ + parser_data.Initialize(); +} + +void PropertyParserNumber::Shutdown() +{ + parser_data.Shutdown(); +} + +PropertyParserNumber::PropertyParserNumber(Units units, Unit zero_unit) : units(units), zero_unit(zero_unit) {} + +PropertyParserNumber::~PropertyParserNumber() {} + +bool PropertyParserNumber::ParseValue(Property& property, const String& value, const ParameterMap& /*parameters*/) const +{ + // Find the beginning of the unit string in 'value'. + size_t unit_pos = 0; + for (size_t i = value.size(); i--;) + { + const char c = value[i]; + if ((c >= '0' && c <= '9') || StringUtilities::IsWhitespace(c)) + { + unit_pos = i + 1; + break; + } + } + + String str_number = value.substr(0, unit_pos); + String str_unit = StringUtilities::ToLower(value.substr(unit_pos)); + + char* str_end = nullptr; + float float_value = strtof(str_number.c_str(), &str_end); + if (str_number.c_str() == str_end) + { + // Number conversion failed + return false; + } + + const auto it = parser_data->unit_string_map.find(str_unit); + if (it == parser_data->unit_string_map.end()) + { + // Invalid unit name + return false; + } + + const Unit unit = it->second; + + if (Any(unit & units)) + { + property.value = float_value; + property.unit = unit; + return true; + } + + // Detected unit not allowed. + // However, we allow a value of "0" if zero_unit is set and no unit specified (that is, unit is a pure NUMBER). + if (unit == Unit::NUMBER) + { + if (zero_unit != Unit::UNKNOWN && float_value == 0.0f) + { + property.unit = zero_unit; + property.value = Variant(0.0f); + return true; + } + } + + return false; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserNumber.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserNumber.h new file mode 100644 index 00000000..8ba3d7ba --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserNumber.h @@ -0,0 +1,37 @@ +#pragma once + +#include "../../Include/RmlUi/Core/PropertyParser.h" +#include "ControlledLifetimeResource.h" + +namespace Rml { + +/** + A property parser that parses a floating-point number with an optional unit. + */ + +class PropertyParserNumber : public PropertyParser { +public: + PropertyParserNumber(Units units, Unit zero_unit = Unit::UNKNOWN); + virtual ~PropertyParserNumber(); + + /// Called to parse a RCSS number declaration. + /// @param[out] property The property to set the parsed value on. + /// @param[in] value The raw value defined for this property. + /// @param[in] parameters The parameters defined for this property. + /// @return True if the value was validated successfully, false otherwise. + bool ParseValue(Property& property, const String& value, const ParameterMap& parameters) const override; + + static void Initialize(); + static void Shutdown(); + +private: + static ControlledLifetimeResource parser_data; + + // Stores a bit mask of allowed units. + Units units; + + // If zero unit is set and pure numbers are not allowed, parsing of "0" is still allowed and assigned the given unit. + Unit zero_unit; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserRatio.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserRatio.cpp new file mode 100644 index 00000000..2651c443 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserRatio.cpp @@ -0,0 +1,39 @@ +#include "PropertyParserRatio.h" + +namespace Rml { + +PropertyParserRatio::PropertyParserRatio() {} + +PropertyParserRatio::~PropertyParserRatio() {} + +bool PropertyParserRatio::ParseValue(Property& property, const String& value, const ParameterMap& /*parameters*/) const +{ + StringList parts; + StringUtilities::ExpandString(parts, value, '/'); + + if (parts.size() != 2) + { + return false; + } + + float first_value = 0; + if (!TypeConverter::Convert(parts[0], first_value)) + { + // Number conversion failed + return false; + } + + float second_value = 0; + if (!TypeConverter::Convert(parts[1], second_value)) + { + // Number conversion failed + return false; + } + + property.value = Variant(Vector2f(first_value, second_value)); + property.unit = Unit::RATIO; + + return true; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserRatio.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserRatio.h new file mode 100644 index 00000000..9c26dbfe --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserRatio.h @@ -0,0 +1,24 @@ +#pragma once + +#include "../../Include/RmlUi/Core/PropertyParser.h" + +namespace Rml { + +/** + A property parser that parses an ratio in the format of x/y, like 16/9. + */ + +class PropertyParserRatio : public PropertyParser { +public: + PropertyParserRatio(); + virtual ~PropertyParserRatio(); + + /// Called to parse a RCSS string declaration. + /// @param[out] property The property to set the parsed value on. + /// @param[in] value The raw value defined for this property. + /// @param[in] parameters The parameters defined for this property; not used for this parser. + /// @return True if the value was validated successfully, false otherwise. + bool ParseValue(Property& property, const String& value, const ParameterMap& parameters) const override; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserString.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserString.cpp new file mode 100644 index 00000000..d01d4eb0 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserString.cpp @@ -0,0 +1,17 @@ +#include "PropertyParserString.h" + +namespace Rml { + +PropertyParserString::PropertyParserString() {} + +PropertyParserString::~PropertyParserString() {} + +bool PropertyParserString::ParseValue(Property& property, const String& value, const ParameterMap& /*parameters*/) const +{ + property.value = Variant(value); + property.unit = Unit::STRING; + + return true; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserString.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserString.h new file mode 100644 index 00000000..aa987b6e --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserString.h @@ -0,0 +1,24 @@ +#pragma once + +#include "../../Include/RmlUi/Core/PropertyParser.h" + +namespace Rml { + +/** + A passthrough property parser that parses a string. + */ + +class PropertyParserString : public PropertyParser { +public: + PropertyParserString(); + virtual ~PropertyParserString(); + + /// Called to parse a RCSS string declaration. + /// @param[out] property The property to set the parsed value on. + /// @param[in] value The raw value defined for this property. + /// @param[in] parameters The parameters defined for this property; not used for this parser. + /// @return True if the value was validated successfully, false otherwise. + bool ParseValue(Property& property, const String& value, const ParameterMap& parameters) const override; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserTransform.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserTransform.cpp new file mode 100644 index 00000000..fd7553c1 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserTransform.cpp @@ -0,0 +1,263 @@ +#include "PropertyParserTransform.h" +#include "../../Include/RmlUi/Core/NumericValue.h" +#include "../../Include/RmlUi/Core/Transform.h" +#include "../../Include/RmlUi/Core/TransformPrimitive.h" +#include + +namespace Rml { + +PropertyParserTransform::PropertyParserTransform() : + number(Unit::NUMBER), length(Unit::LENGTH, Unit::PX), length_pct(Unit::LENGTH_PERCENT, Unit::PX), angle(Unit::ANGLE, Unit::RAD) +{} + +PropertyParserTransform::~PropertyParserTransform() {} + +bool PropertyParserTransform::ParseValue(Property& property, const String& value, const ParameterMap& /*parameters*/) const +{ + if (value == "none") + { + property.value = Variant(TransformPtr()); + property.unit = Unit::TRANSFORM; + return true; + } + + TransformPtr transform = MakeShared(); + + char const* next = value.c_str(); + + NumericValue args[16]; + + const PropertyParser* number16[] = {&number, &number, &number, &number, &number, &number, &number, &number, &number, &number, &number, &number, + &number, &number, &number, &number}; + const PropertyParser* lengthpct2_length1[] = {&length_pct, &length_pct, &length}; + const PropertyParser* number3angle1[] = {&number, &number, &number, &angle}; + const PropertyParser* angle2[] = {&angle, &angle}; + const PropertyParser* length1[] = {&length}; + + // For semantic purposes, define subsets of the above parsers when scanning primitives below. + auto lengthpct1 = lengthpct2_length1; + auto lengthpct2 = lengthpct2_length1; + auto angle1 = angle2; + auto number1 = number16; + auto number2 = number16; + auto number3 = number16; + auto number6 = number16; + + while (*next) + { + using namespace Transforms; + int bytes_read = 0; + + if (Scan(bytes_read, next, "perspective", length1, args, 1)) + { + transform->AddPrimitive({Perspective(args)}); + } + else if (Scan(bytes_read, next, "matrix", number6, args, 6)) + { + transform->AddPrimitive({Matrix2D(args)}); + } + else if (Scan(bytes_read, next, "matrix3d", number16, args, 16)) + { + transform->AddPrimitive({Matrix3D(args)}); + } + else if (Scan(bytes_read, next, "translateX", lengthpct1, args, 1)) + { + transform->AddPrimitive({TranslateX(args)}); + } + else if (Scan(bytes_read, next, "translateY", lengthpct1, args, 1)) + { + transform->AddPrimitive({TranslateY(args)}); + } + else if (Scan(bytes_read, next, "translateZ", length1, args, 1)) + { + transform->AddPrimitive({TranslateZ(args)}); + } + else if (Scan(bytes_read, next, "translate", lengthpct2, args, 2)) + { + transform->AddPrimitive({Translate2D(args)}); + } + else if (Scan(bytes_read, next, "translate3d", lengthpct2_length1, args, 3)) + { + transform->AddPrimitive({Translate3D(args)}); + } + else if (Scan(bytes_read, next, "scaleX", number1, args, 1)) + { + transform->AddPrimitive({ScaleX(args)}); + } + else if (Scan(bytes_read, next, "scaleY", number1, args, 1)) + { + transform->AddPrimitive({ScaleY(args)}); + } + else if (Scan(bytes_read, next, "scaleZ", number1, args, 1)) + { + transform->AddPrimitive({ScaleZ(args)}); + } + else if (Scan(bytes_read, next, "scale", number2, args, 2)) + { + transform->AddPrimitive({Scale2D(args)}); + } + else if (Scan(bytes_read, next, "scale", number1, args, 1)) + { + args[1] = args[0]; + transform->AddPrimitive({Scale2D(args)}); + } + else if (Scan(bytes_read, next, "scale3d", number3, args, 3)) + { + transform->AddPrimitive({Scale3D(args)}); + } + else if (Scan(bytes_read, next, "rotateX", angle1, args, 1)) + { + transform->AddPrimitive({RotateX(args)}); + } + else if (Scan(bytes_read, next, "rotateY", angle1, args, 1)) + { + transform->AddPrimitive({RotateY(args)}); + } + else if (Scan(bytes_read, next, "rotateZ", angle1, args, 1)) + { + transform->AddPrimitive({RotateZ(args)}); + } + else if (Scan(bytes_read, next, "rotate", angle1, args, 1)) + { + transform->AddPrimitive({Rotate2D(args)}); + } + else if (Scan(bytes_read, next, "rotate3d", number3angle1, args, 4)) + { + transform->AddPrimitive({Rotate3D(args)}); + } + else if (Scan(bytes_read, next, "skewX", angle1, args, 1)) + { + transform->AddPrimitive({SkewX(args)}); + } + else if (Scan(bytes_read, next, "skewY", angle1, args, 1)) + { + transform->AddPrimitive({SkewY(args)}); + } + else if (Scan(bytes_read, next, "skew", angle2, args, 2)) + { + transform->AddPrimitive({Skew2D(args)}); + } + + if (bytes_read > 0) + { + next += bytes_read; + } + else + { + return false; + } + } + + property.value = Variant(std::move(transform)); + property.unit = Unit::TRANSFORM; + + return true; +} + +bool PropertyParserTransform::Scan(int& out_bytes_read, const char* str, const char* keyword, const PropertyParser** parsers, NumericValue* args, + int nargs) const +{ + out_bytes_read = 0; + int total_bytes_read = 0, bytes_read = 0; + + /* skip leading white space */ + bytes_read = 0; + sscanf(str, " %n", &bytes_read); + str += bytes_read; + total_bytes_read += bytes_read; + + /* find the keyword */ + if (!memcmp(str, keyword, strlen(keyword))) + { + bytes_read = (int)strlen(keyword); + str += bytes_read; + total_bytes_read += bytes_read; + } + else + { + return false; + } + + /* skip any white space */ + bytes_read = 0; + sscanf(str, " %n", &bytes_read); + str += bytes_read; + total_bytes_read += bytes_read; + + /* find the opening brace */ + bytes_read = 0; + if (sscanf(str, " ( %n", &bytes_read), bytes_read) + { + str += bytes_read; + total_bytes_read += bytes_read; + } + else + { + return false; + } + + /* use the quicker stack-based argument buffer, if possible */ + char* arg = nullptr; + char arg_stack[1024]; + String arg_heap; + if (strlen(str) < sizeof(arg_stack)) + { + arg = arg_stack; + } + else + { + arg_heap = str; + arg = &arg_heap[0]; + } + + /* parse the arguments */ + for (int i = 0; i < nargs; ++i) + { + Property prop; + + bytes_read = 0; + if (sscanf(str, " %[^,)] %n", arg, &bytes_read), bytes_read && parsers[i]->ParseValue(prop, String(arg), ParameterMap())) + { + args[i].number = prop.value.Get(); + args[i].unit = prop.unit; + str += bytes_read; + total_bytes_read += bytes_read; + } + else + { + return false; + } + + /* find the comma */ + if (i < nargs - 1) + { + bytes_read = 0; + if (sscanf(str, " , %n", &bytes_read), bytes_read) + { + str += bytes_read; + total_bytes_read += bytes_read; + } + else + { + return false; + } + } + } + + /* find the closing brace */ + bytes_read = 0; + if (sscanf(str, " ) %n", &bytes_read), bytes_read) + { + str += bytes_read; + total_bytes_read += bytes_read; + } + else + { + return false; + } + + out_bytes_read = total_bytes_read; + return total_bytes_read > 0; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserTransform.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserTransform.h new file mode 100644 index 00000000..0d4279f2 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyParserTransform.h @@ -0,0 +1,37 @@ +#pragma once + +#include "../../Include/RmlUi/Core/PropertyParser.h" +#include "PropertyParserNumber.h" + +namespace Rml { + +/** + A property parser that parses a RCSS transform property specification. + */ +class PropertyParserTransform : public PropertyParser { +public: + PropertyParserTransform(); + virtual ~PropertyParserTransform(); + + /// Called to parse a RCSS transform declaration. + /// @param[out] property The property to set the parsed value on. + /// @param[in] value The raw value defined for this property. + /// @param[in] parameters The parameters defined for this property. + /// @return True if the value was validated successfully, false otherwise. + bool ParseValue(Property& property, const String& value, const ParameterMap& parameters) const override; + +private: + /// Scan a string for a parameterized keyword with a certain number of numeric arguments. + /// @param[out] out_bytes_read The number of bytes read if the keyword occurs at the beginning of str, 0 otherwise. + /// @param[in] str The string to search for the parameterized keyword + /// @param[in] keyword The name of the keyword to search for + /// @param[in] parsers The numeric argument parsers + /// @param[out] args The numeric arguments encountered + /// @param[in] nargs The number of numeric arguments expected + /// @return True if parsed successfully, false otherwise. + bool Scan(int& out_bytes_read, const char* str, const char* keyword, const PropertyParser** parsers, NumericValue* args, int nargs) const; + + PropertyParserNumber number, length, length_pct, angle; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyShorthandDefinition.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyShorthandDefinition.h new file mode 100644 index 00000000..884f0768 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertyShorthandDefinition.h @@ -0,0 +1,45 @@ +#pragma once + +#include "../../Include/RmlUi/Core/ID.h" + +namespace Rml { + +enum class ShorthandType; +class PropertyDefinition; +struct ShorthandDefinition; + +enum class ShorthandItemType { Invalid, Property, Shorthand }; + +// Each entry in a shorthand points either to another shorthand or a property +struct ShorthandItem { + ShorthandItem() : type(ShorthandItemType::Invalid), property_id(PropertyId::Invalid), property_definition(nullptr) {} + ShorthandItem(PropertyId id, const PropertyDefinition* definition, bool optional, bool repeats) : + type(ShorthandItemType::Property), optional(optional), repeats(repeats), property_id(id), property_definition(definition) + {} + ShorthandItem(ShorthandId id, const ShorthandDefinition* definition, bool optional, bool repeats) : + type(ShorthandItemType::Shorthand), optional(optional), repeats(repeats), shorthand_id(id), shorthand_definition(definition) + {} + + ShorthandItemType type = ShorthandItemType::Invalid; + bool optional = false; + bool repeats = false; + union { + PropertyId property_id; + ShorthandId shorthand_id; + }; + union { + const PropertyDefinition* property_definition; + const ShorthandDefinition* shorthand_definition; + }; +}; + +// A list of shorthands or properties +using ShorthandItemList = Vector; + +struct ShorthandDefinition { + ShorthandId id; + ShorthandItemList items; + ShorthandType type; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertySpecification.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertySpecification.cpp new file mode 100644 index 00000000..9c965ff7 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/PropertySpecification.cpp @@ -0,0 +1,602 @@ +#include "../../Include/RmlUi/Core/PropertySpecification.h" +#include "../../Include/RmlUi/Core/Debug.h" +#include "../../Include/RmlUi/Core/Log.h" +#include "../../Include/RmlUi/Core/Profiling.h" +#include "../../Include/RmlUi/Core/PropertyDefinition.h" +#include "../../Include/RmlUi/Core/PropertyDictionary.h" +#include "IdNameMap.h" +#include "PropertyShorthandDefinition.h" +#include +#include +#include + +namespace Rml { + +PropertySpecification::PropertySpecification(size_t reserve_num_properties, size_t reserve_num_shorthands) : + // Increment reserve numbers by one because the 'invalid' property occupies the first element + properties(reserve_num_properties + 1), shorthands(reserve_num_shorthands + 1), + property_map(MakeUnique(reserve_num_properties + 1)), shorthand_map(MakeUnique(reserve_num_shorthands + 1)) +{} + +PropertySpecification::~PropertySpecification() {} + +PropertyDefinition& PropertySpecification::RegisterProperty(const String& property_name, const String& default_value, bool inherited, + bool forces_layout, PropertyId id) +{ + if (id == PropertyId::Invalid) + id = property_map->GetOrCreateId(property_name); + else + property_map->AddPair(id, property_name); + + size_t index = (size_t)id; + + if (index >= size_t(PropertyId::MaxNumIds)) + { + Log::Message(Log::LT_ERROR, + "Fatal error while registering property '%s': Maximum number of allowed properties exceeded. Continuing execution may lead to crash.", + property_name.c_str()); + RMLUI_ERROR; + return *properties[0]; + } + + if (index < properties.size()) + { + // We don't want to owerwrite an existing entry. + if (properties[index]) + { + Log::Message(Log::LT_ERROR, "While registering property '%s': The property is already registered.", property_name.c_str()); + return *properties[index]; + } + } + else + { + // Resize vector to hold the new index + properties.resize((index * 3) / 2 + 1); + } + + // Create and insert the new property + properties[index] = MakeUnique(id, default_value, inherited, forces_layout); + property_ids.Insert(id); + if (inherited) + property_ids_inherited.Insert(id); + if (forces_layout) + property_ids_forcing_layout.Insert(id); + + return *properties[index]; +} + +const PropertyDefinition* PropertySpecification::GetProperty(PropertyId id) const +{ + if (id == PropertyId::Invalid || (size_t)id >= properties.size()) + return nullptr; + + return properties[(size_t)id].get(); +} + +const PropertyDefinition* PropertySpecification::GetProperty(const String& property_name) const +{ + return GetProperty(property_map->GetId(property_name)); +} + +const PropertyIdSet& PropertySpecification::GetRegisteredProperties() const +{ + return property_ids; +} + +const PropertyIdSet& PropertySpecification::GetRegisteredInheritedProperties() const +{ + return property_ids_inherited; +} + +const PropertyIdSet& PropertySpecification::GetRegisteredPropertiesForcingLayout() const +{ + return property_ids_forcing_layout; +} + +ShorthandId PropertySpecification::RegisterShorthand(const String& shorthand_name, const String& property_names, ShorthandType type, ShorthandId id) +{ + if (id == ShorthandId::Invalid) + id = shorthand_map->GetOrCreateId(shorthand_name); + else + shorthand_map->AddPair(id, shorthand_name); + + StringList property_list; + StringUtilities::ExpandString(property_list, StringUtilities::ToLower(property_names)); + + // Construct the new shorthand definition and resolve its properties. + UniquePtr property_shorthand(new ShorthandDefinition()); + + for (const String& raw_name : property_list) + { + ShorthandItem item; + bool optional = false; + bool repeats = false; + String name = raw_name; + + if (!raw_name.empty() && raw_name.back() == '?') + { + optional = true; + name.pop_back(); + } + if (!raw_name.empty() && raw_name.back() == '#') + { + repeats = true; + name.pop_back(); + } + + PropertyId property_id = property_map->GetId(name); + if (property_id != PropertyId::Invalid) + { + // We have a valid property + if (const PropertyDefinition* property = GetProperty(property_id)) + item = ShorthandItem(property_id, property, optional, repeats); + } + else + { + // Otherwise, we must be a shorthand + ShorthandId shorthand_id = shorthand_map->GetId(name); + + // Test for valid shorthand id. The recursive types (and only those) can hold other shorthands. + if (shorthand_id != ShorthandId::Invalid && (type == ShorthandType::RecursiveRepeat || type == ShorthandType::RecursiveCommaSeparated)) + { + if (const ShorthandDefinition* shorthand = GetShorthand(shorthand_id)) + item = ShorthandItem(shorthand_id, shorthand, optional, repeats); + } + } + + if (item.type == ShorthandItemType::Invalid) + { + Log::Message(Log::LT_ERROR, "Shorthand property '%s' was registered with invalid property '%s'.", shorthand_name.c_str(), name.c_str()); + return ShorthandId::Invalid; + } + property_shorthand->items.push_back(item); + } + + property_shorthand->id = id; + property_shorthand->type = type; + + const size_t index = (size_t)id; + + if (index >= size_t(ShorthandId::MaxNumIds)) + { + Log::Message(Log::LT_ERROR, "Error while registering shorthand '%s': Maximum number of allowed shorthands exceeded.", shorthand_name.c_str()); + return ShorthandId::Invalid; + } + + if (index < shorthands.size()) + { + // We don't want to owerwrite an existing entry. + if (shorthands[index]) + { + Log::Message(Log::LT_ERROR, "The shorthand '%s' already exists, ignoring.", shorthand_name.c_str()); + return ShorthandId::Invalid; + } + } + else + { + // Resize vector to hold the new index + shorthands.resize((index * 3) / 2 + 1); + } + + shorthands[index] = std::move(property_shorthand); + return id; +} + +const ShorthandDefinition* PropertySpecification::GetShorthand(ShorthandId id) const +{ + if (id == ShorthandId::Invalid || (size_t)id >= shorthands.size()) + return nullptr; + + return shorthands[(size_t)id].get(); +} + +const ShorthandDefinition* PropertySpecification::GetShorthand(const String& shorthand_name) const +{ + return GetShorthand(shorthand_map->GetId(shorthand_name)); +} + +bool PropertySpecification::ParsePropertyDeclaration(PropertyDictionary& dictionary, const String& property_name, const String& property_value) const +{ + RMLUI_ZoneScoped; + + // Try as a property first + PropertyId property_id = property_map->GetId(property_name); + if (property_id != PropertyId::Invalid) + return ParsePropertyDeclaration(dictionary, property_id, property_value); + + // Then, as a shorthand + ShorthandId shorthand_id = shorthand_map->GetId(property_name); + if (shorthand_id != ShorthandId::Invalid) + return ParseShorthandDeclaration(dictionary, shorthand_id, property_value); + + return false; +} + +bool PropertySpecification::ParsePropertyDeclaration(PropertyDictionary& dictionary, PropertyId property_id, const String& property_value) const +{ + // Parse as a single property. + const PropertyDefinition* property_definition = GetProperty(property_id); + if (!property_definition) + return false; + + StringList property_values; + ParsePropertyValues(property_values, property_value, SplitOption::None); + if (property_values.empty()) + return false; + + Property new_property; + if (!property_definition->ParseValue(new_property, property_values[0])) + return false; + + dictionary.SetProperty(property_id, new_property); + return true; +} + +bool PropertySpecification::ParseShorthandDeclaration(PropertyDictionary& dictionary, ShorthandId shorthand_id, const String& property_value) const +{ + const ShorthandDefinition* shorthand_definition = GetShorthand(shorthand_id); + if (!shorthand_definition) + return false; + + const SplitOption split_option = + (shorthand_definition->type == ShorthandType::RecursiveCommaSeparated ? SplitOption::Comma : SplitOption::Whitespace); + + StringList property_values; + ParsePropertyValues(property_values, property_value, split_option); + if (property_values.empty()) + return false; + + // Handle the special behavior of the flex shorthand first, otherwise it acts like 'FallThrough'. + if (shorthand_definition->type == ShorthandType::Flex && !property_values.empty()) + { + RMLUI_ASSERT(shorthand_definition->items.size() == 3); + if (property_values[0] == "none") + { + property_values = {"0", "0", "auto"}; + } + else + { + // Default values when omitted from the 'flex' shorthand is specified here. These defaults are special + // for this shorthand only, otherwise each underlying property has a different default value. + const char* default_omitted_values[] = {"1", "1", "0"}; // flex-grow, flex-shrink, flex-basis + Property new_property; + bool result = true; + for (int i = 0; i < 3; i++) + { + auto& item = shorthand_definition->items[i]; + result &= item.property_definition->ParseValue(new_property, default_omitted_values[i]); + dictionary.SetProperty(item.property_id, new_property); + } + (void)result; + RMLUI_ASSERT(result); + } + } + + // If this definition is a 'box'-style shorthand (x-top x-right x-bottom x-left) that needs replication. + if (shorthand_definition->type == ShorthandType::Box && property_values.size() < 4) + { + // This array tells which property index each side is parsed from + Array box_side_to_value_index = {0, 0, 0, 0}; + switch (property_values.size()) + { + case 1: + // Only one value is defined, so it is parsed onto all four sides. + box_side_to_value_index = {0, 0, 0, 0}; + break; + case 2: + // Two values are defined, so the first one is parsed onto the top and bottom value, the second onto + // the left and right. + box_side_to_value_index = {0, 1, 0, 1}; + break; + case 3: + // Three values are defined, so the first is parsed into the top value, the second onto the left and + // right, and the third onto the bottom. + box_side_to_value_index = {0, 1, 2, 1}; + break; + default: RMLUI_ERROR; break; + } + + for (int i = 0; i < 4; i++) + { + RMLUI_ASSERT(shorthand_definition->items[i].type == ShorthandItemType::Property); + Property new_property; + int value_index = box_side_to_value_index[i]; + if (!shorthand_definition->items[i].property_definition->ParseValue(new_property, property_values[value_index])) + return false; + + dictionary.SetProperty(shorthand_definition->items[i].property_definition->GetId(), new_property); + } + } + else if (shorthand_definition->type == ShorthandType::RecursiveRepeat) + { + bool result = true; + + for (size_t i = 0; i < shorthand_definition->items.size(); i++) + { + const ShorthandItem& item = shorthand_definition->items[i]; + if (item.type == ShorthandItemType::Property) + result &= ParsePropertyDeclaration(dictionary, item.property_id, property_value); + else if (item.type == ShorthandItemType::Shorthand) + result &= ParseShorthandDeclaration(dictionary, item.shorthand_id, property_value); + else + result = false; + } + + if (!result) + return false; + } + else if (shorthand_definition->type == ShorthandType::RecursiveCommaSeparated) + { + size_t num_optional = 0; + for (auto& item : shorthand_definition->items) + if (item.optional) + num_optional += 1; + + if (property_values.size() + num_optional < shorthand_definition->items.size()) + { + // Not enough subvalues declared. + return false; + } + + size_t subvalue_i = 0; + String temp_subvalue; + for (size_t i = 0; i < shorthand_definition->items.size() && subvalue_i < property_values.size(); i++) + { + bool result = false; + + const String* subvalue = &property_values[subvalue_i]; + + const ShorthandItem& item = shorthand_definition->items[i]; + if (item.repeats) + { + property_values.erase(property_values.begin(), property_values.begin() + subvalue_i); + temp_subvalue.clear(); + StringUtilities::JoinString(temp_subvalue, property_values); + subvalue = &temp_subvalue; + } + + if (item.type == ShorthandItemType::Property) + result = ParsePropertyDeclaration(dictionary, item.property_id, *subvalue); + else if (item.type == ShorthandItemType::Shorthand) + result = ParseShorthandDeclaration(dictionary, item.shorthand_id, *subvalue); + + if (result) + subvalue_i += 1; + else if (item.repeats || !item.optional) + return false; + + if (item.repeats) + break; + } + } + else + { + RMLUI_ASSERT(shorthand_definition->type == ShorthandType::Box || shorthand_definition->type == ShorthandType::FallThrough || + shorthand_definition->type == ShorthandType::Replicate || shorthand_definition->type == ShorthandType::Flex); + + // Abort over-specified shorthand values. + if (property_values.size() > shorthand_definition->items.size()) + return false; + + size_t value_index = 0; + size_t property_index = 0; + + for (; value_index < property_values.size() && property_index < shorthand_definition->items.size(); property_index++) + { + Property new_property; + + if (!shorthand_definition->items[property_index].property_definition->ParseValue(new_property, property_values[value_index])) + { + // This definition failed to parse; if we're falling through, try the next property. If there is no + // next property, then abort! + if (shorthand_definition->type == ShorthandType::FallThrough || shorthand_definition->type == ShorthandType::Flex) + { + if (property_index + 1 < shorthand_definition->items.size()) + continue; + } + return false; + } + + dictionary.SetProperty(shorthand_definition->items[property_index].property_id, new_property); + + // Increment the value index, unless we're replicating the last value and we're up to the last value. + if (shorthand_definition->type != ShorthandType::Replicate || value_index < property_values.size() - 1) + value_index++; + } + + // Abort if we still have values left to parse but no more properties to pass them to. + if (shorthand_definition->type != ShorthandType::Replicate && value_index < property_values.size() && + property_index >= shorthand_definition->items.size()) + return false; + } + + return true; +} + +void PropertySpecification::SetPropertyDefaults(PropertyDictionary& dictionary) const +{ + for (const auto& property : properties) + { + if (property && dictionary.GetProperty(property->GetId()) == nullptr) + dictionary.SetProperty(property->GetId(), *property->GetDefaultValue()); + } +} + +String PropertySpecification::PropertiesToString(const PropertyDictionary& dictionary, bool include_name, char delimiter) const +{ + const PropertyMap& properties = dictionary.GetProperties(); + + // For determinism we print the strings in order of increasing property ids. + Vector ids; + ids.reserve(properties.size()); + for (auto& pair : properties) + ids.push_back(pair.first); + + std::sort(ids.begin(), ids.end()); + + String result; + for (PropertyId id : ids) + { + const Property& p = properties.find(id)->second; + if (include_name) + result += property_map->GetName(id) + ": "; + result += p.ToString() + delimiter; + } + + if (!result.empty()) + result.pop_back(); + + return result; +} + +void PropertySpecification::ParsePropertyValues(StringList& values_list, const String& values, const SplitOption split_option) const +{ + RMLUI_ASSERT(values_list.empty()); + + const bool split_values = (split_option != SplitOption::None); + const bool split_by_comma = (split_option == SplitOption::Comma); + const bool split_by_whitespace = (split_option == SplitOption::Whitespace); + + String value; + + auto SubmitExactValue = [&]() { + values_list.push_back(std::move(value)); + value.clear(); + }; + + auto SubmitValue = [&]() { + value = StringUtilities::StripWhitespace(value); + if (!value.empty()) + SubmitExactValue(); + }; + + auto IsAllWhitespace = [](const String& string) { return std::all_of(string.begin(), string.end(), StringUtilities::IsWhitespace); }; + + auto Error = [&]() { values_list.clear(); }; + + enum ParseState { VALUE, VALUE_PARENTHESIS, VALUE_QUOTE, VALUE_QUOTE_ESCAPE_NEXT }; + ParseState state = VALUE; + int open_parentheses = 0; + char open_quote_character = 0; + size_t character_index = 0; + + while (character_index < values.size()) + { + const char character = values[character_index]; + character_index++; + + switch (state) + { + case VALUE: + { + if (character == ';') + { + if (value.size() > 0) + { + values_list.push_back(value); + value.clear(); + } + } + else if ((split_by_comma && character == ',') || (split_by_whitespace && StringUtilities::IsWhitespace(character))) + { + SubmitValue(); + } + else if (character == '"' || character == '\'') + { + state = VALUE_QUOTE; + open_quote_character = character; + if (split_by_whitespace) + SubmitValue(); + else if (split_by_comma) + value += character; + else if (IsAllWhitespace(value)) + value.clear(); + else + return Error(); + } + else if (character == '(') + { + open_parentheses = 1; + value += character; + state = VALUE_PARENTHESIS; + } + else + { + value += character; + } + } + break; + case VALUE_PARENTHESIS: + { + if (character == '(') + { + open_parentheses++; + } + else if (character == ')') + { + open_parentheses--; + if (open_parentheses == 0) + state = VALUE; + } + else if (character == '"' || character == '\'') + { + state = VALUE_QUOTE; + open_quote_character = character; + } + + value += character; + } + break; + case VALUE_QUOTE: + { + if (character == open_quote_character) + { + if (open_parentheses == 0) + { + state = VALUE; + if (split_by_comma) + value += character; + else + SubmitExactValue(); + } + else + { + state = VALUE_PARENTHESIS; + value += character; + } + } + else if (character == '\\') + { + state = VALUE_QUOTE_ESCAPE_NEXT; + } + else + { + value += character; + } + } + break; + case VALUE_QUOTE_ESCAPE_NEXT: + { + if (character == '"' || character == '\'' || character == '\\') + { + value += character; + } + else + { + value += '\\'; + value += character; + } + state = VALUE_QUOTE; + } + break; + } + } + + if (state == VALUE) + SubmitValue(); + + if (!split_values && values_list.size() > 1) + return Error(); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/RenderInterface.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/RenderInterface.cpp new file mode 100644 index 00000000..75be1f81 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/RenderInterface.cpp @@ -0,0 +1,65 @@ +#include "../../Include/RmlUi/Core/RenderInterface.h" + +namespace Rml { + +namespace CoreInternal { + bool HasRenderManager(RenderInterface* render_interface); +} + +RenderInterface::RenderInterface() {} + +RenderInterface::~RenderInterface() +{ + // Note: We cannot automatically release render resources here, because that involves a virtual call to this interface during its destruction + // which is illegal. + RMLUI_ASSERTMSG(!CoreInternal::HasRenderManager(this), + "RenderInterface is being destroyed, but it is still actively referenced and used within the RmlUi library. This may lead to use-after-free " + "or nullptr dereference when releasing render resources. Ensure that the render interface is destroyed *after* the call to Rml::Shutdown."); +} + +void RenderInterface::EnableClipMask(bool /*enable*/) {} + +void RenderInterface::RenderToClipMask(ClipMaskOperation /*operation*/, CompiledGeometryHandle /*geometry*/, Vector2f /*translation*/) {} + +void RenderInterface::SetTransform(const Matrix4f* /*transform*/) {} + +LayerHandle RenderInterface::PushLayer() +{ + return {}; +} + +void RenderInterface::CompositeLayers(LayerHandle /*source*/, LayerHandle /*destination*/, BlendMode /*blend_mode*/, + Span /*filters*/) +{} + +void RenderInterface::PopLayer() {} + +TextureHandle RenderInterface::SaveLayerAsTexture() +{ + return TextureHandle{}; +} + +CompiledFilterHandle RenderInterface::SaveLayerAsMaskImage() +{ + return CompiledFilterHandle{}; +} + +CompiledFilterHandle RenderInterface::CompileFilter(const String& /*name*/, const Dictionary& /*parameters*/) +{ + return CompiledFilterHandle{}; +} + +void RenderInterface::ReleaseFilter(CompiledFilterHandle /*filter*/) {} + +CompiledShaderHandle RenderInterface::CompileShader(const String& /*name*/, const Dictionary& /*parameters*/) +{ + return CompiledShaderHandle{}; +} + +void RenderInterface::RenderShader(CompiledShaderHandle /*shader*/, CompiledGeometryHandle /*geometry*/, Vector2f /*translation*/, + TextureHandle /*texture*/) +{} + +void RenderInterface::ReleaseShader(CompiledShaderHandle /*shader*/) {} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/RenderInterfaceCompatibility.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/RenderInterfaceCompatibility.cpp new file mode 100644 index 00000000..02d2532c --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/RenderInterfaceCompatibility.cpp @@ -0,0 +1,189 @@ +#include "../../Include/RmlUi/Core/RenderInterfaceCompatibility.h" +#include "../../Include/RmlUi/Core/Math.h" + +namespace Rml { + +static void UnPremultiplyAlpha(const byte* source, byte* destination) +{ + const byte alpha = source[3]; + destination[0] = (alpha > 0 ? (source[0] * 255) / alpha : 255); + destination[1] = (alpha > 0 ? (source[1] * 255) / alpha : 255); + destination[2] = (alpha > 0 ? (source[2] * 255) / alpha : 255); + destination[3] = alpha; +} + +RenderInterfaceCompatibility::RenderInterfaceCompatibility() : adapter(new RenderInterfaceAdapter(*this)) {} + +RenderInterfaceCompatibility::~RenderInterfaceCompatibility() {} + +CompiledGeometryHandle RenderInterfaceCompatibility::CompileGeometry(Vertex* /*vertices*/, int /*num_vertices*/, int* /*indices*/, + int /*num_indices*/, TextureHandle /*texture*/) +{ + return 0; +} + +void RenderInterfaceCompatibility::RenderCompiledGeometry(CompiledGeometryHandle /*geometry*/, const Vector2f& /*translation*/) {} + +void RenderInterfaceCompatibility::ReleaseCompiledGeometry(CompiledGeometryHandle /*geometry*/) {} + +bool RenderInterfaceCompatibility::LoadTexture(TextureHandle& /*texture_handle*/, Vector2i& /*texture_dimensions*/, const String& /*source*/) +{ + return false; +} + +bool RenderInterfaceCompatibility::GenerateTexture(TextureHandle& /*texture_handle*/, const byte* /*source*/, const Vector2i& /*source_dimensions*/) +{ + return false; +} + +void RenderInterfaceCompatibility::ReleaseTexture(TextureHandle /*texture*/) {} + +void RenderInterfaceCompatibility::SetTransform(const Matrix4f* /*transform*/) {} + +RenderInterface* RenderInterfaceCompatibility::GetAdaptedInterface() +{ + return static_cast(adapter.get()); +} + +RenderInterfaceAdapter::RenderInterfaceAdapter(RenderInterfaceCompatibility& legacy) : legacy(legacy) {} + +CompiledGeometryHandle RenderInterfaceAdapter::CompileGeometry(Span vertices, Span indices) +{ + // Previously, vertex colors were given in unpremultipled alpha, while now they are given in premultiplied alpha. If + // not corrected for, transparent colors may look darker than they should with the legacy renderer. Thus, here we + // make such a conversion. + // + // When upgrading your renderer, it is strongly recommended to convert your pipeline to use premultiplied alpha, + // both to avoid copying vertex data like here and to achieve correct blending results. + // + // Note that, the vertices and indices are now guaranteed to be valid and immutable until the call to + // ReleaseGeometry. Thus, it is possible to avoid copying the data even if you need access to it during the render + // call. However, (1) due to the need to modify the vertices, we need to make a copy of them here. And (2), due to a + // limitation in the legacy render interface, vertices and indices were previously submitted as pointers to mutable + // vertices and indices. They were never intended to be mutable, but to avoid a const_cast we need to copy both of + // them for that reason too. + + Vector vertices_unpremultiplied(vertices.begin(), vertices.end()); + for (size_t i = 0; i < vertices.size(); i++) + { + UnPremultiplyAlpha(vertices[i].colour, vertices_unpremultiplied[i].colour); + } + + Vector indices_copy(indices.begin(), indices.end()); + + AdaptedGeometry* data = new AdaptedGeometry{std::move(vertices_unpremultiplied), std::move(indices_copy), {}}; + return reinterpret_cast(data); +} + +void RenderInterfaceAdapter::RenderGeometry(CompiledGeometryHandle handle, Vector2f translation, TextureHandle texture) +{ + AdaptedGeometry* geometry = reinterpret_cast(handle); + + // Textures were previously stored with the compiled geometry, but is now instead submitted during rendering. + LegacyCompiledGeometryHandle& legacy_geometry = geometry->textures[texture]; + if (!legacy_geometry) + { + legacy_geometry = legacy.CompileGeometry(geometry->vertices.data(), (int)geometry->vertices.size(), geometry->indices.data(), + (int)geometry->indices.size(), texture); + } + + // If the legacy renderer supports compiling, use that, otherwise render the geometry in immediate mode. + if (legacy_geometry) + { + legacy.RenderCompiledGeometry(legacy_geometry, translation); + } + else + { + legacy.RenderGeometry(geometry->vertices.data(), (int)geometry->vertices.size(), geometry->indices.data(), (int)geometry->indices.size(), + texture, translation); + } +} + +void RenderInterfaceAdapter::ReleaseGeometry(CompiledGeometryHandle handle) +{ + AdaptedGeometry* geometry = reinterpret_cast(handle); + for (auto& pair : geometry->textures) + legacy.ReleaseCompiledGeometry(pair.second); + + delete reinterpret_cast(geometry); +} + +void RenderInterfaceAdapter::EnableScissorRegion(bool enable) +{ + legacy.EnableScissorRegion(enable); +} + +void RenderInterfaceAdapter::SetScissorRegion(Rectanglei region) +{ + legacy.SetScissorRegion(region.Left(), region.Top(), region.Width(), region.Height()); +} + +void RenderInterfaceAdapter::EnableClipMask(bool enable) +{ + legacy.EnableScissorRegion(enable); +} + +void RenderInterfaceAdapter::RenderToClipMask(ClipMaskOperation operation, CompiledGeometryHandle handle, Vector2f translation) +{ + switch (operation) + { + case ClipMaskOperation::Set: + case ClipMaskOperation::Intersect: + // Intersect is considered like Set. This typically occurs in nested clipping situations, which never worked + // correctly in legacy. + break; + case ClipMaskOperation::SetInverse: + // Using features not supported in legacy, bail out. + return; + } + + // New features can render more complex clip masks, while legacy only supported rectangle scissoring. Find the + // geometry's rectangular coverage. + const AdaptedGeometry* geometry = reinterpret_cast(handle); + + Rectanglef rectangle = Rectanglef::FromPosition(geometry->vertices[0].position); + for (const Vertex& vertex : geometry->vertices) + rectangle = rectangle.Join(vertex.position); + + const Rectanglei scissor = Rectanglei(rectangle.Translate(translation)); + legacy.SetScissorRegion(scissor.Left(), scissor.Top(), scissor.Width(), scissor.Height()); +} + +TextureHandle RenderInterfaceAdapter::LoadTexture(Vector2i& texture_dimensions, const String& source) +{ + TextureHandle texture_handle = {}; + if (!legacy.LoadTexture(texture_handle, texture_dimensions, source)) + texture_handle = {}; + return texture_handle; +} + +TextureHandle RenderInterfaceAdapter::GenerateTexture(Span source_data, Vector2i source_dimensions) +{ + // Previously, textures were given in unpremultiplied alpha format. Since RmlUi 6, they are given in premultiplied + // alpha. For compatibility, convert the texture to unpremultiplied alpha which is expected by legacy render + // interfaces. + const int num_bytes = source_dimensions.x * source_dimensions.y * 4; + std::unique_ptr unpremultiplied_copy(new byte[num_bytes]); + + for (int i = 0; i < num_bytes; i += 4) + { + UnPremultiplyAlpha(&source_data[i], unpremultiplied_copy.get() + i); + } + + TextureHandle texture_handle = {}; + if (!legacy.GenerateTexture(texture_handle, unpremultiplied_copy.get(), source_dimensions)) + texture_handle = {}; + return texture_handle; +} + +void RenderInterfaceAdapter::ReleaseTexture(TextureHandle texture_handle) +{ + legacy.ReleaseTexture(texture_handle); +} + +void RenderInterfaceAdapter::SetTransform(const Matrix4f* transform) +{ + legacy.SetTransform(transform); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/RenderManager.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/RenderManager.cpp new file mode 100644 index 00000000..4beea1e6 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/RenderManager.cpp @@ -0,0 +1,372 @@ +#include "../../Include/RmlUi/Core/RenderManager.h" +#include "../../Include/RmlUi/Core/Core.h" +#include "../../Include/RmlUi/Core/Geometry.h" +#include "../../Include/RmlUi/Core/Profiling.h" +#include "../../Include/RmlUi/Core/RenderInterface.h" +#include "../../Include/RmlUi/Core/SystemInterface.h" +#include "TextureDatabase.h" + +namespace Rml { + +RenderManager::RenderManager(RenderInterface* render_interface) : render_interface(render_interface), texture_database(MakeUnique()) +{ + RMLUI_ASSERT(render_interface); + + constexpr size_t reserve_geometry = 256; + geometry_list.reserve(reserve_geometry); +} + +RenderManager::~RenderManager() +{ + struct ResourceCount { + const char* name; + int count; + }; + ResourceCount elements[] = { + {"Geometry", (int)geometry_list.size()}, + {"CompiledFilter", compiled_filter_count}, + {"CompiledShader", compiled_shader_count}, + {"CallbackTexture", (int)texture_database->callback_database.size()}, + }; + + for (auto& element : elements) + { + if (element.count != 0) + { + Log::Message(Log::LT_ERROR, "Leaking %s detected (%d). Ensure that all RmlUi resources have been released by the end of Rml::Shutdown.", + element.name, element.count); + } + } + + ReleaseAllTextures(); +} + +void RenderManager::PrepareRender(Vector2i dimensions) +{ +#ifdef RMLUI_DEBUG + const RenderState default_state; + RMLUI_ASSERT(state.clip_mask_list == default_state.clip_mask_list); + RMLUI_ASSERT(state.scissor_region == default_state.scissor_region); + RMLUI_ASSERT(state.transform == default_state.transform); + RMLUI_ASSERTMSG(render_stack.empty(), "Unbalanced render stack detected, ensure every PushLayer call has a corresponding call to PopLayer."); +#endif + + SetViewport(dimensions); +} + +void RenderManager::SetViewport(Vector2i dimensions) +{ + viewport_dimensions = dimensions; +} + +Vector2i RenderManager::GetViewport() const +{ + return viewport_dimensions; +} + +Geometry RenderManager::MakeGeometry(Mesh&& mesh) +{ + if (!mesh) + return Geometry(); + return Geometry(this, InsertGeometry(std::move(mesh))); +} + +Texture RenderManager::LoadTexture(const String& source, const String& document_path) +{ + String path; + if (source.size() > 0 && source[0] == '?') + path = source; + else + GetSystemInterface()->JoinPath(path, StringUtilities::Replace(document_path, '|', ':'), source); + + return Texture(this, texture_database->file_database.InsertTexture(path)); +} + +CallbackTexture RenderManager::MakeCallbackTexture(CallbackTextureFunction callback) +{ + return CallbackTexture(this, texture_database->callback_database.CreateTexture(std::move(callback))); +} + +void RenderManager::DisableScissorRegion() +{ + SetScissorRegion(Rectanglei::MakeInvalid()); +} + +void RenderManager::SetScissorRegion(Rectanglei new_region) +{ + const bool old_scissor_enable = state.scissor_region.Valid(); + const bool new_scissor_enable = new_region.Valid(); + + if (new_scissor_enable != old_scissor_enable) + render_interface->EnableScissorRegion(new_scissor_enable); + + if (new_scissor_enable) + { + new_region = new_region.Intersect(Rectanglei::FromSize(viewport_dimensions)); + + if (new_region != state.scissor_region) + render_interface->SetScissorRegion(new_region); + } + + state.scissor_region = new_region; +} + +Rectanglei RenderManager::GetScissorRegion() const +{ + return state.scissor_region; +} + +void RenderManager::DisableClipMask() +{ + if (!state.clip_mask_list.empty()) + { + state.clip_mask_list.clear(); + ApplyClipMask(state.clip_mask_list); + } +} + +void RenderManager::SetClipMask(ClipMaskOperation operation, Geometry* geometry, Vector2f translation) +{ + RMLUI_ASSERT(geometry && geometry->render_manager == this); + state.clip_mask_list = {ClipMaskGeometry{operation, geometry, translation, nullptr}}; + ApplyClipMask(state.clip_mask_list); +} + +void RenderManager::SetClipMask(ClipMaskGeometryList in_clip_elements) +{ + if (state.clip_mask_list != in_clip_elements) + { + state.clip_mask_list = std::move(in_clip_elements); + ApplyClipMask(state.clip_mask_list); + } +} + +void RenderManager::SetTransform(const Matrix4f* p_new_transform) +{ + static const Matrix4f identity_transform = Matrix4f::Identity(); + const Matrix4f& new_transform = (p_new_transform ? *p_new_transform : identity_transform); + + if (state.transform != new_transform) + { + render_interface->SetTransform(p_new_transform); + state.transform = new_transform; + } +} + +void RenderManager::ApplyClipMask(const ClipMaskGeometryList& clip_elements) +{ + const bool clip_mask_enabled = !clip_elements.empty(); + render_interface->EnableClipMask(clip_mask_enabled); + + if (clip_mask_enabled) + { + const Matrix4f initial_transform = state.transform; + + for (const ClipMaskGeometry& element_clip : clip_elements) + { + RMLUI_ASSERT(element_clip.geometry->render_manager == this); + SetTransform(element_clip.transform); + if (CompiledGeometryHandle handle = GetCompiledGeometryHandle(element_clip.geometry->resource_handle)) + render_interface->RenderToClipMask(element_clip.operation, handle, element_clip.absolute_offset); + } + + // Apply the initially set transform in case it was changed. + SetTransform(&initial_transform); + } +} + +void RenderManager::SetState(const RenderState& next) +{ + SetScissorRegion(next.scissor_region); + + SetClipMask(next.clip_mask_list); + + SetTransform(&next.transform); +} + +void RenderManager::ResetState() +{ + SetState(RenderState{}); +} + +StableVectorIndex RenderManager::InsertGeometry(Mesh&& mesh) +{ + return geometry_list.insert(GeometryData{std::move(mesh), CompiledGeometryHandle{}}); +} + +CompiledGeometryHandle RenderManager::GetCompiledGeometryHandle(StableVectorIndex index) +{ + if (index == StableVectorIndex::Invalid) + return {}; + + GeometryData& geometry = geometry_list[index]; + if (!geometry.handle && !geometry.mesh.indices.empty()) + { + RMLUI_ZoneScopedNC("CompileGeometry", 0x1E60D2); + geometry.handle = render_interface->CompileGeometry(geometry.mesh.vertices, geometry.mesh.indices); + + if (!geometry.handle) + Log::Message(Log::LT_ERROR, "Got empty compiled geometry."); + } + return geometry.handle; +} + +void RenderManager::Render(const Geometry& geometry, Vector2f translation, Texture texture, const CompiledShader& shader) +{ + RMLUI_ASSERT(geometry); + RMLUI_ASSERTMSG(translation == translation.Round(), "RenderManager::Render expects translation to be rounded"); + + if (geometry.render_manager != this || (shader && shader.render_manager != this) || (texture && texture.render_manager != this)) + { + RMLUI_ERRORMSG("Trying to render geometry with resources constructed in different render managers."); + return; + } + + if (CompiledGeometryHandle geometry_handle = GetCompiledGeometryHandle(geometry.resource_handle)) + { + TextureHandle texture_handle = {}; + if (texture.file_index != TextureFileIndex::Invalid) + texture_handle = texture_database->file_database.GetHandle(render_interface, texture.file_index); + else if (texture.callback_index != StableVectorIndex::Invalid) + texture_handle = texture_database->callback_database.GetHandle(this, render_interface, texture.callback_index); + + RMLUI_ZoneScopedNC("RenderGeometry", 0x3E60B2); + if (shader) + render_interface->RenderShader(shader.resource_handle, geometry_handle, translation, texture_handle); + else + render_interface->RenderGeometry(geometry_handle, translation, texture_handle); + } +} + +void RenderManager::GetTextureSourceList(StringList& source_list) const +{ + texture_database->file_database.GetSourceList(source_list); +} + +const Mesh& RenderManager::GetMesh(const Geometry& geometry) const +{ + RMLUI_ASSERT(geometry.render_manager == this && geometry.resource_handle != geometry.InvalidHandle()); + return geometry_list[geometry.resource_handle].mesh; +} + +bool RenderManager::ReleaseTexture(const String& texture_source) +{ + return texture_database->file_database.ReleaseTexture(render_interface, texture_source); +} + +void RenderManager::ReleaseAllTextures() +{ + texture_database->callback_database.ReleaseAllTextures(render_interface); + texture_database->file_database.ReleaseAllTextures(render_interface); +} + +void RenderManager::ReleaseAllCompiledGeometry() +{ + geometry_list.for_each([this](GeometryData& data) { + if (data.handle) + { + render_interface->ReleaseGeometry(data.handle); + data.handle = {}; + } + }); +} + +CompiledFilter RenderManager::CompileFilter(const String& name, const Dictionary& parameters) +{ + if (CompiledFilterHandle handle = render_interface->CompileFilter(name, parameters)) + { + compiled_filter_count += 1; + return CompiledFilter(this, handle); + } + + return CompiledFilter(); +} + +CompiledShader RenderManager::CompileShader(const String& name, const Dictionary& parameters) +{ + if (CompiledShaderHandle handle = render_interface->CompileShader(name, parameters)) + { + compiled_shader_count += 1; + return CompiledShader(this, handle); + } + + return CompiledShader(); +} + +LayerHandle RenderManager::PushLayer() +{ + const LayerHandle layer = render_interface->PushLayer(); + render_stack.push_back(layer); + return layer; +} + +void RenderManager::CompositeLayers(LayerHandle source, LayerHandle destination, BlendMode blend_mode, Span filters) +{ + RMLUI_ASSERT(source == 0 || std::find(render_stack.begin(), render_stack.end(), source) != render_stack.end()); + RMLUI_ASSERT(destination == 0 || std::find(render_stack.begin(), render_stack.end(), destination) != render_stack.end()); + render_interface->CompositeLayers(source, destination, blend_mode, filters); +} + +void RenderManager::PopLayer() +{ + RMLUI_ASSERT(!render_stack.empty()); + render_interface->PopLayer(); + render_stack.pop_back(); +} + +LayerHandle RenderManager::GetTopLayer() const +{ + return render_stack.empty() ? LayerHandle{} : render_stack.back(); +} + +LayerHandle RenderManager::GetNextLayer() const +{ + RMLUI_ASSERT(!render_stack.empty()); + return render_stack.size() < 2 ? LayerHandle{} : render_stack[render_stack.size() - 2]; +} + +CompiledFilter RenderManager::SaveLayerAsMaskImage() +{ + if (CompiledFilterHandle handle = render_interface->SaveLayerAsMaskImage()) + { + compiled_filter_count += 1; + return CompiledFilter(this, handle); + } + return CompiledFilter(); +} + +void RenderManager::ReleaseResource(const CallbackTexture& texture) +{ + RMLUI_ASSERT(texture.render_manager == this && texture.resource_handle != texture.InvalidHandle()); + + texture_database->callback_database.ReleaseTexture(render_interface, texture.resource_handle); +} + +Mesh RenderManager::ReleaseResource(const Geometry& geometry) +{ + RMLUI_ASSERT(geometry.render_manager == this && geometry.resource_handle != geometry.InvalidHandle()); + RMLUI_ZoneScopedNC("ReleaseGeometry", 0x1E60D2); + + GeometryData data = geometry_list.erase(geometry.resource_handle); + if (data.handle) + render_interface->ReleaseGeometry(data.handle); + return std::move(data.mesh); +} + +void RenderManager::ReleaseResource(const CompiledFilter& filter) +{ + RMLUI_ASSERT(filter.render_manager == this && filter.resource_handle != filter.InvalidHandle()); + + render_interface->ReleaseFilter(filter.resource_handle); + compiled_filter_count -= 1; +} + +void RenderManager::ReleaseResource(const CompiledShader& shader) +{ + RMLUI_ASSERT(shader.render_manager == this && shader.resource_handle != shader.InvalidHandle()); + + render_interface->ReleaseShader(shader.resource_handle); + compiled_shader_count -= 1; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/RenderManagerAccess.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/RenderManagerAccess.cpp new file mode 100644 index 00000000..5d30d31d --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/RenderManagerAccess.cpp @@ -0,0 +1,48 @@ +#include "RenderManagerAccess.h" +#include "../../Include/RmlUi/Core/Texture.h" +#include "TextureDatabase.h" + +namespace Rml { + +Vector2i RenderManagerAccess::GetDimensions(RenderManager* render_manager, TextureFileIndex texture) +{ + return render_manager->texture_database->file_database.GetDimensions(render_manager->render_interface, texture); +} + +Vector2i RenderManagerAccess::GetDimensions(RenderManager* render_manager, StableVectorIndex callback_texture) +{ + return render_manager->texture_database->callback_database.GetDimensions(render_manager, render_manager->render_interface, callback_texture); +} + +void RenderManagerAccess::Render(RenderManager* render_manager, const Geometry& geometry, Vector2f translation, Texture texture, + const CompiledShader& shader) +{ + render_manager->Render(geometry, translation, texture, shader); +} + +void RenderManagerAccess::GetTextureSourceList(RenderManager* render_manager, StringList& source_list) +{ + render_manager->GetTextureSourceList(source_list); +} + +const Mesh& RenderManagerAccess::GetMesh(RenderManager* render_manager, const Geometry& geometry) +{ + return render_manager->GetMesh(geometry); +} + +bool RenderManagerAccess::ReleaseTexture(RenderManager* render_manager, const String& texture_source) +{ + return render_manager->ReleaseTexture(texture_source); +} + +void RenderManagerAccess::ReleaseAllTextures(RenderManager* render_manager) +{ + render_manager->ReleaseAllTextures(); +} + +void RenderManagerAccess::ReleaseAllCompiledGeometry(RenderManager* render_manager) +{ + render_manager->ReleaseAllCompiledGeometry(); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/RenderManagerAccess.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/RenderManagerAccess.h new file mode 100644 index 00000000..e459b9f3 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/RenderManagerAccess.h @@ -0,0 +1,48 @@ +#pragma once + +#include "../../Include/RmlUi/Core/Core.h" +#include "../../Include/RmlUi/Core/RenderManager.h" +#include "../../Include/RmlUi/Core/Types.h" + +namespace Rml { + +class CompiledFilter; +class CompiledShader; +class CallbackTexture; +class Geometry; +class Texture; + +class RenderManagerAccess { +private: + template + static auto ReleaseResource(RenderManager* render_manager, T& resource) + { + return render_manager->ReleaseResource(resource); + } + + static Vector2i GetDimensions(RenderManager* render_manager, TextureFileIndex texture); + static Vector2i GetDimensions(RenderManager* render_manager, StableVectorIndex callback_texture); + + static void Render(RenderManager* render_manager, const Geometry& geometry, Vector2f translation, Texture texture, const CompiledShader& shader); + + static void GetTextureSourceList(RenderManager* render_manager, StringList& source_list); + static const Mesh& GetMesh(RenderManager* render_manager, const Geometry& geometry); + + static bool ReleaseTexture(RenderManager* render_manager, const String& texture_source); + static void ReleaseAllTextures(RenderManager* render_manager); + static void ReleaseAllCompiledGeometry(RenderManager* render_manager); + + friend class CompiledFilter; + friend class CompiledShader; + friend class CallbackTexture; + friend class Geometry; + friend class Texture; + + friend StringList Rml::GetTextureSourceList(); + friend bool Rml::ReleaseTexture(const String&, RenderInterface*); + friend void Rml::ReleaseTextures(RenderInterface*); + friend void Rml::ReleaseCompiledGeometry(RenderInterface*); + friend void Rml::ReleaseRenderManagers(); +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ScrollController.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ScrollController.cpp new file mode 100644 index 00000000..c9a1169d --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ScrollController.cpp @@ -0,0 +1,332 @@ +#include "ScrollController.h" +#include "../../Include/RmlUi/Core/ComputedValues.h" +#include "../../Include/RmlUi/Core/Core.h" +#include "../../Include/RmlUi/Core/Element.h" +#include "../../Include/RmlUi/Core/SystemInterface.h" + +namespace Rml { + +static constexpr float AUTOSCROLL_SPEED_FACTOR = 0.09f; +static constexpr float AUTOSCROLL_DEADZONE = 10.0f; // [dp] + +static constexpr float SMOOTHSCROLL_WINDOW_SIZE = 100.f; // The window where smoothing is applied, as a distance from scroll start and end. [dp] +static constexpr float SMOOTHSCROLL_MAX_VELOCITY = 10'000.f; // [dp/s] +static constexpr float SMOOTHSCROLL_VELOCITY_CONSTANT = 800.f; // [dp/s] +static constexpr float SMOOTHSCROLL_VELOCITY_SQUARE_FACTOR = 0.05f; +static constexpr float SMOOTHSCROLL_FIRST_FRAME_DELTA_TIME_MIN = 1.f / 100.f; // To make the scroll feel a bit more responsive. [s] + +// Factor to multiply friction by before applying to velocity. +static constexpr float INERTIA_FRICTION_FACTOR = 5.0f; +static constexpr float INERTIA_VELOCITY_CUTOFF = 30.0f; + +// Saturate the delta time to some reasonable FPS value, to avoid large steps in case of stuttering or freezing. +static constexpr float DELTA_TIME_MAX = 1.f / 15.f; // [s] + +// Determines the autoscroll velocity based on the distance from the scroll-start mouse position. [px/s] +static Vector2f CalculateAutoscrollVelocity(Vector2f target_delta, float dp_ratio) +{ + target_delta = target_delta / dp_ratio; + target_delta = { + Math::Absolute(target_delta.x) < AUTOSCROLL_DEADZONE ? 0.f : target_delta.x, + Math::Absolute(target_delta.y) < AUTOSCROLL_DEADZONE ? 0.f : target_delta.y, + }; + + // We use a signed square model for the velocity, which seems to work quite well. This is mostly about feeling and tuning. + return AUTOSCROLL_SPEED_FACTOR * target_delta * Math::Absolute(target_delta); +} + +// Determines the smoothscroll velocity based on the distance to the target, and the distance scrolled so far. [px/s] +static Vector2f CalculateSmoothscrollVelocity(Vector2f target_delta, Vector2f scrolled_distance, float dp_ratio) +{ + scrolled_distance = Math::Absolute(scrolled_distance) / dp_ratio; + target_delta = target_delta / dp_ratio; + + const Vector2f target_delta_abs = Math::Absolute(target_delta); + Vector2f target_delta_signum = { + target_delta.x > 0.f ? 1.f : (target_delta.x < 0.f ? -1.f : 0.f), + target_delta.y > 0.f ? 1.f : (target_delta.y < 0.f ? -1.f : 0.f), + }; + + // The window provides velocity smoothing near the start and end of the scroll. + const Tween tween(Tween::Exponential, Tween::Out); + const Vector2f alpha_in = Math::Min(scrolled_distance / SMOOTHSCROLL_WINDOW_SIZE, Vector2f(1.f)); + const Vector2f alpha_out = Math::Min(target_delta_abs / SMOOTHSCROLL_WINDOW_SIZE, Vector2f(1.f)); + const Vector2f smooth_window = { + 0.35f + 0.65f * tween(alpha_in.x) * tween(alpha_out.x), + 0.35f + 0.65f * tween(alpha_in.y) * tween(alpha_out.y), + }; + + const Vector2f velocity_constant = Vector2f(SMOOTHSCROLL_VELOCITY_CONSTANT); + const Vector2f velocity_square = SMOOTHSCROLL_VELOCITY_SQUARE_FACTOR * target_delta_abs * target_delta_abs; + + // Short scrolls are dominated by the smoothed constant velocity, while the square term is added for quick longer scrolls. + return dp_ratio * target_delta_signum * smooth_window * Math::Min(velocity_constant + velocity_square, Vector2f(SMOOTHSCROLL_MAX_VELOCITY)); +} + +void ScrollController::ActivateAutoscroll(Element* in_target, Vector2i start_position) +{ + Reset(); + if (!in_target) + return; + target = in_target; + mode = Mode::Autoscroll; + autoscroll_start_position = start_position; + UpdateTime(); +} + +void ScrollController::ActivateSmoothscroll(Element* in_target, Vector2f delta_distance, ScrollBehavior scroll_behavior) +{ + Reset(); + if (!in_target) + return; + + target = in_target; + + // Do instant scroll if preferred. + if (smoothscroll_prefer_instant && scroll_behavior != ScrollBehavior::Smooth) + { + PerformScrollOnTarget(delta_distance); + target = nullptr; + return; + } + + mode = Mode::Smoothscroll; + UpdateTime(); + IncrementSmoothscrollTarget(delta_distance); + + // If the target is scrolled to its edge already, simply cancel the smoothscroll operation. + if (HasSmoothscrollReachedTarget()) + Reset(); +} + +void ScrollController::ActivateInertia(Element* in_target, Vector2f velocity) +{ + Reset(); + if (!in_target || (velocity.x == 0 && velocity.y == 0)) + return; + + target = in_target; + inertia_scroll_velocity = velocity; + mode = Mode::Inertia; + UpdateTime(); +} + +void ScrollController::InstantScrollOnTarget(Element* in_target, Vector2f delta_distance) +{ + if (!in_target) + return; + + // instant scroll element without changing the current target + + Element* safe_target = target; + + target = in_target; + PerformScrollOnTarget(delta_distance); + target = safe_target; +} + +bool ScrollController::Update(Vector2i mouse_position, float dp_ratio) +{ + const float dt = (mode == Mode::None ? 0.f : UpdateTime()); + + switch (mode) + { + case Mode::Smoothscroll: UpdateSmoothscroll(dt, dp_ratio); break; + case Mode::Autoscroll: UpdateAutoscroll(dt, mouse_position, dp_ratio); break; + case Mode::Inertia: UpdateInertia(dt); break; + case Mode::None: break; + } + + return mode != Mode::None; +} + +void ScrollController::UpdateAutoscroll(float dt, Vector2i mouse_position, float dp_ratio) +{ + RMLUI_ASSERT(mode == Mode::Autoscroll && target); + + const Vector2f scroll_delta = Vector2f(mouse_position - autoscroll_start_position); + const Vector2f scroll_velocity = CalculateAutoscrollVelocity(scroll_delta, dp_ratio); + + autoscroll_accumulated_length += scroll_velocity * dt; + + // Only submit the integer part of the scroll length, accumulate and store fractional parts to enable sub-pixel-per-frame scrolling speeds. + Vector2f scroll_length_integral; + autoscroll_accumulated_length.x = Math::DecomposeFractionalIntegral(autoscroll_accumulated_length.x, &scroll_length_integral.x); + autoscroll_accumulated_length.y = Math::DecomposeFractionalIntegral(autoscroll_accumulated_length.y, &scroll_length_integral.y); + + if (scroll_velocity != Vector2f(0.f)) + autoscroll_moved = true; + + PerformScrollOnTarget(scroll_length_integral); +} + +void ScrollController::UpdateSmoothscroll(float dt, float dp_ratio) +{ + RMLUI_ASSERT(mode == Mode::Smoothscroll && target); + + const Vector2f target_delta = Vector2f(smoothscroll_target_distance - smoothscroll_scrolled_distance); + const Vector2f velocity = CalculateSmoothscrollVelocity(target_delta, smoothscroll_scrolled_distance, dp_ratio); + + if (smoothscroll_scrolled_distance == Vector2f{0}) + dt = Math::Max(dt, SMOOTHSCROLL_FIRST_FRAME_DELTA_TIME_MIN); + + Vector2f scroll_distance_fractional = smoothscroll_speed_factor * velocity * dt + smoothscroll_accumulated_fractional_distance; + + Vector2f scroll_distance_integral; + smoothscroll_accumulated_fractional_distance.x = Math::DecomposeFractionalIntegral(scroll_distance_fractional.x, &scroll_distance_integral.x); + smoothscroll_accumulated_fractional_distance.y = Math::DecomposeFractionalIntegral(scroll_distance_fractional.y, &scroll_distance_integral.y); + + for (int i = 0; i < 2; i++) + { + // Clamp the distance to the target in case of overshooting integration. + if (target_delta[i] > 0.f) + scroll_distance_integral[i] = Math::Min(scroll_distance_integral[i], target_delta[i]); + else if (target_delta[i] < 0.f) + scroll_distance_integral[i] = Math::Max(scroll_distance_integral[i], target_delta[i]); + else + scroll_distance_integral[i] = 0.f; + } + +#if 0 + // Useful debugging output for velocity model tuning. + Log::Message(Log::LT_INFO, "Scroll y0 %8.2f y1 %8.2f dt %1.4f v %8.2f dy %8.2f frac %1.2f", smoothscroll_scrolled_distance.y, + target_delta.y, dt, velocity.y, scroll_distance_integral.y, smoothscroll_accumulated_fractional_distance.y); +#endif + + smoothscroll_scrolled_distance += scroll_distance_integral; + PerformScrollOnTarget(scroll_distance_integral); + + if (HasSmoothscrollReachedTarget()) + Reset(); +} + +void ScrollController::UpdateInertia(float dt) +{ + RMLUI_ASSERT(mode == Mode::Inertia && target); + + if (inertia_scroll_velocity.x == 0.0f && inertia_scroll_velocity.y == 0.0f) + { + Reset(); + return; + } + + // Apply and dampen inertia. + Vector2f scroll_delta = inertia_scroll_velocity * dt; + PerformScrollOnTarget(scroll_delta); + + float dampening = 1.0f - INERTIA_FRICTION_FACTOR * dt; + inertia_scroll_velocity *= dampening; + + if (Math::Absolute(inertia_scroll_velocity.x) < INERTIA_VELOCITY_CUTOFF) + inertia_scroll_velocity.x = 0.0f; + if (Math::Absolute(inertia_scroll_velocity.y) < INERTIA_VELOCITY_CUTOFF) + inertia_scroll_velocity.y = 0.0f; +} + +bool ScrollController::HasSmoothscrollReachedTarget() const +{ + constexpr float epsilon = 0.1f; + return (smoothscroll_target_distance - smoothscroll_scrolled_distance).SquaredMagnitude() < epsilon; +} + +void ScrollController::PerformScrollOnTarget(Vector2f delta_distance) +{ + RMLUI_ASSERT(target); + auto overflow_is_scrollable = [](Style::Overflow overflow) { return overflow == Style::Overflow::Auto || overflow == Style::Overflow::Scroll; }; + auto& computed_values = target->GetComputedValues(); + + if (delta_distance.x != 0.f && overflow_is_scrollable(computed_values.overflow_x())) + target->SetScrollLeft(target->GetScrollLeft() + delta_distance.x); + if (delta_distance.y != 0.f && overflow_is_scrollable(computed_values.overflow_y())) + target->SetScrollTop(target->GetScrollTop() + delta_distance.y); +} + +void ScrollController::IncrementSmoothscrollTarget(Vector2f delta_distance) +{ + auto OppositeDirection = [](float a, float b) { return (a < 0.f && b > 0.f) || (a > 0.f && b < 0.f); }; + Vector2f delta = smoothscroll_target_distance - smoothscroll_scrolled_distance; + + // Reset movement state if we start scrolling in the opposite direction. + for (int i = 0; i < 2; i++) + { + if (OppositeDirection(delta_distance[i], delta[i])) + { + smoothscroll_target_distance[i] = 0.f; + smoothscroll_scrolled_distance[i] = 0.f; + smoothscroll_accumulated_fractional_distance[i] = 0.f; + } + } + + // Clamp the delta distance to the scrollable area. + const Vector2f scroll_offset = {target->GetScrollLeft(), target->GetScrollTop()}; + const Vector2f max_offset = {target->GetScrollWidth() - target->GetClientWidth(), target->GetScrollHeight() - target->GetClientHeight()}; + + const Vector2f target_offset = scroll_offset + smoothscroll_target_distance - smoothscroll_scrolled_distance; + const Vector2f clamped_delta = Math::Clamp(delta_distance + target_offset, Vector2f(0.f), max_offset) - target_offset; + + smoothscroll_target_distance += clamped_delta; +} + +void ScrollController::Reset() +{ + mode = Mode::None; + target = nullptr; + + autoscroll_start_position = Vector2i{}; + autoscroll_accumulated_length = Vector2f{}; + autoscroll_moved = false; + + smoothscroll_target_distance = Vector2f{}; + smoothscroll_scrolled_distance = Vector2f{}; + smoothscroll_accumulated_fractional_distance = Vector2f{}; + // Keep smoothscroll configuration parameters. + + inertia_scroll_velocity = Vector2f{}; +} + +void ScrollController::SetDefaultScrollBehavior(ScrollBehavior scroll_behavior, float speed_factor) +{ + smoothscroll_prefer_instant = (scroll_behavior == ScrollBehavior::Instant); + smoothscroll_speed_factor = speed_factor; +} + +String ScrollController::GetAutoscrollCursor(Vector2i mouse_position, float dp_ratio) const +{ + RMLUI_ASSERT(mode == Mode::Autoscroll); + + const Vector2f scroll_delta = Vector2f(mouse_position - autoscroll_start_position); + const Vector2f scroll_velocity = CalculateAutoscrollVelocity(scroll_delta, dp_ratio); + + if (scroll_velocity == Vector2f(0.f)) + return "rmlui-scroll-idle"; + + String result = "rmlui-scroll"; + + if (scroll_velocity.y > 0.f) + result += "-up"; + else if (scroll_velocity.y < 0.f) + result += "-down"; + + if (scroll_velocity.x > 0.f) + result += "-right"; + else if (scroll_velocity.x < 0.f) + result += "-left"; + + return result; +} + +bool ScrollController::HasAutoscrollMoved() const +{ + return mode == Mode::Autoscroll && autoscroll_moved; +} + +float ScrollController::UpdateTime() +{ + const double previous_tick = previous_update_time; + previous_update_time = GetSystemInterface()->GetElapsedTime(); + + const float dt = float(previous_update_time - previous_tick); + return Math::Min(dt, DELTA_TIME_MAX); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ScrollController.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ScrollController.h new file mode 100644 index 00000000..9dcab920 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/ScrollController.h @@ -0,0 +1,81 @@ +#pragma once + +#include "../../Include/RmlUi/Core/Header.h" +#include "../../Include/RmlUi/Core/ScrollTypes.h" +#include "../../Include/RmlUi/Core/Types.h" + +namespace Rml { + +/** + Implements scrolling behavior that occurs over time. + + Scrolling modes are activated externally, targeting a given element. The actual scrolling takes place during update calls. + */ + +class ScrollController { +public: + enum class Mode { + None, + Smoothscroll, // Smooth scrolling to target distance. + Autoscroll, // Scrolling with middle mouse button. + Inertia, // Applying scrolling inertia when using swipe gesture + }; + + void ActivateAutoscroll(Element* target, Vector2i start_position); + void ActivateSmoothscroll(Element* target, Vector2f delta_distance, ScrollBehavior scroll_behavior); + void ActivateInertia(Element* target, Vector2f velocity); + + void InstantScrollOnTarget(Element* target, Vector2f delta_distance); + + bool Update(Vector2i mouse_position, float dp_ratio); + + void IncrementSmoothscrollTarget(Vector2f delta_distance); + + // Resets any active mode and its state. + void Reset(); + + // Sets the scroll behavior for mouse wheel processing and scrollbar interaction. + void SetDefaultScrollBehavior(ScrollBehavior scroll_behavior, float speed_factor); + + // Returns the autoscroll cursor based on the active scroll velocity. + String GetAutoscrollCursor(Vector2i mouse_position, float dp_ratio) const; + // Returns true if autoscroll mode is active and the cursor has been moved outside the idle scroll area. + bool HasAutoscrollMoved() const; + + Mode GetMode() const { return mode; } + Element* GetTarget() const { return target; } + +private: + // Updates time to now, and returns the delta time since the previous time update. + float UpdateTime(); + + void UpdateAutoscroll(float dt, Vector2i mouse_position, float dp_ratio); + + void UpdateSmoothscroll(float dt, float dp_ratio); + + void UpdateInertia(float dt); + + bool HasSmoothscrollReachedTarget() const; + + void PerformScrollOnTarget(Vector2f delta_distance); + + Mode mode = Mode::None; + + Element* target = nullptr; + double previous_update_time = 0; + + Vector2i autoscroll_start_position; + Vector2f autoscroll_accumulated_length; + bool autoscroll_moved = false; + + bool smoothscroll_prefer_instant = false; + float smoothscroll_speed_factor = 1.f; + + Vector2f smoothscroll_target_distance; + Vector2f smoothscroll_scrolled_distance; + Vector2f smoothscroll_accumulated_fractional_distance; + + Vector2f inertia_scroll_velocity; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Spritesheet.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Spritesheet.cpp new file mode 100644 index 00000000..6603e96c --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Spritesheet.cpp @@ -0,0 +1,78 @@ +#include "../../Include/RmlUi/Core/Spritesheet.h" +#include "../../Include/RmlUi/Core/Log.h" + +namespace Rml { + +Spritesheet::Spritesheet(const String& name, const String& source, const String& document_path, int definition_line_number, float display_scale) : + name(name), definition_line_number(definition_line_number), display_scale(display_scale), texture_source(source, document_path) +{} + +bool SpritesheetList::AddSpriteSheet(const String& name, const String& image_source, const String& definition_source, int definition_line_number, + float display_scale, const SpriteDefinitionList& sprite_definitions) +{ + auto sprite_sheet_ptr = MakeShared(name, image_source, definition_source, definition_line_number, display_scale); + Spritesheet* sprite_sheet = sprite_sheet_ptr.get(); + spritesheets.push_back(std::move(sprite_sheet_ptr)); + + sprite_map.reserve(sprite_map.size() + sprite_definitions.size()); + + // Insert all the sprites, overwriting any existing sprites already defined in the current media block scope. + for (auto& sprite_definition : sprite_definitions) + { + const String& sprite_name = sprite_definition.first; + const Rectanglef& sprite_rectangle = sprite_definition.second; + + Sprite& new_sprite = sprite_map[sprite_name]; + if (new_sprite.sprite_sheet) + { + Log::Message(Log::LT_WARNING, "Sprite '%s' was overwritten due to duplicate names at the same block scope. Declared at %s:%d and %s:%d", + sprite_name.c_str(), new_sprite.sprite_sheet->texture_source.GetDefinitionSource().c_str(), + new_sprite.sprite_sheet->definition_line_number, definition_source.c_str(), definition_line_number); + } + + new_sprite = Sprite{sprite_rectangle, sprite_sheet}; + } + + return true; +} + +const Sprite* SpritesheetList::GetSprite(const String& name) const +{ + auto it = sprite_map.find(name); + if (it != sprite_map.end()) + return &it->second; + return nullptr; +} + +void SpritesheetList::Merge(const SpritesheetList& other) +{ + spritesheets.insert(spritesheets.end(), other.spritesheets.begin(), other.spritesheets.end()); + sprite_map.reserve(sprite_map.size() + other.sprite_map.size()); + + for (auto& pair : other.sprite_map) + { + const String& sprite_name = pair.first; + const Sprite& sprite = pair.second; + + // Add the sprite into our map. If a sprite with the same name exists, it will be overwritten by the other sprite. + sprite_map[sprite_name] = sprite; + } +} + +void SpritesheetList::Reserve(size_t size_sprite_sheets, size_t size_sprites) +{ + spritesheets.reserve(size_sprite_sheets); + sprite_map.reserve(size_sprites); +} + +size_t SpritesheetList::NumSpriteSheets() const +{ + return spritesheets.size(); +} + +size_t SpritesheetList::NumSprites() const +{ + return sprite_map.size(); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Stream.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Stream.cpp new file mode 100644 index 00000000..38a4b36d --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Stream.cpp @@ -0,0 +1,119 @@ +#include "../../Include/RmlUi/Core/Stream.h" +#include +#include + +namespace Rml { + +const size_t READ_BLOCK_SIZE = 1024; + +Stream::Stream() +{ + stream_mode = 0; +} + +Stream::~Stream() +{ + Stream::Close(); +} + +void Stream::Close() +{ + stream_mode = 0; +} + +int Stream::GetStreamMode() const +{ + return stream_mode; +} + +const URL& Stream::GetSourceURL() const +{ + return url; +} + +bool Stream::IsEOS() const +{ + return Tell() >= Length(); +} + +size_t Stream::Peek(void* buffer, size_t bytes) const +{ + size_t pos = Tell(); + size_t read = Read(buffer, bytes); + Seek((long)pos, SEEK_SET); + return read; +} + +size_t Stream::Read(Stream* stream, size_t bytes) const +{ + byte buffer[READ_BLOCK_SIZE]; + size_t total_bytes_read = 0; + while (total_bytes_read < bytes) + { + size_t bytes_read = this->Read(buffer, Math::Min(READ_BLOCK_SIZE, bytes - total_bytes_read)); + if (bytes_read < 1) + return total_bytes_read; + stream->Write(buffer, bytes_read); + total_bytes_read += bytes_read; + } + return total_bytes_read; +} + +size_t Stream::Read(String& string, size_t bytes) const +{ + size_t string_size = string.size(); + string.resize(string_size + bytes + 1); + size_t read = Read(&string[string_size], bytes); + string[string_size + read] = '\0'; + string.resize(string_size + read); + return read; +} + +size_t Stream::Write(const Stream* stream, size_t bytes) +{ + return stream->Read(this, bytes); +} + +size_t Stream::Write(const char* string) +{ + return Write(string, strlen(string)); +} + +size_t Stream::Write(const String& string) +{ + return Write(string.c_str(), string.size()); +} + +size_t Stream::PushFront(const void* /*buffer*/, size_t /*bytes*/) +{ + RMLUI_ERRORMSG("No generic way to PushFront to a stream."); + return false; +} + +size_t Stream::PushBack(const void* buffer, size_t bytes) +{ + size_t pos = Tell(); + Seek(0, SEEK_END); + size_t wrote = Write(buffer, bytes); + Seek((long)pos, SEEK_SET); + return wrote; +} + +size_t Stream::PopFront(size_t /*bytes*/) +{ + RMLUI_ERRORMSG("No generic way to PopFront from a stream."); + return 0; +} + +size_t Stream::PopBack(size_t bytes) +{ + return Truncate(Length() - bytes); +} + +void Stream::SetStreamDetails(const URL& _url, int _stream_mode) +{ + url = _url; + stream_mode = _stream_mode; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StreamFile.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StreamFile.cpp new file mode 100644 index 00000000..7920edc1 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StreamFile.cpp @@ -0,0 +1,100 @@ +#include "StreamFile.h" +#include "../../Include/RmlUi/Core/Core.h" +#include "../../Include/RmlUi/Core/FileInterface.h" +#include "../../Include/RmlUi/Core/StringUtilities.h" + +namespace Rml { + +StreamFile::StreamFile() +{ + file_handle = 0; + length = 0; +} + +StreamFile::~StreamFile() +{ + if (file_handle) + StreamFile::Close(); +} + +bool StreamFile::Open(const String& path) +{ + String url_safe_path = StringUtilities::Replace(path, ':', '|'); + SetStreamDetails(URL(url_safe_path), Stream::MODE_READ); + + if (file_handle) + Close(); + + // Fix the path if a leading colon has been replaced with a pipe. + String fixed_path = StringUtilities::Replace(path, '|', ':'); + file_handle = GetFileInterface()->Open(fixed_path); + if (!file_handle) + { + Log::Message(Log::LT_WARNING, "Unable to open file %s.", fixed_path.c_str()); + return false; + } + + GetLength(); + + return true; +} + +void StreamFile::Close() +{ + if (file_handle) + { + GetFileInterface()->Close(file_handle); + file_handle = 0; + } + + length = 0; + Stream::Close(); +} + +size_t StreamFile::Length() const +{ + return length; +} + +size_t StreamFile::Tell() const +{ + return GetFileInterface()->Tell(file_handle); +} + +bool StreamFile::Seek(long offset, int origin) const +{ + return GetFileInterface()->Seek(file_handle, offset, origin); +} + +size_t StreamFile::Read(void* buffer, size_t bytes) const +{ + return GetFileInterface()->Read(buffer, bytes, file_handle); +} + +size_t StreamFile::Write(const void* /*buffer*/, size_t /*bytes*/) +{ + RMLUI_ERROR; + return 0; +} + +size_t StreamFile::Truncate(size_t /*bytes*/) +{ + RMLUI_ERROR; + return 0; +} + +bool StreamFile::IsReadReady() +{ + return Tell() < Length(); +} + +bool StreamFile::IsWriteReady() +{ + return false; +} +void StreamFile::GetLength() +{ + length = GetFileInterface()->Length(file_handle); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StreamFile.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StreamFile.h new file mode 100644 index 00000000..0e1a86c5 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StreamFile.h @@ -0,0 +1,50 @@ +#pragma once + +#include "../../Include/RmlUi/Core/Stream.h" +#include "../../Include/RmlUi/Core/Types.h" + +namespace Rml { + +class StreamFile final : public Stream { +public: + StreamFile(); + virtual ~StreamFile(); + + /// Attempts to open the stream pointing at a given location. + bool Open(const String& path); + /// Closes the stream. + void Close() override; + + /// Returns the size of this stream (in bytes). + size_t Length() const override; + + /// Returns the position of the stream pointer (in bytes). + size_t Tell() const override; + /// Sets the stream position (in bytes). + bool Seek(long offset, int origin) const override; + + /// Read from the stream. + size_t Read(void* buffer, size_t bytes) const override; + using Stream::Read; + + /// Write to the stream at the current position. + size_t Write(const void* buffer, size_t bytes) override; + using Stream::Write; + + /// Truncate the stream to the specified length. + size_t Truncate(size_t bytes) override; + + /// Returns true if the stream is ready for reading, false otherwise. + bool IsReadReady() override; + /// Returns false. + bool IsWriteReady() override; + +private: + // Determines the length of the stream. + void GetLength(); + + FileHandle file_handle; + size_t length; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StreamMemory.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StreamMemory.cpp new file mode 100644 index 00000000..cb88177c --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StreamMemory.cpp @@ -0,0 +1,196 @@ +#include "../../Include/RmlUi/Core/StreamMemory.h" +#include +#include + +namespace Rml { + +const int DEFAULT_BUFFER_SIZE = 256; +const int BUFFER_INCREMENTS = 256; + +StreamMemory::StreamMemory() +{ + buffer = nullptr; + buffer_ptr = nullptr; + buffer_size = 0; + buffer_used = 0; + owns_buffer = true; + Reallocate(DEFAULT_BUFFER_SIZE); +} + +StreamMemory::StreamMemory(size_t initial_size) +{ + buffer = nullptr; + buffer_ptr = nullptr; + buffer_size = 0; + buffer_used = 0; + owns_buffer = true; + Reallocate(initial_size); +} + +StreamMemory::StreamMemory(const byte* _buffer, size_t _buffer_size) +{ + buffer = (byte*)_buffer; + buffer_size = _buffer_size; + buffer_used = _buffer_size; + owns_buffer = false; + buffer_ptr = buffer; +} + +StreamMemory::~StreamMemory() +{ + if (owns_buffer) + free(buffer); +} + +void StreamMemory::Close() +{ + Stream::Close(); +} + +bool StreamMemory::IsEOS() const +{ + return buffer_ptr >= buffer + buffer_used; +} + +size_t StreamMemory::Tell() const +{ + return buffer_ptr - buffer; +} + +size_t StreamMemory::Length() const +{ + return buffer_used; +} + +size_t StreamMemory::Read(void* _buffer, size_t bytes) const +{ + bytes = Math::Min(bytes, (size_t)(buffer + buffer_used - buffer_ptr)); + + memcpy(_buffer, buffer_ptr, bytes); + + buffer_ptr += bytes; + + return bytes; +} + +size_t StreamMemory::Peek(void* _buffer, size_t bytes) const +{ + bytes = Math::Min(bytes, (size_t)(buffer + buffer_used - buffer_ptr)); + + memcpy(_buffer, buffer_ptr, bytes); + + return bytes; +} + +size_t StreamMemory::Write(const void* _buffer, size_t bytes) +{ + if (buffer_ptr + bytes > buffer + buffer_size) + if (!Reallocate(bytes + BUFFER_INCREMENTS)) + return 0; + + memcpy(buffer_ptr, _buffer, bytes); + + buffer_ptr += bytes; + buffer_used = Math::Max((size_t)(buffer_ptr - buffer), buffer_used); + + return bytes; +} + +size_t StreamMemory::Truncate(size_t bytes) +{ + if (bytes > buffer_used) + return 0; + + size_t old_size = buffer_used; + buffer_used = bytes; + buffer_ptr = buffer + bytes; + return old_size - buffer_used; +} + +bool StreamMemory::Seek(long offset, int origin) const +{ + byte* new_ptr = nullptr; + + switch (origin) + { + case SEEK_SET: new_ptr = buffer + offset; break; + case SEEK_END: new_ptr = buffer + (buffer_used - offset); break; + case SEEK_CUR: new_ptr = buffer_ptr + offset; + } + + // Check of overruns + if (new_ptr < buffer || new_ptr > buffer + buffer_used) + return false; + + buffer_ptr = new_ptr; + + return true; +} + +size_t StreamMemory::PushFront(const void* _buffer, size_t bytes) +{ + if (buffer_used + bytes > buffer_size) + if (!Reallocate(bytes + BUFFER_INCREMENTS)) + return 0; + + memmove(&buffer[bytes], &buffer[0], buffer_used); + memcpy(buffer, _buffer, bytes); + buffer_used += bytes; + buffer_ptr += bytes; + return bytes; +} + +size_t StreamMemory::PopFront(size_t bytes) +{ + Erase(0, bytes); + buffer_ptr -= bytes; + buffer_ptr = Math::Max(buffer_ptr, buffer); + return bytes; +} + +const byte* StreamMemory::RawStream() const +{ + return buffer; +} + +void StreamMemory::Erase(size_t offset, size_t bytes) +{ + bytes = Math::Min(bytes, buffer_used - offset); + memmove(&buffer[offset], &buffer[offset + bytes], buffer_used - offset - bytes); + buffer_used -= bytes; +} + +bool StreamMemory::IsReadReady() +{ + return !IsEOS(); +} + +bool StreamMemory::IsWriteReady() +{ + return true; +} + +void StreamMemory::SetSourceURL(const URL& url) +{ + SetStreamDetails(url, Stream::MODE_READ | (owns_buffer ? Stream::MODE_WRITE : 0)); +} + +bool StreamMemory::Reallocate(size_t size) +{ + RMLUI_ASSERT(owns_buffer); + if (!owns_buffer) + return false; + + byte* new_buffer = (byte*)realloc(buffer, buffer_size + size); + if (new_buffer == nullptr) + return false; + + buffer_ptr = new_buffer + (buffer_ptr - buffer); + + buffer = new_buffer; + buffer_size += size; + + return true; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StringUtilities.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StringUtilities.cpp new file mode 100644 index 00000000..c5156778 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StringUtilities.cpp @@ -0,0 +1,637 @@ +#include "../../Include/RmlUi/Core/StringUtilities.h" +#include "../../Include/RmlUi/Core/Log.h" +#include +#include +#include +#include +#include +#include + +namespace Rml { + +static bool IsEscapedCharacter(const String& string, size_t index) +{ + if (index == 0 || index > string.size()) + return false; + + size_t num_preceding_backslashes = 0; + for (size_t i = index; i > 0 && string[i - 1] == '\\'; --i) + num_preceding_backslashes += 1; + + return (num_preceding_backslashes % 2) == 1; +} + +static int FormatString(String& string, const char* format, va_list argument_list) +{ + constexpr size_t InternalBufferSize = 256; + char buffer[InternalBufferSize]; + char* buffer_ptr = buffer; + + size_t max_size = InternalBufferSize; + int length = 0; + + for (int i = 0; i < 2; i++) + { + va_list argument_list_copy; + va_copy(argument_list_copy, argument_list); + + length = vsnprintf(buffer_ptr, max_size, format, argument_list_copy); + + va_end(argument_list_copy); + + if (length < 0) + { + RMLUI_ERRORMSG("Error while formatting string"); + return 0; + } + + if (i > 0) + { + RMLUI_ASSERT(string.size() == (size_t)length); + break; + } + + if ((size_t)length < max_size) + { + string = buffer_ptr; + break; + } + + string.resize((size_t)length); + max_size = (size_t)length + 1; + buffer_ptr = &(*string.begin()); // C++17 Upgrade: Replace with string.data() + } + + return length; +} + +int FormatString(String& string, const char* format, ...) +{ + va_list argument_list; + va_start(argument_list, format); + int result = FormatString(string, format, argument_list); + va_end(argument_list); + return result; +} + +String CreateString(const char* format, ...) +{ + String result; + va_list argument_list; + va_start(argument_list, format); + FormatString(result, format, argument_list); + va_end(argument_list); + return result; +} + +static inline char CharToLower(char c) +{ + if (c >= 'A' && c <= 'Z') + c += char('a' - 'A'); + return c; +} + +String StringUtilities::ToLower(String string) +{ + std::transform(string.begin(), string.end(), string.begin(), &CharToLower); + return string; +} + +String StringUtilities::ToUpper(String string) +{ + std::transform(string.begin(), string.end(), string.begin(), [](char c) { + if (c >= 'a' && c <= 'z') + c -= char('a' - 'A'); + return c; + }); + return string; +} + +RMLUICORE_API String StringUtilities::EncodeRml(const String& string) +{ + String result; + result.reserve(string.size()); + for (char c : string) + { + switch (c) + { + case '<': result += "<"; break; + case '>': result += ">"; break; + case '&': result += "&"; break; + case '"': result += """; break; + default: result += c; break; + } + } + return result; +} + +String StringUtilities::DecodeRml(const String& s) +{ + String result; + result.reserve(s.size()); + for (size_t i = 0; i < s.size();) + { + if (s[i] == '&') + { + if (s[i + 1] == 'l' && s[i + 2] == 't' && s[i + 3] == ';') + { + result += "<"; + i += 4; + continue; + } + else if (s[i + 1] == 'g' && s[i + 2] == 't' && s[i + 3] == ';') + { + result += ">"; + i += 4; + continue; + } + else if (s[i + 1] == 'a' && s[i + 2] == 'm' && s[i + 3] == 'p' && s[i + 4] == ';') + { + result += "&"; + i += 5; + continue; + } + else if (s[i + 1] == 'q' && s[i + 2] == 'u' && s[i + 3] == 'o' && s[i + 4] == 't' && s[i + 5] == ';') + { + result += "\""; + i += 6; + continue; + } + else if (s[i + 1] == '#') + { + size_t start = i + 2; + if (s[i + 2] == 'x') + { + start++; + size_t j = 0; + for (; j < 8; j++) + { + const auto& c = s[start + j]; + if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) + break; + } + + if (j > 0 && s[start + j] == ';') + { + String tmp = s.substr(start, j); + const char* begin = tmp.c_str(); + char* end; + unsigned long code_point = strtoul(begin, &end, 16); + if (code_point != 0 && code_point != ULONG_MAX) + { + result += ToUTF8(static_cast(code_point)); + i = start + (end - begin) + 1; + continue; + } + } + } + else + { + size_t j = 0; + for (; j < 8; j++) + { + const auto& c = s[start + j]; + if (!(c >= '0' && c <= '9')) + break; + } + + if (j > 0 && s[start + j] == ';') + { + String tmp = s.substr(start, j); + const char* begin = tmp.c_str(); + char* end; + unsigned long code_point = strtoul(begin, &end, 10); + if (code_point != 0 && code_point != ULONG_MAX) + { + result += ToUTF8(static_cast(code_point)); + i = start + (end - begin) + 1; + continue; + } + } + } + } + } + result += s[i]; + i += 1; + } + return result; +} + +String StringUtilities::Replace(String subject, const String& search, const String& replace) +{ + size_t pos = 0; + while ((pos = subject.find(search, pos)) != String::npos) + { + subject.replace(pos, search.length(), replace); + pos += replace.length(); + } + return subject; +} + +String StringUtilities::Replace(String subject, char search, char replace) +{ + const size_t size = subject.size(); + for (size_t i = 0; i < size; i++) + { + if (subject[i] == search) + subject[i] = replace; + } + return subject; +} + +void StringUtilities::ExpandString(StringList& string_list, const String& string, const char delimiter, bool ignore_repeated_delimiters) +{ + char quote = 0; + bool last_char_delimiter = true; + const char* ptr = string.c_str(); + const char* start_ptr = nullptr; + const char* end_ptr = ptr; + + size_t num_delimiter_values = std::count(string.begin(), string.end(), delimiter); + string_list.reserve(string_list.size() + num_delimiter_values + 1); + + while (*ptr) + { + // Switch into quote mode if the last char was a delimeter ( excluding whitespace ) + // and we're not already in quote mode + if (last_char_delimiter && !quote && (*ptr == '"' || *ptr == '\'')) + { + quote = *ptr; + } + // Switch out of quote mode if we encounter a quote that hasn't been escaped + else if (*ptr == quote && *(ptr - 1) != '\\') + { + quote = 0; + } + // If we encounter a delimiter while not in quote mode, add the item to the list + else if (*ptr == delimiter && !quote) + { + if (start_ptr) + string_list.emplace_back(start_ptr, end_ptr + 1); + else if (!ignore_repeated_delimiters) + string_list.emplace_back(); + last_char_delimiter = true; + start_ptr = nullptr; + } + // Otherwise if its not white space or we're in quote mode, advance the pointers + else if (!IsWhitespace(*ptr) || quote) + { + if (!start_ptr) + start_ptr = ptr; + end_ptr = ptr; + last_char_delimiter = false; + } + + ptr++; + } + + // If there's data pending, add it. + if (start_ptr) + string_list.emplace_back(start_ptr, end_ptr + 1); +} + +void StringUtilities::ExpandString(StringList& string_list, const String& string, const char delimiter, char quote_character, char unquote_character, + bool ignore_repeated_delimiters) +{ + int quote_mode_depth = 0; + const char* begin_ptr = string.c_str(); + const char* ptr = string.c_str(); + const char* start_ptr = nullptr; + const char* end_ptr = ptr; + + while (*ptr) + { + const size_t index = size_t(ptr - begin_ptr); + const bool escaped = IsEscapedCharacter(string, index); + + // Increment the quote depth for each quote character encountered + if (*ptr == quote_character && !escaped) + { + ++quote_mode_depth; + } + // And decrement it for every unquote character + else if (*ptr == unquote_character && !escaped) + { + --quote_mode_depth; + } + + // If we encounter a delimiter while not in quote mode, add the item to the list + if (*ptr == delimiter && quote_mode_depth == 0 && !escaped) + { + if (start_ptr) + string_list.emplace_back(start_ptr, end_ptr + 1); + else if (!ignore_repeated_delimiters) + string_list.emplace_back(); + start_ptr = nullptr; + } + // Otherwise if its not white space or we're in quote mode, advance the pointers + else if (!IsWhitespace(*ptr) || quote_mode_depth > 0) + { + if (!start_ptr) + start_ptr = ptr; + end_ptr = ptr; + } + + ptr++; + } + + // If there's data pending, add it. + if (start_ptr) + string_list.emplace_back(start_ptr, end_ptr + 1); +} + +void StringUtilities::JoinString(String& string, const StringList& string_list, const char delimiter) +{ + for (size_t i = 0; i < string_list.size(); i++) + { + string += string_list[i]; + if (delimiter != '\0' && i < string_list.size() - 1) + string += delimiter; + } +} + +String StringUtilities::StripWhitespace(const String& string) +{ + return StripWhitespace(StringView(string)); +} + +RMLUICORE_API String StringUtilities::StripWhitespace(StringView string) +{ + const char* start = string.begin(); + const char* end = string.end(); + + while (start < end && IsWhitespace(*start)) + start++; + + while (end > start && IsWhitespace(*(end - 1))) + end--; + + if (start < end) + return String(start, end); + + return String(); +} + +void StringUtilities::TrimTrailingDotZeros(String& string) +{ + size_t new_size = string.size(); + for (size_t i = string.size() - 1; i < string.size(); i--) + { + if (string[i] == '.') + { + new_size = i; + break; + } + else if (string[i] == '0') + new_size = i; + else + break; + } + + if (new_size < string.size()) + string.resize(new_size); +} + +bool StringUtilities::StartsWith(StringView string, StringView start) +{ + if (string.size() < start.size()) + return false; + + StringView substring(string.begin(), string.begin() + start.size()); + return substring == start; +} + +bool StringUtilities::EndsWith(StringView string, StringView end) +{ + if (string.size() < end.size()) + return false; + + StringView substring(string.end() - end.size(), string.end()); + return substring == end; +} + +bool StringUtilities::StringCompareCaseInsensitive(const StringView lhs, const StringView rhs) +{ + if (lhs.size() != rhs.size()) + return false; + + const char* left = lhs.begin(); + const char* right = rhs.begin(); + const char* const left_end = lhs.end(); + + for (; left != left_end; ++left, ++right) + { + if (CharToLower(*left) != CharToLower(*right)) + return false; + } + + return true; +} + +Character StringUtilities::ToCharacter(const char* p, const char* p_end) +{ + RMLUI_ASSERTMSG(p && p != p_end, "ToCharacter expects a valid, non-empty input string"); + + if ((*p & (1 << 7)) == 0) + return static_cast(*p); + + int num_bytes = 0; + int code = 0; + + if ((*p & 0b1110'0000) == 0b1100'0000) + { + num_bytes = 2; + code = (*p & 0b0001'1111); + } + else if ((*p & 0b1111'0000) == 0b1110'0000) + { + num_bytes = 3; + code = (*p & 0b0000'1111); + } + else if ((*p & 0b1111'1000) == 0b1111'0000) + { + num_bytes = 4; + code = (*p & 0b0000'0111); + } + else + { + // Invalid begin byte + return Character::Null; + } + + if (p_end - p < num_bytes) + return Character::Null; + + for (int i = 1; i < num_bytes; i++) + { + const char byte = *(p + i); + if ((byte & 0b1100'0000) != 0b1000'0000) + { + // Invalid continuation byte + return Character::Null; + } + + code = ((code << 6) | (byte & 0b0011'1111)); + } + + return static_cast(code); +} + +size_t StringUtilities::BytesUTF8(Character character) +{ + char32_t c = (char32_t)character; + + if (c < 0x80) + return 1; + else if (c < 0x800) + return 2; + else if (c < 0x10000) + return 3; + else if (c <= 0x10FFFF) + return 4; + else + // Invalid character. + return 0; +} + +String StringUtilities::ToUTF8(Character character) +{ + return ToUTF8(&character, 1); +} + +String StringUtilities::ToUTF8(const Character* characters, int num_characters) +{ + String result; + result.reserve(num_characters); + + bool invalid_character = false; + + for (int i = 0; i < num_characters; i++) + { + char32_t c = (char32_t)characters[i]; + + constexpr int l3 = 0b0000'0111; + constexpr int l4 = 0b0000'1111; + constexpr int l5 = 0b0001'1111; + constexpr int l6 = 0b0011'1111; + constexpr int h1 = 0b1000'0000; + constexpr int h2 = 0b1100'0000; + constexpr int h3 = 0b1110'0000; + constexpr int h4 = 0b1111'0000; + + if (c < 0x80) + result += (char)c; + else if (c < 0x800) + result += {char(((c >> 6) & l5) | h2), char((c & l6) | h1)}; + else if (c < 0x10000) + result += {char(((c >> 12) & l4) | h3), char(((c >> 6) & l6) | h1), char((c & l6) | h1)}; + else if (c <= 0x10FFFF) + result += {char(((c >> 18) & l3) | h4), char(((c >> 12) & l6) | h1), char(((c >> 6) & l6) | h1), char((c & l6) | h1)}; + else + invalid_character = true; + } + + if (invalid_character) + Log::Message(Log::LT_WARNING, "One or more invalid code points encountered while encoding to UTF-8."); + + return result; +} + +size_t StringUtilities::LengthUTF8(StringView string_view) +{ + const char* const p_end = string_view.end(); + + // Skip any continuation bytes at the beginning + const char* p = string_view.begin(); + + size_t num_continuation_bytes = 0; + + while (p != p_end) + { + if ((*p & 0b1100'0000) == 0b1000'0000) + ++num_continuation_bytes; + ++p; + } + + return string_view.size() - num_continuation_bytes; +} + +int StringUtilities::ConvertCharacterOffsetToByteOffset(StringView string, int character_offset) +{ + if (character_offset >= (int)string.size()) + return (int)string.size(); + + int character_count = 0; + for (auto it = StringIteratorU8(string.begin(), string.begin(), string.end()); it; ++it) + { + character_count += 1; + if (character_count > character_offset) + return (int)it.offset(); + } + return (int)string.size(); +} + +int StringUtilities::ConvertByteOffsetToCharacterOffset(StringView string, int byte_offset) +{ + int character_count = 0; + for (auto it = StringIteratorU8(string.begin(), string.begin(), string.end()); it; ++it) + { + if (it.offset() >= byte_offset) + break; + character_count += 1; + } + return character_count; +} + +StringView::StringView() +{ + const char* empty_string = ""; + p_begin = empty_string; + p_end = empty_string; +} + +StringView::StringView(const char* p_begin, const char* p_end) : p_begin(p_begin), p_end(p_end) +{ + RMLUI_ASSERT(p_end >= p_begin); +} +StringView::StringView(const String& string) : p_begin(string.data()), p_end(string.data() + string.size()) {} +StringView::StringView(const String& string, size_t offset) : p_begin(string.data() + offset), p_end(string.data() + string.size()) {} +StringView::StringView(const String& string, size_t offset, size_t count) : + p_begin(string.data() + offset), p_end(string.data() + std::min(offset + count, string.size())) +{} + +bool StringView::operator==(const StringView& other) const +{ + return size() == other.size() && strncmp(p_begin, other.p_begin, size()) == 0; +} + +StringIteratorU8::StringIteratorU8(const char* p_begin, const char* p, const char* p_end) : view(p_begin, p_end), p(p) {} +StringIteratorU8::StringIteratorU8(StringView string) : view(string), p(view.begin()) {} +StringIteratorU8::StringIteratorU8(const String& string) : view(string), p(string.data()) {} +StringIteratorU8::StringIteratorU8(const String& string, size_t offset) : view(string), p(string.data() + offset) {} +StringIteratorU8::StringIteratorU8(const String& string, size_t offset, size_t count) : view(string, 0, offset + count), p(string.data() + offset) {} +StringIteratorU8& StringIteratorU8::operator++() +{ + RMLUI_ASSERT(p < view.end()); + ++p; + SeekForward(); + return *this; +} +StringIteratorU8& StringIteratorU8::operator--() +{ + RMLUI_ASSERT(p >= view.begin()); + --p; + SeekBack(); + return *this; +} +inline void StringIteratorU8::SeekBack() +{ + p = StringUtilities::SeekBackwardUTF8(p, view.begin()); +} + +inline void StringIteratorU8::SeekForward() +{ + p = StringUtilities::SeekForwardUTF8(p, view.end()); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StyleSheet.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StyleSheet.cpp new file mode 100644 index 00000000..95249ca7 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StyleSheet.cpp @@ -0,0 +1,239 @@ +#include "../../Include/RmlUi/Core/StyleSheet.h" +#include "../../Include/RmlUi/Core/Decorator.h" +#include "../../Include/RmlUi/Core/Element.h" +#include "../../Include/RmlUi/Core/Profiling.h" +#include "../../Include/RmlUi/Core/PropertyDefinition.h" +#include "../../Include/RmlUi/Core/StyleSheetSpecification.h" +#include "ElementDefinition.h" +#include "ElementStyle.h" +#include "StyleSheetNode.h" +#include + +namespace Rml { + +StyleSheet::StyleSheet() +{ + root = MakeUnique(); + specificity_offset = 0; +} + +StyleSheet::~StyleSheet() {} + +UniquePtr StyleSheet::CombineStyleSheet(const StyleSheet& other_sheet) const +{ + RMLUI_ZoneScoped; + + UniquePtr new_sheet = UniquePtr(new StyleSheet()); + + new_sheet->root = root->DeepCopy(); + new_sheet->specificity_offset = specificity_offset; + new_sheet->keyframes = keyframes; + new_sheet->named_decorator_map = named_decorator_map; + new_sheet->spritesheet_list = spritesheet_list; + + new_sheet->MergeStyleSheet(other_sheet); + + return new_sheet; +} + +void StyleSheet::MergeStyleSheet(const StyleSheet& other_sheet) +{ + RMLUI_ZoneScoped; + + root->MergeHierarchy(other_sheet.root.get(), specificity_offset); + specificity_offset += other_sheet.specificity_offset; + + // Any matching @keyframe names are overridden as per CSS rules + keyframes.reserve(keyframes.size() + other_sheet.keyframes.size()); + for (auto& other_keyframes : other_sheet.keyframes) + { + keyframes[other_keyframes.first] = other_keyframes.second; + } + + // Copy over the decorators, and replace any matching decorator names from other_sheet + named_decorator_map.reserve(named_decorator_map.size() + other_sheet.named_decorator_map.size()); + for (auto& other_decorator : other_sheet.named_decorator_map) + { + named_decorator_map[other_decorator.first] = other_decorator.second; + } + + spritesheet_list.Reserve(spritesheet_list.NumSpriteSheets() + other_sheet.spritesheet_list.NumSpriteSheets(), + spritesheet_list.NumSprites() + other_sheet.spritesheet_list.NumSprites()); + spritesheet_list.Merge(other_sheet.spritesheet_list); +} + +void StyleSheet::BuildNodeIndex() +{ + RMLUI_ZoneScoped; + styled_node_index = {}; + root->BuildIndex(styled_node_index); +} + +const NamedDecorator* StyleSheet::GetNamedDecorator(const String& name) const +{ + auto it = named_decorator_map.find(name); + if (it != named_decorator_map.end()) + return &(it->second); + return nullptr; +} + +const Keyframes* StyleSheet::GetKeyframes(const String& name) const +{ + auto it = keyframes.find(name); + if (it != keyframes.end()) + return &(it->second); + return nullptr; +} + +const DecoratorPtrList& StyleSheet::InstanceDecorators(RenderManager& render_manager, const DecoratorDeclarationList& declaration_list, + const PropertySource* source) const +{ + RMLUI_ASSERT_NONRECURSIVE; // Since we may return a reference to the below static variable. + static DecoratorPtrList non_cached_decorator_list; + + // Empty declaration values are used for interpolated values which we don't want to cache. + const bool enable_cache = !declaration_list.value.empty(); + + // Generate the cache key. Relative paths of textures may be affected by the source path, and ultimately + // which texture should be displayed. Thus, we need to include this path in the cache key. + String key; + + if (enable_cache) + { + key.reserve(declaration_list.value.size() + 1 + (source ? source->path.size() : 0)); + key = declaration_list.value; + key += ';'; + if (source) + key += source->path; + + auto it_cache = decorator_cache.find(key); + if (it_cache != decorator_cache.end()) + return it_cache->second; + } + else + { + non_cached_decorator_list.clear(); + } + + DecoratorPtrList& decorators = enable_cache ? decorator_cache[key] : non_cached_decorator_list; + decorators.reserve(declaration_list.list.size()); + + for (const DecoratorDeclaration& declaration : declaration_list.list) + { + SharedPtr decorator; + + if (declaration.instancer) + { + RMLUI_ZoneScopedN("InstanceDecorator"); + decorator = declaration.instancer->InstanceDecorator(declaration.type, declaration.properties, + DecoratorInstancerInterface(render_manager, *this, source)); + + if (!decorator) + Log::Message(Log::LT_WARNING, "Decorator '%s' in '%s' could not be instanced, declared at %s:%d", declaration.type.c_str(), + declaration_list.value.c_str(), source ? source->path.c_str() : "", source ? source->line_number : -1); + } + else + { + // If we have no instancer, this means the type is the name of an @decorator rule. + auto it_map = named_decorator_map.find(declaration.type); + if (it_map != named_decorator_map.end()) + decorator = it_map->second.instancer->InstanceDecorator(it_map->second.type, it_map->second.properties, + DecoratorInstancerInterface(render_manager, *this, source)); + + if (!decorator) + Log::Message(Log::LT_WARNING, "Decorator name '%s' could not be found in any @decorator rule, declared at %s:%d", + declaration.type.c_str(), source ? source->path.c_str() : "", source ? source->line_number : -1); + } + + if (!decorator) + { + decorators.clear(); + break; + } + + decorators.push_back(std::move(decorator)); + } + + return decorators; +} + +const Sprite* StyleSheet::GetSprite(const String& name) const +{ + return spritesheet_list.GetSprite(name); +} + +SharedPtr StyleSheet::GetElementDefinition(const Element* element) const +{ + RMLUI_ASSERT_NONRECURSIVE; + + // Using static to avoid allocations. Make sure we don't call this function recursively. + static Vector applicable_nodes; + applicable_nodes.clear(); + + auto AddApplicableNodes = [element](const StyleSheetIndex::NodeIndex& node_index, const String& key) { + auto it_nodes = node_index.find(Hash()(key)); + if (it_nodes != node_index.end()) + { + const StyleSheetIndex::NodeList& nodes = it_nodes->second; + + for (const StyleSheetNode* node : nodes) + { + // We found a node that has at least one requirement matching the element. Now see if we satisfy the remaining requirements of the + // node, including all ancestor nodes. What this involves is traversing the style nodes backwards, trying to match nodes in the + // element's hierarchy to nodes in the style hierarchy. + if (node->IsApplicable(element, nullptr)) + applicable_nodes.push_back(node); + } + } + }; + + // See if there are any styles defined for this element. + const String& tag = element->GetTagName(); + const String& id = element->GetId(); + const StringList& class_names = element->GetStyle()->GetClassNameList(); + + // Text elements are never matched. + if (tag == "#text") + return nullptr; + + // First, look up the indexed requirements. + if (!id.empty()) + AddApplicableNodes(styled_node_index.ids, id); + + for (const String& name : class_names) + AddApplicableNodes(styled_node_index.classes, name); + + AddApplicableNodes(styled_node_index.tags, tag); + + // Also check all remaining nodes that don't contain any indexed requirements. + for (const StyleSheetNode* node : styled_node_index.other) + { + if (node->IsApplicable(element, nullptr)) + applicable_nodes.push_back(node); + } + + // If this element definition won't actually store any information, don't bother with it. + if (applicable_nodes.empty()) + return nullptr; + + // Sort the applicable nodes by specificity first, then by pointer value in case we have duplicate specificities. + std::sort(applicable_nodes.begin(), applicable_nodes.end(), [](const StyleSheetNode* a, const StyleSheetNode* b) { + const int a_specificity = a->GetSpecificity(); + const int b_specificity = b->GetSpecificity(); + if (a_specificity == b_specificity) + return a < b; + return a_specificity < b_specificity; + }); + + // Check if this puppy has already been cached in the node index. + SharedPtr& definition = node_cache[applicable_nodes]; + if (!definition) + { + // Otherwise, create a new definition and add it to our cache. + definition = MakeShared(applicable_nodes); + } + + return definition; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StyleSheetContainer.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StyleSheetContainer.cpp new file mode 100644 index 00000000..8185ab44 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StyleSheetContainer.cpp @@ -0,0 +1,215 @@ +#include "../../Include/RmlUi/Core/StyleSheetContainer.h" +#include "../../Include/RmlUi/Core/ComputedValues.h" +#include "../../Include/RmlUi/Core/Context.h" +#include "../../Include/RmlUi/Core/Profiling.h" +#include "../../Include/RmlUi/Core/PropertyDictionary.h" +#include "../../Include/RmlUi/Core/StyleSheet.h" +#include "../../Include/RmlUi/Core/Utilities.h" +#include "ComputeProperty.h" +#include "StyleSheetParser.h" + +namespace Rml { + +StyleSheetContainer::StyleSheetContainer() {} + +StyleSheetContainer::~StyleSheetContainer() {} + +bool StyleSheetContainer::LoadStyleSheetContainer(Stream* stream, int begin_line_number) +{ + StyleSheetParser parser; + bool result = parser.Parse(media_blocks, stream, begin_line_number); + return result; +} + +bool StyleSheetContainer::UpdateCompiledStyleSheet(const Context* context) +{ + RMLUI_ZoneScoped; + + const float dp_ratio = context->GetDensityIndependentPixelRatio(); + const Vector2i vp_dimensions_i(context->GetDimensions()); + const Vector2f vp_dimensions(vp_dimensions_i); + + Vector new_active_media_block_indices; + + const float font_size = DefaultComputedValues().font_size(); + + for (int media_block_index = 0; media_block_index < (int)media_blocks.size(); media_block_index++) + { + const MediaBlock& media_block = media_blocks[media_block_index]; + bool all_match = true; + bool expected_match_value = media_block.modifier == MediaQueryModifier::Not ? false : true; + + for (const auto& property : media_block.properties.GetProperties()) + { + const MediaQueryId id = static_cast(property.first); + Vector2i ratio; + + switch (id) + { + case MediaQueryId::Width: + if (vp_dimensions.x != ComputeLength(property.second.GetNumericValue(), font_size, font_size, dp_ratio, vp_dimensions)) + all_match = false; + break; + case MediaQueryId::MinWidth: + if (vp_dimensions.x < ComputeLength(property.second.GetNumericValue(), font_size, font_size, dp_ratio, vp_dimensions)) + all_match = false; + break; + case MediaQueryId::MaxWidth: + if (vp_dimensions.x > ComputeLength(property.second.GetNumericValue(), font_size, font_size, dp_ratio, vp_dimensions)) + all_match = false; + break; + case MediaQueryId::Height: + if (vp_dimensions.y != ComputeLength(property.second.GetNumericValue(), font_size, font_size, dp_ratio, vp_dimensions)) + all_match = false; + break; + case MediaQueryId::MinHeight: + if (vp_dimensions.y < ComputeLength(property.second.GetNumericValue(), font_size, font_size, dp_ratio, vp_dimensions)) + all_match = false; + break; + case MediaQueryId::MaxHeight: + if (vp_dimensions.y > ComputeLength(property.second.GetNumericValue(), font_size, font_size, dp_ratio, vp_dimensions)) + all_match = false; + break; + case MediaQueryId::AspectRatio: + ratio = Vector2i(property.second.Get()); + if (vp_dimensions_i.x * ratio.y != vp_dimensions_i.y * ratio.x) + all_match = false; + break; + case MediaQueryId::MinAspectRatio: + ratio = Vector2i(property.second.Get()); + if (vp_dimensions_i.x * ratio.y < vp_dimensions_i.y * ratio.x) + all_match = false; + break; + case MediaQueryId::MaxAspectRatio: + ratio = Vector2i(property.second.Get()); + if (vp_dimensions_i.x * ratio.y > vp_dimensions_i.y * ratio.x) + all_match = false; + break; + case MediaQueryId::Resolution: + if (dp_ratio != property.second.Get()) + all_match = false; + break; + case MediaQueryId::MinResolution: + if (dp_ratio < property.second.Get()) + all_match = false; + break; + case MediaQueryId::MaxResolution: + if (dp_ratio > property.second.Get()) + all_match = false; + break; + case MediaQueryId::Orientation: + // Landscape (x > y) = 0 + // Portrait (x <= y) = 1 + if ((vp_dimensions.x <= vp_dimensions.y) != property.second.Get()) + all_match = false; + break; + case MediaQueryId::Theme: + if (!context->IsThemeActive(property.second.Get())) + all_match = false; + break; + // Invalid properties + case MediaQueryId::Invalid: + case MediaQueryId::NumDefinedIds: break; + } + + if (all_match != expected_match_value) + break; + } + + if (all_match == expected_match_value) + new_active_media_block_indices.push_back(media_block_index); + } + + const bool style_sheet_changed = (new_active_media_block_indices != active_media_block_indices || !compiled_style_sheet); + + if (style_sheet_changed) + { + StyleSheet* first_sheet = nullptr; + UniquePtr new_sheet; + + for (int index : new_active_media_block_indices) + { + MediaBlock& media_block = media_blocks[index]; + if (!first_sheet) + first_sheet = media_block.stylesheet.get(); + else if (!new_sheet) + new_sheet = first_sheet->CombineStyleSheet(*media_block.stylesheet); + else + new_sheet->MergeStyleSheet(*media_block.stylesheet); + } + + if (!first_sheet) + { + new_sheet.reset(new StyleSheet); + first_sheet = new_sheet.get(); + } + + compiled_style_sheet = (new_sheet ? new_sheet.get() : first_sheet); + combined_compiled_style_sheet = std::move(new_sheet); + + compiled_style_sheet->BuildNodeIndex(); + } + + active_media_block_indices = std::move(new_active_media_block_indices); + + return style_sheet_changed; +} + +StyleSheet* StyleSheetContainer::GetCompiledStyleSheet() +{ + return compiled_style_sheet; +} + +SharedPtr StyleSheetContainer::CombineStyleSheetContainer(const StyleSheetContainer& container) const +{ + RMLUI_ZoneScoped; + + SharedPtr new_sheet = MakeShared(); + + for (const MediaBlock& media_block : media_blocks) + { + new_sheet->media_blocks.emplace_back(media_block.properties, media_block.stylesheet, media_block.modifier); + } + + new_sheet->MergeStyleSheetContainer(container); + + return new_sheet; +} + +void StyleSheetContainer::MergeStyleSheetContainer(const StyleSheetContainer& other) +{ + RMLUI_ZoneScoped; + + // Style sheet container must not be merged after it's been compiled. This will invalidate references to the compiled style sheet. + RMLUI_ASSERT(!compiled_style_sheet); + + auto it_other_begin = other.media_blocks.begin(); + +#if 0 + // If the last block here has the same media requirements as the first block in other, we can safely merge them + // while retaining correct specificity of all properties. This is essentially an optimization to avoid more + // style sheet merging later on. + if (!media_blocks.empty() && !other.media_blocks.empty()) + { + MediaBlock& block_local = media_blocks.back(); + const MediaBlock& block_other = other.media_blocks.front(); + if (block_local.properties.GetProperties() == block_other.properties.GetProperties()) + { + // Now we can safely merge the two style sheets. + block_local.stylesheet = block_local.stylesheet->CombineStyleSheet(*block_other.stylesheet); + + // And we need to skip the first media block in the 'other' style sheet, since we merged it just now. + ++it_other_begin; + } + } +#endif + + // Add all the other blocks into ours. + for (auto it = it_other_begin; it != other.media_blocks.end(); ++it) + { + const MediaBlock& block_other = *it; + media_blocks.emplace_back(block_other.properties, block_other.stylesheet, block_other.modifier); + } +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StyleSheetFactory.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StyleSheetFactory.cpp new file mode 100644 index 00000000..7e04fc28 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StyleSheetFactory.cpp @@ -0,0 +1,200 @@ +#include "StyleSheetFactory.h" +#include "../../Include/RmlUi/Core/Log.h" +#include "../../Include/RmlUi/Core/StyleSheetContainer.h" +#include "StreamFile.h" +#include "StyleSheetNode.h" +#include "StyleSheetParser.h" +#include "StyleSheetSelector.h" + +namespace Rml { + +static UniquePtr instance; + +StyleSheetFactory::StyleSheetFactory() : + selectors{ + {"nth-child", StructuralSelectorType::Nth_Child}, + {"nth-last-child", StructuralSelectorType::Nth_Last_Child}, + {"nth-of-type", StructuralSelectorType::Nth_Of_Type}, + {"nth-last-of-type", StructuralSelectorType::Nth_Last_Of_Type}, + {"first-child", StructuralSelectorType::First_Child}, + {"last-child", StructuralSelectorType::Last_Child}, + {"first-of-type", StructuralSelectorType::First_Of_Type}, + {"last-of-type", StructuralSelectorType::Last_Of_Type}, + {"only-child", StructuralSelectorType::Only_Child}, + {"only-of-type", StructuralSelectorType::Only_Of_Type}, + {"empty", StructuralSelectorType::Empty}, + {"not", StructuralSelectorType::Not}, + {"scope", StructuralSelectorType::Scope}, + } +{} + +StyleSheetFactory::~StyleSheetFactory() {} + +bool StyleSheetFactory::Initialise() +{ + RMLUI_ASSERT(instance == nullptr); + instance = UniquePtr(new StyleSheetFactory); + return true; +} + +void StyleSheetFactory::Shutdown() +{ + instance.reset(); +} + +const StyleSheetContainer* StyleSheetFactory::GetStyleSheetContainer(const String& sheet_name) +{ + // Look up the sheet definition in the cache + auto it = instance->stylesheets.find(sheet_name); + if (it != instance->stylesheets.end()) + return it->second.get(); + + // Don't currently have the sheet, attempt to load it + UniquePtr sheet = instance->LoadStyleSheetContainer(sheet_name); + if (!sheet) + return nullptr; + + const StyleSheetContainer* result = sheet.get(); + + // Add it to the cache. + instance->stylesheets[sheet_name] = std::move(sheet); + + return result; +} + +void StyleSheetFactory::ClearStyleSheetCache() +{ + instance->stylesheets.clear(); +} + +StructuralSelector StyleSheetFactory::GetSelector(const String& name) +{ + SelectorMap::const_iterator it; + const size_t parameter_start = name.find('('); + + if (parameter_start == String::npos) + it = instance->selectors.find(name); + else + it = instance->selectors.find(name.substr(0, parameter_start)); + + if (it == instance->selectors.end()) + return StructuralSelector(StructuralSelectorType::Invalid, 0, 0); + + const StructuralSelectorType selector_type = it->second; + + bool requires_parameter = false; + switch (selector_type) + { + case StructuralSelectorType::Nth_Child: + case StructuralSelectorType::Nth_Last_Child: + case StructuralSelectorType::Nth_Of_Type: + case StructuralSelectorType::Nth_Last_Of_Type: + case StructuralSelectorType::Not: requires_parameter = true; break; + default: break; + } + + const size_t parameter_end = name.rfind(')'); + const bool has_parameter = (parameter_start != String::npos && parameter_end != String::npos && parameter_start < parameter_end); + + if (requires_parameter != has_parameter) + { + Log::Message(Log::LT_WARNING, "Invalid selector ':%s' encountered, expected %s parameters", name.c_str(), + requires_parameter ? "parenthesized" : "no"); + return StructuralSelector(StructuralSelectorType::Invalid, 0, 0); + } + + // Parse the 'a' and 'b' values. + int a = 1; + int b = 0; + + if (has_parameter) + { + const String parameters = StringUtilities::StripWhitespace(StringView(name, parameter_start + 1, parameter_end - (parameter_start + 1))); + + if (selector_type == StructuralSelectorType::Not) + { + auto list = MakeShared(); + list->root = MakeUnique(); + list->leafs = StyleSheetParser::ConstructNodes(*list->root, parameters); + + int specificity = 0; + for (const StyleSheetNode* node : list->leafs) + specificity = Math::Max(specificity, node->GetSpecificity()); + + return StructuralSelector(selector_type, std::move(list), specificity); + } + + // Check for 'even' or 'odd' first. + if (parameters == "even") + { + a = 2; + b = 0; + } + else if (parameters == "odd") + { + a = 2; + b = 1; + } + else + { + // Alrighty; we've got an equation in the form of [[+/-]an][(+/-)b]. So, foist up, we split on 'n'. + const size_t n_index = parameters.find('n'); + if (n_index == String::npos) + { + // The equation is 0n + b. So a = 0, and we only have to parse b. + a = 0; + b = atoi(parameters.c_str()); + } + else + { + if (n_index == 0) + a = 1; + else + { + const String a_parameter = parameters.substr(0, n_index); + if (StringUtilities::StripWhitespace(a_parameter) == "-") + a = -1; + else + a = atoi(a_parameter.c_str()); + } + + size_t pm_index = parameters.find('+', n_index + 1); + if (pm_index != String::npos) + b = 1; + else + { + pm_index = parameters.find('-', n_index + 1); + if (pm_index != String::npos) + b = -1; + } + + if (n_index == parameters.size() - 1 || pm_index == String::npos) + b = 0; + else + b = b * atoi(parameters.data() + pm_index + 1); + } + } + } + + return StructuralSelector(selector_type, a, b); +} + +UniquePtr StyleSheetFactory::LoadStyleSheetContainer(const String& sheet) +{ + UniquePtr new_style_sheet; + + // Open stream, construct new sheet and pass the stream into the sheet + auto stream = MakeUnique(); + if (stream->Open(sheet)) + { + new_style_sheet = MakeUnique(); + if (!new_style_sheet->LoadStyleSheetContainer(stream.get())) + { + new_style_sheet.reset(); + } + } + + return new_style_sheet; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StyleSheetFactory.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StyleSheetFactory.h new file mode 100644 index 00000000..5b728671 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StyleSheetFactory.h @@ -0,0 +1,52 @@ +#pragma once + +#include "../../Include/RmlUi/Core/Types.h" + +namespace Rml { + +class StyleSheetContainer; +enum class StructuralSelectorType; +struct StructuralSelector; + +/** + Creates stylesheets on the fly as needed. The factory keeps a cache of built sheets for optimisation. + */ + +class StyleSheetFactory { +public: + ~StyleSheetFactory(); + + /// Initialise the style factory + static bool Initialise(); + /// Shutdown style manager + static void Shutdown(); + + /// Gets the named sheet, retrieving it from the cache if its already been loaded. + /// @param sheet name of sheet to load + /// @lifetime Returned pointer is valid until the next call to ClearStyleSheetCache or Shutdown, it should not be stored around. + static const StyleSheetContainer* GetStyleSheetContainer(const String& sheet); + + /// Clear the style sheet cache. + static void ClearStyleSheetCache(); + + /// Returns one of the available node selectors. + /// @param name[in] The name of the desired selector. + /// @return The selector registered with the given name, or nullptr if none exists. + static StructuralSelector GetSelector(const String& name); + +private: + StyleSheetFactory(); + + // Loads an individual style sheet + UniquePtr LoadStyleSheetContainer(const String& sheet); + + // Individual loaded stylesheets + using StyleSheets = UnorderedMap>; + StyleSheets stylesheets; + + // Custom complex selectors available for style sheets. + using SelectorMap = UnorderedMap; + SelectorMap selectors; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StyleSheetNode.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StyleSheetNode.cpp new file mode 100644 index 00000000..498608c1 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StyleSheetNode.cpp @@ -0,0 +1,392 @@ +#include "StyleSheetNode.h" +#include "../../Include/RmlUi/Core/Element.h" +#include "../../Include/RmlUi/Core/Profiling.h" +#include "../../Include/RmlUi/Core/StyleSheet.h" +#include "StyleSheetFactory.h" +#include "StyleSheetSelector.h" +#include +#include + +namespace Rml { + +static inline bool IsTextElement(const Element* element) +{ + return element->GetTagName() == "#text"; +} + +StyleSheetNode::StyleSheetNode() +{ + CalculateAndSetSpecificity(); +} + +StyleSheetNode::StyleSheetNode(StyleSheetNode* parent, const CompoundSelector& selector) : parent(parent), selector(selector) +{ + CalculateAndSetSpecificity(); +} + +StyleSheetNode::StyleSheetNode(StyleSheetNode* parent, CompoundSelector&& selector) : parent(parent), selector(std::move(selector)) +{ + CalculateAndSetSpecificity(); +} + +StyleSheetNode* StyleSheetNode::GetOrCreateChildNode(const CompoundSelector& other) +{ + // See if we match an existing child + for (const auto& child : children) + { + if (child->selector == other) + return child.get(); + } + + // We don't, so create a new child + auto child = MakeUnique(this, other); + StyleSheetNode* result = child.get(); + + children.push_back(std::move(child)); + + return result; +} + +StyleSheetNode* StyleSheetNode::GetOrCreateChildNode(CompoundSelector&& other) +{ + // See if we match an existing child + for (const auto& child : children) + { + if (child->selector == other) + return child.get(); + } + + // We don't, so create a new child + auto child = MakeUnique(this, std::move(other)); + StyleSheetNode* result = child.get(); + + children.push_back(std::move(child)); + + return result; +} + +void StyleSheetNode::MergeHierarchy(StyleSheetNode* node, int specificity_offset) +{ + RMLUI_ZoneScoped; + + // Merge the other node's properties into ours. + properties.Merge(node->properties, specificity_offset); + + for (const auto& other_child : node->children) + { + StyleSheetNode* local_node = GetOrCreateChildNode(other_child->selector); + local_node->MergeHierarchy(other_child.get(), specificity_offset); + } +} + +UniquePtr StyleSheetNode::DeepCopy(StyleSheetNode* in_parent) const +{ + RMLUI_ZoneScoped; + + auto node = MakeUnique(in_parent, selector); + + node->properties = properties; + node->children.resize(children.size()); + + for (size_t i = 0; i < children.size(); i++) + { + node->children[i] = children[i]->DeepCopy(node.get()); + } + + return node; +} + +void StyleSheetNode::BuildIndex(StyleSheetIndex& styled_node_index) const +{ + // If this has properties defined, then we insert it into the styled node index. + if (properties.GetNumProperties() > 0) + { + auto IndexInsertNode = [](StyleSheetIndex::NodeIndex& node_index, const String& key, const StyleSheetNode* node) { + StyleSheetIndex::NodeList& nodes = node_index[Hash()(key)]; + auto it = std::find(nodes.begin(), nodes.end(), node); + if (it == nodes.end()) + nodes.push_back(node); + }; + + // Add this node to the appropriate index for looking up applicable nodes later. Prioritize the most unique requirement first and the most + // general requirement last. This way we are able to rule out as many nodes as possible as quickly as possible. + if (!selector.id.empty()) + { + IndexInsertNode(styled_node_index.ids, selector.id, this); + } + else if (!selector.class_names.empty()) + { + // @performance Right now we just use the first class for simplicity. Later we may want to devise a better strategy to try to add the + // class with the most unique name. For example by adding the class from this node's list that has the fewest existing matches. + IndexInsertNode(styled_node_index.classes, selector.class_names.front(), this); + } + else if (!selector.tag.empty()) + { + IndexInsertNode(styled_node_index.tags, selector.tag, this); + } + else + { + styled_node_index.other.push_back(this); + } + } + + for (auto& child : children) + child->BuildIndex(styled_node_index); +} + +int StyleSheetNode::GetSpecificity() const +{ + return specificity; +} + +void StyleSheetNode::ImportProperties(const PropertyDictionary& _properties, int rule_specificity) +{ + properties.Import(_properties, specificity + rule_specificity); +} + +const PropertyDictionary& StyleSheetNode::GetProperties() const +{ + return properties; +} + +bool StyleSheetNode::Match(const Element* element, const Element* scope) const +{ + if (!selector.tag.empty() && selector.tag != element->GetTagName()) + return false; + + if (!selector.id.empty() && selector.id != element->GetId()) + return false; + + for (auto& name : selector.class_names) + { + if (!element->IsClassSet(name)) + return false; + } + + for (auto& name : selector.pseudo_class_names) + { + if (!element->IsPseudoClassSet(name)) + return false; + } + + if (!selector.attributes.empty() && !MatchAttributes(element)) + return false; + + if (!selector.structural_selectors.empty() && !MatchStructuralSelector(element, scope)) + return false; + + return true; +} + +bool StyleSheetNode::MatchStructuralSelector(const Element* element, const Element* scope) const +{ + for (auto& node_selector : selector.structural_selectors) + { + if (!IsSelectorApplicable(element, node_selector, scope)) + return false; + } + + return true; +} + +bool StyleSheetNode::MatchAttributes(const Element* element) const +{ + for (const AttributeSelector& attribute : selector.attributes) + { + const Variant* variant = element->GetAttribute(attribute.name); + if (!variant) + return false; + if (attribute.type == AttributeSelectorType::Always) + continue; + + String buffer; + const String* element_value_ptr = &buffer; + if (variant->GetType() == Variant::STRING) + element_value_ptr = &variant->GetReference(); + else + variant->GetInto(buffer); + + const String& element_value = *element_value_ptr; + const String& css_value = attribute.value; + + auto BeginsWith = [](const String& target, const String& prefix) { + return prefix.size() <= target.size() && std::equal(prefix.begin(), prefix.end(), target.begin()); + }; + auto EndsWith = [](const String& target, const String& suffix) { + return suffix.size() <= target.size() && std::equal(suffix.rbegin(), suffix.rend(), target.rbegin()); + }; + + switch (attribute.type) + { + case AttributeSelectorType::Always: break; + case AttributeSelectorType::Equal: + if (element_value != css_value) + return false; + break; + case AttributeSelectorType::InList: + { + bool found = false; + for (size_t index = element_value.find(css_value); index != String::npos; index = element_value.find(css_value, index + 1)) + { + const size_t index_right = index + css_value.size(); + const bool whitespace_left = (index == 0 || element_value[index - 1] == ' '); + const bool whitespace_right = (index_right == element_value.size() || element_value[index_right] == ' '); + + if (whitespace_left && whitespace_right) + { + found = true; + break; + } + } + if (!found) + return false; + } + break; + case AttributeSelectorType::BeginsWithThenHyphen: + // Begins with 'css_value' followed by a hyphen, or matches exactly. + if (!BeginsWith(element_value, css_value) || (element_value.size() != css_value.size() && element_value[css_value.size()] != '-')) + return false; + break; + case AttributeSelectorType::BeginsWith: + if (!BeginsWith(element_value, css_value)) + return false; + break; + case AttributeSelectorType::EndsWith: + if (!EndsWith(element_value, css_value)) + return false; + break; + case AttributeSelectorType::Contains: + if (element_value.find(css_value) == String::npos) + return false; + break; + } + } + return true; +} + +bool StyleSheetNode::TraverseMatch(const Element* element, const Element* scope) const +{ + RMLUI_ASSERT(parent); + if (!parent->parent) + return true; + + switch (selector.combinator) + { + case SelectorCombinator::Descendant: + case SelectorCombinator::Child: + { + // Try to match the next element parent. If it succeeds we continue on to the next node, otherwise we try an alternate path through the + // hierarchy using the next element parent. Repeat until we run out of elements. + for (element = element->GetParentNode(); element; element = element->GetParentNode()) + { + if (parent->Match(element, scope) && parent->TraverseMatch(element, scope)) + return true; + // If the node has a child combinator we must match this first ancestor. + else if (selector.combinator == SelectorCombinator::Child) + return false; + } + } + break; + case SelectorCombinator::NextSibling: + case SelectorCombinator::SubsequentSibling: + { + Element* parent_element = element->GetParentNode(); + if (!parent_element) + return false; + + const int preceding_sibling_index = [element, parent_element] { + const int num_children = parent_element->GetNumChildren(true); + for (int i = 0; i < num_children; i++) + { + if (parent_element->GetChild(i) == element) + return i - 1; + } + return -1; + }(); + + // Try to match the previous sibling. If it succeeds we continue on to the next node, otherwise we try to again with its previous sibling. + for (int i = preceding_sibling_index; i >= 0; i--) + { + element = parent_element->GetChild(i); + + // First check if our sibling is a text element and if so skip it. For the descendant/child combinator above we can omit this step since + // text elements don't have children and thus any ancestor is not a text element. + if (IsTextElement(element)) + continue; + else if (parent->Match(element, scope) && parent->TraverseMatch(element, scope)) + return true; + // If the node has a next-sibling combinator we must match this first sibling. + else if (selector.combinator == SelectorCombinator::NextSibling) + return false; + } + } + break; + } + + // We have run out of element ancestors before we matched every node. Bail out. + return false; +} + +bool StyleSheetNode::IsApplicable(const Element* element, const Element* scope) const +{ + // Determine whether the element matches the current node and its entire lineage. The entire hierarchy of the element's document will be + // considered during the match as necessary. + + // We could in principle just call Match() here and then go on with the ancestor style nodes. Instead, we test the requirements of this node in a + // particular order for performance reasons. + for (const String& name : selector.pseudo_class_names) + { + if (!element->IsPseudoClassSet(name)) + return false; + } + + if (!selector.tag.empty() && selector.tag != element->GetTagName()) + return false; + + for (const String& name : selector.class_names) + { + if (!element->IsClassSet(name)) + return false; + } + + if (!selector.id.empty() && selector.id != element->GetId()) + return false; + + if (!selector.attributes.empty() && !MatchAttributes(element)) + return false; + + // Check the structural selector requirements last as they can be quite slow. + if (!selector.structural_selectors.empty() && !MatchStructuralSelector(element, scope)) + return false; + + // Walk up through all our parent nodes, each one of them must be matched by some ancestor or sibling element. + if (parent && !TraverseMatch(element, scope)) + return false; + + return true; +} + +void StyleSheetNode::CalculateAndSetSpecificity() +{ + // First calculate the specificity of this node alone. + specificity = 0; + + if (!selector.tag.empty()) + specificity += SelectorSpecificity::Tag; + + if (!selector.id.empty()) + specificity += SelectorSpecificity::ID; + + specificity += SelectorSpecificity::Class * (int)selector.class_names.size(); + specificity += SelectorSpecificity::Attribute * (int)selector.attributes.size(); + specificity += SelectorSpecificity::PseudoClass * (int)selector.pseudo_class_names.size(); + + for (const StructuralSelector& selector : selector.structural_selectors) + specificity += selector.specificity; + + // Then add our parent's specificity onto ours. + if (parent) + specificity += parent->specificity; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StyleSheetNode.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StyleSheetNode.h new file mode 100644 index 00000000..7baee2f8 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StyleSheetNode.h @@ -0,0 +1,76 @@ +#pragma once + +#include "../../Include/RmlUi/Core/PropertyDictionary.h" +#include "../../Include/RmlUi/Core/Types.h" +#include "StyleSheetSelector.h" + +namespace Rml { + +struct StyleSheetIndex; +class StyleSheetNode; +using StyleSheetNodeList = Vector>; + +/** + A style sheet is composed of a tree of nodes. + */ + +class StyleSheetNode { +public: + StyleSheetNode(); + StyleSheetNode(StyleSheetNode* parent, const CompoundSelector& selector); + StyleSheetNode(StyleSheetNode* parent, CompoundSelector&& selector); + + /// Retrieves or creates a child node with requirements equivalent to the 'other' node. + StyleSheetNode* GetOrCreateChildNode(const CompoundSelector& other); + /// Retrieves a child node with the given requirements if they match an existing node, or else creates a new one. + StyleSheetNode* GetOrCreateChildNode(CompoundSelector&& other); + + /// Merges an entire tree hierarchy into our hierarchy. + void MergeHierarchy(StyleSheetNode* node, int specificity_offset = 0); + /// Copy this node including all descendent nodes. + UniquePtr DeepCopy(StyleSheetNode* parent = nullptr) const; + /// Builds up a style sheet's index recursively. + void BuildIndex(StyleSheetIndex& styled_node_index) const; + + /// Imports properties from a single rule definition into the node's properties and sets the appropriate specificity on them. Any existing + /// attributes sharing a key with a new attribute will be overwritten if they are of a lower specificity. + /// @param[in] properties The properties to import. + /// @param[in] rule_specificity The specificity of the importing rule. + void ImportProperties(const PropertyDictionary& properties, int rule_specificity); + /// Returns the node's default properties. + const PropertyDictionary& GetProperties() const; + + /// Returns true if this node is applicable to the given element, given its IDs, classes and heritage. + /// @note For performance reasons this call does not check whether 'element' is a text element. The caller must manually check this condition and + /// consider any text element not applicable. + bool IsApplicable(const Element* element, const Element* scope) const; + + /// Returns the specificity of this node. + int GetSpecificity() const; + +private: + void CalculateAndSetSpecificity(); + + // Match an element to the local node requirements. + inline bool Match(const Element* element, const Element* scope) const; + inline bool MatchStructuralSelector(const Element* element, const Element* scope) const; + inline bool MatchAttributes(const Element* element) const; + + // Recursively traverse the nodes up towards the root to match the element and its hierarchy. + bool TraverseMatch(const Element* element, const Element* scope) const; + + // The parent of this node; is nullptr for the root node. + StyleSheetNode* parent = nullptr; + + // Node requirements + CompoundSelector selector; + + // A measure of specificity of this node; the attribute in a node with a higher value will override those of a node with a lower value. + int specificity = 0; + + PropertyDictionary properties; + + StyleSheetNodeList children; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StyleSheetParser.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StyleSheetParser.cpp new file mode 100644 index 00000000..f4d75ed5 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StyleSheetParser.cpp @@ -0,0 +1,1315 @@ +#include "StyleSheetParser.h" +#include "../../Include/RmlUi/Core/Core.h" +#include "../../Include/RmlUi/Core/Decorator.h" +#include "../../Include/RmlUi/Core/Factory.h" +#include "../../Include/RmlUi/Core/Log.h" +#include "../../Include/RmlUi/Core/Profiling.h" +#include "../../Include/RmlUi/Core/PropertyDefinition.h" +#include "../../Include/RmlUi/Core/PropertySpecification.h" +#include "../../Include/RmlUi/Core/StreamMemory.h" +#include "../../Include/RmlUi/Core/StyleSheet.h" +#include "../../Include/RmlUi/Core/StyleSheetContainer.h" +#include "../../Include/RmlUi/Core/StyleSheetSpecification.h" +#include "ComputeProperty.h" +#include "ControlledLifetimeResource.h" +#include "StyleSheetFactory.h" +#include "StyleSheetNode.h" +#include +#include + +namespace Rml { + +static bool IsEscapedCharacter(const String& string, size_t index) +{ + if (index == 0 || index > string.size()) + return false; + + size_t num_preceding_backslashes = 0; + for (size_t i = index; i > 0 && string[i - 1] == '\\'; --i) + num_preceding_backslashes += 1; + + return (num_preceding_backslashes % 2) == 1; +} + +static String UnescapeSelectorToken(StringView token) +{ + String result; + result.reserve(token.size()); + + for (const char* p = token.begin(); p != token.end(); ++p) + { + const char c = *p; + if (c == '\\' && (p + 1) != token.end()) + { + result += *(p + 1); + ++p; + } + else + { + result += c; + } + } + + return result; +} + +static String ExtractUnescapedSelectorToken(const String& string, size_t begin, size_t end) +{ + return UnescapeSelectorToken(StringView(string, begin, end - begin)); +} + +static size_t FindAttributeSelectorEnd(const String& rule, size_t start_index) +{ + char quote = 0; + for (size_t index = start_index + 1; index < rule.size(); index++) + { + const char c = rule[index]; + if ((c == '\'' || c == '"') && !IsEscapedCharacter(rule, index)) + { + if (!quote) + quote = c; + else if (quote == c) + quote = 0; + } + else if (c == ']' && !quote && !IsEscapedCharacter(rule, index)) + { + return index; + } + } + + return String::npos; +} + +static size_t FindFirstUnescaped(const String& string, size_t begin, size_t end, StringView tokens) +{ + for (size_t index = begin; index < end; index++) + { + if (std::find(tokens.begin(), tokens.end(), string[index]) != tokens.end() && !IsEscapedCharacter(string, index)) + return index; + } + + return String::npos; +} + +static void ParseAttributeSelector(AttributeSelector& attribute, const String& rule, size_t begin, size_t end) +{ + static const StringView attribute_operators = "=~|^$*"; + + const size_t i_operator = FindFirstUnescaped(rule, begin, end, attribute_operators); + const size_t i_name_end = Math::Min(i_operator, end); + attribute.name = UnescapeSelectorToken(StringUtilities::StripWhitespace(rule.substr(begin, i_name_end - begin))); + + if (i_operator == String::npos) + return; + + const char c = rule[i_operator]; + attribute.type = AttributeSelectorType(c); + + // Move cursor past operator. Non-'=' symbols are always followed by '=' so move two characters. + size_t i_value_begin = i_operator + (c == '=' ? 1 : 2); + String value = StringUtilities::StripWhitespace(rule.substr(i_value_begin, end - i_value_begin)); + if (value.size() >= 2 && ((value.front() == '"' && value.back() == '"') || (value.front() == '\'' && value.back() == '\''))) + value = value.substr(1, value.size() - 2); + + attribute.value = UnescapeSelectorToken(value); +} + +class AbstractPropertyParser : NonCopyMoveable { +protected: + ~AbstractPropertyParser() = default; + +public: + virtual bool Parse(const String& name, const String& value) = 0; +}; + +/* + * PropertySpecificationParser just passes the parsing to a property specification. Usually + * the main stylesheet specification, except for e.g. @decorator blocks. + */ +class PropertySpecificationParser final : public AbstractPropertyParser { +private: + // The dictionary to store the properties in. + PropertyDictionary& properties; + + // The specification used to parse the values. Normally the default stylesheet specification, but not for e.g. all at-rules such as decorators. + const PropertySpecification& specification; + +public: + PropertySpecificationParser(PropertyDictionary& properties, const PropertySpecification& specification) : + properties(properties), specification(specification) + {} + + bool Parse(const String& name, const String& value) override { return specification.ParsePropertyDeclaration(properties, name, value); } +}; + +/* + * Spritesheets need a special parser because its property names are arbitrary keys, + * while its values are always rectangles. Thus, it must be parsed with a special "rectangle" parser + * for every name-value pair. We can probably optimize this for @performance. + */ +class SpritesheetPropertyParser final : public AbstractPropertyParser { +private: + String image_source; + float image_resolution_factor = 1.f; + SpriteDefinitionList sprite_definitions; + + PropertyDictionary properties; + PropertySpecification specification; + PropertyId id_src, id_rx, id_ry, id_rw, id_rh, id_resolution; + ShorthandId id_rectangle; + +public: + SpritesheetPropertyParser() : specification(6, 1) + { + id_src = specification.RegisterProperty("src", "", false, false).AddParser("string").GetId(); + id_rx = specification.RegisterProperty("rectangle-x", "", false, false).AddParser("length").GetId(); + id_ry = specification.RegisterProperty("rectangle-y", "", false, false).AddParser("length").GetId(); + id_rw = specification.RegisterProperty("rectangle-w", "", false, false).AddParser("length").GetId(); + id_rh = specification.RegisterProperty("rectangle-h", "", false, false).AddParser("length").GetId(); + id_rectangle = specification.RegisterShorthand("rectangle", "rectangle-x, rectangle-y, rectangle-w, rectangle-h", ShorthandType::FallThrough); + id_resolution = specification.RegisterProperty("resolution", "", false, false).AddParser("resolution").GetId(); + } + + const String& GetImageSource() const { return image_source; } + const SpriteDefinitionList& GetSpriteDefinitions() const { return sprite_definitions; } + float GetImageResolutionFactor() const { return image_resolution_factor; } + + void Clear() + { + image_resolution_factor = 1.f; + image_source.clear(); + sprite_definitions.clear(); + } + + bool Parse(const String& name, const String& value) override + { + if (name == "src") + { + if (!specification.ParsePropertyDeclaration(properties, id_src, value)) + return false; + + if (const Property* property = properties.GetProperty(id_src)) + { + if (property->unit == Unit::STRING) + image_source = property->Get(); + } + } + else if (name == "resolution") + { + if (!specification.ParsePropertyDeclaration(properties, id_resolution, value)) + return false; + + if (const Property* property = properties.GetProperty(id_resolution)) + { + if (property->unit == Unit::X) + image_resolution_factor = property->Get(); + } + } + else + { + if (!specification.ParseShorthandDeclaration(properties, id_rectangle, value)) + return false; + + Vector2f position, size; + if (auto p = properties.GetProperty(id_rx)) + position.x = p->Get(); + if (auto p = properties.GetProperty(id_ry)) + position.y = p->Get(); + if (auto p = properties.GetProperty(id_rw)) + size.x = p->Get(); + if (auto p = properties.GetProperty(id_rh)) + size.y = p->Get(); + + sprite_definitions.emplace_back(name, Rectanglef::FromPositionSize(position, size)); + } + + return true; + } +}; + +/* + * Media queries need a special parser because they have unique properties that + * aren't admissible in other property declaration contexts. + */ +class MediaQueryPropertyParser final : public AbstractPropertyParser { +private: + // The dictionary to store the properties in. + PropertyDictionary* properties = nullptr; + PropertySpecification specification; + + static PropertyId CastId(MediaQueryId id) { return static_cast(id); } + +public: + MediaQueryPropertyParser() : specification(14, 0) + { + specification.RegisterProperty("width", "", false, false, CastId(MediaQueryId::Width)).AddParser("length"); + specification.RegisterProperty("min-width", "", false, false, CastId(MediaQueryId::MinWidth)).AddParser("length"); + specification.RegisterProperty("max-width", "", false, false, CastId(MediaQueryId::MaxWidth)).AddParser("length"); + + specification.RegisterProperty("height", "", false, false, CastId(MediaQueryId::Height)).AddParser("length"); + specification.RegisterProperty("min-height", "", false, false, CastId(MediaQueryId::MinHeight)).AddParser("length"); + specification.RegisterProperty("max-height", "", false, false, CastId(MediaQueryId::MaxHeight)).AddParser("length"); + + specification.RegisterProperty("aspect-ratio", "", false, false, CastId(MediaQueryId::AspectRatio)).AddParser("ratio"); + specification.RegisterProperty("min-aspect-ratio", "", false, false, CastId(MediaQueryId::MinAspectRatio)).AddParser("ratio"); + specification.RegisterProperty("max-aspect-ratio", "", false, false, CastId(MediaQueryId::MaxAspectRatio)).AddParser("ratio"); + + specification.RegisterProperty("resolution", "", false, false, CastId(MediaQueryId::Resolution)).AddParser("resolution"); + specification.RegisterProperty("min-resolution", "", false, false, CastId(MediaQueryId::MinResolution)).AddParser("resolution"); + specification.RegisterProperty("max-resolution", "", false, false, CastId(MediaQueryId::MaxResolution)).AddParser("resolution"); + + specification.RegisterProperty("orientation", "", false, false, CastId(MediaQueryId::Orientation)) + .AddParser("keyword", "landscape, portrait"); + + specification.RegisterProperty("theme", "", false, false, CastId(MediaQueryId::Theme)).AddParser("string"); + } + + void SetTargetProperties(PropertyDictionary* _properties) { properties = _properties; } + + void Clear() { properties = nullptr; } + + bool Parse(const String& name, const String& value) override + { + RMLUI_ASSERT(properties); + return specification.ParsePropertyDeclaration(*properties, name, value); + } +}; + +/* + * Font faces need a special parser because they have unique properties that + * aren't admissible in other property declaration contexts. + */ +class FontFacePropertyParser final : public AbstractPropertyParser { +private: + // The dictionary to store the properties in. + PropertyDictionary* properties = nullptr; + PropertySpecification specification; + + static PropertyId CastId(FontFaceId id) { return static_cast(id); } + +public: + StringList sources; + + const PropertySpecification& GetPropertySpecification() { return specification; } + + FontFacePropertyParser() : specification(5, 0) + { + // standard + specification.RegisterProperty("font-family", "", false, false, CastId(FontFaceId::FontFamily)).AddParser("string"); + specification.RegisterProperty("font-weight", "all", false, false, CastId(FontFaceId::FontWeight)) + .AddParser("keyword", "all=0, normal=400, bold=700") + .AddParser("number"); + specification.RegisterProperty("font-style", "normal", false, false, CastId(FontFaceId::FontStyle)).AddParser("keyword", "normal, italic"); + // src handled by Parse + + // extended + specification.RegisterProperty("-rmlui-fallback-face", "false", false, false, CastId(FontFaceId::FallbackFace)) + .AddParser("keyword", "false, true"); + specification.RegisterProperty("-rmlui-face-index", "0", false, false, CastId(FontFaceId::FaceIndex)).AddParser("number"); + } + + void SetTargetProperties(PropertyDictionary* _properties) { properties = _properties; } + + void Clear() + { + properties = nullptr; + sources.clear(); + } + + bool Parse(const String& name, const String& value) override + { + RMLUI_ASSERT(properties); + + if (name == "src") + { + StringUtilities::ExpandString(sources, value); + return true; + } + + return specification.ParsePropertyDeclaration(*properties, name, value); + } +}; + +struct StyleSheetParserData { + // The following parsers are reasonably heavy to initialize, so we construct them during library initialization. + SpritesheetPropertyParser spritesheet; + MediaQueryPropertyParser media_query; + FontFacePropertyParser font_face; +}; + +static ControlledLifetimeResource style_sheet_property_parsers; + +StyleSheetParser::StyleSheetParser() +{ + line_number = 0; + stream = nullptr; + parse_buffer_pos = 0; +} + +StyleSheetParser::~StyleSheetParser() {} + +void StyleSheetParser::Initialise() +{ + style_sheet_property_parsers.Initialize(); +} + +void StyleSheetParser::Shutdown() +{ + style_sheet_property_parsers.Shutdown(); +} + +static bool IsValidIdentifier(const String& str) +{ + if (str.empty()) + return false; + + for (size_t i = 0; i < str.size(); i++) + { + char c = str[i]; + bool valid = ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || (c == '-') || (c == '_')); + if (!valid) + return false; + } + + return true; +} + +static void PostprocessKeyframes(KeyframesMap& keyframes_map) +{ + for (auto& keyframes_pair : keyframes_map) + { + Keyframes& keyframes = keyframes_pair.second; + auto& blocks = keyframes.blocks; + auto& property_ids = keyframes.property_ids; + + // Sort keyframes on selector value. + std::sort(blocks.begin(), blocks.end(), [](const KeyframeBlock& a, const KeyframeBlock& b) { return a.normalized_time < b.normalized_time; }); + + // Add all property names specified by any block + if (blocks.size() > 0) + property_ids.reserve(blocks.size() * blocks[0].properties.GetNumProperties()); + for (auto& block : blocks) + { + for (auto& property : block.properties.GetProperties()) + property_ids.push_back(property.first); + } + // Remove duplicate property names + std::sort(property_ids.begin(), property_ids.end()); + property_ids.erase(std::unique(property_ids.begin(), property_ids.end()), property_ids.end()); + property_ids.shrink_to_fit(); + } +} + +bool StyleSheetParser::ParseKeyframeBlock(KeyframesMap& keyframes_map, const String& identifier, const String& rules, + const PropertyDictionary& properties) +{ + if (!IsValidIdentifier(identifier)) + { + Log::Message(Log::LT_WARNING, "Invalid keyframes identifier '%s' at %s:%d", identifier.c_str(), stream_file_name.c_str(), line_number); + return false; + } + if (properties.GetNumProperties() == 0) + return true; + + StringList rule_list; + StringUtilities::ExpandString(rule_list, rules); + + Vector rule_values; + rule_values.reserve(rule_list.size()); + + for (auto rule : rule_list) + { + float value = 0.0f; + int count = 0; + rule = StringUtilities::ToLower(std::move(rule)); + if (rule == "from") + rule_values.push_back(0.0f); + else if (rule == "to") + rule_values.push_back(1.0f); + else if (sscanf(rule.c_str(), "%f%%%n", &value, &count) == 1) + if (count > 0 && value >= 0.0f && value <= 100.0f) + rule_values.push_back(0.01f * value); + } + + if (rule_values.empty()) + { + Log::Message(Log::LT_WARNING, "Invalid keyframes rule(s) '%s' at %s:%d", rules.c_str(), stream_file_name.c_str(), line_number); + return false; + } + + Keyframes& keyframes = keyframes_map[identifier]; + + for (float selector : rule_values) + { + auto it = std::find_if(keyframes.blocks.begin(), keyframes.blocks.end(), + [selector](const KeyframeBlock& keyframe_block) { return Math::Absolute(keyframe_block.normalized_time - selector) < 0.0001f; }); + if (it == keyframes.blocks.end()) + { + keyframes.blocks.emplace_back(selector); + it = (keyframes.blocks.end() - 1); + } + else + { + // In case of duplicate keyframes, we only use the latest definition as per CSS rules + it->properties = PropertyDictionary(); + } + + it->properties.Import(properties); + } + + return true; +} + +bool StyleSheetParser::ParseDecoratorBlock(const String& at_name, NamedDecoratorMap& named_decorator_map, + const SharedPtr& source) +{ + StringList name_type; + StringUtilities::ExpandString(name_type, at_name, ':'); + + if (name_type.size() != 2 || name_type[0].empty() || name_type[1].empty()) + { + Log::Message(Log::LT_WARNING, "Decorator syntax error at %s:%d. Use syntax: '@decorator name : type { ... }'.", stream_file_name.c_str(), + line_number); + return false; + } + + const String& name = name_type[0]; + String decorator_type = name_type[1]; + + auto it_find = named_decorator_map.find(name); + if (it_find != named_decorator_map.end()) + { + Log::Message(Log::LT_WARNING, "Decorator with name '%s' already declared, ignoring decorator at %s:%d.", name.c_str(), + stream_file_name.c_str(), line_number); + return false; + } + + // Get the instancer associated with the decorator type + DecoratorInstancer* decorator_instancer = Factory::GetDecoratorInstancer(decorator_type); + PropertyDictionary properties; + + if (!decorator_instancer) + { + // Type is not a declared decorator type, instead, see if it is another decorator name, then we inherit its properties. + auto it = named_decorator_map.find(decorator_type); + if (it != named_decorator_map.end()) + { + // Yes, try to retrieve the instancer from the parent type, and add its property values. + decorator_instancer = Factory::GetDecoratorInstancer(it->second.type); + properties = it->second.properties; + decorator_type = it->second.type; + } + + // If we still don't have an instancer, we cannot continue. + if (!decorator_instancer) + { + Log::Message(Log::LT_WARNING, "Invalid decorator type '%s' declared at %s:%d.", decorator_type.c_str(), stream_file_name.c_str(), + line_number); + return false; + } + } + + const PropertySpecification& property_specification = decorator_instancer->GetPropertySpecification(); + + PropertySpecificationParser parser(properties, property_specification); + ReadProperties(parser); + + // Set non-defined properties to their defaults + property_specification.SetPropertyDefaults(properties); + properties.SetSourceOfAllProperties(source); + + named_decorator_map.emplace(name, NamedDecorator{std::move(decorator_type), decorator_instancer, std::move(properties)}); + + return true; +} + +bool StyleSheetParser::ParseFontFaceBlock(const SharedPtr& source) +{ + auto& font_face_property_parser = style_sheet_property_parsers->font_face; + + PropertyDictionary properties; + font_face_property_parser.SetTargetProperties(&properties); + + ReadProperties(font_face_property_parser); + + // Set non-defined properties to their defaults + font_face_property_parser.GetPropertySpecification().SetPropertyDefaults(properties); + properties.SetSourceOfAllProperties(source); + + // check required properties + if (!properties.GetProperty(static_cast(Rml::FontFaceId::FontFamily))) + { + Log::Message(Log::LT_WARNING, "@font-face block missing font-family at %s:%d.", stream_file_name.c_str(), line_number); + return false; + } + + if (font_face_property_parser.sources.empty()) + { + Log::Message(Log::LT_WARNING, "@font-face block missing src at %s:%d.", stream_file_name.c_str(), line_number); + return false; + } + + auto get_property = [&properties](FontFaceId id) { return properties.GetProperty(static_cast(id)); }; + const String family = get_property(FontFaceId::FontFamily)->Get(); + const bool is_fallback = get_property(FontFaceId::FallbackFace)->Get(); + const int face_index = get_property(FontFaceId::FaceIndex)->Get(); + const Style::FontWeight weight = get_property(FontFaceId::FontWeight)->Get(); + const Style::FontStyle style = get_property(FontFaceId::FontStyle)->Get(); + + for (const String& src : font_face_property_parser.sources) + { + Rml::LoadFontFace(src, family, style, weight, is_fallback, face_index); + } + + return true; +} + +bool StyleSheetParser::ParseMediaFeatureMap(const String& rules, PropertyDictionary& properties, MediaQueryModifier& modifier) +{ + style_sheet_property_parsers->media_query.SetTargetProperties(&properties); + + enum ParseState { Global, Name, Value }; + ParseState state = Global; + + char character = 0; + + String name; + + String current_string; + + modifier = MediaQueryModifier::None; + + for (size_t cursor = 0; cursor < rules.length(); cursor++) + { + character = rules[cursor]; + + switch (character) + { + case ' ': + { + if (state == Global) + { + current_string = StringUtilities::StripWhitespace(StringUtilities::ToLower(std::move(current_string))); + + if (current_string == "not") + { + // we can only ever see one "not" on the entire global query. + if (modifier != MediaQueryModifier::None) + { + Log::Message(Log::LT_WARNING, "Unexpected '%s' in @media query list at %s:%d.", current_string.c_str(), + stream_file_name.c_str(), line_number); + return false; + } + + modifier = MediaQueryModifier::Not; + current_string.clear(); + } + } + + break; + } + case '(': + { + if (state != Global) + { + Log::Message(Log::LT_WARNING, "Unexpected '(' in @media query list at %s:%d.", stream_file_name.c_str(), line_number); + return false; + } + + current_string = StringUtilities::StripWhitespace(StringUtilities::ToLower(std::move(current_string))); + + // allow an empty string to pass through only if we had just parsed a modifier. + if (current_string != "and" && (properties.GetNumProperties() != 0 || !current_string.empty())) + { + Log::Message(Log::LT_WARNING, "Unexpected '%s' in @media query list at %s:%d. Expected 'and'.", current_string.c_str(), + stream_file_name.c_str(), line_number); + return false; + } + + current_string.clear(); + state = Name; + } + break; + case ')': + { + if (state != Value) + { + Log::Message(Log::LT_WARNING, "Unexpected ')' in @media query list at %s:%d.", stream_file_name.c_str(), line_number); + return false; + } + + current_string = StringUtilities::StripWhitespace(current_string); + + if (!style_sheet_property_parsers->media_query.Parse(name, current_string)) + Log::Message(Log::LT_WARNING, "Syntax error parsing media-query property declaration '%s: %s;' in %s: %d.", name.c_str(), + current_string.c_str(), stream_file_name.c_str(), line_number); + + current_string.clear(); + state = Global; + } + break; + case ':': + { + if (state != Name) + { + Log::Message(Log::LT_WARNING, "Unexpected ':' in @media query list at %s:%d.", stream_file_name.c_str(), line_number); + return false; + } + + current_string = StringUtilities::StripWhitespace(StringUtilities::ToLower(std::move(current_string))); + + if (!IsValidIdentifier(current_string)) + { + Log::Message(Log::LT_WARNING, "Malformed property name '%s' in @media query list at %s:%d.", current_string.c_str(), + stream_file_name.c_str(), line_number); + return false; + } + + name = current_string; + current_string.clear(); + + state = Value; + } + break; + default: current_string += character; + } + } + + if (properties.GetNumProperties() == 0) + { + Log::Message(Log::LT_WARNING, "Media query list parsing yielded no properties at %s:%d.", stream_file_name.c_str(), line_number); + } + + return true; +} + +bool StyleSheetParser::Parse(MediaBlockList& style_sheets, Stream* _stream, int begin_line_number) +{ + RMLUI_ZoneScoped; + + int rule_count = 0; + line_number = begin_line_number; + stream = _stream; + parse_buffer.clear(); + parse_buffer_pos = 0; + stream_file_name = StringUtilities::Replace(stream->GetSourceURL().GetURL(), '|', ':'); + + enum class State { Global, AtRuleIdentifier, KeyframeBlock, Invalid }; + State state = State::Global; + + MediaBlock current_block = {}; + + // Need to track whether currently inside a nested media block or not, since the default scope is also a media block + bool inside_media_block = false; + + // At-rules given by the following syntax in global space: @identifier name { block } + String at_rule_name; + + // Look for more styles while data is available + while (FillBuffer()) + { + String pre_token_str; + + while (char token = FindAnyToken(pre_token_str, "{@}")) + { + switch (state) + { + case State::Global: + { + if (token == '{') + { + // Initialize current block if not present + if (!current_block.stylesheet) + { + current_block = MediaBlock{PropertyDictionary{}, UniquePtr(new StyleSheet()), MediaQueryModifier::None}; + } + + const int rule_line_number = line_number; + + // Read the attributes + PropertyDictionary properties; + PropertySpecificationParser parser(properties, StyleSheetSpecification::GetPropertySpecification()); + ReadProperties(parser); + + StringList rule_name_list; + StringUtilities::ExpandString(rule_name_list, pre_token_str, ',', '(', ')'); + + // Add style nodes to the root of the tree + for (size_t i = 0; i < rule_name_list.size(); i++) + { + auto source = MakeShared(stream_file_name, rule_line_number, UnescapeSelectorToken(rule_name_list[i])); + properties.SetSourceOfAllProperties(source); + if (!ImportProperties(current_block.stylesheet->root.get(), rule_name_list[i], properties, rule_count)) + { + Log::Message(Log::LT_WARNING, "Invalid selector '%s' encountered while parsing stylesheet at %s:%d.", + rule_name_list[i].c_str(), stream_file_name.c_str(), line_number); + } + } + + rule_count++; + } + else if (token == '@') + { + state = State::AtRuleIdentifier; + } + else if (inside_media_block && token == '}') + { + // Complete current block + PostprocessKeyframes(current_block.stylesheet->keyframes); + current_block.stylesheet->specificity_offset = rule_count; + style_sheets.push_back(std::move(current_block)); + current_block = {}; + + inside_media_block = false; + break; + } + else + { + Log::Message(Log::LT_WARNING, "Invalid character '%c' found while parsing stylesheet at %s:%d. Trying to proceed.", token, + stream_file_name.c_str(), line_number); + } + } + break; + case State::AtRuleIdentifier: + { + if (token == '{') + { + // Initialize current block if not present + if (!current_block.stylesheet) + { + current_block = {PropertyDictionary{}, UniquePtr(new StyleSheet()), MediaQueryModifier::None}; + } + + const String at_rule_identifier = StringUtilities::StripWhitespace(pre_token_str.substr(0, pre_token_str.find(' '))); + at_rule_name = StringUtilities::StripWhitespace(pre_token_str.substr(at_rule_identifier.size())); + + if (at_rule_identifier == "keyframes") + { + state = State::KeyframeBlock; + } + else if (at_rule_identifier == "decorator") + { + auto source = MakeShared(stream_file_name, (int)line_number, pre_token_str); + ParseDecoratorBlock(at_rule_name, current_block.stylesheet->named_decorator_map, source); + + at_rule_name.clear(); + state = State::Global; + } + else if (at_rule_identifier == "spritesheet") + { + auto& spritesheet_property_parser = style_sheet_property_parsers->spritesheet; + ReadProperties(spritesheet_property_parser); + + const String& image_source = spritesheet_property_parser.GetImageSource(); + const SpriteDefinitionList& sprite_definitions = spritesheet_property_parser.GetSpriteDefinitions(); + const float image_resolution_factor = spritesheet_property_parser.GetImageResolutionFactor(); + + if (sprite_definitions.empty()) + { + Log::Message(Log::LT_WARNING, "Spritesheet '%s' has no sprites defined, ignored. At %s:%d", at_rule_name.c_str(), + stream_file_name.c_str(), line_number); + } + else if (image_source.empty()) + { + Log::Message(Log::LT_WARNING, "No image source (property 'src') specified for spritesheet '%s'. At %s:%d", + at_rule_name.c_str(), stream_file_name.c_str(), line_number); + } + else if (image_resolution_factor <= 0.0f || image_resolution_factor >= 100.f) + { + Log::Message(Log::LT_WARNING, + "Spritesheet resolution (property 'resolution') value must be larger than 0.0 and smaller than 100.0, given %g. In " + "spritesheet '%s'. At %s:%d", + image_resolution_factor, at_rule_name.c_str(), stream_file_name.c_str(), line_number); + } + else + { + const float display_scale = 1.0f / image_resolution_factor; + current_block.stylesheet->spritesheet_list.AddSpriteSheet(at_rule_name, image_source, stream_file_name, (int)line_number, + display_scale, sprite_definitions); + } + + spritesheet_property_parser.Clear(); + at_rule_name.clear(); + state = State::Global; + } + else if (at_rule_identifier == "media") + { + // complete the current "global" block if present and start a new block + if (current_block.stylesheet) + { + PostprocessKeyframes(current_block.stylesheet->keyframes); + current_block.stylesheet->specificity_offset = rule_count; + style_sheets.push_back(std::move(current_block)); + current_block = {}; + } + + // parse media query list into block + PropertyDictionary feature_map; + MediaQueryModifier modifier; + ParseMediaFeatureMap(at_rule_name, feature_map, modifier); + current_block = {std::move(feature_map), UniquePtr(new StyleSheet()), modifier}; + + inside_media_block = true; + state = State::Global; + } + else if (at_rule_identifier == "font-face") + { + auto source = MakeShared(stream_file_name, (int)line_number, pre_token_str); + ParseFontFaceBlock(source); + style_sheet_property_parsers->font_face.Clear(); + + at_rule_name.clear(); + state = State::Global; + } + else + { + // Invalid identifier, should ignore + at_rule_name.clear(); + state = State::Global; + Log::Message(Log::LT_WARNING, "Invalid at-rule identifier '%s' found in stylesheet at %s:%d", at_rule_identifier.c_str(), + stream_file_name.c_str(), line_number); + } + } + else + { + Log::Message(Log::LT_WARNING, "Invalid character '%c' found while parsing at-rule identifier in stylesheet at %s:%d", token, + stream_file_name.c_str(), line_number); + state = State::Invalid; + } + } + break; + case State::KeyframeBlock: + { + if (token == '{') + { + // Initialize current block if not present + if (!current_block.stylesheet) + { + current_block = {PropertyDictionary{}, UniquePtr(new StyleSheet()), MediaQueryModifier::None}; + } + + // Each keyframe in keyframes has its own block which is processed here + PropertyDictionary properties; + PropertySpecificationParser parser(properties, StyleSheetSpecification::GetPropertySpecification()); + ReadProperties(parser); + + if (!ParseKeyframeBlock(current_block.stylesheet->keyframes, at_rule_name, pre_token_str, properties)) + continue; + } + else if (token == '}') + { + at_rule_name.clear(); + state = State::Global; + } + else + { + Log::Message(Log::LT_WARNING, "Invalid character '%c' found while parsing keyframe block in stylesheet at %s:%d", token, + stream_file_name.c_str(), line_number); + state = State::Invalid; + } + } + break; + default: + RMLUI_ERROR; + state = State::Invalid; + break; + } + + if (state == State::Invalid) + break; + } + + if (state == State::Invalid) + break; + } + + // Complete last block if present + if (current_block.stylesheet) + { + PostprocessKeyframes(current_block.stylesheet->keyframes); + current_block.stylesheet->specificity_offset = rule_count; + style_sheets.push_back(std::move(current_block)); + } + + return !style_sheets.empty(); +} + +void StyleSheetParser::ParseProperties(PropertyDictionary& parsed_properties, const String& properties) +{ + RMLUI_ASSERT(!stream); + StreamMemory stream_owner((const byte*)properties.c_str(), properties.size()); + stream = &stream_owner; + PropertySpecificationParser parser(parsed_properties, StyleSheetSpecification::GetPropertySpecification()); + ReadProperties(parser); + stream = nullptr; +} + +StyleSheetNodeListRaw StyleSheetParser::ConstructNodes(StyleSheetNode& root_node, const String& selectors) +{ + const PropertyDictionary empty_properties; + + StringList selector_list; + StringUtilities::ExpandString(selector_list, selectors, ',', '(', ')'); + + StyleSheetNodeListRaw leaf_nodes; + + for (const String& selector : selector_list) + { + StyleSheetNode* leaf_node = ImportProperties(&root_node, selector, empty_properties, 0); + + if (!leaf_node) + Log::Message(Log::LT_WARNING, "Invalid selector '%s' encountered.", selector.c_str()); + else if (leaf_node != &root_node) + leaf_nodes.push_back(leaf_node); + } + + return leaf_nodes; +} + +void StyleSheetParser::ReadProperties(AbstractPropertyParser& property_parser) +{ + RMLUI_ZoneScoped; + + String name; + String value; + + enum ParseState { NAME, VALUE, QUOTE }; + ParseState state = NAME; + + char character; + char previous_character = 0; + while (ReadCharacter(character)) + { + parse_buffer_pos++; + + switch (state) + { + case NAME: + { + if (character == ';') + { + name = StringUtilities::StripWhitespace(name); + if (!name.empty()) + { + Log::Message(Log::LT_WARNING, "Found name with no value while parsing property declaration '%s' at %s:%d", name.c_str(), + stream_file_name.c_str(), line_number); + name.clear(); + } + } + else if (character == '}') + { + name = StringUtilities::StripWhitespace(name); + if (!name.empty()) + Log::Message(Log::LT_WARNING, "End of rule encountered while parsing property declaration '%s' at %s:%d", name.c_str(), + stream_file_name.c_str(), line_number); + return; + } + else if (character == ':') + { + name = StringUtilities::StripWhitespace(name); + state = VALUE; + } + else + name += character; + } + break; + + case VALUE: + { + if (character == ';') + { + value = StringUtilities::StripWhitespace(value); + + if (!property_parser.Parse(name, value)) + Log::Message(Log::LT_WARNING, "Syntax error parsing property declaration '%s: %s;' in %s: %d.", name.c_str(), value.c_str(), + stream_file_name.c_str(), line_number); + + name.clear(); + value.clear(); + state = NAME; + } + else if (character == '}') + { + break; + } + else + { + value += character; + if (character == '"') + state = QUOTE; + } + } + break; + + case QUOTE: + { + value += character; + if (character == '"' && previous_character != '\\') + state = VALUE; + } + break; + } + + if (character == '}') + break; + previous_character = character; + } + + if (state == VALUE && !name.empty() && !value.empty()) + { + value = StringUtilities::StripWhitespace(value); + + if (!property_parser.Parse(name, value)) + Log::Message(Log::LT_WARNING, "Syntax error parsing property declaration '%s: %s;' in %s: %d.", name.c_str(), value.c_str(), + stream_file_name.c_str(), line_number); + } + else if (!StringUtilities::StripWhitespace(name).empty() || !value.empty()) + { + Log::Message(Log::LT_WARNING, "Invalid property declaration '%s':'%s' at %s:%d", name.c_str(), value.c_str(), stream_file_name.c_str(), + line_number); + } +} + +StyleSheetNode* StyleSheetParser::ImportProperties(StyleSheetNode* node, const String& rule, const PropertyDictionary& properties, + int rule_specificity) +{ + StyleSheetNode* leaf_node = node; + + // Create each node going down the tree. + for (size_t index = 0; index < rule.size();) + { + CompoundSelector selector; + + // Determine the combinator connecting the previous node if any. + for (; index > 0 && index < rule.size(); index++) + { + bool reached_end_of_combinators = false; + switch (rule[index]) + { + case ' ': break; + case '>': selector.combinator = SelectorCombinator::Child; break; + case '+': selector.combinator = SelectorCombinator::NextSibling; break; + case '~': selector.combinator = SelectorCombinator::SubsequentSibling; break; + default: reached_end_of_combinators = true; break; + } + if (reached_end_of_combinators) + break; + } + + // Determine the node's requirements. + while (index < rule.size()) + { + size_t start_index = index; + size_t end_index = index + 1; + + if (rule[start_index] == '*') + start_index += 1; + + if (rule[start_index] == '[') + { + end_index = FindAttributeSelectorEnd(rule, start_index); + if (end_index == String::npos) + return nullptr; + end_index += 1; + } + else + { + int parenthesis_count = 0; + + if (rule[start_index] == ':') + { + // Parse pseudo-element ::A as pseudo-class with name :A + if (end_index < rule.size() && rule[end_index] == ':') + end_index += 1; + } + + // Read until we hit the next identifier. Don't match inside parenthesis in case of structural selectors. + for (; end_index < rule.size(); end_index++) + { + static const String identifiers = "#.:[ >+~"; + if (parenthesis_count == 0 && identifiers.find(rule[end_index]) != String::npos && !IsEscapedCharacter(rule, end_index)) + break; + + if (rule[end_index] == '(' && !IsEscapedCharacter(rule, end_index)) + parenthesis_count += 1; + else if (rule[end_index] == ')' && !IsEscapedCharacter(rule, end_index)) + parenthesis_count -= 1; + } + } + + if (end_index > start_index) + { + switch (rule[start_index]) + { + case '#': selector.id = ExtractUnescapedSelectorToken(rule, start_index + 1, end_index); break; + case '.': selector.class_names.push_back(ExtractUnescapedSelectorToken(rule, start_index + 1, end_index)); break; + case ':': + { + String pseudo_class_name = ExtractUnescapedSelectorToken(rule, start_index + 1, end_index); + StructuralSelector node_selector = StyleSheetFactory::GetSelector(pseudo_class_name); + if (node_selector.type != StructuralSelectorType::Invalid) + selector.structural_selectors.push_back(node_selector); + else + selector.pseudo_class_names.push_back(std::move(pseudo_class_name)); + } + break; + case '[': + { + const size_t i_attr_begin = start_index + 1; + const size_t i_attr_end = end_index - 1; + if (i_attr_end <= i_attr_begin) + return nullptr; + + AttributeSelector attribute; + ParseAttributeSelector(attribute, rule, i_attr_begin, i_attr_end); + + selector.attributes.push_back(std::move(attribute)); + } + break; + default: selector.tag = ExtractUnescapedSelectorToken(rule, start_index, end_index); break; + } + } + + index = end_index; + + // If we reached a combinator then we submit the current node and start fresh with a new node. + static const String combinators(" >+~"); + if (combinators.find(rule[index]) != String::npos) + break; + } + + // Sort the classes and pseudo-classes so they are consistent across equivalent declarations that shuffle the order around. + std::sort(selector.class_names.begin(), selector.class_names.end()); + std::sort(selector.attributes.begin(), selector.attributes.end()); + std::sort(selector.pseudo_class_names.begin(), selector.pseudo_class_names.end()); + std::sort(selector.structural_selectors.begin(), selector.structural_selectors.end()); + + // Add the new child node, or retrieve the existing child if we have an exact match. + leaf_node = leaf_node->GetOrCreateChildNode(std::move(selector)); + } + + // Merge the new properties with those already on the leaf node. + leaf_node->ImportProperties(properties, rule_specificity); + + return leaf_node; +} + +char StyleSheetParser::FindAnyToken(String& buffer, const char* tokens) +{ + buffer.clear(); + char character; + while (ReadCharacter(character)) + { + if (strchr(tokens, character) != nullptr) + { + parse_buffer_pos++; + return character; + } + else + { + buffer += character; + parse_buffer_pos++; + } + } + + return 0; +} + +bool StyleSheetParser::ReadCharacter(char& buffer) +{ + bool comment = false; + + // Continuously fill the buffer until either we run out of stream or we find the requested token. + do + { + while (parse_buffer_pos < parse_buffer.size()) + { + if (parse_buffer[parse_buffer_pos] == '\n') + line_number++; + else if (comment) + { + // Check for closing comment + if (parse_buffer[parse_buffer_pos] == '*') + { + parse_buffer_pos++; + if (parse_buffer_pos >= parse_buffer.size()) + { + if (!FillBuffer()) + return false; + } + + if (parse_buffer[parse_buffer_pos] == '/') + comment = false; + else + parse_buffer_pos--; + } + } + else + { + // Check for an opening comment + if (parse_buffer[parse_buffer_pos] == '/') + { + parse_buffer_pos++; + if (parse_buffer_pos >= parse_buffer.size()) + { + if (!FillBuffer()) + { + buffer = '/'; + parse_buffer = "/"; + return true; + } + } + + if (parse_buffer[parse_buffer_pos] == '*') + comment = true; + else + { + buffer = '/'; + if (parse_buffer_pos == 0) + parse_buffer.insert(parse_buffer_pos, 1, '/'); + else + parse_buffer_pos--; + return true; + } + } + + if (!comment) + { + // If we find a character, return it + buffer = parse_buffer[parse_buffer_pos]; + return true; + } + } + + parse_buffer_pos++; + } + } while (FillBuffer()); + + return false; +} + +bool StyleSheetParser::FillBuffer() +{ + if (stream->IsEOS()) + return false; + + const bool first_read = parse_buffer.empty(); + + // Read in some data (4092 instead of 4096 to avoid the buffer growing when we have to add back + // a character after a failed comment parse.) + parse_buffer.clear(); + bool read = stream->Read(parse_buffer, 4092) > 0; + parse_buffer_pos = 0; + + if (first_read && parse_buffer.substr(0, 3) == "\xEF\xBB\xBF") + { + Log::Message(Log::LT_WARNING, + "UTF-8 BOM encountered in stylesheet %s. This is not supported and can lead to subtle issues, " + "please remove the BOM from the file's text encoding.", + stream_file_name.c_str()); + } + + return read; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StyleSheetParser.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StyleSheetParser.h new file mode 100644 index 00000000..0e6c2cdb --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StyleSheetParser.h @@ -0,0 +1,105 @@ +#pragma once + +#include "../../Include/RmlUi/Core/StyleSheetTypes.h" +#include "../../Include/RmlUi/Core/Types.h" + +namespace Rml { + +class PropertyDictionary; +class Stream; +class StyleSheetNode; +class AbstractPropertyParser; +struct PropertySource; +using StyleSheetNodeListRaw = Vector; + +/** + Helper class for parsing a style sheet into its memory representation. + */ + +class StyleSheetParser { +public: + StyleSheetParser(); + ~StyleSheetParser(); + + /// Parses the given stream into the style sheet + /// @param[out] style_sheets The collection of style sheets to write into, organized into media blocks + /// @param[in] stream The stream to read + /// @param[in] begin_line_number The used line number for the first line in the stream, for reporting errors. + /// @return True on success, false on failure. + bool Parse(MediaBlockList& style_sheets, Stream* stream, int begin_line_number); + + /// Parses the given string into the property dictionary + /// @param[out] parsed_properties The properties dictionary the properties will be read into + /// @param[in] properties The properties to parse + /// @return True if the parse was successful, or false if an error occurred. + void ParseProperties(PropertyDictionary& parsed_properties, const String& properties); + + /// Converts a selector query to a tree of nodes. + /// @param[out] root_node Node to construct into. + /// @param[in] selectors The selector rules as a string value. + /// @return The list of leaf nodes in the constructed tree, which are all owned by the root node. + static StyleSheetNodeListRaw ConstructNodes(StyleSheetNode& root_node, const String& selectors); + + /// Initialises property parsers. Call after initialisation of StylesheetSpecification. + static void Initialise(); + /// Reset property parsers. + static void Shutdown(); + +private: + /// Parses properties from the parse buffer. + /// @param[in-out] property_parser An abstract parser which specifies how the properties are parsed and stored. + void ReadProperties(AbstractPropertyParser& property_parser); + + /// Import properties into the stylesheet node + /// @param[out] node Node to import into + /// @param[in] rule The rule name to parse + /// @param[in] properties The dictionary of properties + /// @param[in] rule_specificity The specific of the rule + /// @return The leaf node of the rule, or nullptr on parse failure. + static StyleSheetNode* ImportProperties(StyleSheetNode* node, const String& rule, const PropertyDictionary& properties, int rule_specificity); + + /// Attempts to parse a @keyframes block + bool ParseKeyframeBlock(KeyframesMap& keyframes_map, const String& identifier, const String& rules, const PropertyDictionary& properties); + + /// Attempts to parse a @decorator block + bool ParseDecoratorBlock(const String& at_name, NamedDecoratorMap& named_decorator_map, const SharedPtr& source); + + /// Attempts to parse a @font-face block + bool ParseFontFaceBlock(const SharedPtr& source); + + /// Attempts to parse the properties of a @media query. + /// @param[in] rules The rules to parse. + /// @param[out] properties Parsed properties representing all values to be matched. + /// @param[out] modifier Media query modifier. + bool ParseMediaFeatureMap(const String& rules, PropertyDictionary& properties, MediaQueryModifier& modifier); + + /// Attempts to find one of the given character tokens in the active stream. + /// If it's found, buffer is filled with all content up until the token, cursor advances past the token. + /// @param[out] buffer The buffer that receives the content. + /// @param[in] tokens The character tokens to find. + /// @return The found token character, or '\0' if none was found. + char FindAnyToken(String& buffer, const char* tokens); + + /// Attempts to find the next character in the active stream. + /// If it's found, buffer is filled with the character + /// @param[out] buffer The buffer that receives the character, if read. + /// @return True if a character was read, false on end of stream. + bool ReadCharacter(char& buffer); + + /// Fill the internal parse buffer + bool FillBuffer(); + + // Stream we're parsing from. + Stream* stream; + // Parser memory buffer. + String parse_buffer; + // How far we've read through the buffer. + size_t parse_buffer_pos; + + // The name of the file we're parsing. + String stream_file_name; + // Current line number we're parsing. + int line_number; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StyleSheetSelector.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StyleSheetSelector.cpp new file mode 100644 index 00000000..01663f4d --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StyleSheetSelector.cpp @@ -0,0 +1,367 @@ +#include "StyleSheetSelector.h" +#include "../../Include/RmlUi/Core/Element.h" +#include "StyleSheetNode.h" +#include + +namespace Rml { + +static inline bool IsTextElement(const Element* element) +{ + return element->GetTagName() == "#text"; +} + +// Returns true if a positive integer can be found for n in the equation an + b = count. +static bool IsNth(int a, int b, int count) +{ + int x = count; + x -= b; + if (a != 0) + x /= a; + + return (x >= 0 && x * a + b == count); +} + +bool operator==(const AttributeSelector& a, const AttributeSelector& b) +{ + return a.type == b.type && a.name == b.name && a.value == b.value; +} +bool operator<(const AttributeSelector& a, const AttributeSelector& b) +{ + return std::tie(a.type, a.name, a.value) < std::tie(b.type, b.name, b.value); +} + +bool operator==(const StructuralSelector& a, const StructuralSelector& b) +{ + // Currently sub-selectors (selector_tree) are only superficially compared. This mainly has the consequence that selectors with a sub-selector + // which are instantiated separately will never compare equal, even if they have the exact same sub-selector expression. This further results in + // such selectors not being de-duplicated. This should not lead to any functional differences but leads to potentially missed memory/performance + // optimizations. E.g. 'div a, div b' will combine the two div nodes, while ':not(div) a, :not(div) b' will not combine the two not-div nodes. + return a.type == b.type && a.a == b.a && a.b == b.b && a.selector_tree == b.selector_tree; +} +bool operator<(const StructuralSelector& a, const StructuralSelector& b) +{ + return std::tie(a.type, a.a, a.b, a.selector_tree) < std::tie(b.type, b.a, b.b, b.selector_tree); +} + +bool operator==(const CompoundSelector& a, const CompoundSelector& b) +{ + if (a.tag != b.tag) + return false; + if (a.id != b.id) + return false; + if (a.class_names != b.class_names) + return false; + if (a.pseudo_class_names != b.pseudo_class_names) + return false; + if (a.attributes != b.attributes) + return false; + if (a.structural_selectors != b.structural_selectors) + return false; + if (a.combinator != b.combinator) + return false; + return true; +} + +bool IsSelectorApplicable(const Element* element, const StructuralSelector& selector, const Element* scope) +{ + RMLUI_ASSERT(element); + + switch (selector.type) + { + case StructuralSelectorType::Nth_Child: + { + Element* parent = element->GetParentNode(); + if (!parent) + return false; + + // Start counting elements until we find this one. + int element_index = 1; + for (int i = 0; i < parent->GetNumChildren(); i++) + { + Element* child = parent->GetChild(i); + + // Skip text nodes. + if (IsTextElement(child)) + continue; + + // If we've found our element, then break; the current index is our element's index. + if (child == element) + break; + + element_index++; + } + + return IsNth(selector.a, selector.b, element_index); + } + break; + case StructuralSelectorType::Nth_Last_Child: + { + Element* parent = element->GetParentNode(); + if (!parent) + return false; + + // Start counting elements until we find this one. + int element_index = 1; + for (int i = parent->GetNumChildren() - 1; i >= 0; --i) + { + Element* child = parent->GetChild(i); + + // Skip text nodes. + if (IsTextElement(child)) + continue; + + // If we've found our element, then break; the current index is our element's index. + if (child == element) + break; + + element_index++; + } + + return IsNth(selector.a, selector.b, element_index); + } + break; + case StructuralSelectorType::Nth_Of_Type: + { + Element* parent = element->GetParentNode(); + if (!parent) + return false; + + // Start counting elements until we find this one. + int element_index = 1; + const int num_children = parent->GetNumChildren(); + for (int i = 0; i < num_children; i++) + { + Element* child = parent->GetChild(i); + + // If we've found our element, then break; the current index is our element's index. + if (child == element) + break; + + // Skip nodes that don't share our tag. + if (child->GetTagName() != element->GetTagName()) + continue; + + element_index++; + } + + return IsNth(selector.a, selector.b, element_index); + } + break; + case StructuralSelectorType::Nth_Last_Of_Type: + { + Element* parent = element->GetParentNode(); + if (!parent) + return false; + + // Start counting elements until we find this one. + int element_index = 1; + for (int i = parent->GetNumChildren() - 1; i >= 0; --i) + { + Element* child = parent->GetChild(i); + + // If we've found our element, then break; the current index is our element's index. + if (child == element) + break; + + // Skip nodes that don't share our tag. + if (child->GetTagName() != element->GetTagName()) + continue; + + element_index++; + } + + return IsNth(selector.a, selector.b, element_index); + } + break; + case StructuralSelectorType::First_Child: + { + Element* parent = element->GetParentNode(); + if (!parent) + return false; + + int child_index = 0; + while (child_index < parent->GetNumChildren()) + { + // If this child (the first non-text child) is our element, then the selector succeeds. + Element* child = parent->GetChild(child_index); + if (child == element) + return true; + + // If this child is not a text element, then the selector fails; this element is non-trivial. + if (!IsTextElement(child)) + return false; + + // Otherwise, skip over the text element to find the last non-trivial element. + child_index++; + } + + return false; + } + break; + case StructuralSelectorType::Last_Child: + { + Element* parent = element->GetParentNode(); + if (!parent) + return false; + + int child_index = parent->GetNumChildren() - 1; + while (child_index >= 0) + { + // If this child (the last non-text child) is our element, then the selector succeeds. + Element* child = parent->GetChild(child_index); + if (child == element) + return true; + + // If this child is not a text element, then the selector fails; this element is non-trivial. + if (!IsTextElement(child)) + return false; + + // Otherwise, skip over the text element to find the last non-trivial element. + child_index--; + } + + return false; + } + break; + case StructuralSelectorType::First_Of_Type: + { + Element* parent = element->GetParentNode(); + if (!parent) + return false; + + int child_index = 0; + while (child_index < parent->GetNumChildren()) + { + // If this child is our element, then it's the first one we've found with our tag; the selector succeeds. + Element* child = parent->GetChild(child_index); + if (child == element) + return true; + + // Otherwise, if this child shares our element's tag, then our element is not the first tagged child; the selector fails. + if (child->GetTagName() == element->GetTagName()) + return false; + + child_index++; + } + + return false; + } + break; + case StructuralSelectorType::Last_Of_Type: + { + Element* parent = element->GetParentNode(); + if (!parent) + return false; + + int child_index = parent->GetNumChildren() - 1; + while (child_index >= 0) + { + // If this child is our element, then it's the first one we've found with our tag; the selector succeeds. + Element* child = parent->GetChild(child_index); + if (child == element) + return true; + + // Otherwise, if this child shares our element's tag, then our element is not the first tagged child; the selector fails. + if (child->GetTagName() == element->GetTagName()) + return false; + + child_index--; + } + + return false; + } + break; + case StructuralSelectorType::Only_Child: + { + Element* parent = element->GetParentNode(); + if (!parent) + return false; + + const int num_children = parent->GetNumChildren(); + for (int i = 0; i < num_children; i++) + { + Element* child = parent->GetChild(i); + + // Skip the child if it is our element. + if (child == element) + continue; + + // Skip the child if it is trivial. + if (IsTextElement(child)) + continue; + + return false; + } + + return true; + } + break; + case StructuralSelectorType::Only_Of_Type: + { + Element* parent = element->GetParentNode(); + if (!parent) + return false; + + const int num_children = parent->GetNumChildren(); + for (int i = 0; i < num_children; i++) + { + Element* child = parent->GetChild(i); + + // Skip the child if it is our element. + if (child == element) + continue; + + // Skip the child if it does not share our tag. + if (child->GetTagName() != element->GetTagName()) + continue; + + // We've found a similarly-tagged child to our element; selector fails. + return false; + } + + return true; + } + break; + case StructuralSelectorType::Empty: + { + return element->GetNumChildren() == 0; + } + break; + case StructuralSelectorType::Not: + { + if (!selector.selector_tree) + { + RMLUI_ERROR; + return false; + } + + bool inner_selector_matches = false; + + for (const StyleSheetNode* node : selector.selector_tree->leafs) + { + if (node->IsApplicable(element, scope)) + { + inner_selector_matches = true; + break; + } + } + + return !inner_selector_matches; + } + break; + case StructuralSelectorType::Scope: + { + return scope && element == scope; + } + break; + case StructuralSelectorType::Invalid: + { + RMLUI_ERROR; + } + break; + } + + return false; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StyleSheetSelector.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StyleSheetSelector.h new file mode 100644 index 00000000..51593791 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StyleSheetSelector.h @@ -0,0 +1,129 @@ +#pragma once + +#include "../../Include/RmlUi/Core/Types.h" + +namespace Rml { + +class Element; +class StyleSheetNode; + +/** + Constants used to determine the specificity of a selector. + */ +namespace SelectorSpecificity { + enum { + Tag = 10'000, + Class = 100'000, + Attribute = Class, + PseudoClass = Class, + ID = 1'000'000, + }; +} + +/** + Combinator determines how two connected compound selectors are matched against the element hierarchy. + */ +enum class SelectorCombinator { + Descendant, // The 'E F' (whitespace) combinator: Matches if F is a descendant of E. + Child, // The 'E > F' combinator: Matches if F is a child of E. + NextSibling, // The 'E + F' combinator: Matches if F is immediately preceded by E. + SubsequentSibling, // The 'E ~ F' combinator: Matches if F is preceded by E. +}; + +/** + Attribute selector. + + Such as [unit], [unit=meter], [href^=http] + */ +enum class AttributeSelectorType { + Always, + Equal = '=', + InList = '~', + BeginsWithThenHyphen = '|', + BeginsWith = '^', + EndsWith = '$', + Contains = '*', +}; +struct AttributeSelector { + AttributeSelectorType type = AttributeSelectorType::Always; + String name; + String value; +}; +bool operator==(const AttributeSelector& a, const AttributeSelector& b); +bool operator<(const AttributeSelector& a, const AttributeSelector& b); +using AttributeSelectorList = Vector; + +/** + A tree of unstyled style sheet nodes. + */ +struct SelectorTree { + UniquePtr root; + Vector leafs; // Owned by root. +}; + +/** + Tree-structural selector. + + Such as :nth-child(2n+1), :empty(), :not(div) + */ +enum class StructuralSelectorType { + Invalid, + Nth_Child, + Nth_Last_Child, + Nth_Of_Type, + Nth_Last_Of_Type, + First_Child, + Last_Child, + First_Of_Type, + Last_Of_Type, + Only_Child, + Only_Of_Type, + Empty, + Not, + Scope, +}; +struct StructuralSelector { + StructuralSelector(StructuralSelectorType type, int a, int b) : type(type), a(a), b(b) {} + StructuralSelector(StructuralSelectorType type, SharedPtr tree, int specificity) : + type(type), specificity(specificity), selector_tree(std::move(tree)) + {} + + StructuralSelectorType type = StructuralSelectorType::Invalid; + + // For counting selectors, the following are the 'a' and 'b' variables of an + b. + int a = 0; + int b = 0; + + // Specificity is usually determined like a pseudo class, but some types override this value. + int specificity = SelectorSpecificity::PseudoClass; + + // For selectors that contain internal selectors such as :not(). + SharedPtr selector_tree; +}; +bool operator==(const StructuralSelector& a, const StructuralSelector& b); +bool operator<(const StructuralSelector& a, const StructuralSelector& b); +using StructuralSelectorList = Vector; + +/** + Compound selector contains all the basic selectors for a single node. + + Such as div#foo.bar:nth-child(2) + */ +struct CompoundSelector { + String tag; + String id; + StringList class_names; + StringList pseudo_class_names; + AttributeSelectorList attributes; + StructuralSelectorList structural_selectors; + SelectorCombinator combinator = SelectorCombinator::Descendant; // Determines how to match with our parent node. +}; +bool operator==(const CompoundSelector& a, const CompoundSelector& b); + +/// Returns true if the node the given selector is discriminating for is applicable to a given element. +/// @param element[in] The element to determine node applicability for. +/// @param selector[in] The selector to test against the element. +/// @param scope[in] The element considered as the reference point/scope (for :scope). +bool IsSelectorApplicable(const Element* element, const StructuralSelector& selector, const Element* scope); + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StyleSheetSpecification.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StyleSheetSpecification.cpp new file mode 100644 index 00000000..b7eda4a5 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/StyleSheetSpecification.cpp @@ -0,0 +1,440 @@ +#include "../../Include/RmlUi/Core/StyleSheetSpecification.h" +#include "../../Include/RmlUi/Core/PropertyDefinition.h" +#include "../../Include/RmlUi/Core/PropertyIdSet.h" +#include "IdNameMap.h" +#include "PropertyParserAnimation.h" +#include "PropertyParserBoxShadow.h" +#include "PropertyParserColorStopList.h" +#include "PropertyParserColour.h" +#include "PropertyParserDecorator.h" +#include "PropertyParserFilter.h" +#include "PropertyParserFontEffect.h" +#include "PropertyParserKeyword.h" +#include "PropertyParserNumber.h" +#include "PropertyParserRatio.h" +#include "PropertyParserString.h" +#include "PropertyParserTransform.h" +#include "PropertyShorthandDefinition.h" + +namespace Rml { + +static StyleSheetSpecification* instance = nullptr; + +struct DefaultStyleSheetParsers : NonCopyMoveable { + PropertyParserNumber number = PropertyParserNumber(Unit::NUMBER); + PropertyParserNumber length = PropertyParserNumber(Unit::LENGTH, Unit::PX); + PropertyParserNumber length_percent = PropertyParserNumber(Unit::LENGTH_PERCENT, Unit::PX); + PropertyParserNumber number_percent = PropertyParserNumber(Unit::NUMBER_PERCENT); + PropertyParserNumber number_length_percent = PropertyParserNumber(Unit::NUMBER_LENGTH_PERCENT, Unit::PX); + PropertyParserNumber angle = PropertyParserNumber(Unit::ANGLE, Unit::RAD); + PropertyParserKeyword keyword = PropertyParserKeyword(); + PropertyParserString string = PropertyParserString(); + PropertyParserAnimation animation = PropertyParserAnimation(PropertyParserAnimation::ANIMATION_PARSER); + PropertyParserAnimation transition = PropertyParserAnimation(PropertyParserAnimation::TRANSITION_PARSER); + PropertyParserColour color = PropertyParserColour(); + PropertyParserColorStopList color_stop_list = PropertyParserColorStopList(&color); + PropertyParserDecorator decorator = PropertyParserDecorator(); + PropertyParserFilter filter = PropertyParserFilter(); + PropertyParserFontEffect font_effect = PropertyParserFontEffect(); + PropertyParserTransform transform = PropertyParserTransform(); + PropertyParserRatio ratio = PropertyParserRatio(); + PropertyParserNumber resolution = PropertyParserNumber(Unit::X); + PropertyParserBoxShadow box_shadow = PropertyParserBoxShadow(&color, &length); +}; + +StyleSheetSpecification::StyleSheetSpecification() : + // Reserve space for all defined ids and some more for custom properties + properties((size_t)PropertyId::MaxNumIds, 2 * (size_t)ShorthandId::NumDefinedIds) +{ + RMLUI_ASSERT(instance == nullptr); + instance = this; + + default_parsers.reset(new DefaultStyleSheetParsers); +} + +StyleSheetSpecification::~StyleSheetSpecification() +{ + RMLUI_ASSERT(instance == this); + instance = nullptr; +} + +PropertyDefinition& StyleSheetSpecification::RegisterProperty(PropertyId id, const String& property_name, const String& default_value, bool inherited, + bool forces_layout) +{ + return properties.RegisterProperty(property_name, default_value, inherited, forces_layout, id); +} + +ShorthandId StyleSheetSpecification::RegisterShorthand(ShorthandId id, const String& shorthand_name, const String& property_names, ShorthandType type) +{ + return properties.RegisterShorthand(shorthand_name, property_names, type, id); +} + +void StyleSheetSpecification::Initialise() +{ + RMLUI_ASSERT(!instance); + + PropertyParserAnimation::Initialize(); + PropertyParserColour::Initialize(); + PropertyParserDecorator::Initialize(); + PropertyParserNumber::Initialize(); + + new StyleSheetSpecification(); + + instance->RegisterDefaultParsers(); + instance->RegisterDefaultProperties(); +} + +void StyleSheetSpecification::Shutdown() +{ + RMLUI_ASSERT(instance); + + delete instance; + + PropertyParserAnimation::Shutdown(); + PropertyParserColour::Shutdown(); + PropertyParserDecorator::Shutdown(); + PropertyParserNumber::Shutdown(); +} + +bool StyleSheetSpecification::RegisterParser(const String& parser_name, PropertyParser* parser) +{ + ParserMap::iterator iterator = instance->parsers.find(parser_name); + if (iterator != instance->parsers.end()) + { + Log::Message(Log::LT_WARNING, "Parser with name %s already exists!", parser_name.c_str()); + return false; + } + + instance->parsers[parser_name] = parser; + return true; +} + +PropertyParser* StyleSheetSpecification::GetParser(const String& parser_name) +{ + ParserMap::iterator iterator = instance->parsers.find(parser_name); + if (iterator == instance->parsers.end()) + return nullptr; + + return (*iterator).second; +} + +PropertyDefinition& StyleSheetSpecification::RegisterProperty(const String& property_name, const String& default_value, bool inherited, + bool forces_layout) +{ + RMLUI_ASSERTMSG((size_t)instance->properties.property_map->GetId(property_name) < (size_t)PropertyId::FirstCustomId, + "Custom property name matches an internal property, please make a unique name for the given property."); + return instance->RegisterProperty(PropertyId::Invalid, property_name, default_value, inherited, forces_layout); +} + +const PropertyDefinition* StyleSheetSpecification::GetProperty(const String& property_name) +{ + return instance->properties.GetProperty(property_name); +} + +const PropertyDefinition* StyleSheetSpecification::GetProperty(PropertyId id) +{ + return instance->properties.GetProperty(id); +} + +const PropertyIdSet& StyleSheetSpecification::GetRegisteredProperties() +{ + return instance->properties.GetRegisteredProperties(); +} + +const PropertyIdSet& StyleSheetSpecification::GetRegisteredInheritedProperties() +{ + return instance->properties.GetRegisteredInheritedProperties(); +} + +const PropertyIdSet& StyleSheetSpecification::GetRegisteredPropertiesForcingLayout() +{ + return instance->properties.GetRegisteredPropertiesForcingLayout(); +} + +ShorthandId StyleSheetSpecification::RegisterShorthand(const String& shorthand_name, const String& property_names, ShorthandType type) +{ + RMLUI_ASSERTMSG(instance->properties.property_map->GetId(shorthand_name) == PropertyId::Invalid, + "Custom shorthand name matches a property name, please make a unique name."); + RMLUI_ASSERTMSG((size_t)instance->properties.shorthand_map->GetId(shorthand_name) < (size_t)ShorthandId::FirstCustomId, + "Custom shorthand name matches an internal shorthand, please make a unique name for the given shorthand property."); + return instance->properties.RegisterShorthand(shorthand_name, property_names, type); +} + +const ShorthandDefinition* StyleSheetSpecification::GetShorthand(const String& shorthand_name) +{ + return instance->properties.GetShorthand(shorthand_name); +} + +const ShorthandDefinition* StyleSheetSpecification::GetShorthand(ShorthandId id) +{ + return instance->properties.GetShorthand(id); +} + +bool StyleSheetSpecification::ParsePropertyDeclaration(PropertyDictionary& dictionary, const String& property_name, const String& property_value) +{ + return instance->properties.ParsePropertyDeclaration(dictionary, property_name, property_value); +} + +PropertyId StyleSheetSpecification::GetPropertyId(const String& property_name) +{ + return instance->properties.property_map->GetId(property_name); +} + +ShorthandId StyleSheetSpecification::GetShorthandId(const String& shorthand_name) +{ + return instance->properties.shorthand_map->GetId(shorthand_name); +} + +const String& StyleSheetSpecification::GetPropertyName(PropertyId id) +{ + return instance->properties.property_map->GetName(id); +} + +const String& StyleSheetSpecification::GetShorthandName(ShorthandId id) +{ + return instance->properties.shorthand_map->GetName(id); +} + +PropertyIdSet StyleSheetSpecification::GetShorthandUnderlyingProperties(ShorthandId id) +{ + PropertyIdSet result; + const ShorthandDefinition* shorthand = instance->properties.GetShorthand(id); + if (!shorthand) + return result; + + for (auto& item : shorthand->items) + { + if (item.type == ShorthandItemType::Property) + { + result.Insert(item.property_id); + } + else if (item.type == ShorthandItemType::Shorthand) + { + // When we have a shorthand pointing to another shorthands, call us recursively. Add the union of the previous result and new properties. + result |= GetShorthandUnderlyingProperties(item.shorthand_id); + } + } + return result; +} + +const PropertySpecification& StyleSheetSpecification::GetPropertySpecification() +{ + return instance->properties; +} + +void StyleSheetSpecification::RegisterDefaultParsers() +{ + RegisterParser("number", &default_parsers->number); + RegisterParser("length", &default_parsers->length); + RegisterParser("length_percent", &default_parsers->length_percent); + RegisterParser("number_percent", &default_parsers->number_percent); + RegisterParser("number_length_percent", &default_parsers->number_length_percent); + RegisterParser("angle", &default_parsers->angle); + RegisterParser("keyword", &default_parsers->keyword); + RegisterParser("string", &default_parsers->string); + RegisterParser("animation", &default_parsers->animation); + RegisterParser("transition", &default_parsers->transition); + RegisterParser("color", &default_parsers->color); + RegisterParser("color_stop_list", &default_parsers->color_stop_list); + RegisterParser("decorator", &default_parsers->decorator); + RegisterParser("filter", &default_parsers->filter); + RegisterParser("font_effect", &default_parsers->font_effect); + RegisterParser("transform", &default_parsers->transform); + RegisterParser("ratio", &default_parsers->ratio); + RegisterParser("resolution", &default_parsers->resolution); + RegisterParser("box_shadow", &default_parsers->box_shadow); +} + +void StyleSheetSpecification::RegisterDefaultProperties() +{ + /* + Style property specifications (ala RCSS). + + Note: Whenever keywords or default values are changed, make sure its computed value is + changed correspondingly, see `ComputedValues.h`. + + When adding new properties, it may be desirable to add it to the computed values as well. + Then, make sure to resolve it as appropriate in `ElementStyle.cpp`. + + */ + + // clang-format off + RegisterProperty(PropertyId::MarginTop, "margin-top", "0px", false, true) + .AddParser("keyword", "auto") + .AddParser("length_percent").SetRelativeTarget(RelativeTarget::ContainingBlockWidth); + RegisterProperty(PropertyId::MarginRight, "margin-right", "0px", false, true) + .AddParser("keyword", "auto") + .AddParser("length_percent").SetRelativeTarget(RelativeTarget::ContainingBlockWidth); + RegisterProperty(PropertyId::MarginBottom, "margin-bottom", "0px", false, true) + .AddParser("keyword", "auto") + .AddParser("length_percent").SetRelativeTarget(RelativeTarget::ContainingBlockWidth); + RegisterProperty(PropertyId::MarginLeft, "margin-left", "0px", false, true) + .AddParser("keyword", "auto") + .AddParser("length_percent").SetRelativeTarget(RelativeTarget::ContainingBlockWidth); + RegisterShorthand(ShorthandId::Margin, "margin", "margin-top, margin-right, margin-bottom, margin-left", ShorthandType::Box); + + RegisterProperty(PropertyId::PaddingTop, "padding-top", "0px", false, true).AddParser("length_percent").SetRelativeTarget(RelativeTarget::ContainingBlockWidth); + RegisterProperty(PropertyId::PaddingRight, "padding-right", "0px", false, true).AddParser("length_percent").SetRelativeTarget(RelativeTarget::ContainingBlockWidth); + RegisterProperty(PropertyId::PaddingBottom, "padding-bottom", "0px", false, true).AddParser("length_percent").SetRelativeTarget(RelativeTarget::ContainingBlockWidth); + RegisterProperty(PropertyId::PaddingLeft, "padding-left", "0px", false, true).AddParser("length_percent").SetRelativeTarget(RelativeTarget::ContainingBlockWidth); + RegisterShorthand(ShorthandId::Padding, "padding", "padding-top, padding-right, padding-bottom, padding-left", ShorthandType::Box); + + RegisterProperty(PropertyId::BorderTopWidth, "border-top-width", "0px", false, true).AddParser("length"); + RegisterProperty(PropertyId::BorderRightWidth, "border-right-width", "0px", false, true).AddParser("length"); + RegisterProperty(PropertyId::BorderBottomWidth, "border-bottom-width", "0px", false, true).AddParser("length"); + RegisterProperty(PropertyId::BorderLeftWidth, "border-left-width", "0px", false, true).AddParser("length"); + RegisterShorthand(ShorthandId::BorderWidth, "border-width", "border-top-width, border-right-width, border-bottom-width, border-left-width", ShorthandType::Box); + + RegisterProperty(PropertyId::BorderTopColor, "border-top-color", "black", false, false).AddParser("color"); + RegisterProperty(PropertyId::BorderRightColor, "border-right-color", "black", false, false).AddParser("color"); + RegisterProperty(PropertyId::BorderBottomColor, "border-bottom-color", "black", false, false).AddParser("color"); + RegisterProperty(PropertyId::BorderLeftColor, "border-left-color", "black", false, false).AddParser("color"); + RegisterShorthand(ShorthandId::BorderColor, "border-color", "border-top-color, border-right-color, border-bottom-color, border-left-color", ShorthandType::Box); + + RegisterShorthand(ShorthandId::BorderTop, "border-top", "border-top-width, border-top-color", ShorthandType::FallThrough); + RegisterShorthand(ShorthandId::BorderRight, "border-right", "border-right-width, border-right-color", ShorthandType::FallThrough); + RegisterShorthand(ShorthandId::BorderBottom, "border-bottom", "border-bottom-width, border-bottom-color", ShorthandType::FallThrough); + RegisterShorthand(ShorthandId::BorderLeft, "border-left", "border-left-width, border-left-color", ShorthandType::FallThrough); + RegisterShorthand(ShorthandId::Border, "border", "border-top, border-right, border-bottom, border-left", ShorthandType::RecursiveRepeat); + + RegisterProperty(PropertyId::BorderTopLeftRadius, "border-top-left-radius", "0px", false, false).AddParser("length"); + RegisterProperty(PropertyId::BorderTopRightRadius, "border-top-right-radius", "0px", false, false).AddParser("length"); + RegisterProperty(PropertyId::BorderBottomRightRadius, "border-bottom-right-radius", "0px", false, false).AddParser("length"); + RegisterProperty(PropertyId::BorderBottomLeftRadius, "border-bottom-left-radius", "0px", false, false).AddParser("length"); + RegisterShorthand(ShorthandId::BorderRadius, "border-radius", "border-top-left-radius, border-top-right-radius, border-bottom-right-radius, border-bottom-left-radius", ShorthandType::Box); + + RegisterProperty(PropertyId::Display, "display", "inline", false, true) + .AddParser("keyword", "none, block, inline, inline-block, flow-root, flex, inline-flex, table, inline-table, table-row, table-row-group, table-column, table-column-group, table-cell"); + RegisterProperty(PropertyId::Position, "position", "static", false, true).AddParser("keyword", "static, relative, absolute, fixed"); + RegisterProperty(PropertyId::Top, "top", "auto", false, false).AddParser("keyword", "auto").AddParser("length_percent").SetRelativeTarget(RelativeTarget::ContainingBlockHeight); + RegisterProperty(PropertyId::Right, "right", "auto", false, false).AddParser("keyword", "auto").AddParser("length_percent").SetRelativeTarget(RelativeTarget::ContainingBlockWidth); + RegisterProperty(PropertyId::Bottom, "bottom", "auto", false, false).AddParser("keyword", "auto").AddParser("length_percent").SetRelativeTarget(RelativeTarget::ContainingBlockHeight); + RegisterProperty(PropertyId::Left, "left", "auto", false, false).AddParser("keyword", "auto").AddParser("length_percent").SetRelativeTarget(RelativeTarget::ContainingBlockWidth); + RegisterShorthand(ShorthandId::Inset, "inset", "top, right, bottom, left", ShorthandType::Box); + + RegisterProperty(PropertyId::Float, "float", "none", false, true).AddParser("keyword", "none, left, right"); + RegisterProperty(PropertyId::Clear, "clear", "none", false, true).AddParser("keyword", "none, left, right, both"); + + RegisterProperty(PropertyId::BoxSizing, "box-sizing", "content-box", false, true).AddParser("keyword", "content-box, border-box"); + + RegisterProperty(PropertyId::ZIndex, "z-index", "auto", false, false).AddParser("keyword", "auto").AddParser("number"); + + RegisterProperty(PropertyId::Width, "width", "auto", false, true).AddParser("keyword", "auto").AddParser("length_percent").SetRelativeTarget(RelativeTarget::ContainingBlockWidth); + RegisterProperty(PropertyId::MinWidth, "min-width", "0px", false, true).AddParser("length_percent").SetRelativeTarget(RelativeTarget::ContainingBlockWidth); + RegisterProperty(PropertyId::MaxWidth, "max-width", "none", false, true).AddParser("keyword", "none").AddParser("length_percent").SetRelativeTarget(RelativeTarget::ContainingBlockWidth); + + RegisterProperty(PropertyId::Height, "height", "auto", false, true).AddParser("keyword", "auto").AddParser("length_percent").SetRelativeTarget(RelativeTarget::ContainingBlockHeight); + RegisterProperty(PropertyId::MinHeight, "min-height", "0px", false, true).AddParser("length_percent").SetRelativeTarget(RelativeTarget::ContainingBlockHeight); + RegisterProperty(PropertyId::MaxHeight, "max-height", "none", false, true).AddParser("keyword", "none").AddParser("length_percent").SetRelativeTarget(RelativeTarget::ContainingBlockHeight); + + RegisterProperty(PropertyId::LineHeight, "line-height", "1.2", true, true).AddParser("number_length_percent").SetRelativeTarget(RelativeTarget::FontSize); + RegisterProperty(PropertyId::VerticalAlign, "vertical-align", "baseline", false, true) + .AddParser("keyword", "baseline, middle, sub, super, text-top, text-bottom, top, center, bottom") + .AddParser("length_percent").SetRelativeTarget(RelativeTarget::LineHeight); + + RegisterProperty(PropertyId::OverflowX, "overflow-x", "visible", false, true).AddParser("keyword", "visible, hidden, auto, scroll"); + RegisterProperty(PropertyId::OverflowY, "overflow-y", "visible", false, true).AddParser("keyword", "visible, hidden, auto, scroll"); + RegisterShorthand(ShorthandId::Overflow, "overflow", "overflow-x, overflow-y", ShorthandType::Replicate); + RegisterProperty(PropertyId::Clip, "clip", "auto", false, false).AddParser("keyword", "auto, none, always").AddParser("number"); + RegisterProperty(PropertyId::Visibility, "visibility", "visible", false, false).AddParser("keyword", "visible, hidden"); + RegisterProperty(PropertyId::TextOverflow, "text-overflow", "clip", false, false).AddParser("keyword", "clip, ellipsis").AddParser("string"); + + // Need some work on this if we are to include images. + RegisterProperty(PropertyId::BackgroundColor, "background-color", "transparent", false, false).AddParser("color"); + RegisterShorthand(ShorthandId::Background, "background", "background-color", ShorthandType::FallThrough); + + RegisterProperty(PropertyId::Color, "color", "white", true, false).AddParser("color"); + + RegisterProperty(PropertyId::CaretColor, "caret-color", "auto", true, false).AddParser("keyword", "auto").AddParser("color"); + + RegisterProperty(PropertyId::ImageColor, "image-color", "white", false, false).AddParser("color"); + RegisterProperty(PropertyId::Opacity, "opacity", "1", true, false).AddParser("number"); + + RegisterProperty(PropertyId::FontFamily, "font-family", "", true, true).AddParser("string"); + RegisterProperty(PropertyId::FontStyle, "font-style", "normal", true, true).AddParser("keyword", "normal, italic"); + RegisterProperty(PropertyId::FontWeight, "font-weight", "normal", true, true).AddParser("keyword", "normal=400, bold=700").AddParser("number"); + RegisterProperty(PropertyId::FontSize, "font-size", "12px", true, true).AddParser("length").AddParser("length_percent").SetRelativeTarget(RelativeTarget::ParentFontSize); + RegisterProperty(PropertyId::FontKerning, "font-kerning", "auto", true, true).AddParser("keyword", "auto, normal, none"); + RegisterProperty(PropertyId::LetterSpacing, "letter-spacing", "normal", true, true).AddParser("keyword", "normal").AddParser("length"); + RegisterShorthand(ShorthandId::Font, "font", "font-style, font-weight, font-size, font-family", ShorthandType::FallThrough); + + RegisterProperty(PropertyId::TextAlign, "text-align", "left", true, true).AddParser("keyword", "left, right, center, justify"); + RegisterProperty(PropertyId::TextDecoration, "text-decoration", "none", true, false).AddParser("keyword", "none, underline, overline, line-through"); + RegisterProperty(PropertyId::TextTransform, "text-transform", "none", true, true).AddParser("keyword", "none, capitalize, uppercase, lowercase"); + RegisterProperty(PropertyId::WhiteSpace, "white-space", "normal", true, true).AddParser("keyword", "normal, pre, nowrap, pre-wrap, pre-line"); + RegisterProperty(PropertyId::WordBreak, "word-break", "normal", true, true).AddParser("keyword", "normal, break-all, break-word"); + + RegisterProperty(PropertyId::RowGap, "row-gap", "0px", false, true).AddParser("length_percent").SetRelativeTarget(RelativeTarget::ContainingBlockHeight); + RegisterProperty(PropertyId::ColumnGap, "column-gap", "0px", false, true).AddParser("length_percent").SetRelativeTarget(RelativeTarget::ContainingBlockHeight); + RegisterShorthand(ShorthandId::Gap, "gap", "row-gap, column-gap", ShorthandType::Replicate); + + RegisterProperty(PropertyId::Cursor, "cursor", "", true, false).AddParser("string"); + + // Functional property specifications. + RegisterProperty(PropertyId::Drag, "drag", "none", false, false).AddParser("keyword", "none, drag, drag-drop, block, clone"); + RegisterProperty(PropertyId::TabIndex, "tab-index", "none", false, false).AddParser("keyword", "none, auto"); + RegisterProperty(PropertyId::Focus, "focus", "auto", true, false).AddParser("keyword", "none, auto"); + + RegisterProperty(PropertyId::NavUp, "nav-up", "none", false, false).AddParser("keyword", "none, auto, horizontal, vertical, tree-order").AddParser("string"); + RegisterProperty(PropertyId::NavRight, "nav-right", "none", false, false).AddParser("keyword", "none, auto, horizontal, vertical, tree-order").AddParser("string"); + RegisterProperty(PropertyId::NavDown, "nav-down", "none", false, false).AddParser("keyword", "none, auto, horizontal, vertical, tree-order").AddParser("string"); + RegisterProperty(PropertyId::NavLeft, "nav-left", "none", false, false).AddParser("keyword", "none, auto, horizontal, vertical, tree-order").AddParser("string"); + RegisterShorthand(ShorthandId::Nav, "nav", "nav-up, nav-right, nav-down, nav-left", ShorthandType::Box); + + RegisterProperty(PropertyId::ScrollbarMargin, "scrollbar-margin", "0", false, false).AddParser("length"); + RegisterProperty(PropertyId::OverscrollBehavior, "overscroll-behavior", "auto", false, false).AddParser("keyword", "auto, contain"); + RegisterProperty(PropertyId::PointerEvents, "pointer-events", "auto", true, false).AddParser("keyword", "none, auto"); + + // Perspective and Transform specifications + RegisterProperty(PropertyId::Perspective, "perspective", "none", false, false).AddParser("keyword", "none").AddParser("length"); + RegisterProperty(PropertyId::PerspectiveOriginX, "perspective-origin-x", "50%", false, false).AddParser("keyword", "left, center, right").AddParser("length_percent"); + RegisterProperty(PropertyId::PerspectiveOriginY, "perspective-origin-y", "50%", false, false).AddParser("keyword", "top, center, bottom").AddParser("length_percent"); + RegisterShorthand(ShorthandId::PerspectiveOrigin, "perspective-origin", "perspective-origin-x, perspective-origin-y", ShorthandType::FallThrough); + RegisterProperty(PropertyId::Transform, "transform", "none", false, false).AddParser("transform"); + RegisterProperty(PropertyId::TransformOriginX, "transform-origin-x", "50%", false, false).AddParser("keyword", "left, center, right").AddParser("length_percent"); + RegisterProperty(PropertyId::TransformOriginY, "transform-origin-y", "50%", false, false).AddParser("keyword", "top, center, bottom").AddParser("length_percent"); + RegisterProperty(PropertyId::TransformOriginZ, "transform-origin-z", "0", false, false).AddParser("length"); + RegisterShorthand(ShorthandId::TransformOrigin, "transform-origin", "transform-origin-x, transform-origin-y, transform-origin-z", ShorthandType::FallThrough); + + RegisterProperty(PropertyId::Transition, "transition", "none", false, false).AddParser("transition"); + RegisterProperty(PropertyId::Animation, "animation", "none", false, false).AddParser("animation"); + + // Decorators and effects + RegisterProperty(PropertyId::Decorator, "decorator", "", false, false).AddParser("decorator"); + RegisterProperty(PropertyId::MaskImage, "mask-image", "", false, false).AddParser("decorator"); + RegisterProperty(PropertyId::FontEffect, "font-effect", "", true, false).AddParser("font_effect"); + + RegisterProperty(PropertyId::Filter, "filter", "", false, false).AddParser("filter", "filter"); + RegisterProperty(PropertyId::BackdropFilter, "backdrop-filter", "", false, false).AddParser("filter"); + + RegisterProperty(PropertyId::BoxShadow, "box-shadow", "none", false, false).AddParser("box_shadow"); + + // Rare properties (not added to computed values) + RegisterProperty(PropertyId::FillImage, "fill-image", "", false, false).AddParser("string"); + + // Flexbox + RegisterProperty(PropertyId::AlignContent, "align-content", "stretch", false, true).AddParser("keyword", "flex-start, flex-end, center, space-between, space-around, space-evenly, stretch"); + RegisterProperty(PropertyId::AlignItems, "align-items", "stretch", false, true).AddParser("keyword", "flex-start, flex-end, center, baseline, stretch"); + RegisterProperty(PropertyId::AlignSelf, "align-self", "auto", false, true).AddParser("keyword", "auto, flex-start, flex-end, center, baseline, stretch"); + + RegisterProperty(PropertyId::FlexBasis, "flex-basis", "auto", false, true).AddParser("keyword", "auto").AddParser("length_percent"); + RegisterProperty(PropertyId::FlexDirection, "flex-direction", "row", false, true).AddParser("keyword", "row, row-reverse, column, column-reverse"); + + RegisterProperty(PropertyId::FlexGrow, "flex-grow", "0", false, true).AddParser("number"); + RegisterProperty(PropertyId::FlexShrink, "flex-shrink", "1", false, true).AddParser("number"); + RegisterProperty(PropertyId::FlexWrap, "flex-wrap", "nowrap", false, true).AddParser("keyword", "nowrap, wrap, wrap-reverse"); + RegisterProperty(PropertyId::JustifyContent, "justify-content", "flex-start", false, true).AddParser("keyword", "flex-start, flex-end, center, space-between, space-around, space-evenly"); + + RegisterShorthand(ShorthandId::Flex, "flex", "flex-grow, flex-shrink, flex-basis", ShorthandType::Flex); + RegisterShorthand(ShorthandId::FlexFlow, "flex-flow", "flex-direction, flex-wrap", ShorthandType::FallThrough); + + // Internationalization properties (internal) + RegisterProperty(PropertyId::RmlUi_Language, "-rmlui-language", "", true, true).AddParser("string"); + RegisterProperty(PropertyId::RmlUi_Direction, "-rmlui-direction", "auto", true, true).AddParser("keyword", "auto, ltr, rtl"); + + RMLUI_ASSERTMSG(instance->properties.shorthand_map->AssertAllInserted(ShorthandId::NumDefinedIds), "Missing specification for one or more Shorthand IDs."); + RMLUI_ASSERTMSG(instance->properties.property_map->AssertAllInserted(PropertyId::NumDefinedIds), "Missing specification for one or more Property IDs."); + // clang-format on +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/SystemInterface.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/SystemInterface.cpp new file mode 100644 index 00000000..57502580 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/SystemInterface.cpp @@ -0,0 +1,90 @@ +#include "../../Include/RmlUi/Core/SystemInterface.h" +#include "../../Include/RmlUi/Core/Log.h" +#include "../../Include/RmlUi/Core/StringUtilities.h" +#include "../../Include/RmlUi/Core/URL.h" +#include "LogDefault.h" +#include + +namespace Rml { + +static String& GlobalClipBoardText() +{ + static String clipboard_text; + return clipboard_text; +} + +SystemInterface::SystemInterface() {} + +SystemInterface::~SystemInterface() {} + +double SystemInterface::GetElapsedTime() +{ + static const auto start = std::chrono::steady_clock::now(); + const auto current = std::chrono::steady_clock::now(); + std::chrono::duration diff = current - start; + return diff.count(); +} + +bool SystemInterface::LogMessage(Log::Type type, const String& message) +{ + return LogDefault::LogMessage(type, message); +} + +void SystemInterface::SetMouseCursor(const String& /*cursor_name*/) {} + +void SystemInterface::SetClipboardText(const String& text) +{ + // The default implementation will only copy and paste within the application + GlobalClipBoardText() = text; +} + +void SystemInterface::GetClipboardText(String& text) +{ + text = GlobalClipBoardText(); +} + +int SystemInterface::TranslateString(String& translated, const String& input) +{ + translated = input; + return 0; +} + +void SystemInterface::JoinPath(String& translated_path, const String& document_path, const String& path) +{ + // If the path is absolute, strip the leading / and return it. + if (path.size() > 0 && path[0] == '/') + { + translated_path = path.substr(1); + return; + } + + // If the path is a Windows-style absolute path, return it directly. + size_t drive_pos = path.find(':'); + size_t slash_pos = Math::Min(path.find('/'), path.find('\\')); + if (drive_pos != String::npos && drive_pos < slash_pos) + { + translated_path = path; + return; + } + + using StringUtilities::Replace; + + // Strip off the referencing document name. + translated_path = document_path; + translated_path = Replace(translated_path, '\\', '/'); + size_t file_start = translated_path.rfind('/'); + if (file_start != String::npos) + translated_path.resize(file_start + 1); + else + translated_path.clear(); + + // Append the paths and send through URL to removing any '..'. + URL url(Replace(translated_path, ':', '|') + Replace(path, '\\', '/')); + translated_path = Replace(url.GetPathedFileName(), '|', ':'); +} + +void SystemInterface::ActivateKeyboard(Rml::Vector2f /*caret_position*/, float /*line_height*/) {} + +void SystemInterface::DeactivateKeyboard() {} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Template.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Template.cpp new file mode 100644 index 00000000..cbc55330 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Template.cpp @@ -0,0 +1,117 @@ +#include "Template.h" +#include "../../Include/RmlUi/Core/Element.h" +#include "../../Include/RmlUi/Core/ElementUtilities.h" +#include "../../Include/RmlUi/Core/XMLParser.h" +#include "XMLParseTools.h" +#include + +namespace Rml { + +Template::Template() {} + +Template::~Template() {} + +const String& Template::GetName() const +{ + return name; +} + +bool Template::Load(Stream* stream) +{ + // Load the entire template into memory so we can pull out + // the header and body tags + String buffer; + stream->Read(buffer, stream->Length()); + + // Pull out the header + const char* head_start = XMLParseTools::FindTag("head", buffer.c_str()); + if (!head_start) + return false; + + const char* head_end = XMLParseTools::FindTag("head", head_start, true); + if (!head_end) + return false; + // Advance to the end of the tag + head_end = strchr(head_end, '>') + 1; + + // Pull out the body + const char* body_start = XMLParseTools::FindTag("body", head_end); + if (!body_start) + return false; + + const char* body_end = XMLParseTools::FindTag("body", body_start, true); + if (!body_end) + return false; + // Advance to the end of the tag + body_end = strchr(body_end, '>') + 1; + + // Find the RML tag, skip over it and read the attributes, + // storing the ones we're interested in. + String attribute_name; + String attribute_value; + const char* ptr = XMLParseTools::FindTag("template", buffer.c_str()); + if (!ptr) + return false; + + while (XMLParseTools::ReadAttribute(++ptr, attribute_name, attribute_value)) + { + if (attribute_name == "name") + name = attribute_value; + if (attribute_name == "content") + content = attribute_value; + } + + // Create a stream around the header, parse it and store it + auto header_stream = MakeUnique((const byte*)head_start, head_end - head_start); + header_stream->SetSourceURL(stream->GetSourceURL()); + + XMLParser parser(nullptr); + parser.Parse(header_stream.get()); + + header_stream.reset(); + + header = *parser.GetDocumentHeader(); + + // Store the body in stream form + body = MakeUnique(body_end - body_start); + body->SetSourceURL(stream->GetSourceURL()); + body->PushBack(body_start, body_end - body_start); + + return true; +} + +Element* Template::ParseTemplate(Element* element) +{ + body->Seek(0, SEEK_SET); + + const int num_children_before = element->GetNumChildren(); + + XMLParser parser(element); + parser.Parse(body.get()); + + // If there's an inject attribute on the template, attempt to find the required element. + if (!content.empty()) + { + const int num_children_after = element->GetNumChildren(); + + // We look through the newly added elements (and only those), and look for the desired id. + for (int i = num_children_before; i < num_children_after; ++i) + { + Element* content_element = ElementUtilities::GetElementById(element->GetChild(i), content); + if (content_element) + { + element = content_element; + break; + } + } + } + + return element; +} + +const DocumentHeader* Template::GetHeader() +{ + return &header; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Template.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Template.h new file mode 100644 index 00000000..bcb025c6 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Template.h @@ -0,0 +1,40 @@ +#pragma once + +#include "../../Include/RmlUi/Core/StreamMemory.h" +#include "DocumentHeader.h" + +namespace Rml { + +class Element; + +/** + Contains a RML template. The Header is stored in parsed form, body in an unparsed stream. + */ + +class Template { +public: + Template(); + ~Template(); + + /// Load a template from the given stream + bool Load(Stream* stream); + + /// Get the ID of the template + const String& GetName() const; + + /// Parse the template into the given element + /// @param element Element to parse into + /// @returns The element to continue the parse from + Element* ParseTemplate(Element* element); + + /// Get the template header + const DocumentHeader* GetHeader(); + +private: + String name; + String content; + DocumentHeader header; + UniquePtr body; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TemplateCache.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TemplateCache.cpp new file mode 100644 index 00000000..cbc52144 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TemplateCache.cpp @@ -0,0 +1,96 @@ +#include "TemplateCache.h" +#include "../../Include/RmlUi/Core/Log.h" +#include "StreamFile.h" +#include "Template.h" + +namespace Rml { + +static TemplateCache* instance = nullptr; + +TemplateCache::TemplateCache() +{ + RMLUI_ASSERT(instance == nullptr); + instance = this; +} + +TemplateCache::~TemplateCache() +{ + for (Templates::iterator itr = instance->templates.begin(); itr != instance->templates.end(); ++itr) + { + delete (*itr).second; + } + + instance = nullptr; +} + +bool TemplateCache::Initialise() +{ + new TemplateCache(); + + return true; +} + +void TemplateCache::Shutdown() +{ + delete instance; +} + +Template* TemplateCache::LoadTemplate(const String& name) +{ + // Check if the template is already loaded + Templates::iterator itr = instance->templates.find(name); + if (itr != instance->templates.end()) + return (*itr).second; + + // Nope, we better load it + Template* new_template = nullptr; + auto stream = MakeUnique(); + if (stream->Open(name)) + { + new_template = new Template(); + if (!new_template->Load(stream.get())) + { + Log::Message(Log::LT_ERROR, "Failed to load template %s.", name.c_str()); + delete new_template; + new_template = nullptr; + } + else if (new_template->GetName().empty()) + { + Log::Message(Log::LT_ERROR, "Failed to load template %s, template is missing its name.", name.c_str()); + delete new_template; + new_template = nullptr; + } + else + { + instance->templates[name] = new_template; + instance->template_ids[new_template->GetName()] = new_template; + } + } + else + { + Log::Message(Log::LT_ERROR, "Failed to open template file %s.", name.c_str()); + } + + return new_template; +} + +Template* TemplateCache::GetTemplate(const String& name) +{ + // Check if the template is already loaded + Templates::iterator itr = instance->template_ids.find(name); + if (itr != instance->template_ids.end()) + return (*itr).second; + + return nullptr; +} + +void TemplateCache::Clear() +{ + for (Templates::iterator i = instance->templates.begin(); i != instance->templates.end(); ++i) + delete (*i).second; + + instance->templates.clear(); + instance->template_ids.clear(); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TemplateCache.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TemplateCache.h new file mode 100644 index 00000000..8d68071e --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TemplateCache.h @@ -0,0 +1,36 @@ +#pragma once + +#include "../../Include/RmlUi/Core/Types.h" + +namespace Rml { + +class Template; + +/** + Manages requests for loading templates, caching as it goes. + */ + +class TemplateCache { +public: + /// Initialisation and Shutdown + static bool Initialise(); + static void Shutdown(); + + /// Load the named template from the given path, if its already loaded get the cached copy + static Template* LoadTemplate(const String& path); + /// Get the template by id + static Template* GetTemplate(const String& id); + + /// Clear the template cache. + static void Clear(); + +private: + TemplateCache(); + ~TemplateCache(); + + using Templates = UnorderedMap; + Templates templates; + Templates template_ids; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Texture.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Texture.cpp new file mode 100644 index 00000000..e61e1aa1 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Texture.cpp @@ -0,0 +1,49 @@ +#include "../../Include/RmlUi/Core/Texture.h" +#include "RenderManagerAccess.h" + +namespace Rml { + +Texture::Texture(RenderManager* render_manager, TextureFileIndex file_index) : render_manager(render_manager), file_index(file_index) {} + +Texture::Texture(RenderManager* render_manager, StableVectorIndex callback_index) : render_manager(render_manager), callback_index(callback_index) {} + +Vector2i Texture::GetDimensions() const +{ + if (file_index != TextureFileIndex::Invalid) + return RenderManagerAccess::GetDimensions(render_manager, file_index); + if (callback_index != StableVectorIndex::Invalid) + return RenderManagerAccess::GetDimensions(render_manager, callback_index); + return {}; +} + +Texture::operator bool() const +{ + return callback_index != StableVectorIndex::Invalid || file_index != TextureFileIndex::Invalid; +} + +bool Texture::operator==(const Texture& other) const +{ + return render_manager == other.render_manager && file_index == other.file_index && callback_index == other.callback_index; +} + +TextureSource::TextureSource(String source, String document_path) : source(std::move(source)), document_path(std::move(document_path)) {} + +Texture TextureSource::GetTexture(RenderManager& render_manager) const +{ + Texture& texture = textures[&render_manager]; + if (!texture) + texture = render_manager.LoadTexture(source, document_path); + return texture; +} + +const String& TextureSource::GetSource() const +{ + return source; +} + +const String& TextureSource::GetDefinitionSource() const +{ + return document_path; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TextureDatabase.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TextureDatabase.cpp new file mode 100644 index 00000000..80d60040 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TextureDatabase.cpp @@ -0,0 +1,180 @@ +#include "TextureDatabase.h" +#include "../../Include/RmlUi/Core/Log.h" +#include "../../Include/RmlUi/Core/RenderInterface.h" + +namespace Rml { + +CallbackTextureDatabase::CallbackTextureDatabase() +{ + constexpr size_t reserve_callback_textures = 30; + texture_list.reserve(reserve_callback_textures); +} + +CallbackTextureDatabase::~CallbackTextureDatabase() +{ + if (!texture_list.empty()) + { + Log::Message(Log::LT_ERROR, "TextureDatabase destroyed with outstanding callback textures. Will likely result in memory corruption."); + RMLUI_ERROR; + } +} + +StableVectorIndex CallbackTextureDatabase::CreateTexture(CallbackTextureFunction&& callback) +{ + RMLUI_ASSERT(callback); + return texture_list.insert(CallbackTextureEntry{std::move(callback), TextureHandle(), Vector2i()}); +} + +void CallbackTextureDatabase::ReleaseTexture(RenderInterface* render_interface, StableVectorIndex callback_index) +{ + CallbackTextureEntry& data = texture_list[callback_index]; + if (data.texture_handle) + render_interface->ReleaseTexture(data.texture_handle); + texture_list.erase(callback_index); +} + +Vector2i CallbackTextureDatabase::GetDimensions(RenderManager* render_manager, RenderInterface* render_interface, StableVectorIndex callback_index) +{ + return EnsureLoaded(render_manager, render_interface, callback_index).dimensions; +} + +TextureHandle CallbackTextureDatabase::GetHandle(RenderManager* render_manager, RenderInterface* render_interface, StableVectorIndex callback_index) +{ + return EnsureLoaded(render_manager, render_interface, callback_index).texture_handle; +} + +auto CallbackTextureDatabase::EnsureLoaded(RenderManager* render_manager, RenderInterface* render_interface, StableVectorIndex callback_index) + -> CallbackTextureEntry& +{ + CallbackTextureEntry& data = texture_list[callback_index]; + if (!data.texture_handle && !data.load_failed) + { + if (!data.callback(CallbackTextureInterface(*render_manager, *render_interface, data.texture_handle, data.dimensions))) + { + data.load_failed = true; + data.texture_handle = {}; + data.dimensions = {}; + } + } + return data; +} + +size_t CallbackTextureDatabase::size() const +{ + return texture_list.size(); +} + +void CallbackTextureDatabase::ReleaseAllTextures(RenderInterface* render_interface) +{ + texture_list.for_each([render_interface](CallbackTextureEntry& texture) { + if (texture.texture_handle) + { + render_interface->ReleaseTexture(texture.texture_handle); + texture.texture_handle = {}; + texture.dimensions = {}; + } + }); +} + +FileTextureDatabase::FileTextureDatabase() {} + +FileTextureDatabase::~FileTextureDatabase() +{ +#ifdef RMLUI_DEBUG + for (const FileTextureEntry& texture : texture_list) + { + RMLUI_ASSERTMSG(!texture.texture_handle, + "TextureDatabase destroyed without releasing all file textures first. Ensure that 'ReleaseAllTextures' is called before destruction."); + } +#endif +} + +TextureFileIndex FileTextureDatabase::InsertTexture(const String& source) +{ + auto it = texture_map.find(source); + if (it != texture_map.end()) + return it->second; + + // The texture is not yet loaded from the render interface. That is deferred until the texture is needed, such as when it becomes visible. + const auto index = TextureFileIndex(texture_list.size()); + texture_map[source] = index; + texture_list.push_back({}); + + return index; +} + +FileTextureDatabase::FileTextureEntry FileTextureDatabase::LoadTextureEntry(RenderInterface* render_interface, const String& source) +{ + FileTextureEntry result = {}; + result.texture_handle = render_interface->LoadTexture(result.dimensions, source); + if (!result.texture_handle) + { + result.load_texture_failed = true; + Rml::Log::Message(Rml::Log::LT_WARNING, "Could not load texture: %s", source.c_str()); + } + return result; +} + +FileTextureDatabase::FileTextureEntry& FileTextureDatabase::EnsureLoaded(RenderInterface* render_interface, TextureFileIndex index) +{ + FileTextureEntry& entry = texture_list[size_t(index)]; + if (!entry.texture_handle) + { + auto it = std::find_if(texture_map.begin(), texture_map.end(), [index](const auto& pair) { return pair.second == index; }); + RMLUI_ASSERT(it != texture_map.end()); + const String& source = it->first; + if (!entry.load_texture_failed) + entry = LoadTextureEntry(render_interface, source); + } + return entry; +} + +TextureHandle FileTextureDatabase::GetHandle(RenderInterface* render_interface, TextureFileIndex index) +{ + RMLUI_ASSERT(size_t(index) < texture_list.size()); + return EnsureLoaded(render_interface, index).texture_handle; +} + +Vector2i FileTextureDatabase::GetDimensions(RenderInterface* render_interface, TextureFileIndex index) +{ + RMLUI_ASSERT(size_t(index) < texture_list.size()); + return EnsureLoaded(render_interface, index).dimensions; +} + +void FileTextureDatabase::GetSourceList(StringList& source_list) const +{ + source_list.reserve(source_list.size() + texture_list.size()); + for (const auto& texture : texture_map) + source_list.push_back(texture.first); +} + +bool FileTextureDatabase::ReleaseTexture(RenderInterface* render_interface, const String& source) +{ + auto it = texture_map.find(source); + if (it == texture_map.end()) + return false; + + FileTextureEntry& texture = texture_list[size_t(it->second)]; + if (texture.texture_handle) + { + render_interface->ReleaseTexture(texture.texture_handle); + texture = {}; + return true; + } + + return false; +} + +void FileTextureDatabase::ReleaseAllTextures(RenderInterface* render_interface) +{ + for (FileTextureEntry& texture : texture_list) + { + if (texture.texture_handle) + { + render_interface->ReleaseTexture(texture.texture_handle); + texture = {}; + } + } +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TextureDatabase.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TextureDatabase.h new file mode 100644 index 00000000..c560a373 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TextureDatabase.h @@ -0,0 +1,74 @@ +#pragma once + +#include "../../Include/RmlUi/Core/CallbackTexture.h" +#include "../../Include/RmlUi/Core/StableVector.h" +#include "../../Include/RmlUi/Core/Types.h" + +namespace Rml { + +class RenderInterface; + +class CallbackTextureDatabase : NonCopyMoveable { +public: + CallbackTextureDatabase(); + ~CallbackTextureDatabase(); + + StableVectorIndex CreateTexture(CallbackTextureFunction&& callback); + void ReleaseTexture(RenderInterface* render_interface, StableVectorIndex callback_index); + + Vector2i GetDimensions(RenderManager* render_manager, RenderInterface* render_interface, StableVectorIndex callback_index); + TextureHandle GetHandle(RenderManager* render_manager, RenderInterface* render_interface, StableVectorIndex callback_index); + + size_t size() const; + + void ReleaseAllTextures(RenderInterface* render_interface); + +private: + struct CallbackTextureEntry { + CallbackTextureFunction callback; + TextureHandle texture_handle = {}; + Vector2i dimensions; + bool load_failed = false; + }; + + CallbackTextureEntry& EnsureLoaded(RenderManager* render_manager, RenderInterface* render_interface, StableVectorIndex callback_index); + + StableVector texture_list; +}; + +class FileTextureDatabase : NonCopyMoveable { +public: + FileTextureDatabase(); + ~FileTextureDatabase(); + + TextureFileIndex InsertTexture(const String& source); + + TextureHandle GetHandle(RenderInterface* render_interface, TextureFileIndex index); + Vector2i GetDimensions(RenderInterface* render_interface, TextureFileIndex index); + + void GetSourceList(StringList& source_list) const; + + bool ReleaseTexture(RenderInterface* render_interface, const String& source); + void ReleaseAllTextures(RenderInterface* render_interface); + +private: + struct FileTextureEntry { + TextureHandle texture_handle = {}; + Vector2i dimensions; + bool load_texture_failed = false; + }; + + FileTextureEntry LoadTextureEntry(RenderInterface* render_interface, const String& source); + FileTextureEntry& EnsureLoaded(RenderInterface* render_interface, TextureFileIndex index); + + Vector texture_list; + UnorderedMap texture_map; // key: source, value: index into 'texture_list' +}; + +class TextureDatabase { +public: + FileTextureDatabase file_database; + CallbackTextureDatabase callback_database; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TextureLayout.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TextureLayout.cpp new file mode 100644 index 00000000..ffa226f4 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TextureLayout.cpp @@ -0,0 +1,70 @@ +#include "TextureLayout.h" +#include "TextureLayoutRectangle.h" +#include "TextureLayoutTexture.h" +#include + +namespace Rml { + +struct RectangleSort { + bool operator()(const TextureLayoutRectangle& lhs, const TextureLayoutRectangle& rhs) const + { + return lhs.GetDimensions().y > rhs.GetDimensions().y; + } +}; + +TextureLayout::TextureLayout() {} + +TextureLayout::~TextureLayout() {} + +void TextureLayout::AddRectangle(int id, Vector2i dimensions) +{ + rectangles.push_back(TextureLayoutRectangle(id, dimensions)); +} + +TextureLayoutRectangle& TextureLayout::GetRectangle(int index) +{ + RMLUI_ASSERT(index >= 0); + RMLUI_ASSERT(index < GetNumRectangles()); + + return rectangles[index]; +} + +int TextureLayout::GetNumRectangles() const +{ + return (int)rectangles.size(); +} + +TextureLayoutTexture& TextureLayout::GetTexture(int index) +{ + RMLUI_ASSERT(index >= 0); + RMLUI_ASSERT(index < GetNumTextures()); + + return textures[index]; +} + +int TextureLayout::GetNumTextures() const +{ + return (int)textures.size(); +} + +bool TextureLayout::GenerateLayout(int max_texture_dimensions) +{ + // Sort the rectangles by height. + std::sort(rectangles.begin(), rectangles.end(), RectangleSort()); + + int num_placed_rectangles = 0; + while (num_placed_rectangles != GetNumRectangles()) + { + TextureLayoutTexture texture; + int texture_size = texture.Generate(*this, max_texture_dimensions); + if (texture_size == 0) + return false; + + textures.push_back(texture); + num_placed_rectangles += texture_size; + } + + return true; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TextureLayout.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TextureLayout.h new file mode 100644 index 00000000..2ac0a1b0 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TextureLayout.h @@ -0,0 +1,53 @@ +#pragma once + +#include "TextureLayoutRectangle.h" +#include "TextureLayoutTexture.h" + +namespace Rml { + +/** + A texture layout generates and stores a layout of rectangles within a series of textures. It is + used primarily by the font system for generating font textures. + */ + +class TextureLayout { +public: + TextureLayout(); + ~TextureLayout(); + + /// Adds a rectangle to the list of rectangles to be laid out. All rectangles must be added to + /// the layout before the layout is generated. + /// @param[in] id The id of the rectangle; used to identify the rectangle after it has been positioned. + /// @param[in] dimensions The dimensions of the rectangle. + void AddRectangle(int id, Vector2i dimensions); + + /// Returns one of the layout's rectangles. + /// @param[in] index The index of the desired rectangle. + /// @return The desired rectangle. + TextureLayoutRectangle& GetRectangle(int index); + /// Returns the number of rectangles in the layout. + /// @return The layout's rectangle count. + int GetNumRectangles() const; + + /// Returns one of the layout's textures. + /// @param[in] index The index of the desired texture. + /// @return The desired texture. + TextureLayoutTexture& GetTexture(int index); + /// Returns the number of textures in the layout. + /// @return The layout's texture count. + int GetNumTextures() const; + + /// Attempts to generate an efficient texture layout for the rectangles. + /// @param[in] max_texture_dimensions The maximum dimensions allowed for any single texture. + /// @return True if the layout was generated successfully, false if not. + bool GenerateLayout(int max_texture_dimensions); + +private: + using RectangleList = Vector; + using TextureList = Vector; + + TextureList textures; + RectangleList rectangles; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TextureLayoutRectangle.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TextureLayoutRectangle.cpp new file mode 100644 index 00000000..60946791 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TextureLayoutRectangle.cpp @@ -0,0 +1,68 @@ +#include "TextureLayoutRectangle.h" + +namespace Rml { + +TextureLayoutRectangle::TextureLayoutRectangle(const int _id, const Vector2i dimensions) : dimensions(dimensions), texture_position(0, 0) +{ + id = _id; + texture_index = -1; + + texture_data = nullptr; + texture_stride = 0; +} + +TextureLayoutRectangle::~TextureLayoutRectangle() {} + +int TextureLayoutRectangle::GetId() const +{ + return id; +} + +Vector2i TextureLayoutRectangle::GetPosition() const +{ + return texture_position; +} + +Vector2i TextureLayoutRectangle::GetDimensions() const +{ + return dimensions; +} + +void TextureLayoutRectangle::Place(const int _texture_index, const Vector2i position) +{ + texture_index = _texture_index; + texture_position = position; +} + +void TextureLayoutRectangle::Unplace() +{ + texture_index = -1; +} + +bool TextureLayoutRectangle::IsPlaced() const +{ + return texture_index > -1; +} + +void TextureLayoutRectangle::Allocate(byte* _texture_data, int _texture_stride) +{ + texture_data = _texture_data + ((texture_position.y * _texture_stride) + texture_position.x * 4); + texture_stride = _texture_stride; +} + +int TextureLayoutRectangle::GetTextureIndex() +{ + return texture_index; +} + +byte* TextureLayoutRectangle::GetTextureData() +{ + return texture_data; +} + +int TextureLayoutRectangle::GetTextureStride() const +{ + return texture_stride; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TextureLayoutRectangle.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TextureLayoutRectangle.h new file mode 100644 index 00000000..3dda070e --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TextureLayoutRectangle.h @@ -0,0 +1,62 @@ +#pragma once + +#include "../../Include/RmlUi/Core/Types.h" + +namespace Rml { + +/** + A texture layout rectangle is an area positioned with a texture layout. + */ + +class TextureLayoutRectangle { +public: + TextureLayoutRectangle(int id, Vector2i dimensions); + ~TextureLayoutRectangle(); + + /// Returns the rectangle's id. + /// @return The rectangle's id. + int GetId() const; + /// Returns the rectangle's position; this is only valid if it has been placed. + /// @return The rectangle's position within its texture. + Vector2i GetPosition() const; + /// Returns the rectangle's dimensions. + /// @return The rectangle's dimensions. + Vector2i GetDimensions() const; + + /// Places the rectangle within a texture. + /// @param[in] texture_index The index of the texture this rectangle is placed on. + /// @param[in] position The position within the texture of this rectangle's top-left corner. + void Place(int texture_index, Vector2i position); + /// Unplaces the rectangle. + void Unplace(); + /// Returns the rectangle's placed state. + /// @return True if the rectangle has been placed, false if not. + bool IsPlaced() const; + + /// Sets the rectangle's texture data and stride. + /// @param[in] texture_data The pointer to the top-left corner of the texture's data. + /// @param[in] texture_stride The stride of the texture data, in bytes. + void Allocate(byte* texture_data, int texture_stride); + + /// Returns the index of the texture this rectangle is placed on. + /// @return The texture index. + int GetTextureIndex(); + /// Returns the rectangle's allocated texture data. + /// @return The texture data. + byte* GetTextureData(); + /// Returns the stride of the rectangle's texture data. + /// @return The texture data stride. + int GetTextureStride() const; + +private: + int id; + Vector2i dimensions; + + int texture_index; + Vector2i texture_position; + + byte* texture_data; + int texture_stride; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TextureLayoutRow.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TextureLayoutRow.cpp new file mode 100644 index 00000000..79acc151 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TextureLayoutRow.cpp @@ -0,0 +1,74 @@ +#include "TextureLayoutRow.h" +#include "TextureLayout.h" + +namespace Rml { + +TextureLayoutRow::TextureLayoutRow() +{ + height = 0; +} + +TextureLayoutRow::~TextureLayoutRow() {} + +int TextureLayoutRow::Generate(TextureLayout& layout, int max_width, int y) +{ + int width = 1; + int first_unplaced_index = 0; + int placed_rectangles = 0; + + while (width < max_width) + { + // Find the first unplaced rectangle we can fit. + int index; + for (index = first_unplaced_index; index < layout.GetNumRectangles(); ++index) + { + TextureLayoutRectangle& rectangle = layout.GetRectangle(index); + if (!rectangle.IsPlaced()) + { + if (width + rectangle.GetDimensions().x + 1 <= max_width) + break; + } + } + + if (index == layout.GetNumRectangles()) + return placed_rectangles; + + TextureLayoutRectangle& rectangle = layout.GetRectangle(index); + + // Increment the row height if necessary. + height = Math::Max(height, rectangle.GetDimensions().y); + + // Add this glyph onto our list and mark it as placed. + rectangles.push_back(&rectangle); + rectangle.Place(layout.GetNumTextures(), Vector2i(width, y)); + ++placed_rectangles; + + // Increment our width. An extra pixel is added on so the rectangles aren't pushed up + // against each other. This will avoid filtering artifacts. + if (rectangle.GetDimensions().x > 0) + width += rectangle.GetDimensions().x + 1; + + first_unplaced_index = index + 1; + } + + return placed_rectangles; +} + +void TextureLayoutRow::Allocate(byte* texture_data, int stride) +{ + for (size_t i = 0; i < rectangles.size(); ++i) + rectangles[i]->Allocate(texture_data, stride); +} + +int TextureLayoutRow::GetHeight() const +{ + return height; +} + +void TextureLayoutRow::Unplace() +{ + for (size_t i = 0; i < rectangles.size(); ++i) + rectangles[i]->Unplace(); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TextureLayoutRow.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TextureLayoutRow.h new file mode 100644 index 00000000..f268bb4d --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TextureLayoutRow.h @@ -0,0 +1,44 @@ +#pragma once + +#include "TextureLayoutRectangle.h" + +namespace Rml { + +class TextureLayout; + +/** + A texture layout row is a single row of rectangles positioned vertically within a texture. + */ + +class TextureLayoutRow { +public: + TextureLayoutRow(); + ~TextureLayoutRow(); + + /// Attempts to position unplaced rectangles from the layout into this row. + /// @param[in] layout The layout to position rectangles from. + /// @param[in] width The maximum width of this row. + /// @param[in] y The y-coordinate of this row. + /// @return The number of placed rectangles. + int Generate(TextureLayout& layout, int width, int y); + + /// Assigns allocated texture data to all rectangles in this row. + /// @param[in] texture_data The pointer to the beginning of the texture's data. + /// @param[in] stride The stride of the texture's surface, in bytes; + void Allocate(byte* texture_data, int stride); + + /// Returns the height of the row. + /// @return The row's height. + int GetHeight() const; + + /// Resets the placed status for all of the rectangles within this row. + void Unplace(); + +private: + using RectangleList = Vector; + + int height; + RectangleList rectangles; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TextureLayoutTexture.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TextureLayoutTexture.cpp new file mode 100644 index 00000000..affa3702 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TextureLayoutTexture.cpp @@ -0,0 +1,123 @@ +#include "TextureLayoutTexture.h" +#include "TextureDatabase.h" +#include "TextureLayout.h" + +namespace Rml { + +TextureLayoutTexture::TextureLayoutTexture() : dimensions(0, 0) {} + +TextureLayoutTexture::~TextureLayoutTexture() +{ + // Don't free texture data; freed in the texture loader. +} + +Vector2i TextureLayoutTexture::GetDimensions() const +{ + return dimensions; +} + +int TextureLayoutTexture::Generate(TextureLayout& layout, int maximum_dimensions) +{ + // Come up with an estimate for how big a texture we need. Calculate the total square pixels + // required by the remaining rectangles to place, square-root it to get the dimensions of the + // smallest texture necessary (under optimal circumstances) and round it up to the nearest + // power of two. + int square_pixels = 0; + int unplaced_rectangles = 0; + for (int i = 0; i < layout.GetNumRectangles(); ++i) + { + const TextureLayoutRectangle& rectangle = layout.GetRectangle(i); + + if (!rectangle.IsPlaced()) + { + int x = rectangle.GetDimensions().x + 1; + int y = rectangle.GetDimensions().y + 1; + + square_pixels += x * y; + ++unplaced_rectangles; + } + } + + int texture_width = int(Math::SquareRoot((float)square_pixels)); + + dimensions.y = Math::ToPowerOfTwo(texture_width); + dimensions.x = dimensions.y >> 1; + + dimensions.x = Math::Min(dimensions.x, maximum_dimensions); + dimensions.y = Math::Min(dimensions.y, maximum_dimensions); + + // Now we're layout out the rectangles in the texture. If we don't fit all the rectangles on + // and have room to grow (ie, haven't hit the maximum texture size in both dimensions) then + // we'll have another go with a bigger texture. + int num_placed_rectangles = 0; + for (;;) + { + bool success = true; + int height = 1; + + while (num_placed_rectangles != unplaced_rectangles) + { + TextureLayoutRow row; + int row_size = row.Generate(layout, dimensions.x, height); + if (row_size == 0) + { + success = false; + break; + } + + height += row.GetHeight() + 1; + if (height > dimensions.y) + { + // D'oh! We've exceeded our height boundaries. This row should be unplaced. + row.Unplace(); + success = false; + break; + } + + rows.push_back(row); + num_placed_rectangles += row_size; + } + + // If the rectangles were successfully laid out within the texture limits, we're done. + if (success) + return num_placed_rectangles; + + // Couldn't do it! Increase the texture size, clear the rectangles and try again - unless + // we've hit the maximum texture size, in which case return true if we've placed any + // rectangles (ie, the layout isn't empty). + if (dimensions.y > dimensions.x) + dimensions.x = dimensions.y; + else + { + if (dimensions.y << 1 > maximum_dimensions) + return num_placed_rectangles; + + dimensions.y <<= 1; + } + + // Unplace all of the glyphs we tried to place and have an other crack. + for (size_t i = 0; i < rows.size(); i++) + rows[i].Unplace(); + + rows.clear(); + num_placed_rectangles = 0; + } +} + +Vector TextureLayoutTexture::AllocateTexture() +{ + Vector texture_data; + + if (dimensions.x > 0 && dimensions.y > 0) + { + // Set the texture to transparent black. + texture_data.resize(dimensions.x * dimensions.y * 4, 0); + + for (size_t i = 0; i < rows.size(); ++i) + rows[i].Allocate(texture_data.data(), dimensions.x * 4); + } + + return texture_data; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TextureLayoutTexture.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TextureLayoutTexture.h new file mode 100644 index 00000000..b744a66c --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TextureLayoutTexture.h @@ -0,0 +1,43 @@ +#pragma once + +#include "../../Include/RmlUi/Core/Texture.h" +#include "TextureLayoutRow.h" + +namespace Rml { + +class TextureLayout; + +/** + A texture layout texture is a single rectangular area which sub-rectangles are placed on within + a complete texture layout. + */ + +class TextureLayoutTexture { +public: + TextureLayoutTexture(); + ~TextureLayoutTexture(); + + /// Returns the texture's dimensions. This is only valid after the texture has been generated. + /// @return The texture's dimensions. + Vector2i GetDimensions() const; + + /// Attempts to position unplaced rectangles from the layout into this texture. The size of + /// this texture will be determined by its contents. + /// @param[in] layout The layout to position rectangles from. + /// @param[in] maximum_dimensions The maximum dimensions of this texture. If this is not big enough to place all the rectangles, then as many will + /// be placed as possible. + /// @return The number of placed rectangles. + int Generate(TextureLayout& layout, int maximum_dimensions); + + /// Allocates the texture. + /// @return The allocated texture data. + Vector AllocateTexture(); + +private: + using RowList = Vector; + + Vector2i dimensions; + RowList rows; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Traits.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Traits.cpp new file mode 100644 index 00000000..db3d773b --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Traits.cpp @@ -0,0 +1,11 @@ +#include "../../Include/RmlUi/Core/Traits.h" + +namespace Rml { + +int FamilyBase::GetNewId() +{ + static int id = 0; + return id++; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Transform.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Transform.cpp new file mode 100644 index 00000000..0674d264 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Transform.cpp @@ -0,0 +1,39 @@ +#include "../../Include/RmlUi/Core/Transform.h" +#include "../../Include/RmlUi/Core/Property.h" +#include "../../Include/RmlUi/Core/StyleSheetSpecification.h" +#include "../../Include/RmlUi/Core/TransformPrimitive.h" + +namespace Rml { + +Transform::Transform() {} + +Transform::Transform(PrimitiveList primitives) : primitives(std::move(primitives)) {} + +Property Transform::MakeProperty(PrimitiveList primitives) +{ + Property p(MakeShared(std::move(primitives)), Unit::TRANSFORM); + p.definition = StyleSheetSpecification::GetProperty(PropertyId::Transform); + return p; +} + +void Transform::ClearPrimitives() +{ + primitives.clear(); +} + +void Transform::AddPrimitive(const TransformPrimitive& p) +{ + primitives.push_back(p); +} + +int Transform::GetNumPrimitives() const noexcept +{ + return (int)primitives.size(); +} + +const TransformPrimitive& Transform::GetPrimitive(int i) const noexcept +{ + return primitives[i]; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TransformPrimitive.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TransformPrimitive.cpp new file mode 100644 index 00000000..16ddfff0 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TransformPrimitive.cpp @@ -0,0 +1,148 @@ +#include "../../Include/RmlUi/Core/TransformPrimitive.h" +#include "../../Include/RmlUi/Core/Element.h" +#include "../../Include/RmlUi/Core/TypeConverter.h" + +namespace Rml { +namespace Transforms { + + /// Returns the numeric value converted to 'base_unit'. Only accepts base units of 'Number' or 'Rad': + /// 'Number' will pass-through the provided value. + /// 'Rad' will convert {Rad, Deg, %} -> Rad. + static float ResolvePrimitiveAbsoluteValue(NumericValue value, Unit base_unit) noexcept + { + RMLUI_ASSERT(base_unit == Unit::RAD || base_unit == Unit::NUMBER); + + if (base_unit == Unit::RAD) + { + switch (value.unit) + { + case Unit::RAD: return value.number; + case Unit::DEG: return Math::DegreesToRadians(value.number); + case Unit::PERCENT: return value.number * 0.01f * 2.0f * Math::RMLUI_PI; + default: Log::Message(Log::LT_WARNING, "Trying to pass a non-angle unit to a property expecting an angle."); + } + } + else if (base_unit == Unit::NUMBER && value.unit != Unit::NUMBER) + { + Log::Message(Log::LT_WARNING, "A unit was passed to a property which expected a unit-less number."); + } + + return value.number; + } + + template + inline ResolvedPrimitive::ResolvedPrimitive(const float* values) noexcept + { + for (size_t i = 0; i < N; ++i) + this->values[i] = values[i]; + } + + template + inline ResolvedPrimitive::ResolvedPrimitive(const NumericValue* values) noexcept + { + for (size_t i = 0; i < N; ++i) + this->values[i] = values[i].number; + } + + template + inline ResolvedPrimitive::ResolvedPrimitive(const NumericValue* values, Array base_units) noexcept + { + for (size_t i = 0; i < N; ++i) + this->values[i] = ResolvePrimitiveAbsoluteValue(values[i], base_units[i]); + } + + template + inline ResolvedPrimitive::ResolvedPrimitive(Array values, Array base_units) noexcept + { + for (size_t i = 0; i < N; ++i) + this->values[i] = ResolvePrimitiveAbsoluteValue(values[i], base_units[i]); + } + + template + inline ResolvedPrimitive::ResolvedPrimitive(Array values) noexcept : values(values) + {} + + template + inline UnresolvedPrimitive::UnresolvedPrimitive(const NumericValue* values) noexcept + { + for (size_t i = 0; i < N; ++i) + this->values[i] = values[i]; + } + + template + inline UnresolvedPrimitive::UnresolvedPrimitive(Array values) noexcept : values(values) + {} + + Matrix2D::Matrix2D(const NumericValue* values) noexcept : ResolvedPrimitive(values) {} + + Matrix3D::Matrix3D(const NumericValue* values) noexcept : ResolvedPrimitive(values) {} + Matrix3D::Matrix3D(const Matrix4f& matrix) noexcept : ResolvedPrimitive(matrix.data()) {} + + TranslateX::TranslateX(const NumericValue* values) noexcept : UnresolvedPrimitive(values) {} + TranslateX::TranslateX(float x, Unit unit) noexcept : UnresolvedPrimitive({NumericValue(x, unit)}) {} + + TranslateY::TranslateY(const NumericValue* values) noexcept : UnresolvedPrimitive(values) {} + TranslateY::TranslateY(float y, Unit unit) noexcept : UnresolvedPrimitive({NumericValue(y, unit)}) {} + + TranslateZ::TranslateZ(const NumericValue* values) noexcept : UnresolvedPrimitive(values) {} + TranslateZ::TranslateZ(float z, Unit unit) noexcept : UnresolvedPrimitive({NumericValue(z, unit)}) {} + + Translate2D::Translate2D(const NumericValue* values) noexcept : UnresolvedPrimitive(values) {} + Translate2D::Translate2D(float x, float y, Unit units) noexcept : UnresolvedPrimitive({NumericValue(x, units), NumericValue(y, units)}) {} + + Translate3D::Translate3D(const NumericValue* values) noexcept : UnresolvedPrimitive(values) {} + Translate3D::Translate3D(NumericValue x, NumericValue y, NumericValue z) noexcept : UnresolvedPrimitive({x, y, z}) {} + Translate3D::Translate3D(float x, float y, float z, Unit units) noexcept : + UnresolvedPrimitive({NumericValue(x, units), NumericValue(y, units), NumericValue(z, units)}) + {} + + ScaleX::ScaleX(const NumericValue* values) noexcept : ResolvedPrimitive(values) {} + ScaleX::ScaleX(float value) noexcept : ResolvedPrimitive({value}) {} + + ScaleY::ScaleY(const NumericValue* values) noexcept : ResolvedPrimitive(values) {} + ScaleY::ScaleY(float value) noexcept : ResolvedPrimitive({value}) {} + + ScaleZ::ScaleZ(const NumericValue* values) noexcept : ResolvedPrimitive(values) {} + ScaleZ::ScaleZ(float value) noexcept : ResolvedPrimitive({value}) {} + + Scale2D::Scale2D(const NumericValue* values) noexcept : ResolvedPrimitive(values) {} + Scale2D::Scale2D(float xy) noexcept : ResolvedPrimitive({xy, xy}) {} + Scale2D::Scale2D(float x, float y) noexcept : ResolvedPrimitive({x, y}) {} + + Scale3D::Scale3D(const NumericValue* values) noexcept : ResolvedPrimitive(values) {} + Scale3D::Scale3D(float xyz) noexcept : ResolvedPrimitive({xyz, xyz, xyz}) {} + Scale3D::Scale3D(float x, float y, float z) noexcept : ResolvedPrimitive({x, y, z}) {} + + RotateX::RotateX(const NumericValue* values) noexcept : ResolvedPrimitive(values, {Unit::RAD}) {} + RotateX::RotateX(float angle, Unit unit) noexcept : ResolvedPrimitive({NumericValue{angle, unit}}, {Unit::RAD}) {} + + RotateY::RotateY(const NumericValue* values) noexcept : ResolvedPrimitive(values, {Unit::RAD}) {} + RotateY::RotateY(float angle, Unit unit) noexcept : ResolvedPrimitive({NumericValue{angle, unit}}, {Unit::RAD}) {} + + RotateZ::RotateZ(const NumericValue* values) noexcept : ResolvedPrimitive(values, {Unit::RAD}) {} + RotateZ::RotateZ(float angle, Unit unit) noexcept : ResolvedPrimitive({NumericValue{angle, unit}}, {Unit::RAD}) {} + + Rotate2D::Rotate2D(const NumericValue* values) noexcept : ResolvedPrimitive(values, {Unit::RAD}) {} + Rotate2D::Rotate2D(float angle, Unit unit) noexcept : ResolvedPrimitive({NumericValue{angle, unit}}, {Unit::RAD}) {} + + Rotate3D::Rotate3D(const NumericValue* values) noexcept : ResolvedPrimitive(values, {Unit::NUMBER, Unit::NUMBER, Unit::NUMBER, Unit::RAD}) {} + Rotate3D::Rotate3D(float x, float y, float z, float angle, Unit angle_unit) noexcept : + ResolvedPrimitive( + {NumericValue{x, Unit::NUMBER}, NumericValue{y, Unit::NUMBER}, NumericValue{z, Unit::NUMBER}, NumericValue{angle, angle_unit}}, + {Unit::NUMBER, Unit::NUMBER, Unit::NUMBER, Unit::RAD}) + {} + + SkewX::SkewX(const NumericValue* values) noexcept : ResolvedPrimitive(values, {Unit::RAD}) {} + SkewX::SkewX(float angle, Unit unit) noexcept : ResolvedPrimitive({NumericValue{angle, unit}}, {Unit::RAD}) {} + + SkewY::SkewY(const NumericValue* values) noexcept : ResolvedPrimitive(values, {Unit::RAD}) {} + SkewY::SkewY(float angle, Unit unit) noexcept : ResolvedPrimitive({NumericValue{angle, unit}}, {Unit::RAD}) {} + + Skew2D::Skew2D(const NumericValue* values) noexcept : ResolvedPrimitive(values, {Unit::RAD, Unit::RAD}) {} + Skew2D::Skew2D(float x, float y, Unit unit) noexcept : ResolvedPrimitive({NumericValue{x, unit}, {NumericValue{y, unit}}}, {Unit::RAD, Unit::RAD}) + {} + + Perspective::Perspective(const NumericValue* values) noexcept : UnresolvedPrimitive(values) {} + +} // namespace Transforms +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TransformState.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TransformState.cpp new file mode 100644 index 00000000..94693f34 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TransformState.cpp @@ -0,0 +1,66 @@ +#include "TransformState.h" + +namespace Rml { + +bool TransformState::SetTransform(const Matrix4f* in_transform) +{ + bool is_changed = (have_transform != (bool)in_transform); + if (in_transform) + { + is_changed |= (have_transform && transform != *in_transform); + transform = *in_transform; + have_transform = true; + } + else + have_transform = false; + + if (is_changed) + dirty_inverse_transform = true; + + return is_changed; +} +bool TransformState::SetLocalPerspective(const Matrix4f* in_perspective) +{ + bool is_changed = (have_perspective != (bool)in_perspective); + + if (in_perspective) + { + is_changed |= (have_perspective && local_perspective != *in_perspective); + local_perspective = *in_perspective; + have_perspective = true; + } + else + have_perspective = false; + + return is_changed; +} + +const Matrix4f* TransformState::GetTransform() const +{ + return have_transform ? &transform : nullptr; +} + +const Matrix4f* TransformState::GetLocalPerspective() const +{ + return have_perspective ? &local_perspective : nullptr; +} + +const Matrix4f* TransformState::GetInverseTransform() const +{ + if (!have_transform) + return nullptr; + + if (dirty_inverse_transform) + { + inverse_transform = transform; + have_inverse_transform = inverse_transform.Invert(); + dirty_inverse_transform = false; + } + + if (have_inverse_transform) + return &inverse_transform; + + return nullptr; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TransformState.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TransformState.h new file mode 100644 index 00000000..8ad91200 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TransformState.h @@ -0,0 +1,38 @@ +#pragma once + +#include "../../Include/RmlUi/Core/Header.h" +#include "../../Include/RmlUi/Core/Types.h" + +namespace Rml { + +class TransformState { +public: + // Returns true if transform was changed. + bool SetTransform(const Matrix4f* in_transform); + + // Returns true if local perspecitve was changed. + bool SetLocalPerspective(const Matrix4f* in_perspective); + + const Matrix4f* GetTransform() const; + const Matrix4f* GetLocalPerspective() const; + + // Returns a nullptr if there is no transform set, or the transform is singular. + const Matrix4f* GetInverseTransform() const; + +private: + bool have_transform = false; + bool have_perspective = false; + mutable bool have_inverse_transform = false; + mutable bool dirty_inverse_transform = false; + + // The accumulated transform matrix combines all transform and perspective properties of the owning element and all ancestors. + Matrix4f transform; + + // Local perspective which applies to children of the owning element. + Matrix4f local_perspective; + + // The inverse of the transform matrix for projecting points from screen space to the current element's space, such as used for picking elements. + mutable Matrix4f inverse_transform; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TransformUtilities.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TransformUtilities.cpp new file mode 100644 index 00000000..9ce16c5d --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TransformUtilities.cpp @@ -0,0 +1,774 @@ +#include "TransformUtilities.h" +#include "../../Include/RmlUi/Core/Element.h" +#include "../../Include/RmlUi/Core/TransformPrimitive.h" + +namespace Rml { + +using namespace Transforms; + +static Vector3f Combine(const Vector3f a, const Vector3f b, float a_scale, float b_scale) +{ + Vector3f result; + result.x = a_scale * a.x + b_scale * b.x; + result.y = a_scale * a.y + b_scale * b.y; + result.z = a_scale * a.z + b_scale * b.z; + return result; +} + +// Interpolate two quaternions a, b with weight alpha [0, 1] +static Vector4f QuaternionSlerp(const Vector4f a, const Vector4f b, float alpha) +{ + using namespace Math; + + const float eps = 0.9995f; + + float dot = a.DotProduct(b); + dot = Clamp(dot, -1.f, 1.f); + + if (dot > eps) + return a; + + float theta = ACos(dot); + float w = Sin(alpha * theta) / SquareRoot(1.f - dot * dot); + float a_scale = Cos(alpha * theta) - dot * w; + + Vector4f result; + for (int i = 0; i < 4; i++) + { + result[i] = a[i] * a_scale + b[i] * w; + } + + return result; +} + +// Resolve a numeric property value with the element's width as relative base value. +static inline float ResolveWidth(NumericValue value, Element& e) noexcept +{ + if (value.unit == Unit::PX || value.unit == Unit::NUMBER) + return value.number; + return e.ResolveNumericValue(value, e.GetBox().GetSize(BoxArea::Border).x); +} + +// Resolve a numeric property value with the element's height as relative base value. +static inline float ResolveHeight(NumericValue value, Element& e) noexcept +{ + if (value.unit == Unit::PX || value.unit == Unit::NUMBER) + return value.number; + return e.ResolveNumericValue(value, e.GetBox().GetSize(BoxArea::Border).y); +} + +// Resolve a length numeric property value for the given element. +static inline float ResolveLength(NumericValue value, Element& e) noexcept +{ + if (value.unit == Unit::PX || value.unit == Unit::NUMBER) + return value.number; + return e.ResolveLength(value); +} + +struct SetIdentityVisitor { + template + void operator()(Transforms::ResolvedPrimitive& p) + { + for (auto& value : p.values) + value = 0.0f; + } + template + void operator()(Transforms::UnresolvedPrimitive& p) + { + for (auto& value : p.values) + value.number = 0.0f; + } + void operator()(Transforms::Matrix2D& p) + { + for (int i = 0; i < 6; i++) + p.values[i] = ((i == 0 || i == 3) ? 1.0f : 0.0f); + } + void operator()(Transforms::Matrix3D& p) + { + for (int i = 0; i < 16; i++) + p.values[i] = ((i % 5) == 0 ? 1.0f : 0.0f); + } + void operator()(Transforms::ScaleX& p) { p.values[0] = 1; } + void operator()(Transforms::ScaleY& p) { p.values[0] = 1; } + void operator()(Transforms::ScaleZ& p) { p.values[0] = 1; } + void operator()(Transforms::Scale2D& p) { p.values[0] = p.values[1] = 1; } + void operator()(Transforms::Scale3D& p) { p.values[0] = p.values[1] = p.values[2] = 1; } + void operator()(Transforms::Rotate3D& p) { p.values[3] = 0; } // Keep the existing rotation axis to allow interpolation with matching axes. + void operator()(Transforms::DecomposedMatrix4& p) + { + p.perspective = Vector4f(0, 0, 0, 1); + p.quaternion = Vector4f(0, 0, 0, 1); + p.translation = Vector3f(0, 0, 0); + p.scale = Vector3f(1, 1, 1); + p.skew = Vector3f(0, 0, 0); + } + + void run(TransformPrimitive& primitive) + { + switch (primitive.type) + { + case TransformPrimitive::MATRIX2D: this->operator()(primitive.matrix_2d); break; + case TransformPrimitive::MATRIX3D: this->operator()(primitive.matrix_3d); break; + case TransformPrimitive::TRANSLATEX: this->operator()(primitive.translate_x); break; + case TransformPrimitive::TRANSLATEY: this->operator()(primitive.translate_y); break; + case TransformPrimitive::TRANSLATEZ: this->operator()(primitive.translate_z); break; + case TransformPrimitive::TRANSLATE2D: this->operator()(primitive.translate_2d); break; + case TransformPrimitive::TRANSLATE3D: this->operator()(primitive.translate_3d); break; + case TransformPrimitive::SCALEX: this->operator()(primitive.scale_x); break; + case TransformPrimitive::SCALEY: this->operator()(primitive.scale_y); break; + case TransformPrimitive::SCALEZ: this->operator()(primitive.scale_z); break; + case TransformPrimitive::SCALE2D: this->operator()(primitive.scale_2d); break; + case TransformPrimitive::SCALE3D: this->operator()(primitive.scale_3d); break; + case TransformPrimitive::ROTATEX: this->operator()(primitive.rotate_x); break; + case TransformPrimitive::ROTATEY: this->operator()(primitive.rotate_y); break; + case TransformPrimitive::ROTATEZ: this->operator()(primitive.rotate_z); break; + case TransformPrimitive::ROTATE2D: this->operator()(primitive.rotate_2d); break; + case TransformPrimitive::ROTATE3D: this->operator()(primitive.rotate_3d); break; + case TransformPrimitive::SKEWX: this->operator()(primitive.skew_x); break; + case TransformPrimitive::SKEWY: this->operator()(primitive.skew_y); break; + case TransformPrimitive::SKEW2D: this->operator()(primitive.skew_2d); break; + case TransformPrimitive::PERSPECTIVE: this->operator()(primitive.perspective); break; + case TransformPrimitive::DECOMPOSEDMATRIX4: this->operator()(primitive.decomposed_matrix_4); break; + } + } +}; + +void TransformUtilities::SetIdentity(TransformPrimitive& p) noexcept +{ + SetIdentityVisitor{}.run(p); +} + +struct ResolveTransformVisitor { + Matrix4f& m; + Element& e; + + void operator()(const Transforms::Matrix2D& p) + { + m = Matrix4f::FromRows(Vector4f(p.values[0], p.values[2], 0, p.values[4]), Vector4f(p.values[1], p.values[3], 0, p.values[5]), + Vector4f(0, 0, 1, 0), Vector4f(0, 0, 0, 1)); + } + void operator()(const Transforms::Matrix3D& p) + { + m = Matrix4f::FromColumns(Vector4f(p.values[0], p.values[1], p.values[2], p.values[3]), + Vector4f(p.values[4], p.values[5], p.values[6], p.values[7]), Vector4f(p.values[8], p.values[9], p.values[10], p.values[11]), + Vector4f(p.values[12], p.values[13], p.values[14], p.values[15])); + } + + void operator()(const Transforms::TranslateX& p) { m = Matrix4f::TranslateX(ResolveWidth(p.values[0], e)); } + void operator()(const Transforms::TranslateY& p) { m = Matrix4f::TranslateY(ResolveHeight(p.values[0], e)); } + void operator()(const Transforms::TranslateZ& p) { m = Matrix4f::TranslateZ(ResolveLength(p.values[0], e)); } + void operator()(const Transforms::Translate2D& p) { m = Matrix4f::Translate(ResolveWidth(p.values[0], e), ResolveHeight(p.values[1], e), 0); } + void operator()(const Transforms::Translate3D& p) + { + m = Matrix4f::Translate(ResolveWidth(p.values[0], e), ResolveHeight(p.values[1], e), ResolveLength(p.values[2], e)); + } + + void operator()(const Transforms::ScaleX& p) { m = Matrix4f::ScaleX(p.values[0]); } + void operator()(const Transforms::ScaleY& p) { m = Matrix4f::ScaleY(p.values[0]); } + void operator()(const Transforms::ScaleZ& p) { m = Matrix4f::ScaleZ(p.values[0]); } + void operator()(const Transforms::Scale2D& p) { m = Matrix4f::Scale(p.values[0], p.values[1], 1); } + void operator()(const Transforms::Scale3D& p) { m = Matrix4f::Scale(p.values[0], p.values[1], p.values[2]); } + + void operator()(const Transforms::RotateX& p) { m = Matrix4f::RotateX(p.values[0]); } + void operator()(const Transforms::RotateY& p) { m = Matrix4f::RotateY(p.values[0]); } + void operator()(const Transforms::RotateZ& p) { m = Matrix4f::RotateZ(p.values[0]); } + void operator()(const Transforms::Rotate2D& p) { m = Matrix4f::RotateZ(p.values[0]); } + void operator()(const Transforms::Rotate3D& p) { m = Matrix4f::Rotate(Vector3f(p.values[0], p.values[1], p.values[2]), p.values[3]); } + + void operator()(const Transforms::SkewX& p) { m = Matrix4f::SkewX(p.values[0]); } + void operator()(const Transforms::SkewY& p) { m = Matrix4f::SkewY(p.values[0]); } + void operator()(const Transforms::Skew2D& p) { m = Matrix4f::Skew(p.values[0], p.values[1]); } + + void operator()(const Transforms::DecomposedMatrix4& p) { m = Matrix4f::Compose(p.translation, p.scale, p.skew, p.perspective, p.quaternion); } + void operator()(const Transforms::Perspective& p) { m = Matrix4f::Perspective(ResolveLength(p.values[0], e)); } + + void run(const TransformPrimitive& primitive) + { + switch (primitive.type) + { + case TransformPrimitive::MATRIX2D: this->operator()(primitive.matrix_2d); break; + case TransformPrimitive::MATRIX3D: this->operator()(primitive.matrix_3d); break; + case TransformPrimitive::TRANSLATEX: this->operator()(primitive.translate_x); break; + case TransformPrimitive::TRANSLATEY: this->operator()(primitive.translate_y); break; + case TransformPrimitive::TRANSLATEZ: this->operator()(primitive.translate_z); break; + case TransformPrimitive::TRANSLATE2D: this->operator()(primitive.translate_2d); break; + case TransformPrimitive::TRANSLATE3D: this->operator()(primitive.translate_3d); break; + case TransformPrimitive::SCALEX: this->operator()(primitive.scale_x); break; + case TransformPrimitive::SCALEY: this->operator()(primitive.scale_y); break; + case TransformPrimitive::SCALEZ: this->operator()(primitive.scale_z); break; + case TransformPrimitive::SCALE2D: this->operator()(primitive.scale_2d); break; + case TransformPrimitive::SCALE3D: this->operator()(primitive.scale_3d); break; + case TransformPrimitive::ROTATEX: this->operator()(primitive.rotate_x); break; + case TransformPrimitive::ROTATEY: this->operator()(primitive.rotate_y); break; + case TransformPrimitive::ROTATEZ: this->operator()(primitive.rotate_z); break; + case TransformPrimitive::ROTATE2D: this->operator()(primitive.rotate_2d); break; + case TransformPrimitive::ROTATE3D: this->operator()(primitive.rotate_3d); break; + case TransformPrimitive::SKEWX: this->operator()(primitive.skew_x); break; + case TransformPrimitive::SKEWY: this->operator()(primitive.skew_y); break; + case TransformPrimitive::SKEW2D: this->operator()(primitive.skew_2d); break; + case TransformPrimitive::PERSPECTIVE: this->operator()(primitive.perspective); break; + case TransformPrimitive::DECOMPOSEDMATRIX4: this->operator()(primitive.decomposed_matrix_4); break; + } + } +}; + +Matrix4f TransformUtilities::ResolveTransform(const TransformPrimitive& p, Element& e) noexcept +{ + Matrix4f m; + ResolveTransformVisitor visitor{m, e}; + visitor.run(p); + return m; +} + +struct PrepareVisitor { + Element& e; + + bool operator()(TranslateX& p) + { + p.values[0] = NumericValue{ResolveWidth(p.values[0], e), Unit::PX}; + return true; + } + bool operator()(TranslateY& p) + { + p.values[0] = NumericValue{ResolveHeight(p.values[0], e), Unit::PX}; + return true; + } + bool operator()(TranslateZ& p) + { + p.values[0] = NumericValue{ResolveLength(p.values[0], e), Unit::PX}; + return true; + } + bool operator()(Translate2D& p) + { + p.values[0] = NumericValue{ResolveWidth(p.values[0], e), Unit::PX}; + p.values[1] = NumericValue{ResolveHeight(p.values[1], e), Unit::PX}; + return true; + } + bool operator()(Translate3D& p) + { + p.values[0] = NumericValue{ResolveWidth(p.values[0], e), Unit::PX}; + p.values[1] = NumericValue{ResolveHeight(p.values[1], e), Unit::PX}; + p.values[2] = NumericValue{ResolveLength(p.values[2], e), Unit::PX}; + return true; + } + template + bool operator()(ResolvedPrimitive& /*p*/) + { + // No conversion needed for resolved transforms (with some exceptions below) + return true; + } + bool operator()(DecomposedMatrix4& /*p*/) { return true; } + bool operator()(Rotate3D& p) + { + // Rotate3D can be interpolated if and only if their rotation axes point in the same direction. + // We normalize the rotation vector here for easy comparison, and return true here. Later on we make the + // pair-wise check in 'TryConvertToMatchingGenericType' to see if we need to decompose. + Vector3f vec = Vector3f(p.values[0], p.values[1], p.values[2]).Normalise(); + p.values[0] = vec.x; + p.values[1] = vec.y; + p.values[2] = vec.z; + return true; + } + bool operator()(Matrix3D& /*p*/) + { + // Matrices must be decomposed for interpolation + return false; + } + bool operator()(Matrix2D& /*p*/) + { + // Matrix2D can also be optimized for interpolation, but for now we decompose it to a full DecomposedMatrix4 + return false; + } + bool operator()(Perspective& /*p*/) + { + // Perspective must be decomposed + return false; + } + + bool run(TransformPrimitive& primitive) + { + switch (primitive.type) + { + case TransformPrimitive::MATRIX2D: return this->operator()(primitive.matrix_2d); + case TransformPrimitive::MATRIX3D: return this->operator()(primitive.matrix_3d); + case TransformPrimitive::TRANSLATEX: return this->operator()(primitive.translate_x); + case TransformPrimitive::TRANSLATEY: return this->operator()(primitive.translate_y); + case TransformPrimitive::TRANSLATEZ: return this->operator()(primitive.translate_z); + case TransformPrimitive::TRANSLATE2D: return this->operator()(primitive.translate_2d); + case TransformPrimitive::TRANSLATE3D: return this->operator()(primitive.translate_3d); + case TransformPrimitive::SCALEX: return this->operator()(primitive.scale_x); + case TransformPrimitive::SCALEY: return this->operator()(primitive.scale_y); + case TransformPrimitive::SCALEZ: return this->operator()(primitive.scale_z); + case TransformPrimitive::SCALE2D: return this->operator()(primitive.scale_2d); + case TransformPrimitive::SCALE3D: return this->operator()(primitive.scale_3d); + case TransformPrimitive::ROTATEX: return this->operator()(primitive.rotate_x); + case TransformPrimitive::ROTATEY: return this->operator()(primitive.rotate_y); + case TransformPrimitive::ROTATEZ: return this->operator()(primitive.rotate_z); + case TransformPrimitive::ROTATE2D: return this->operator()(primitive.rotate_2d); + case TransformPrimitive::ROTATE3D: return this->operator()(primitive.rotate_3d); + case TransformPrimitive::SKEWX: return this->operator()(primitive.skew_x); + case TransformPrimitive::SKEWY: return this->operator()(primitive.skew_y); + case TransformPrimitive::SKEW2D: return this->operator()(primitive.skew_2d); + case TransformPrimitive::PERSPECTIVE: return this->operator()(primitive.perspective); + case TransformPrimitive::DECOMPOSEDMATRIX4: return this->operator()(primitive.decomposed_matrix_4); + } + RMLUI_ASSERT(false); + return false; + } +}; + +bool TransformUtilities::PrepareForInterpolation(TransformPrimitive& p, Element& e) noexcept +{ + return PrepareVisitor{e}.run(p); +} + +enum class GenericType { None, Scale3D, Translate3D, Rotate3D }; + +struct GetGenericTypeVisitor { + GenericType run(const TransformPrimitive& primitive) + { + switch (primitive.type) + { + case TransformPrimitive::TRANSLATEX: + case TransformPrimitive::TRANSLATEY: + case TransformPrimitive::TRANSLATEZ: + case TransformPrimitive::TRANSLATE2D: + case TransformPrimitive::TRANSLATE3D: return GenericType::Translate3D; + + case TransformPrimitive::SCALEX: + case TransformPrimitive::SCALEY: + case TransformPrimitive::SCALEZ: + case TransformPrimitive::SCALE2D: + case TransformPrimitive::SCALE3D: return GenericType::Scale3D; + + case TransformPrimitive::ROTATEX: + case TransformPrimitive::ROTATEY: + case TransformPrimitive::ROTATEZ: + case TransformPrimitive::ROTATE2D: + case TransformPrimitive::ROTATE3D: return GenericType::Rotate3D; + + case TransformPrimitive::MATRIX2D: + case TransformPrimitive::MATRIX3D: + case TransformPrimitive::SKEWX: + case TransformPrimitive::SKEWY: + case TransformPrimitive::SKEW2D: + case TransformPrimitive::PERSPECTIVE: + case TransformPrimitive::DECOMPOSEDMATRIX4: return GenericType::None; + } + RMLUI_ASSERT(false); + return GenericType::None; + } +}; + +struct ConvertToGenericTypeVisitor { + Translate3D operator()(const TranslateX& p) { return Translate3D{p.values[0], {0.0f, Unit::PX}, {0.0f, Unit::PX}}; } + Translate3D operator()(const TranslateY& p) { return Translate3D{{0.0f, Unit::PX}, p.values[0], {0.0f, Unit::PX}}; } + Translate3D operator()(const TranslateZ& p) { return Translate3D{{0.0f, Unit::PX}, {0.0f, Unit::PX}, p.values[0]}; } + Translate3D operator()(const Translate2D& p) { return Translate3D{p.values[0], p.values[1], {0.0f, Unit::PX}}; } + Scale3D operator()(const ScaleX& p) { return Scale3D{p.values[0], 1.0f, 1.0f}; } + Scale3D operator()(const ScaleY& p) { return Scale3D{1.0f, p.values[0], 1.0f}; } + Scale3D operator()(const ScaleZ& p) { return Scale3D{1.0f, 1.0f, p.values[0]}; } + Scale3D operator()(const Scale2D& p) { return Scale3D{p.values[0], p.values[1], 1.0f}; } + Rotate3D operator()(const RotateX& p) { return Rotate3D{1, 0, 0, p.values[0], Unit::RAD}; } + Rotate3D operator()(const RotateY& p) { return Rotate3D{0, 1, 0, p.values[0], Unit::RAD}; } + Rotate3D operator()(const RotateZ& p) { return Rotate3D{0, 0, 1, p.values[0], Unit::RAD}; } + Rotate3D operator()(const Rotate2D& p) { return Rotate3D{0, 0, 1, p.values[0], Unit::RAD}; } + + template + TransformPrimitive operator()(const T& p) + { + RMLUI_ERROR; + return p; + } + + TransformPrimitive run(const TransformPrimitive& primitive) + { + TransformPrimitive result = primitive; + // clang-format off + switch (primitive.type) + { + case TransformPrimitive::TRANSLATEX: result.type = TransformPrimitive::TRANSLATE3D; result.translate_3d = this->operator()(primitive.translate_x); break; + case TransformPrimitive::TRANSLATEY: result.type = TransformPrimitive::TRANSLATE3D; result.translate_3d = this->operator()(primitive.translate_y); break; + case TransformPrimitive::TRANSLATEZ: result.type = TransformPrimitive::TRANSLATE3D; result.translate_3d = this->operator()(primitive.translate_z); break; + case TransformPrimitive::TRANSLATE2D: result.type = TransformPrimitive::TRANSLATE3D; result.translate_3d = this->operator()(primitive.translate_2d); break; + case TransformPrimitive::TRANSLATE3D: break; + case TransformPrimitive::SCALEX: result.type = TransformPrimitive::SCALE3D; result.scale_3d = this->operator()(primitive.scale_x); break; + case TransformPrimitive::SCALEY: result.type = TransformPrimitive::SCALE3D; result.scale_3d = this->operator()(primitive.scale_y); break; + case TransformPrimitive::SCALEZ: result.type = TransformPrimitive::SCALE3D; result.scale_3d = this->operator()(primitive.scale_z); break; + case TransformPrimitive::SCALE2D: result.type = TransformPrimitive::SCALE3D; result.scale_3d = this->operator()(primitive.scale_2d); break; + case TransformPrimitive::SCALE3D: break; + case TransformPrimitive::ROTATEX: result.type = TransformPrimitive::ROTATE3D; result.rotate_3d = this->operator()(primitive.rotate_x); break; + case TransformPrimitive::ROTATEY: result.type = TransformPrimitive::ROTATE3D; result.rotate_3d = this->operator()(primitive.rotate_y); break; + case TransformPrimitive::ROTATEZ: result.type = TransformPrimitive::ROTATE3D; result.rotate_3d = this->operator()(primitive.rotate_z); break; + case TransformPrimitive::ROTATE2D: result.type = TransformPrimitive::ROTATE3D; result.rotate_3d = this->operator()(primitive.rotate_2d); break; + case TransformPrimitive::ROTATE3D: break; + default: RMLUI_ASSERT(false); break; + } + // clang-format on + return result; + } +}; + +static bool CanInterpolateRotate3D(const Rotate3D& p0, const Rotate3D& p1) +{ + // Rotate3D can only be interpolated if and only if their rotation axes point in the same direction. + // Assumes each rotation axis has already been normalized. + auto& v0 = p0.values; + auto& v1 = p1.values; + return v0[0] == v1[0] && v0[1] == v1[1] && v0[2] == v1[2]; +} + +bool TransformUtilities::TryConvertToMatchingGenericType(TransformPrimitive& p0, TransformPrimitive& p1) noexcept +{ + if (p0.type == p1.type) + { + if (p0.type == TransformPrimitive::ROTATE3D && !CanInterpolateRotate3D(p0.rotate_3d, p1.rotate_3d)) + return false; + + return true; + } + + GenericType c0 = GetGenericTypeVisitor{}.run(p0); + GenericType c1 = GetGenericTypeVisitor{}.run(p1); + + if (c0 == c1 && c0 != GenericType::None) + { + TransformPrimitive new_p0 = ConvertToGenericTypeVisitor{}.run(p0); + TransformPrimitive new_p1 = ConvertToGenericTypeVisitor{}.run(p1); + + RMLUI_ASSERT(new_p0.type == new_p1.type); + + if (new_p0.type == TransformPrimitive::ROTATE3D && !CanInterpolateRotate3D(new_p0.rotate_3d, new_p1.rotate_3d)) + return false; + + p0 = new_p0; + p1 = new_p1; + + return true; + } + + return false; +} + +struct InterpolateVisitor { + const TransformPrimitive& other_variant; + float alpha; + + template + bool Interpolate(ResolvedPrimitive& p0, const ResolvedPrimitive& p1) + { + for (size_t i = 0; i < N; i++) + p0.values[i] = p0.values[i] * (1.0f - alpha) + p1.values[i] * alpha; + return true; + } + template + bool Interpolate(UnresolvedPrimitive& p0, const UnresolvedPrimitive& p1) + { + // Assumes that the underlying units have been resolved (e.g. to pixels) + for (size_t i = 0; i < N; i++) + p0.values[i].number = p0.values[i].number * (1.0f - alpha) + p1.values[i].number * alpha; + return true; + } + bool Interpolate(Rotate3D& p0, const Rotate3D& p1) + { + RMLUI_ASSERT(CanInterpolateRotate3D(p0, p1)); + // We can only interpolate rotate3d if their rotation axes align. That should be the case if we get here, + // otherwise the generic type matching should decompose them. Thus, we only need to interpolate + // the angle value here. + p0.values[3] = p0.values[3] * (1.0f - alpha) + p1.values[3] * alpha; + return true; + } + bool Interpolate(Matrix2D& /*p0*/, const Matrix2D& /*p1*/) + { + RMLUI_ERROR; + return false; /* Error if we get here, see PrepareForInterpolation() */ + } + bool Interpolate(Matrix3D& /*p0*/, const Matrix3D& /*p1*/) + { + RMLUI_ERROR; + return false; /* Error if we get here, see PrepareForInterpolation() */ + } + bool Interpolate(Perspective& /*p0*/, const Perspective& /*p1*/) + { + RMLUI_ERROR; + return false; /* Error if we get here, see PrepareForInterpolation() */ + } + + bool Interpolate(DecomposedMatrix4& p0, const DecomposedMatrix4& p1) + { + p0.perspective = p0.perspective * (1.0f - alpha) + p1.perspective * alpha; + p0.quaternion = QuaternionSlerp(p0.quaternion, p1.quaternion, alpha); + p0.translation = p0.translation * (1.0f - alpha) + p1.translation * alpha; + p0.scale = p0.scale * (1.0f - alpha) + p1.scale * alpha; + p0.skew = p0.skew * (1.0f - alpha) + p1.skew * alpha; + return true; + } + + bool run(TransformPrimitive& variant) + { + RMLUI_ASSERT(variant.type == other_variant.type); + switch (variant.type) + { + case TransformPrimitive::MATRIX2D: return Interpolate(variant.matrix_2d, other_variant.matrix_2d); + case TransformPrimitive::MATRIX3D: return Interpolate(variant.matrix_3d, other_variant.matrix_3d); + case TransformPrimitive::TRANSLATEX: return Interpolate(variant.translate_x, other_variant.translate_x); + case TransformPrimitive::TRANSLATEY: return Interpolate(variant.translate_y, other_variant.translate_y); + case TransformPrimitive::TRANSLATEZ: return Interpolate(variant.translate_z, other_variant.translate_z); + case TransformPrimitive::TRANSLATE2D: return Interpolate(variant.translate_2d, other_variant.translate_2d); + case TransformPrimitive::TRANSLATE3D: return Interpolate(variant.translate_3d, other_variant.translate_3d); + case TransformPrimitive::SCALEX: return Interpolate(variant.scale_x, other_variant.scale_x); + case TransformPrimitive::SCALEY: return Interpolate(variant.scale_y, other_variant.scale_y); + case TransformPrimitive::SCALEZ: return Interpolate(variant.scale_z, other_variant.scale_z); + case TransformPrimitive::SCALE2D: return Interpolate(variant.scale_2d, other_variant.scale_2d); + case TransformPrimitive::SCALE3D: return Interpolate(variant.scale_3d, other_variant.scale_3d); + case TransformPrimitive::ROTATEX: return Interpolate(variant.rotate_x, other_variant.rotate_x); + case TransformPrimitive::ROTATEY: return Interpolate(variant.rotate_y, other_variant.rotate_y); + case TransformPrimitive::ROTATEZ: return Interpolate(variant.rotate_z, other_variant.rotate_z); + case TransformPrimitive::ROTATE2D: return Interpolate(variant.rotate_2d, other_variant.rotate_2d); + case TransformPrimitive::ROTATE3D: return Interpolate(variant.rotate_3d, other_variant.rotate_3d); + case TransformPrimitive::SKEWX: return Interpolate(variant.skew_x, other_variant.skew_x); + case TransformPrimitive::SKEWY: return Interpolate(variant.skew_y, other_variant.skew_y); + case TransformPrimitive::SKEW2D: return Interpolate(variant.skew_2d, other_variant.skew_2d); + case TransformPrimitive::PERSPECTIVE: return Interpolate(variant.perspective, other_variant.perspective); + case TransformPrimitive::DECOMPOSEDMATRIX4: return Interpolate(variant.decomposed_matrix_4, other_variant.decomposed_matrix_4); + } + RMLUI_ASSERT(false); + return false; + } +}; + +bool TransformUtilities::InterpolateWith(TransformPrimitive& target, const TransformPrimitive& other, float alpha) noexcept +{ + if (target.type != other.type) + return false; + + bool result = InterpolateVisitor{other, alpha}.run(target); + return result; +} + +template +static String ToString(const Transforms::ResolvedPrimitive& p, const String& unit, bool rad_to_deg = false, + bool only_unit_on_last_value = false) noexcept +{ + float multiplier = 1.0f; + String tmp; + String result = "("; + for (size_t i = 0; i < N; i++) + { + if (only_unit_on_last_value && i < N - 1) + multiplier = 1.0f; + else if (rad_to_deg) + multiplier = 180.f / Math::RMLUI_PI; + + if (TypeConverter::Convert(p.values[i] * multiplier, tmp)) + result += tmp; + + if (!unit.empty() && (!only_unit_on_last_value || (i == N - 1))) + result += unit; + + if (i < N - 1) + result += ", "; + } + result += ")"; + return result; +} + +static inline String ToString(NumericValue value) noexcept +{ + return ToString(value.number) + ToString(value.unit); +} + +template +static inline String ToString(const Transforms::UnresolvedPrimitive& p) noexcept +{ + String result = "("; + for (size_t i = 0; i < N; i++) + { + result += ToString(p.values[i]); + if (i != N - 1) + result += ", "; + } + result += ")"; + return result; +} + +static inline String ToString(const Transforms::DecomposedMatrix4& p) noexcept +{ + static const Transforms::DecomposedMatrix4 d{Vector4f(0, 0, 0, 1), Vector4f(0, 0, 0, 1), Vector3f(0, 0, 0), Vector3f(1, 1, 1), Vector3f(0, 0, 0)}; + String tmp; + String result; + + if (p.perspective != d.perspective && TypeConverter::Convert(p.perspective, tmp)) + result += "perspective(" + tmp + "), "; + if (p.quaternion != d.quaternion && TypeConverter::Convert(p.quaternion, tmp)) + result += "quaternion(" + tmp + "), "; + if (p.translation != d.translation && TypeConverter::Convert(p.translation, tmp)) + result += "translation(" + tmp + "), "; + if (p.scale != d.scale && TypeConverter::Convert(p.scale, tmp)) + result += "scale(" + tmp + "), "; + if (p.skew != d.skew && TypeConverter::Convert(p.skew, tmp)) + result += "skew(" + tmp + "), "; + + if (result.size() > 2) + result.resize(result.size() - 2); + + result = "decomposedMatrix3d{ " + result + " }"; + + return result; +} + +// clang-format off +static inline String ToString(const Transforms::Matrix2D& p) noexcept { return "matrix" + ToString(static_cast&>(p), ""); } +static inline String ToString(const Transforms::Matrix3D& p) noexcept { return "matrix3d" + ToString(static_cast&>(p), ""); } +static inline String ToString(const Transforms::TranslateX& p) noexcept { return "translateX" + ToString(static_cast&>(p)); } +static inline String ToString(const Transforms::TranslateY& p) noexcept { return "translateY" + ToString(static_cast&>(p)); } +static inline String ToString(const Transforms::TranslateZ& p) noexcept { return "translateZ" + ToString(static_cast&>(p)); } +static inline String ToString(const Transforms::Translate2D& p) noexcept { return "translate" + ToString(static_cast&>(p)); } +static inline String ToString(const Transforms::Translate3D& p) noexcept { return "translate3d" + ToString(static_cast&>(p)); } +static inline String ToString(const Transforms::ScaleX& p) noexcept { return "scaleX" + ToString(static_cast&>(p), ""); } +static inline String ToString(const Transforms::ScaleY& p) noexcept { return "scaleY" + ToString(static_cast&>(p), ""); } +static inline String ToString(const Transforms::ScaleZ& p) noexcept { return "scaleZ" + ToString(static_cast&>(p), ""); } +static inline String ToString(const Transforms::Scale2D& p) noexcept { return "scale" + ToString(static_cast&>(p), ""); } +static inline String ToString(const Transforms::Scale3D& p) noexcept { return "scale3d" + ToString(static_cast&>(p), ""); } +static inline String ToString(const Transforms::RotateX& p) noexcept { return "rotateX" + ToString(static_cast&>(p), "deg", true); } +static inline String ToString(const Transforms::RotateY& p) noexcept { return "rotateY" + ToString(static_cast&>(p), "deg", true); } +static inline String ToString(const Transforms::RotateZ& p) noexcept { return "rotateZ" + ToString(static_cast&>(p), "deg", true); } +static inline String ToString(const Transforms::Rotate2D& p) noexcept { return "rotate" + ToString(static_cast&>(p), "deg", true); } +static inline String ToString(const Transforms::Rotate3D& p) noexcept { return "rotate3d" + ToString(static_cast&>(p), "deg", true, true); } +static inline String ToString(const Transforms::SkewX& p) noexcept { return "skewX" + ToString(static_cast&>(p), "deg", true); } +static inline String ToString(const Transforms::SkewY& p) noexcept { return "skewY" + ToString(static_cast&>(p), "deg", true); } +static inline String ToString(const Transforms::Skew2D& p) noexcept { return "skew" + ToString(static_cast&>(p), "deg", true); } +static inline String ToString(const Transforms::Perspective& p) noexcept { return "perspective" + ToString(static_cast&>(p)); } +// clang-format on + +struct ToStringVisitor { + String run(const TransformPrimitive& variant) + { + switch (variant.type) + { + case TransformPrimitive::MATRIX2D: return ToString(variant.matrix_2d); + case TransformPrimitive::MATRIX3D: return ToString(variant.matrix_3d); + case TransformPrimitive::TRANSLATEX: return ToString(variant.translate_x); + case TransformPrimitive::TRANSLATEY: return ToString(variant.translate_y); + case TransformPrimitive::TRANSLATEZ: return ToString(variant.translate_z); + case TransformPrimitive::TRANSLATE2D: return ToString(variant.translate_2d); + case TransformPrimitive::TRANSLATE3D: return ToString(variant.translate_3d); + case TransformPrimitive::SCALEX: return ToString(variant.scale_x); + case TransformPrimitive::SCALEY: return ToString(variant.scale_y); + case TransformPrimitive::SCALEZ: return ToString(variant.scale_z); + case TransformPrimitive::SCALE2D: return ToString(variant.scale_2d); + case TransformPrimitive::SCALE3D: return ToString(variant.scale_3d); + case TransformPrimitive::ROTATEX: return ToString(variant.rotate_x); + case TransformPrimitive::ROTATEY: return ToString(variant.rotate_y); + case TransformPrimitive::ROTATEZ: return ToString(variant.rotate_z); + case TransformPrimitive::ROTATE2D: return ToString(variant.rotate_2d); + case TransformPrimitive::ROTATE3D: return ToString(variant.rotate_3d); + case TransformPrimitive::SKEWX: return ToString(variant.skew_x); + case TransformPrimitive::SKEWY: return ToString(variant.skew_y); + case TransformPrimitive::SKEW2D: return ToString(variant.skew_2d); + case TransformPrimitive::PERSPECTIVE: return ToString(variant.perspective); + case TransformPrimitive::DECOMPOSEDMATRIX4: return ToString(variant.decomposed_matrix_4); + } + RMLUI_ASSERT(false); + return String(); + } +}; + +String TransformUtilities::ToString(const TransformPrimitive& p) noexcept +{ + String result = ToStringVisitor{}.run(p); + return result; +} + +bool TransformUtilities::Decompose(Transforms::DecomposedMatrix4& d, const Matrix4f& m) noexcept +{ + // Follows the procedure given in https://drafts.csswg.org/css-transforms-2/#interpolation-of-3d-matrices + + const float eps = 0.0005f; + + if (Math::Absolute(m[3][3]) < eps) + return false; + + // Perspective matrix + Matrix4f p = m; + + for (int i = 0; i < 3; i++) + p[i][3] = 0; + p[3][3] = 1; + + if (Math::Absolute(p.Determinant()) < eps) + return false; + + if (m[0][3] != 0 || m[1][3] != 0 || m[2][3] != 0) + { + auto rhs = m.GetColumn(3); + Matrix4f p_inv = p; + if (!p_inv.Invert()) + return false; + auto& p_inv_trans = p.Transpose(); + d.perspective = p_inv_trans * rhs; + } + else + { + d.perspective[0] = d.perspective[1] = d.perspective[2] = 0; + d.perspective[3] = 1; + } + + for (int i = 0; i < 3; i++) + d.translation[i] = m[3][i]; + + Vector3f row[3]; + for (int i = 0; i < 3; i++) + { + row[i][0] = m[i][0]; + row[i][1] = m[i][1]; + row[i][2] = m[i][2]; + } + + d.scale[0] = row[0].Magnitude(); + row[0] = row[0].Normalise(); + + d.skew[0] = row[0].DotProduct(row[1]); + row[1] = Combine(row[1], row[0], 1, -d.skew[0]); + + d.scale[1] = row[1].Magnitude(); + row[1] = row[1].Normalise(); + d.skew[0] /= d.scale[1]; + + d.skew[1] = row[0].DotProduct(row[2]); + row[2] = Combine(row[2], row[0], 1, -d.skew[1]); + d.skew[2] = row[1].DotProduct(row[2]); + row[2] = Combine(row[2], row[1], 1, -d.skew[2]); + + d.scale[2] = row[2].Magnitude(); + row[2] = row[2].Normalise(); + d.skew[2] /= d.scale[2]; + d.skew[1] /= d.scale[2]; + + // Check if we need to flip coordinate system + auto pdum3 = row[1].CrossProduct(row[2]); + if (row[0].DotProduct(pdum3) < 0.0f) + { + for (int i = 0; i < 3; i++) + { + d.scale[i] *= -1.f; + row[i] *= -1.f; + } + } + + d.quaternion[0] = 0.5f * Math::SquareRoot(Math::Max(1.f + row[0][0] - row[1][1] - row[2][2], 0.0f)); + d.quaternion[1] = 0.5f * Math::SquareRoot(Math::Max(1.f - row[0][0] + row[1][1] - row[2][2], 0.0f)); + d.quaternion[2] = 0.5f * Math::SquareRoot(Math::Max(1.f - row[0][0] - row[1][1] + row[2][2], 0.0f)); + d.quaternion[3] = 0.5f * Math::SquareRoot(Math::Max(1.f + row[0][0] + row[1][1] + row[2][2], 0.0f)); + + if (row[2][1] > row[1][2]) + d.quaternion[0] *= -1.f; + if (row[0][2] > row[2][0]) + d.quaternion[1] *= -1.f; + if (row[1][0] > row[0][1]) + d.quaternion[2] *= -1.f; + + return true; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TransformUtilities.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TransformUtilities.h new file mode 100644 index 00000000..c17aa953 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TransformUtilities.h @@ -0,0 +1,40 @@ +#pragma once + +#include "../../Include/RmlUi/Core/Header.h" +#include "../../Include/RmlUi/Core/Types.h" + +namespace Rml { + +struct TransformPrimitive; +namespace Transforms { + struct DecomposedMatrix4; +} + +namespace TransformUtilities { + // Set the primitive to its identity value. + void SetIdentity(TransformPrimitive& primitive) noexcept; + + // Resolve the primitive into a transformation matrix, given the current element properties and layout. + Matrix4f ResolveTransform(const TransformPrimitive& primitive, Element& e) noexcept; + + // Prepares the primitive for interpolation. This must be done before calling InterpolateWith(). + // Promote units to basic types which can be interpolated, that is, convert 'length -> pixel' for unresolved primitives. + // Returns false if the owning transform must to be converted to a DecomposedMatrix4 primitive. + bool PrepareForInterpolation(TransformPrimitive& primitive, Element& e) noexcept; + + // If primitives do not match, try to convert them to a common generic type, e.g. TranslateX -> Translate3D. + // Returns true if they are already the same type or were converted to a common generic type. + bool TryConvertToMatchingGenericType(TransformPrimitive& p0, TransformPrimitive& p1) noexcept; + + // Interpolate the target primitive with another primitive, weighted by alpha [0, 1]. + // Primitives must be of the same type, and PrepareForInterpolation() must previously have been called on both. + bool InterpolateWith(TransformPrimitive& target, const TransformPrimitive& other, float alpha) noexcept; + + // Decompose a Matrix4 into its decomposed components. + // Returns true on success, or false if the matrix is singular. + bool Decompose(Transforms::DecomposedMatrix4& decomposed_matrix, const Matrix4f& matrix) noexcept; + + String ToString(const TransformPrimitive& primitive) noexcept; +} // namespace TransformUtilities + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Tween.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Tween.cpp new file mode 100644 index 00000000..6296bfeb --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Tween.cpp @@ -0,0 +1,212 @@ +#include "../../Include/RmlUi/Core/Tween.h" +#include "../../Include/RmlUi/Core/Math.h" +#include + +namespace Rml { + +namespace TweenFunctions { + + /** + Tweening functions below. + Partly based on http://libclaw.sourceforge.net/tweeners.html + */ + + static inline float square(float t) + { + return t * t; + } + + static float back(float t) + { + return t * t * (2.70158f * t - 1.70158f); + } + + static float bounce(float t) + { + if (t > 1.f - 1.f / 2.75f) + return 1.f - 7.5625f * square(1.f - t); + else if (t > 1.f - 2.f / 2.75f) + return 1.0f - (7.5625f * square(1.f - t - 1.5f / 2.75f) + 0.75f); + else if (t > 1.f - 2.5f / 2.75f) + return 1.0f - (7.5625f * square(1.f - t - 2.25f / 2.75f) + 0.9375f); + return 1.0f - (7.5625f * square(1.f - t - 2.625f / 2.75f) + 0.984375f); + } + + static float circular(float t) + { + return 1.f - Math::SquareRoot(1.f - t * t); + } + + static float cubic(float t) + { + return t * t * t; + } + + static float elastic(float t) + { + if (t == 0) + return t; + if (t == 1) + return t; + return -Math::Exp(7.24f * (t - 1.f)) * Math::Sin((t - 1.1f) * 2.f * Math::RMLUI_PI / 0.4f); + } + + static float exponential(float t) + { + if (t == 0) + return t; + if (t == 1) + return t; + return Math::Exp(7.24f * (t - 1.f)); + } + + static float linear(float t) + { + return t; + } + + static float quadratic(float t) + { + return t * t; + } + + static float quartic(float t) + { + return t * t * t * t; + } + + static float quintic(float t) + { + return t * t * t * t * t; + } + + static float sine(float t) + { + return 1.f - Math::Cos(t * Math::RMLUI_PI * 0.5f); + } + +} // namespace TweenFunctions + +Tween::Tween(Type type, Direction direction) +{ + if (direction & In) + type_in = type; + if (direction & Out) + type_out = type; +} +Tween::Tween(Type type_in, Type type_out) : type_in(type_in), type_out(type_out) {} +Tween::Tween(CallbackFnc callback, Direction direction) : callback(callback) +{ + if (direction & In) + type_in = Callback; + if (direction & Out) + type_out = Callback; +} +float Tween::operator()(float t) const +{ + if (type_in != None && type_out == None) + { + return in(t); + } + if (type_in == None && type_out != None) + { + return out(t); + } + if (type_in != None && type_out != None) + { + return in_out(t); + } + return t; +} + +void Tween::reverse() +{ + std::swap(type_in, type_out); +} + +bool Tween::operator==(const Tween& other) const +{ + return type_in == other.type_in && type_out == other.type_out && callback == other.callback; +} + +bool Tween::operator!=(const Tween& other) const +{ + return !(*this == other); +} + +String Tween::to_string() const +{ + static const Array type_str = { + {"none", "back", "bounce", "circular", "cubic", "elastic", "exponential", "linear", "quadratic", "quartic", "quintic", "sine", "callback"}}; + + if (size_t(type_in) < type_str.size() && size_t(type_out) < type_str.size()) + { + if (type_in == None && type_out == None) + { + return "none"; + } + else if (type_in == type_out) + { + return type_str[size_t(type_in)] + String("-in-out"); + } + else if (type_in == None) + { + return type_str[size_t(type_out)] + String("-out"); + } + else if (type_out == None) + { + return type_str[size_t(type_in)] + String("-in"); + } + else if (type_in != type_out) + { + return type_str[size_t(type_in)] + String("-in-") + type_str[size_t(type_out)] + String("-out"); + } + } + return "unknown"; +} + +float Tween::tween(Type type, float t) const +{ + using namespace TweenFunctions; + + switch (type) + { + case Back: return back(t); + case Bounce: return bounce(t); + case Circular: return circular(t); + case Cubic: return cubic(t); + case Elastic: return elastic(t); + case Exponential: return exponential(t); + case Linear: return linear(t); + case Quadratic: return quadratic(t); + case Quartic: return quartic(t); + case Quintic: return quintic(t); + case Sine: return sine(t); + case Callback: + if (callback) + return (*callback)(t); + break; + default: break; + } + return t; +} + +float Tween::in(float t) const +{ + return tween(type_in, t); +} + +float Tween::out(float t) const +{ + return 1.0f - tween(type_out, 1.0f - t); +} + +float Tween::in_out(float t) const +{ + if (t < 0.5f) + return tween(type_in, 2.0f * t) * 0.5f; + else + return 0.5f + out(2.0f * t - 1.0f) * 0.5f; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TypeConverter.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TypeConverter.cpp new file mode 100644 index 00000000..dc981d7a --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/TypeConverter.cpp @@ -0,0 +1,280 @@ +#include "../../Include/RmlUi/Core/TypeConverter.h" +#include "../../Include/RmlUi/Core/Animation.h" +#include "../../Include/RmlUi/Core/DecorationTypes.h" +#include "../../Include/RmlUi/Core/Decorator.h" +#include "../../Include/RmlUi/Core/Filter.h" +#include "../../Include/RmlUi/Core/PropertyDictionary.h" +#include "../../Include/RmlUi/Core/PropertySpecification.h" +#include "../../Include/RmlUi/Core/StyleSheetSpecification.h" +#include "../../Include/RmlUi/Core/StyleSheetTypes.h" +#include "../../Include/RmlUi/Core/Transform.h" +#include "../../Include/RmlUi/Core/TransformPrimitive.h" +#include "PropertyParserColour.h" +#include "PropertyParserDecorator.h" +#include "TransformUtilities.h" + +namespace Rml { + +bool TypeConverter::Convert(const Unit& src, String& dest) +{ + switch (src) + { + // clang-format off + case Unit::NUMBER: dest = ""; return true; + case Unit::PERCENT: dest = "%"; return true; + + case Unit::PX: dest = "px"; return true; + case Unit::DP: dest = "dp"; return true; + case Unit::VW: dest = "vw"; return true; + case Unit::VH: dest = "vh"; return true; + case Unit::X: dest = "x"; return true; + case Unit::EM: dest = "em"; return true; + case Unit::REM: dest = "rem"; return true; + + case Unit::INCH: dest = "in"; return true; + case Unit::CM: dest = "cm"; return true; + case Unit::MM: dest = "mm"; return true; + case Unit::PT: dest = "pt"; return true; + case Unit::PC: dest = "pc"; return true; + + case Unit::DEG: dest = "deg"; return true; + case Unit::RAD: dest = "rad"; return true; + // clang-format on + default: break; + } + + return false; +} + +bool TypeConverter::Convert(const TransformPtr& src, TransformPtr& dest) +{ + dest = src; + return true; +} + +bool TypeConverter::Convert(const TransformPtr& src, String& dest) +{ + if (src) + { + dest.clear(); + const Transform::PrimitiveList& primitives = src->GetPrimitives(); + for (size_t i = 0; i < primitives.size(); i++) + { + dest += TransformUtilities::ToString(primitives[i]); + if (i != primitives.size() - 1) + dest += ' '; + } + } + else + { + dest = "none"; + } + return true; +} + +bool TypeConverter::Convert(const TransitionList& src, TransitionList& dest) +{ + dest = src; + return true; +} + +bool TypeConverter::Convert(const TransitionList& src, String& dest) +{ + if (src.none) + { + dest = "none"; + return true; + } + String tmp; + for (size_t i = 0; i < src.transitions.size(); i++) + { + const Transition& t = src.transitions[i]; + dest += StyleSheetSpecification::GetPropertyName(t.id) + ' '; + dest += t.tween.to_string() + ' '; + if (TypeConverter::Convert(t.duration, tmp)) + dest += tmp + "s "; + if (t.delay > 0.0f && TypeConverter::Convert(t.delay, tmp)) + dest += tmp + "s "; + if (t.reverse_adjustment_factor > 0.0f && TypeConverter::Convert(t.reverse_adjustment_factor, tmp)) + dest += tmp + ' '; + if (dest.size() > 0) + dest.resize(dest.size() - 1); + if (i != src.transitions.size() - 1) + dest += ", "; + } + return true; +} + +bool TypeConverter::Convert(const AnimationList& src, AnimationList& dest) +{ + dest = src; + return true; +} + +bool TypeConverter::Convert(const AnimationList& src, String& dest) +{ + String tmp; + for (size_t i = 0; i < src.size(); i++) + { + const Animation& a = src[i]; + if (TypeConverter::Convert(a.duration, tmp)) + dest += tmp + "s "; + dest += a.tween.to_string() + " "; + if (a.delay > 0.0f && TypeConverter::Convert(a.delay, tmp)) + dest += tmp + "s "; + if (a.alternate) + dest += "alternate "; + if (a.paused) + dest += "paused "; + if (a.num_iterations == -1) + dest += "infinite "; + else if (TypeConverter::Convert(a.num_iterations, tmp)) + dest += tmp + " "; + dest += a.name; + if (i != src.size() - 1) + dest += ", "; + } + return true; +} + +template +void AppendPaintArea(const EffectDeclaration& /*declaration*/, String& /*dest*/) +{} +template <> +void AppendPaintArea(const DecoratorDeclaration& declaration, String& dest) +{ + if (declaration.paint_area >= BoxArea::Border && declaration.paint_area <= BoxArea::Padding) + dest += " " + PropertyParserDecorator::ConvertAreaToString(declaration.paint_area); +} + +template +static bool ConvertEffectToString(const EffectsPtr& src, String& dest, const String& separator) +{ + if (!src || src->list.empty()) + dest = "none"; + else if (!src->value.empty()) + dest += src->value; + else + { + dest.clear(); + for (const auto& declaration : src->list) + { + dest += declaration.type; + if (auto* instancer = declaration.instancer) + dest += '(' + instancer->GetPropertySpecification().PropertiesToString(declaration.properties, false, ' ') + ')'; + + AppendPaintArea(declaration, dest); + if (&declaration != &src->list.back()) + dest += separator; + } + } + return true; +} + +bool TypeConverter::Convert(const DecoratorsPtr& src, DecoratorsPtr& dest) +{ + dest = src; + return true; +} + +bool TypeConverter::Convert(const DecoratorsPtr& src, String& dest) +{ + return ConvertEffectToString(src, dest, ", "); +} + +bool TypeConverter::Convert(const FiltersPtr& src, FiltersPtr& dest) +{ + dest = src; + return true; +} + +bool TypeConverter::Convert(const FiltersPtr& src, String& dest) +{ + return ConvertEffectToString(src, dest, " "); +} + +bool TypeConverter::Convert(const FontEffectsPtr& src, FontEffectsPtr& dest) +{ + dest = src; + return true; +} + +bool TypeConverter::Convert(const FontEffectsPtr& src, String& dest) +{ + if (!src || src->list.empty()) + dest = "none"; + else + dest += src->value; + return true; +} + +bool TypeConverter::Convert(const ColorStopList& src, ColorStopList& dest) +{ + dest = src; + return true; +} + +bool TypeConverter::Convert(const ColorStopList& src, String& dest) +{ + dest.clear(); + for (size_t i = 0; i < src.size(); i++) + { + const ColorStop& stop = src[i]; + dest += ToString(stop.color.ToNonPremultiplied()); + + if (Any(stop.position.unit & Unit::NUMBER_LENGTH_PERCENT)) + dest += " " + ToString(stop.position.number) + ToString(stop.position.unit); + + if (i < src.size() - 1) + dest += ", "; + } + return true; +} + +bool TypeConverter::Convert(const BoxShadowList& src, BoxShadowList& dest) +{ + dest = src; + return true; +} + +bool TypeConverter::Convert(const BoxShadowList& src, String& dest) +{ + dest.clear(); + String temp, str_unit; + for (size_t i = 0; i < src.size(); i++) + { + const BoxShadow& shadow = src[i]; + for (const NumericValue* value : {&shadow.offset_x, &shadow.offset_y, &shadow.blur_radius, &shadow.spread_distance}) + { + if (TypeConverter::Convert(value->unit, str_unit)) + temp += " " + ToString(value->number) + str_unit; + } + + if (shadow.inset) + temp += " inset"; + + dest += ToString(shadow.color.ToNonPremultiplied()) + temp; + + if (i < src.size() - 1) + { + dest += ", "; + temp.clear(); + } + } + return true; +} + +bool TypeConverter::Convert(const Colourb& src, String& dest) +{ + if (src.alpha == 255) + return FormatString(dest, "#%02hhx%02hhx%02hhx", src.red, src.green, src.blue) > 0; + else + return FormatString(dest, "#%02hhx%02hhx%02hhx%02hhx", src.red, src.green, src.blue, src.alpha) > 0; +} + +bool TypeConverter::Convert(const String& src, Colourb& dest) +{ + return PropertyParserColour::ParseColour(dest, src); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/URL.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/URL.cpp new file mode 100644 index 00000000..6cc0d8b9 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/URL.cpp @@ -0,0 +1,619 @@ +#include "../../Include/RmlUi/Core/URL.h" +#include "../../Include/RmlUi/Core/Log.h" +#include "../../Include/RmlUi/Core/StringUtilities.h" +#include +#include + +namespace Rml { + +const char* DEFAULT_PROTOCOL = "file"; + +URL::URL() +{ + port = 0; + url_dirty = false; +} + +URL::URL(const String& _url) +{ + port = 0; + RMLUI_VERIFY(SetURL(_url)); +} + +URL::URL(const char* _url) +{ + port = 0; + RMLUI_VERIFY(SetURL(_url)); +} + +URL::~URL() {} + +bool URL::SetURL(const String& _url) +{ + url_dirty = false; + url = _url; + + // Make sure an Empty URL is completely Empty. + if (url.empty()) + { + protocol.clear(); + login.clear(); + password.clear(); + host.clear(); + port = 0; + path.clear(); + file_name.clear(); + extension.clear(); + + return true; + } + + // Find the protocol. This consists of the string appearing before the + // '://' token (ie, file://, http://). + const char* host_begin = strchr(_url.c_str(), ':'); + if (nullptr != host_begin) + { + protocol = String(_url.c_str(), host_begin); + if (0 != strncmp(host_begin, "://", 3)) + { + char malformed_terminator[4] = {0, 0, 0, 0}; + strncpy(malformed_terminator, host_begin, 3); + Log::Message(Log::LT_ERROR, "Malformed protocol identifier found in URL %s; expected %s://, found %s%s.\n", _url.c_str(), + protocol.c_str(), protocol.c_str(), malformed_terminator); + + return false; + } + host_begin += 3; + } + else + { + protocol = DEFAULT_PROTOCOL; + host_begin = _url.c_str(); + } + + // We only want to look for a host if a protocol was specified. + const char* path_begin; + if (host_begin != _url.c_str()) + { + // Find the host. This is the string appearing after the protocol or after + // the username:password combination, and terminated either with a colon, + // if a port is specified, or a forward slash if there is no port. + + // Check for a login pair + const char* at_symbol = strchr(host_begin, '@'); + if (at_symbol) + { + String login_password; + login_password = String(host_begin, at_symbol); + host_begin = at_symbol + 1; + + const char* password_ptr = strchr(login_password.c_str(), ':'); + if (password_ptr) + { + login = String(login_password.c_str(), password_ptr); + password = String(password_ptr + 1); + } + else + { + login = login_password; + } + } + + // Get the host portion + path_begin = strchr(host_begin, '/'); + // Search for the colon in the host name, which will indicate a port. + const char* port_begin = strchr(host_begin, ':'); + if (nullptr != port_begin && (nullptr == path_begin || port_begin < path_begin)) + { + if (1 != sscanf(port_begin, ":%d", &port)) + { + Log::Message(Log::LT_ERROR, "Malformed port number found in URL %s.\n", _url.c_str()); + return false; + } + + host = String(host_begin, port_begin); + + // Don't continue if there is no path. + if (nullptr == path_begin) + { + return true; + } + + // Increment the path string past the trailing slash. + ++path_begin; + } + else + { + port = -1; + + if (nullptr == path_begin) + { + host = host_begin; + return true; + } + else + { + // Assign the host name, then increment the path string past the + // trailing slash. + host = String(host_begin, path_begin); + ++path_begin; + } + } + } + else + { + path_begin = _url.c_str(); + } + + // Check for parameters + String path_segment; + const char* parameters = strchr(path_begin, '?'); + if (parameters) + { + // Pull the path segment out, so further processing doesn't read the parameters + path_segment = String(path_begin, parameters); + path_begin = path_segment.c_str(); + + // Loop through all parameters, loading them + StringList parameter_list; + StringUtilities::ExpandString(parameter_list, parameters + 1, '&'); + for (size_t i = 0; i < parameter_list.size(); i++) + { + // Split into key and value + StringList key_value; + StringUtilities::ExpandString(key_value, parameter_list[i], '='); + + key_value[0] = UrlDecode(key_value[0]); + if (key_value.size() == 2) + this->parameters[key_value[0]] = UrlDecode(key_value[1]); + else + this->parameters[key_value[0]] = ""; + } + } + + // Find the path. This is the string appearing after the host, terminated + // by the last forward slash. + const char* file_name_begin = strrchr(path_begin, '/'); + if (nullptr == file_name_begin) + { + // No path! + file_name_begin = path_begin; + path = ""; + } + else + { + // Copy the path including the trailing slash. + path = String(path_begin, ++file_name_begin); + + // Normalise the path, stripping any ../'s from it + size_t parent_dir_pos = String::npos; + while ((parent_dir_pos = path.find("/../")) != String::npos && parent_dir_pos != 0) + { + // Find the start of the parent directory. + size_t parent_dir_start_pos = path.rfind('/', parent_dir_pos - 1); + if (parent_dir_start_pos == String::npos) + parent_dir_start_pos = 0; + else + parent_dir_start_pos += 1; + + // Strip out the parent dir and the /.. + path.erase(parent_dir_start_pos, parent_dir_pos - parent_dir_start_pos + 4); + + // We've altered the URL, mark it dirty + url_dirty = true; + } + } + + // Find the file name. This is the string after the trailing slash of the + // path, and just before the extension. + const char* extension_begin = strrchr(file_name_begin, '.'); + if (nullptr == extension_begin) + { + file_name = file_name_begin; + extension = ""; + } + else + { + file_name = String(file_name_begin, extension_begin); + extension = extension_begin + 1; + } + + return true; +} + +const String& URL::GetURL() const +{ + if (url_dirty) + ConstructURL(); + + return url; +} + +bool URL::SetProtocol(const String& _protocol) +{ + protocol = _protocol; + url_dirty = true; + + return true; +} + +const String& URL::GetProtocol() const +{ + return protocol; +} + +bool URL::SetLogin(const String& _login) +{ + login = _login; + url_dirty = true; + return true; +} + +const String& URL::GetLogin() const +{ + return login; +} + +bool URL::SetPassword(const String& _password) +{ + password = _password; + url_dirty = true; + return true; +} + +const String& URL::GetPassword() const +{ + return password; +} + +bool URL::SetHost(const String& _host) +{ + host = _host; + url_dirty = true; + + return true; +} + +const String& URL::GetHost() const +{ + return host; +} + +bool URL::SetPort(int _port) +{ + port = _port; + url_dirty = true; + + return true; +} + +int URL::GetPort() const +{ + return port; +} + +bool URL::SetPath(const String& _path) +{ + path = _path; + url_dirty = true; + + return true; +} + +bool URL::PrefixPath(const String& prefix) +{ + // If there's no trailing slash on the end of the prefix, add one. + if (!prefix.empty() && prefix[prefix.size() - 1] != '/') + path = prefix + "/" + path; + else + path = prefix + path; + + url_dirty = true; + + return true; +} + +const String& URL::GetPath() const +{ + return path; +} + +bool URL::SetFileName(const String& _file_name) +{ + file_name = _file_name; + url_dirty = true; + + return true; +} + +const String& URL::GetFileName() const +{ + return file_name; +} + +bool URL::SetExtension(const String& _extension) +{ + extension = _extension; + url_dirty = true; + + return true; +} + +const String& URL::GetExtension() const +{ + return extension; +} + +const URL::Parameters& URL::GetParameters() const +{ + return parameters; +} + +void URL::SetParameter(const String& key, const String& value) +{ + parameters[key] = value; + url_dirty = true; +} + +void URL::SetParameters(const Parameters& _parameters) +{ + parameters = _parameters; + url_dirty = true; +} + +void URL::ClearParameters() +{ + parameters.clear(); +} + +String URL::GetPathedFileName() const +{ + String pathed_file_name = path; + + // Append the file name. + pathed_file_name += file_name; + + // Append the extension. + if (!extension.empty()) + { + pathed_file_name += "."; + pathed_file_name += extension; + } + + return pathed_file_name; +} + +String URL::GetQueryString() const +{ + String query_string; + + int count = 0; + for (Parameters::const_iterator itr = parameters.begin(); itr != parameters.end(); ++itr) + { + query_string += (count == 0) ? "" : "&"; + + query_string += UrlEncode((*itr).first); + query_string += "="; + query_string += UrlEncode((*itr).second); + + count++; + } + + return query_string; +} + +bool URL::operator<(const URL& rhs) const +{ + if (url_dirty) + ConstructURL(); + if (rhs.url_dirty) + rhs.ConstructURL(); + + return url < rhs.url; +} + +void URL::ConstructURL() const +{ + url = ""; + + // Append the protocol. + if (!protocol.empty() && !host.empty()) + { + url = protocol; + url += "://"; + } + + // Append login and password + if (!login.empty()) + { + url += login; + if (!password.empty()) + { + url += ":"; + url += password; + } + url += "@"; + } + RMLUI_ASSERTMSG(password.empty() || (!password.empty() && !login.empty()), "Can't have a password without a login!"); + + // Append the host. + url += host; + + // Only check ports if there is some host/protocol part + if (!url.empty()) + { + if (port > 0) + { + RMLUI_ASSERTMSG(!host.empty(), "Can't have a port without a host!"); + constexpr size_t port_buffer_size = 16; + char port_string[port_buffer_size]; + snprintf(port_string, port_buffer_size, ":%d/", port); + url += port_string; + } + else + { + url += "/"; + } + } + + // Append the path. + if (!path.empty()) + { + url += path; + } + + // Append the file name. + url += file_name; + + // Append the extension. + if (!extension.empty()) + { + url += "."; + url += extension; + } + + // Append parameters + if (!parameters.empty()) + { + url += "?"; + url += GetQueryString(); + } + + url_dirty = false; +} + +String URL::UrlEncode(const String& value) +{ + String encoded; + constexpr size_t hex_buffer_size = 4; + char hex[hex_buffer_size] = {0, 0, 0, 0}; + + encoded.clear(); + + const char* value_c = value.c_str(); + for (String::size_type i = 0; value_c[i]; i++) + { + char c = value_c[i]; + if (IsUnreservedChar(c)) + encoded += c; + else + { + snprintf(hex, hex_buffer_size, "%%%02X", c); + encoded += hex; + } + } + + return encoded; +} + +String URL::UrlDecode(const String& value) +{ + String decoded; + + decoded.clear(); + + const char* value_c = value.c_str(); + String::size_type value_len = value.size(); + for (String::size_type i = 0; i < value_len; i++) + { + char c = value_c[i]; + if (c == '+') + { + decoded += ' '; + } + else if (c == '%') + { + char* endp; + String t = value.substr(i + 1, 2); + int ch = strtol(t.c_str(), &endp, 16); + if (*endp == '\0') + decoded += char(ch); + else + decoded += t; + i += 2; + } + else + { + decoded += c; + } + } + + return decoded; +} + +bool URL::IsUnreservedChar(const char in) +{ + switch (in) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + case 'g': + case 'h': + case 'i': + case 'j': + case 'k': + case 'l': + case 'm': + case 'n': + case 'o': + case 'p': + case 'q': + case 'r': + case 's': + case 't': + case 'u': + case 'v': + case 'w': + case 'x': + case 'y': + case 'z': + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + case 'H': + case 'I': + case 'J': + case 'K': + case 'L': + case 'M': + case 'N': + case 'O': + case 'P': + case 'Q': + case 'R': + case 'S': + case 'T': + case 'U': + case 'V': + case 'W': + case 'X': + case 'Y': + case 'Z': + case '-': + case '.': + case '_': + case '~': return true; + default: break; + } + return false; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Variant.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Variant.cpp new file mode 100644 index 00000000..d583aee4 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/Variant.cpp @@ -0,0 +1,533 @@ +#include "../../Include/RmlUi/Core/Variant.h" +#include "../../Include/RmlUi/Core/DecorationTypes.h" +#include + +namespace Rml { + +Variant::Variant() +{ + // Make sure our object size assumptions fit inside the static buffer + static_assert(sizeof(Colourb) <= LOCAL_DATA_SIZE, "Local data too small for Colourb"); + static_assert(sizeof(Colourf) <= LOCAL_DATA_SIZE, "Local data too small for Colourf"); + static_assert(sizeof(Vector4f) <= LOCAL_DATA_SIZE, "Local data too small for Vector4f"); + static_assert(sizeof(String) <= LOCAL_DATA_SIZE, "Local data too small for String"); + static_assert(sizeof(TransformPtr) <= LOCAL_DATA_SIZE, "Local data too small for TransformPtr"); + static_assert(sizeof(TransitionList) <= LOCAL_DATA_SIZE, "Local data too small for TransitionList"); + static_assert(sizeof(AnimationList) <= LOCAL_DATA_SIZE, "Local data too small for AnimationList"); + static_assert(sizeof(DecoratorsPtr) <= LOCAL_DATA_SIZE, "Local data too small for DecoratorsPtr"); + static_assert(sizeof(FiltersPtr) <= LOCAL_DATA_SIZE, "Local data too small for FiltersPtr"); + static_assert(sizeof(FontEffectsPtr) <= LOCAL_DATA_SIZE, "Local data too small for FontEffectsPtr"); + static_assert(sizeof(ColorStopList) <= LOCAL_DATA_SIZE, "Local data too small for ColorStopList"); + static_assert(sizeof(BoxShadowList) <= LOCAL_DATA_SIZE, "Local data too small for BoxShadowList"); +} + +Variant::Variant(const Variant& copy) +{ + Set(copy); +} + +Variant::Variant(Variant&& other) noexcept +{ + Set(std::move(other)); +} + +Variant::~Variant() +{ + Clear(); +} + +void Variant::Clear() +{ + // Free any allocated types. + switch (type) + { + case STRING: + { + // Clean up the string. + String* string = (String*)data; + string->~String(); + } + break; + case TRANSFORMPTR: + { + // Clean up the transform. + TransformPtr* transform = (TransformPtr*)data; + transform->~TransformPtr(); + } + break; + case TRANSITIONLIST: + { + // Clean up the transition list. + TransitionList* transition_list = (TransitionList*)data; + transition_list->~TransitionList(); + } + break; + case ANIMATIONLIST: + { + // Clean up the transition list. + AnimationList* animation_list = (AnimationList*)data; + animation_list->~AnimationList(); + } + break; + case DECORATORSPTR: + { + DecoratorsPtr* decorators = (DecoratorsPtr*)data; + decorators->~DecoratorsPtr(); + } + break; + case FILTERSPTR: + { + FiltersPtr* decorators = (FiltersPtr*)data; + decorators->~FiltersPtr(); + } + break; + case FONTEFFECTSPTR: + { + FontEffectsPtr* font_effects = (FontEffectsPtr*)data; + font_effects->~shared_ptr(); + } + break; + case COLORSTOPLIST: + { + ColorStopList* value = (ColorStopList*)data; + value->~ColorStopList(); + } + break; + case BOXSHADOWLIST: + { + BoxShadowList* value = (BoxShadowList*)data; + value->~BoxShadowList(); + } + break; + default: break; + } + type = NONE; +} + +#define SET_VARIANT(type) *((type*)data) = value; + +void Variant::Set(const Variant& copy) +{ + switch (copy.type) + { + case STRING: Set(*reinterpret_cast(copy.data)); break; + case TRANSFORMPTR: Set(*reinterpret_cast(copy.data)); break; + case TRANSITIONLIST: Set(*reinterpret_cast(copy.data)); break; + case ANIMATIONLIST: Set(*reinterpret_cast(copy.data)); break; + case DECORATORSPTR: Set(*reinterpret_cast(copy.data)); break; + case FILTERSPTR: Set(*reinterpret_cast(copy.data)); break; + case FONTEFFECTSPTR: Set(*reinterpret_cast(copy.data)); break; + case COLORSTOPLIST: Set(*reinterpret_cast(copy.data)); break; + case BOXSHADOWLIST: Set(*reinterpret_cast(copy.data)); break; + default: + memcpy(data, copy.data, LOCAL_DATA_SIZE); + type = copy.type; + break; + } + RMLUI_ASSERT(type == copy.type); +} + +void Variant::Set(Variant&& other) +{ + switch (other.type) + { + case STRING: Set(std::move(*reinterpret_cast(other.data))); break; + case TRANSFORMPTR: Set(std::move(*reinterpret_cast(other.data))); break; + case TRANSITIONLIST: Set(std::move(*reinterpret_cast(other.data))); break; + case ANIMATIONLIST: Set(std::move(*reinterpret_cast(other.data))); break; + case DECORATORSPTR: Set(std::move(*reinterpret_cast(other.data))); break; + case FILTERSPTR: Set(std::move(*reinterpret_cast(other.data))); break; + case FONTEFFECTSPTR: Set(std::move(*reinterpret_cast(other.data))); break; + case COLORSTOPLIST: Set(std::move(*reinterpret_cast(other.data))); break; + case BOXSHADOWLIST: Set(std::move(*reinterpret_cast(other.data))); break; + default: + memcpy(data, other.data, LOCAL_DATA_SIZE); + type = other.type; + break; + } + RMLUI_ASSERT(type == other.type); +} + +void Variant::Set(const bool value) +{ + type = BOOL; + SET_VARIANT(bool); +} + +void Variant::Set(const byte value) +{ + type = BYTE; + SET_VARIANT(byte); +} + +void Variant::Set(const char value) +{ + type = CHAR; + SET_VARIANT(char); +} + +void Variant::Set(const float value) +{ + type = FLOAT; + SET_VARIANT(float); +} + +void Variant::Set(const double value) +{ + type = DOUBLE; + SET_VARIANT(double); +} + +void Variant::Set(const int value) +{ + type = INT; + SET_VARIANT(int); +} + +void Variant::Set(const int64_t value) +{ + type = INT64; + SET_VARIANT(int64_t); +} + +void Variant::Set(const unsigned int value) +{ + type = UINT; + SET_VARIANT(unsigned int); +} + +void Variant::Set(const uint64_t value) +{ + type = UINT64; + SET_VARIANT(uint64_t); +} + +void Variant::Set(const char* value) +{ + Set(String(value)); +} + +void Variant::Set(void* voidptr) +{ + type = VOIDPTR; + memcpy(data, &voidptr, sizeof(void*)); +} + +void Variant::Set(const Vector2f value) +{ + type = VECTOR2; + SET_VARIANT(Vector2f); +} + +void Variant::Set(const Vector3f value) +{ + type = VECTOR3; + SET_VARIANT(Vector3f); +} + +void Variant::Set(const Vector4f value) +{ + type = VECTOR4; + SET_VARIANT(Vector4f); +} + +void Variant::Set(const Colourf value) +{ + type = COLOURF; + SET_VARIANT(Colourf); +} + +void Variant::Set(const Colourb value) +{ + type = COLOURB; + SET_VARIANT(Colourb); +} + +void Variant::Set(ScriptInterface* value) +{ + type = SCRIPTINTERFACE; + memcpy(data, &value, sizeof(ScriptInterface*)); +} + +void Variant::Set(const String& value) +{ + if (type == STRING) + { + (*(String*)data) = value; + } + else + { + type = STRING; + new (data) String(value); + } +} +void Variant::Set(String&& value) +{ + if (type == STRING) + { + (*(String*)data) = std::move(value); + } + else + { + type = STRING; + new (data) String(std::move(value)); + } +} + +void Variant::Set(const TransformPtr& value) +{ + if (type == TRANSFORMPTR) + { + SET_VARIANT(TransformPtr); + } + else + { + type = TRANSFORMPTR; + new (data) TransformPtr(value); + } +} +void Variant::Set(TransformPtr&& value) +{ + if (type == TRANSFORMPTR) + { + (*(TransformPtr*)data) = std::move(value); + } + else + { + type = TRANSFORMPTR; + new (data) TransformPtr(std::move(value)); + } +} + +void Variant::Set(const TransitionList& value) +{ + if (type == TRANSITIONLIST) + { + *(TransitionList*)data = value; + } + else + { + type = TRANSITIONLIST; + new (data) TransitionList(value); + } +} +void Variant::Set(TransitionList&& value) +{ + if (type == TRANSITIONLIST) + { + (*(TransitionList*)data) = std::move(value); + } + else + { + type = TRANSITIONLIST; + new (data) TransitionList(std::move(value)); + } +} + +void Variant::Set(const AnimationList& value) +{ + if (type == ANIMATIONLIST) + { + *(AnimationList*)data = value; + } + else + { + type = ANIMATIONLIST; + new (data) AnimationList(value); + } +} +void Variant::Set(AnimationList&& value) +{ + if (type == ANIMATIONLIST) + { + (*(AnimationList*)data) = std::move(value); + } + else + { + type = ANIMATIONLIST; + new (data) AnimationList(std::move(value)); + } +} + +void Variant::Set(const DecoratorsPtr& value) +{ + if (type == DECORATORSPTR) + { + *(DecoratorsPtr*)data = value; + } + else + { + type = DECORATORSPTR; + new (data) DecoratorsPtr(value); + } +} +void Variant::Set(DecoratorsPtr&& value) +{ + if (type == DECORATORSPTR) + { + (*(DecoratorsPtr*)data) = std::move(value); + } + else + { + type = DECORATORSPTR; + new (data) DecoratorsPtr(std::move(value)); + } +} +void Variant::Set(const FiltersPtr& value) +{ + if (type == FILTERSPTR) + { + *(FiltersPtr*)data = value; + } + else + { + type = FILTERSPTR; + new (data) FiltersPtr(value); + } +} +void Variant::Set(FiltersPtr&& value) +{ + if (type == FILTERSPTR) + { + (*(FiltersPtr*)data) = std::move(value); + } + else + { + type = FILTERSPTR; + new (data) FiltersPtr(std::move(value)); + } +} +void Variant::Set(const FontEffectsPtr& value) +{ + if (type == FONTEFFECTSPTR) + { + *(FontEffectsPtr*)data = value; + } + else + { + type = FONTEFFECTSPTR; + new (data) FontEffectsPtr(value); + } +} +void Variant::Set(FontEffectsPtr&& value) +{ + if (type == FONTEFFECTSPTR) + { + (*(FontEffectsPtr*)data) = std::move(value); + } + else + { + type = FONTEFFECTSPTR; + new (data) FontEffectsPtr(std::move(value)); + } +} +void Variant::Set(const ColorStopList& value) +{ + if (type == COLORSTOPLIST) + { + *(ColorStopList*)data = value; + } + else + { + type = COLORSTOPLIST; + new (data) ColorStopList(value); + } +} +void Variant::Set(ColorStopList&& value) +{ + if (type == COLORSTOPLIST) + { + (*(ColorStopList*)data) = std::move(value); + } + else + { + type = COLORSTOPLIST; + new (data) ColorStopList(std::move(value)); + } +} +void Variant::Set(const BoxShadowList& value) +{ + if (type == BOXSHADOWLIST) + { + *(BoxShadowList*)data = value; + } + else + { + type = BOXSHADOWLIST; + new (data) BoxShadowList(value); + } +} +void Variant::Set(BoxShadowList&& value) +{ + if (type == BOXSHADOWLIST) + { + (*(BoxShadowList*)data) = std::move(value); + } + else + { + type = BOXSHADOWLIST; + new (data) BoxShadowList(std::move(value)); + } +} + +Variant& Variant::operator=(const Variant& copy) +{ + if (this == ©) + return *this; + if (copy.type != type) + Clear(); + Set(copy); + return *this; +} + +Variant& Variant::operator=(Variant&& other) noexcept +{ + if (this == &other) + return *this; + if (other.type != type) + Clear(); + Set(std::move(other)); + return *this; +} + +#define DEFAULT_VARIANT_COMPARE(TYPE) static_cast(*(TYPE*)data) == static_cast(*(TYPE*)other.data) + +bool Variant::operator==(const Variant& other) const +{ + if (type != other.type) + return false; + + switch (type) + { + case BOOL: return DEFAULT_VARIANT_COMPARE(bool); + case BYTE: return DEFAULT_VARIANT_COMPARE(byte); + case CHAR: return DEFAULT_VARIANT_COMPARE(char); + case FLOAT: return DEFAULT_VARIANT_COMPARE(float); + case DOUBLE: return DEFAULT_VARIANT_COMPARE(double); + case INT: return DEFAULT_VARIANT_COMPARE(int); + case INT64: return DEFAULT_VARIANT_COMPARE(int64_t); + case UINT: return DEFAULT_VARIANT_COMPARE(unsigned int); + case UINT64: return DEFAULT_VARIANT_COMPARE(uint64_t); + case STRING: return DEFAULT_VARIANT_COMPARE(String); + case VECTOR2: return DEFAULT_VARIANT_COMPARE(Vector2f); + case VECTOR3: return DEFAULT_VARIANT_COMPARE(Vector3f); + case VECTOR4: return DEFAULT_VARIANT_COMPARE(Vector4f); + case COLOURF: return DEFAULT_VARIANT_COMPARE(Colourf); + case COLOURB: return DEFAULT_VARIANT_COMPARE(Colourb); + case SCRIPTINTERFACE: return DEFAULT_VARIANT_COMPARE(ScriptInterface*); + case VOIDPTR: return DEFAULT_VARIANT_COMPARE(void*); + case TRANSFORMPTR: return DEFAULT_VARIANT_COMPARE(TransformPtr); + case TRANSITIONLIST: return DEFAULT_VARIANT_COMPARE(TransitionList); + case ANIMATIONLIST: return DEFAULT_VARIANT_COMPARE(AnimationList); + case DECORATORSPTR: return DEFAULT_VARIANT_COMPARE(DecoratorsPtr); + case FILTERSPTR: return DEFAULT_VARIANT_COMPARE(FiltersPtr); + case FONTEFFECTSPTR: return DEFAULT_VARIANT_COMPARE(FontEffectsPtr); + case COLORSTOPLIST: return DEFAULT_VARIANT_COMPARE(ColorStopList); + case BOXSHADOWLIST: return DEFAULT_VARIANT_COMPARE(BoxShadowList); + case NONE: return true; + } + RMLUI_ERRORMSG("Variant comparison not implemented for this type."); + return false; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/WidgetScroll.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/WidgetScroll.cpp new file mode 100644 index 00000000..0227ce23 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/WidgetScroll.cpp @@ -0,0 +1,504 @@ +#include "WidgetScroll.h" +#include "../../Include/RmlUi/Core/ComputedValues.h" +#include "../../Include/RmlUi/Core/Context.h" +#include "../../Include/RmlUi/Core/Element.h" +#include "../../Include/RmlUi/Core/ElementUtilities.h" +#include "../../Include/RmlUi/Core/Event.h" +#include "../../Include/RmlUi/Core/Factory.h" +#include "../../Include/RmlUi/Core/Property.h" +#include "Clock.h" +#include "Layout/LayoutDetails.h" +#include + +namespace Rml { + +static constexpr float DEFAULT_REPEAT_DELAY = 0.5f; +static constexpr float DEFAULT_REPEAT_PERIOD = 0.1f; + +static constexpr float SCROLL_LINE_LENGTH = 30.f; // [dp] +static constexpr float SCROLL_PAGE_FACTOR = 0.8f; + +WidgetScroll::WidgetScroll(Element* _parent) +{ + parent = _parent; + + orientation = UNKNOWN; + + track = nullptr; + bar = nullptr; + arrows[0] = nullptr; + arrows[1] = nullptr; + + bar_position = 0; + bar_drag_anchor = 0; + + arrow_timers[0] = -1; + arrow_timers[1] = -1; + last_update_time = 0; + + track_length = 0; + bar_length = 0; +} + +WidgetScroll::~WidgetScroll() +{ + if (bar != nullptr) + { + bar->RemoveEventListener(EventId::Drag, this); + bar->RemoveEventListener(EventId::Dragstart, this); + } + + if (track != nullptr) + track->RemoveEventListener(EventId::Click, this); + + for (int i = 0; i < 2; i++) + { + if (arrows[i] != nullptr) + { + arrows[i]->RemoveEventListener(EventId::Mousedown, this); + arrows[i]->RemoveEventListener(EventId::Mouseup, this); + arrows[i]->RemoveEventListener(EventId::Mouseout, this); + } + } +} + +bool WidgetScroll::Initialise(Orientation _orientation) +{ + // Check that we haven't already been successfully initialised. + if (orientation != UNKNOWN) + { + RMLUI_ERROR; + return false; + } + + // Check that a valid orientation has been passed in. + if (_orientation != HORIZONTAL && _orientation != VERTICAL) + { + RMLUI_ERROR; + return false; + } + + orientation = _orientation; + + // Create all of our child elements as standard elements, and abort if we can't create them. + ElementPtr track_element = Factory::InstanceElement(parent, "*", "slidertrack", XMLAttributes()); + ElementPtr bar_element = Factory::InstanceElement(parent, "*", "sliderbar", XMLAttributes()); + ElementPtr arrow0_element = Factory::InstanceElement(parent, "*", "sliderarrowdec", XMLAttributes()); + ElementPtr arrow1_element = Factory::InstanceElement(parent, "*", "sliderarrowinc", XMLAttributes()); + + if (!track_element || !bar_element || !arrow0_element || !arrow1_element) + { + return false; + } + + // Add them as non-DOM elements. + track = parent->AppendChild(std::move(track_element), false); + bar = parent->AppendChild(std::move(bar_element), false); + arrows[0] = parent->AppendChild(std::move(arrow0_element), false); + arrows[1] = parent->AppendChild(std::move(arrow1_element), false); + + bar->SetProperty(PropertyId::Drag, Property(Style::Drag::Drag)); + + // Attach the listeners as appropriate. + bar->AddEventListener(EventId::Drag, this); + bar->AddEventListener(EventId::Dragstart, this); + + track->AddEventListener(EventId::Click, this); + + for (int i = 0; i < 2; i++) + { + arrows[i]->AddEventListener(EventId::Mousedown, this); + arrows[i]->AddEventListener(EventId::Mouseup, this); + arrows[i]->AddEventListener(EventId::Mouseout, this); + } + + return true; +} + +void WidgetScroll::Update() +{ + if (!std::any_of(std::begin(arrow_timers), std::end(arrow_timers), [](float timer) { return timer > 0; })) + return; + + const double current_time = Clock::GetElapsedTime(); + const float delta_time = float(current_time - last_update_time); + last_update_time = current_time; + + for (int i = 0; i < 2; i++) + { + if (arrow_timers[i] > 0) + { + arrow_timers[i] -= delta_time; + while (arrow_timers[i] <= 0) + { + arrow_timers[i] += DEFAULT_REPEAT_PERIOD; + if (i == 0) + ScrollLineUp(); + else + ScrollLineDown(); + } + + if (Context* ctx = parent->GetContext()) + ctx->RequestNextUpdate(arrow_timers[i]); + } + } +} + +void WidgetScroll::SetBarPosition(float _bar_position) +{ + bar_position = Math::Clamp(_bar_position, 0.0f, 1.0f); + PositionBar(); +} + +float WidgetScroll::GetBarPosition() const +{ + return bar_position; +} + +WidgetScroll::Orientation WidgetScroll::GetOrientation() const +{ + return orientation; +} + +void WidgetScroll::GetDimensions(Vector2f& dimensions) const +{ + switch (orientation) + { + case VERTICAL: + dimensions.x = 16; + dimensions.y = 256; + break; + case HORIZONTAL: + dimensions.x = 256; + dimensions.y = 16; + break; + case UNKNOWN: break; + } +} + +void WidgetScroll::FormatElements(const Vector2f containing_block, float slider_length) +{ + int length_axis = orientation == VERTICAL ? 1 : 0; + + // Build the box for the containing slider element. + Box parent_box; + LayoutDetails::BuildBox(parent_box, containing_block, parent); + slider_length -= orientation == VERTICAL + ? (parent_box.GetCumulativeEdge(BoxArea::Content, BoxEdge::Top) + parent_box.GetCumulativeEdge(BoxArea::Content, BoxEdge::Bottom)) + : (parent_box.GetCumulativeEdge(BoxArea::Content, BoxEdge::Left) + parent_box.GetCumulativeEdge(BoxArea::Content, BoxEdge::Right)); + + // Set the length of the slider. + Vector2f content = parent_box.GetSize(); + content[length_axis] = slider_length; + parent_box.SetContent(content); + // And set it on the slider element! + parent->SetBox(parent_box); + + // Generate the initial dimensions for the track. It'll need to be cut down to fit the arrows. + Box track_box; + LayoutDetails::BuildBox(track_box, parent_box.GetSize(), track); + content = track_box.GetSize(); + content[length_axis] = slider_length -= orientation == VERTICAL + ? (track_box.GetCumulativeEdge(BoxArea::Content, BoxEdge::Top) + track_box.GetCumulativeEdge(BoxArea::Content, BoxEdge::Bottom)) + : (track_box.GetCumulativeEdge(BoxArea::Content, BoxEdge::Left) + track_box.GetCumulativeEdge(BoxArea::Content, BoxEdge::Right)); + // If no height has been explicitly specified for the track, it'll be initialised to -1 as per normal block + // elements. We'll fix that up here. + if (orientation == HORIZONTAL && content.y < 0) + content.y = parent_box.GetSize().y; + + // Now we size the arrows. + for (int i = 0; i < 2; i++) + { + Box arrow_box; + LayoutDetails::BuildBox(arrow_box, parent_box.GetSize(), arrows[i]); + + // Clamp the size to (0, 0). + Vector2f arrow_size = arrow_box.GetSize(); + if (arrow_size.x < 0 || arrow_size.y < 0) + arrow_box.SetContent(Vector2f(0, 0)); + + arrows[i]->SetBox(arrow_box); + + // Shrink the track length by the arrow size. + content[length_axis] -= arrow_box.GetSize(BoxArea::Margin)[length_axis]; + } + + // Now the track has been sized, we can fix everything into position. + track_box.SetContent(content); + track->SetBox(track_box); + + if (orientation == VERTICAL) + { + Vector2f offset(arrows[0]->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Left), arrows[0]->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Top)); + arrows[0]->SetOffset(offset, parent); + + offset.x = track->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Left); + offset.y += arrows[0]->GetBox().GetSize(BoxArea::Border).y + arrows[0]->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Bottom) + + track->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Top); + track->SetOffset(offset, parent); + + offset.x = arrows[1]->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Left); + offset.y += track->GetBox().GetSize(BoxArea::Border).y + track->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Bottom) + + arrows[1]->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Top); + arrows[1]->SetOffset(offset, parent); + } + else + { + Vector2f offset(arrows[0]->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Left), arrows[0]->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Top)); + arrows[0]->SetOffset(offset, parent); + + offset.x += arrows[0]->GetBox().GetSize(BoxArea::Border).x + arrows[0]->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Right) + + track->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Left); + offset.y = track->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Top); + track->SetOffset(offset, parent); + + offset.x += track->GetBox().GetSize(BoxArea::Border).x + track->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Right) + + arrows[1]->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Left); + offset.y = arrows[1]->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Top); + arrows[1]->SetOffset(offset, parent); + } + + FormatBar(); +} + +void WidgetScroll::FormatBar() +{ + Box bar_box; + LayoutDetails::BuildBox(bar_box, parent->GetBox().GetSize(), bar); + + const auto& computed = bar->GetComputedValues(); + + const Style::Width width = computed.width(); + const Style::Height height = computed.height(); + + Vector2f bar_box_content = bar_box.GetSize(); + if (orientation == HORIZONTAL) + { + if (height.type == height.Auto) + bar_box_content.y = parent->GetBox().GetSize().y; + } + + float relative_bar_length; + if (track_length <= 0) + relative_bar_length = 1; + else if (bar_length <= 0) + relative_bar_length = 0; + else + relative_bar_length = bar_length / track_length; + + Vector2f track_size = track->GetBox().GetSize(); + + if (orientation == VERTICAL) + { + float track_length = track_size.y - bar_box.GetSizeAcross(BoxDirection::Vertical, BoxArea::Margin, BoxArea::Padding); + + if (height.type == height.Auto) + { + bar_box_content.y = track_length * relative_bar_length; + + // Check for 'min-height' restrictions. + float min_track_length = ResolveValue(computed.min_height(), track_length); + bar_box_content.y = Math::Max(min_track_length, bar_box_content.y); + + // Check for 'max-height' restrictions. + float max_track_length = ResolveValue(computed.max_height(), track_length); + bar_box_content.y = Math::Min(max_track_length, bar_box_content.y); + } + + // Make sure we haven't gone further than we're allowed to (min-height may have made us too big). + bar_box_content.y = Math::Min(bar_box_content.y, track_length); + } + else + { + float track_length = track_size.x - bar_box.GetSizeAcross(BoxDirection::Horizontal, BoxArea::Margin, BoxArea::Padding); + + if (width.type == width.Auto) + { + bar_box_content.x = track_length * relative_bar_length; + + // Check for 'min-width' restrictions. + float min_track_length = ResolveValue(computed.min_width(), track_length); + bar_box_content.x = Math::Max(min_track_length, bar_box_content.x); + + // Check for 'max-width' restrictions. + float max_track_length = ResolveValue(computed.max_width(), track_length); + bar_box_content.x = Math::Min(max_track_length, bar_box_content.x); + } + + // Make sure we haven't gone further than we're allowed to (min-width may have made us too big). + bar_box_content.x = Math::Min(bar_box_content.x, track_length); + } + + // Set the new dimensions on the bar to re-decorate it. + bar_box.SetContent(bar_box_content.Round()); + bar->SetBox(bar_box); + + // Now that it's been resized, re-position it. + PositionBar(); +} + +void WidgetScroll::ProcessEvent(Event& event) +{ + if (event.GetTargetElement() == bar) + { + if (event == EventId::Drag) + { + float new_bar_position = 0.f; + if (orientation == HORIZONTAL) + { + float traversable_track_length = track->GetBox().GetSize().x - bar->GetBox().GetSize().x; + if (traversable_track_length > 0) + { + float traversable_track_origin = track->GetAbsoluteOffset().x + bar_drag_anchor; + new_bar_position = (event.GetParameter("mouse_x", 0.f) - traversable_track_origin) / traversable_track_length; + } + } + else + { + float traversable_track_length = track->GetBox().GetSize().y - bar->GetBox().GetSize().y; + if (traversable_track_length > 0) + { + float traversable_track_origin = track->GetAbsoluteOffset().y + bar_drag_anchor; + new_bar_position = (event.GetParameter("mouse_y", 0.f) - traversable_track_origin) / traversable_track_length; + } + } + + SetBarPosition(new_bar_position); + Scroll(0.f, ScrollBehavior::Instant); + } + else if (event == EventId::Dragstart) + { + if (orientation == HORIZONTAL) + bar_drag_anchor = event.GetParameter("mouse_x", 0.f) - bar->GetAbsoluteOffset().x; + else + bar_drag_anchor = event.GetParameter("mouse_y", 0.f) - bar->GetAbsoluteOffset().y; + } + } + else if (event.GetTargetElement() == track) + { + if (event == EventId::Click) + { + float click_position = 0.f; + if (orientation == HORIZONTAL) + { + float mouse_position = event.GetParameter("mouse_x", 0.f); + click_position = (mouse_position - track->GetAbsoluteOffset().x) / track->GetBox().GetSize().x; + } + else + { + float mouse_position = event.GetParameter("mouse_y", 0); + click_position = (mouse_position - track->GetAbsoluteOffset().y) / track->GetBox().GetSize().y; + } + + if (click_position <= bar_position) + ScrollPageUp(); + else + ScrollPageDown(); + } + } + + if (event == EventId::Mousedown) + { + if (event.GetTargetElement() == arrows[0]) + { + arrow_timers[0] = DEFAULT_REPEAT_DELAY; + last_update_time = Clock::GetElapsedTime(); + ScrollLineUp(); + } + else if (event.GetTargetElement() == arrows[1]) + { + arrow_timers[1] = DEFAULT_REPEAT_DELAY; + last_update_time = Clock::GetElapsedTime(); + ScrollLineDown(); + } + } + else if (event == EventId::Mouseup || event == EventId::Mouseout) + { + if (event.GetTargetElement() == arrows[0]) + arrow_timers[0] = -1; + else if (event.GetTargetElement() == arrows[1]) + arrow_timers[1] = -1; + } +} + +void WidgetScroll::PositionBar() +{ + const Vector2f track_dimensions = track->GetBox().GetSize(); + const Vector2f bar_dimensions = bar->GetBox().GetSize(BoxArea::Border); + + if (orientation == VERTICAL) + { + const float traversable_track_length = track_dimensions.y - bar_dimensions.y; + const Vector2f offset = { + bar->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Left), + track->GetRelativeOffset().y + traversable_track_length * bar_position, + }; + bar->SetOffset(offset.Round(), parent); + } + else + { + const float traversable_track_length = track_dimensions.x - bar_dimensions.x; + const Vector2f offset = { + track->GetRelativeOffset().x + traversable_track_length * bar_position, + bar->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Top), + }; + bar->SetOffset(offset.Round(), parent); + } +} + +void WidgetScroll::SetTrackLength(float _track_length) +{ + track_length = _track_length; +} + +void WidgetScroll::SetBarLength(float _bar_length) +{ + bar_length = _bar_length; +} + +void WidgetScroll::ScrollLineDown() +{ + Scroll(SCROLL_LINE_LENGTH * ElementUtilities::GetDensityIndependentPixelRatio(parent), ScrollBehavior::Auto); +} + +void WidgetScroll::ScrollLineUp() +{ + Scroll(-SCROLL_LINE_LENGTH * ElementUtilities::GetDensityIndependentPixelRatio(parent), ScrollBehavior::Auto); +} + +void WidgetScroll::ScrollPageDown() +{ + Scroll(SCROLL_PAGE_FACTOR * bar_length, ScrollBehavior::Auto); +} + +void WidgetScroll::ScrollPageUp() +{ + Scroll(-SCROLL_PAGE_FACTOR * bar_length, ScrollBehavior::Auto); +} + +void WidgetScroll::Scroll(float distance, ScrollBehavior behavior) +{ + float traversable_track_length = (track_length - bar_length); + + float new_bar_position = bar_position; + if (traversable_track_length > 0.f) + new_bar_position = Math::Clamp((bar_position * traversable_track_length + distance) / traversable_track_length, 0.f, 1.f); + + // 'parent' is the scrollbar element, its parent again is the actual element we want to scroll + Element* element_scroll = parent->GetParentNode(); + if (!element_scroll) + { + RMLUI_ERROR; + return; + } + + Vector2f scroll_offset = {element_scroll->GetScrollLeft(), element_scroll->GetScrollTop()}; + if (orientation == HORIZONTAL) + scroll_offset.x = new_bar_position * (element_scroll->GetScrollWidth() - element_scroll->GetClientWidth()); + else + scroll_offset.y = new_bar_position * (element_scroll->GetScrollHeight() - element_scroll->GetClientHeight()); + + element_scroll->ScrollTo(scroll_offset, behavior); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/WidgetScroll.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/WidgetScroll.h new file mode 100644 index 00000000..5d492193 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/WidgetScroll.h @@ -0,0 +1,99 @@ +#pragma once + +#include "../../Include/RmlUi/Core/EventListener.h" + +namespace Rml { + +class Element; +enum class ScrollBehavior; + +/** + A widget for incorporating scrolling functionality into an element. + */ + +class WidgetScroll final : public EventListener { +public: + enum Orientation { UNKNOWN, VERTICAL, HORIZONTAL }; + + WidgetScroll(Element* parent); + virtual ~WidgetScroll(); + + /// Initialises the slider to a given orientation. + bool Initialise(Orientation orientation); + + /// Updates the key repeats for the increment / decrement arrows. + void Update(); + + /// Sets the position of the bar. + /// @param[in] bar_position The new position of the bar (0 representing the start of the track, 1 representing the end). + void SetBarPosition(float bar_position); + /// Returns the current position of the bar. + /// @return The current position of the bar (0 representing the start of the track, 1 representing the end). + float GetBarPosition() const; + + /// Returns the slider's orientation. + /// @return The orientation of the slider. + Orientation GetOrientation() const; + + /// Sets the dimensions to the size of the slider. + /// @param[in] dimensions The dimensions to size. + void GetDimensions(Vector2f& dimensions) const; + + /// Sets the length of the entire track in scrollable units (usually lines or characters). This affects the + /// length of the bar element and the speed of scrolling using the mouse-wheel or arrows. + /// @param[in] track_length The length of the track. + void SetTrackLength(float track_length); + /// Sets the length the bar represents in scrollable units (usually lines or characters), relative to the track + /// length. For example, for a scroll bar, this would represent how big each visible 'page' is compared to the + /// whole document (which would be set as the track length). + /// @param[in] bar_length The length of the slider's bar. + void SetBarLength(float bar_length); + + /// Lays out and resizes the internal elements. + /// @param[in] containing_block The padded box containing the slider. This is used to resolve relative properties. + /// @param[in] slider_length The total length, in pixels, of the slider widget. + void FormatElements(Vector2f containing_block, float slider_length); + +private: + /// Handles events coming through from the slider's components. + void ProcessEvent(Event& event) override; + + /// Lays out and positions the bar element. + void FormatBar(); + + // Set the offset on 'bar' based on its position. + void PositionBar(); + + void ScrollLineDown(); + void ScrollLineUp(); + void ScrollPageDown(); + void ScrollPageUp(); + + // Scrolls the parent element by the given distance. + void Scroll(float distance, ScrollBehavior behavior); + + Element* parent; + + Orientation orientation; + + // The background track element, across which the bar slides. + Element* track; + // The bar element. This is the element that is dragged across the trough. + Element* bar; + // The two (optional) buttons for incrementing and decrementing the slider. + Element* arrows[2]; + + // A number from 0 to 1, indicating how far along the track the bar is. + float bar_position; + // If the bar is being dragged, this is the pixel offset from the start of the bar to where it was picked up. + float bar_drag_anchor; + + // Set to the auto-repeat timer if either of the arrow buttons have been pressed, -1 if they haven't. + float arrow_timers[2]; + double last_update_time; + + float track_length; + float bar_length; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/XMLNodeHandler.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/XMLNodeHandler.cpp new file mode 100644 index 00000000..48be3f50 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/XMLNodeHandler.cpp @@ -0,0 +1,7 @@ +#include "../../Include/RmlUi/Core/XMLNodeHandler.h" + +namespace Rml { + +XMLNodeHandler::~XMLNodeHandler() {} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/XMLNodeHandlerBody.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/XMLNodeHandlerBody.cpp new file mode 100644 index 00000000..33805422 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/XMLNodeHandlerBody.cpp @@ -0,0 +1,58 @@ +#include "XMLNodeHandlerBody.h" +#include "../../Include/RmlUi/Core/ElementDocument.h" +#include "../../Include/RmlUi/Core/Factory.h" +#include "../../Include/RmlUi/Core/XMLParser.h" +#include "XMLParseTools.h" + +namespace Rml { + +XMLNodeHandlerBody::XMLNodeHandlerBody() {} + +XMLNodeHandlerBody::~XMLNodeHandlerBody() {} + +Element* XMLNodeHandlerBody::ElementStart(XMLParser* parser, const String& /*name*/, const XMLAttributes& attributes) +{ + Element* element = parser->GetParseFrame()->element; + + // Apply any attributes to the document, but only if the current element is the root of the current document, + // which should only hold for body elements, but not for inline injected templates. + ElementDocument* document = parser->GetParseFrame()->element->GetOwnerDocument(); + if (document && document == element) + { + for (auto& pair : attributes) + { + Variant* attribute = document->GetAttribute(pair.first); + if (attribute && *attribute != pair.second && pair.first != "template") + { + Log::Message(Log::LT_WARNING, "Overriding attribute '%s' in element %s during template injection.", pair.first.c_str(), + element->GetAddress().c_str()); + } + } + + document->SetAttributes(attributes); + } + + // Check for and apply any template + String template_name = Get(attributes, "template", ""); + if (!template_name.empty()) + { + element = XMLParseTools::ParseTemplate(element, template_name); + } + + // Tell the parser to use the element handler for all children + parser->PushDefaultHandler(); + + return element; +} + +bool XMLNodeHandlerBody::ElementEnd(XMLParser* /*parser*/, const String& /*name*/) +{ + return true; +} + +bool XMLNodeHandlerBody::ElementData(XMLParser* parser, const String& data, XMLDataType /*type*/) +{ + return Factory::InstanceElementText(parser->GetParseFrame()->element, data); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/XMLNodeHandlerBody.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/XMLNodeHandlerBody.h new file mode 100644 index 00000000..ded1d503 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/XMLNodeHandlerBody.h @@ -0,0 +1,24 @@ +#pragma once + +#include "../../Include/RmlUi/Core/XMLNodeHandler.h" + +namespace Rml { + +/** + Element Node handler that processes the HEAD tag + */ + +class XMLNodeHandlerBody : public XMLNodeHandler { +public: + XMLNodeHandlerBody(); + ~XMLNodeHandlerBody(); + + /// Called when a new element start is opened + Element* ElementStart(XMLParser* parser, const String& name, const XMLAttributes& attributes) override; + /// Called when an element is closed + bool ElementEnd(XMLParser* parser, const String& name) override; + /// Called for element data + bool ElementData(XMLParser* parser, const String& data, XMLDataType type) override; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/XMLNodeHandlerDefault.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/XMLNodeHandlerDefault.cpp new file mode 100644 index 00000000..82dfdf1d --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/XMLNodeHandlerDefault.cpp @@ -0,0 +1,61 @@ +#include "XMLNodeHandlerDefault.h" +#include "../../Include/RmlUi/Core/Element.h" +#include "../../Include/RmlUi/Core/ElementUtilities.h" +#include "../../Include/RmlUi/Core/Factory.h" +#include "../../Include/RmlUi/Core/Log.h" +#include "../../Include/RmlUi/Core/Profiling.h" +#include "../../Include/RmlUi/Core/XMLParser.h" +#include "XMLParseTools.h" + +namespace Rml { + +XMLNodeHandlerDefault::XMLNodeHandlerDefault() {} + +XMLNodeHandlerDefault::~XMLNodeHandlerDefault() {} + +Element* XMLNodeHandlerDefault::ElementStart(XMLParser* parser, const String& name, const XMLAttributes& attributes) +{ + RMLUI_ZoneScopedC(0x556B2F); + + // Determine the parent + Element* parent = parser->GetParseFrame()->element; + + // Attempt to instance the element with the instancer + ElementPtr element = Factory::InstanceElement(parent, name, name, attributes); + if (!element) + { + Log::Message(Log::LT_ERROR, "Failed to create element for tag %s, instancer returned nullptr.", name.c_str()); + return nullptr; + } + + // Move and append the element to the parent + Element* result = parent->AppendChild(std::move(element)); + + return result; +} + +bool XMLNodeHandlerDefault::ElementEnd(XMLParser* /*parser*/, const String& /*name*/) +{ + return true; +} + +bool XMLNodeHandlerDefault::ElementData(XMLParser* parser, const String& data, XMLDataType type) +{ + RMLUI_ZoneScopedC(0x006400); + + // Determine the parent + Element* parent = parser->GetParseFrame()->element; + RMLUI_ASSERT(parent); + + if (type == XMLDataType::InnerXML) + { + // Structural data views use the raw inner xml contents of the node, store them as an attribute to be processed by the data view. + parent->SetAttribute("rmlui-inner-rml", data); + return true; + } + + // Parse the text into the element + return Factory::InstanceElementText(parent, data); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/XMLNodeHandlerDefault.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/XMLNodeHandlerDefault.h new file mode 100644 index 00000000..361c9d06 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/XMLNodeHandlerDefault.h @@ -0,0 +1,25 @@ +#pragma once + +#include "../../Include/RmlUi/Core/Types.h" +#include "../../Include/RmlUi/Core/XMLNodeHandler.h" + +namespace Rml { + +/** + Element Node handler that creates elements + */ + +class XMLNodeHandlerDefault : public XMLNodeHandler { +public: + XMLNodeHandlerDefault(); + ~XMLNodeHandlerDefault(); + + /// Called when a new element start is opened + Element* ElementStart(XMLParser* parser, const String& name, const XMLAttributes& attributes) override; + /// Called when an element is closed + bool ElementEnd(XMLParser* parser, const String& name) override; + /// Called for element data + bool ElementData(XMLParser* parser, const String& data, XMLDataType type) override; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/XMLNodeHandlerHead.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/XMLNodeHandlerHead.cpp new file mode 100644 index 00000000..d35bd0a0 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/XMLNodeHandlerHead.cpp @@ -0,0 +1,141 @@ +#include "XMLNodeHandlerHead.h" +#include "../../Include/RmlUi/Core/Core.h" +#include "../../Include/RmlUi/Core/Element.h" +#include "../../Include/RmlUi/Core/ElementDocument.h" +#include "../../Include/RmlUi/Core/StringUtilities.h" +#include "../../Include/RmlUi/Core/SystemInterface.h" +#include "../../Include/RmlUi/Core/URL.h" +#include "../../Include/RmlUi/Core/XMLParser.h" +#include "DocumentHeader.h" +#include "TemplateCache.h" + +namespace Rml { + +static String Absolutepath(const String& source, const String& base) +{ + String joined_path; + ::Rml::GetSystemInterface()->JoinPath(joined_path, StringUtilities::Replace(base, '|', ':'), StringUtilities::Replace(source, '|', ':')); + return StringUtilities::Replace(joined_path, ':', '|'); +} + +static DocumentHeader::Resource MakeInlineResource(XMLParser* parser, const String& data) +{ + DocumentHeader::Resource resource; + resource.is_inline = true; + resource.content = data; + resource.path = parser->GetSourceURL().GetURL(); + resource.line = parser->GetLineNumberOpenTag(); + return resource; +} + +static DocumentHeader::Resource MakeExternalResource(XMLParser* parser, const String& path) +{ + DocumentHeader::Resource resource; + resource.is_inline = false; + resource.path = Absolutepath(path, parser->GetSourceURL().GetURL()); + return resource; +} + +XMLNodeHandlerHead::XMLNodeHandlerHead() {} + +XMLNodeHandlerHead::~XMLNodeHandlerHead() {} + +Element* XMLNodeHandlerHead::ElementStart(XMLParser* parser, const String& name, const XMLAttributes& attributes) +{ + if (name == "head") + { + // Process the head attribute + parser->GetDocumentHeader()->source = parser->GetSourceURL().GetURL(); + } + + // Is it a link tag? + else if (name == "link") + { + // Lookup the type and href + String type = StringUtilities::ToLower(Get(attributes, "type", "")); + String href = Get(attributes, "href", ""); + + if (!type.empty() && !href.empty()) + { + // If its RCSS (... or CSS!), add to the RCSS fields. + if (type == "text/rcss" || type == "text/css") + { + parser->GetDocumentHeader()->rcss.push_back(MakeExternalResource(parser, href)); + } + + // If its an template, add to the template fields + else if (type == "text/template") + { + parser->GetDocumentHeader()->template_resources.push_back(href); + } + + else + { + Log::ParseError(parser->GetSourceURL().GetURL(), parser->GetLineNumber(), "Invalid link type '%s'", type.c_str()); + } + } + else + { + Log::ParseError(parser->GetSourceURL().GetURL(), parser->GetLineNumber(), "Link tag requires type and href attributes"); + } + } + + // Process script tags + else if (name == "script") + { + // Check if its an external string + String src = Get(attributes, "src", ""); + if (src.size() > 0) + { + parser->GetDocumentHeader()->scripts.push_back(MakeExternalResource(parser, src)); + } + } + + // No elements constructed + return nullptr; +} + +bool XMLNodeHandlerHead::ElementEnd(XMLParser* parser, const String& name) +{ + // When the head tag closes, inject the header into the active document + if (name == "head") + { + Element* element = parser->GetParseFrame()->element; + if (!element) + return true; + + ElementDocument* document = element->GetOwnerDocument(); + if (document) + document->ProcessHeader(parser->GetDocumentHeader()); + } + return true; +} + +bool XMLNodeHandlerHead::ElementData(XMLParser* parser, const String& data, XMLDataType /*type*/) +{ + const String& tag = parser->GetParseFrame()->tag; + + // Store the title + if (tag == "title") + { + SystemInterface* system_interface = GetSystemInterface(); + if (system_interface != nullptr) + system_interface->TranslateString(parser->GetDocumentHeader()->title, data); + } + + // Store an inline script + if (tag == "script" && data.size() > 0) + { + parser->GetDocumentHeader()->scripts.push_back(MakeInlineResource(parser, data)); + } + + // Store an inline style + if (tag == "style" && data.size() > 0) + { + parser->GetDocumentHeader()->rcss.push_back(MakeInlineResource(parser, data)); + } + + return true; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/XMLNodeHandlerHead.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/XMLNodeHandlerHead.h new file mode 100644 index 00000000..52acaff5 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/XMLNodeHandlerHead.h @@ -0,0 +1,24 @@ +#pragma once + +#include "../../Include/RmlUi/Core/XMLNodeHandler.h" + +namespace Rml { + +/** + Element Node handler that processes the HEAD tag + */ + +class XMLNodeHandlerHead : public XMLNodeHandler { +public: + XMLNodeHandlerHead(); + ~XMLNodeHandlerHead(); + + /// Called when a new element start is opened + Element* ElementStart(XMLParser* parser, const String& name, const XMLAttributes& attributes) override; + /// Called when an element is closed + bool ElementEnd(XMLParser* parser, const String& name) override; + /// Called for element data + bool ElementData(XMLParser* parser, const String& data, XMLDataType type) override; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/XMLNodeHandlerTemplate.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/XMLNodeHandlerTemplate.cpp new file mode 100644 index 00000000..e14bc478 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/XMLNodeHandlerTemplate.cpp @@ -0,0 +1,48 @@ +#include "XMLNodeHandlerTemplate.h" +#include "../../Include/RmlUi/Core/Dictionary.h" +#include "../../Include/RmlUi/Core/Element.h" +#include "../../Include/RmlUi/Core/Factory.h" +#include "../../Include/RmlUi/Core/XMLParser.h" +#include "Template.h" +#include "TemplateCache.h" +#include "XMLParseTools.h" + +namespace Rml { + +XMLNodeHandlerTemplate::XMLNodeHandlerTemplate() {} + +XMLNodeHandlerTemplate::~XMLNodeHandlerTemplate() {} + +Element* XMLNodeHandlerTemplate::ElementStart(XMLParser* parser, const String& name, const XMLAttributes& attributes) +{ + RMLUI_ASSERT(name == "template"); + (void)name; + + // Tell the parser to use the default handler for all child nodes + parser->PushDefaultHandler(); + + const String template_name = Get(attributes, "src", ""); + Element* element = parser->GetParseFrame()->element; + + if (template_name.empty()) + { + Log::Message(Log::LT_WARNING, + "Inline template injection requires the 'src' attribute with the target template name, but none provided. In element %s", + element->GetAddress().c_str()); + return element; + } + + return XMLParseTools::ParseTemplate(element, template_name); +} + +bool XMLNodeHandlerTemplate::ElementEnd(XMLParser* /*parser*/, const String& /*name*/) +{ + return true; +} + +bool XMLNodeHandlerTemplate::ElementData(XMLParser* parser, const String& data, XMLDataType /*type*/) +{ + return Factory::InstanceElementText(parser->GetParseFrame()->element, data); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/XMLNodeHandlerTemplate.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/XMLNodeHandlerTemplate.h new file mode 100644 index 00000000..929ef69a --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/XMLNodeHandlerTemplate.h @@ -0,0 +1,24 @@ +#pragma once + +#include "../../Include/RmlUi/Core/XMLNodeHandler.h" + +namespace Rml { + +/** + Element Node handler that processes the custom template tags + */ + +class XMLNodeHandlerTemplate : public XMLNodeHandler { +public: + XMLNodeHandlerTemplate(); + ~XMLNodeHandlerTemplate(); + + /// Called when a new element start is opened + Element* ElementStart(XMLParser* parser, const String& name, const XMLAttributes& attributes) override; + /// Called when an element is closed + bool ElementEnd(XMLParser* parser, const String& name) override; + /// Called for element data + bool ElementData(XMLParser* parser, const String& data, XMLDataType type) override; +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/XMLParseTools.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/XMLParseTools.cpp new file mode 100644 index 00000000..b1d37e6b --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/XMLParseTools.cpp @@ -0,0 +1,165 @@ +#include "XMLParseTools.h" +#include "../../Include/RmlUi/Core/ElementDocument.h" +#include "../../Include/RmlUi/Core/StreamMemory.h" +#include "../../Include/RmlUi/Core/StringUtilities.h" +#include "../../Include/RmlUi/Core/Types.h" +#include "Template.h" +#include "TemplateCache.h" +#include +#include + +namespace Rml { + +const char* XMLParseTools::FindTag(const char* tag, const char* string, bool closing_tag) +{ + const size_t length = strlen(tag); + const char* ptr = string; + bool found_closing = false; + + while (*ptr) + { + // Check if the first character matches + if (tolower((*ptr)) == tag[0]) + { + // If it does, check the whole word + if (StringUtilities::StringCompareCaseInsensitive(StringView(ptr, ptr + length), StringView(tag, tag + length))) + { + // Check for opening <, loop back in the string skipping white space and forward slashes if + // we're looking for the closing tag + const char* tag_start = ptr - 1; + while (tag_start > string && (StringUtilities::IsWhitespace(*tag_start) || *tag_start == '/')) + { + if (*tag_start == '/') + found_closing = true; + tag_start--; + } + + // If the character we're looking at is a <, and found closing matches closing tag, + // its the tag we're looking for + if (*tag_start == '<' && found_closing == closing_tag) + return tag_start; + + // Otherwise, keep looking + } + } + ptr++; + } + + return nullptr; +} + +bool XMLParseTools::ReadAttribute(const char*& string, String& name, String& value) +{ + const char* ptr = string; + + name = ""; + value = ""; + + // Skip whitespace + while (StringUtilities::IsWhitespace(*ptr)) + ptr++; + + // Look for the end of the attribute name + bool found_whitespace = false; + while (*ptr != '=' && *ptr != '>' && (!found_whitespace || StringUtilities::IsWhitespace(*ptr))) + { + if (StringUtilities::IsWhitespace(*ptr)) + found_whitespace = true; + else + name += *ptr; + ptr++; + } + if (*ptr == '>') + return false; + + // If we stopped on an equals, parse the value + if (*ptr == '=') + { + // Skip over white space, ='s and quotes + bool quoted = false; + while (StringUtilities::IsWhitespace(*ptr) || *ptr == '\'' || *ptr == '"' || *ptr == '=') + { + if (*ptr == '\'' || *ptr == '"') + quoted = true; + ptr++; + } + if (*ptr == '>') + return false; + + // Store the value + while (*ptr != '\'' && *ptr != '"' && *ptr != '>' && (*ptr != ' ' || quoted)) + { + value += *ptr++; + } + if (*ptr == '>') + return false; + + // Advance passed the quote + if (quoted) + ptr++; + } + else + { + ptr--; + } + + // Update the string pointer + string = ptr; + + return true; +} + +Element* XMLParseTools::ParseTemplate(Element* element, const String& template_name) +{ + // Load the template, and parse it + Template* parse_template = TemplateCache::GetTemplate(template_name); + if (!parse_template) + { + Log::ParseError(element->GetOwnerDocument()->GetSourceURL(), -1, "Failed to find template '%s'.", template_name.c_str()); + return element; + } + + return parse_template->ParseTemplate(element); +} + +const char* XMLParseTools::ParseDataBrackets(bool& inside_brackets, bool& inside_string, char c, char previous) +{ + if (inside_brackets) + { + if (c == '\'') + inside_string = !inside_string; + + if (!inside_string) + { + if (c == '}' && previous == '}') + inside_brackets = false; + + else if (c == '{' && previous == '{') + return "Nested double curly brackets are illegal."; + + else if (previous == '}' && c != '}' && c != '\'') + return "Single closing curly bracket encountered, use double curly brackets to close an expression."; + + else if (previous == '/' && c == '>') + return "Closing double curly brackets not found, XML end node encountered first."; + + else if (previous == '<' && c == '/') + return "Closing double curly brackets not found, XML end node encountered first."; + } + } + else + { + if (c == '{' && previous == '{') + { + inside_brackets = true; + } + else if (c == '}' && previous == '}') + { + return "Closing double curly brackets encountered outside an expression."; + } + } + + return nullptr; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/XMLParseTools.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/XMLParseTools.h new file mode 100644 index 00000000..cba4407b --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/XMLParseTools.h @@ -0,0 +1,40 @@ +#pragma once + +#include "../../Include/RmlUi/Core/Types.h" + +namespace Rml { + +class Element; + +/** + Tools for aiding in parsing XML documents. + */ + +class XMLParseTools { +public: + /// Searchs a string for the specified tag + /// @param tag Tag to find, *must* be in lower case + /// @param string String to search + /// @param closing_tag Is it the closing tag we're looking for? + static const char* FindTag(const char* tag, const char* string, bool closing_tag = false); + /// Reads the next attribute from the string, advancing the pointer + /// @param[in,out] string String to read the attribute from, pointer will be advanced passed the read + /// @param[out] name Name of the attribute read + /// @param[out] value Value of the attribute read + static bool ReadAttribute(const char*& string, String& name, String& value); + + /// Applies the named template to the specified element + /// @param element Element to apply the template to + /// @param template_name Name of the template to apply, in TEMPLATE:ELEMENT_ID form + /// @returns Element to continue the parse from + static Element* ParseTemplate(Element* element, const String& template_name); + + /// Determine the presence of data expression brackets inside XML data. + /// Call this for each iteration through the data string. + /// 'inside_brackets' should be initialized to false. + /// 'inside_string' should be initialized to false. + /// Returns nullptr on success, or an error string on failure. + static const char* ParseDataBrackets(bool& inside_brackets, bool& inside_string, char c, char previous); +}; + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/XMLParser.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/XMLParser.cpp new file mode 100644 index 00000000..e4df9b75 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/XMLParser.cpp @@ -0,0 +1,191 @@ +#include "../../Include/RmlUi/Core/XMLParser.h" +#include "../../Include/RmlUi/Core/Element.h" +#include "../../Include/RmlUi/Core/Factory.h" +#include "../../Include/RmlUi/Core/Log.h" +#include "../../Include/RmlUi/Core/Profiling.h" +#include "../../Include/RmlUi/Core/Stream.h" +#include "../../Include/RmlUi/Core/Types.h" +#include "../../Include/RmlUi/Core/URL.h" +#include "../../Include/RmlUi/Core/XMLNodeHandler.h" +#include "ControlledLifetimeResource.h" +#include "DocumentHeader.h" + +namespace Rml { + +struct XmlParserData { + UnorderedMap> node_handlers; + UnorderedSet cdata_tags; + SharedPtr default_node_handler; +}; + +static ControlledLifetimeResource xml_parser_data; + +XMLParser::XMLParser(Element* root) +{ + for (const String& cdata_tag : xml_parser_data->cdata_tags) + RegisterCDATATag(cdata_tag); + + for (const String& name : Factory::GetStructuralDataViewAttributeNames()) + RegisterInnerXMLAttribute(name); + + // Add the first frame. + ParseFrame frame; + frame.element = root; + if (root != nullptr) + { + frame.tag = root->GetTagName(); + auto itr = xml_parser_data->node_handlers.find(root->GetTagName()); + if (itr != xml_parser_data->node_handlers.end()) + { + frame.node_handler = itr->second.get(); + } + else + { + frame.node_handler = xml_parser_data->default_node_handler.get(); + } + } + stack.push(frame); + + active_handler = nullptr; + + header = MakeUnique(); +} + +XMLParser::~XMLParser() {} + +void XMLParser::RegisterPersistentCDATATag(const String& _tag) +{ + if (!xml_parser_data) + xml_parser_data.Initialize(); + + String tag = StringUtilities::ToLower(_tag); + + if (tag.empty()) + return; + + xml_parser_data->cdata_tags.insert(tag); +} + +XMLNodeHandler* XMLParser::RegisterNodeHandler(const String& _tag, SharedPtr handler) +{ + if (!xml_parser_data) + xml_parser_data.Initialize(); + + String tag = StringUtilities::ToLower(_tag); + + // Check for a default node registration. + if (tag.empty()) + { + xml_parser_data->default_node_handler = std::move(handler); + return xml_parser_data->default_node_handler.get(); + } + + XMLNodeHandler* result = handler.get(); + xml_parser_data->node_handlers[tag] = std::move(handler); + return result; +} + +XMLNodeHandler* XMLParser::GetNodeHandler(const String& tag) +{ + auto it = xml_parser_data->node_handlers.find(tag); + if (it != xml_parser_data->node_handlers.end()) + return it->second.get(); + + return nullptr; +} + +void XMLParser::ReleaseHandlers() +{ + xml_parser_data.Shutdown(); +} + +DocumentHeader* XMLParser::GetDocumentHeader() +{ + return header.get(); +} + +void XMLParser::PushDefaultHandler() +{ + active_handler = xml_parser_data->default_node_handler.get(); +} + +bool XMLParser::PushHandler(const String& tag) +{ + auto it = xml_parser_data->node_handlers.find(StringUtilities::ToLower(tag)); + if (it == xml_parser_data->node_handlers.end()) + return false; + + active_handler = it->second.get(); + return true; +} + +const XMLParser::ParseFrame* XMLParser::GetParseFrame() const +{ + return &stack.top(); +} + +const URL& XMLParser::GetSourceURL() const +{ + RMLUI_ASSERT(GetSourceURLPtr()); + return *GetSourceURLPtr(); +} + +void XMLParser::HandleElementStart(const String& _name, const XMLAttributes& attributes) +{ + RMLUI_ZoneScoped; + const String name = StringUtilities::ToLower(_name); + + // Check for a specific handler that will override the child handler. + auto itr = xml_parser_data->node_handlers.find(name); + if (itr != xml_parser_data->node_handlers.end()) + active_handler = itr->second.get(); + + // Store the current active handler, so we can use it through this function (as active handler may change) + XMLNodeHandler* node_handler = active_handler; + + Element* element = nullptr; + + // Get the handler to handle the open tag + if (node_handler) + { + element = node_handler->ElementStart(this, name, attributes); + } + + // Push onto the stack + ParseFrame frame; + frame.node_handler = node_handler; + frame.child_handler = active_handler; + frame.element = (element ? element : stack.top().element); + frame.tag = name; + stack.push(frame); +} + +void XMLParser::HandleElementEnd(const String& _name) +{ + RMLUI_ASSERT(stack.size() >= 1); + RMLUI_ZoneScoped; + String name = StringUtilities::ToLower(_name); + + ParseFrame frame = std::move(stack.top()); + stack.pop(); + + active_handler = stack.top().child_handler; + + if (name != frame.tag) + { + Log::Message(Log::LT_ERROR, "Closing tag '%s' mismatched on %s:%d was expecting '%s'.", name.c_str(), GetSourceURL().GetURL().c_str(), + GetLineNumber(), frame.tag.c_str()); + } + + if (frame.node_handler) + frame.node_handler->ElementEnd(this, name); +} + +void XMLParser::HandleData(const String& data, XMLDataType type) +{ + RMLUI_ZoneScoped; + if (stack.top().node_handler) + stack.top().node_handler->ElementData(this, data, type); +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/precompiled.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/precompiled.h new file mode 100644 index 00000000..ad1418d6 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Core/precompiled.h @@ -0,0 +1,3 @@ +#pragma once + +#include "../../Include/RmlUi/Core.h" diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/.clang-format b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/.clang-format new file mode 100644 index 00000000..cbb10f54 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/.clang-format @@ -0,0 +1,2 @@ +BasedOnStyle: InheritParentConfig +NamespaceIndentation: None diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/BeaconSource.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/BeaconSource.h new file mode 100644 index 00000000..20495ac6 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/BeaconSource.h @@ -0,0 +1,27 @@ +static const char* beacon_rcss = R"RCSS( +body +{ + position: absolute; + top: 5px; + right: 33dp; + z-index: 1000000; + width: 20px; + font-family: rmlui-debugger-font; + font-size: 12dp; + color: black; + visibility: hidden; +} +button +{ + display: block; + width: 18dp; + height: 18dp; + text-align: center; + border-width: 1px; + font-weight: bold; +} +)RCSS"; + +static const char* beacon_rml = R"RML( + +)RML"; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/CMakeLists.txt b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/CMakeLists.txt new file mode 100644 index 00000000..4f3f08d9 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/CMakeLists.txt @@ -0,0 +1,71 @@ +add_library(rmlui_debugger + BeaconSource.h + CommonSource.h + Debugger.cpp + DebuggerPlugin.cpp + DebuggerPlugin.h + DebuggerSystemInterface.cpp + DebuggerSystemInterface.h + ElementContextHook.cpp + ElementContextHook.h + ElementDataModels.cpp + ElementDataModels.h + ElementDebugDocument.cpp + ElementDebugDocument.h + ElementInfo.cpp + ElementInfo.h + ElementLog.cpp + ElementLog.h + FontSource.h + Geometry.cpp + Geometry.h + InfoSource.h + LogSource.h + MenuSource.h +) + +set_common_target_options(rmlui_debugger) + +target_include_directories(rmlui_debugger PRIVATE "${PROJECT_SOURCE_DIR}/Include") +target_include_directories(rmlui_debugger INTERFACE + "$" + "$" +) + +# Add public headers as files in the project (it's not necessary but convenient for IDE integration) +# Setting them as PRIVATE so that it's addition doesn't propagate, it won't affect availability since +# the entire include directory has already been declared as public +target_sources(rmlui_debugger PRIVATE + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Debugger/Debugger.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Debugger/Header.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Debugger.h" +) + +target_link_libraries(rmlui_debugger PUBLIC rmlui_core) + +add_library(RmlUi::Debugger ALIAS rmlui_debugger) + +# Set up definitions to export functions and classes as appropriate +get_target_property(rmlui_debugger_TYPE rmlui_debugger "TYPE") +if(rmlui_debugger_TYPE STREQUAL "SHARED_LIBRARY") + # If RmlUi debugger is being compiled as a shared library, notify code to export functions using __dllspec() + # For applications consuming the library, the headers will automatically use dllimport + # Platform and compiler handling is already done by code via pre-processor macros + target_compile_definitions(rmlui_debugger PRIVATE "RMLUI_DEBUGGER_EXPORTS") +endif() + +# Set additional target properties +set_target_properties(rmlui_debugger PROPERTIES + # Add export name so that it can be exported with a namespaced name instead + # of using the name we actually used to declare the target + EXPORT_NAME "Debugger" +) + +install(TARGETS rmlui_debugger + EXPORT RmlUiTargets + ${RMLUI_RUNTIME_DEPENDENCY_SET_ARG} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} +) +install_target_pdb(rmlui_debugger) diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/CommonSource.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/CommonSource.h new file mode 100644 index 00000000..b9179834 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/CommonSource.h @@ -0,0 +1,169 @@ +static const char* common_rcss = R"RCSS( +body +{ + font-family: rmlui-debugger-font; + z-index: 1000000; + font-size: 13dp; + line-height: 1.4; + color: black; + padding-top: 30dp; +} +div, h1, h2, h3, h4, p +{ + display: block; +} +em +{ + font-style: italic; +} +h1 +{ + position: absolute; + top: 0; + right: 0; + left: 0; + height: 22dp; + padding: 4dp 5dp; + color: white; + background-color: #888; + font-size: 15dp; +} +h2 +{ + background-color: #ddd; + border-width: 1dp 0px; + border-color: #888; +} +h3 +{ + margin-top: 0.6em; + color: red; +} +h4 +{ + color: #cc0000; +} +handle#position_handle +{ + display: block; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; +} +h1 .button +{ + z-index: 1; +} +div#close_button +{ + margin-left: 10dp; + z-index: 1; + float: right; + width: 18dp; + color: black; + background-color: #ddd; + border-width: 1dp; + border-color: #666; + text-align: center; +} +div#close_button:hover +{ + background-color: #eee; +} +div#close_button:active +{ + background-color: #fff; +} +div#content +{ + position: relative; + width: auto; + height: 100%; + overflow: auto; + background: white; + border-width: 2dp; + border-color: #888; + border-top-width: 0; +} +.error +{ + background: #d24040; + color: white; + border-color: #b74e4e; +} +.warning +{ + background: #e8d34e; + color: black; + border-color: #ca9466; +} +.info +{ + background: #2a9cdb; + color: white; + border-color: #3b70bb; +} +.debug +{ + background: #3fab2a; + color: white; + border-color: #226c13; +} +scrollbarvertical +{ + width: 16dp; + scrollbar-margin: 16dp; +} +scrollbarhorizontal +{ + height: 16dp; + scrollbar-margin: 16dp; +} +scrollbarvertical slidertrack, +scrollbarhorizontal slidertrack +{ + background: #aaa; + border-color: #888; +} +scrollbarvertical slidertrack +{ + border-left-width: 1dp; +} +scrollbarhorizontal slidertrack +{ + height: 15dp; + border-top-width: 1dp; +} +scrollbarvertical sliderbar, +scrollbarhorizontal sliderbar +{ + background: #ddd; + border-color: #888; +} +scrollbarvertical sliderbar +{ + border-width: 1dp 0; + margin-left: 1dp; +} +scrollbarhorizontal sliderbar +{ + height: 15dp; + border-width: 0 1dp; + margin-top: 1dp; +} +scrollbarcorner +{ + background: #888; +} +handle#size_handle +{ + position: absolute; + width: 16dp; + height: 16dp; + bottom: 0dp; + right: 2dp; + background-color: #888; +} +)RCSS"; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/DataModelsSource.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/DataModelsSource.h new file mode 100644 index 00000000..562085fe --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/DataModelsSource.h @@ -0,0 +1,46 @@ +static const char* data_models_rcss = R"RCSS( +body { + width: 320dp; + min-width: 320dp; + margin-top: 52dp; + margin-right: 30dp; + margin-left: auto; +} +div#content { + height: auto; + min-height: 200dp; + max-height: 650dp; +} +div#content div h2 { + padding-left: 5dp; +} +div#content div div +{ + font-size: 12dp; + padding-left: 10dp; +} +div#content .name { + color: #610; +} +scrollbarvertical { + scrollbar-margin: 0px; +} +)RCSS"; + +static const char* data_models_rml = R"RML( +

    + +
    X
    +
    Data Models
    +

    +
    +
    +

    Data models

    +
    +
    +
    +

    Data variables

    +
    +
    +
    +)RML"; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/Debugger.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/Debugger.cpp new file mode 100644 index 00000000..580a8d68 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/Debugger.cpp @@ -0,0 +1,71 @@ +#include "../../Include/RmlUi/Debugger/Debugger.h" +#include "../../Include/RmlUi/Core/Core.h" +#include "DebuggerPlugin.h" + +namespace Rml { +namespace Debugger { + +bool Initialise(Context* context) +{ + if (DebuggerPlugin::GetInstance() != nullptr) + { + Log::Message(Log::LT_WARNING, "Unable to initialise debugger plugin, already initialised!"); + return false; + } + + DebuggerPlugin* plugin = new DebuggerPlugin(); + if (!plugin->Initialise(context)) + { + Log::Message(Log::LT_WARNING, "Unable to initialise debugger plugin."); + + delete plugin; + return false; + } + + SetContext(context); + RegisterPlugin(plugin); + + return true; +} + +void Shutdown() +{ + DebuggerPlugin* plugin = DebuggerPlugin::GetInstance(); + if (!plugin) + { + Log::Message(Log::LT_WARNING, "Unable to shutdown debugger plugin, it was not initialised!"); + return; + } + + UnregisterPlugin(plugin); +} + +bool SetContext(Context* context) +{ + DebuggerPlugin* plugin = DebuggerPlugin::GetInstance(); + if (plugin == nullptr) + return false; + + plugin->SetContext(context); + + return true; +} + +void SetVisible(bool visibility) +{ + DebuggerPlugin* plugin = DebuggerPlugin::GetInstance(); + if (plugin != nullptr) + plugin->SetVisible(visibility); +} + +bool IsVisible() +{ + DebuggerPlugin* plugin = DebuggerPlugin::GetInstance(); + if (plugin == nullptr) + return false; + + return plugin->IsVisible(); +} + +} // namespace Debugger +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/DebuggerPlugin.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/DebuggerPlugin.cpp new file mode 100644 index 00000000..07c45ccd --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/DebuggerPlugin.cpp @@ -0,0 +1,451 @@ +#include "DebuggerPlugin.h" +#include "../../Include/RmlUi/Core/Context.h" +#include "../../Include/RmlUi/Core/Core.h" +#include "../../Include/RmlUi/Core/ElementInstancer.h" +#include "../../Include/RmlUi/Core/ElementUtilities.h" +#include "../../Include/RmlUi/Core/Factory.h" +#include "../../Include/RmlUi/Core/Types.h" +#include "DebuggerSystemInterface.h" +#include "ElementContextHook.h" +#include "ElementDataModels.h" +#include "ElementDebugDocument.h" +#include "ElementInfo.h" +#include "ElementLog.h" +#include "FontSource.h" +#include "Geometry.h" +#include "MenuSource.h" +#include + +namespace Rml { +namespace Debugger { + +DebuggerPlugin* DebuggerPlugin::instance = nullptr; + +DebuggerPlugin::DebuggerPlugin() +{ + RMLUI_ASSERT(instance == nullptr); + instance = this; + host_context = nullptr; + debug_context = nullptr; + log_interface = nullptr; + + menu_element = nullptr; + info_element = nullptr; + log_element = nullptr; + data_explorer_element = nullptr; + hook_element = nullptr; + + render_outlines = false; + + application_interface = nullptr; +} + +DebuggerPlugin::~DebuggerPlugin() +{ + instance = nullptr; +} + +bool DebuggerPlugin::Initialise(Context* context) +{ + host_context = context; + Geometry::SetContext(context); + + if (!LoadFont()) + { + Log::Message(Log::LT_ERROR, "Failed to initialise debugger, unable to load font."); + return false; + } + + if (!LoadMenuElement() || !LoadInfoElement() || !LoadLogElement() || !LoadDataExplorerElement()) + { + Log::Message(Log::LT_ERROR, "Failed to initialise debugger, error while load debugger elements."); + return false; + } + + hook_element_instancer = MakeUnique>(); + Factory::RegisterElementInstancer("debug-hook", hook_element_instancer.get()); + + return true; +} + +bool DebuggerPlugin::SetContext(Context* context) +{ + if (debug_context && hook_element) + { + debug_context->UnloadDocument(hook_element); + hook_element = nullptr; + } + + if (context) + { + ElementDocument* element = context->CreateDocument("debug-hook"); + if (!element) + return false; + + RMLUI_ASSERT(!hook_element); + hook_element = rmlui_dynamic_cast(element); + if (!hook_element) + { + context->UnloadDocument(element); + return false; + } + + hook_element->Initialise(this); + } + + if (info_element) + { + SetupInfoListeners(context); + info_element->Reset(); + } + + if (data_explorer_element) + { + data_explorer_element->SetDebugContext(context); + } + + debug_context = context; + return true; +} + +void DebuggerPlugin::SetVisible(bool visibility) +{ + if (visibility) + menu_element->SetProperty(PropertyId::Visibility, Property(Style::Visibility::Visible)); + else + menu_element->SetProperty(PropertyId::Visibility, Property(Style::Visibility::Hidden)); +} + +bool DebuggerPlugin::IsVisible() +{ + return menu_element->IsVisible(); +} + +void DebuggerPlugin::Render() +{ + // Render the outlines of the debug context's elements. + if (render_outlines && debug_context) + { + for (int i = 0; i < debug_context->GetNumDocuments(); ++i) + { + ElementDocument* document = debug_context->GetDocument(i); + if (document->GetId().find("rmlui-debug-") == 0) + continue; + + Stack element_stack; + element_stack.push(document); + + while (!element_stack.empty()) + { + Element* element = element_stack.top(); + element_stack.pop(); + if (element->IsVisible()) + { + ElementUtilities::ApplyTransform(*element); + for (int j = 0; j < element->GetNumBoxes(); ++j) + { + const RenderBox box = element->GetRenderBox(BoxArea::Border, j); + Geometry::RenderOutline(element->GetAbsoluteOffset(BoxArea::Border) + box.GetBorderOffset(), box.GetFillSize(), + Colourb(255, 0, 0, 128), 1); + } + + for (int j = 0; j < element->GetNumChildren(); ++j) + element_stack.push(element->GetChild(j)); + } + } + } + } + + // Render the info element's boxes. + if (info_element && info_element->IsVisible()) + { + info_element->RenderHoverElement(); + info_element->RenderSourceElement(); + } +} + +void DebuggerPlugin::OnShutdown() +{ + // Release the elements before we leak track, this ensures the debugger hook has been cleared + // and that we don't try send the messages to the debug log window + ReleaseElements(); + + hook_element_instancer.reset(); + + delete this; +} + +void DebuggerPlugin::OnContextDestroy(Context* context) +{ + if (context == debug_context) + { + // The context we're debugging is being destroyed, so we need to remove our debug hook elements. + SetContext(nullptr); + } + + if (context == host_context) + { + // Our host is being destroyed, so we need to shut down the debugger. + + ReleaseElements(); + + Geometry::SetContext(nullptr); + host_context = nullptr; + } +} + +void DebuggerPlugin::OnElementDestroy(Element* element) +{ + // Detect external destruction of the debugger documents. This can happen for example if the user calls + // `Context::UnloadAllDocuments()` on the host context. + if (element == menu_element || element == info_element || element == log_element || element == data_explorer_element) + { + ReleaseElements(); + Log::Message(Log::LT_ERROR, + "A document owned by the Debugger plugin was destroyed externally. This is not allowed. Consider shutting down the debugger instead."); + } + + if (info_element) + info_element->OnElementDestroy(element); +} + +void DebuggerPlugin::ProcessEvent(Event& event) +{ + struct ButtonIdToDocumentMapping { + String id; + ElementDocument* document; + }; + const ButtonIdToDocumentMapping button_mappings[] = { + {"event-log-button", log_element}, + {"debug-info-button", info_element}, + {"data-models-button", data_explorer_element}, + }; + + if (event == EventId::Click) + { + for (const ButtonIdToDocumentMapping& button_mapping : button_mappings) + { + if (event.GetTargetElement()->GetId() == button_mapping.id) + { + if (button_mapping.document->IsVisible()) + button_mapping.document->Hide(); + else + button_mapping.document->Show(); + } + } + + if (event.GetTargetElement()->GetId() == "outlines-button") + { + render_outlines = !render_outlines; + event.GetTargetElement()->SetClass("open", render_outlines); + } + } + else if (event == EventId::Hide || event == EventId::Show) + { + for (const ButtonIdToDocumentMapping& button_mapping : button_mappings) + { + if (event.GetTargetElement() == button_mapping.document) + { + Element* button = menu_element->GetElementById(button_mapping.id); + const bool set_open = (event == EventId::Show); + button->SetClass("open", set_open); + } + } + } +} + +DebuggerPlugin* DebuggerPlugin::GetInstance() +{ + return instance; +} + +bool DebuggerPlugin::LoadFont() +{ + const String font_family_name = "rmlui-debugger-font"; + + return (LoadFontFace({courier_prime_code, sizeof(courier_prime_code)}, font_family_name, Style::FontStyle::Normal, Style::FontWeight::Normal) && + LoadFontFace({courier_prime_code_italic, sizeof(courier_prime_code_italic)}, font_family_name, Style::FontStyle::Italic, + Style::FontWeight::Normal)); +} + +bool DebuggerPlugin::LoadMenuElement() +{ + debug_document_instancer = MakeUnique>(); + Factory::RegisterElementInstancer("debug-document", debug_document_instancer.get()); + menu_element = host_context->CreateDocument("debug-document"); + if (!menu_element) + return false; + + menu_element->SetId("rmlui-debug-menu"); + menu_element->SetProperty(PropertyId::Visibility, Property(Style::Visibility::Hidden)); + menu_element->SetInnerRML(menu_rml); + + SharedPtr style_sheet = Factory::InstanceStyleSheetString(menu_rcss); + if (!style_sheet) + { + host_context->UnloadDocument(menu_element); + menu_element = nullptr; + return false; + } + + menu_element->SetStyleSheetContainer(std::move(style_sheet)); + + menu_element->GetElementById("version-number")->SetInnerRML(Rml::GetVersion()); + + for (auto* id : {"event-log-button", "debug-info-button", "outlines-button", "data-models-button"}) + { + Element* button = menu_element->GetElementById(id); + button->AddEventListener(EventId::Click, this); + } + + return true; +} + +bool DebuggerPlugin::LoadInfoElement() +{ + info_element_instancer = MakeUnique>(); + Factory::RegisterElementInstancer("debug-info", info_element_instancer.get()); + info_element = rmlui_dynamic_cast(host_context->CreateDocument("debug-info")); + if (!info_element) + return false; + + info_element->SetProperty(PropertyId::Visibility, Property(Style::Visibility::Hidden)); + + if (!info_element->Initialise()) + { + host_context->UnloadDocument(info_element); + info_element = nullptr; + return false; + } + + info_element->AddEventListener(EventId::Hide, this); + info_element->AddEventListener(EventId::Show, this); + + return true; +} + +bool DebuggerPlugin::LoadLogElement() +{ + log_element_instancer = MakeUnique>(); + Factory::RegisterElementInstancer("debug-log", log_element_instancer.get()); + log_element = rmlui_dynamic_cast(host_context->CreateDocument("debug-log")); + if (!log_element) + return false; + + log_element->SetProperty(PropertyId::Visibility, Property(Style::Visibility::Hidden)); + + if (!log_element->Initialise()) + { + host_context->UnloadDocument(log_element); + log_element = nullptr; + return false; + } + + log_element->AddEventListener(EventId::Hide, this); + log_element->AddEventListener(EventId::Show, this); + + // Make the system interface; this will trap the log messages for us. + application_interface = Rml::GetSystemInterface(); + log_interface = MakeUnique(application_interface, log_element); + Rml::SetSystemInterface(log_interface.get()); + + return true; +} + +bool DebuggerPlugin::LoadDataExplorerElement() +{ + data_explorer_element_instancer = MakeUnique>(); + Factory::RegisterElementInstancer("debug-data-models", data_explorer_element_instancer.get()); + data_explorer_element = rmlui_dynamic_cast(host_context->CreateDocument("debug-data-models")); + if (!data_explorer_element) + return false; + + data_explorer_element->SetProperty(PropertyId::Visibility, Property(Style::Visibility::Hidden)); + + if (!data_explorer_element->Initialise(debug_context)) + { + host_context->UnloadDocument(data_explorer_element); + data_explorer_element = nullptr; + return false; + } + + data_explorer_element->AddEventListener(EventId::Hide, this); + data_explorer_element->AddEventListener(EventId::Show, this); + + return true; +} + +void DebuggerPlugin::SetupInfoListeners(Rml::Context* new_context) +{ + RMLUI_ASSERT(info_element); + + if (debug_context) + { + debug_context->RemoveEventListener("click", info_element, true); + debug_context->RemoveEventListener("mouseover", info_element, true); + debug_context->RemoveEventListener("mouseout", info_element, true); + } + + if (new_context) + { + new_context->AddEventListener("click", info_element, true); + new_context->AddEventListener("mouseover", info_element, true); + new_context->AddEventListener("mouseout", info_element, true); + } +} + +void DebuggerPlugin::ReleaseElements() +{ + // Erase event listeners to prevent crashes. + if (info_element) + SetupInfoListeners(nullptr); + + if (host_context) + { + if (menu_element) + { + host_context->UnloadDocument(menu_element); + menu_element = nullptr; + } + + if (info_element) + { + host_context->UnloadDocument(info_element); + info_element = nullptr; + } + + if (log_element) + { + host_context->UnloadDocument(log_element); + log_element = nullptr; + Rml::SetSystemInterface(application_interface); + application_interface = nullptr; + log_interface.reset(); + } + if (data_explorer_element) + { + host_context->UnloadDocument(data_explorer_element); + data_explorer_element = nullptr; + } + + // Update to release documents before the plugin gets deleted. + // Helps avoid cleanup crashes. + host_context->Update(); + } + + if (debug_context) + { + if (hook_element) + { + debug_context->UnloadDocument(hook_element); + hook_element = nullptr; + } + + // Update to release documents before the plugin gets deleted. + // Helps avoid cleanup crashes. + debug_context->Update(); + } +} + +} // namespace Debugger +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/DebuggerPlugin.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/DebuggerPlugin.h new file mode 100644 index 00000000..a3911264 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/DebuggerPlugin.h @@ -0,0 +1,104 @@ +#pragma once + +#include "../../Include/RmlUi/Core/EventListener.h" +#include "../../Include/RmlUi/Core/Plugin.h" + +namespace Rml { + +class ElementDocument; +class SystemInterface; + +namespace Debugger { + +class ElementLog; +class ElementInfo; +class ElementContextHook; +class ElementDataModels; +class DebuggerSystemInterface; + +/** + RmlUi plugin interface for the debugger. + */ + +class DebuggerPlugin : public Rml::Plugin, public Rml::EventListener { +public: + DebuggerPlugin(); + ~DebuggerPlugin(); + + /// Initialises the debugging tools into the given context. + /// @param[in] context The context to load the tools into. + /// @return True on success, false if an error occured. + bool Initialise(Context* context); + + /// Sets the context to be debugged. + /// @param[in] context The context to be debugged. + /// @return True if the debugger is initialised and the context was switched, false otherwise. + bool SetContext(Context* context); + + /// Sets the visibility of the debugger. + /// @param[in] visibility True to show the debugger, false to hide it. + void SetVisible(bool visibility); + /// Returns the visibility of the debugger. + /// @return True if the debugger is visible, false if not. + bool IsVisible(); + + /// Renders any debug elements in the debug context. + void Render(); + + /// Called when RmlUi shuts down. + void OnShutdown() override; + + /// Called whenever a RmlUi context is destroyed. + /// @param[in] context The destroyed context. + void OnContextDestroy(Context* context) override; + + /// Called whenever an element is destroyed. + /// @param[in] element The destroyed element. + void OnElementDestroy(Element* element) override; + + /// Event handler for events from the debugger elements. + /// @param[in] event The event to process. + void ProcessEvent(Event& event) override; + + /// Access the singleton instance of the debugger + /// @return nullptr or an instance of the plugin + static DebuggerPlugin* GetInstance(); + +private: + bool LoadFont(); + bool LoadMenuElement(); + bool LoadInfoElement(); + bool LoadLogElement(); + bool LoadDataExplorerElement(); + + void SetupInfoListeners(Rml::Context* new_context); + + // Release all loaded elements + void ReleaseElements(); + + // The context hosting the debug documents. + Context* host_context; + // The context we're debugging. + Context* debug_context; + + // The debug elements. + ElementDocument* menu_element; + ElementInfo* info_element; + ElementLog* log_element; + ElementDataModels* data_explorer_element; + ElementContextHook* hook_element; + + Rml::SystemInterface* application_interface; + UniquePtr log_interface; + + UniquePtr hook_element_instancer, debug_document_instancer, info_element_instancer, log_element_instancer, + data_explorer_element_instancer; + + bool render_outlines; + + // Singleton instance + static DebuggerPlugin* instance; +}; + +} // namespace Debugger +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/DebuggerSystemInterface.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/DebuggerSystemInterface.cpp new file mode 100644 index 00000000..95470f90 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/DebuggerSystemInterface.cpp @@ -0,0 +1,66 @@ +#include "DebuggerSystemInterface.h" +#include "ElementLog.h" + +namespace Rml { +namespace Debugger { + +DebuggerSystemInterface::DebuggerSystemInterface(Rml::SystemInterface* _application_interface, ElementLog* _log) +{ + application_interface = _application_interface; + log = _log; +} + +DebuggerSystemInterface::~DebuggerSystemInterface() +{ + application_interface = nullptr; +} + +double DebuggerSystemInterface::GetElapsedTime() +{ + return application_interface->GetElapsedTime(); +} + +int DebuggerSystemInterface::TranslateString(String& translated, const String& input) +{ + return application_interface->TranslateString(translated, input); +} + +void DebuggerSystemInterface::JoinPath(String& translated_path, const String& document_path, const String& path) +{ + application_interface->JoinPath(translated_path, document_path, path); +} + +bool DebuggerSystemInterface::LogMessage(Log::Type type, const String& message) +{ + log->AddLogMessage(type, message); + + return application_interface->LogMessage(type, message); +} + +void DebuggerSystemInterface::SetMouseCursor(const String& cursor_name) +{ + application_interface->SetMouseCursor(cursor_name); +} + +void DebuggerSystemInterface::SetClipboardText(const String& text) +{ + application_interface->SetClipboardText(text); +} + +void DebuggerSystemInterface::GetClipboardText(String& text) +{ + application_interface->GetClipboardText(text); +} + +void DebuggerSystemInterface::ActivateKeyboard(Rml::Vector2f caret_position, float line_height) +{ + application_interface->ActivateKeyboard(caret_position, line_height); +} + +void DebuggerSystemInterface::DeactivateKeyboard() +{ + application_interface->DeactivateKeyboard(); +} + +} // namespace Debugger +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/DebuggerSystemInterface.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/DebuggerSystemInterface.h new file mode 100644 index 00000000..7d17abe4 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/DebuggerSystemInterface.h @@ -0,0 +1,68 @@ +#pragma once + +#include "../../Include/RmlUi/Core/SystemInterface.h" + +namespace Rml { +namespace Debugger { + +class ElementLog; + +/** + The log interface the debugger installs into RmlUi. This is a pass-through interface, so it holds onto the + application's system interface and passes all the calls through. + */ + +class DebuggerSystemInterface : public Rml::SystemInterface { +public: + /// Instances a new debugging log interface. + /// @param[in] log The logging element to send messages to. + DebuggerSystemInterface(Rml::SystemInterface* application_interface, ElementLog* log); + virtual ~DebuggerSystemInterface(); + + /// Get the number of seconds elapsed since the start of the application. + /// @return Elapsed time, in seconds. + double GetElapsedTime() override; + + /// Translate the input string into the translated string. + /// @param[out] translated Translated string ready for display. + /// @param[in] input String as received from XML. + /// @return Number of translations that occured. + int TranslateString(String& translated, const String& input) override; + + /// Joins the path of an RML or RCSS file with the path of a resource specified within the file. + /// @param[out] translated_path The joined path. + /// @param[in] document_path The path of the source document (including the file name). + /// @param[in] path The path of the resource specified in the document. + void JoinPath(String& translated_path, const String& document_path, const String& path) override; + + /// Log the specified message. + /// @param[in] type Type of log message, ERROR, WARNING, etc. + /// @param[in] message Message to log. + /// @return True to continue execution, false to break into the debugger. + bool LogMessage(Log::Type type, const String& message) override; + + /// Set mouse cursor. + /// @param[in] cursor_name Cursor name to activate. + void SetMouseCursor(const String& cursor_name) override; + + /// Set clipboard text. + /// @param[in] text Text to apply to clipboard. + void SetClipboardText(const String& text) override; + + /// Get clipboard text. + /// @param[out] text Retrieved text from clipboard. + void GetClipboardText(String& text) override; + + /// Activate keyboard (for touchscreen devices). + void ActivateKeyboard(Rml::Vector2f caret_position, float line_height) override; + + /// Deactivate keyboard (for touchscreen devices). + void DeactivateKeyboard() override; + +private: + Rml::SystemInterface* application_interface; + ElementLog* log; +}; + +} // namespace Debugger +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/ElementContextHook.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/ElementContextHook.cpp new file mode 100644 index 00000000..d567db6d --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/ElementContextHook.cpp @@ -0,0 +1,31 @@ +#include "ElementContextHook.h" +#include "DebuggerPlugin.h" + +namespace Rml { +namespace Debugger { + +ElementContextHook::ElementContextHook(const String& tag) : ElementDebugDocument(tag) +{ + debugger = nullptr; +} + +ElementContextHook::~ElementContextHook() {} + +void ElementContextHook::Initialise(DebuggerPlugin* _debugger) +{ + SetId("rmlui-debug-hook"); + SetProperty(PropertyId::ZIndex, Property(999'999, Unit::NUMBER)); + debugger = _debugger; +} + +void ElementContextHook::OnRender() +{ + // Make sure we're in the front of the render queue for this context (at least next frame). + PullToFront(); + + // Render the debugging elements. + debugger->Render(); +} + +} // namespace Debugger +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/ElementContextHook.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/ElementContextHook.h new file mode 100644 index 00000000..28b4e733 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/ElementContextHook.h @@ -0,0 +1,31 @@ +#pragma once + +#include "../../Include/RmlUi/Core/ElementDocument.h" +#include "ElementDebugDocument.h" + +namespace Rml { +namespace Debugger { + +class DebuggerPlugin; + +/** + An element that the debugger uses to render into a foreign context. + */ + +class ElementContextHook : public ElementDebugDocument { +public: + RMLUI_RTTI_DefineWithParent(ElementContextHook, ElementDebugDocument) + + ElementContextHook(const String& tag); + virtual ~ElementContextHook(); + + void Initialise(DebuggerPlugin* debugger); + + void OnRender() override; + +private: + DebuggerPlugin* debugger; +}; + +} // namespace Debugger +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/ElementDataModels.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/ElementDataModels.cpp new file mode 100644 index 00000000..317b7399 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/ElementDataModels.cpp @@ -0,0 +1,201 @@ +#include "ElementDataModels.h" +#include "../../Include/RmlUi/Core/Context.h" +#include "../../Include/RmlUi/Core/Core.h" +#include "../../Include/RmlUi/Core/DataModelHandle.h" +#include "../../Include/RmlUi/Core/DataVariable.h" +#include "../../Include/RmlUi/Core/StyleSheet.h" +#include "../../Include/RmlUi/Core/StyleSheetSpecification.h" +#include "../../Include/RmlUi/Core/SystemInterface.h" +#include "CommonSource.h" +#include "DataModelsSource.h" +#include + +namespace Rml { +namespace Debugger { + +static void ReadDataVariableRecursive(String& out_rml, const int indent_level, const DataVariable variable) +{ + constexpr int max_num_child_elements = 50; + + String indent_child_element; + for (int i = 0; i <= indent_level; i++) + indent_child_element += "  "; + + switch (variable.Type()) + { + case DataVariableType::Scalar: + { + Variant variant; + if (!variable.Get(variant)) + out_rml += "(unreadable)"; + else if (variant.GetType() == Variant::NONE) + out_rml += "(none)"; + else if (variant.GetType() == Variant::BOOL) + out_rml += variant.Get() ? "true" : "false"; + else + out_rml += StringUtilities::EncodeRml(variant.Get()); + out_rml += "
    "; + } + break; + case DataVariableType::Array: + { + const int size = variable.Size(); + out_rml += "Array (" + ToString(variable.Size()) + ")
    "; + for (int i = 0; i < size; i++) + { + if (i >= max_num_child_elements) + { + out_rml += indent_child_element + "[...]
    "; + break; + } + + out_rml += indent_child_element + "[" + ToString(i) + "]: "; + DataVariable child_variable = variable.Child(DataAddressEntry(i)); + ReadDataVariableRecursive(out_rml, indent_level + 1, child_variable); + } + } + break; + case DataVariableType::Struct: + { + const StringList members = Detail::DataVariableAccessor::GetDefinition(variable)->ReflectMemberNames(); + out_rml += "Struct (" + ToString(members.size()) + ")
    "; + for (const String& member_name : members) + { + if (std::distance(&members.front(), &member_name) >= max_num_child_elements) + { + out_rml += indent_child_element + "[...]
    "; + break; + } + + out_rml += indent_child_element + "." + member_name + ": "; + DataVariable child_variable = variable.Child(DataAddressEntry(member_name)); + ReadDataVariableRecursive(out_rml, indent_level + 1, child_variable); + } + } + break; + } +} + +ElementDataModels::ElementDataModels(const String& tag) : ElementDebugDocument(tag) {} + +ElementDataModels::~ElementDataModels() +{ + RemoveEventListener(EventId::Click, this); +} + +bool ElementDataModels::Initialise(Context* target_context) +{ + SetInnerRML(data_models_rml); + SetId("rmlui-debug-data-models"); + + AddEventListener(EventId::Click, this); + + SharedPtr style_sheet = Factory::InstanceStyleSheetString(String(common_rcss) + String(data_models_rcss)); + if (!style_sheet) + return false; + + SetStyleSheetContainer(std::move(style_sheet)); + + SetDebugContext(target_context); + + return true; +} + +void ElementDataModels::Reset() +{ + SetDebugContext(nullptr); +} + +void ElementDataModels::SetDebugContext(Context* new_debug_context) +{ + debug_context = new_debug_context; +} + +void ElementDataModels::OnUpdate() +{ + if (!IsVisible() || !debug_context) + return; + + const double t = GetSystemInterface()->GetElapsedTime(); + const float dt = (float)(t - previous_update_time); + + constexpr float update_interval = 0.3f; + + if (dt > update_interval) + { + previous_update_time = t; + UpdateContent(); + } +} + +void ElementDataModels::ProcessEvent(Event& event) +{ + if (!IsVisible()) + return; + + Element* target_element = event.GetTargetElement(); + if (target_element->GetOwnerDocument() != this) + return; + + if (event == EventId::Click) + { + const String& id = event.GetTargetElement()->GetId(); + + if (id == "close_button") + Hide(); + + event.StopPropagation(); + } +} + +void ElementDataModels::UpdateContent() +{ + RMLUI_ASSERT(debug_context); + Element* models_content_element = GetElementById("content"); + + SmallOrderedMap new_model_rml_map; + + const UnorderedMap data_models = debug_context->GetDataModels(); + for (const auto& name_model_pair : data_models) + { + const String& model_name = name_model_pair.first; + const DataModelConstructor& model_constructor = name_model_pair.second; + + String& model_rml = new_model_rml_map[model_name]; + model_rml += "
    "; + model_rml += "

    " + model_name + "

    "; + model_rml += "
    "; + + const UnorderedMap& variables = Detail::DataModelConstructorAccessor::GetAllVariables(model_constructor); + for (const auto& name_variable_pair : variables) + { + const String& name = name_variable_pair.first; + const DataVariable& variable = name_variable_pair.second; + model_rml += "" + name + ": "; + ReadDataVariableRecursive(model_rml, 0, variable); + } + + if (variables.empty()) + model_rml += "No data variables in data model.
    "; + + model_rml += "
    "; + model_rml += "
    "; + } + + if (new_model_rml_map != model_rml_map) + { + model_rml_map = std::move(new_model_rml_map); + + String models_rml; + for (const auto& name_rml_pair : model_rml_map) + models_rml += name_rml_pair.second; + + if (data_models.empty()) + models_rml = "No data models in context."; + + models_content_element->SetInnerRML(models_rml); + } +} + +} // namespace Debugger +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/ElementDataModels.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/ElementDataModels.h new file mode 100644 index 00000000..fb751774 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/ElementDataModels.h @@ -0,0 +1,37 @@ +#pragma once + +#include "../../Include/RmlUi/Core/ElementDocument.h" +#include "../../Include/RmlUi/Core/EventListener.h" +#include "ElementDebugDocument.h" + +namespace Rml { +namespace Debugger { + +class ElementDataModels : public ElementDebugDocument, public EventListener { +public: + RMLUI_RTTI_DefineWithParent(ElementDataModels, ElementDebugDocument) + + ElementDataModels(const String& tag); + ~ElementDataModels(); + + bool Initialise(Context* debug_context); + void Reset(); + + void SetDebugContext(Context* debug_context); + +protected: + void ProcessEvent(Event& event) override; + void OnUpdate() override; + +private: + void UpdateContent(); + + Context* debug_context = nullptr; + + double previous_update_time = {}; + + SmallOrderedMap model_rml_map; +}; + +} // namespace Debugger +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/ElementDebugDocument.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/ElementDebugDocument.cpp new file mode 100644 index 00000000..1ce98180 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/ElementDebugDocument.cpp @@ -0,0 +1,12 @@ +#include "ElementDebugDocument.h" + +namespace Rml { +namespace Debugger { + +ElementDebugDocument::ElementDebugDocument(const String& tag) : ElementDocument(tag) +{ + SetFocusableFromModal(true); +} + +} // namespace Debugger +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/ElementDebugDocument.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/ElementDebugDocument.h new file mode 100644 index 00000000..2931b977 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/ElementDebugDocument.h @@ -0,0 +1,16 @@ +#pragma once + +#include "../../Include/RmlUi/Core/ElementDocument.h" + +namespace Rml { +namespace Debugger { + +class ElementDebugDocument : public ElementDocument { +public: + RMLUI_RTTI_DefineWithParent(ElementDebugDocument, ElementDocument) + + ElementDebugDocument(const String& tag); +}; + +} // namespace Debugger +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/ElementInfo.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/ElementInfo.cpp new file mode 100644 index 00000000..85265213 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/ElementInfo.cpp @@ -0,0 +1,723 @@ +#include "ElementInfo.h" +#include "../../Include/RmlUi/Core/Context.h" +#include "../../Include/RmlUi/Core/Core.h" +#include "../../Include/RmlUi/Core/ElementText.h" +#include "../../Include/RmlUi/Core/ElementUtilities.h" +#include "../../Include/RmlUi/Core/Factory.h" +#include "../../Include/RmlUi/Core/PropertiesIteratorView.h" +#include "../../Include/RmlUi/Core/Property.h" +#include "../../Include/RmlUi/Core/PropertyDefinition.h" +#include "../../Include/RmlUi/Core/RenderManager.h" +#include "../../Include/RmlUi/Core/StyleSheet.h" +#include "../../Include/RmlUi/Core/StyleSheetSpecification.h" +#include "../../Include/RmlUi/Core/SystemInterface.h" +#include "CommonSource.h" +#include "Geometry.h" +#include "InfoSource.h" +#include + +namespace Rml { +namespace Debugger { + +ElementInfo::ElementInfo(const String& tag) : ElementDebugDocument(tag) +{ + hover_element = nullptr; + source_element = nullptr; + enable_element_select = true; + show_source_element = true; + update_source_element = true; + force_update_once = false; + title_dirty = true; + previous_update_time = 0.0; +} + +ElementInfo::~ElementInfo() +{ + RemoveEventListener(EventId::Click, this); + RemoveEventListener(EventId::Mouseover, this); + RemoveEventListener(EventId::Mouseout, this); +} + +bool ElementInfo::Initialise() +{ + SetInnerRML(info_rml); + SetId("rmlui-debug-info"); + + AddEventListener(EventId::Click, this); + AddEventListener(EventId::Mouseover, this); + AddEventListener(EventId::Mouseout, this); + + SharedPtr style_sheet = Factory::InstanceStyleSheetString(String(common_rcss) + String(info_rcss)); + if (!style_sheet) + return false; + + SetStyleSheetContainer(std::move(style_sheet)); + + return true; +} + +void ElementInfo::Reset() +{ + hover_element = nullptr; + show_source_element = true; + update_source_element = true; + SetSourceElement(nullptr); +} + +void ElementInfo::OnUpdate() +{ + if (source_element && (update_source_element || force_update_once) && IsVisible()) + { + const double t = GetSystemInterface()->GetElapsedTime(); + const float dt = (float)(t - previous_update_time); + + constexpr float update_interval = 0.3f; + + if (dt > update_interval || (force_update_once)) + { + if (force_update_once && source_element) + { + // Since an update is being forced, it is possibly because we are reacting to an event and made some changes. + // Update the source element's document to reflect any recent changes. + if (auto document = source_element->GetOwnerDocument()) + document->UpdateDocument(); + } + force_update_once = false; + + UpdateSourceElement(); + } + } + + if (title_dirty) + { + UpdateTitle(); + title_dirty = false; + } +} + +void ElementInfo::OnElementDestroy(Element* element) +{ + if (hover_element == element) + hover_element = nullptr; + + if (source_element == element) + source_element = nullptr; +} + +void ElementInfo::RenderHoverElement() +{ + if (hover_element) + { + ElementUtilities::ApplyTransform(*hover_element); + for (int i = 0; i < hover_element->GetNumBoxes(); i++) + { + // Render the content area. + Vector2f box_offset; + const Box& element_box = hover_element->GetBox(i, box_offset); + Vector2f size = element_box.GetSize(BoxArea::Border); + size = Vector2f(std::max(size.x, 2.0f), std::max(size.y, 2.0f)); + Geometry::RenderOutline(hover_element->GetAbsoluteOffset(BoxArea::Border) + box_offset, size, Colourb(255, 0, 0, 255), 1); + } + } +} + +void ElementInfo::RenderSourceElement() +{ + if (source_element && show_source_element) + { + ElementUtilities::ApplyTransform(*source_element); + + for (int i = 0; i < source_element->GetNumBoxes(); i++) + { + Vector2f box_offset; + const Box& element_box = source_element->GetBox(i, box_offset); + const Vector2f border_offset = box_offset + source_element->GetAbsoluteOffset(BoxArea::Border); + + // Content area: + Geometry::RenderBox(border_offset + element_box.GetPosition(BoxArea::Content), element_box.GetSize(), Colourb(158, 214, 237, 128)); + + // Padding area: + Geometry::RenderBox(border_offset + element_box.GetPosition(BoxArea::Padding), element_box.GetSize(BoxArea::Padding), + border_offset + element_box.GetPosition(BoxArea::Content), element_box.GetSize(), Colourb(135, 122, 214, 128)); + + // Border area: + Geometry::RenderBox(border_offset + element_box.GetPosition(BoxArea::Border), element_box.GetSize(BoxArea::Border), + border_offset + element_box.GetPosition(BoxArea::Padding), element_box.GetSize(BoxArea::Padding), Colourb(133, 133, 133, 128)); + + // Border area: + Geometry::RenderBox(border_offset + element_box.GetPosition(BoxArea::Margin), element_box.GetSize(BoxArea::Margin), + border_offset + element_box.GetPosition(BoxArea::Border), element_box.GetSize(BoxArea::Border), Colourb(240, 255, 131, 128)); + } + + if (Context* context = source_element->GetContext()) + { + context->GetRenderManager().SetTransform(nullptr); + + Rectanglef bounding_box; + if (ElementUtilities::GetBoundingBox(bounding_box, source_element, BoxArea::Auto)) + { + bounding_box = bounding_box.Extend(1.f); + Math::ExpandToPixelGrid(bounding_box); + Geometry::RenderOutline(bounding_box.Position(), bounding_box.Size(), Colourb(255, 255, 255, 200), 1.f); + } + } + } +} + +void ElementInfo::ProcessEvent(Event& event) +{ + // Only process events if we're visible + if (IsVisible()) + { + if (event == EventId::Click) + { + Element* target_element = event.GetTargetElement(); + + // Deal with clicks on our own elements differently. + if (target_element->GetOwnerDocument() == this) + { + const String& id = event.GetTargetElement()->GetId(); + + if (id == "close_button") + { + Hide(); + } + else if (id == "update_source") + { + update_source_element = !update_source_element; + target_element->SetClass("active", update_source_element); + } + else if (id == "show_source") + { + show_source_element = !target_element->IsClassSet("active"); + target_element->SetClass("active", show_source_element); + } + else if (id == "enable_element_select") + { + enable_element_select = !target_element->IsClassSet("active"); + target_element->SetClass("active", enable_element_select); + } + else if (target_element->GetTagName() == "pseudo" && source_element) + { + const String name = target_element->GetAttribute("name", ""); + + if (!name.empty()) + { + bool pseudo_active = target_element->IsClassSet("active"); + if (name == "focus") + { + if (!pseudo_active) + source_element->Focus(); + else if (auto document = source_element->GetOwnerDocument()) + document->Focus(); + } + else + { + source_element->SetPseudoClass(name, !pseudo_active); + } + + force_update_once = true; + } + } + else if (id == "offset_parent") + { + if (source_element) + { + Element* offset_parent = source_element->GetOffsetParent(); + if (offset_parent) + SetSourceElement(offset_parent); + } + } + // Check if the id is in the form "a %d" or "c %d" - these are the ancestor or child labels. + else + { + int element_index; + if (sscanf(target_element->GetId().c_str(), "a %d", &element_index) == 1) + { + Element* new_source_element = source_element; + for (int i = 0; i < element_index; i++) + { + if (new_source_element != nullptr) + new_source_element = new_source_element->GetParentNode(); + } + SetSourceElement(new_source_element); + } + else if (sscanf(target_element->GetId().c_str(), "c %d", &element_index) == 1) + { + if (source_element != nullptr) + SetSourceElement(source_element->GetChild(element_index)); + } + } + event.StopPropagation(); + } + // Otherwise we just want to focus on the clicked element (unless it's on a debug element) + else if (enable_element_select && target_element->GetOwnerDocument() != nullptr && !IsDebuggerElement(target_element)) + { + Element* new_source_element = target_element; + if (new_source_element != source_element) + { + SetSourceElement(new_source_element); + event.StopPropagation(); + } + } + } + else if (event == EventId::Mouseover) + { + Element* target_element = event.GetTargetElement(); + ElementDocument* owner_document = target_element->GetOwnerDocument(); + if (owner_document == this) + { + // Check if the id is in the form "a %d" or "c %d" - these are the ancestor or child labels. + const String& id = target_element->GetId(); + int element_index; + if (sscanf(id.c_str(), "a %d", &element_index) == 1) + { + hover_element = source_element; + for (int i = 0; i < element_index; i++) + { + if (hover_element != nullptr) + hover_element = hover_element->GetParentNode(); + } + } + else if (sscanf(id.c_str(), "c %d", &element_index) == 1) + { + if (source_element != nullptr) + hover_element = source_element->GetChild(element_index); + } + else if (id == "offset_parent") + { + if (source_element) + hover_element = source_element->GetOffsetParent(); + } + else + { + hover_element = nullptr; + } + + if (id == "show_source" && !show_source_element) + { + // Preview the source element view while hovering + show_source_element = true; + } + + if (id == "show_source" || id == "update_source" || id == "enable_element_select") + { + title_dirty = true; + } + } + // Otherwise we just want to focus on the clicked element (unless it's on a debug element) + else if (enable_element_select && owner_document && owner_document->GetId().find("rmlui-debug-") != 0) + { + if (Context* context = owner_document->GetContext()) + hover_element = context->GetHoverElement(); + } + } + else if (event == EventId::Mouseout) + { + Element* target_element = event.GetTargetElement(); + ElementDocument* owner_document = target_element->GetOwnerDocument(); + if (owner_document == this) + { + const String& id = target_element->GetId(); + if (id == "show_source") + { + // Disable the preview of the source element view + if (show_source_element && !target_element->IsClassSet("active")) + show_source_element = false; + } + + if (id == "show_source" || id == "update_source" || id == "enable_element_select") + { + title_dirty = true; + } + } + else if (enable_element_select && owner_document && owner_document->GetId().find("rmlui-debug-") != 0) + { + // Update hover element also on MouseOut as we may not get a MouseOver event when moving the mouse to a parent element. + if (Context* context = owner_document->GetContext()) + hover_element = context->GetHoverElement(); + } + } + } +} + +void ElementInfo::SetSourceElement(Element* new_source_element) +{ + source_element = new_source_element; + force_update_once = true; +} + +void ElementInfo::UpdateSourceElement() +{ + previous_update_time = GetSystemInterface()->GetElapsedTime(); + title_dirty = true; + + // Set the pseudo classes + if (Element* pseudo = GetElementById("pseudo")) + { + StringList list; + if (source_element) + list = source_element->GetActivePseudoClasses(); + + auto EraseFromList = [](StringList& list, const String& value) { + auto it = std::find(list.begin(), list.end(), value); + if (it == list.end()) + return false; + list.erase(it); + return true; + }; + + // There are some fixed pseudo classes that we always show and iterate through to determine if they are set. + // We also want to show other pseudo classes when they are set, they are added under the #extra element last. + for (int i = 0; i < pseudo->GetNumChildren(); i++) + { + Element* child = pseudo->GetChild(i); + const String name = child->GetAttribute("name", ""); + + if (!name.empty()) + { + bool active = EraseFromList(list, name); + child->SetClass("active", active); + } + else if (child->GetId() == "extra") + { + // First, we iterate through the extra elements and remove those that are no longer active. + for (int j = 0; j < child->GetNumChildren(); j++) + { + Element* grandchild = child->GetChild(j); + const String grandchild_name = grandchild->GetAttribute("name", ""); + bool active = (EraseFromList(list, grandchild_name) == 1); + if (!active) + child->RemoveChild(grandchild); + } + // Finally, create new pseudo buttons for the rest of the active pseudo classes. + for (auto& extra_pseudo : list) + { + Element* grandchild = child->AppendChild(CreateElement("pseudo")); + grandchild->SetClass("active", true); + grandchild->SetAttribute("name", extra_pseudo); + grandchild->SetInnerRML(":" + extra_pseudo); + } + } + } + } + + // Set the attributes + if (Element* attributes_content = GetElementById("attributes-content")) + { + String attributes; + + if (source_element != nullptr) + { + { + String name; + String value; + + // The element's attribute list is not always synchronized with its internal values, fetch + // them manually here (see e.g. Element::OnAttributeChange for relevant attributes) + { + name = "id"; + value = source_element->GetId(); + if (!value.empty()) + attributes += CreateString("%s: %s
    ", name.c_str(), value.c_str()); + } + { + name = "class"; + value = source_element->GetClassNames(); + if (!value.empty()) + attributes += CreateString("%s: %s
    ", name.c_str(), value.c_str()); + } + } + + for (const auto& pair : source_element->GetAttributes()) + { + auto& name = pair.first; + auto& variant = pair.second; + String value = StringUtilities::EncodeRml(variant.Get()); + if (name != "class" && name != "style" && name != "id") + attributes += CreateString("%s: %s
    ", name.c_str(), value.c_str()); + } + + // Text is not an attribute but useful nonetheless + if (auto text_element = rmlui_dynamic_cast(source_element)) + { + const String& text_content = text_element->GetText(); + attributes += CreateString("Text: %s
    ", text_content.c_str()); + } + } + + if (attributes.empty()) + { + while (attributes_content->HasChildNodes()) + attributes_content->RemoveChild(attributes_content->GetChild(0)); + attributes_rml.clear(); + } + else if (attributes != attributes_rml) + { + attributes_content->SetInnerRML(attributes); + attributes_rml = std::move(attributes); + } + } + + // Set the properties + if (Element* properties_content = GetElementById("properties-content")) + { + String properties; + if (source_element != nullptr) + BuildElementPropertiesRML(properties, source_element, source_element); + + if (properties != properties_rml) + { + properties_content->SetInnerRML(properties); + properties_rml = std::move(properties); + } + } + + // Set the events + if (Element* events_content = GetElementById("events-content")) + { + String events; + + if (source_element != nullptr) + { + events = source_element->GetEventDispatcherSummary(); + } + + if (events.empty()) + { + while (events_content->HasChildNodes()) + events_content->RemoveChild(events_content->GetChild(0)); + events_rml.clear(); + } + else if (events != events_rml) + { + events_content->SetInnerRML(events); + events_rml = std::move(events); + } + } + + // Set the position + if (Element* position_content = GetElementById("position-content")) + { + String position; + + if (source_element) + { + const Vector2f element_offset = source_element->GetRelativeOffset(BoxArea::Border); + const auto& box = source_element->GetBox(); + + const Vector2f element_size = source_element->GetBox().GetSize(BoxArea::Border); + Element* offset_parent = source_element->GetOffsetParent(); + const String offset_parent_rml = + (offset_parent ? StringUtilities::EncodeRml(offset_parent->GetAddress(false, false)) : String("none")); + + auto box_string = [&box](BoxDirection direction) { + const BoxEdge edge1 = (direction == BoxDirection::Horizontal ? BoxEdge::Left : BoxEdge::Top); + const BoxEdge edge2 = (direction == BoxDirection::Horizontal ? BoxEdge::Right : BoxEdge::Bottom); + const float content_size = (direction == BoxDirection::Horizontal ? box.GetSize().x : box.GetSize().y); + const String edge1_str = ToString(box.GetEdge(BoxArea::Margin, edge1)) + "|" + ToString(box.GetEdge(BoxArea::Border, edge1)) + "|" + + ToString(box.GetEdge(BoxArea::Padding, edge1)); + const String edge2_str = ToString(box.GetEdge(BoxArea::Padding, edge2)) + "|" + ToString(box.GetEdge(BoxArea::Border, edge2)) + "|" + + ToString(box.GetEdge(BoxArea::Margin, edge2)); + return CreateString("%s <%s> %s", edge1_str.c_str(), ToString(content_size).c_str(), edge2_str.c_str()); + }; + + position = "left: " + ToString(element_offset.x) + "px
    " + // + "top: " + ToString(element_offset.y) + "px
    " + // + "width: " + ToString(element_size.x) + "px
    " + // + "height: " + ToString(element_size.y) + "px
    " + // + "offset parent:

    " + offset_parent_rml + "


    " + // + "box-x (px): " + box_string(BoxDirection::Horizontal) + "
    " + // + "box-y (px): " + box_string(BoxDirection::Vertical); + } + + if (position != position_rml) + { + position_content->SetInnerRML(position); + position_rml = std::move(position); + } + } + + // Set the ancestors + if (Element* ancestors_content = GetElementById("ancestors-content")) + { + String ancestors; + Element* element_ancestor = nullptr; + if (source_element != nullptr) + element_ancestor = source_element->GetParentNode(); + + int ancestor_depth = 1; + while (element_ancestor) + { + String ancestor_name = element_ancestor->GetAddress(false, false); + ancestors += CreateString("

    %s

    ", ancestor_depth, ancestor_name.c_str()); + element_ancestor = element_ancestor->GetParentNode(); + ancestor_depth++; + } + + if (ancestors.empty()) + { + while (ancestors_content->HasChildNodes()) + ancestors_content->RemoveChild(ancestors_content->GetFirstChild()); + ancestors_rml.clear(); + } + else if (ancestors != ancestors_rml) + { + ancestors_content->SetInnerRML(ancestors); + ancestors_rml = std::move(ancestors); + } + } + + // Set the children + if (Element* children_content = GetElementById("children-content")) + { + String children; + if (source_element != nullptr) + { + const int num_dom_children = (source_element->GetNumChildren(false)); + + for (int i = 0; i < source_element->GetNumChildren(true); i++) + { + Element* child = source_element->GetChild(i); + + // If this is a debugger document, do not show it. + if (IsDebuggerElement(child)) + continue; + + String child_name = child->GetAddress(false, false); + auto document = rmlui_dynamic_cast(child); + if (document && !document->GetTitle().empty()) + child_name += " (" + document->GetTitle() + ')'; + + const char* non_dom_string = (i >= num_dom_children ? " class=\"non_dom\"" : ""); + + children += CreateString("

    %s

    ", i, non_dom_string, child_name.c_str()); + } + } + + if (children.empty()) + { + while (children_content->HasChildNodes()) + children_content->RemoveChild(children_content->GetChild(0)); + children_rml.clear(); + } + else if (children != children_rml) + { + children_content->SetInnerRML(children); + children_rml = std::move(children); + } + } +} + +void ElementInfo::BuildElementPropertiesRML(String& property_rml, Element* element, Element* primary_element) +{ + NamedPropertyList property_list; + + for (auto it = element->IterateLocalProperties(); !it.AtEnd(); ++it) + { + PropertyId property_id = it.GetId(); + const String& property_name = it.GetName(); + const Property* prop = &it.GetProperty(); + + // Check that this property isn't overridden or just not inherited. + if (primary_element->GetProperty(property_id) != prop) + continue; + + property_list.push_back(NamedProperty{property_name, prop}); + } + + std::sort(property_list.begin(), property_list.end(), [](const NamedProperty& a, const NamedProperty& b) { + if (a.second->source && !b.second->source) + return false; + if (!a.second->source && b.second->source) + return true; + if (a.second->specificity < b.second->specificity) + return false; + if (a.second->specificity > b.second->specificity) + return true; + if (a.second->definition && !b.second->definition) + return false; + if (!a.second->definition && b.second->definition) + return true; + const String& a_name = StyleSheetSpecification::GetPropertyName(a.second->definition->GetId()); + const String& b_name = StyleSheetSpecification::GetPropertyName(b.second->definition->GetId()); + return a_name < b_name; + }); + + if (!property_list.empty()) + { + // Print the 'inherited from ...' header if we're not the primary element. + if (element != primary_element) + { + property_rml += "

    inherited from " + element->GetAddress(false, false) + "

    "; + } + + const PropertySource* previous_source = nullptr; + bool first_iteration = true; + + for (auto& named_property : property_list) + { + auto& source = named_property.second->source; + if (source.get() != previous_source || first_iteration) + { + previous_source = source.get(); + first_iteration = false; + + // Print the rule name header. + if (source) + { + String str_line_number; + TypeConverter::Convert(source->line_number, str_line_number); + property_rml += "

    " + source->rule_name + "

    "; + property_rml += "

    " + source->path + " : " + str_line_number + "

    "; + } + else + { + property_rml += "

    inline

    "; + } + } + + BuildPropertyRML(property_rml, named_property.first, named_property.second); + } + } + + if (element->GetParentNode() != nullptr) + BuildElementPropertiesRML(property_rml, element->GetParentNode(), primary_element); +} + +void ElementInfo::BuildPropertyRML(String& property_rml, const String& name, const Property* property) +{ + const String property_value = property->ToString(); + + property_rml += "" + name + ": " + property_value + "
    "; +} + +void ElementInfo::UpdateTitle() +{ + auto title_content = GetElementById("title-content"); + auto enable_select = GetElementById("enable_element_select"); + auto show_source = GetElementById("show_source"); + auto update_source = GetElementById("update_source"); + + if (title_content && enable_select && show_source && update_source) + { + if (enable_select->IsPseudoClassSet("hover")) + title_content->SetInnerRML("(select elements)"); + else if (show_source->IsPseudoClassSet("hover")) + title_content->SetInnerRML("(draw element dimensions)"); + else if (update_source->IsPseudoClassSet("hover")) + title_content->SetInnerRML("(update info continuously)"); + else if (source_element) + title_content->SetInnerRML(source_element->GetTagName()); + else + title_content->SetInnerRML("Element Information"); + } +} + +bool ElementInfo::IsDebuggerElement(Element* element) +{ + return element->GetOwnerDocument()->GetId().find("rmlui-debug-") == 0; +} + +} // namespace Debugger +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/ElementInfo.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/ElementInfo.h new file mode 100644 index 00000000..eeeef2e1 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/ElementInfo.h @@ -0,0 +1,70 @@ +#pragma once + +#include "../../Include/RmlUi/Core/ElementDocument.h" +#include "../../Include/RmlUi/Core/EventListener.h" +#include "ElementDebugDocument.h" + +namespace Rml { +namespace Debugger { + +typedef Pair NamedProperty; +typedef Vector NamedPropertyList; + +class ElementInfo : public ElementDebugDocument, public EventListener { +public: + RMLUI_RTTI_DefineWithParent(ElementInfo, ElementDebugDocument) + + ElementInfo(const String& tag); + ~ElementInfo(); + + /// Initialises the info element. + /// @return True if the element initialised successfully, false otherwise. + bool Initialise(); + /// Clears the element references. + void Reset(); + + /// Called when an element is destroyed. + void OnElementDestroy(Element* element); + + void RenderHoverElement(); + void RenderSourceElement(); + + Element* GetSourceElement() const { return source_element; } + +protected: + void ProcessEvent(Event& event) override; + /// Updates the element info if changed + void OnUpdate() override; + +private: + void SetSourceElement(Element* new_source_element); + void UpdateSourceElement(); + + void BuildElementPropertiesRML(String& property_rml, Element* element, Element* primary_element); + void BuildPropertyRML(String& property_rml, const String& name, const Property* property); + + void UpdateTitle(); + + bool IsDebuggerElement(Element* element); + + double previous_update_time; + + String attributes_rml, properties_rml, events_rml, position_rml, ancestors_rml, children_rml; + + // Enables or disables the selection of elements in user context. + bool enable_element_select; + // Draws the dimensions of the source element. + bool show_source_element; + // Updates the source element information at regular intervals. + bool update_source_element; + // Forces an update to the source element during the next update loop. + bool force_update_once; + + bool title_dirty; + + Element* hover_element; + Element* source_element; +}; + +} // namespace Debugger +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/ElementLog.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/ElementLog.cpp new file mode 100644 index 00000000..f06b5785 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/ElementLog.cpp @@ -0,0 +1,290 @@ +#include "ElementLog.h" +#include "../../Include/RmlUi/Core/Context.h" +#include "../../Include/RmlUi/Core/Factory.h" +#include "BeaconSource.h" +#include "CommonSource.h" +#include "LogSource.h" +#include + +namespace Rml { +namespace Debugger { + +const int MAX_LOG_MESSAGES = 50; + +ElementLog::ElementLog(const String& tag) : ElementDebugDocument(tag) +{ + dirty_logs = false; + beacon = nullptr; + current_beacon_level = Log::LT_MAX; + auto_scroll = true; + message_content = nullptr; + current_index = 0; + + // Set up the log type buttons. + log_types[Log::LT_ALWAYS].visible = true; + log_types[Log::LT_ALWAYS].class_name = "error"; + log_types[Log::LT_ALWAYS].alert_contents = "A"; + + log_types[Log::LT_ERROR].visible = true; + log_types[Log::LT_ERROR].class_name = "error"; + log_types[Log::LT_ERROR].alert_contents = "!"; + log_types[Log::LT_ERROR].button_name = "error_button"; + + log_types[Log::LT_ASSERT].visible = true; + log_types[Log::LT_ASSERT].class_name = "error"; + log_types[Log::LT_ASSERT].alert_contents = "!"; + + log_types[Log::LT_WARNING].visible = true; + log_types[Log::LT_WARNING].class_name = "warning"; + log_types[Log::LT_WARNING].alert_contents = "!"; + log_types[Log::LT_WARNING].button_name = "warning_button"; + + log_types[Log::LT_INFO].visible = false; + log_types[Log::LT_INFO].class_name = "info"; + log_types[Log::LT_INFO].alert_contents = "i"; + log_types[Log::LT_INFO].button_name = "info_button"; + + log_types[Log::LT_DEBUG].visible = true; + log_types[Log::LT_DEBUG].class_name = "debug"; + log_types[Log::LT_DEBUG].alert_contents = "?"; + log_types[Log::LT_DEBUG].button_name = "debug_button"; +} + +ElementLog::~ElementLog() +{ + RemoveEventListener(EventId::Click, this); + + if (beacon && beacon->GetFirstChild()) + beacon->GetFirstChild()->RemoveEventListener(EventId::Click, this); + + if (beacon && beacon->GetParentNode()) + beacon->GetParentNode()->RemoveChild(beacon); + + if (message_content) + { + message_content->RemoveEventListener(EventId::Resize, this); + } +} + +bool ElementLog::Initialise() +{ + SetInnerRML(log_rml); + SetId("rmlui-debug-log"); + + message_content = GetElementById("content"); + if (message_content) + { + message_content->AddEventListener(EventId::Resize, this); + } + + SharedPtr style_sheet = Factory::InstanceStyleSheetString(String(common_rcss) + String(log_rcss)); + if (!style_sheet) + return false; + + SetStyleSheetContainer(std::move(style_sheet)); + + AddEventListener(EventId::Click, this); + + // Create the log beacon. + beacon = GetContext()->CreateDocument("debug-document"); + RMLUI_ASSERT(rmlui_dynamic_cast(beacon)); + if (!beacon) + return false; + + beacon->SetId("rmlui-debug-log-beacon"); + beacon->SetProperty(PropertyId::Visibility, Property(Style::Visibility::Hidden)); + beacon->SetInnerRML(beacon_rml); + + Element* button = beacon->GetFirstChild(); + if (button) + beacon->GetFirstChild()->AddEventListener(EventId::Click, this); + + style_sheet = Factory::InstanceStyleSheetString(String(common_rcss) + String(beacon_rcss)); + if (!style_sheet) + { + GetContext()->UnloadDocument(beacon); + beacon = nullptr; + return false; + } + + beacon->SetStyleSheetContainer(style_sheet); + + return true; +} + +void ElementLog::AddLogMessage(Log::Type type, const String& message) +{ + LogMessageList& log_message_list = log_types[type].log_messages; + if (log_message_list.size() >= MAX_LOG_MESSAGES) + { + log_message_list.erase(log_message_list.begin()); + } + + // Add the message to the list of messages for the specified log type. + LogMessage log_message; + log_message.index = current_index++; + log_message.message = StringUtilities::EncodeRml(message); + log_message_list.push_back(log_message); + + // If this log type is invisible, and there is a button for this log type, then change its text from + // "Off" to "Off*" to signal that there are unread logs. + if (!log_types[type].visible) + { + if (!log_types[type].button_name.empty()) + { + Element* button = GetElementById(log_types[type].button_name); + if (button) + { + button->SetInnerRML("Off*"); + } + } + } + // Trigger the beacon if we're hidden. Override any lower-level log type if it is already visible. + else + { + if (beacon != nullptr) + { + if (type < current_beacon_level) + { + beacon->SetProperty(PropertyId::Visibility, Property(Style::Visibility::Visible)); + + current_beacon_level = type; + Element* beacon_button = beacon->GetFirstChild(); + if (beacon_button) + { + beacon_button->SetClassNames(log_types[type].class_name); + beacon_button->SetInnerRML(log_types[type].alert_contents); + } + + // We need to update the document manually in case the beacon appears during context update. + beacon->UpdateDocument(); + } + } + } + + // Force a refresh of the RML. + dirty_logs = true; +} + +void ElementLog::OnUpdate() +{ + ElementDocument::OnUpdate(); + + if (dirty_logs) + { + // Set the log content: + String messages; + if (message_content) + { + unsigned int log_pointers[Log::LT_MAX]; + for (int i = 0; i < Log::LT_MAX; i++) + log_pointers[i] = 0; + int next_type = FindNextEarliestLogType(log_pointers); + int num_messages = 0; + while (next_type != -1 && num_messages < MAX_LOG_MESSAGES) + { + messages += CreateString("
    %s

    ", + log_types[next_type].class_name.c_str(), log_types[next_type].alert_contents.c_str()); + messages += log_types[next_type].log_messages[log_pointers[next_type]].message; + messages += "

    "; + + log_pointers[next_type]++; + next_type = FindNextEarliestLogType(log_pointers); + num_messages++; + } + + if (message_content->HasChildNodes()) + { + float last_element_top = message_content->GetLastChild()->GetAbsoluteTop(); + auto_scroll = message_content->GetAbsoluteTop() + message_content->GetAbsoluteTop() > last_element_top; + } + else + auto_scroll = true; + + message_content->SetInnerRML(messages); + + dirty_logs = false; + } + } +} + +void ElementLog::ProcessEvent(Event& event) +{ + // Only process events if we're visible + if (beacon != nullptr) + { + if (event == EventId::Click) + { + if (event.GetTargetElement() == beacon->GetFirstChild()) + { + Show(); + beacon->SetProperty(PropertyId::Visibility, Property(Style::Visibility::Hidden)); + current_beacon_level = Log::LT_MAX; + } + else if (event.GetTargetElement()->GetId() == "close_button") + { + Hide(); + } + else if (event.GetTargetElement()->GetId() == "clear_button") + { + for (int i = 0; i < Log::LT_MAX; i++) + { + log_types[i].log_messages.clear(); + if (!log_types[i].visible) + { + if (Element* button = GetElementById(log_types[i].button_name)) + button->SetInnerRML("Off"); + } + } + dirty_logs = true; + } + else + { + for (int i = 0; i < Log::LT_MAX; i++) + { + if (!log_types[i].button_name.empty() && event.GetTargetElement()->GetId() == log_types[i].button_name) + { + log_types[i].visible = !log_types[i].visible; + if (log_types[i].visible) + event.GetTargetElement()->SetInnerRML("On"); + else + event.GetTargetElement()->SetInnerRML("Off"); + dirty_logs = true; + } + } + } + } + } + + if (event == EventId::Resize && auto_scroll) + { + if (message_content != nullptr && message_content->HasChildNodes()) + message_content->GetLastChild()->ScrollIntoView(); + } +} + +int ElementLog::FindNextEarliestLogType(unsigned int log_pointers[Log::LT_MAX]) +{ + int log_channel = -1; + unsigned int index = UINT_MAX; + + for (int i = 0; i < Log::LT_MAX; i++) + { + if (log_types[i].visible) + { + if (log_pointers[i] < log_types[i].log_messages.size()) + { + if (log_types[i].log_messages[log_pointers[i]].index < index) + { + index = log_types[i].log_messages[log_pointers[i]].index; + log_channel = i; + } + } + } + } + + return log_channel; +} + +} // namespace Debugger +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/ElementLog.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/ElementLog.h new file mode 100644 index 00000000..e03319ff --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/ElementLog.h @@ -0,0 +1,58 @@ +#pragma once + +#include "../../Include/RmlUi/Core/ElementDocument.h" +#include "../../Include/RmlUi/Core/EventListener.h" +#include "../../Include/RmlUi/Core/Types.h" +#include "ElementDebugDocument.h" + +namespace Rml { +namespace Debugger { + +class DebuggerSystemInterface; + +class ElementLog : public ElementDebugDocument, public Rml::EventListener { +public: + RMLUI_RTTI_DefineWithParent(ElementLog, ElementDebugDocument) + + ElementLog(const String& tag); + ~ElementLog(); + + /// Initialises the log element. + /// @return True if the element initialised successfully, false otherwise. + bool Initialise(); + + /// Adds a log message to the debug log. + void AddLogMessage(Log::Type type, const String& message); + +protected: + void OnUpdate() override; + void ProcessEvent(Event& event) override; + +private: + struct LogMessage { + unsigned int index; + String message; + }; + using LogMessageList = Vector; + + struct LogType { + bool visible; + String class_name; + String alert_contents; + String button_name; + LogMessageList log_messages; + }; + LogType log_types[Log::LT_MAX]; + + int FindNextEarliestLogType(unsigned int log_pointers[Log::LT_MAX]); + + unsigned int current_index; + bool dirty_logs; + bool auto_scroll; + Element* message_content; + ElementDocument* beacon; + int current_beacon_level; +}; + +} // namespace Debugger +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/FontSource.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/FontSource.h new file mode 100644 index 00000000..46d77d48 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/FontSource.h @@ -0,0 +1,5164 @@ +#pragma once + +/* + The fonts 'Courier Prime Code' and 'Courier Prime Code Italic', included + in this file, are licensed under the following terms. + + ----------------------------------------------------------- + + Copyright (c) 2013, Quote-Unquote Apps (http://quoteunquoteapps.com), + with Reserved Font Name Courier Prime. + + This Font Software is licensed under the SIL Open Font License, Version 1.1. + This license is copied below, and is also available with a FAQ at: + http://scripts.sil.org/OFL + + + ----------------------------------------------------------- + SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 + ----------------------------------------------------------- + + PREAMBLE + The goals of the Open Font License (OFL) are to stimulate worldwide + development of collaborative font projects, to support the font creation + efforts of academic and linguistic communities, and to provide a free and + open framework in which fonts may be shared and improved in partnership + with others. + + The OFL allows the licensed fonts to be used, studied, modified and + redistributed freely as long as they are not sold by themselves. The + fonts, including any derivative works, can be bundled, embedded, + redistributed and/or sold with any software provided that any reserved + names are not used by derivative works. The fonts and derivatives, + however, cannot be released under any other type of license. The + requirement for fonts to remain under this license does not apply + to any document created using the fonts or their derivatives. + + DEFINITIONS + "Font Software" refers to the set of files released by the Copyright + Holder(s) under this license and clearly marked as such. This may + include source files, build scripts and documentation. + + "Reserved Font Name" refers to any names specified as such after the + copyright statement(s). + + "Original Version" refers to the collection of Font Software components as + distributed by the Copyright Holder(s). + + "Modified Version" refers to any derivative made by adding to, deleting, + or substituting -- in part or in whole -- any of the components of the + Original Version, by changing formats or by porting the Font Software to a + new environment. + + "Author" refers to any designer, engineer, programmer, technical + writer or other person who contributed to the Font Software. + + PERMISSION & CONDITIONS + Permission is hereby granted, free of charge, to any person obtaining + a copy of the Font Software, to use, study, copy, merge, embed, modify, + redistribute, and sell modified and unmodified copies of the Font + Software, subject to the following conditions: + + 1) Neither the Font Software nor any of its individual components, + in Original or Modified Versions, may be sold by itself. + + 2) Original or Modified Versions of the Font Software may be bundled, + redistributed and/or sold with any software, provided that each copy + contains the above copyright notice and this license. These can be + included either as stand-alone text files, human-readable headers or + in the appropriate machine-readable metadata fields within text or + binary files as long as those fields can be easily viewed by the user. + + 3) No Modified Version of the Font Software may use the Reserved Font + Name(s) unless explicit written permission is granted by the corresponding + Copyright Holder. This restriction only applies to the primary font name as + presented to the users. + + 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font + Software shall not be used to promote, endorse or advertise any + Modified Version, except to acknowledge the contribution(s) of the + Copyright Holder(s) and the Author(s) or with their explicit written + permission. + + 5) The Font Software, modified or unmodified, in part or in whole, + must be distributed entirely under this license, and must not be + distributed under any other license. The requirement for fonts to + remain under this license does not apply to any document created + using the Font Software. + + TERMINATION + This license becomes null and void if any of the above conditions are + not met. + + DISCLAIMER + THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT + OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE + COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL + DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM + OTHER DEALINGS IN THE FONT SOFTWARE. + +*/ + +// clang-format off +static const unsigned char courier_prime_code[] = { + 0x0,0x1,0x0,0x0,0x0,0x10,0x1,0x0,0x0,0x4,0x0,0x0,0x44,0x53,0x49,0x47,0x0,0x0,0x0,0x1,0x0,0x1,0x20,0x48,0x0,0x0,0x0,0x8,0x47,0x53, + 0x55,0x42,0x1a,0x6f,0xb,0xcc,0x0,0x0,0x1,0xc,0x0,0x0,0x4,0x6e,0x4f,0x53,0x2f,0x32,0x71,0x18,0x89,0x8e,0x0,0x0,0x5,0x7c,0x0,0x0,0x0,0x60, + 0x63,0x6d,0x61,0x70,0xd7,0xce,0xb9,0xf9,0x0,0x0,0x5,0xdc,0x0,0x0,0x6,0x46,0x63,0x76,0x74,0x20,0x22,0xd6,0xfe,0xb8,0x0,0x1,0x13,0x98,0x0,0x0, + 0x0,0x3c,0x66,0x70,0x67,0x6d,0x3f,0xae,0x1b,0x9f,0x0,0x1,0x13,0xd4,0x0,0x0,0xb,0xe2,0x67,0x61,0x73,0x70,0x0,0x0,0x0,0x10,0x0,0x1,0x13,0x90, + 0x0,0x0,0x0,0x8,0x67,0x6c,0x79,0x66,0xcf,0x99,0x79,0xec,0x0,0x0,0xc,0x24,0x0,0x0,0xbc,0xce,0x68,0x65,0x61,0x64,0xd,0x2f,0xe1,0xd2,0x0,0x0, + 0xc8,0xf4,0x0,0x0,0x0,0x36,0x68,0x68,0x65,0x61,0xf,0x24,0xb,0x9c,0x0,0x0,0xc9,0x2c,0x0,0x0,0x0,0x24,0x68,0x6d,0x74,0x78,0x59,0x11,0x10,0xfa, + 0x0,0x0,0xc9,0x50,0x0,0x0,0x6,0x38,0x6c,0x6f,0x63,0x61,0x6d,0x14,0x9f,0x1,0x0,0x0,0xcf,0x88,0x0,0x0,0x3,0x38,0x6d,0x61,0x78,0x70,0x2,0xe9, + 0xc,0xe7,0x0,0x0,0xd2,0xc0,0x0,0x0,0x0,0x20,0x6e,0x61,0x6d,0x65,0xb8,0x46,0xea,0xc9,0x0,0x0,0xd2,0xe0,0x0,0x0,0x37,0x6b,0x70,0x6f,0x73,0x74, + 0xae,0xc8,0xe9,0xb5,0x0,0x1,0xa,0x4c,0x0,0x0,0x9,0x44,0x70,0x72,0x65,0x70,0x19,0x50,0x2,0x10,0x0,0x1,0x1f,0xb8,0x0,0x0,0x0,0x8d,0x0,0x1, + 0x0,0x0,0x0,0xa,0x0,0xa2,0x2,0x30,0x0,0x3,0x44,0x46,0x4c,0x54,0x0,0x14,0x67,0x72,0x65,0x6b,0x0,0x28,0x6c,0x61,0x74,0x6e,0x0,0x3c,0x0,0x4, + 0x0,0x0,0x0,0x0,0xff,0xff,0x0,0x5,0x0,0x0,0x0,0x6,0x0,0xc,0x0,0x15,0x0,0x1b,0x0,0x4,0x0,0x0,0x0,0x0,0xff,0xff,0x0,0x5,0x0,0x1, + 0x0,0x7,0x0,0xd,0x0,0x16,0x0,0x1c,0x0,0x16,0x0,0x3,0x43,0x41,0x54,0x20,0x0,0x26,0x4d,0x4f,0x4c,0x20,0x0,0x38,0x52,0x4f,0x4d,0x20,0x0,0x4a, + 0x0,0x0,0xff,0xff,0x0,0x5,0x0,0x2,0x0,0x8,0x0,0xe,0x0,0x17,0x0,0x1d,0x0,0x0,0xff,0xff,0x0,0x6,0x0,0x3,0x0,0x9,0x0,0xf,0x0,0x12, + 0x0,0x18,0x0,0x1e,0x0,0x0,0xff,0xff,0x0,0x6,0x0,0x4,0x0,0xa,0x0,0x10,0x0,0x13,0x0,0x19,0x0,0x1f,0x0,0x0,0xff,0xff,0x0,0x6,0x0,0x5, + 0x0,0xb,0x0,0x11,0x0,0x14,0x0,0x1a,0x0,0x20,0x0,0x21,0x61,0x61,0x6c,0x74,0x0,0xc8,0x61,0x61,0x6c,0x74,0x0,0xce,0x61,0x61,0x6c,0x74,0x0,0xd4, + 0x61,0x61,0x6c,0x74,0x0,0xda,0x61,0x61,0x6c,0x74,0x0,0xe0,0x61,0x61,0x6c,0x74,0x0,0xe6,0x63,0x61,0x73,0x65,0x0,0xec,0x63,0x61,0x73,0x65,0x0,0xf2, + 0x63,0x61,0x73,0x65,0x0,0xf8,0x63,0x61,0x73,0x65,0x0,0xfe,0x63,0x61,0x73,0x65,0x1,0x4,0x63,0x61,0x73,0x65,0x1,0xa,0x66,0x72,0x61,0x63,0x1,0x10, + 0x66,0x72,0x61,0x63,0x1,0x16,0x66,0x72,0x61,0x63,0x1,0x1c,0x66,0x72,0x61,0x63,0x1,0x22,0x66,0x72,0x61,0x63,0x1,0x28,0x66,0x72,0x61,0x63,0x1,0x2e, + 0x6c,0x6f,0x63,0x6c,0x1,0x34,0x6c,0x6f,0x63,0x6c,0x1,0x3a,0x6c,0x6f,0x63,0x6c,0x1,0x40,0x6f,0x72,0x64,0x6e,0x1,0x46,0x6f,0x72,0x64,0x6e,0x1,0x4c, + 0x6f,0x72,0x64,0x6e,0x1,0x52,0x6f,0x72,0x64,0x6e,0x1,0x58,0x6f,0x72,0x64,0x6e,0x1,0x5e,0x6f,0x72,0x64,0x6e,0x1,0x64,0x73,0x75,0x70,0x73,0x1,0x6a, + 0x73,0x75,0x70,0x73,0x1,0x70,0x73,0x75,0x70,0x73,0x1,0x76,0x73,0x75,0x70,0x73,0x1,0x7c,0x73,0x75,0x70,0x73,0x1,0x82,0x73,0x75,0x70,0x73,0x1,0x88, + 0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x0,0x0,0x1,0x0,0x0, + 0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x0,0x0,0x1,0x0,0x7,0x0,0x0,0x0,0x1,0x0,0x7,0x0,0x0,0x0,0x1,0x0,0x7,0x0,0x0,0x0,0x1,0x0,0x7, + 0x0,0x0,0x0,0x1,0x0,0x7,0x0,0x0,0x0,0x1,0x0,0x7,0x0,0x0,0x0,0x1,0x0,0x5,0x0,0x0,0x0,0x1,0x0,0x5,0x0,0x0,0x0,0x1,0x0,0x5, + 0x0,0x0,0x0,0x1,0x0,0x5,0x0,0x0,0x0,0x1,0x0,0x5,0x0,0x0,0x0,0x1,0x0,0x5,0x0,0x0,0x0,0x1,0x0,0x3,0x0,0x0,0x0,0x1,0x0,0x2, + 0x0,0x0,0x0,0x1,0x0,0x1,0x0,0x0,0x0,0x1,0x0,0x6,0x0,0x0,0x0,0x1,0x0,0x6,0x0,0x0,0x0,0x1,0x0,0x6,0x0,0x0,0x0,0x1,0x0,0x6, + 0x0,0x0,0x0,0x1,0x0,0x6,0x0,0x0,0x0,0x1,0x0,0x6,0x0,0x0,0x0,0x1,0x0,0x4,0x0,0x0,0x0,0x1,0x0,0x4,0x0,0x0,0x0,0x1,0x0,0x4, + 0x0,0x0,0x0,0x1,0x0,0x4,0x0,0x0,0x0,0x1,0x0,0x4,0x0,0x0,0x0,0x1,0x0,0x4,0x0,0xa,0x0,0x16,0x0,0x1e,0x0,0x26,0x0,0x2e,0x0,0x38, + 0x0,0x40,0x0,0x48,0x0,0x52,0x0,0x5a,0x0,0x62,0x0,0x1,0x0,0x0,0x0,0x1,0x1,0x10,0x0,0x1,0x0,0x0,0x0,0x1,0x0,0x4c,0x0,0x1,0x0,0x0, + 0x0,0x1,0x0,0x52,0x0,0x6,0x0,0x0,0x0,0x2,0x0,0x58,0x0,0x6c,0x0,0x1,0x0,0x0,0x0,0x1,0x0,0x76,0x0,0x4,0x0,0x0,0x0,0x1,0x0,0x74, + 0x0,0x6,0x0,0x0,0x0,0x2,0x0,0x98,0x0,0xaa,0x0,0x1,0x0,0x0,0x0,0x1,0x0,0xb2,0x0,0x4,0x0,0x0,0x0,0x1,0x1,0x6,0x0,0x1,0x0,0x0, + 0x0,0x1,0x1,0x1c,0x0,0x2,0x1,0x22,0x0,0x4,0x0,0x5e,0x0,0x63,0x0,0xdd,0x0,0xe3,0x0,0x2,0x1,0x14,0x0,0x4,0x0,0x5e,0x0,0x63,0x0,0xdd, + 0x0,0xe3,0x0,0x3,0x0,0x0,0x0,0x2,0x1,0x12,0x1,0x18,0x0,0x1,0x1,0x12,0x0,0x1,0x0,0x0,0x0,0x8,0x0,0x3,0x0,0x0,0x0,0x2,0x1,0xa, + 0x1,0x4,0x0,0x1,0x1,0xa,0x0,0x1,0x0,0x0,0x0,0x8,0x0,0x1,0x0,0xfc,0x0,0xd,0x0,0x1,0x1,0x0,0x0,0x2,0x0,0xa,0x0,0x20,0x0,0x2, + 0x0,0x6,0x0,0xe,0x1,0x12,0x0,0x3,0x1,0x32,0x1,0x9,0x1,0x13,0x0,0x3,0x1,0x32,0x1,0xb,0x0,0x1,0x0,0x4,0x1,0x14,0x0,0x3,0x1,0x32, + 0x1,0xb,0x0,0x3,0x0,0x1,0x0,0xdc,0x0,0x1,0x0,0xe6,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x9,0x0,0x3,0x0,0x1,0x0,0xca,0x0,0x1,0x0,0xdc, + 0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x9,0x0,0x2,0x0,0xd2,0x0,0xe,0x1,0x7f,0x1,0x84,0x1,0x85,0x1,0x86,0x1,0x87,0x1,0x88,0x1,0x89,0x1,0x8a, + 0x1,0x8b,0x1,0x8c,0x1,0x8d,0x1,0x8e,0x1,0x8f,0x1,0x90,0x0,0x2,0x0,0xcc,0x0,0x1a,0x1,0x1,0x1,0x2,0x0,0x5e,0x0,0x63,0x1,0x1,0x1,0x2, + 0x0,0xdd,0x0,0xe3,0x1,0x15,0x1,0x16,0x1,0x17,0x1,0x18,0x1,0x7f,0x1,0x84,0x1,0x85,0x1,0x86,0x1,0x87,0x1,0x88,0x1,0x89,0x1,0x8a,0x1,0x8b, + 0x1,0x8c,0x1,0x8d,0x1,0x8e,0x1,0x8f,0x1,0x90,0x0,0x1,0x0,0xca,0x0,0x2,0x0,0xa,0x0,0x14,0x0,0x1,0x0,0x4,0x0,0x3e,0x0,0x2,0x1,0x24, + 0x0,0x1,0x0,0x4,0x0,0xbd,0x0,0x2,0x1,0x24,0x0,0x2,0x0,0xb4,0x0,0x4,0x1,0x1,0x1,0x2,0x1,0x1,0x1,0x2,0x0,0x1,0x0,0x4,0x0,0x5c, + 0x0,0x62,0x0,0xdb,0x0,0xe2,0x0,0x1,0x0,0x1,0x0,0xb9,0x0,0x1,0x0,0x1,0x1,0x24,0x0,0x1,0x0,0x1,0x0,0x3a,0x0,0x2,0x0,0x1,0x1,0x8, + 0x1,0xb,0x0,0x0,0x0,0x1,0x0,0x2,0x1,0x8,0x1,0xa,0x0,0x2,0x0,0x1,0x1,0x7,0x1,0x10,0x0,0x0,0x0,0x1,0x0,0x2,0x0,0x2,0x0,0x7f, + 0x0,0x1,0x0,0x2,0x0,0x47,0x0,0xc6,0x0,0x2,0x0,0x4,0x1,0x7e,0x1,0x7e,0x0,0x0,0x1,0x80,0x1,0x80,0x0,0x1,0x1,0x82,0x1,0x83,0x0,0x2, + 0x1,0x91,0x1,0x9a,0x0,0x4,0x0,0x1,0x0,0x1a,0x0,0x2,0x0,0x47,0x0,0x5c,0x0,0x62,0x0,0x7f,0x0,0xc6,0x0,0xdb,0x0,0xe2,0x1,0x8,0x1,0x9, + 0x1,0xa,0x1,0xb,0x1,0x7e,0x1,0x80,0x1,0x82,0x1,0x83,0x1,0x91,0x1,0x92,0x1,0x93,0x1,0x94,0x1,0x95,0x1,0x96,0x1,0x97,0x1,0x98,0x1,0x99, + 0x1,0x9a,0x0,0x1,0x0,0x2,0x0,0x3a,0x0,0xb9,0x0,0x1,0x0,0x4,0x0,0x2,0x0,0x47,0x0,0x7f,0x0,0xc6,0x0,0x0,0x0,0x3,0x4,0xe0,0x1,0x90, + 0x0,0x5,0x0,0x8,0x5,0x33,0x4,0xcc,0x0,0x0,0x0,0x99,0x5,0x33,0x4,0xcc,0x0,0x0,0x2,0xcc,0x0,0x82,0x2,0x2a,0x0,0x0,0x0,0x0,0x5,0x9, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x51,0x55,0x51,0x41,0x0,0x40,0x0,0x20, + 0xfb,0x2,0x5,0x8d,0xfd,0x7d,0x0,0xc2,0x7,0x5,0x3,0x47,0x20,0x0,0x0,0x93,0x0,0x0,0x0,0x0,0x3,0x9c,0x4,0xa3,0x0,0x0,0x0,0x20,0x0,0x3, + 0x0,0x0,0x0,0x3,0x0,0x0,0x0,0x3,0x0,0x0,0x2,0x14,0x0,0x1,0x0,0x0,0x0,0x0,0x0,0x1c,0x0,0x3,0x0,0x1,0x0,0x0,0x2,0x14,0x0,0x6, + 0x1,0xf8,0x0,0x0,0x0,0x9,0x0,0xf7,0x0,0x1,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0x1,0x29,0x1,0x2f, + 0x1,0x2b,0x1,0x51,0x1,0x67,0x1,0x72,0x1,0x30,0x1,0x38,0x1,0x39,0x1,0x22,0x1,0x69,0x1,0x27,0x1,0x41,0x1,0x2c,0x1,0x32,0x1,0x7,0x1,0x8, + 0x1,0x9,0x1,0xa,0x1,0xb,0x1,0xc,0x1,0xd,0x1,0xe,0x1,0xf,0x1,0x10,0x1,0x26,0x1,0x31,0x1,0x60,0x1,0x5b,0x1,0x5c,0x1,0x2d,0x1,0x71, + 0x0,0x2,0x0,0xd,0x0,0xe,0x0,0x14,0x0,0x18,0x0,0x22,0x0,0x23,0x0,0x28,0x0,0x2b,0x0,0x36,0x0,0x38,0x0,0x3a,0x0,0x40,0x0,0x41,0x0,0x47, + 0x0,0x52,0x0,0x54,0x0,0x55,0x0,0x59,0x0,0x5f,0x0,0x64,0x0,0x6f,0x0,0x70,0x0,0x75,0x0,0x76,0x0,0x7b,0x1,0x36,0x1,0x23,0x1,0x37,0x1,0x79, + 0x1,0x33,0x1,0x95,0x0,0x7f,0x0,0x8a,0x0,0x8b,0x0,0x91,0x0,0x95,0x0,0x9f,0x0,0xa0,0x0,0xa5,0x0,0xa8,0x0,0xb4,0x0,0xb7,0x0,0xb9,0x0,0xbf, + 0x0,0xc0,0x0,0xc6,0x0,0xd1,0x0,0xd3,0x0,0xd4,0x0,0xd8,0x0,0xdf,0x0,0xe4,0x0,0xef,0x0,0xf0,0x0,0xf5,0x0,0xf6,0x0,0xfb,0x1,0x34,0x1,0x6f, + 0x1,0x35,0x1,0x59,0x0,0x0,0x0,0x6,0x0,0xa,0x0,0x11,0x0,0x19,0x0,0x46,0x0,0x4b,0x0,0x68,0x0,0x80,0x0,0x84,0x0,0x82,0x0,0x83,0x0,0x88, + 0x0,0x87,0x0,0x8e,0x0,0x96,0x0,0x9c,0x0,0x99,0x0,0x9a,0x0,0xaa,0x0,0xaf,0x0,0xac,0x0,0xad,0x0,0xc5,0x0,0xc7,0x0,0xcb,0x0,0xc9,0x0,0xca, + 0x0,0xcf,0x0,0xe5,0x0,0xe9,0x0,0xe7,0x0,0xe8,0x1,0x7a,0x1,0x78,0x1,0x4f,0x1,0x55,0x1,0x76,0x1,0x25,0x1,0x73,0x0,0xde,0x1,0x75,0x1,0x74, + 0x1,0x77,0x1,0x80,0x1,0x93,0x1,0x65,0x0,0xc,0x0,0x4f,0x1,0x5e,0x1,0x6a,0x1,0x61,0x1,0x5d,0x1,0x57,0x0,0x0,0x1,0x66,0x1,0x6d,0x1,0x6b, + 0x1,0x6,0x1,0x5f,0x1,0x1,0x1,0x2,0x1,0x4,0x0,0x89,0x0,0xce,0x1,0x2e,0x1,0x2a,0x1,0x62,0x1,0x6c,0x1,0x53,0x1,0x58,0x0,0x0,0x1,0x44, + 0x1,0x45,0x1,0x28,0x1,0x4e,0x0,0x7,0x0,0xb,0x0,0x50,0x0,0x51,0x0,0xd0,0x1,0x40,0x1,0x3f,0x1,0x49,0x1,0x4a,0x1,0x4b,0x1,0x4c,0x1,0x5a, + 0x1,0x6e,0x0,0xf9,0x0,0x79,0x1,0x11,0x1,0x52,0x1,0x46,0x1,0x47,0x0,0xff,0x1,0x0,0x1,0x7b,0x1,0x24,0x1,0x4d,0x1,0x48,0x1,0x68,0x0,0x5, + 0x0,0x1c,0x0,0x3,0x0,0x1d,0x0,0x1f,0x0,0x2d,0x0,0x2f,0x0,0x30,0x0,0x32,0x0,0x48,0x0,0x4a,0x0,0x0,0x0,0x4c,0x0,0x65,0x0,0x67,0x0,0x69, + 0x0,0xa9,0x1,0x92,0x1,0x9a,0x1,0x97,0x1,0x82,0x1,0x94,0x1,0x99,0x1,0x91,0x1,0x96,0x1,0x98,0x1,0x83,0x0,0x4,0x4,0x32,0x0,0x0,0x0,0x5a, + 0x0,0x40,0x0,0x5,0x0,0x1a,0x0,0x2f,0x0,0x7e,0x0,0xb4,0x1,0x37,0x1,0x48,0x1,0x7e,0x1,0x92,0x2,0x1b,0x2,0x37,0x2,0xc7,0x2,0xdd,0x3,0x26, + 0x3,0x94,0x3,0xa9,0x3,0xbc,0x3,0xc0,0x1e,0x85,0x1e,0xf3,0x20,0x11,0x20,0x14,0x20,0x1a,0x20,0x1e,0x20,0x22,0x20,0x26,0x20,0x30,0x20,0x33,0x20,0x3a, + 0x20,0x44,0x20,0x74,0x20,0xa3,0x20,0xa9,0x20,0xac,0x21,0x22,0x22,0x2,0x22,0xf,0x22,0x12,0x22,0x1a,0x22,0x1e,0x22,0x2b,0x22,0x48,0x22,0x60,0x22,0x65, + 0x25,0xca,0xfb,0x2,0xff,0xff,0x0,0x0,0x0,0x20,0x0,0x30,0x0,0xa0,0x0,0xb6,0x1,0x39,0x1,0x4a,0x1,0x92,0x2,0x18,0x2,0x37,0x2,0xc6,0x2,0xd8, + 0x3,0x26,0x3,0x94,0x3,0xa9,0x3,0xbc,0x3,0xc0,0x1e,0x80,0x1e,0xf2,0x20,0x11,0x20,0x13,0x20,0x18,0x20,0x1c,0x20,0x20,0x20,0x26,0x20,0x30,0x20,0x32, + 0x20,0x39,0x20,0x44,0x20,0x74,0x20,0xa3,0x20,0xa9,0x20,0xac,0x21,0x22,0x22,0x2,0x22,0xf,0x22,0x11,0x22,0x1a,0x22,0x1e,0x22,0x2b,0x22,0x48,0x22,0x60, + 0x22,0x64,0x25,0xca,0xfb,0x1,0xff,0xff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xff,0xc1,0x0,0x0,0xfe,0x7e,0x0,0x0,0x0,0x0, + 0xfe,0x58,0xfd,0x6f,0xfd,0x5b,0xfd,0x49,0xfd,0x46,0x0,0x0,0x0,0x0,0xe1,0x31,0x0,0x0,0xe1,0x33,0x0,0x0,0x0,0x0,0xe1,0x2,0xe1,0x38,0xe1,0x4a, + 0xe1,0xd,0xe0,0xcd,0xe0,0xa4,0xe0,0xb1,0xe0,0xad,0xe0,0xa6,0xe0,0x55,0xdf,0x64,0xdf,0x5c,0x0,0x0,0xdf,0x52,0xdf,0x40,0xdf,0x34,0xdf,0x10,0xdf,0x5, + 0x0,0x0,0xdb,0xa4,0x0,0x0,0x0,0x1,0x0,0x5a,0x0,0x78,0x1,0x14,0x1,0x3c,0x2,0x3e,0x2,0x5c,0x0,0x0,0x2,0xc2,0x0,0x0,0x2,0xc6,0x2,0xc8, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x2,0xc8,0x2,0xd2,0x0,0x0,0x2,0xd2,0x0,0x0,0x2,0xd2,0x2,0xd6,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x2,0xc2,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x2,0xba,0x0,0x0,0x2,0xba,0x0,0x0,0x0,0x1,0x1,0x29,0x1,0x2f,0x1,0x2b,0x1,0x51,0x1,0x67,0x1,0x72,0x1,0x30,0x1,0x38,0x1,0x39,0x1,0x22, + 0x1,0x69,0x1,0x27,0x1,0x41,0x1,0x2c,0x1,0x32,0x1,0x7,0x1,0x8,0x1,0x9,0x1,0xa,0x1,0xb,0x1,0xc,0x1,0xd,0x1,0xe,0x1,0xf,0x1,0x10, + 0x1,0x26,0x1,0x31,0x1,0x60,0x1,0x5b,0x1,0x5c,0x1,0x2d,0x1,0x71,0x0,0x2,0x0,0xd,0x0,0xe,0x0,0x14,0x0,0x18,0x0,0x22,0x0,0x23,0x0,0x28, + 0x0,0x2b,0x0,0x36,0x0,0x38,0x0,0x3a,0x0,0x40,0x0,0x41,0x0,0x47,0x0,0x52,0x0,0x54,0x0,0x55,0x0,0x59,0x0,0x5f,0x0,0x64,0x0,0x6f,0x0,0x70, + 0x0,0x75,0x0,0x76,0x0,0x7b,0x1,0x36,0x1,0x23,0x1,0x37,0x1,0x79,0x1,0x33,0x1,0x95,0x0,0x7f,0x0,0x8a,0x0,0x8b,0x0,0x91,0x0,0x95,0x0,0x9f, + 0x0,0xa0,0x0,0xa5,0x0,0xa8,0x0,0xb4,0x0,0xb7,0x0,0xb9,0x0,0xbf,0x0,0xc0,0x0,0xc6,0x0,0xd1,0x0,0xd3,0x0,0xd4,0x0,0xd8,0x0,0xdf,0x0,0xe4, + 0x0,0xef,0x0,0xf0,0x0,0xf5,0x0,0xf6,0x0,0xfb,0x1,0x34,0x1,0x6f,0x1,0x35,0x1,0x59,0x1,0x4e,0x1,0x2a,0x1,0x4f,0x1,0x55,0x1,0x50,0x1,0x57, + 0x1,0x70,0x1,0x76,0x1,0x93,0x1,0x74,0x1,0x1,0x1,0x44,0x1,0x62,0x1,0x43,0x1,0x75,0x1,0x97,0x1,0x78,0x1,0x6a,0x1,0x16,0x1,0x17,0x1,0x80, + 0x1,0x73,0x1,0x24,0x1,0x91,0x1,0x15,0x1,0x2,0x1,0x45,0x1,0x13,0x1,0x12,0x1,0x14,0x1,0x2e,0x0,0x7,0x0,0x3,0x0,0x5,0x0,0xb,0x0,0x6, + 0x0,0xa,0x0,0xc,0x0,0x11,0x0,0x1f,0x0,0x19,0x0,0x1c,0x0,0x1d,0x0,0x32,0x0,0x2d,0x0,0x2f,0x0,0x30,0x0,0x15,0x0,0x46,0x0,0x4c,0x0,0x48, + 0x0,0x4a,0x0,0x50,0x0,0x4b,0x1,0x64,0x0,0x4f,0x0,0x69,0x0,0x65,0x0,0x67,0x0,0x68,0x0,0x77,0x0,0x53,0x0,0xde,0x0,0x84,0x0,0x80,0x0,0x82, + 0x0,0x88,0x0,0x83,0x0,0x87,0x0,0x89,0x0,0x8e,0x0,0x9c,0x0,0x96,0x0,0x99,0x0,0x9a,0x0,0xaf,0x0,0xaa,0x0,0xac,0x0,0xad,0x0,0x92,0x0,0xc5, + 0x0,0xcb,0x0,0xc7,0x0,0xc9,0x0,0xcf,0x0,0xca,0x1,0x5a,0x0,0xce,0x0,0xe9,0x0,0xe5,0x0,0xe7,0x0,0xe8,0x0,0xf7,0x0,0xd2,0x0,0xf9,0x0,0x8, + 0x0,0x85,0x0,0x4,0x0,0x81,0x0,0x9,0x0,0x86,0x0,0xf,0x0,0x8c,0x0,0x12,0x0,0x8f,0x0,0x13,0x0,0x90,0x0,0x10,0x0,0x8d,0x0,0x16,0x0,0x93, + 0x0,0x17,0x0,0x94,0x0,0x20,0x0,0x9d,0x0,0x1a,0x0,0x97,0x0,0x1e,0x0,0x9b,0x0,0x21,0x0,0x9e,0x0,0x1b,0x0,0x98,0x0,0x25,0x0,0xa2,0x0,0x24, + 0x0,0xa1,0x0,0x27,0x0,0xa4,0x0,0x26,0x0,0xa3,0x0,0x2a,0x0,0xa7,0x0,0x29,0x0,0xa6,0x0,0x35,0x0,0xb3,0x0,0x33,0x0,0xb1,0x0,0x2e,0x0,0xab, + 0x0,0x34,0x0,0xb2,0x0,0x31,0x0,0xa9,0x0,0x2c,0x0,0xb0,0x0,0x37,0x0,0xb6,0x0,0x39,0x0,0xb8,0x0,0x3b,0x0,0xba,0x0,0x3d,0x0,0xbc,0x0,0x3c, + 0x0,0xbb,0x0,0x3e,0x0,0xbd,0x0,0x3f,0x0,0xbe,0x0,0x42,0x0,0xc1,0x0,0x44,0x0,0xc3,0x0,0x43,0x0,0xc2,0x0,0x45,0x0,0xc4,0x0,0x4e,0x0,0xcd, + 0x0,0x49,0x0,0xc8,0x0,0x4d,0x0,0xcc,0x0,0x51,0x0,0xd0,0x0,0x56,0x0,0xd5,0x0,0x58,0x0,0xd7,0x0,0x57,0x0,0xd6,0x0,0x5a,0x0,0xd9,0x0,0x5d, + 0x0,0xdc,0x0,0x5c,0x0,0xdb,0x0,0x5b,0x0,0xda,0x0,0x62,0x0,0xe2,0x0,0x61,0x0,0xe1,0x0,0x60,0x0,0xe0,0x0,0x6e,0x0,0xee,0x0,0x6b,0x0,0xeb, + 0x0,0x66,0x0,0xe6,0x0,0x6d,0x0,0xed,0x0,0x6a,0x0,0xea,0x0,0x6c,0x0,0xec,0x0,0x72,0x0,0xf2,0x0,0x78,0x0,0xf8,0x0,0x79,0x0,0x7c,0x0,0xfc, + 0x0,0x7e,0x0,0xfe,0x0,0x7d,0x0,0xfd,0x0,0x5e,0x0,0xdd,0x0,0x63,0x0,0xe3,0x1,0x92,0x1,0x83,0x1,0x82,0x1,0x94,0x1,0x99,0x1,0x98,0x1,0x9a, + 0x1,0x96,0x0,0x74,0x0,0xf4,0x0,0x71,0x0,0xf1,0x0,0x73,0x0,0xf3,0x0,0x7a,0x0,0xfa,0x1,0x40,0x1,0x3f,0x1,0x49,0x1,0x4a,0x1,0x48,0x1,0x7a, + 0x1,0x7b,0x1,0x25,0x1,0x6d,0x1,0x63,0x1,0x61,0x1,0x5d,0x0,0xff,0x1,0x0,0x0,0x0,0x0,0xa,0x0,0xd3,0xfd,0x7d,0x3,0xa0,0x6,0x55,0x0,0x3, + 0x0,0xf,0x0,0x15,0x0,0x19,0x0,0x23,0x0,0x29,0x0,0x35,0x0,0x39,0x0,0x3d,0x0,0x48,0x0,0x19,0x40,0x16,0x42,0x3e,0x3c,0x3a,0x37,0x36,0x33,0x2a, + 0x27,0x24,0x1f,0x1a,0x18,0x16,0x11,0x10,0x9,0x4,0x2,0x0,0xa,0x30,0x2b,0x1,0x21,0x11,0x21,0x5,0x15,0x33,0x15,0x23,0x15,0x21,0x35,0x23,0x35,0x33, + 0x35,0x1,0x15,0x21,0x35,0x23,0x35,0x7,0x23,0x35,0x33,0x7,0x15,0x33,0x15,0x23,0x15,0x33,0x35,0x33,0x35,0x7,0x15,0x21,0x15,0x21,0x35,0x3,0x15,0x33, + 0x35,0x33,0x15,0x23,0x35,0x23,0x11,0x21,0x11,0x1,0x11,0x21,0x11,0x7,0x23,0x35,0x33,0x5,0x15,0x33,0x7,0x15,0x21,0x35,0x23,0x37,0x33,0x35,0x3,0xa0, + 0xfd,0x33,0x2,0xcd,0xfd,0xdc,0x94,0x96,0x1,0x78,0x96,0x96,0xfe,0x88,0x1,0x78,0x96,0x4c,0x4b,0x4b,0x96,0x96,0x96,0xe2,0x96,0x4b,0xfe,0xd3,0x1,0x78, + 0xe2,0x4c,0x4b,0xe2,0x4b,0x1,0x78,0xfe,0x88,0x1,0x78,0x4b,0xe2,0xe2,0xfe,0xd3,0x9f,0x9f,0x1,0x78,0xe7,0x9e,0x49,0xfd,0x7d,0x8,0xd8,0x98,0x4a,0x54, + 0x4b,0x4b,0x54,0x4a,0xfe,0xdc,0xeb,0x4d,0x9e,0x9e,0x51,0xdb,0x4b,0x54,0x4b,0x9f,0x4b,0x88,0x96,0x4d,0xe3,0xfe,0xec,0x7f,0x34,0x6a,0xb5,0xff,0x0,0x1, + 0x0,0xfe,0x78,0xff,0x0,0x1,0x0,0xb5,0x6a,0xe9,0x4b,0x6a,0x4b,0x4b,0x6a,0x4b,0x0,0x0,0x0,0x2,0x0,0x64,0xff,0xf6,0x4,0x64,0x4,0xb9,0x0,0x1e, + 0x0,0x22,0x0,0x25,0x40,0x22,0x21,0x1,0x4,0x3,0x1,0x4a,0x0,0x4,0x0,0x1,0x0,0x4,0x1,0x62,0x0,0x3,0x3,0x16,0x4b,0x2,0x1,0x0,0x0,0xf, + 0x0,0x4c,0x14,0x2a,0x13,0x13,0x25,0x5,0x7,0x19,0x2b,0x24,0x15,0x14,0x6,0x7,0x6,0x23,0x22,0x26,0x27,0x3,0x21,0x3,0x6,0x6,0x23,0x22,0x27,0x26, + 0x26,0x35,0x34,0x37,0x1,0x36,0x36,0x33,0x32,0x16,0x17,0x1,0x1,0x21,0x3,0x23,0x4,0x64,0x1b,0x1e,0x1a,0xe,0x14,0x1a,0x8,0x6d,0xfe,0x8,0x6d,0x8, + 0x1a,0x14,0xe,0x1a,0x1e,0x1b,0x6,0x1,0x8a,0xd,0x34,0x2f,0x2f,0x34,0xd,0x1,0x8a,0xfd,0x3e,0x1,0x8f,0xc5,0x4,0x42,0xd,0x13,0x1a,0xa,0x8,0x16, + 0x18,0x1,0x26,0xfe,0xda,0x18,0x16,0x8,0xa,0x1a,0x13,0xd,0x12,0x4,0x24,0x23,0x1e,0x1e,0x23,0xfb,0xdc,0x1,0x86,0x2,0x39,0x0,0x0,0x0,0xff,0xff, + 0x0,0x64,0xff,0xf6,0x4,0x64,0x6,0x95,0x0,0x27,0x1,0x80,0x0,0x1c,0x1,0xe,0x1,0x2,0x0,0x2,0x0,0x0,0x0,0x9,0xb1,0x0,0x1,0xb8,0x1,0xe, + 0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0x64,0xff,0xf6,0x4,0x64,0x6,0x6a,0x0,0x27,0x1,0x82,0x0,0x0,0x1,0xe,0x1,0x2,0x0,0x2,0x0,0x0,0x0,0x9, + 0xb1,0x0,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0x64,0xff,0xf6,0x4,0x64,0x6,0x82,0x0,0x27,0x1,0x92,0x0,0x0,0x1,0xe,0x1,0x2, + 0x0,0x2,0x0,0x0,0x0,0x9,0xb1,0x0,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0x64,0xff,0xf6,0x4,0x64,0x6,0x61,0x0,0x22,0x0,0x2, + 0x0,0x0,0x1,0x7,0x1,0x93,0xff,0xfe,0x1,0xe,0x0,0x9,0xb1,0x2,0x2,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0x64,0xff,0xf6,0x4,0x64, + 0x6,0x95,0x0,0x27,0x1,0x95,0xff,0xe4,0x1,0xe,0x1,0x2,0x0,0x2,0x0,0x0,0x0,0x9,0xb1,0x0,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff, + 0x0,0x64,0xff,0xf6,0x4,0x64,0x5,0xf0,0x0,0x27,0x1,0x97,0x0,0x0,0x1,0xe,0x1,0x2,0x0,0x2,0x0,0x0,0x0,0x9,0xb1,0x0,0x1,0xb8,0x1,0xe, + 0xb0,0x33,0x2b,0x0,0x0,0x2,0x0,0x64,0xfe,0x28,0x4,0x8c,0x4,0xb9,0x0,0x34,0x0,0x38,0x0,0x6d,0xb5,0x37,0x1,0x7,0x4,0x1,0x4a,0x4b,0xb0,0x1b, + 0x50,0x58,0x40,0x23,0x0,0x1,0x2,0x3,0x2,0x1,0x3,0x70,0x0,0x7,0x0,0x2,0x1,0x7,0x2,0x62,0x6,0x1,0x5,0x0,0x0,0x5,0x0,0x60,0x0,0x4, + 0x4,0x16,0x4b,0x0,0x3,0x3,0xf,0x3,0x4c,0x1b,0x40,0x2a,0x0,0x1,0x2,0x3,0x2,0x1,0x3,0x70,0x0,0x6,0x3,0x5,0x3,0x6,0x5,0x70,0x0,0x7, + 0x0,0x2,0x1,0x7,0x2,0x62,0x0,0x5,0x0,0x0,0x5,0x0,0x60,0x0,0x4,0x4,0x16,0x4b,0x0,0x3,0x3,0xf,0x3,0x4c,0x59,0x40,0xb,0x12,0x31,0x2d, + 0x2a,0x13,0x12,0x15,0x25,0x8,0x7,0x1c,0x2b,0x0,0x15,0x14,0x6,0x7,0x6,0x23,0x22,0x26,0x35,0x34,0x36,0x37,0x33,0x27,0x3,0x21,0x3,0x6,0x6,0x23, + 0x22,0x27,0x26,0x26,0x35,0x34,0x37,0x1,0x36,0x36,0x33,0x32,0x16,0x17,0x1,0x16,0x15,0x14,0x7,0x33,0x6,0x6,0x15,0x14,0x16,0x33,0x32,0x37,0x36,0x33, + 0x32,0x17,0x1,0x21,0x3,0x23,0x4,0x8c,0x11,0x11,0x4b,0x59,0x5d,0x6b,0x61,0x5b,0x16,0x3,0x6d,0xfe,0x8,0x6d,0x8,0x1a,0x14,0xe,0x1a,0x1e,0x1b,0x6, + 0x1,0x8a,0xd,0x34,0x2f,0x2f,0x34,0xd,0x1,0x8a,0x7,0x8,0x2,0x6a,0x5b,0x2a,0x23,0x34,0x3d,0x8,0x3,0x1e,0x9,0xfd,0x12,0x1,0x8f,0xc5,0x4,0xfe, + 0x7e,0x7,0x16,0x19,0x6,0x1a,0x68,0x57,0x51,0x96,0x4e,0x8,0x1,0x26,0xfe,0xda,0x18,0x16,0x8,0xa,0x1a,0x13,0xd,0x12,0x4,0x24,0x23,0x1e,0x1e,0x23, + 0xfb,0xdc,0x15,0xa,0xe,0xb,0x5e,0x86,0x3c,0x23,0x26,0x11,0x2,0x36,0x3,0x4a,0x2,0x39,0xff,0xff,0x0,0x64,0xff,0xf6,0x4,0x64,0x7,0x5,0x0,0x27, + 0x1,0x99,0x0,0x0,0x0,0xf0,0x1,0x2,0x0,0x2,0x0,0x0,0x0,0x8,0xb1,0x0,0x2,0xb0,0xf0,0xb0,0x33,0x2b,0x0,0x0,0xff,0xff,0x0,0x64,0xff,0xf6, + 0x4,0x64,0x6,0x3b,0x0,0x27,0x1,0x9a,0x0,0x0,0x1,0xe,0x1,0x2,0x0,0x2,0x0,0x0,0x0,0x9,0xb1,0x0,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0, + 0x0,0x2,0x0,0x14,0xff,0xf6,0x4,0x92,0x4,0xa3,0x0,0x30,0x0,0x34,0x0,0x7c,0x4b,0xb0,0x31,0x50,0x58,0x40,0x29,0x0,0x5,0x0,0x6,0x9,0x5,0x6, + 0x61,0xb,0x1,0x9,0x0,0x1,0x7,0x9,0x1,0x61,0x8,0x1,0x4,0x4,0x3,0x59,0x0,0x3,0x3,0xe,0x4b,0xa,0x1,0x7,0x7,0x0,0x5b,0x2,0x1,0x0, + 0x0,0xf,0x0,0x4c,0x1b,0x40,0x2d,0x0,0x5,0x0,0x6,0x9,0x5,0x6,0x61,0xb,0x1,0x9,0x0,0x1,0x7,0x9,0x1,0x61,0x8,0x1,0x4,0x4,0x3,0x59, + 0x0,0x3,0x3,0xe,0x4b,0xa,0x1,0x7,0x7,0x0,0x59,0x0,0x0,0x0,0xf,0x4b,0x0,0x2,0x2,0xf,0x2,0x4c,0x59,0x40,0x18,0x31,0x31,0x0,0x0,0x31, + 0x34,0x31,0x34,0x33,0x32,0x0,0x30,0x0,0x2f,0x24,0x21,0x24,0x5a,0x13,0x13,0x44,0xc,0x7,0x1b,0x2b,0x24,0x16,0x15,0x14,0x6,0x23,0x21,0x23,0x22,0x26, + 0x35,0x11,0x21,0x3,0x6,0x6,0x23,0x22,0x27,0x26,0x26,0x35,0x34,0x37,0x1,0x36,0x36,0x3b,0x2,0x21,0x32,0x16,0x15,0x14,0x6,0x23,0x21,0x11,0x21,0x32, + 0x16,0x15,0x14,0x6,0x23,0x21,0x11,0x21,0x1,0x11,0x23,0x3,0x4,0x75,0x1d,0x1d,0x22,0xfe,0x54,0x29,0x22,0x1d,0xfe,0xee,0x82,0x9,0x1a,0x14,0xe,0x1a, + 0x1d,0x1b,0x7,0x1,0x93,0xe,0x29,0x22,0x69,0x65,0x1,0x6a,0x22,0x1d,0x1d,0x22,0xfe,0x96,0x1,0x3d,0x22,0x1d,0x1d,0x22,0xfe,0xc3,0x1,0x7e,0xfd,0xec, + 0x19,0xc3,0x90,0x21,0x27,0x27,0x21,0x1f,0x25,0x1,0x2e,0xfe,0xb2,0x18,0x16,0x8,0x9,0x1a,0x13,0xf,0x11,0x4,0xb,0x25,0x1f,0x21,0x27,0x27,0x21,0xfe, + 0x99,0x21,0x27,0x27,0x21,0xfe,0x74,0x1,0x72,0x2,0x11,0xfd,0xef,0x0,0x0,0x3,0x0,0xcd,0x0,0x0,0x4,0x32,0x4,0xa3,0x0,0x16,0x0,0x1e,0x0,0x27, + 0x0,0x43,0x40,0x40,0x6,0x1,0x5,0x2,0x1,0x4a,0x0,0x2,0x8,0x1,0x5,0x4,0x2,0x5,0x61,0x7,0x1,0x3,0x3,0x1,0x59,0x6,0x1,0x1,0x1,0xe, + 0x4b,0x0,0x4,0x4,0x0,0x59,0x0,0x0,0x0,0xf,0x0,0x4c,0x1f,0x1f,0x17,0x17,0x0,0x0,0x1f,0x27,0x1f,0x26,0x22,0x20,0x17,0x1e,0x17,0x1d,0x1a,0x18, + 0x0,0x16,0x0,0x14,0x3c,0x9,0x7,0x15,0x2b,0x0,0x16,0x16,0x15,0x14,0x6,0x7,0x16,0x16,0x15,0x14,0x6,0x6,0x23,0x21,0x22,0x26,0x35,0x11,0x34,0x36, + 0x33,0x21,0x5,0x11,0x21,0x32,0x36,0x35,0x34,0x23,0x1,0x11,0x21,0x32,0x36,0x35,0x34,0x26,0x23,0x2,0xf9,0xa4,0x53,0x46,0x40,0x60,0x68,0x5a,0xac,0x7a, + 0xfe,0x5a,0x22,0x1d,0x1d,0x22,0x1,0x78,0xfe,0xdf,0x1,0xb,0x73,0x75,0xe8,0xfe,0xf5,0x1,0x3b,0x77,0x83,0x81,0x7e,0x4,0xa3,0x53,0x94,0x62,0x4f,0x77, + 0x25,0x24,0x91,0x6a,0x69,0x97,0x50,0x1f,0x25,0x4,0x1b,0x25,0x1f,0x90,0xfe,0x90,0x5f,0x58,0xb9,0xfe,0x0,0xfe,0x7d,0x61,0x5f,0x62,0x61,0x0,0x0,0x0, + 0x0,0x1,0x0,0x96,0xff,0xea,0x4,0x32,0x4,0xb9,0x0,0x29,0x0,0x3c,0x40,0x39,0x4,0x1,0x0,0x1,0x1,0x4a,0x0,0x0,0x1,0x3,0x1,0x0,0x3,0x70, + 0x0,0x3,0x2,0x1,0x3,0x2,0x6e,0x0,0x1,0x1,0x5,0x5b,0x6,0x1,0x5,0x5,0x16,0x4b,0x0,0x2,0x2,0x4,0x5b,0x0,0x4,0x4,0x17,0x4,0x4c,0x0, + 0x0,0x0,0x29,0x0,0x28,0x27,0x22,0x26,0x23,0x17,0x7,0x7,0x19,0x2b,0x0,0x16,0x17,0x16,0x15,0x14,0x7,0x6,0x23,0x22,0x27,0x26,0x23,0x22,0x6,0x6, + 0x15,0x14,0x16,0x16,0x33,0x32,0x37,0x36,0x33,0x32,0x17,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x22,0x26,0x2,0x35,0x34,0x12,0x36,0x33,0x2,0xfc,0xb6,0x5b, + 0x25,0xd,0x15,0x20,0xe,0x14,0x97,0x9f,0x6c,0xa3,0x59,0x59,0xa3,0x6c,0x9f,0x97,0x14,0xe,0x20,0x15,0xd,0x25,0x5b,0xb6,0x64,0x99,0xe9,0x80,0x80,0xe9, + 0x99,0x4,0xb9,0x31,0x34,0x16,0x22,0x13,0x1d,0x2c,0xb,0x5a,0x72,0xd3,0x8e,0x8e,0xd4,0x72,0x5a,0xb,0x2c,0x1d,0x13,0x22,0x16,0x34,0x31,0x99,0x1,0x18, + 0xb7,0xb7,0x1,0x17,0x99,0x0,0x0,0x0,0xff,0xff,0x0,0x96,0xff,0xea,0x4,0x32,0x6,0x95,0x0,0x27,0x1,0x80,0x0,0x1c,0x1,0xe,0x1,0x2,0x0,0xe, + 0x0,0x0,0x0,0x9,0xb1,0x0,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0x96,0xff,0xea,0x4,0x32,0x6,0x84,0x0,0x22,0x0,0xe,0x0,0x0, + 0x1,0x7,0x1,0x83,0x0,0x32,0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0x0,0x1,0x0,0x96,0xfe,0x28,0x4,0x32,0x4,0xb9, + 0x0,0x48,0x0,0x9e,0x40,0xf,0x36,0x1,0x7,0x8,0x29,0x9,0x2,0x1,0x9,0x15,0x1,0x2,0x4,0x3,0x4a,0x4b,0xb0,0xa,0x50,0x58,0x40,0x38,0x0,0x7, + 0x8,0x0,0x8,0x7,0x0,0x70,0x0,0x0,0x9,0x8,0x0,0x9,0x6e,0x0,0x1,0x9,0x5,0x4,0x1,0x68,0x0,0x3,0x5,0x4,0x5,0x3,0x4,0x70,0x0,0x9, + 0x0,0x5,0x3,0x9,0x5,0x63,0x0,0x4,0x0,0x2,0x4,0x2,0x60,0x0,0x8,0x8,0x6,0x5b,0x0,0x6,0x6,0x16,0x8,0x4c,0x1b,0x40,0x39,0x0,0x7,0x8, + 0x0,0x8,0x7,0x0,0x70,0x0,0x0,0x9,0x8,0x0,0x9,0x6e,0x0,0x1,0x9,0x5,0x9,0x1,0x5,0x70,0x0,0x3,0x5,0x4,0x5,0x3,0x4,0x70,0x0,0x9, + 0x0,0x5,0x3,0x9,0x5,0x63,0x0,0x4,0x0,0x2,0x4,0x2,0x60,0x0,0x8,0x8,0x6,0x5b,0x0,0x6,0x6,0x16,0x8,0x4c,0x59,0x40,0xe,0x47,0x45,0x23, + 0x17,0x2b,0x24,0x24,0x17,0x24,0x18,0x20,0xa,0x7,0x1d,0x2b,0x24,0x33,0x32,0x17,0x16,0x15,0x14,0x7,0x6,0x7,0x15,0x36,0x16,0x15,0x14,0x6,0x23,0x22, + 0x26,0x27,0x26,0x35,0x34,0x37,0x36,0x33,0x32,0x17,0x16,0x16,0x33,0x32,0x36,0x35,0x34,0x26,0x23,0x7,0x22,0x26,0x35,0x35,0x26,0x26,0x2,0x35,0x34,0x12, + 0x36,0x33,0x32,0x16,0x17,0x16,0x15,0x14,0x7,0x6,0x23,0x22,0x27,0x26,0x23,0x22,0x6,0x6,0x15,0x14,0x16,0x16,0x33,0x32,0x37,0x3,0xe2,0xe,0x20,0x15, + 0xd,0x25,0x9c,0xa8,0x65,0x66,0x7c,0x79,0x44,0x80,0x33,0x17,0x8,0x11,0x1d,0x8,0x5,0x2b,0x67,0x2f,0x33,0x3a,0x36,0x3e,0x27,0xf,0x13,0x82,0xc3,0x6a, + 0x80,0xe9,0x99,0x64,0xb6,0x5b,0x25,0xd,0x15,0x20,0xe,0x14,0x97,0x9f,0x6c,0xa3,0x59,0x59,0xa3,0x6c,0x9f,0x97,0xe3,0x2c,0x1d,0x13,0x22,0x16,0x58,0xc, + 0x5c,0x1,0x5e,0x4b,0x57,0x68,0x18,0x18,0xa,0x19,0xe,0x15,0x2d,0x2,0x10,0x16,0x21,0x1f,0x21,0x20,0x1,0x15,0x17,0xa1,0x14,0xa2,0x1,0x6,0xa6,0xb7, + 0x1,0x17,0x99,0x31,0x34,0x16,0x22,0x13,0x1d,0x2c,0xb,0x5a,0x72,0xd3,0x8e,0x8e,0xd4,0x72,0x5a,0x0,0xff,0xff,0x0,0x96,0xff,0xea,0x4,0x32,0x6,0x82, + 0x0,0x27,0x1,0x92,0x0,0x3c,0x1,0xe,0x1,0x2,0x0,0xe,0x0,0x0,0x0,0x9,0xb1,0x0,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0x96, + 0xff,0xea,0x4,0x32,0x6,0x61,0x0,0x27,0x1,0x94,0x0,0x32,0x1,0xe,0x1,0x2,0x0,0xe,0x0,0x0,0x0,0x9,0xb1,0x0,0x1,0xb8,0x1,0xe,0xb0,0x33, + 0x2b,0x0,0x0,0x2,0x0,0xaf,0x0,0x0,0x4,0x4c,0x4,0xa3,0x0,0x10,0x0,0x1b,0x0,0x2c,0x40,0x29,0x5,0x1,0x3,0x3,0x1,0x59,0x4,0x1,0x1,0x1, + 0xe,0x4b,0x0,0x2,0x2,0x0,0x59,0x0,0x0,0x0,0xf,0x0,0x4c,0x11,0x11,0x0,0x0,0x11,0x1b,0x11,0x1a,0x14,0x12,0x0,0x10,0x0,0xe,0x36,0x6,0x7, + 0x15,0x2b,0x0,0x16,0x12,0x15,0x14,0x2,0x6,0x23,0x21,0x22,0x26,0x35,0x11,0x34,0x36,0x33,0x21,0x7,0x11,0x33,0x32,0x36,0x36,0x35,0x34,0x26,0x26,0x23, + 0x2,0xdb,0xee,0x83,0x83,0xee,0x9d,0xfe,0xb0,0x22,0x1d,0x1d,0x22,0x1,0x50,0xf9,0xe6,0x78,0xaf,0x60,0x60,0xaf,0x78,0x4,0xa3,0x8f,0xfe,0xf3,0xb6,0xb6, + 0xfe,0xf4,0x8f,0x1f,0x25,0x4,0x1b,0x25,0x1f,0x90,0xfc,0x7d,0x65,0xca,0x92,0x92,0xca,0x66,0x0,0x2,0x0,0x32,0x0,0x0,0x4,0x60,0x4,0xa3,0x0,0x19, + 0x0,0x2d,0x0,0x3c,0x40,0x39,0x5,0x1,0x2,0x6,0x1,0x1,0x7,0x2,0x1,0x63,0x0,0x4,0x4,0x3,0x59,0x8,0x1,0x3,0x3,0xe,0x4b,0x9,0x1,0x7, + 0x7,0x0,0x59,0x0,0x0,0x0,0xf,0x0,0x4c,0x1a,0x1a,0x0,0x0,0x1a,0x2d,0x1a,0x2c,0x2b,0x29,0x25,0x23,0x22,0x20,0x0,0x19,0x0,0x17,0x24,0x23,0x36, + 0xa,0x7,0x17,0x2b,0x0,0x16,0x12,0x15,0x14,0x2,0x6,0x23,0x21,0x22,0x26,0x35,0x11,0x23,0x22,0x26,0x35,0x34,0x36,0x33,0x33,0x11,0x34,0x36,0x33,0x21, + 0x12,0x36,0x36,0x35,0x34,0x26,0x26,0x23,0x23,0x11,0x33,0x32,0x16,0x15,0x14,0x6,0x23,0x23,0x11,0x33,0x2,0xef,0xee,0x83,0x83,0xee,0x9d,0xfe,0xce,0x22, + 0x1d,0x70,0x22,0x1d,0x1d,0x22,0x70,0x1d,0x22,0x1,0x32,0x65,0xaf,0x60,0x60,0xaf,0x78,0xc8,0xdc,0x22,0x1d,0x1d,0x22,0xdc,0xc8,0x4,0xa3,0x8f,0xfe,0xf3, + 0xb6,0xb6,0xfe,0xf4,0x8f,0x1f,0x25,0x1,0xd8,0x21,0x27,0x27,0x21,0x1,0xb3,0x25,0x1f,0xfb,0xed,0x65,0xca,0x92,0x92,0xca,0x66,0xfe,0x99,0x21,0x27,0x27, + 0x21,0xfe,0x74,0x0,0xff,0xff,0x0,0xaf,0x0,0x0,0x4,0x4c,0x6,0x84,0x0,0x22,0x0,0x14,0x0,0x0,0x1,0x7,0x1,0x83,0xff,0xe2,0x1,0xe,0x0,0x9, + 0xb1,0x2,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0xaf,0x0,0x0,0x4,0x4c,0x5,0xf0,0x0,0x22,0x0,0x14,0x0,0x0,0x1,0x7,0x1,0x97, + 0xff,0xe2,0x1,0xe,0x0,0x9,0xb1,0x2,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0x0,0x1,0x0,0xe1,0x0,0x0,0x4,0x10,0x4,0xa3,0x0,0x22,0x0,0x2f, + 0x40,0x2c,0x0,0x3,0x0,0x4,0x5,0x3,0x4,0x61,0x0,0x2,0x2,0x1,0x59,0x0,0x1,0x1,0xe,0x4b,0x6,0x1,0x5,0x5,0x0,0x59,0x0,0x0,0x0,0xf, + 0x0,0x4c,0x0,0x0,0x0,0x22,0x0,0x21,0x24,0x21,0x24,0x45,0x44,0x7,0x7,0x19,0x2b,0x24,0x16,0x15,0x14,0x6,0x23,0x21,0x23,0x22,0x26,0x35,0x11,0x34, + 0x36,0x33,0x33,0x21,0x32,0x16,0x15,0x14,0x6,0x23,0x21,0x11,0x21,0x32,0x16,0x15,0x14,0x6,0x23,0x21,0x11,0x21,0x3,0xf3,0x1d,0x1d,0x22,0xfd,0x78,0x29, + 0x22,0x1d,0x1d,0x22,0x57,0x2,0x46,0x22,0x1d,0x1d,0x22,0xfd,0xba,0x1,0xc9,0x22,0x1d,0x1d,0x22,0xfe,0x37,0x2,0x5a,0x90,0x21,0x27,0x27,0x21,0x1f,0x25, + 0x4,0x1b,0x25,0x1f,0x21,0x27,0x27,0x21,0xfe,0x99,0x21,0x27,0x27,0x21,0xfe,0x74,0x0,0x0,0xff,0xff,0x0,0xe1,0x0,0x0,0x4,0x10,0x6,0x95,0x0,0x22, + 0x0,0x18,0x0,0x0,0x1,0x7,0x1,0x80,0xff,0xe0,0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0xe1,0x0,0x0, + 0x4,0x10,0x6,0x6a,0x0,0x22,0x0,0x18,0x0,0x0,0x1,0x7,0x1,0x82,0x0,0xa,0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0, + 0xff,0xff,0x0,0xe1,0x0,0x0,0x4,0x10,0x6,0x84,0x0,0x22,0x0,0x18,0x0,0x0,0x1,0x7,0x1,0x83,0x0,0xa,0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8, + 0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0xe1,0x0,0x0,0x4,0x10,0x6,0x82,0x0,0x22,0x0,0x18,0x0,0x0,0x1,0x7,0x1,0x92,0x0,0x1e,0x1,0xe, + 0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0xe1,0x0,0x0,0x4,0x10,0x6,0x61,0x0,0x22,0x0,0x18,0x0,0x0,0x1,0x7, + 0x1,0x93,0x0,0x14,0x1,0xe,0x0,0x9,0xb1,0x1,0x2,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0xe1,0x0,0x0,0x4,0x10,0x6,0x61,0x0,0x27, + 0x1,0x94,0x0,0x14,0x1,0xe,0x1,0x2,0x0,0x18,0x0,0x0,0x0,0x9,0xb1,0x0,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0xe1,0x0,0x0, + 0x4,0x10,0x6,0x95,0x0,0x22,0x0,0x18,0x0,0x0,0x1,0x7,0x1,0x95,0x0,0x20,0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0, + 0xff,0xff,0x0,0xe1,0x0,0x0,0x4,0x10,0x5,0xf0,0x0,0x27,0x1,0x97,0x0,0xa,0x1,0xe,0x1,0x2,0x0,0x18,0x0,0x0,0x0,0x9,0xb1,0x0,0x1,0xb8, + 0x1,0xe,0xb0,0x33,0x2b,0x0,0x0,0x1,0x0,0xe1,0xfe,0x28,0x4,0x19,0x4,0xa3,0x0,0x3c,0x0,0x6f,0x4b,0xb0,0x1b,0x50,0x58,0x40,0x26,0x0,0x4,0x0, + 0x5,0x6,0x4,0x5,0x61,0x9,0x1,0x8,0x0,0x0,0x8,0x0,0x5f,0x0,0x3,0x3,0x2,0x59,0x0,0x2,0x2,0xe,0x4b,0x0,0x6,0x6,0x1,0x5b,0x7,0x1, + 0x1,0x1,0xf,0x1,0x4c,0x1b,0x40,0x2d,0x0,0x9,0x1,0x8,0x1,0x9,0x8,0x70,0x0,0x4,0x0,0x5,0x6,0x4,0x5,0x61,0x0,0x8,0x0,0x0,0x8,0x0, + 0x5f,0x0,0x3,0x3,0x2,0x59,0x0,0x2,0x2,0xe,0x4b,0x0,0x6,0x6,0x1,0x5b,0x7,0x1,0x1,0x1,0xf,0x1,0x4c,0x59,0x40,0xe,0x3b,0x38,0x26,0x14, + 0x21,0x24,0x21,0x24,0x45,0x35,0x25,0xa,0x7,0x1d,0x2b,0x0,0x15,0x14,0x6,0x7,0x6,0x23,0x22,0x26,0x35,0x34,0x36,0x37,0x21,0x23,0x22,0x26,0x35,0x11, + 0x34,0x36,0x33,0x33,0x21,0x32,0x16,0x15,0x14,0x6,0x23,0x21,0x11,0x21,0x32,0x16,0x15,0x14,0x6,0x23,0x21,0x11,0x21,0x32,0x16,0x15,0x14,0x6,0x23,0x23, + 0x6,0x6,0x15,0x14,0x16,0x33,0x32,0x37,0x36,0x33,0x32,0x17,0x4,0x19,0x11,0x11,0x4b,0x59,0x5d,0x6b,0x50,0x4c,0xfe,0x22,0x29,0x22,0x1d,0x1d,0x22,0x57, + 0x2,0x46,0x22,0x1d,0x1d,0x22,0xfd,0xba,0x1,0xc9,0x22,0x1d,0x1d,0x22,0xfe,0x37,0x2,0x5a,0x22,0x1d,0x1d,0x22,0x4,0x59,0x4d,0x2a,0x23,0x34,0x3d,0x8, + 0x3,0x1e,0x9,0xfe,0x7e,0x7,0x16,0x19,0x6,0x1a,0x68,0x57,0x49,0x8a,0x46,0x1f,0x25,0x4,0x1b,0x25,0x1f,0x21,0x27,0x27,0x21,0xfe,0x99,0x21,0x27,0x27, + 0x21,0xfe,0x74,0x21,0x27,0x27,0x21,0x53,0x7a,0x37,0x23,0x26,0x11,0x2,0x36,0x0,0x0,0x1,0x0,0xf5,0xff,0xf6,0x4,0x10,0x4,0xa3,0x0,0x1d,0x0,0x29, + 0x40,0x26,0x0,0x1,0x0,0x2,0x3,0x1,0x2,0x61,0x0,0x0,0x0,0x4,0x59,0x5,0x1,0x4,0x4,0xe,0x4b,0x0,0x3,0x3,0xf,0x3,0x4c,0x0,0x0,0x0, + 0x1d,0x0,0x1a,0x33,0x24,0x21,0x24,0x6,0x7,0x18,0x2b,0x0,0x16,0x15,0x14,0x6,0x23,0x21,0x11,0x21,0x32,0x16,0x15,0x14,0x6,0x23,0x21,0x11,0x14,0x6, + 0x23,0x23,0x22,0x26,0x35,0x11,0x34,0x36,0x33,0x33,0x21,0x3,0xf3,0x1d,0x1d,0x22,0xfd,0xba,0x1,0xd3,0x22,0x1d,0x1d,0x22,0xfe,0x2d,0x21,0x29,0x2,0x29, + 0x21,0x1f,0x20,0x57,0x2,0x46,0x4,0xa3,0x21,0x27,0x27,0x21,0xfe,0x71,0x21,0x27,0x27,0x21,0xfe,0x43,0x23,0x1e,0x1e,0x23,0x4,0x28,0x25,0x1f,0x0,0x1, + 0x0,0x6e,0xff,0xea,0x4,0x3b,0x4,0xb9,0x0,0x31,0x0,0x43,0x40,0x40,0x27,0x1,0x4,0x5,0x1,0x4a,0x15,0x1,0x3,0x1,0x49,0x0,0x2,0x3,0x6,0x3, + 0x2,0x6,0x70,0x7,0x1,0x6,0x0,0x5,0x4,0x6,0x5,0x61,0x0,0x3,0x3,0x1,0x5b,0x0,0x1,0x1,0x16,0x4b,0x0,0x4,0x4,0x0,0x5b,0x0,0x0,0x0, + 0x17,0x0,0x4c,0x0,0x0,0x0,0x31,0x0,0x2e,0x22,0x26,0x23,0x17,0x26,0x27,0x8,0x7,0x1a,0x2b,0x0,0x16,0x15,0x11,0x14,0x7,0x6,0x6,0x23,0x22,0x26, + 0x2,0x35,0x34,0x12,0x36,0x33,0x32,0x16,0x17,0x16,0x15,0x14,0x7,0x6,0x23,0x22,0x27,0x26,0x23,0x22,0x6,0x6,0x15,0x14,0x16,0x16,0x33,0x32,0x37,0x11, + 0x21,0x22,0x26,0x35,0x34,0x36,0x33,0x21,0x33,0x4,0x1e,0x1d,0x24,0x52,0xd6,0x75,0x9c,0xee,0x82,0x89,0xf3,0x9a,0x67,0xb3,0x59,0x26,0xb,0x14,0x21,0x10, + 0x12,0x8e,0xa9,0x6d,0xac,0x63,0x5c,0xa8,0x6e,0xaf,0x7c,0xfe,0xf5,0x22,0x1d,0x1d,0x22,0x1,0x46,0x1c,0x2,0x52,0x1f,0x25,0xfe,0x81,0x31,0x14,0x2f,0x31, + 0x99,0x1,0x18,0xb7,0xb6,0x1,0x17,0x9a,0x2b,0x30,0x15,0x24,0x13,0x1b,0x2d,0xa,0x51,0x72,0xd4,0x8d,0x8e,0xd4,0x72,0x39,0x1,0xb,0x21,0x27,0x27,0x21, + 0xff,0xff,0x0,0x6e,0xff,0xea,0x4,0x3b,0x6,0x6a,0x0,0x22,0x0,0x23,0x0,0x0,0x1,0x7,0x1,0x82,0x0,0x1e,0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8, + 0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0x6e,0xff,0xea,0x4,0x3b,0x6,0x82,0x0,0x27,0x1,0x92,0x0,0x28,0x1,0xe,0x1,0x2,0x0,0x23,0x0,0x0, + 0x0,0x9,0xb1,0x0,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0x6e,0xfd,0xf2,0x4,0x3b,0x4,0xb9,0x0,0x22,0x0,0x23,0x0,0x0,0x0,0x3, + 0x1,0x7e,0x4,0xdc,0x0,0x0,0xff,0xff,0x0,0x6e,0xff,0xea,0x4,0x3b,0x6,0x61,0x0,0x27,0x1,0x94,0x0,0x1e,0x1,0xe,0x1,0x2,0x0,0x23,0x0,0x0, + 0x0,0x9,0xb1,0x0,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0x0,0x1,0x0,0xaf,0xff,0xf6,0x4,0x1d,0x4,0xad,0x0,0x23,0x0,0x27,0x40,0x24,0x0,0x4, + 0x0,0x1,0x0,0x4,0x1,0x61,0x6,0x5,0x2,0x3,0x3,0xe,0x4b,0x2,0x1,0x0,0x0,0xf,0x0,0x4c,0x0,0x0,0x0,0x23,0x0,0x21,0x13,0x35,0x33,0x13, + 0x35,0x7,0x7,0x19,0x2b,0x0,0x16,0x15,0x11,0x14,0x6,0x23,0x23,0x22,0x26,0x35,0x11,0x21,0x11,0x14,0x6,0x23,0x23,0x22,0x26,0x35,0x11,0x34,0x36,0x33, + 0x33,0x32,0x16,0x15,0x11,0x21,0x11,0x34,0x36,0x33,0x33,0x3,0xfc,0x21,0x21,0x29,0x2,0x29,0x21,0xfd,0xbe,0x21,0x29,0x2,0x29,0x21,0x21,0x29,0x2,0x29, + 0x21,0x2,0x42,0x21,0x29,0x2,0x4,0xad,0x1e,0x23,0xfb,0xcb,0x23,0x1e,0x1e,0x23,0x1,0xe5,0xfe,0x1b,0x23,0x1e,0x1e,0x23,0x4,0x35,0x23,0x1e,0x1e,0x23, + 0xfe,0x40,0x1,0xc0,0x23,0x1e,0x0,0x2,0x0,0x1e,0xff,0xf6,0x4,0xae,0x4,0xad,0x0,0x35,0x0,0x39,0x0,0x6a,0x4b,0xb0,0x24,0x50,0x58,0x40,0x24,0x0, + 0xb,0x0,0x2,0x1,0xb,0x2,0x61,0x8,0x1,0x6,0x6,0xe,0x4b,0xa,0x4,0x2,0x0,0x0,0x5,0x5b,0xc,0x9,0x7,0x3,0x5,0x5,0x19,0x4b,0x3,0x1, + 0x1,0x1,0xf,0x1,0x4c,0x1b,0x40,0x22,0xc,0x9,0x7,0x3,0x5,0xa,0x4,0x2,0x0,0xb,0x5,0x0,0x63,0x0,0xb,0x0,0x2,0x1,0xb,0x2,0x61,0x8, + 0x1,0x6,0x6,0xe,0x4b,0x3,0x1,0x1,0x1,0xf,0x1,0x4c,0x59,0x40,0x16,0x0,0x0,0x39,0x38,0x37,0x36,0x0,0x35,0x0,0x34,0x33,0x13,0x33,0x24,0x23, + 0x33,0x13,0x33,0x24,0xd,0x7,0x1d,0x2b,0x0,0x16,0x15,0x14,0x6,0x23,0x23,0x11,0x14,0x6,0x23,0x23,0x22,0x26,0x35,0x11,0x21,0x11,0x14,0x6,0x23,0x23, + 0x22,0x26,0x35,0x11,0x23,0x22,0x26,0x35,0x34,0x36,0x33,0x33,0x35,0x34,0x36,0x33,0x33,0x32,0x16,0x15,0x15,0x21,0x35,0x34,0x36,0x33,0x33,0x32,0x16,0x15, + 0x15,0x33,0x7,0x21,0x15,0x21,0x4,0x91,0x1d,0x1d,0x22,0x52,0x21,0x29,0x2,0x29,0x21,0xfd,0xbe,0x21,0x29,0x2,0x29,0x21,0x52,0x22,0x1d,0x1d,0x22,0x52, + 0x21,0x29,0x2,0x29,0x21,0x2,0x42,0x21,0x29,0x2,0x29,0x21,0x52,0xe8,0xfd,0xbe,0x2,0x42,0x3,0xce,0x21,0x27,0x27,0x21,0xfc,0xf9,0x23,0x1e,0x1e,0x23, + 0x1,0xe5,0xfe,0x1b,0x23,0x1e,0x1e,0x23,0x3,0x7,0x21,0x27,0x27,0x21,0x9e,0x23,0x1e,0x1e,0x23,0x9e,0x9e,0x23,0x1e,0x1e,0x23,0x9e,0x90,0x92,0x0,0x0, + 0xff,0xff,0x0,0xaf,0xff,0xf6,0x4,0x1d,0x6,0x82,0x0,0x22,0x0,0x28,0x0,0x0,0x1,0x7,0x1,0x92,0x0,0x0,0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8, + 0x1,0xe,0xb0,0x33,0x2b,0x0,0x0,0x1,0x0,0xc8,0x0,0x0,0x4,0x4,0x4,0xa3,0x0,0x1f,0x0,0x29,0x40,0x26,0x4,0x1,0x2,0x2,0x3,0x59,0x0,0x3, + 0x3,0xe,0x4b,0x6,0x5,0x2,0x1,0x1,0x0,0x59,0x0,0x0,0x0,0xf,0x0,0x4c,0x0,0x0,0x0,0x1f,0x0,0x1e,0x24,0x34,0x21,0x24,0x34,0x7,0x7,0x19, + 0x2b,0x24,0x16,0x15,0x14,0x6,0x23,0x21,0x22,0x26,0x35,0x34,0x36,0x33,0x21,0x11,0x21,0x22,0x26,0x35,0x34,0x36,0x33,0x21,0x32,0x16,0x15,0x14,0x6,0x23, + 0x21,0x11,0x21,0x3,0xe7,0x1d,0x1d,0x22,0xfd,0x42,0x22,0x1d,0x1d,0x22,0x1,0x14,0xff,0x0,0x22,0x1d,0x1d,0x22,0x2,0x96,0x22,0x1d,0x1d,0x22,0xff,0x0, + 0x1,0x14,0x90,0x21,0x27,0x27,0x21,0x21,0x27,0x27,0x21,0x3,0x83,0x21,0x27,0x27,0x21,0x21,0x27,0x27,0x21,0xfc,0x7d,0x0,0x0,0x0,0x0,0x2,0x0,0xb3, + 0xff,0xea,0x4,0x1e,0x4,0xad,0x0,0xf,0x0,0x2c,0x0,0x67,0xb5,0x1b,0x1,0x0,0x4,0x1,0x4a,0x4b,0xb0,0x28,0x50,0x58,0x40,0x1c,0x0,0x3,0x1,0x4, + 0x1,0x3,0x4,0x70,0x7,0x5,0x6,0x3,0x1,0x1,0xe,0x4b,0x0,0x4,0x4,0x0,0x5b,0x2,0x1,0x0,0x0,0xf,0x0,0x4c,0x1b,0x40,0x20,0x0,0x3,0x1, + 0x4,0x1,0x3,0x4,0x70,0x7,0x5,0x6,0x3,0x1,0x1,0xe,0x4b,0x0,0x0,0x0,0xf,0x4b,0x0,0x4,0x4,0x2,0x5b,0x0,0x2,0x2,0x17,0x2,0x4c,0x59, + 0x40,0x16,0x10,0x10,0x0,0x0,0x10,0x2c,0x10,0x2a,0x25,0x23,0x1f,0x1e,0x17,0x15,0x0,0xf,0x0,0xd,0x35,0x8,0x7,0x15,0x2b,0x0,0x16,0x15,0x11,0x14, + 0x6,0x23,0x23,0x22,0x26,0x35,0x11,0x34,0x36,0x33,0x33,0x20,0x16,0x15,0x11,0x14,0x6,0x23,0x22,0x26,0x27,0x26,0x35,0x34,0x37,0x36,0x33,0x32,0x17,0x16, + 0x16,0x33,0x32,0x36,0x35,0x11,0x34,0x36,0x33,0x33,0x1,0x28,0x21,0x21,0x29,0x2,0x29,0x21,0x21,0x29,0x2,0x2,0xfe,0x21,0x8d,0x84,0x37,0x80,0x30,0x27, + 0xb,0x15,0x24,0xc,0x10,0x27,0x5c,0x2c,0x3f,0x3b,0x21,0x29,0x2,0x4,0xad,0x1e,0x23,0xfb,0xcb,0x23,0x1e,0x1e,0x23,0x4,0x35,0x23,0x1e,0x1e,0x23,0xfc, + 0x91,0x86,0x8d,0x1a,0x18,0x14,0x21,0x11,0x1b,0x31,0x7,0x13,0x16,0x4a,0x53,0x3,0x51,0x23,0x1e,0x0,0x0,0x0,0xff,0xff,0x0,0xc8,0x0,0x0,0x4,0x4, + 0x6,0x95,0x0,0x22,0x0,0x2b,0x0,0x0,0x1,0x7,0x1,0x80,0x0,0x0,0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff, + 0x0,0xc8,0x0,0x0,0x4,0x4,0x6,0x6a,0x0,0x22,0x0,0x2b,0x0,0x0,0x1,0x7,0x1,0x82,0x0,0x0,0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0xe, + 0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0xc8,0x0,0x0,0x4,0x4,0x6,0x82,0x0,0x22,0x0,0x2b,0x0,0x0,0x1,0x7,0x1,0x92,0x0,0x0,0x1,0xe,0x0,0x9, + 0xb1,0x1,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0xc8,0x0,0x0,0x4,0x4,0x6,0x61,0x0,0x22,0x0,0x2b,0x0,0x0,0x1,0x7,0x1,0x93, + 0x0,0x0,0x1,0xe,0x0,0x9,0xb1,0x1,0x2,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0xc8,0x0,0x0,0x4,0x4,0x6,0x61,0x0,0x22,0x0,0x2b, + 0x0,0x0,0x1,0x7,0x1,0x94,0x0,0x0,0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0xc8,0x0,0x0,0x4,0x4, + 0x6,0x95,0x0,0x22,0x0,0x2b,0x0,0x0,0x1,0x7,0x1,0x95,0x0,0x0,0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff, + 0x0,0xc8,0x0,0x0,0x4,0x4,0x5,0xf0,0x0,0x22,0x0,0x2b,0x0,0x0,0x1,0x7,0x1,0x97,0x0,0x0,0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0xe, + 0xb0,0x33,0x2b,0x0,0x0,0x1,0x0,0xc8,0xfe,0x28,0x4,0x4,0x4,0xa3,0x0,0x39,0x0,0x69,0x4b,0xb0,0x1b,0x50,0x58,0x40,0x21,0x2,0x1,0x1,0x0,0x3, + 0x1,0x3,0x5f,0x8,0x1,0x6,0x6,0x7,0x59,0x0,0x7,0x7,0xe,0x4b,0xa,0x9,0x2,0x5,0x5,0x0,0x59,0x4,0x1,0x0,0x0,0xf,0x0,0x4c,0x1b,0x40, + 0x28,0x0,0x2,0x0,0x1,0x0,0x2,0x1,0x70,0x0,0x1,0x0,0x3,0x1,0x3,0x5f,0x8,0x1,0x6,0x6,0x7,0x59,0x0,0x7,0x7,0xe,0x4b,0xa,0x9,0x2, + 0x5,0x5,0x0,0x59,0x4,0x1,0x0,0x0,0xf,0x0,0x4c,0x59,0x40,0x12,0x0,0x0,0x0,0x39,0x0,0x38,0x24,0x34,0x21,0x24,0x25,0x27,0x31,0x25,0x24,0xb, + 0x7,0x1d,0x2b,0x24,0x16,0x15,0x14,0x6,0x23,0x21,0x6,0x6,0x15,0x14,0x16,0x33,0x32,0x37,0x36,0x33,0x32,0x17,0x16,0x15,0x14,0x6,0x7,0x6,0x23,0x22, + 0x26,0x35,0x34,0x36,0x37,0x21,0x22,0x26,0x35,0x34,0x36,0x33,0x21,0x11,0x21,0x22,0x26,0x35,0x34,0x36,0x33,0x21,0x32,0x16,0x15,0x14,0x6,0x23,0x21,0x11, + 0x21,0x3,0xe7,0x1d,0x1d,0x22,0xfe,0xf0,0x59,0x4d,0x2a,0x23,0x34,0x3d,0x8,0x3,0x1e,0x9,0x2,0x11,0x11,0x4b,0x59,0x5d,0x6b,0x50,0x4c,0xfe,0xf8,0x22, + 0x1d,0x1d,0x22,0x1,0x14,0xff,0x0,0x22,0x1d,0x1d,0x22,0x2,0x96,0x22,0x1d,0x1d,0x22,0xff,0x0,0x1,0x14,0x90,0x21,0x27,0x27,0x21,0x53,0x7a,0x37,0x23, + 0x26,0x11,0x2,0x36,0x12,0x7,0x16,0x19,0x6,0x1a,0x68,0x57,0x49,0x8a,0x46,0x21,0x27,0x27,0x21,0x3,0x83,0x21,0x27,0x27,0x21,0x21,0x27,0x27,0x21,0xfc, + 0x7d,0x0,0xff,0xff,0x0,0xc8,0x0,0x0,0x4,0x4,0x6,0x3b,0x0,0x27,0x1,0x9a,0x0,0x0,0x1,0xe,0x1,0x2,0x0,0x2b,0x0,0x0,0x0,0x9,0xb1,0x0, + 0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0x0,0x1,0x0,0xb8,0xff,0xea,0x3,0xba,0x4,0xa3,0x0,0x20,0x0,0x2e,0x40,0x2b,0x0,0x1,0x3,0x2,0x3,0x1, + 0x2,0x70,0x0,0x3,0x3,0x4,0x59,0x5,0x1,0x4,0x4,0xe,0x4b,0x0,0x2,0x2,0x0,0x5b,0x0,0x0,0x0,0x17,0x0,0x4c,0x0,0x0,0x0,0x20,0x0,0x1d, + 0x22,0x24,0x17,0x25,0x6,0x7,0x18,0x2b,0x0,0x16,0x15,0x11,0x14,0x6,0x23,0x22,0x26,0x27,0x26,0x35,0x34,0x37,0x36,0x33,0x32,0x17,0x16,0x16,0x33,0x32, + 0x35,0x11,0x21,0x22,0x26,0x35,0x34,0x36,0x33,0x21,0x33,0x3,0x9b,0x1f,0xc3,0xb2,0x65,0xbf,0x4b,0x1e,0x12,0x1a,0x1e,0x11,0x13,0x3e,0x94,0x4e,0xde,0xfe, + 0x97,0x22,0x1d,0x1d,0x22,0x1,0xa2,0x1e,0x4,0xa3,0x1f,0x25,0xfc,0xee,0xab,0xb8,0x3b,0x37,0x17,0x1d,0x17,0x1e,0x27,0xe,0x2f,0x31,0xed,0x2,0xa8,0x21, + 0x27,0x27,0x21,0x0,0x0,0x0,0xff,0xff,0x0,0xb8,0xff,0xea,0x4,0x45,0x6,0x82,0x0,0x27,0x1,0x92,0x0,0xa0,0x1,0xe,0x1,0x2,0x0,0x36,0x0,0x0, + 0x0,0x9,0xb1,0x0,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0x0,0x1,0x0,0xd7,0xff,0xed,0x4,0x47,0x4,0xb8,0x0,0x25,0x0,0x3d,0x40,0x9,0x24,0x1a, + 0x9,0x8,0x4,0x0,0x2,0x1,0x4a,0x4b,0xb0,0x2d,0x50,0x58,0x40,0xd,0x3,0x1,0x2,0x2,0xe,0x4b,0x1,0x1,0x0,0x0,0x17,0x0,0x4c,0x1b,0x40,0x11, + 0x0,0x3,0x3,0x16,0x4b,0x0,0x2,0x2,0xe,0x4b,0x1,0x1,0x0,0x0,0x17,0x0,0x4c,0x59,0xb6,0x25,0x35,0x36,0x24,0x4,0x7,0x18,0x2b,0x24,0x15,0x14, + 0x7,0x6,0x23,0x22,0x27,0x1,0x7,0x11,0x14,0x6,0x23,0x23,0x22,0x26,0x35,0x11,0x34,0x36,0x33,0x33,0x32,0x16,0x15,0x11,0x1,0x36,0x33,0x32,0x17,0x16, + 0x15,0x14,0x7,0x1,0x1,0x4,0x47,0x2b,0x21,0x16,0x19,0x13,0xfe,0x62,0xae,0x21,0x29,0x2,0x29,0x21,0x21,0x29,0x2,0x29,0x21,0x2,0x3b,0x10,0x18,0x1c, + 0x20,0x27,0x16,0xfe,0x67,0x1,0xb2,0x45,0x11,0x1c,0x18,0x13,0x19,0x2,0x20,0xb8,0xfe,0xc9,0x23,0x1e,0x1e,0x23,0x4,0x35,0x23,0x1e,0x1e,0x23,0xfd,0xdc, + 0x2,0x5f,0x11,0x15,0x19,0x1a,0x14,0x16,0xfe,0x4f,0xfd,0xc7,0xff,0xff,0x0,0xd7,0xfd,0xf2,0x4,0x47,0x4,0xb8,0x0,0x22,0x0,0x38,0x0,0x0,0x0,0x3, + 0x1,0x7e,0x4,0xc8,0x0,0x0,0x0,0x1,0x0,0xf5,0x0,0x0,0x4,0x10,0x4,0xad,0x0,0x14,0x0,0x1f,0x40,0x1c,0x0,0x1,0x1,0xe,0x4b,0x3,0x1,0x2, + 0x2,0x0,0x5a,0x0,0x0,0x0,0xf,0x0,0x4c,0x0,0x0,0x0,0x14,0x0,0x13,0x35,0x44,0x4,0x7,0x16,0x2b,0x24,0x16,0x15,0x14,0x6,0x23,0x21,0x23,0x22, + 0x26,0x35,0x11,0x34,0x36,0x33,0x33,0x32,0x16,0x15,0x11,0x21,0x3,0xf3,0x1d,0x1d,0x22,0xfd,0x8c,0x29,0x23,0x1c,0x21,0x29,0x2,0x29,0x21,0x2,0x46,0x90, + 0x21,0x27,0x27,0x21,0x1f,0x25,0x4,0x28,0x23,0x1e,0x1e,0x23,0xfc,0x24,0x0,0x0,0xff,0xff,0x0,0xe1,0x0,0x0,0x4,0x10,0x6,0x95,0x0,0x27,0x1,0x80, + 0xff,0x40,0x1,0xe,0x1,0x2,0x0,0x3a,0x0,0x0,0x0,0x9,0xb1,0x0,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0xf5,0x0,0x0,0x4,0x10, + 0x4,0xad,0x0,0x22,0x0,0x3a,0x0,0x0,0x1,0x7,0x1,0x81,0x1,0xcc,0xff,0x83,0x0,0x9,0xb1,0x1,0x1,0xb8,0xff,0x83,0xb0,0x33,0x2b,0x0,0xff,0xff, + 0x0,0xf5,0xfd,0xf2,0x4,0x10,0x4,0xad,0x0,0x22,0x0,0x3a,0x0,0x0,0x0,0x3,0x1,0x7e,0x4,0xc8,0x0,0x0,0xff,0xff,0x0,0xf5,0x0,0x0,0x4,0x10, + 0x4,0xad,0x0,0x22,0x0,0x3a,0x0,0x0,0x1,0x7,0x1,0x94,0x0,0x82,0xfe,0x2,0x0,0x9,0xb1,0x1,0x1,0xb8,0xfe,0x2,0xb0,0x33,0x2b,0x0,0x0,0x1, + 0x0,0x3c,0x0,0x0,0x4,0x10,0x4,0xad,0x0,0x2a,0x0,0x39,0x40,0x36,0x28,0x1e,0x15,0xb,0x4,0x1,0x3,0x1,0x4a,0x0,0x3,0x2,0x1,0x2,0x3,0x1, + 0x70,0x0,0x1,0x4,0x2,0x1,0x4,0x6e,0x0,0x2,0x2,0xe,0x4b,0x5,0x1,0x4,0x4,0x0,0x5a,0x0,0x0,0x0,0xf,0x0,0x4c,0x0,0x0,0x0,0x2a,0x0, + 0x29,0x25,0x39,0x25,0x44,0x6,0x7,0x18,0x2b,0x24,0x16,0x15,0x14,0x6,0x23,0x21,0x23,0x22,0x26,0x35,0x11,0x7,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37, + 0x37,0x11,0x34,0x36,0x33,0x33,0x32,0x16,0x15,0x11,0x37,0x36,0x33,0x32,0x17,0x16,0x15,0x14,0x7,0x5,0x11,0x21,0x3,0xf3,0x1d,0x1d,0x22,0xfd,0xc8,0x29, + 0x23,0x1c,0x88,0x18,0x10,0x1a,0x19,0x12,0x20,0xd5,0x21,0x29,0x2,0x29,0x21,0xce,0x18,0x10,0x1a,0x19,0x12,0x20,0xfe,0xe5,0x2,0xa,0x90,0x21,0x27,0x27, + 0x21,0x1f,0x25,0x1,0x7c,0x5c,0x10,0x25,0x1b,0x12,0x19,0x15,0x91,0x2,0x7,0x23,0x1e,0x1e,0x23,0xfe,0x5f,0x8c,0x10,0x25,0x1b,0x12,0x19,0x15,0xc0,0xfe, + 0x69,0x0,0x0,0x1,0x0,0x90,0xff,0xf6,0x4,0x3c,0x4,0xad,0x0,0x29,0x0,0x2e,0x40,0x2b,0x24,0x13,0xb,0x3,0x1,0x3,0x1,0x4a,0x0,0x1,0x3,0x0, + 0x3,0x1,0x0,0x70,0x5,0x4,0x2,0x3,0x3,0xe,0x4b,0x2,0x1,0x0,0x0,0xf,0x0,0x4c,0x0,0x0,0x0,0x29,0x0,0x27,0x35,0x36,0x26,0x35,0x6,0x7, + 0x18,0x2b,0x0,0x16,0x17,0x13,0x16,0x6,0x23,0x23,0x22,0x26,0x27,0x3,0x3,0x6,0x6,0x23,0x22,0x26,0x27,0x3,0x3,0x6,0x6,0x23,0x23,0x22,0x26,0x37, + 0x13,0x36,0x36,0x33,0x33,0x32,0x16,0x17,0x1,0x1,0x36,0x36,0x33,0x33,0x3,0xf7,0x25,0x1,0x1e,0x1,0x21,0x29,0x2,0x29,0x20,0x1,0x15,0xcf,0x10,0x2a, + 0x22,0x22,0x2a,0x10,0xcf,0x15,0x1,0x20,0x29,0x2,0x29,0x21,0x1,0x1e,0x1,0x26,0x25,0x2,0x23,0x2d,0x10,0x1,0x9,0x1,0x9,0x10,0x2e,0x23,0x2,0x4, + 0xad,0x1d,0x24,0xfb,0xcb,0x23,0x1e,0x1e,0x23,0x3,0x2d,0xfe,0x42,0x23,0x1d,0x1d,0x23,0x1,0xbe,0xfc,0xd3,0x23,0x1e,0x1e,0x23,0x4,0x35,0x24,0x1d,0x1f, + 0x22,0xfd,0xc6,0x2,0x3a,0x22,0x1f,0x0,0x0,0x0,0x0,0x1,0x0,0xaf,0xff,0xea,0x4,0x1d,0x4,0xad,0x0,0x21,0x0,0x51,0x4b,0xb0,0x28,0x50,0x58,0xb6, + 0x1b,0xa,0x2,0x0,0x2,0x1,0x4a,0x1b,0xb6,0x1b,0xa,0x2,0x1,0x2,0x1,0x4a,0x59,0x4b,0xb0,0x28,0x50,0x58,0x40,0xe,0x4,0x3,0x2,0x2,0x2,0xe, + 0x4b,0x1,0x1,0x0,0x0,0x17,0x0,0x4c,0x1b,0x40,0x12,0x4,0x3,0x2,0x2,0x2,0xe,0x4b,0x0,0x1,0x1,0xf,0x4b,0x0,0x0,0x0,0x17,0x0,0x4c,0x59, + 0x40,0xc,0x0,0x0,0x0,0x21,0x0,0x1f,0x35,0x37,0x25,0x5,0x7,0x17,0x2b,0x0,0x16,0x15,0x11,0x14,0x6,0x23,0x22,0x26,0x27,0x1,0x23,0x11,0x14,0x6, + 0x23,0x23,0x22,0x26,0x35,0x11,0x34,0x36,0x33,0x33,0x32,0x17,0x1,0x33,0x11,0x34,0x36,0x33,0x33,0x3,0xfd,0x20,0x25,0x26,0x1b,0x27,0x12,0xfd,0xc7,0x4, + 0x20,0x28,0x2,0x28,0x20,0x22,0x29,0x1b,0x2b,0x13,0x2,0x34,0x4,0x20,0x28,0x2,0x4,0xad,0x1e,0x23,0xfb,0xb9,0x1b,0x20,0x1c,0x1b,0x3,0x78,0xfc,0x9e, + 0x23,0x1e,0x1e,0x23,0x4,0x35,0x23,0x1e,0x1c,0xfc,0x8e,0x3,0x4d,0x23,0x1e,0x0,0x0,0x0,0xff,0xff,0x0,0xaf,0xff,0xea,0x4,0x1d,0x6,0x95,0x0,0x27, + 0x1,0x80,0xff,0xfe,0x1,0xe,0x1,0x2,0x0,0x41,0x0,0x0,0x0,0x9,0xb1,0x0,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0xaf,0xff,0xea, + 0x4,0x1d,0x6,0x84,0x0,0x22,0x0,0x41,0x0,0x0,0x1,0x7,0x1,0x83,0x0,0xa,0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0, + 0xff,0xff,0x0,0xaf,0xfd,0xf2,0x4,0x1d,0x4,0xad,0x0,0x22,0x0,0x41,0x0,0x0,0x0,0x3,0x1,0x7e,0x4,0xd2,0x0,0x0,0x0,0x1,0x0,0xaf,0xfe,0x66, + 0x4,0x1d,0x4,0xad,0x0,0x2f,0x0,0x3c,0x40,0x39,0x29,0x18,0x17,0x3,0x3,0x4,0xb,0x1,0x0,0x2,0x2,0x4a,0x0,0x1,0x3,0x2,0x3,0x1,0x2,0x70, + 0x6,0x5,0x2,0x4,0x4,0xe,0x4b,0x0,0x3,0x3,0xf,0x4b,0x0,0x2,0x2,0x0,0x5b,0x0,0x0,0x0,0x13,0x0,0x4c,0x0,0x0,0x0,0x2f,0x0,0x2d,0x35, + 0x38,0x23,0x17,0x25,0x7,0x7,0x19,0x2b,0x0,0x16,0x15,0x11,0x14,0x6,0x23,0x22,0x26,0x27,0x26,0x35,0x34,0x37,0x36,0x33,0x32,0x17,0x16,0x33,0x32,0x36, + 0x35,0x35,0x1,0x23,0x11,0x14,0x6,0x23,0x23,0x22,0x26,0x35,0x11,0x34,0x36,0x33,0x33,0x32,0x17,0x1,0x33,0x11,0x34,0x36,0x33,0x33,0x3,0xfd,0x20,0x8d, + 0x84,0x3a,0x78,0x35,0x27,0xb,0x15,0x22,0xf,0xd,0x60,0x51,0x3f,0x3b,0xfd,0xbe,0x4,0x20,0x28,0x2,0x28,0x20,0x22,0x29,0x1b,0x2b,0x13,0x2,0x34,0x4, + 0x20,0x28,0x2,0x4,0xad,0x1e,0x23,0xfb,0xd,0x86,0x8d,0x1b,0x17,0x11,0x23,0x10,0x1b,0x2f,0x6,0x28,0x4a,0x53,0x7e,0x3,0x86,0xfc,0x9e,0x23,0x1e,0x1e, + 0x23,0x4,0x35,0x23,0x1e,0x1c,0xfc,0x8e,0x3,0x4d,0x23,0x1e,0x0,0x0,0xff,0xff,0x0,0xaf,0xff,0xea,0x4,0x1d,0x6,0x3b,0x0,0x22,0x0,0x41,0x0,0x0, + 0x1,0x7,0x1,0x9a,0x0,0x0,0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0x0,0x2,0x0,0x5a,0xff,0xea,0x4,0x72,0x4,0xb9, + 0x0,0xf,0x0,0x1f,0x0,0x2c,0x40,0x29,0x5,0x1,0x3,0x3,0x1,0x5b,0x4,0x1,0x1,0x1,0x16,0x4b,0x0,0x2,0x2,0x0,0x5b,0x0,0x0,0x0,0x17,0x0, + 0x4c,0x10,0x10,0x0,0x0,0x10,0x1f,0x10,0x1e,0x18,0x16,0x0,0xf,0x0,0xe,0x26,0x6,0x7,0x15,0x2b,0x0,0x16,0x12,0x15,0x14,0x2,0x6,0x23,0x22,0x26, + 0x2,0x35,0x34,0x12,0x36,0x33,0xe,0x2,0x15,0x14,0x16,0x16,0x33,0x32,0x36,0x36,0x35,0x34,0x26,0x26,0x23,0x3,0x2,0xee,0x82,0x82,0xee,0x9c,0x9c,0xee, + 0x82,0x82,0xee,0x9c,0x6f,0xa8,0x5b,0x5b,0xa7,0x70,0x70,0xa7,0x5b,0x5b,0xa8,0x6f,0x4,0xb9,0x99,0xfe,0xe8,0xb7,0xb7,0xfe,0xe9,0x99,0x99,0x1,0x17,0xb7, + 0xb7,0x1,0x18,0x99,0x94,0x71,0xd4,0x8f,0x8f,0xd3,0x71,0x71,0xd3,0x8f,0x8f,0xd4,0x71,0x0,0xff,0xff,0x0,0x5a,0xff,0xea,0x4,0x72,0x6,0x95,0x0,0x22, + 0x0,0x47,0x0,0x0,0x1,0x7,0x1,0x80,0xff,0xea,0x1,0xe,0x0,0x9,0xb1,0x2,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0x5a,0xff,0xea, + 0x4,0x72,0x6,0x6a,0x0,0x22,0x0,0x47,0x0,0x0,0x1,0x7,0x1,0x82,0x0,0x0,0x1,0xe,0x0,0x9,0xb1,0x2,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0, + 0xff,0xff,0x0,0x5a,0xff,0xea,0x4,0x72,0x6,0x82,0x0,0x22,0x0,0x47,0x0,0x0,0x1,0x7,0x1,0x92,0x0,0x0,0x1,0xe,0x0,0x9,0xb1,0x2,0x1,0xb8, + 0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0x5a,0xff,0xea,0x4,0x72,0x6,0x61,0x0,0x22,0x0,0x47,0x0,0x0,0x1,0x7,0x1,0x93,0x0,0x0,0x1,0xe, + 0x0,0x9,0xb1,0x2,0x2,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0x5a,0xff,0xea,0x4,0x72,0x6,0x95,0x0,0x22,0x0,0x47,0x0,0x0,0x1,0x7, + 0x1,0x95,0x0,0x16,0x1,0xe,0x0,0x9,0xb1,0x2,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0x5a,0xff,0xea,0x4,0x72,0x6,0x84,0x0,0x22, + 0x0,0x47,0x0,0x0,0x1,0x7,0x1,0x96,0x0,0x0,0x1,0xe,0x0,0x9,0xb1,0x2,0x2,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0x5a,0xff,0xea, + 0x4,0x72,0x5,0xf0,0x0,0x22,0x0,0x47,0x0,0x0,0x1,0x7,0x1,0x97,0x0,0x0,0x1,0xe,0x0,0x9,0xb1,0x2,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0, + 0x0,0x3,0x0,0x5a,0xff,0xae,0x4,0x72,0x4,0xf7,0x0,0x25,0x0,0x2e,0x0,0x37,0x0,0x46,0x40,0x43,0x22,0x1b,0x2,0x4,0x2,0x35,0x34,0x28,0x27,0x25, + 0x12,0x6,0x5,0x4,0xf,0x8,0x2,0x0,0x5,0x3,0x4a,0x0,0x3,0x2,0x3,0x72,0x0,0x1,0x0,0x1,0x73,0x0,0x4,0x4,0x2,0x5b,0x0,0x2,0x2,0x16, + 0x4b,0x6,0x1,0x5,0x5,0x0,0x5b,0x0,0x0,0x0,0x17,0x0,0x4c,0x2f,0x2f,0x2f,0x37,0x2f,0x36,0x2b,0x13,0x2d,0x13,0x25,0x7,0x7,0x19,0x2b,0x0,0x16, + 0x15,0x14,0x2,0x6,0x23,0x22,0x27,0x7,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x37,0x26,0x26,0x35,0x34,0x12,0x36,0x33,0x32,0x17,0x37,0x36,0x33,0x32, + 0x17,0x16,0x15,0x14,0x7,0x7,0x0,0x17,0x1,0x26,0x23,0x22,0x6,0x6,0x15,0x0,0x36,0x36,0x35,0x34,0x27,0x1,0x16,0x33,0x4,0x2c,0x46,0x82,0xee,0x9c, + 0xa2,0x7b,0x4d,0x17,0x18,0x12,0x1d,0x25,0x10,0x58,0x41,0x46,0x82,0xee,0x9c,0xa3,0x79,0x4e,0x17,0x19,0x13,0x1c,0x23,0xf,0x59,0xfd,0xa,0x49,0x1,0xef, + 0x53,0x73,0x6f,0xa8,0x5b,0x1,0xe2,0xa7,0x5b,0x4a,0xfe,0x11,0x57,0x70,0x3,0xb5,0xde,0x86,0xb7,0xfe,0xe9,0x99,0x53,0x6f,0x20,0x13,0x19,0x1b,0x11,0x18, + 0x7f,0x51,0xde,0x85,0xb7,0x1,0x18,0x99,0x53,0x71,0x20,0x13,0x19,0x1b,0x12,0x17,0x81,0xfd,0x95,0x79,0x2,0xc8,0x3b,0x71,0xd4,0x8f,0xfe,0x2d,0x71,0xd3, + 0x8f,0xb6,0x7b,0xfd,0x37,0x3b,0x0,0x0,0xff,0xff,0x0,0x5a,0xff,0xea,0x4,0x72,0x6,0x3b,0x0,0x22,0x0,0x47,0x0,0x0,0x1,0x7,0x1,0x9a,0x0,0x0, + 0x1,0xe,0x0,0x9,0xb1,0x2,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0x0,0x2,0x0,0x54,0xff,0xfc,0x4,0x75,0x4,0xa7,0x0,0x23,0x0,0x2c,0x0,0x35, + 0x40,0x32,0x0,0x3,0x0,0x4,0x5,0x3,0x4,0x61,0x7,0x1,0x2,0x2,0x1,0x59,0x0,0x1,0x1,0xe,0x4b,0x6,0x8,0x2,0x5,0x5,0x0,0x59,0x0,0x0, + 0x0,0xf,0x0,0x4c,0x0,0x0,0x29,0x28,0x27,0x26,0x0,0x23,0x0,0x22,0x24,0x21,0x24,0x46,0x44,0x9,0x7,0x19,0x2b,0x24,0x16,0x15,0x14,0x6,0x23,0x21, + 0x23,0x6,0x26,0x2,0x35,0x34,0x12,0x36,0x17,0x33,0x21,0x32,0x16,0x15,0x14,0x6,0x23,0x21,0x11,0x33,0x32,0x16,0x15,0x14,0x6,0x23,0x23,0x11,0x21,0x0, + 0x16,0x16,0x17,0x11,0xe,0x2,0x15,0x4,0x58,0x1d,0x1d,0x22,0xfe,0x72,0x45,0x9f,0xee,0x82,0x82,0xee,0x9f,0x7d,0x1,0x42,0x22,0x1d,0x1d,0x22,0xfe,0xbe, + 0xf7,0x22,0x1d,0x1d,0x22,0xf7,0x1,0x56,0xfc,0xb8,0x56,0x9d,0x69,0x69,0x9d,0x56,0x90,0x21,0x27,0x27,0x21,0x4,0x8c,0x1,0xf,0xba,0xba,0x1,0xf,0x8d, + 0x4,0x21,0x27,0x27,0x21,0xfe,0x99,0x21,0x27,0x27,0x21,0xfe,0x74,0x1,0x32,0xca,0x6c,0x4,0x3,0x92,0x4,0x6b,0xcb,0x8f,0x0,0x0,0x0,0x2,0x0,0xeb, + 0xff,0xf6,0x4,0x30,0x4,0xa3,0x0,0x16,0x0,0x1f,0x0,0x30,0x40,0x2d,0x6,0x1,0x4,0x0,0x0,0x1,0x4,0x0,0x61,0x0,0x3,0x3,0x2,0x59,0x5,0x1, + 0x2,0x2,0xe,0x4b,0x0,0x1,0x1,0xf,0x1,0x4c,0x17,0x17,0x0,0x0,0x17,0x1f,0x17,0x1e,0x1d,0x1b,0x0,0x16,0x0,0x13,0x33,0x26,0x7,0x7,0x16,0x2b, + 0x0,0x16,0x16,0x15,0x14,0x6,0x6,0x23,0x21,0x11,0x14,0x6,0x23,0x23,0x22,0x26,0x35,0x11,0x34,0x36,0x33,0x33,0x21,0x12,0x36,0x35,0x34,0x26,0x23,0x23, + 0x11,0x33,0x3,0x9,0xc3,0x64,0x64,0xc3,0x89,0xff,0x1,0x21,0x29,0x2,0x29,0x21,0x1d,0x22,0x57,0x0,0xff,0x7d,0x99,0x99,0x92,0xea,0xea,0x4,0xa3,0x64, + 0xb4,0x77,0x76,0xb3,0x62,0xfe,0xae,0x23,0x1e,0x1e,0x23,0x4,0x28,0x25,0x1f,0xfd,0x76,0x83,0x78,0x7a,0x85,0xfe,0x6,0x0,0x0,0x2,0x0,0xeb,0xff,0xf6, + 0x4,0x26,0x4,0xad,0x0,0x1a,0x0,0x23,0x0,0x5e,0x4b,0xb0,0x2f,0x50,0x58,0x40,0x1f,0x7,0x1,0x5,0x0,0x0,0x1,0x5,0x0,0x61,0x0,0x2,0x2,0xe, + 0x4b,0x0,0x4,0x4,0x3,0x59,0x6,0x1,0x3,0x3,0x11,0x4b,0x0,0x1,0x1,0xf,0x1,0x4c,0x1b,0x40,0x1d,0x6,0x1,0x3,0x0,0x4,0x5,0x3,0x4,0x61, + 0x7,0x1,0x5,0x0,0x0,0x1,0x5,0x0,0x61,0x0,0x2,0x2,0xe,0x4b,0x0,0x1,0x1,0xf,0x1,0x4c,0x59,0x40,0x14,0x1b,0x1b,0x0,0x0,0x1b,0x23,0x1b, + 0x22,0x21,0x1f,0x0,0x1a,0x0,0x19,0x35,0x33,0x26,0x8,0x7,0x17,0x2b,0x0,0x16,0x16,0x15,0x14,0x6,0x6,0x23,0x21,0x15,0x14,0x6,0x23,0x23,0x22,0x26, + 0x35,0x11,0x34,0x36,0x33,0x33,0x32,0x16,0x15,0x15,0x21,0x12,0x36,0x35,0x34,0x26,0x23,0x21,0x11,0x21,0x3,0x21,0xad,0x58,0x58,0xac,0x7a,0xfe,0xd9,0x21, + 0x29,0x2,0x29,0x21,0x21,0x29,0x2,0x29,0x21,0x1,0x27,0x61,0x7f,0x7c,0x79,0xfe,0xee,0x1,0x12,0x3,0xb2,0x5b,0x9d,0x64,0x63,0x9c,0x59,0xc7,0x23,0x1e, + 0x1e,0x23,0x4,0x35,0x23,0x1e,0x1e,0x23,0xba,0xfd,0xdc,0x67,0x61,0x5e,0x6e,0xfe,0x6c,0x0,0x0,0x2,0x0,0x5a,0xff,0x58,0x4,0x72,0x4,0xb9,0x0,0x1a, + 0x0,0x35,0x0,0x68,0x40,0xc,0x2a,0x20,0x2,0x3,0x4,0x1a,0x9,0x2,0x1,0x3,0x2,0x4a,0x4b,0xb0,0xa,0x50,0x58,0x40,0x21,0x0,0x4,0x5,0x3,0x3, + 0x4,0x68,0x0,0x0,0x1,0x0,0x73,0x0,0x5,0x5,0x2,0x5b,0x0,0x2,0x2,0x16,0x4b,0x0,0x3,0x3,0x1,0x5c,0x0,0x1,0x1,0x17,0x1,0x4c,0x1b,0x40, + 0x22,0x0,0x4,0x5,0x3,0x5,0x4,0x3,0x70,0x0,0x0,0x1,0x0,0x73,0x0,0x5,0x5,0x2,0x5b,0x0,0x2,0x2,0x16,0x4b,0x0,0x3,0x3,0x1,0x5c,0x0, + 0x1,0x1,0x17,0x1,0x4c,0x59,0x40,0x9,0x28,0x27,0x29,0x26,0x23,0x25,0x6,0x7,0x1a,0x2b,0x5,0x16,0x15,0x14,0x7,0x6,0x23,0x22,0x27,0x27,0x6,0x23, + 0x22,0x26,0x2,0x35,0x34,0x12,0x36,0x33,0x32,0x16,0x12,0x15,0x14,0x2,0x7,0x0,0x16,0x16,0x33,0x32,0x37,0x27,0x26,0x35,0x34,0x37,0x36,0x33,0x32,0x17, + 0x17,0x36,0x36,0x35,0x34,0x26,0x26,0x23,0x22,0x6,0x6,0x15,0x3,0xea,0xf,0x2b,0x1d,0x15,0x1b,0x15,0x5a,0x4f,0x5d,0x9c,0xee,0x82,0x82,0xee,0x9c,0x9c, + 0xee,0x82,0x72,0x69,0xfd,0x5d,0x5b,0xa7,0x70,0x30,0x28,0x69,0xf,0x2b,0x1d,0x15,0x1a,0x16,0x72,0x47,0x4c,0x5b,0xa8,0x6f,0x6f,0xa8,0x5b,0x36,0x17,0x11, + 0x1d,0x1a,0x13,0x21,0x8c,0x1b,0x99,0x1,0x17,0xb7,0xb7,0x1,0x18,0x99,0x99,0xfe,0xe8,0xb7,0xac,0xfe,0xf6,0x4f,0x1,0x76,0xd3,0x71,0xa,0xa4,0x19,0xf, + 0x1c,0x1c,0x13,0x22,0xb2,0x3c,0xc7,0x83,0x8f,0xd4,0x71,0x71,0xd4,0x8f,0x0,0x0,0x0,0x2,0x0,0xeb,0xff,0xf1,0x4,0x19,0x4,0xa3,0x0,0x20,0x0,0x28, + 0x0,0x2b,0x40,0x28,0x1f,0x1,0x1,0x4,0x1,0x4a,0x0,0x4,0x0,0x1,0x0,0x4,0x1,0x61,0x0,0x5,0x5,0x3,0x59,0x0,0x3,0x3,0xe,0x4b,0x2,0x1, + 0x0,0x0,0x17,0x0,0x4c,0x23,0x28,0x45,0x33,0x12,0x25,0x6,0x7,0x1a,0x2b,0x24,0x15,0x14,0x6,0x7,0x6,0x23,0x22,0x27,0x1,0x23,0x11,0x14,0x6,0x23, + 0x23,0x22,0x26,0x35,0x11,0x34,0x36,0x3b,0x2,0x32,0x16,0x16,0x15,0x14,0x6,0x7,0x1,0x1,0x33,0x20,0x35,0x34,0x26,0x23,0x23,0x4,0x19,0x17,0x18,0x23, + 0x11,0x1b,0x12,0xfe,0xd6,0xde,0x21,0x29,0x2,0x29,0x21,0x1d,0x22,0x57,0xeb,0x85,0xb9,0x5e,0x85,0x80,0x1,0x8,0xfd,0x76,0xd6,0x1,0x17,0x8b,0x8c,0xd6, + 0x42,0x10,0xf,0x18,0xb,0xf,0x1c,0x1,0xcc,0xfe,0x5e,0x23,0x1e,0x1e,0x23,0x4,0x28,0x25,0x1f,0x59,0xa1,0x6d,0x82,0xaf,0x21,0xfe,0x6e,0x2,0x11,0xd3, + 0x6a,0x6d,0x0,0x0,0xff,0xff,0x0,0xeb,0xff,0xf1,0x4,0x19,0x6,0x95,0x0,0x27,0x1,0x80,0xff,0xf6,0x1,0xe,0x1,0x2,0x0,0x55,0x0,0x0,0x0,0x9, + 0xb1,0x0,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0xeb,0xff,0xf1,0x4,0x19,0x6,0x84,0x0,0x22,0x0,0x55,0x0,0x0,0x1,0x7,0x1,0x83, + 0xff,0xf6,0x1,0xe,0x0,0x9,0xb1,0x2,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0xeb,0xfd,0xf2,0x4,0x19,0x4,0xa3,0x0,0x22,0x0,0x55, + 0x0,0x0,0x0,0x3,0x1,0x7e,0x4,0xd2,0x0,0x0,0x0,0x1,0x0,0xa7,0xff,0xea,0x4,0x26,0x4,0xb9,0x0,0x3f,0x0,0x39,0x40,0x36,0x0,0x0,0x1,0x3, + 0x1,0x0,0x3,0x70,0x0,0x3,0x4,0x1,0x3,0x4,0x6e,0x0,0x1,0x1,0x5,0x5b,0x6,0x1,0x5,0x5,0x16,0x4b,0x0,0x4,0x4,0x2,0x5b,0x0,0x2,0x2, + 0x17,0x2,0x4c,0x0,0x0,0x0,0x3f,0x0,0x3e,0x2d,0x2b,0x27,0x26,0x1f,0x1d,0x23,0x16,0x7,0x7,0x16,0x2b,0x0,0x17,0x16,0x15,0x14,0x7,0x6,0x23,0x22, + 0x27,0x26,0x23,0x22,0x6,0x15,0x14,0x16,0x17,0x16,0x16,0x17,0x16,0x16,0x17,0x16,0x16,0x15,0x14,0x6,0x6,0x23,0x22,0x26,0x27,0x26,0x35,0x34,0x37,0x36, + 0x33,0x32,0x17,0x16,0x16,0x33,0x32,0x36,0x35,0x34,0x26,0x27,0x26,0x26,0x27,0x26,0x26,0x27,0x26,0x26,0x35,0x34,0x36,0x36,0x33,0x3,0x2d,0xaf,0x24,0xd, + 0x14,0x23,0xf,0x12,0x9a,0xbf,0x77,0x85,0x27,0x26,0x23,0x5b,0x55,0x52,0x6e,0x32,0x61,0x6d,0x63,0xc5,0x90,0x72,0xda,0x59,0x22,0xe,0x17,0x20,0x11,0x11, + 0x4b,0xb7,0x5e,0x8d,0x91,0x3f,0x3a,0x27,0x5a,0x4e,0x54,0x70,0x30,0x4e,0x56,0x66,0xb8,0x78,0x4,0xb9,0x64,0x15,0x23,0x16,0x1b,0x2d,0xa,0x5c,0x6a,0x5c, + 0x28,0x38,0x13,0x12,0x16,0xf,0xe,0x19,0x14,0x27,0x90,0x7c,0x69,0x9d,0x57,0x3d,0x37,0x15,0x22,0x17,0x1b,0x2c,0xb,0x30,0x3a,0x6d,0x5c,0x3e,0x4e,0x19, + 0x11,0x15,0xe,0xf,0x1b,0x16,0x23,0x7a,0x62,0x6b,0x9c,0x53,0x0,0x0,0xff,0xff,0x0,0xa7,0xff,0xea,0x4,0x26,0x6,0x95,0x0,0x22,0x0,0x59,0x0,0x0, + 0x1,0x7,0x1,0x80,0xff,0xea,0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0xa7,0xff,0xea,0x4,0x26,0x6,0x84, + 0x0,0x22,0x0,0x59,0x0,0x0,0x1,0x7,0x1,0x83,0xff,0xfb,0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0x0,0x1,0x0,0xa7, + 0xfe,0x28,0x4,0x26,0x4,0xb9,0x0,0x5f,0x0,0xf4,0x40,0xa,0x23,0x1,0x1,0x0,0xf,0x1,0x2,0x4,0x2,0x4a,0x4b,0xb0,0xa,0x50,0x58,0x40,0x3f,0x0, + 0x9,0xa,0x6,0xa,0x9,0x6,0x70,0x0,0x6,0x7,0xa,0x6,0x7,0x6e,0x0,0x1,0x0,0x5,0x4,0x1,0x68,0x0,0x5,0x3,0x0,0x5,0x66,0x0,0x3,0x4, + 0x0,0x3,0x4,0x6e,0x0,0x4,0x0,0x2,0x4,0x2,0x60,0x0,0xa,0xa,0x8,0x5b,0x0,0x8,0x8,0x16,0x4b,0x0,0x7,0x7,0x0,0x5b,0x0,0x0,0x0,0x17, + 0x0,0x4c,0x1b,0x4b,0xb0,0xc,0x50,0x58,0x40,0x40,0x0,0x9,0xa,0x6,0xa,0x9,0x6,0x70,0x0,0x6,0x7,0xa,0x6,0x7,0x6e,0x0,0x1,0x0,0x5,0x0, + 0x1,0x5,0x70,0x0,0x5,0x3,0x0,0x5,0x66,0x0,0x3,0x4,0x0,0x3,0x4,0x6e,0x0,0x4,0x0,0x2,0x4,0x2,0x60,0x0,0xa,0xa,0x8,0x5b,0x0,0x8, + 0x8,0x16,0x4b,0x0,0x7,0x7,0x0,0x5b,0x0,0x0,0x0,0x17,0x0,0x4c,0x1b,0x40,0x41,0x0,0x9,0xa,0x6,0xa,0x9,0x6,0x70,0x0,0x6,0x7,0xa,0x6, + 0x7,0x6e,0x0,0x1,0x0,0x5,0x0,0x1,0x5,0x70,0x0,0x5,0x3,0x0,0x5,0x3,0x6e,0x0,0x3,0x4,0x0,0x3,0x4,0x6e,0x0,0x4,0x0,0x2,0x4,0x2, + 0x60,0x0,0xa,0xa,0x8,0x5b,0x0,0x8,0x8,0x16,0x4b,0x0,0x7,0x7,0x0,0x5b,0x0,0x0,0x0,0x17,0x0,0x4c,0x59,0x59,0x40,0x11,0x51,0x4f,0x4c,0x4b, + 0x45,0x43,0x24,0x1c,0x24,0x24,0x17,0x24,0x11,0x12,0xb,0x7,0x1c,0x2b,0x24,0x6,0x6,0x7,0x15,0x36,0x16,0x15,0x14,0x6,0x23,0x22,0x26,0x27,0x26,0x35, + 0x34,0x37,0x36,0x33,0x32,0x17,0x16,0x16,0x33,0x32,0x36,0x35,0x34,0x26,0x23,0x7,0x22,0x26,0x35,0x35,0x26,0x26,0x27,0x26,0x35,0x34,0x37,0x36,0x33,0x32, + 0x17,0x16,0x16,0x33,0x32,0x36,0x35,0x34,0x26,0x27,0x26,0x26,0x27,0x26,0x26,0x27,0x26,0x26,0x35,0x34,0x36,0x36,0x33,0x32,0x17,0x16,0x15,0x14,0x7,0x6, + 0x23,0x22,0x27,0x26,0x23,0x22,0x6,0x15,0x14,0x16,0x17,0x16,0x16,0x17,0x16,0x16,0x17,0x16,0x16,0x15,0x4,0x26,0x57,0xaf,0x7f,0x65,0x66,0x7c,0x79,0x44, + 0x80,0x33,0x17,0x8,0x11,0x1d,0x8,0x5,0x2b,0x67,0x2f,0x33,0x3a,0x36,0x3e,0x27,0xf,0x13,0x5c,0xaf,0x49,0x22,0xe,0x17,0x20,0x11,0x11,0x4b,0xb7,0x5e, + 0x8d,0x91,0x3f,0x3a,0x27,0x5a,0x4e,0x54,0x70,0x30,0x4e,0x56,0x66,0xb8,0x78,0xeb,0xaf,0x24,0xd,0x14,0x23,0xf,0x12,0x9a,0xbf,0x77,0x85,0x27,0x26,0x23, + 0x5b,0x55,0x52,0x6e,0x32,0x61,0x6d,0xe5,0x97,0x5b,0x7,0x5d,0x1,0x5e,0x4b,0x57,0x68,0x18,0x18,0xa,0x19,0xe,0x15,0x2d,0x2,0x10,0x16,0x21,0x1f,0x21, + 0x20,0x1,0x15,0x17,0x9f,0x9,0x3a,0x2d,0x15,0x22,0x17,0x1b,0x2c,0xb,0x30,0x3a,0x6d,0x5c,0x3e,0x4e,0x19,0x11,0x15,0xe,0xf,0x1b,0x16,0x23,0x7a,0x62, + 0x6b,0x9c,0x53,0x64,0x15,0x23,0x16,0x1b,0x2d,0xa,0x5c,0x6a,0x5c,0x28,0x38,0x13,0x12,0x16,0xf,0xe,0x19,0x14,0x27,0x90,0x7c,0x0,0x0,0x0,0xff,0xff, + 0x0,0xa7,0xff,0xea,0x4,0x26,0x6,0x82,0x0,0x27,0x1,0x92,0x0,0x6,0x1,0xe,0x1,0x2,0x0,0x59,0x0,0x0,0x0,0x9,0xb1,0x0,0x1,0xb8,0x1,0xe, + 0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0xa7,0xfd,0xf2,0x4,0x26,0x4,0xb9,0x0,0x22,0x0,0x59,0x0,0x0,0x0,0x3,0x1,0x7e,0x4,0xc2,0x0,0x0,0x0,0x1, + 0x0,0x7a,0xff,0xf6,0x4,0x52,0x4,0xa3,0x0,0x17,0x0,0x21,0x40,0x1e,0x2,0x1,0x0,0x0,0x3,0x59,0x4,0x1,0x3,0x3,0xe,0x4b,0x0,0x1,0x1,0xf, + 0x1,0x4c,0x0,0x0,0x0,0x17,0x0,0x15,0x23,0x33,0x24,0x5,0x7,0x17,0x2b,0x0,0x16,0x15,0x14,0x6,0x23,0x21,0x11,0x14,0x6,0x23,0x23,0x22,0x26,0x35, + 0x11,0x21,0x22,0x26,0x35,0x34,0x36,0x33,0x21,0x4,0x35,0x1d,0x1d,0x22,0xfe,0x9e,0x21,0x29,0x2,0x29,0x21,0xfe,0x9e,0x22,0x1d,0x1d,0x22,0x3,0x5a,0x4, + 0xa3,0x21,0x27,0x27,0x21,0xfc,0x24,0x23,0x1e,0x1e,0x23,0x3,0xdc,0x21,0x27,0x27,0x21,0x0,0x0,0x0,0x0,0x1,0x0,0x7a,0xff,0xf6,0x4,0x52,0x4,0xa3, + 0x0,0x29,0x0,0x2f,0x40,0x2c,0x5,0x1,0x1,0x4,0x1,0x2,0x3,0x1,0x2,0x61,0x6,0x1,0x0,0x0,0x7,0x59,0x8,0x1,0x7,0x7,0xe,0x4b,0x0,0x3, + 0x3,0xf,0x3,0x4c,0x0,0x0,0x0,0x29,0x0,0x27,0x21,0x24,0x23,0x33,0x24,0x21,0x24,0x9,0x7,0x1b,0x2b,0x0,0x16,0x15,0x14,0x6,0x23,0x21,0x11,0x33, + 0x32,0x16,0x15,0x14,0x6,0x23,0x23,0x11,0x14,0x6,0x23,0x23,0x22,0x26,0x35,0x11,0x23,0x22,0x26,0x35,0x34,0x36,0x33,0x33,0x11,0x21,0x22,0x26,0x35,0x34, + 0x36,0x33,0x21,0x4,0x35,0x1d,0x1d,0x22,0xfe,0x9e,0xce,0x22,0x1d,0x1d,0x22,0xce,0x21,0x29,0x2,0x29,0x21,0xce,0x22,0x1d,0x1d,0x22,0xce,0xfe,0x9e,0x22, + 0x1d,0x1d,0x22,0x3,0x5a,0x4,0xa3,0x21,0x27,0x27,0x21,0xfe,0x3b,0x21,0x27,0x27,0x21,0xfe,0x79,0x23,0x1e,0x1e,0x23,0x1,0x87,0x21,0x27,0x27,0x21,0x1, + 0xc5,0x21,0x27,0x27,0x21,0x0,0x0,0x0,0xff,0xff,0x0,0x7a,0xff,0xf6,0x4,0x52,0x6,0x84,0x0,0x22,0x0,0x5f,0x0,0x0,0x1,0x7,0x1,0x83,0x0,0x0, + 0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0x0,0x1,0x0,0x7a,0xfe,0x28,0x4,0x52,0x4,0xa3,0x0,0x34,0x0,0x7e,0x40,0xb, + 0x29,0x9,0x2,0x1,0x0,0x15,0x1,0x2,0x4,0x2,0x4a,0x4b,0xb0,0xa,0x50,0x58,0x40,0x29,0x0,0x1,0x0,0x5,0x4,0x1,0x68,0x0,0x5,0x3,0x0,0x5, + 0x3,0x6e,0x0,0x3,0x4,0x0,0x3,0x4,0x6e,0x0,0x4,0x0,0x2,0x4,0x2,0x60,0x6,0x1,0x0,0x0,0x7,0x59,0x8,0x1,0x7,0x7,0xe,0x0,0x4c,0x1b, + 0x40,0x2a,0x0,0x1,0x0,0x5,0x0,0x1,0x5,0x70,0x0,0x5,0x3,0x0,0x5,0x3,0x6e,0x0,0x3,0x4,0x0,0x3,0x4,0x6e,0x0,0x4,0x0,0x2,0x4,0x2, + 0x60,0x6,0x1,0x0,0x0,0x7,0x59,0x8,0x1,0x7,0x7,0xe,0x0,0x4c,0x59,0x40,0x10,0x0,0x0,0x0,0x34,0x0,0x32,0x27,0x24,0x24,0x17,0x24,0x14,0x24, + 0x9,0x7,0x1b,0x2b,0x0,0x16,0x15,0x14,0x6,0x23,0x21,0x11,0x14,0x7,0x15,0x36,0x16,0x15,0x14,0x6,0x23,0x22,0x26,0x27,0x26,0x35,0x34,0x37,0x36,0x33, + 0x32,0x17,0x16,0x16,0x33,0x32,0x36,0x35,0x34,0x26,0x23,0x7,0x22,0x26,0x35,0x35,0x26,0x35,0x11,0x21,0x22,0x26,0x35,0x34,0x36,0x33,0x21,0x4,0x35,0x1d, + 0x1d,0x22,0xfe,0x9e,0x9,0x65,0x66,0x7c,0x79,0x44,0x80,0x33,0x17,0x8,0x11,0x1d,0x8,0x5,0x2b,0x67,0x2f,0x33,0x3a,0x36,0x3e,0x27,0xf,0x13,0x9,0xfe, + 0x9e,0x22,0x1d,0x1d,0x22,0x3,0x5a,0x4,0xa3,0x21,0x27,0x27,0x21,0xfc,0x24,0x1a,0xf,0x7f,0x1,0x5e,0x4b,0x57,0x68,0x18,0x18,0xa,0x19,0xe,0x15,0x2d, + 0x2,0x10,0x16,0x21,0x1f,0x21,0x20,0x1,0x15,0x17,0xbf,0xf,0x1a,0x3,0xdc,0x21,0x27,0x27,0x21,0x0,0xff,0xff,0x0,0x7a,0xfd,0xf2,0x4,0x52,0x4,0xa3, + 0x0,0x22,0x0,0x5f,0x0,0x0,0x0,0x3,0x1,0x7e,0x4,0xc8,0x0,0x0,0x0,0x1,0x0,0xaf,0xff,0xea,0x4,0x1d,0x4,0xad,0x0,0x1f,0x0,0x21,0x40,0x1e, + 0x4,0x3,0x2,0x1,0x1,0xe,0x4b,0x0,0x2,0x2,0x0,0x5b,0x0,0x0,0x0,0x17,0x0,0x4c,0x0,0x0,0x0,0x1f,0x0,0x1d,0x25,0x36,0x26,0x5,0x7,0x17, + 0x2b,0x0,0x16,0x15,0x11,0x14,0x6,0x6,0x23,0x22,0x26,0x26,0x35,0x11,0x34,0x36,0x33,0x33,0x32,0x16,0x15,0x11,0x14,0x16,0x33,0x32,0x36,0x35,0x11,0x34, + 0x36,0x33,0x33,0x3,0xfc,0x21,0x6e,0xc6,0x83,0x83,0xc6,0x6e,0x21,0x29,0x2,0x29,0x21,0x96,0x8b,0x8b,0x96,0x21,0x29,0x2,0x4,0xad,0x1e,0x23,0xfd,0x31, + 0x8c,0xc3,0x64,0x64,0xc3,0x8c,0x2,0xcf,0x23,0x1e,0x1e,0x23,0xfd,0x45,0x99,0x9a,0x9a,0x99,0x2,0xbb,0x23,0x1e,0xff,0xff,0x0,0xaf,0xff,0xea,0x4,0x1d, + 0x6,0x95,0x0,0x22,0x0,0x64,0x0,0x0,0x1,0x7,0x1,0x80,0x0,0x1c,0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff, + 0x0,0xaf,0xff,0xea,0x4,0x1d,0x6,0x6a,0x0,0x22,0x0,0x64,0x0,0x0,0x1,0x7,0x1,0x82,0x0,0x0,0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0xe, + 0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0xaf,0xff,0xea,0x4,0x1d,0x6,0x82,0x0,0x22,0x0,0x64,0x0,0x0,0x1,0x7,0x1,0x92,0x0,0x0,0x1,0xe,0x0,0x9, + 0xb1,0x1,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0xaf,0xff,0xea,0x4,0x1d,0x6,0x61,0x0,0x22,0x0,0x64,0x0,0x0,0x1,0x7,0x1,0x93, + 0x0,0x0,0x1,0xe,0x0,0x9,0xb1,0x1,0x2,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0xaf,0xff,0xea,0x4,0x1d,0x6,0x95,0x0,0x22,0x0,0x64, + 0x0,0x0,0x1,0x7,0x1,0x95,0xff,0xe4,0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0xaf,0xff,0xea,0x4,0x4f, + 0x6,0x84,0x0,0x22,0x0,0x64,0x0,0x0,0x1,0x7,0x1,0x96,0x0,0x0,0x1,0xe,0x0,0x9,0xb1,0x1,0x2,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff, + 0x0,0xaf,0xff,0xea,0x4,0x1d,0x5,0xf0,0x0,0x27,0x1,0x97,0x0,0x0,0x1,0xe,0x1,0x2,0x0,0x64,0x0,0x0,0x0,0x9,0xb1,0x0,0x1,0xb8,0x1,0xe, + 0xb0,0x33,0x2b,0x0,0x0,0x1,0x0,0xaf,0xfe,0x28,0x4,0x1d,0x4,0xad,0x0,0x38,0x0,0x58,0x4b,0xb0,0x1b,0x50,0x58,0x40,0x1a,0x1,0x1,0x0,0x0,0x2, + 0x0,0x2,0x5f,0x7,0x6,0x2,0x4,0x4,0xe,0x4b,0x0,0x5,0x5,0x3,0x5b,0x0,0x3,0x3,0x17,0x3,0x4c,0x1b,0x40,0x21,0x0,0x1,0x3,0x0,0x3,0x1, + 0x0,0x70,0x0,0x0,0x0,0x2,0x0,0x2,0x5f,0x7,0x6,0x2,0x4,0x4,0xe,0x4b,0x0,0x5,0x5,0x3,0x5b,0x0,0x3,0x3,0x17,0x3,0x4c,0x59,0x40,0xf, + 0x0,0x0,0x0,0x38,0x0,0x36,0x25,0x36,0x25,0x27,0x31,0x2b,0x8,0x7,0x1a,0x2b,0x0,0x16,0x15,0x11,0x14,0x6,0x7,0x6,0x6,0x15,0x14,0x16,0x33,0x32, + 0x37,0x36,0x33,0x32,0x17,0x16,0x15,0x14,0x6,0x7,0x6,0x23,0x22,0x26,0x35,0x34,0x36,0x37,0x23,0x22,0x26,0x26,0x35,0x11,0x34,0x36,0x33,0x33,0x32,0x16, + 0x15,0x11,0x14,0x16,0x33,0x32,0x36,0x35,0x11,0x34,0x36,0x33,0x33,0x3,0xfc,0x21,0x6e,0x64,0x69,0x5b,0x2a,0x23,0x34,0x3d,0x8,0x3,0x1e,0x9,0x2,0x11, + 0x11,0x4b,0x59,0x5d,0x6b,0x44,0x41,0xa,0x83,0xc6,0x6e,0x21,0x29,0x2,0x29,0x21,0x96,0x8b,0x8b,0x96,0x21,0x29,0x2,0x4,0xad,0x1e,0x23,0xfd,0x31,0x8b, + 0xc4,0x33,0x5d,0x86,0x3c,0x23,0x26,0x11,0x2,0x36,0x12,0x7,0x16,0x19,0x6,0x1a,0x68,0x57,0x44,0x7f,0x40,0x64,0xc3,0x8c,0x2,0xcf,0x23,0x1e,0x1e,0x23, + 0xfd,0x45,0x99,0x9a,0x9a,0x99,0x2,0xbb,0x23,0x1e,0x0,0x0,0xff,0xff,0x0,0xaf,0xff,0xea,0x4,0x1d,0x7,0x5,0x0,0x22,0x0,0x64,0x0,0x0,0x1,0x7, + 0x1,0x99,0x0,0x0,0x0,0xf0,0x0,0x8,0xb1,0x1,0x2,0xb0,0xf0,0xb0,0x33,0x2b,0x0,0x0,0xff,0xff,0x0,0xaf,0xff,0xea,0x4,0x1d,0x6,0x3b,0x0,0x27, + 0x1,0x9a,0x0,0x0,0x1,0xe,0x1,0x2,0x0,0x64,0x0,0x0,0x0,0x9,0xb1,0x0,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0x0,0x1,0x0,0x63,0xff,0xea, + 0x4,0x65,0x4,0xad,0x0,0x1d,0x0,0x1b,0x40,0x18,0x17,0x1,0x0,0x1,0x1,0x4a,0x2,0x1,0x1,0x1,0xe,0x4b,0x0,0x0,0x0,0x17,0x0,0x4c,0x16,0x29, + 0x27,0x3,0x7,0x17,0x2b,0x0,0x16,0x15,0x14,0x7,0x1,0x6,0x6,0x23,0x22,0x26,0x27,0x1,0x26,0x35,0x34,0x36,0x37,0x36,0x33,0x32,0x16,0x17,0x1,0x1, + 0x36,0x36,0x33,0x32,0x17,0x4,0x49,0x1c,0x7,0xfe,0x71,0xd,0x33,0x2b,0x2b,0x33,0xd,0xfe,0x71,0x7,0x1c,0x1e,0x1a,0xe,0x14,0x1a,0x8,0x1,0x69,0x1, + 0x69,0x8,0x1a,0x14,0xe,0x1a,0x4,0x9b,0x1a,0x13,0xa,0x15,0xfb,0xdc,0x23,0x1e,0x1e,0x23,0x4,0x24,0x15,0xa,0x13,0x1a,0xa,0x8,0x16,0x18,0xfc,0x37, + 0x3,0xc9,0x18,0x16,0x8,0x0,0x0,0x1,0x0,0x43,0xff,0xea,0x4,0x81,0x4,0xad,0x0,0x2b,0x0,0x4b,0x40,0xd,0x12,0x2,0x2,0x3,0x2,0x25,0x1d,0xb, + 0x3,0x0,0x3,0x2,0x4a,0x4b,0xb0,0x19,0x50,0x58,0x40,0x12,0x4,0x1,0x2,0x2,0xe,0x4b,0x0,0x3,0x3,0x11,0x4b,0x1,0x1,0x0,0x0,0x17,0x0,0x4c, + 0x1b,0x40,0x15,0x0,0x3,0x2,0x0,0x2,0x3,0x0,0x70,0x4,0x1,0x2,0x2,0xe,0x4b,0x1,0x1,0x0,0x0,0x17,0x0,0x4c,0x59,0xb7,0x36,0x26,0x38,0x24, + 0x27,0x5,0x7,0x19,0x2b,0x0,0x16,0x15,0x14,0x7,0x3,0x6,0x6,0x23,0x22,0x27,0x3,0x3,0x6,0x23,0x22,0x26,0x27,0x3,0x26,0x35,0x34,0x36,0x37,0x36, + 0x33,0x32,0x16,0x17,0x13,0x13,0x36,0x36,0x33,0x32,0x16,0x17,0x13,0x13,0x36,0x36,0x33,0x32,0x17,0x4,0x61,0x20,0x1,0x95,0x5,0x29,0x28,0x4d,0x17,0xcf, + 0xcf,0x17,0x4d,0x28,0x29,0x5,0x95,0x1,0x20,0x24,0x7,0xd,0x1e,0x1c,0x4,0x70,0xcf,0x7,0x23,0x20,0x20,0x23,0x7,0xcf,0x70,0x4,0x1c,0x1e,0xd,0x7, + 0x4,0xa7,0x1a,0x19,0xc,0x6,0xfb,0xc9,0x24,0x1d,0x41,0x2,0x4a,0xfd,0xb6,0x41,0x1d,0x24,0x4,0x37,0x6,0xc,0x19,0x1a,0x5,0x1,0x1b,0x1f,0xfc,0x8f, + 0x2,0x4b,0x14,0x14,0x14,0x14,0xfd,0xb5,0x3,0x71,0x1f,0x1b,0x1,0x0,0x0,0x0,0xff,0xff,0x0,0x43,0xff,0xea,0x4,0x81,0x6,0x95,0x0,0x22,0x0,0x70, + 0x0,0x0,0x1,0x7,0x1,0x80,0x0,0x1e,0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0x43,0xff,0xea,0x4,0x81, + 0x6,0x82,0x0,0x22,0x0,0x70,0x0,0x0,0x1,0x7,0x1,0x92,0x0,0x0,0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff, + 0x0,0x43,0xff,0xea,0x4,0x81,0x6,0x61,0x0,0x22,0x0,0x70,0x0,0x0,0x1,0x7,0x1,0x93,0x0,0x0,0x1,0xe,0x0,0x9,0xb1,0x1,0x2,0xb8,0x1,0xe, + 0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0x43,0xff,0xea,0x4,0x81,0x6,0x95,0x0,0x22,0x0,0x70,0x0,0x0,0x1,0x7,0x1,0x95,0xff,0xe2,0x1,0xe,0x0,0x9, + 0xb1,0x1,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0x0,0x1,0x0,0x95,0xff,0xf0,0x4,0x43,0x4,0xb3,0x0,0x28,0x0,0x22,0x40,0x1f,0x27,0x24,0x1d,0x13, + 0x10,0x8,0x6,0x0,0x2,0x1,0x4a,0x3,0x1,0x2,0x2,0x16,0x4b,0x1,0x1,0x0,0x0,0x17,0x0,0x4c,0x24,0x2c,0x25,0x24,0x4,0x7,0x18,0x2b,0x24,0x15, + 0x14,0x7,0x6,0x23,0x22,0x27,0x1,0x1,0x6,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x1,0x1,0x26,0x35,0x34,0x37,0x36,0x33,0x32,0x17,0x1,0x1,0x36, + 0x33,0x32,0x17,0x16,0x15,0x14,0x7,0x1,0x1,0x4,0x43,0x2d,0x25,0x14,0x1e,0x14,0xfe,0xbf,0xfe,0xc3,0xb,0x18,0xf,0x14,0x25,0x2d,0xf,0x1,0x6a,0xfe, + 0xad,0xf,0x2d,0x25,0x14,0x1d,0x15,0x1,0x29,0x1,0x27,0x15,0x1d,0x14,0x25,0x2d,0xf,0xfe,0xac,0x1,0x6b,0x44,0x11,0x1f,0x14,0x10,0x1f,0x1,0xd8,0xfe, + 0x28,0x10,0xf,0x10,0x14,0x1e,0x12,0x15,0x2,0x4,0x1,0xed,0x15,0x12,0x1e,0x14,0x10,0x1f,0xfe,0x41,0x1,0xbf,0x1f,0x10,0x14,0x1e,0x12,0x15,0xfe,0x12, + 0xfd,0xfd,0x0,0x1,0x0,0x7d,0xff,0xf6,0x4,0x4f,0x4,0xb1,0x0,0x1e,0x0,0x1d,0x40,0x1a,0x19,0xe,0x5,0x3,0x0,0x1,0x1,0x4a,0x2,0x1,0x1,0x1, + 0x16,0x4b,0x0,0x0,0x0,0xf,0x0,0x4c,0x14,0x2a,0x38,0x3,0x7,0x17,0x2b,0x0,0x16,0x15,0x14,0x7,0x1,0x11,0x14,0x6,0x23,0x23,0x22,0x26,0x35,0x11, + 0x1,0x26,0x35,0x34,0x36,0x37,0x36,0x33,0x32,0x17,0x1,0x1,0x36,0x33,0x32,0x17,0x4,0x38,0x17,0xf,0xfe,0x71,0x21,0x29,0x2,0x29,0x21,0xfe,0x71,0xf, + 0x17,0x19,0x21,0x13,0x1e,0x12,0x1,0x55,0x1,0x55,0x14,0x1c,0x13,0x21,0x4,0x98,0x19,0x10,0x10,0x17,0xfd,0x9f,0xfe,0x50,0x23,0x1e,0x1e,0x23,0x1,0xb0, + 0x2,0x61,0x17,0x10,0x10,0x19,0xb,0xe,0x1d,0xfd,0xf2,0x2,0xe,0x1d,0xe,0x0,0x0,0x0,0xff,0xff,0x0,0x7d,0xff,0xf6,0x4,0x4f,0x6,0x95,0x0,0x27, + 0x1,0x80,0x0,0x1c,0x1,0xe,0x1,0x2,0x0,0x76,0x0,0x0,0x0,0x9,0xb1,0x0,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0x7d,0xff,0xf6, + 0x4,0x4f,0x6,0x82,0x0,0x22,0x0,0x76,0x0,0x0,0x1,0x7,0x1,0x92,0x0,0x1,0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0, + 0xff,0xff,0x0,0x7d,0xff,0xf6,0x4,0x4f,0x6,0x61,0x0,0x22,0x0,0x76,0x0,0x0,0x1,0x7,0x1,0x93,0x0,0x0,0x1,0xe,0x0,0x9,0xb1,0x1,0x2,0xb8, + 0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0x7d,0xff,0xf6,0x4,0x4f,0x6,0x95,0x0,0x22,0x0,0x76,0x0,0x0,0x1,0x7,0x1,0x95,0xff,0xe4,0x1,0xe, + 0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0x0,0x1,0x0,0xa7,0x0,0x0,0x4,0x23,0x4,0xa3,0x0,0x1d,0x0,0x25,0x40,0x22,0x0,0x1, + 0x1,0x2,0x59,0x0,0x2,0x2,0xe,0x4b,0x4,0x1,0x3,0x3,0x0,0x59,0x0,0x0,0x0,0xf,0x0,0x4c,0x0,0x0,0x0,0x1d,0x0,0x1c,0x44,0x25,0x44,0x5, + 0x7,0x17,0x2b,0x24,0x16,0x15,0x14,0x6,0x23,0x21,0x23,0x22,0x26,0x35,0x34,0x37,0x1,0x21,0x22,0x26,0x35,0x34,0x36,0x33,0x21,0x33,0x32,0x16,0x15,0x14, + 0x7,0x1,0x21,0x4,0x6,0x1d,0x1d,0x22,0xfd,0x52,0x50,0x22,0x1d,0xe,0x2,0x81,0xfd,0xca,0x22,0x1d,0x1d,0x22,0x2,0x7b,0x57,0x22,0x1d,0xa,0xfd,0x7d, + 0x2,0x60,0x90,0x21,0x27,0x27,0x21,0x1f,0x25,0x16,0x15,0x3,0xa4,0x21,0x27,0x27,0x21,0x1f,0x25,0x19,0xe,0xfc,0x58,0x0,0xff,0xff,0x0,0xa7,0x0,0x0, + 0x4,0x23,0x6,0x95,0x0,0x22,0x0,0x7b,0x0,0x0,0x1,0x7,0x1,0x80,0xff,0xfe,0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0, + 0xff,0xff,0x0,0xa7,0x0,0x0,0x4,0x23,0x6,0x84,0x0,0x22,0x0,0x7b,0x0,0x0,0x1,0x7,0x1,0x83,0x0,0x8,0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8, + 0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0xa7,0x0,0x0,0x4,0x23,0x6,0x61,0x0,0x27,0x1,0x94,0x0,0xa,0x1,0xe,0x1,0x2,0x0,0x7b,0x0,0x0, + 0x0,0x9,0xb1,0x0,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0x0,0x2,0x0,0x9f,0xff,0xea,0x3,0xf6,0x3,0xb2,0x0,0x29,0x0,0x35,0x0,0x86,0x40,0xc, + 0x17,0x1,0x6,0x2,0x2c,0x2b,0xb,0x3,0x7,0x6,0x2,0x4a,0x4b,0xb0,0x28,0x50,0x58,0x40,0x28,0x0,0x4,0x3,0x2,0x3,0x4,0x2,0x70,0x0,0x2,0x0, + 0x6,0x7,0x2,0x6,0x63,0x0,0x3,0x3,0x5,0x5b,0x8,0x1,0x5,0x5,0x19,0x4b,0x9,0x1,0x7,0x7,0x0,0x5b,0x1,0x1,0x0,0x0,0xf,0x0,0x4c,0x1b, + 0x40,0x2c,0x0,0x4,0x3,0x2,0x3,0x4,0x2,0x70,0x0,0x2,0x0,0x6,0x7,0x2,0x6,0x63,0x0,0x3,0x3,0x5,0x5b,0x8,0x1,0x5,0x5,0x19,0x4b,0x0, + 0x0,0x0,0xf,0x4b,0x9,0x1,0x7,0x7,0x1,0x5b,0x0,0x1,0x1,0x17,0x1,0x4c,0x59,0x40,0x16,0x2a,0x2a,0x0,0x0,0x2a,0x35,0x2a,0x34,0x30,0x2e,0x0, + 0x29,0x0,0x28,0x23,0x24,0x25,0x25,0x35,0xa,0x7,0x19,0x2b,0x0,0x16,0x15,0x11,0x14,0x6,0x23,0x23,0x22,0x26,0x35,0x35,0x6,0x6,0x23,0x22,0x26,0x26, + 0x35,0x34,0x36,0x33,0x32,0x17,0x35,0x34,0x26,0x23,0x22,0x6,0x7,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x12,0x37,0x35,0x26,0x26,0x23, + 0x22,0x6,0x15,0x14,0x16,0x33,0x3,0x3a,0xbc,0x1e,0x27,0x2,0x27,0x1e,0x51,0xbd,0x69,0x64,0x9a,0x56,0xdf,0xb2,0x9f,0x91,0x6d,0x79,0x4b,0xa2,0x49,0x10, + 0xf,0x20,0x17,0xb,0x25,0x54,0xc1,0x61,0x42,0xa0,0x44,0x9c,0x4f,0x73,0x85,0x65,0x5b,0x3,0xb2,0xa8,0xa5,0xfd,0xd2,0x23,0x1e,0x1e,0x23,0x44,0x45,0x4c, + 0x45,0x84,0x5b,0x9f,0x96,0x33,0x55,0x61,0x5b,0x2a,0x25,0x9,0x30,0x18,0x10,0x23,0x13,0x2c,0x2f,0xfc,0xc5,0xa5,0x6d,0x16,0x17,0x4f,0x56,0x49,0x51,0x0, + 0xff,0xff,0x0,0x9f,0xff,0xea,0x3,0xf6,0x5,0x87,0x0,0x22,0x0,0x7f,0x0,0x0,0x0,0x2,0x1,0x80,0x0,0x0,0x0,0x0,0xff,0xff,0x0,0x9f,0xff,0xea, + 0x3,0xf6,0x5,0x5c,0x0,0x22,0x0,0x7f,0x0,0x0,0x0,0x2,0x1,0x82,0xd,0x0,0x0,0x0,0xff,0xff,0x0,0x9f,0xff,0xea,0x3,0xf6,0x5,0x74,0x0,0x22, + 0x0,0x7f,0x0,0x0,0x0,0x2,0x1,0x92,0xa,0x0,0x0,0x0,0xff,0xff,0x0,0x9f,0xff,0xea,0x3,0xf6,0x5,0x53,0x0,0x22,0x0,0x7f,0x0,0x0,0x0,0x2, + 0x1,0x93,0xa,0x0,0x0,0x0,0xff,0xff,0x0,0x9f,0xff,0xea,0x3,0xf6,0x5,0x87,0x0,0x22,0x0,0x7f,0x0,0x0,0x0,0x2,0x1,0x95,0x32,0x0,0x0,0x0, + 0xff,0xff,0x0,0x9f,0xff,0xea,0x3,0xf6,0x4,0xe2,0x0,0x22,0x0,0x7f,0x0,0x0,0x0,0x2,0x1,0x97,0x14,0x0,0x0,0x0,0x0,0x2,0x0,0x9f,0xfe,0x28, + 0x4,0x2d,0x3,0xb2,0x0,0x3b,0x0,0x47,0x0,0x59,0x40,0x56,0x29,0x1,0x8,0x3,0x44,0x43,0x1d,0x3,0x7,0x8,0x1c,0x3,0x2,0x2,0x7,0x3,0x4a,0x0, + 0x5,0x4,0x3,0x4,0x5,0x3,0x70,0x0,0x3,0xa,0x1,0x8,0x7,0x3,0x8,0x63,0x0,0x0,0x0,0x1,0x0,0x1,0x5f,0x0,0x4,0x4,0x6,0x5b,0x9,0x1, + 0x6,0x6,0x19,0x4b,0x0,0x7,0x7,0x2,0x5b,0x0,0x2,0x2,0x17,0x2,0x4c,0x3c,0x3c,0x0,0x0,0x3c,0x47,0x3c,0x46,0x42,0x40,0x0,0x3b,0x0,0x3a,0x23, + 0x24,0x25,0x28,0x28,0x2b,0xb,0x7,0x1a,0x2b,0x0,0x16,0x15,0x11,0x14,0x6,0x7,0x6,0x6,0x15,0x14,0x16,0x33,0x32,0x37,0x36,0x16,0x15,0x14,0x6,0x7, + 0x6,0x23,0x22,0x26,0x35,0x34,0x36,0x37,0x35,0x6,0x6,0x23,0x22,0x26,0x26,0x35,0x34,0x36,0x33,0x32,0x17,0x35,0x34,0x26,0x23,0x22,0x6,0x7,0x6,0x23, + 0x22,0x27,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x2,0x6,0x15,0x14,0x16,0x33,0x32,0x37,0x35,0x26,0x26,0x23,0x3,0x3a,0xbc,0x7,0x9,0x5b,0x50,0x2a,0x23, + 0x37,0x3a,0x18,0x1c,0x11,0x11,0x4b,0x59,0x5d,0x6b,0x67,0x64,0x51,0xbd,0x69,0x64,0x9a,0x56,0xdf,0xb2,0x9f,0x91,0x6d,0x79,0x4b,0xa2,0x49,0x10,0xf,0x20, + 0x17,0xb,0x25,0x54,0xc1,0x61,0xc0,0x85,0x65,0x5b,0xc7,0xa0,0x44,0x9c,0x4f,0x3,0xb2,0xa8,0xa5,0xfd,0xd2,0x13,0x18,0x8,0x54,0x7d,0x38,0x24,0x27,0xa, + 0x4,0x23,0x27,0x14,0x18,0x6,0x1a,0x68,0x57,0x51,0x98,0x50,0x5b,0x45,0x4c,0x45,0x84,0x5b,0x9f,0x96,0x33,0x55,0x61,0x5b,0x2a,0x25,0x9,0x30,0x18,0x10, + 0x23,0x13,0x2c,0x2f,0xfe,0x4,0x4f,0x56,0x49,0x51,0xa5,0x6d,0x16,0x17,0x0,0x0,0xff,0xff,0x0,0x9f,0xff,0xea,0x3,0xf6,0x6,0x15,0x0,0x22,0x0,0x7f, + 0x0,0x0,0x0,0x2,0x1,0x99,0xa,0x0,0x0,0x0,0xff,0xff,0x0,0x9f,0xff,0xea,0x3,0xf6,0x5,0x2d,0x0,0x22,0x0,0x7f,0x0,0x0,0x0,0x2,0x1,0x9a, + 0xa,0x0,0x0,0x0,0x0,0x3,0x0,0x4b,0xff,0xea,0x4,0x80,0x3,0xb2,0x0,0x38,0x0,0x3f,0x0,0x4a,0x0,0x65,0x40,0x62,0x35,0x1,0x6,0x8,0x18,0x1, + 0x3,0x1,0x2,0x4a,0x0,0x7,0x6,0x5,0x6,0x7,0x5,0x70,0x0,0x2,0x0,0x1,0x0,0x2,0x1,0x70,0xa,0x1,0x5,0x10,0xd,0x2,0x0,0x2,0x5,0x0, + 0x63,0xf,0xb,0x2,0x6,0x6,0x8,0x5b,0xe,0x9,0x2,0x8,0x8,0x19,0x4b,0xc,0x1,0x1,0x1,0x3,0x5b,0x4,0x1,0x3,0x3,0x17,0x3,0x4c,0x40,0x40, + 0x39,0x39,0x0,0x0,0x40,0x4a,0x40,0x4a,0x46,0x44,0x39,0x3f,0x39,0x3e,0x3c,0x3b,0x0,0x38,0x0,0x37,0x26,0x23,0x23,0x15,0x23,0x27,0x23,0x21,0x24,0x11, + 0x7,0x1d,0x2b,0x0,0x16,0x17,0x16,0x6,0x23,0x21,0x12,0x33,0x32,0x36,0x37,0x36,0x33,0x32,0x17,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x22,0x27,0x6,0x6, + 0x23,0x22,0x26,0x26,0x35,0x34,0x36,0x33,0x35,0x34,0x26,0x23,0x22,0x6,0x7,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x36,0x33,0x32,0x17,0x36,0x36,0x33, + 0x6,0x6,0x7,0x21,0x26,0x26,0x23,0x0,0x6,0x15,0x14,0x16,0x33,0x32,0x36,0x36,0x35,0x35,0x3,0xed,0x8c,0x6,0x1,0x1e,0x22,0xfe,0x71,0x5,0x9f,0x30, + 0x50,0x2a,0x11,0x16,0x1f,0x19,0x15,0x1b,0x37,0x84,0x48,0xa3,0x51,0x30,0x91,0x51,0x4e,0x76,0x40,0xf9,0xd7,0x38,0x4b,0x2d,0x56,0x2e,0x13,0x17,0x1c,0x18, + 0x15,0x17,0x7b,0x92,0x99,0x44,0x28,0x71,0x47,0x45,0x4e,0xc,0x1,0x2c,0x7,0x42,0x42,0xfe,0x24,0x97,0x3c,0x33,0x38,0x5b,0x35,0x3,0xb2,0xe8,0xd4,0x21, + 0x1d,0xfe,0xc3,0x25,0x29,0x11,0x1f,0x19,0x19,0x1c,0x19,0x34,0x36,0x90,0x43,0x4d,0x40,0x76,0x50,0xa7,0xad,0x16,0x65,0x62,0x22,0x23,0xf,0x19,0x18,0x1a, + 0x1c,0x14,0x6a,0x74,0x39,0x3b,0x91,0x68,0x75,0x6c,0x71,0xfe,0x97,0x64,0x64,0x39,0x40,0x39,0x65,0x3f,0x64,0x0,0x0,0x0,0x0,0x2,0x0,0xc0,0xff,0xea, + 0x4,0x41,0x5,0x2a,0x0,0x1d,0x0,0x2d,0x0,0x68,0xb6,0x10,0x3,0x2,0x4,0x5,0x1,0x4a,0x4b,0xb0,0x28,0x50,0x58,0x40,0x1d,0x6,0x1,0x3,0x3,0x10, + 0x4b,0x7,0x1,0x5,0x5,0x0,0x5b,0x0,0x0,0x0,0x19,0x4b,0x0,0x4,0x4,0x1,0x5b,0x2,0x1,0x1,0x1,0x17,0x1,0x4c,0x1b,0x40,0x21,0x6,0x1,0x3, + 0x3,0x10,0x4b,0x7,0x1,0x5,0x5,0x0,0x5b,0x0,0x0,0x0,0x19,0x4b,0x0,0x2,0x2,0xf,0x4b,0x0,0x4,0x4,0x1,0x5b,0x0,0x1,0x1,0x17,0x1,0x4c, + 0x59,0x40,0x14,0x1e,0x1e,0x0,0x0,0x1e,0x2d,0x1e,0x2c,0x26,0x24,0x0,0x1d,0x0,0x1b,0x35,0x26,0x24,0x8,0x7,0x17,0x2b,0x0,0x16,0x15,0x11,0x36,0x33, + 0x32,0x16,0x16,0x15,0x14,0x6,0x6,0x23,0x22,0x26,0x27,0x15,0x14,0x6,0x23,0x23,0x22,0x26,0x35,0x11,0x34,0x36,0x33,0x33,0x0,0x6,0x6,0x15,0x14,0x16, + 0x16,0x33,0x32,0x36,0x36,0x35,0x34,0x26,0x26,0x23,0x1,0x35,0x21,0x81,0xbc,0x7c,0xc3,0x6f,0x6f,0xc3,0x7c,0x62,0xa6,0x3f,0x1e,0x27,0x2,0x27,0x1e,0x21, + 0x29,0x2,0x1,0x24,0x8b,0x4f,0x4f,0x8b,0x59,0x59,0x81,0x44,0x44,0x81,0x59,0x5,0x2a,0x1e,0x23,0xfe,0x24,0xa5,0x75,0xdb,0x94,0x94,0xdb,0x75,0x58,0x52, + 0x5d,0x23,0x1e,0x1e,0x23,0x4,0xb2,0x23,0x1e,0xfd,0xf4,0x50,0x98,0x68,0x68,0x98,0x50,0x55,0x99,0x62,0x62,0x99,0x55,0x0,0x0,0x1,0x0,0x93,0xff,0xea, + 0x4,0x31,0x3,0xb2,0x0,0x2a,0x0,0x36,0x40,0x33,0x0,0x0,0x1,0x3,0x1,0x0,0x3,0x70,0x0,0x3,0x2,0x1,0x3,0x2,0x6e,0x0,0x1,0x1,0x5,0x5b, + 0x6,0x1,0x5,0x5,0x19,0x4b,0x0,0x2,0x2,0x4,0x5b,0x0,0x4,0x4,0x17,0x4,0x4c,0x0,0x0,0x0,0x2a,0x0,0x29,0x27,0x23,0x26,0x23,0x17,0x7,0x7, + 0x19,0x2b,0x0,0x16,0x17,0x16,0x15,0x14,0x7,0x6,0x23,0x22,0x27,0x26,0x23,0x22,0x6,0x6,0x15,0x14,0x16,0x16,0x33,0x32,0x36,0x37,0x36,0x33,0x32,0x17, + 0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x22,0x26,0x26,0x35,0x34,0x36,0x36,0x33,0x2,0xe0,0xc5,0x54,0x23,0xf,0x14,0x20,0x13,0x12,0x8d,0xb2,0x60,0x93,0x51, + 0x51,0x93,0x60,0x5c,0xad,0x47,0x14,0x12,0x1f,0x16,0x11,0x20,0x52,0xd9,0x71,0x8f,0xdb,0x78,0x78,0xdb,0x8f,0x3,0xb2,0x38,0x36,0x16,0x23,0x17,0x1b,0x29, + 0xd,0x61,0x56,0x99,0x61,0x61,0x99,0x56,0x39,0x36,0xf,0x26,0x1d,0x17,0x21,0x17,0x3c,0x44,0x7e,0xdc,0x8a,0x8a,0xdc,0x7e,0xff,0xff,0x0,0x93,0xff,0xea, + 0x4,0x31,0x5,0x87,0x0,0x22,0x0,0x8b,0x0,0x0,0x0,0x2,0x1,0x80,0xfd,0x0,0x0,0x0,0xff,0xff,0x0,0x93,0xff,0xea,0x4,0x31,0x5,0x76,0x0,0x22, + 0x0,0x8b,0x0,0x0,0x0,0x2,0x1,0x83,0x1e,0x0,0x0,0x0,0x0,0x1,0x0,0x93,0xfe,0x28,0x4,0x31,0x3,0xb2,0x0,0x4a,0x0,0x9a,0x40,0xb,0x26,0x6, + 0x2,0x0,0x8,0x12,0x1,0x1,0x3,0x2,0x4a,0x4b,0xb0,0xa,0x50,0x58,0x40,0x38,0x0,0x6,0x7,0x9,0x7,0x6,0x9,0x70,0x0,0x9,0x8,0x7,0x9,0x8, + 0x6e,0x0,0x0,0x8,0x4,0x3,0x0,0x68,0x0,0x2,0x4,0x3,0x4,0x2,0x3,0x70,0x0,0x8,0x0,0x4,0x2,0x8,0x4,0x63,0x0,0x3,0x0,0x1,0x3,0x1, + 0x60,0x0,0x7,0x7,0x5,0x5b,0x0,0x5,0x5,0x19,0x7,0x4c,0x1b,0x40,0x39,0x0,0x6,0x7,0x9,0x7,0x6,0x9,0x70,0x0,0x9,0x8,0x7,0x9,0x8,0x6e, + 0x0,0x0,0x8,0x4,0x8,0x0,0x4,0x70,0x0,0x2,0x4,0x3,0x4,0x2,0x3,0x70,0x0,0x8,0x0,0x4,0x2,0x8,0x4,0x63,0x0,0x3,0x0,0x1,0x3,0x1, + 0x60,0x0,0x7,0x7,0x5,0x5b,0x0,0x5,0x5,0x19,0x7,0x4c,0x59,0x40,0xe,0x49,0x47,0x26,0x23,0x17,0x2b,0x24,0x24,0x17,0x24,0x17,0xa,0x7,0x1d,0x2b, + 0x24,0x15,0x14,0x7,0x6,0x6,0x7,0x15,0x36,0x16,0x15,0x14,0x6,0x23,0x22,0x26,0x27,0x26,0x35,0x34,0x37,0x36,0x33,0x32,0x17,0x16,0x16,0x33,0x32,0x36, + 0x35,0x34,0x26,0x23,0x7,0x22,0x26,0x35,0x35,0x2e,0x2,0x35,0x34,0x36,0x36,0x33,0x32,0x16,0x17,0x16,0x15,0x14,0x7,0x6,0x23,0x22,0x27,0x26,0x23,0x22, + 0x6,0x6,0x15,0x14,0x16,0x16,0x33,0x32,0x36,0x37,0x36,0x33,0x32,0x17,0x4,0x31,0x20,0x46,0xb5,0x61,0x65,0x66,0x7c,0x79,0x44,0x80,0x33,0x17,0x8,0x11, + 0x1d,0x8,0x5,0x2b,0x67,0x2f,0x33,0x3a,0x36,0x3e,0x27,0xf,0x13,0x7d,0xbb,0x66,0x78,0xdb,0x8f,0x6b,0xc5,0x54,0x23,0xf,0x14,0x20,0x13,0x12,0x8d,0xb2, + 0x60,0x93,0x51,0x51,0x93,0x60,0x5c,0xad,0x47,0x14,0x12,0x1f,0x16,0xb9,0x17,0x21,0x17,0x33,0x42,0x8,0x5e,0x1,0x5e,0x4b,0x57,0x68,0x18,0x18,0xa,0x19, + 0xe,0x15,0x2d,0x2,0x10,0x16,0x21,0x1f,0x21,0x20,0x1,0x15,0x17,0x9f,0xf,0x83,0xcf,0x7f,0x8a,0xdc,0x7e,0x38,0x36,0x16,0x23,0x17,0x1b,0x29,0xd,0x61, + 0x56,0x99,0x61,0x61,0x99,0x56,0x39,0x36,0xf,0x26,0x0,0x0,0xff,0xff,0x0,0x93,0xff,0xea,0x4,0x31,0x5,0x74,0x0,0x22,0x1,0x92,0x1e,0x0,0x0,0x2, + 0x0,0x8b,0x0,0x0,0x0,0x0,0xff,0xff,0x0,0x93,0xff,0xea,0x4,0x31,0x5,0x53,0x0,0x22,0x0,0x8b,0x0,0x0,0x0,0x2,0x1,0x94,0x1e,0x0,0x0,0x0, + 0x0,0x2,0x0,0x8b,0xff,0xea,0x4,0xc,0x5,0x2a,0x0,0x1d,0x0,0x2d,0x0,0x68,0xb6,0x18,0xb,0x2,0x4,0x5,0x1,0x4a,0x4b,0xb0,0x28,0x50,0x58,0x40, + 0x1d,0x6,0x1,0x3,0x3,0x10,0x4b,0x7,0x1,0x5,0x5,0x2,0x5b,0x0,0x2,0x2,0x19,0x4b,0x0,0x4,0x4,0x0,0x5b,0x1,0x1,0x0,0x0,0xf,0x0,0x4c, + 0x1b,0x40,0x21,0x6,0x1,0x3,0x3,0x10,0x4b,0x7,0x1,0x5,0x5,0x2,0x5b,0x0,0x2,0x2,0x19,0x4b,0x0,0x0,0x0,0xf,0x4b,0x0,0x4,0x4,0x1,0x5b, + 0x0,0x1,0x1,0x17,0x1,0x4c,0x59,0x40,0x14,0x1e,0x1e,0x0,0x0,0x1e,0x2d,0x1e,0x2c,0x26,0x24,0x0,0x1d,0x0,0x1b,0x26,0x25,0x35,0x8,0x7,0x17,0x2b, + 0x0,0x16,0x15,0x11,0x14,0x6,0x23,0x23,0x22,0x26,0x35,0x35,0x6,0x6,0x23,0x22,0x26,0x26,0x35,0x34,0x36,0x36,0x33,0x32,0x17,0x11,0x34,0x36,0x33,0x33, + 0x0,0x6,0x6,0x15,0x14,0x16,0x16,0x33,0x32,0x36,0x36,0x35,0x34,0x26,0x26,0x23,0x3,0xeb,0x21,0x1e,0x27,0x2,0x27,0x1e,0x3f,0xa6,0x62,0x7c,0xc3,0x6f, + 0x6f,0xc3,0x7c,0xbc,0x81,0x21,0x29,0x2,0xfe,0x28,0x81,0x44,0x44,0x81,0x59,0x59,0x8b,0x4f,0x4f,0x8b,0x59,0x5,0x2a,0x1e,0x23,0xfb,0x4e,0x23,0x1e,0x1e, + 0x23,0x5d,0x52,0x58,0x75,0xdb,0x94,0x94,0xdb,0x75,0xa5,0x1,0xdc,0x23,0x1e,0xfd,0xf4,0x55,0x99,0x62,0x62,0x99,0x55,0x50,0x98,0x68,0x68,0x98,0x50,0x0, + 0x0,0x2,0x0,0x8c,0xff,0xea,0x4,0x40,0x5,0x34,0x0,0x33,0x0,0x43,0x0,0xac,0x40,0xe,0x33,0x28,0x20,0x1c,0x11,0x5,0x2,0x4,0xf,0x1,0x5,0x1, + 0x2,0x4a,0x4b,0xb0,0x1b,0x50,0x58,0x40,0x28,0x0,0x2,0x4,0x1,0x4,0x2,0x1,0x70,0x0,0x3,0x3,0x10,0x4b,0x0,0x4,0x4,0x10,0x4b,0x0,0x5,0x5, + 0x1,0x5b,0x0,0x1,0x1,0x11,0x4b,0x7,0x1,0x6,0x6,0x0,0x5b,0x0,0x0,0x0,0x17,0x0,0x4c,0x1b,0x4b,0xb0,0x20,0x50,0x58,0x40,0x26,0x0,0x2,0x4, + 0x1,0x4,0x2,0x1,0x70,0x0,0x1,0x0,0x5,0x6,0x1,0x5,0x63,0x0,0x3,0x3,0x10,0x4b,0x0,0x4,0x4,0x10,0x4b,0x7,0x1,0x6,0x6,0x0,0x5b,0x0, + 0x0,0x0,0x17,0x0,0x4c,0x1b,0x40,0x28,0x0,0x4,0x3,0x2,0x3,0x4,0x2,0x70,0x0,0x2,0x1,0x3,0x2,0x1,0x6e,0x0,0x1,0x0,0x5,0x6,0x1,0x5, + 0x63,0x0,0x3,0x3,0x10,0x4b,0x7,0x1,0x6,0x6,0x0,0x5b,0x0,0x0,0x0,0x17,0x0,0x4c,0x59,0x59,0x40,0xf,0x34,0x34,0x34,0x43,0x34,0x42,0x2e,0x26, + 0x1e,0x25,0x26,0x24,0x8,0x7,0x1a,0x2b,0x0,0x11,0x14,0x6,0x6,0x23,0x22,0x26,0x26,0x35,0x34,0x36,0x36,0x33,0x32,0x17,0x26,0x27,0x7,0x6,0x23,0x22, + 0x26,0x27,0x26,0x35,0x34,0x37,0x37,0x26,0x27,0x26,0x35,0x34,0x37,0x36,0x33,0x32,0x17,0x16,0x17,0x37,0x36,0x33,0x32,0x16,0x17,0x16,0x15,0x14,0x7,0x7, + 0x2,0x36,0x36,0x35,0x34,0x26,0x26,0x23,0x22,0x6,0x6,0x15,0x14,0x16,0x16,0x33,0x4,0x40,0x7b,0xd8,0x87,0x87,0xd8,0x7b,0x7b,0xd8,0x87,0x79,0x76,0x49, + 0x88,0xf0,0x11,0xe,0x12,0x18,0xa,0x8,0x2b,0x95,0x2d,0x4d,0x25,0xe,0x18,0x22,0x11,0x12,0x7d,0x60,0xed,0x11,0xe,0x12,0x18,0xa,0x8,0x2b,0xa1,0x5a, + 0x8f,0x4f,0x4f,0x8f,0x5e,0x5e,0x8f,0x4f,0x4f,0x8f,0x5e,0x3,0x3b,0xfe,0x75,0x86,0xce,0x72,0x72,0xce,0x86,0x86,0xce,0x72,0x4d,0x78,0x6b,0x5a,0x7,0x17, + 0x1a,0x13,0x12,0x21,0x10,0x38,0x1a,0x27,0x12,0x1d,0x13,0x1a,0x2d,0x9,0x3d,0x45,0x59,0x7,0x17,0x1a,0x13,0x12,0x21,0x10,0x3c,0xfc,0x38,0x4c,0x8a,0x5c, + 0x5c,0x8a,0x4c,0x4c,0x8a,0x5c,0x5c,0x8a,0x4c,0x0,0x0,0x0,0x0,0x3,0x0,0x89,0xff,0xea,0x5,0x29,0x5,0x2a,0x0,0x1e,0x0,0x2f,0x0,0x3d,0x0,0xb1, + 0x40,0xb,0x29,0x1,0x4,0x2,0x19,0xb,0x2,0x6,0x7,0x2,0x4a,0x4b,0xb0,0x28,0x50,0x58,0x40,0x23,0x0,0x4,0x4,0x3,0x5b,0x5,0x8,0x2,0x3,0x3, + 0x10,0x4b,0x9,0x1,0x7,0x7,0x2,0x5b,0x0,0x2,0x2,0x19,0x4b,0x0,0x6,0x6,0x0,0x5b,0x1,0x1,0x0,0x0,0xf,0x0,0x4c,0x1b,0x4b,0xb0,0x31,0x50, + 0x58,0x40,0x27,0x0,0x4,0x4,0x3,0x5b,0x5,0x8,0x2,0x3,0x3,0x10,0x4b,0x9,0x1,0x7,0x7,0x2,0x5b,0x0,0x2,0x2,0x19,0x4b,0x0,0x0,0x0,0xf, + 0x4b,0x0,0x6,0x6,0x1,0x5b,0x0,0x1,0x1,0x17,0x1,0x4c,0x1b,0x40,0x2b,0x8,0x1,0x3,0x3,0x10,0x4b,0x0,0x4,0x4,0x5,0x5b,0x0,0x5,0x5,0x10, + 0x4b,0x9,0x1,0x7,0x7,0x2,0x5b,0x0,0x2,0x2,0x19,0x4b,0x0,0x0,0x0,0xf,0x4b,0x0,0x6,0x6,0x1,0x5b,0x0,0x1,0x1,0x17,0x1,0x4c,0x59,0x59, + 0x40,0x18,0x30,0x30,0x0,0x0,0x30,0x3d,0x30,0x3c,0x36,0x34,0x2f,0x2d,0x27,0x25,0x0,0x1e,0x0,0x1c,0x26,0x25,0x35,0xa,0x7,0x17,0x2b,0x0,0x16,0x15, + 0x11,0x14,0x6,0x23,0x23,0x22,0x26,0x35,0x35,0x6,0x6,0x23,0x22,0x26,0x26,0x35,0x34,0x36,0x36,0x33,0x32,0x16,0x17,0x11,0x34,0x36,0x33,0x33,0x4,0x16, + 0x15,0x14,0x7,0x3,0x6,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x36,0x33,0x33,0x0,0x6,0x15,0x14,0x16,0x33,0x32,0x36,0x36,0x35,0x34,0x26,0x26,0x23,0x3, + 0x85,0x21,0x1e,0x27,0x2,0x27,0x1e,0x37,0x85,0x59,0x71,0xac,0x5f,0x5f,0xac,0x71,0x58,0x7d,0x36,0x21,0x29,0x2,0x1,0xbf,0xe,0x5,0x86,0xd,0x3d,0x1c, + 0x1e,0x2,0x52,0x8,0x22,0x76,0xfc,0x8c,0x77,0x77,0x75,0x4e,0x74,0x3f,0x3f,0x74,0x4e,0x5,0x2a,0x1e,0x23,0xfb,0x4e,0x23,0x1e,0x1e,0x23,0x53,0x50,0x50, + 0x75,0xda,0x95,0x95,0xda,0x75,0x4d,0x4e,0x1,0xd2,0x23,0x1e,0xa,0xf,0xc,0x9,0xf,0xfe,0x95,0x24,0x16,0x14,0x4,0xa,0x1,0x6a,0x20,0xfd,0xfe,0xb7, + 0x99,0x99,0xb7,0x50,0x97,0x69,0x69,0x97,0x50,0x0,0x0,0x0,0x0,0x2,0x0,0x9f,0xff,0xea,0x4,0xc2,0x5,0x2a,0x0,0x2f,0x0,0x3f,0x0,0xb2,0x40,0xa, + 0x1c,0x1,0x8,0x3,0xf,0x1,0x9,0x8,0x2,0x4a,0x4b,0xb0,0x1b,0x50,0x58,0x40,0x29,0x0,0x6,0x6,0x10,0x4b,0x4,0x1,0x0,0x0,0x5,0x5b,0xa,0x7, + 0x2,0x5,0x5,0xe,0x4b,0x0,0x8,0x8,0x3,0x5b,0x0,0x3,0x3,0x11,0x4b,0xb,0x1,0x9,0x9,0x1,0x5b,0x2,0x1,0x1,0x1,0xf,0x1,0x4c,0x1b,0x4b, + 0xb0,0x28,0x50,0x58,0x40,0x25,0xa,0x7,0x2,0x5,0x4,0x1,0x0,0x3,0x5,0x0,0x63,0x0,0x3,0x0,0x8,0x9,0x3,0x8,0x63,0x0,0x6,0x6,0x10,0x4b, + 0xb,0x1,0x9,0x9,0x1,0x5b,0x2,0x1,0x1,0x1,0xf,0x1,0x4c,0x1b,0x40,0x29,0xa,0x7,0x2,0x5,0x4,0x1,0x0,0x3,0x5,0x0,0x63,0x0,0x3,0x0, + 0x8,0x9,0x3,0x8,0x63,0x0,0x6,0x6,0x10,0x4b,0x0,0x1,0x1,0xf,0x4b,0xb,0x1,0x9,0x9,0x2,0x5b,0x0,0x2,0x2,0x17,0x2,0x4c,0x59,0x59,0x40, + 0x18,0x30,0x30,0x0,0x0,0x30,0x3f,0x30,0x3e,0x38,0x36,0x0,0x2f,0x0,0x2e,0x33,0x24,0x22,0x26,0x25,0x33,0x24,0xc,0x7,0x1b,0x2b,0x0,0x16,0x15,0x14, + 0x6,0x23,0x23,0x11,0x14,0x6,0x23,0x23,0x22,0x26,0x35,0x35,0x6,0x6,0x23,0x22,0x26,0x26,0x35,0x34,0x36,0x36,0x33,0x32,0x17,0x11,0x23,0x22,0x26,0x35, + 0x34,0x36,0x33,0x33,0x35,0x34,0x36,0x33,0x33,0x32,0x16,0x15,0x15,0x33,0x0,0x36,0x36,0x35,0x34,0x26,0x26,0x23,0x22,0x6,0x6,0x15,0x14,0x16,0x16,0x33, + 0x4,0xa5,0x1d,0x1d,0x22,0x8b,0x1e,0x27,0x2,0x27,0x1e,0x3c,0x9b,0x5c,0x76,0xba,0x6a,0x6a,0xba,0x76,0xb0,0x79,0xfd,0x22,0x1d,0x1d,0x22,0xfd,0x21,0x29, + 0x2,0x29,0x21,0x8b,0xfe,0x13,0x82,0x4a,0x4a,0x82,0x53,0x53,0x78,0x3f,0x3f,0x78,0x53,0x4,0x7e,0x21,0x27,0x27,0x21,0xfc,0x49,0x23,0x1e,0x1e,0x23,0x53, + 0x4d,0x53,0x6d,0xce,0x8b,0x8b,0xce,0x6d,0x93,0x1,0xb,0x21,0x27,0x27,0x21,0x6b,0x23,0x1e,0x1e,0x23,0x6b,0xfc,0x0,0x49,0x8a,0x5f,0x5f,0x8a,0x49,0x4e, + 0x8b,0x59,0x59,0x8b,0x4e,0x0,0x0,0x0,0x0,0x2,0x0,0x94,0xff,0xea,0x4,0x43,0x3,0xb2,0x0,0x21,0x0,0x28,0x0,0x3f,0x40,0x3c,0x0,0x2,0x0,0x1, + 0x0,0x2,0x1,0x70,0x0,0x5,0x0,0x0,0x2,0x5,0x0,0x61,0x8,0x1,0x6,0x6,0x4,0x5b,0x7,0x1,0x4,0x4,0x19,0x4b,0x0,0x1,0x1,0x3,0x5b,0x0, + 0x3,0x3,0x17,0x3,0x4c,0x22,0x22,0x0,0x0,0x22,0x28,0x22,0x27,0x25,0x24,0x0,0x21,0x0,0x20,0x28,0x23,0x22,0x25,0x9,0x7,0x18,0x2b,0x0,0x16,0x16, + 0x17,0x16,0x6,0x23,0x21,0x16,0x16,0x33,0x32,0x36,0x37,0x36,0x33,0x32,0x16,0x17,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x22,0x26,0x26,0x35,0x34,0x36,0x36, + 0x33,0x6,0x6,0x7,0x21,0x26,0x26,0x23,0x3,0x0,0xcc,0x6d,0x8,0x2,0x1f,0x22,0xfd,0x2c,0x7,0x9a,0x8c,0x64,0xbf,0x45,0x11,0xe,0x13,0x1f,0x9,0x7, + 0x2c,0x51,0xdc,0x70,0x91,0xcc,0x6a,0x79,0xd8,0x8a,0x7c,0xa2,0x19,0x2,0x67,0x15,0x99,0x82,0x3,0xb2,0x75,0xce,0x88,0x21,0x1d,0x95,0x99,0x2e,0x28,0x9, + 0x1d,0x1b,0x12,0x12,0x28,0x15,0x27,0x30,0x74,0xda,0x96,0x8f,0xdc,0x79,0x91,0x7d,0x6f,0x72,0x7a,0x0,0xff,0xff,0x0,0x94,0xff,0xea,0x4,0x43,0x5,0x87, + 0x0,0x22,0x0,0x95,0x0,0x0,0x0,0x2,0x1,0x80,0x0,0x0,0x0,0x0,0xff,0xff,0x0,0x94,0xff,0xea,0x4,0x43,0x5,0x5c,0x0,0x22,0x0,0x95,0x0,0x0, + 0x0,0x2,0x1,0x82,0x14,0x0,0x0,0x0,0xff,0xff,0x0,0x94,0xff,0xea,0x4,0x43,0x5,0x76,0x0,0x22,0x0,0x95,0x0,0x0,0x0,0x2,0x1,0x83,0x10,0x0, + 0x0,0x0,0xff,0xff,0x0,0x94,0xff,0xea,0x4,0x43,0x5,0x74,0x0,0x22,0x0,0x95,0x0,0x0,0x0,0x2,0x1,0x92,0x14,0x0,0x0,0x0,0xff,0xff,0x0,0x94, + 0xff,0xea,0x4,0x43,0x5,0x53,0x0,0x22,0x0,0x95,0x0,0x0,0x0,0x2,0x1,0x93,0xa,0x0,0x0,0x0,0xff,0xff,0x0,0x94,0xff,0xea,0x4,0x43,0x5,0x53, + 0x0,0x22,0x1,0x94,0x14,0x0,0x0,0x2,0x0,0x95,0x0,0x0,0x0,0x0,0xff,0xff,0x0,0x94,0xff,0xea,0x4,0x43,0x5,0x87,0x0,0x22,0x0,0x95,0x0,0x0, + 0x0,0x2,0x1,0x95,0x28,0x0,0x0,0x0,0xff,0xff,0x0,0x94,0xff,0xea,0x4,0x43,0x4,0xe2,0x0,0x22,0x1,0x97,0xa,0x0,0x0,0x2,0x0,0x95,0x0,0x0, + 0x0,0x0,0x0,0x2,0x0,0x94,0xfe,0x28,0x4,0x43,0x3,0xb2,0x0,0x37,0x0,0x3e,0x0,0x4e,0x40,0x4b,0x2d,0x1,0x5,0x1,0x1,0x4a,0x0,0x2,0x0,0x1, + 0x0,0x2,0x1,0x70,0x0,0x7,0x0,0x0,0x2,0x7,0x0,0x61,0x0,0x3,0x0,0x4,0x3,0x4,0x5f,0xa,0x1,0x8,0x8,0x6,0x5b,0x9,0x1,0x6,0x6,0x19, + 0x4b,0x0,0x1,0x1,0x5,0x5b,0x0,0x5,0x5,0x17,0x5,0x4c,0x38,0x38,0x0,0x0,0x38,0x3e,0x38,0x3d,0x3b,0x3a,0x0,0x37,0x0,0x36,0x26,0x28,0x2c,0x23, + 0x22,0x25,0xb,0x7,0x1a,0x2b,0x0,0x16,0x16,0x17,0x16,0x6,0x23,0x21,0x16,0x16,0x33,0x32,0x36,0x37,0x36,0x33,0x32,0x17,0x16,0x15,0x14,0x7,0x6,0x7, + 0x6,0x6,0x15,0x14,0x16,0x33,0x32,0x37,0x36,0x16,0x15,0x14,0x6,0x7,0x6,0x23,0x22,0x26,0x35,0x34,0x36,0x37,0x6,0x23,0x22,0x26,0x26,0x35,0x34,0x36, + 0x36,0x33,0x6,0x6,0x7,0x21,0x26,0x26,0x23,0x3,0x0,0xcc,0x6d,0x8,0x2,0x1f,0x22,0xfd,0x2c,0x7,0x9a,0x8c,0x64,0xbf,0x45,0x11,0xe,0x23,0x16,0x9, + 0x2c,0x1b,0x3d,0x56,0x4b,0x2a,0x23,0x37,0x3a,0x18,0x1c,0x11,0x11,0x4b,0x59,0x5d,0x6b,0x4f,0x4b,0x54,0x4e,0x91,0xcc,0x6a,0x79,0xd8,0x8a,0x7c,0xa2,0x19, + 0x2,0x67,0x15,0x99,0x82,0x3,0xb2,0x75,0xce,0x88,0x21,0x1d,0x95,0x99,0x2e,0x28,0x9,0x35,0x15,0x11,0x28,0x16,0xd,0x39,0x51,0x7a,0x35,0x24,0x27,0xa, + 0x4,0x23,0x27,0x14,0x18,0x6,0x1a,0x68,0x57,0x47,0x85,0x44,0xd,0x74,0xda,0x96,0x8f,0xdc,0x79,0x91,0x7d,0x6f,0x72,0x7a,0x0,0x0,0x0,0x1,0x0,0xbb, + 0xff,0xf6,0x4,0x5b,0x5,0x25,0x0,0x2b,0x0,0x36,0x40,0x33,0x1,0x1,0x1,0x7,0x1,0x4a,0x0,0x0,0x1,0x2,0x1,0x0,0x2,0x70,0x6,0x1,0x2,0x5, + 0x1,0x3,0x4,0x2,0x3,0x61,0x0,0x1,0x1,0x7,0x5b,0x0,0x7,0x7,0x10,0x4b,0x0,0x4,0x4,0xf,0x4,0x4c,0x23,0x24,0x23,0x33,0x24,0x22,0x23,0x14, + 0x8,0x7,0x1c,0x2b,0x0,0x15,0x14,0x7,0x6,0x23,0x22,0x27,0x26,0x23,0x22,0x15,0x15,0x21,0x32,0x16,0x15,0x14,0x6,0x23,0x21,0x11,0x14,0x6,0x23,0x23, + 0x22,0x26,0x35,0x11,0x23,0x22,0x26,0x35,0x34,0x36,0x33,0x33,0x35,0x34,0x36,0x33,0x32,0x17,0x4,0x5b,0x4,0xe,0x26,0x9,0xd,0x7f,0x6e,0xca,0x1,0x5a, + 0x22,0x1d,0x1d,0x22,0xfe,0xa6,0x21,0x29,0x2,0x29,0x21,0xc6,0x22,0x1d,0x1d,0x22,0xc6,0xb9,0xa7,0x86,0x84,0x4,0xdf,0x2e,0x8,0x14,0x38,0x5,0x31,0xd9, + 0x6e,0x21,0x27,0x27,0x21,0xfd,0x7b,0x23,0x1e,0x1e,0x23,0x2,0x85,0x21,0x27,0x27,0x21,0x7b,0xa5,0xb9,0x33,0x0,0x0,0x2,0x0,0x8b,0xfe,0x66,0x4,0xc, + 0x3,0xb2,0x0,0x2b,0x0,0x3b,0x0,0xb8,0x40,0xb,0x21,0x2,0x2,0x6,0x7,0x14,0x1,0x1,0x3,0x2,0x4a,0x4b,0xb0,0x24,0x50,0x58,0x40,0x2a,0x0,0x2, + 0x4,0x3,0x4,0x2,0x3,0x70,0x9,0x1,0x7,0x7,0x0,0x5b,0x8,0x5,0x2,0x0,0x0,0x11,0x4b,0x0,0x6,0x6,0x4,0x5b,0x0,0x4,0x4,0xf,0x4b,0x0, + 0x3,0x3,0x1,0x5b,0x0,0x1,0x1,0x13,0x1,0x4c,0x1b,0x4b,0xb0,0x28,0x50,0x58,0x40,0x28,0x0,0x2,0x4,0x3,0x4,0x2,0x3,0x70,0x0,0x6,0x0,0x4, + 0x2,0x6,0x4,0x63,0x9,0x1,0x7,0x7,0x0,0x5b,0x8,0x5,0x2,0x0,0x0,0x11,0x4b,0x0,0x3,0x3,0x1,0x5b,0x0,0x1,0x1,0x13,0x1,0x4c,0x1b,0x40, + 0x2c,0x0,0x2,0x4,0x3,0x4,0x2,0x3,0x70,0x0,0x6,0x0,0x4,0x2,0x6,0x4,0x63,0x0,0x0,0x0,0x11,0x4b,0x9,0x1,0x7,0x7,0x5,0x5b,0x8,0x1, + 0x5,0x5,0x19,0x4b,0x0,0x3,0x3,0x1,0x5b,0x0,0x1,0x1,0x13,0x1,0x4c,0x59,0x59,0x40,0x16,0x2c,0x2c,0x0,0x0,0x2c,0x3b,0x2c,0x3a,0x34,0x32,0x0, + 0x2b,0x0,0x2a,0x24,0x24,0x17,0x26,0x35,0xa,0x7,0x19,0x2b,0x0,0x16,0x17,0x35,0x34,0x36,0x33,0x33,0x32,0x16,0x15,0x11,0x14,0x6,0x6,0x23,0x22,0x26, + 0x27,0x26,0x35,0x34,0x37,0x36,0x33,0x32,0x17,0x16,0x16,0x33,0x32,0x36,0x35,0x35,0x6,0x23,0x22,0x26,0x26,0x35,0x34,0x36,0x36,0x33,0xe,0x2,0x15,0x14, + 0x16,0x16,0x33,0x32,0x36,0x36,0x35,0x34,0x26,0x26,0x23,0x2,0x9b,0xa6,0x3f,0x1e,0x27,0x2,0x27,0x1e,0x65,0xc0,0x87,0x60,0xc4,0x55,0x27,0x8,0x12,0x27, + 0xb,0xb,0x4e,0xac,0x4f,0x8a,0x8c,0x7c,0xc1,0x7c,0xc3,0x6f,0x6f,0xc3,0x7c,0x4f,0x81,0x44,0x44,0x81,0x59,0x59,0x8b,0x4f,0x4f,0x8b,0x59,0x3,0xb2,0x58, + 0x52,0x5d,0x23,0x1e,0x1e,0x23,0xfc,0x7d,0x77,0xab,0x5a,0x21,0x1f,0xe,0x27,0x10,0x18,0x37,0x4,0x1c,0x20,0x83,0x72,0xc9,0x9c,0x6f,0xd0,0x8c,0x8c,0xd0, + 0x6f,0x94,0x4f,0x8d,0x5b,0x5b,0x8d,0x4f,0x4a,0x8d,0x60,0x60,0x8d,0x4a,0xff,0xff,0x0,0x8b,0xfe,0x66,0x4,0xc,0x5,0x5c,0x0,0x22,0x0,0xa0,0x0,0x0, + 0x0,0x2,0x1,0x82,0xa,0x0,0x0,0x0,0xff,0xff,0x0,0x8b,0xfe,0x66,0x4,0xc,0x5,0x74,0x0,0x22,0x0,0xa0,0x0,0x0,0x0,0x2,0x1,0x92,0xa,0x0, + 0x0,0x0,0x0,0x3,0x0,0x8b,0xfe,0x66,0x4,0xc,0x5,0xa4,0x0,0x10,0x0,0x3c,0x0,0x4c,0x0,0xe4,0x40,0x10,0xa,0x1,0x2,0x1,0x0,0x32,0x13,0x2, + 0x8,0x9,0x25,0x1,0x3,0x5,0x3,0x4a,0x4b,0xb0,0x24,0x50,0x58,0x40,0x33,0x0,0x4,0x6,0x5,0x6,0x4,0x5,0x70,0x0,0x0,0xa,0x1,0x1,0x2,0x0, + 0x1,0x63,0xc,0x1,0x9,0x9,0x2,0x5b,0xb,0x7,0x2,0x2,0x2,0x11,0x4b,0x0,0x8,0x8,0x6,0x5b,0x0,0x6,0x6,0xf,0x4b,0x0,0x5,0x5,0x3,0x5b, + 0x0,0x3,0x3,0x13,0x3,0x4c,0x1b,0x4b,0xb0,0x28,0x50,0x58,0x40,0x31,0x0,0x4,0x6,0x5,0x6,0x4,0x5,0x70,0x0,0x0,0xa,0x1,0x1,0x2,0x0,0x1, + 0x63,0x0,0x8,0x0,0x6,0x4,0x8,0x6,0x63,0xc,0x1,0x9,0x9,0x2,0x5b,0xb,0x7,0x2,0x2,0x2,0x11,0x4b,0x0,0x5,0x5,0x3,0x5b,0x0,0x3,0x3, + 0x13,0x3,0x4c,0x1b,0x40,0x35,0x0,0x4,0x6,0x5,0x6,0x4,0x5,0x70,0x0,0x0,0xa,0x1,0x1,0x7,0x0,0x1,0x63,0x0,0x8,0x0,0x6,0x4,0x8,0x6, + 0x63,0x0,0x2,0x2,0x11,0x4b,0xc,0x1,0x9,0x9,0x7,0x5b,0xb,0x1,0x7,0x7,0x19,0x4b,0x0,0x5,0x5,0x3,0x5b,0x0,0x3,0x3,0x13,0x3,0x4c,0x59, + 0x59,0x40,0x22,0x3d,0x3d,0x11,0x11,0x0,0x0,0x3d,0x4c,0x3d,0x4b,0x45,0x43,0x11,0x3c,0x11,0x3b,0x35,0x33,0x2f,0x2d,0x29,0x28,0x21,0x1f,0x19,0x16,0x0, + 0x10,0x0,0xe,0x15,0xd,0x7,0x15,0x2b,0x0,0x35,0x34,0x37,0x13,0x36,0x33,0x32,0x17,0x16,0x15,0x14,0x7,0x3,0x6,0x23,0x23,0x16,0x16,0x17,0x35,0x34, + 0x36,0x33,0x33,0x32,0x16,0x15,0x11,0x14,0x6,0x6,0x23,0x22,0x26,0x27,0x26,0x35,0x34,0x37,0x36,0x33,0x32,0x17,0x16,0x16,0x33,0x32,0x36,0x35,0x35,0x6, + 0x23,0x22,0x26,0x26,0x35,0x34,0x36,0x36,0x33,0xe,0x2,0x15,0x14,0x16,0x16,0x33,0x32,0x36,0x36,0x35,0x34,0x26,0x26,0x23,0x2,0xf,0x6,0x75,0xf,0x2c, + 0xb,0x6,0x34,0x2,0x3f,0x8,0x22,0x74,0x70,0xa6,0x3f,0x1e,0x27,0x2,0x27,0x1e,0x65,0xc0,0x87,0x60,0xc4,0x55,0x27,0x8,0x12,0x27,0xb,0xb,0x4e,0xac, + 0x4f,0x8a,0x8c,0x7c,0xc1,0x7c,0xc3,0x6f,0x6f,0xc3,0x7c,0x4f,0x81,0x44,0x44,0x81,0x59,0x59,0x8b,0x4f,0x4f,0x8b,0x59,0x4,0x28,0x1a,0xc,0xd,0x1,0x23, + 0x26,0x1,0x7,0x25,0x4,0xc,0xfe,0xe1,0x20,0x76,0x58,0x52,0x5d,0x23,0x1e,0x1e,0x23,0xfc,0x7d,0x77,0xab,0x5a,0x21,0x1f,0xe,0x27,0x10,0x18,0x37,0x4, + 0x1c,0x20,0x83,0x72,0xc9,0x9c,0x6f,0xd0,0x8c,0x8c,0xd0,0x6f,0x94,0x4f,0x8d,0x5b,0x5b,0x8d,0x4f,0x4a,0x8d,0x60,0x60,0x8d,0x4a,0x0,0xff,0xff,0x0,0x8b, + 0xfe,0x66,0x4,0xc,0x5,0x53,0x0,0x22,0x0,0xa0,0x0,0x0,0x0,0x2,0x1,0x94,0xa,0x0,0x0,0x0,0x0,0x1,0x0,0xca,0xff,0xf6,0x4,0x14,0x5,0x2a, + 0x0,0x26,0x0,0x2d,0x40,0x2a,0x3,0x1,0x1,0x2,0x1,0x4a,0x5,0x1,0x4,0x4,0x10,0x4b,0x0,0x2,0x2,0x0,0x5b,0x0,0x0,0x0,0x19,0x4b,0x3,0x1, + 0x1,0x1,0xf,0x1,0x4c,0x0,0x0,0x0,0x26,0x0,0x24,0x36,0x25,0x35,0x25,0x6,0x7,0x18,0x2b,0x0,0x16,0x15,0x11,0x36,0x36,0x33,0x32,0x16,0x15,0x11, + 0x14,0x6,0x23,0x23,0x22,0x26,0x35,0x11,0x34,0x26,0x23,0x22,0x6,0x6,0x15,0x11,0x14,0x6,0x23,0x23,0x22,0x26,0x35,0x11,0x34,0x36,0x33,0x33,0x1,0x3f, + 0x21,0x45,0xcd,0x65,0x97,0xa6,0x21,0x29,0x2,0x29,0x21,0x59,0x5d,0x56,0xa7,0x6b,0x21,0x29,0x2,0x29,0x21,0x21,0x29,0x2,0x5,0x2a,0x1e,0x23,0xfd,0xe7, + 0x66,0x7c,0xae,0x98,0xfd,0xcb,0x23,0x1e,0x1e,0x23,0x2,0x26,0x5a,0x67,0x6d,0xbb,0x6e,0xfe,0xaf,0x23,0x1e,0x1e,0x23,0x4,0xb2,0x23,0x1e,0x0,0x0,0x0, + 0x0,0x1,0x0,0x14,0xff,0xf6,0x4,0x0,0x5,0x2a,0x0,0x38,0x0,0x68,0xb5,0x35,0x1,0x0,0x1,0x1,0x4a,0x4b,0xb0,0x1b,0x50,0x58,0x40,0x23,0x0,0x5, + 0x5,0x10,0x4b,0x7,0x1,0x3,0x3,0x4,0x5b,0x6,0x1,0x4,0x4,0xe,0x4b,0x0,0x1,0x1,0x8,0x5b,0x9,0x1,0x8,0x8,0x11,0x4b,0x2,0x1,0x0,0x0, + 0xf,0x0,0x4c,0x1b,0x40,0x1f,0x6,0x1,0x4,0x7,0x1,0x3,0x8,0x4,0x3,0x63,0x9,0x1,0x8,0x0,0x1,0x0,0x8,0x1,0x63,0x0,0x5,0x5,0x10,0x4b, + 0x2,0x1,0x0,0x0,0xf,0x0,0x4c,0x59,0x40,0x11,0x0,0x0,0x0,0x38,0x0,0x37,0x24,0x23,0x33,0x24,0x23,0x36,0x25,0x35,0xa,0x7,0x1c,0x2b,0x0,0x16, + 0x15,0x11,0x14,0x6,0x23,0x23,0x22,0x26,0x35,0x11,0x34,0x26,0x23,0x22,0x6,0x6,0x15,0x11,0x14,0x6,0x23,0x23,0x22,0x26,0x35,0x11,0x23,0x22,0x26,0x35, + 0x34,0x36,0x33,0x33,0x35,0x34,0x36,0x33,0x33,0x32,0x16,0x15,0x15,0x33,0x32,0x16,0x15,0x14,0x6,0x23,0x23,0x11,0x36,0x36,0x33,0x3,0x60,0xa0,0x21,0x29, + 0x2,0x29,0x21,0x54,0x58,0x4e,0x9a,0x62,0x21,0x29,0x2,0x29,0x21,0x8b,0x22,0x1d,0x1d,0x22,0x8b,0x21,0x29,0x2,0x29,0x21,0xfd,0x22,0x1d,0x1d,0x22,0xfd, + 0x42,0xb9,0x5e,0x3,0x76,0xae,0x98,0xfe,0x7,0x23,0x1e,0x1e,0x23,0x1,0xea,0x5a,0x67,0x68,0xb2,0x68,0xfe,0xd7,0x23,0x1e,0x1e,0x23,0x3,0xb7,0x21,0x27, + 0x27,0x21,0x6b,0x23,0x1e,0x1e,0x23,0x6b,0x21,0x27,0x27,0x21,0xfe,0xba,0x5e,0x70,0xff,0xff,0x0,0xca,0xff,0xf6,0x4,0x14,0x6,0xe6,0x0,0x27,0x1,0x92, + 0xff,0xf6,0x1,0x72,0x1,0x2,0x0,0xa5,0x0,0x0,0x0,0x9,0xb1,0x0,0x1,0xb8,0x1,0x72,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0xbb,0x0,0x0,0x4,0x49, + 0x5,0x53,0x0,0x22,0x0,0xa9,0x0,0x0,0x0,0x2,0x1,0x94,0x4,0x0,0x0,0x0,0x0,0x1,0x0,0xbb,0x0,0x0,0x4,0x49,0x3,0x9c,0x0,0x1b,0x0,0x27, + 0x40,0x24,0x0,0x3,0x3,0x4,0x59,0x5,0x1,0x4,0x4,0x11,0x4b,0x2,0x1,0x0,0x0,0x1,0x59,0x0,0x1,0x1,0xf,0x1,0x4c,0x0,0x0,0x0,0x1b,0x0, + 0x19,0x21,0x24,0x34,0x23,0x6,0x7,0x18,0x2b,0x0,0x16,0x15,0x11,0x21,0x32,0x16,0x15,0x14,0x6,0x23,0x21,0x22,0x26,0x35,0x34,0x36,0x33,0x21,0x11,0x23, + 0x22,0x26,0x35,0x34,0x36,0x33,0x21,0x2,0xac,0x1d,0x1,0x41,0x22,0x1d,0x1d,0x22,0xfc,0xf0,0x22,0x1d,0x1d,0x22,0x1,0x39,0xfd,0x22,0x1d,0x1d,0x22,0x1, + 0x54,0x3,0x9c,0x1f,0x25,0xfd,0x38,0x21,0x27,0x27,0x21,0x21,0x27,0x27,0x21,0x2,0x7c,0x21,0x27,0x27,0x21,0x0,0x0,0x0,0xff,0xff,0x0,0xbb,0x0,0x0, + 0x4,0x49,0x5,0x87,0x0,0x22,0x0,0xa9,0x0,0x0,0x0,0x2,0x1,0x80,0x0,0x0,0x0,0x0,0xff,0xff,0x0,0xbb,0x0,0x0,0x4,0x49,0x5,0x5c,0x0,0x22, + 0x0,0xa9,0x0,0x0,0x0,0x2,0x1,0x82,0xf6,0x0,0x0,0x0,0xff,0xff,0x0,0xbb,0x0,0x0,0x4,0x49,0x5,0x74,0x0,0x22,0x0,0xa9,0x0,0x0,0x0,0x2, + 0x1,0x92,0x0,0x0,0x0,0x0,0xff,0xff,0x0,0xbb,0x0,0x0,0x4,0x49,0x5,0x53,0x0,0x22,0x0,0xa9,0x0,0x0,0x0,0x2,0x1,0x93,0x0,0x0,0x0,0x0, + 0xff,0xff,0x0,0xbb,0x0,0x0,0x4,0x49,0x5,0x53,0x0,0x22,0x0,0xa9,0x0,0x0,0x0,0x2,0x1,0x94,0x4,0x0,0x0,0x0,0xff,0xff,0x0,0xbb,0x0,0x0, + 0x4,0x49,0x5,0x87,0x0,0x22,0x0,0xa9,0x0,0x0,0x0,0x2,0x1,0x95,0xda,0x0,0x0,0x0,0x0,0x4,0x0,0xde,0xfe,0x66,0x3,0xee,0x5,0x53,0x0,0xd, + 0x0,0x1b,0x0,0x2b,0x0,0x48,0x0,0x85,0xb5,0x37,0x1,0x6,0x8,0x1,0x4a,0x4b,0xb0,0x19,0x50,0x58,0x40,0x2c,0x0,0x7,0x4,0x8,0x4,0x7,0x8,0x70, + 0x2,0x1,0x0,0x0,0x1,0x5b,0x3,0x1,0x1,0x1,0x10,0x4b,0xb,0x9,0xa,0x3,0x5,0x5,0x11,0x4b,0x0,0x4,0x4,0xf,0x4b,0x0,0x8,0x8,0x6,0x5b, + 0x0,0x6,0x6,0x13,0x6,0x4c,0x1b,0x40,0x2a,0x0,0x7,0x4,0x8,0x4,0x7,0x8,0x70,0x3,0x1,0x1,0x2,0x1,0x0,0x5,0x1,0x0,0x63,0xb,0x9,0xa, + 0x3,0x5,0x5,0x11,0x4b,0x0,0x4,0x4,0xf,0x4b,0x0,0x8,0x8,0x6,0x5b,0x0,0x6,0x6,0x13,0x6,0x4c,0x59,0x40,0x1a,0x2c,0x2c,0x1c,0x1c,0x2c,0x48, + 0x2c,0x46,0x41,0x3f,0x3b,0x3a,0x33,0x31,0x1c,0x2b,0x1c,0x29,0x39,0x25,0x25,0x25,0x21,0xc,0x7,0x19,0x2b,0x0,0x6,0x23,0x22,0x26,0x35,0x35,0x34,0x36, + 0x33,0x32,0x16,0x15,0x15,0x4,0x6,0x23,0x22,0x26,0x35,0x35,0x34,0x36,0x33,0x32,0x16,0x15,0x15,0x4,0x16,0x15,0x11,0x14,0x6,0x23,0x23,0x22,0x26,0x35, + 0x11,0x34,0x36,0x33,0x33,0x20,0x16,0x15,0x11,0x14,0x6,0x23,0x22,0x26,0x27,0x26,0x35,0x34,0x37,0x36,0x33,0x32,0x17,0x16,0x16,0x33,0x32,0x36,0x35,0x11, + 0x34,0x36,0x33,0x33,0x1,0x9c,0x28,0x37,0x37,0x28,0x28,0x37,0x37,0x28,0x2,0x52,0x28,0x37,0x37,0x28,0x28,0x37,0x37,0x28,0xfd,0x79,0x21,0x21,0x29,0x2, + 0x29,0x21,0x21,0x29,0x2,0x2,0x7b,0x21,0x8d,0x84,0x37,0x80,0x30,0x27,0xb,0x15,0x24,0xc,0x10,0x27,0x5c,0x2c,0x3f,0x3b,0x21,0x29,0x2,0x4,0x6c,0x1d, + 0x1d,0x24,0x82,0x24,0x1d,0x1d,0x24,0x82,0x24,0x1d,0x1d,0x24,0x82,0x24,0x1d,0x1d,0x24,0x82,0xea,0x1e,0x23,0xfc,0xd2,0x23,0x1e,0x1e,0x23,0x3,0x2e,0x23, + 0x1e,0x1e,0x23,0xfc,0x14,0x86,0x8d,0x1a,0x18,0x14,0x21,0x11,0x1b,0x31,0x7,0x13,0x16,0x4a,0x53,0x3,0xce,0x23,0x1e,0x0,0xff,0xff,0x0,0xbb,0x0,0x0, + 0x4,0x49,0x4,0xe2,0x0,0x22,0x0,0xa9,0x0,0x0,0x0,0x2,0x1,0x97,0xf6,0x0,0x0,0x0,0x0,0x2,0x0,0xbb,0xfe,0x28,0x4,0x49,0x5,0x53,0x0,0xd, + 0x0,0x43,0x0,0xbb,0x4b,0xb0,0x19,0x50,0x58,0x40,0x2b,0x4,0x1,0x3,0x0,0x5,0x3,0x5,0x5f,0xb,0x1,0x1,0x1,0x0,0x5b,0x0,0x0,0x0,0x10,0x4b, + 0x0,0x8,0x8,0x9,0x59,0x0,0x9,0x9,0x11,0x4b,0xc,0xa,0x2,0x7,0x7,0x2,0x59,0x6,0x1,0x2,0x2,0xf,0x2,0x4c,0x1b,0x4b,0xb0,0x1b,0x50,0x58, + 0x40,0x29,0x0,0x0,0xb,0x1,0x1,0x9,0x0,0x1,0x63,0x4,0x1,0x3,0x0,0x5,0x3,0x5,0x5f,0x0,0x8,0x8,0x9,0x59,0x0,0x9,0x9,0x11,0x4b,0xc, + 0xa,0x2,0x7,0x7,0x2,0x59,0x6,0x1,0x2,0x2,0xf,0x2,0x4c,0x1b,0x40,0x30,0x0,0x4,0x2,0x3,0x2,0x4,0x3,0x70,0x0,0x0,0xb,0x1,0x1,0x9, + 0x0,0x1,0x63,0x0,0x3,0x0,0x5,0x3,0x5,0x5f,0x0,0x8,0x8,0x9,0x59,0x0,0x9,0x9,0x11,0x4b,0xc,0xa,0x2,0x7,0x7,0x2,0x59,0x6,0x1,0x2, + 0x2,0xf,0x2,0x4c,0x59,0x59,0x40,0x20,0xe,0xe,0x0,0x0,0xe,0x43,0xe,0x42,0x3f,0x3c,0x38,0x36,0x35,0x33,0x2f,0x2d,0x28,0x26,0x1f,0x1c,0x1b,0x19, + 0x14,0x12,0x0,0xd,0x0,0xc,0x25,0xd,0x7,0x15,0x2b,0x0,0x26,0x35,0x35,0x34,0x36,0x33,0x32,0x16,0x15,0x15,0x14,0x6,0x23,0x0,0x16,0x15,0x14,0x6, + 0x23,0x21,0x6,0x6,0x15,0x14,0x16,0x33,0x32,0x37,0x36,0x33,0x32,0x17,0x16,0x15,0x14,0x6,0x7,0x6,0x23,0x22,0x26,0x35,0x34,0x36,0x37,0x21,0x22,0x26, + 0x35,0x34,0x36,0x33,0x21,0x11,0x23,0x22,0x26,0x35,0x34,0x36,0x33,0x21,0x32,0x16,0x15,0x11,0x21,0x2,0x33,0x28,0x28,0x37,0x37,0x28,0x28,0x37,0x1,0xc2, + 0x1d,0x1d,0x22,0xfe,0xbf,0x59,0x4d,0x2a,0x23,0x34,0x3d,0x8,0x3,0x1e,0x9,0x2,0x11,0x11,0x4b,0x59,0x5d,0x6b,0x50,0x4c,0xfe,0xd7,0x22,0x1d,0x1d,0x22, + 0x1,0x39,0xfd,0x22,0x1d,0x1d,0x22,0x1,0x54,0x22,0x1d,0x1,0x41,0x4,0x4f,0x1d,0x24,0x82,0x24,0x1d,0x1d,0x24,0x82,0x24,0x1d,0xfc,0x41,0x21,0x27,0x27, + 0x21,0x53,0x7a,0x37,0x23,0x26,0x11,0x2,0x36,0x12,0x7,0x16,0x19,0x6,0x1a,0x68,0x57,0x49,0x8a,0x46,0x21,0x27,0x27,0x21,0x2,0x7c,0x21,0x27,0x27,0x21, + 0x1f,0x25,0xfd,0x38,0xff,0xff,0x0,0xbb,0x0,0x0,0x4,0x49,0x5,0x2d,0x0,0x22,0x0,0xa9,0x0,0x0,0x0,0x2,0x1,0x9a,0xec,0x0,0x0,0x0,0xff,0xff, + 0x0,0xbe,0xfe,0x66,0x3,0x87,0x5,0x53,0x0,0x22,0x0,0xb5,0x0,0x0,0x0,0x3,0x1,0x94,0x0,0xc2,0x0,0x0,0x0,0x1,0x0,0xbe,0xfe,0x66,0x3,0x87, + 0x3,0x9c,0x0,0x21,0x0,0x34,0x40,0x31,0xb,0x1,0x2,0x1,0x1,0x4a,0x0,0x1,0x3,0x2,0x3,0x1,0x2,0x70,0x0,0x3,0x3,0x4,0x59,0x5,0x1,0x4, + 0x4,0x11,0x4b,0x0,0x2,0x2,0x0,0x5b,0x0,0x0,0x0,0x13,0x0,0x4c,0x0,0x0,0x0,0x21,0x0,0x1e,0x23,0x24,0x17,0x25,0x6,0x7,0x18,0x2b,0x0,0x16, + 0x15,0x11,0x14,0x6,0x23,0x22,0x26,0x27,0x26,0x35,0x34,0x37,0x36,0x33,0x32,0x17,0x16,0x16,0x33,0x32,0x36,0x35,0x11,0x21,0x22,0x26,0x35,0x34,0x36,0x33, + 0x21,0x33,0x3,0x6a,0x1d,0x9d,0xaa,0x53,0xc0,0x4e,0x21,0xd,0x18,0x22,0x11,0xe,0x45,0x94,0x43,0x62,0x4f,0xfe,0x59,0x22,0x1d,0x1d,0x22,0x1,0xd0,0x2e, + 0x3,0x9c,0x1f,0x25,0xfc,0x6f,0xb0,0xb1,0x3f,0x31,0x14,0x20,0x13,0x1a,0x2e,0xa,0x2d,0x36,0x6a,0x72,0x3,0x38,0x21,0x27,0x27,0x21,0x0,0x0,0xff,0xff, + 0x0,0xbe,0xfe,0x66,0x4,0x13,0x5,0x74,0x0,0x22,0x1,0x92,0x6e,0x0,0x0,0x2,0x0,0xb5,0x0,0x0,0x0,0x0,0x0,0x1,0x0,0xe8,0xff,0xf0,0x4,0x38, + 0x5,0x2a,0x0,0x25,0x0,0x24,0x40,0x21,0x24,0x1a,0x9,0x8,0x4,0x0,0x3,0x1,0x4a,0x0,0x2,0x2,0x10,0x4b,0x0,0x3,0x3,0x19,0x4b,0x1,0x1,0x0, + 0x0,0x17,0x0,0x4c,0x25,0x35,0x36,0x24,0x4,0x7,0x18,0x2b,0x24,0x15,0x14,0x7,0x6,0x23,0x22,0x27,0x1,0x7,0x15,0x14,0x6,0x23,0x23,0x22,0x26,0x35, + 0x11,0x34,0x36,0x33,0x33,0x32,0x16,0x15,0x11,0x1,0x36,0x33,0x32,0x17,0x16,0x15,0x14,0x7,0x1,0x1,0x4,0x38,0x2a,0x24,0x18,0x16,0x12,0xfe,0x88,0xb4, + 0x21,0x29,0x2,0x29,0x21,0x21,0x29,0x2,0x29,0x21,0x2,0x11,0x15,0x1a,0x1a,0x1e,0x2a,0x15,0xfe,0x98,0x1,0x81,0x49,0x12,0x1b,0x17,0x15,0x13,0x1,0x9e, + 0x9e,0xcc,0x23,0x1e,0x1e,0x23,0x4,0xb2,0x23,0x1e,0x1e,0x23,0xfc,0xdc,0x1,0xd7,0x12,0x12,0x19,0x1c,0x14,0x12,0xfe,0xc3,0xfe,0x5b,0x0,0x0,0xff,0xff, + 0x0,0xe8,0xfd,0xf2,0x4,0x38,0x5,0x2a,0x0,0x22,0x0,0xb7,0x0,0x0,0x0,0x3,0x1,0x7e,0x4,0xd2,0x0,0x0,0x0,0x1,0x0,0xb1,0x0,0x0,0x4,0x3f, + 0x5,0x20,0x0,0x1c,0x0,0x27,0x40,0x24,0x0,0x2,0x2,0x3,0x59,0x0,0x3,0x3,0x10,0x4b,0x5,0x4,0x2,0x1,0x1,0x0,0x59,0x0,0x0,0x0,0xf,0x0, + 0x4c,0x0,0x0,0x0,0x1c,0x0,0x1b,0x44,0x21,0x24,0x34,0x6,0x7,0x18,0x2b,0x24,0x16,0x15,0x14,0x6,0x23,0x21,0x22,0x26,0x35,0x34,0x36,0x33,0x21,0x11, + 0x21,0x22,0x26,0x35,0x34,0x36,0x33,0x21,0x33,0x32,0x16,0x15,0x11,0x21,0x4,0x22,0x1d,0x1d,0x22,0xfc,0xf0,0x22,0x1d,0x1d,0x22,0x1,0x39,0xfe,0xef,0x22, + 0x1d,0x1d,0x22,0x1,0x4e,0x1a,0x22,0x1d,0x1,0x41,0x90,0x21,0x27,0x27,0x21,0x21,0x27,0x27,0x21,0x4,0x0,0x21,0x27,0x27,0x21,0x1f,0x25,0xfb,0xb4,0x0, + 0xff,0xff,0x0,0xb1,0x0,0x0,0x4,0x3f,0x6,0xf9,0x0,0x27,0x1,0x80,0xff,0xe0,0x1,0x72,0x1,0x2,0x0,0xb9,0x0,0x0,0x0,0x9,0xb1,0x0,0x1,0xb8, + 0x1,0x72,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0x93,0x0,0x0,0x4,0x68,0x5,0x20,0x0,0x22,0x0,0xb9,0xe2,0x0,0x0,0x3,0x1,0x81,0x2,0x5f,0x0,0x0, + 0xff,0xff,0x0,0xb1,0xfd,0xf2,0x4,0x3f,0x5,0x20,0x0,0x22,0x0,0xb9,0x0,0x0,0x0,0x3,0x1,0x7e,0x4,0xcc,0x0,0x0,0xff,0xff,0x0,0x93,0x0,0x0, + 0x4,0x2d,0x5,0x20,0x0,0x22,0x0,0xb9,0xe2,0x0,0x1,0x7,0x1,0x94,0x1,0x68,0xfe,0xc,0x0,0x9,0xb1,0x1,0x1,0xb8,0xfe,0xc,0xb0,0x33,0x2b,0x0, + 0x0,0x1,0x0,0xb1,0x0,0x0,0x4,0x3f,0x5,0x20,0x0,0x32,0x0,0x3f,0x40,0x3c,0x30,0x26,0x18,0xe,0x4,0x2,0x5,0x1,0x4a,0x0,0x2,0x5,0x1,0x5, + 0x2,0x1,0x70,0x0,0x3,0x3,0x4,0x59,0x0,0x4,0x4,0x10,0x4b,0x0,0x5,0x5,0x19,0x4b,0x7,0x6,0x2,0x1,0x1,0x0,0x5a,0x0,0x0,0x0,0xf,0x0, + 0x4c,0x0,0x0,0x0,0x32,0x0,0x31,0x25,0x44,0x27,0x23,0x24,0x34,0x8,0x7,0x1a,0x2b,0x24,0x16,0x15,0x14,0x6,0x23,0x21,0x22,0x26,0x35,0x34,0x36,0x33, + 0x21,0x11,0x7,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x37,0x11,0x21,0x22,0x26,0x35,0x34,0x36,0x33,0x21,0x33,0x32,0x16,0x15,0x11,0x37,0x36,0x33,0x32, + 0x17,0x16,0x15,0x14,0x7,0x7,0x11,0x21,0x4,0x22,0x1d,0x1d,0x22,0xfc,0xf0,0x22,0x1d,0x1d,0x22,0x1,0x39,0xaf,0x18,0x10,0x1a,0x19,0x12,0x20,0xfc,0xfe, + 0xef,0x22,0x1d,0x1d,0x22,0x1,0x4e,0x1a,0x22,0x1d,0xa7,0x18,0x10,0x1a,0x19,0x12,0x20,0xf4,0x1,0x41,0x90,0x21,0x27,0x27,0x21,0x21,0x27,0x27,0x21,0x1, + 0x9d,0x77,0x10,0x25,0x1b,0x12,0x19,0x15,0xab,0x1,0xbf,0x21,0x27,0x27,0x21,0x1f,0x25,0xfe,0x5b,0x72,0x10,0x25,0x1b,0x12,0x19,0x15,0xa6,0xfd,0xfd,0x0, + 0x0,0x0,0x0,0x1,0x0,0x79,0xff,0xf6,0x4,0x55,0x3,0xb2,0x0,0x3c,0x0,0x56,0xb6,0x3a,0x34,0x2,0x0,0x1,0x1,0x4a,0x4b,0xb0,0x28,0x50,0x58,0x40, + 0x16,0x3,0x1,0x1,0x1,0x5,0x5b,0x8,0x7,0x6,0x3,0x5,0x5,0x11,0x4b,0x4,0x2,0x2,0x0,0x0,0xf,0x0,0x4c,0x1b,0x40,0x1a,0x0,0x5,0x5,0x11, + 0x4b,0x3,0x1,0x1,0x1,0x6,0x5b,0x8,0x7,0x2,0x6,0x6,0x19,0x4b,0x4,0x2,0x2,0x0,0x0,0xf,0x0,0x4c,0x59,0x40,0x10,0x0,0x0,0x0,0x3c,0x0, + 0x3b,0x25,0x35,0x36,0x25,0x36,0x25,0x36,0x9,0x7,0x1b,0x2b,0x0,0x16,0x16,0x15,0x11,0x14,0x6,0x23,0x23,0x22,0x26,0x35,0x11,0x34,0x26,0x23,0x22,0x6, + 0x6,0x15,0x11,0x14,0x6,0x23,0x23,0x22,0x26,0x35,0x11,0x34,0x26,0x23,0x22,0x6,0x6,0x15,0x11,0x14,0x6,0x23,0x23,0x22,0x26,0x35,0x11,0x34,0x36,0x33, + 0x33,0x32,0x16,0x15,0x15,0x36,0x36,0x33,0x32,0x16,0x17,0x36,0x33,0x3,0xc1,0x5d,0x37,0x21,0x29,0x2,0x29,0x21,0x2d,0x32,0x2f,0x50,0x2f,0x21,0x29,0x2, + 0x29,0x21,0x2d,0x32,0x2f,0x50,0x2f,0x21,0x29,0x2,0x29,0x21,0x1b,0x25,0x2,0x25,0x1b,0x2b,0x78,0x46,0x44,0x66,0x12,0x59,0x8d,0x3,0xb2,0x3b,0x79,0x58, + 0xfd,0x91,0x23,0x1e,0x1e,0x23,0x2,0x5d,0x45,0x45,0x57,0x9b,0x63,0xfe,0x6e,0x23,0x1e,0x1e,0x23,0x2,0x5d,0x45,0x45,0x57,0x9b,0x63,0xfe,0x6e,0x23,0x1e, + 0x1e,0x23,0x3,0x2e,0x23,0x1e,0x1e,0x23,0x70,0x5e,0x5f,0x5c,0x59,0xb5,0x0,0x1,0x0,0xca,0xff,0xf6,0x4,0x14,0x3,0xb2,0x0,0x26,0x0,0x4c,0xb5,0x23, + 0x1,0x0,0x1,0x1,0x4a,0x4b,0xb0,0x28,0x50,0x58,0x40,0x13,0x0,0x1,0x1,0x3,0x5b,0x5,0x4,0x2,0x3,0x3,0x11,0x4b,0x2,0x1,0x0,0x0,0xf,0x0, + 0x4c,0x1b,0x40,0x17,0x0,0x3,0x3,0x11,0x4b,0x0,0x1,0x1,0x4,0x5b,0x5,0x1,0x4,0x4,0x19,0x4b,0x2,0x1,0x0,0x0,0xf,0x0,0x4c,0x59,0x40,0xd, + 0x0,0x0,0x0,0x26,0x0,0x25,0x35,0x36,0x25,0x35,0x6,0x7,0x18,0x2b,0x0,0x16,0x15,0x11,0x14,0x6,0x23,0x23,0x22,0x26,0x35,0x11,0x34,0x26,0x23,0x22, + 0x6,0x6,0x15,0x11,0x14,0x6,0x23,0x23,0x22,0x26,0x35,0x11,0x34,0x36,0x33,0x33,0x32,0x16,0x15,0x15,0x36,0x36,0x33,0x3,0x6e,0xa6,0x21,0x29,0x2,0x29, + 0x21,0x59,0x5d,0x56,0xa7,0x6b,0x21,0x29,0x2,0x29,0x21,0x1e,0x27,0x2,0x27,0x1e,0x44,0xd2,0x6b,0x3,0xb2,0xae,0x98,0xfd,0xcb,0x23,0x1e,0x1e,0x23,0x2, + 0x26,0x5a,0x67,0x6d,0xbb,0x6e,0xfe,0xaf,0x23,0x1e,0x1e,0x23,0x3,0x2e,0x23,0x1e,0x1e,0x23,0xa3,0x69,0x87,0x0,0xff,0xff,0x0,0xca,0xff,0xf6,0x4,0x14, + 0x5,0x87,0x0,0x22,0x0,0xc0,0x0,0x0,0x0,0x2,0x1,0x80,0xfe,0x0,0x0,0x0,0xff,0xff,0x0,0xca,0xff,0xf6,0x4,0x14,0x5,0x76,0x0,0x22,0x0,0xc0, + 0x0,0x0,0x0,0x2,0x1,0x83,0xa,0x0,0x0,0x0,0xff,0xff,0x0,0xca,0xfd,0xf2,0x4,0x14,0x3,0xb2,0x0,0x22,0x0,0xc0,0x0,0x0,0x0,0x3,0x1,0x7e, + 0x4,0xc8,0x0,0x0,0x0,0x1,0x0,0xca,0xfe,0x66,0x4,0x14,0x3,0xb2,0x0,0x32,0x0,0x75,0x40,0xa,0x2f,0x1,0x4,0x3,0xb,0x1,0x0,0x2,0x2,0x4a, + 0x4b,0xb0,0x28,0x50,0x58,0x40,0x24,0x0,0x1,0x4,0x2,0x4,0x1,0x2,0x70,0x0,0x3,0x3,0x5,0x5b,0x7,0x6,0x2,0x5,0x5,0x11,0x4b,0x0,0x4,0x4, + 0xf,0x4b,0x0,0x2,0x2,0x0,0x5b,0x0,0x0,0x0,0x13,0x0,0x4c,0x1b,0x40,0x28,0x0,0x1,0x4,0x2,0x4,0x1,0x2,0x70,0x0,0x5,0x5,0x11,0x4b,0x0, + 0x3,0x3,0x6,0x5b,0x7,0x1,0x6,0x6,0x19,0x4b,0x0,0x4,0x4,0xf,0x4b,0x0,0x2,0x2,0x0,0x5b,0x0,0x0,0x0,0x13,0x0,0x4c,0x59,0x40,0xf,0x0, + 0x0,0x0,0x32,0x0,0x31,0x35,0x36,0x25,0x23,0x17,0x25,0x8,0x7,0x1a,0x2b,0x0,0x16,0x15,0x11,0x14,0x6,0x23,0x22,0x26,0x27,0x26,0x35,0x34,0x37,0x36, + 0x33,0x32,0x17,0x16,0x33,0x32,0x36,0x35,0x11,0x34,0x26,0x23,0x22,0x6,0x6,0x15,0x11,0x14,0x6,0x23,0x23,0x22,0x26,0x35,0x11,0x34,0x36,0x33,0x33,0x32, + 0x16,0x15,0x15,0x36,0x36,0x33,0x3,0x6e,0xa6,0x8d,0x84,0x3a,0x78,0x35,0x27,0xb,0x15,0x22,0xf,0xd,0x60,0x51,0x3f,0x3b,0x59,0x5d,0x56,0xa7,0x6b,0x21, + 0x29,0x2,0x29,0x21,0x1e,0x27,0x2,0x27,0x1e,0x44,0xd2,0x6b,0x3,0xb2,0xae,0x98,0xfd,0xd,0x86,0x8d,0x1b,0x17,0x11,0x23,0x10,0x1b,0x2f,0x6,0x28,0x4a, + 0x53,0x2,0xc8,0x5a,0x67,0x6d,0xbb,0x6e,0xfe,0xaf,0x23,0x1e,0x1e,0x23,0x3,0x2e,0x23,0x1e,0x1e,0x23,0xa3,0x69,0x87,0x0,0xff,0xff,0x0,0xca,0xff,0xf6, + 0x4,0x14,0x5,0x2d,0x0,0x22,0x0,0xc0,0x0,0x0,0x0,0x2,0x1,0x9a,0xa,0x0,0x0,0x0,0x0,0x2,0x0,0x86,0xff,0xea,0x4,0x46,0x3,0xb2,0x0,0xf, + 0x0,0x1f,0x0,0x2c,0x40,0x29,0x5,0x1,0x3,0x3,0x1,0x5b,0x4,0x1,0x1,0x1,0x19,0x4b,0x0,0x2,0x2,0x0,0x5b,0x0,0x0,0x0,0x17,0x0,0x4c,0x10, + 0x10,0x0,0x0,0x10,0x1f,0x10,0x1e,0x18,0x16,0x0,0xf,0x0,0xe,0x26,0x6,0x7,0x15,0x2b,0x0,0x16,0x16,0x15,0x14,0x6,0x6,0x23,0x22,0x26,0x26,0x35, + 0x34,0x36,0x36,0x33,0xe,0x2,0x15,0x14,0x16,0x16,0x33,0x32,0x36,0x36,0x35,0x34,0x26,0x26,0x23,0x2,0xee,0xdb,0x7d,0x7d,0xdb,0x88,0x88,0xdb,0x7d,0x7d, + 0xdb,0x88,0x61,0x94,0x51,0x51,0x94,0x61,0x61,0x94,0x51,0x51,0x94,0x61,0x3,0xb2,0x7c,0xdd,0x8b,0x8b,0xdd,0x7c,0x7c,0xdd,0x8b,0x8b,0xdd,0x7c,0x94,0x55, + 0x99,0x62,0x62,0x99,0x55,0x55,0x99,0x62,0x62,0x99,0x55,0x0,0xff,0xff,0x0,0x86,0xff,0xea,0x4,0x46,0x5,0x87,0x0,0x22,0x0,0xc6,0x0,0x0,0x0,0x2, + 0x1,0x80,0xea,0x0,0x0,0x0,0xff,0xff,0x0,0x86,0xff,0xea,0x4,0x46,0x5,0x5c,0x0,0x22,0x0,0xc6,0x0,0x0,0x0,0x2,0x1,0x82,0x0,0x0,0x0,0x0, + 0xff,0xff,0x0,0x86,0xff,0xea,0x4,0x46,0x5,0x74,0x0,0x22,0x0,0xc6,0x0,0x0,0x0,0x2,0x1,0x92,0x0,0x0,0x0,0x0,0xff,0xff,0x0,0x86,0xff,0xea, + 0x4,0x46,0x5,0x53,0x0,0x22,0x0,0xc6,0x0,0x0,0x0,0x2,0x1,0x93,0x0,0x0,0x0,0x0,0xff,0xff,0x0,0x86,0xff,0xea,0x4,0x46,0x5,0x87,0x0,0x22, + 0x0,0xc6,0x0,0x0,0x0,0x2,0x1,0x95,0x16,0x0,0x0,0x0,0xff,0xff,0x0,0x86,0xff,0xea,0x4,0x4f,0x5,0x76,0x0,0x22,0x1,0x96,0x0,0x0,0x0,0x2, + 0x0,0xc6,0x0,0x0,0x0,0x0,0xff,0xff,0x0,0x86,0xff,0xea,0x4,0x46,0x4,0xe2,0x0,0x22,0x1,0x97,0x0,0x0,0x0,0x2,0x0,0xc6,0x0,0x0,0x0,0x0, + 0x0,0x3,0x0,0x54,0xff,0xcc,0x4,0x78,0x3,0xd0,0x0,0x27,0x0,0x30,0x0,0x39,0x0,0x6a,0x40,0x13,0x22,0x1,0x4,0x2,0x33,0x32,0x2a,0x29,0x18,0x4, + 0x6,0x5,0x4,0xe,0x1,0x0,0x5,0x3,0x4a,0x4b,0xb0,0x22,0x50,0x58,0x40,0x1f,0x0,0x3,0x3,0x19,0x4b,0x0,0x4,0x4,0x2,0x5b,0x0,0x2,0x2,0x19, + 0x4b,0x0,0x5,0x5,0x0,0x5b,0x0,0x0,0x0,0x17,0x4b,0x0,0x1,0x1,0x17,0x1,0x4c,0x1b,0x40,0x1f,0x0,0x3,0x2,0x3,0x72,0x0,0x1,0x0,0x1,0x73, + 0x0,0x4,0x4,0x2,0x5b,0x0,0x2,0x2,0x19,0x4b,0x0,0x5,0x5,0x0,0x5b,0x0,0x0,0x0,0x17,0x0,0x4c,0x59,0x40,0x9,0x27,0x25,0x24,0x2c,0x24,0x2a, + 0x6,0x7,0x1a,0x2b,0x0,0x15,0x14,0x7,0x7,0x16,0x16,0x15,0x14,0x6,0x6,0x23,0x22,0x26,0x27,0x7,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x37,0x26, + 0x26,0x35,0x34,0x36,0x36,0x33,0x32,0x16,0x17,0x37,0x36,0x33,0x32,0x17,0x0,0x17,0x1,0x26,0x23,0x22,0x6,0x6,0x15,0x24,0x27,0x1,0x16,0x33,0x32,0x36, + 0x36,0x35,0x4,0x78,0x19,0x6d,0x28,0x2c,0x7d,0xdb,0x88,0x56,0x98,0x3e,0x6d,0x19,0x14,0x16,0x1c,0x1a,0x18,0x6e,0x28,0x2c,0x7d,0xdb,0x88,0x56,0x98,0x3e, + 0x6c,0x19,0x14,0x17,0x1c,0xfc,0xc2,0x2a,0x1,0xda,0x50,0x6e,0x61,0x94,0x51,0x2,0x8c,0x2a,0xfe,0x25,0x52,0x6d,0x61,0x94,0x51,0x3,0x98,0x16,0x16,0x18, + 0x69,0x3c,0x91,0x50,0x8b,0xdd,0x7c,0x33,0x2f,0x69,0x17,0x1c,0x1c,0x16,0x15,0x18,0x6a,0x3c,0x91,0x50,0x8b,0xdd,0x7c,0x33,0x2f,0x69,0x17,0x1c,0xfd,0xb7, + 0x4e,0x1,0xca,0x37,0x55,0x99,0x62,0x63,0x4e,0xfe,0x36,0x37,0x55,0x99,0x62,0x0,0x0,0x0,0xff,0xff,0x0,0x86,0xff,0xea,0x4,0x46,0x5,0x2d,0x0,0x22, + 0x0,0xc6,0x0,0x0,0x0,0x2,0x1,0x9a,0x0,0x0,0x0,0x0,0x0,0x3,0x0,0x46,0xff,0xea,0x4,0x80,0x3,0xb2,0x0,0x26,0x0,0x2d,0x0,0x39,0x0,0x58, + 0x40,0x55,0x23,0x1,0x8,0x5,0x19,0x1,0x3,0x1,0x2,0x4a,0x0,0x2,0x0,0x1,0x0,0x2,0x1,0x70,0x0,0x7,0x0,0x0,0x2,0x7,0x0,0x61,0xd,0xa, + 0xc,0x3,0x8,0x8,0x5,0x5b,0xb,0x6,0x2,0x5,0x5,0x19,0x4b,0x9,0x1,0x1,0x1,0x3,0x5b,0x4,0x1,0x3,0x3,0x17,0x3,0x4c,0x2e,0x2e,0x27,0x27, + 0x0,0x0,0x2e,0x39,0x2e,0x38,0x34,0x32,0x27,0x2d,0x27,0x2c,0x2a,0x29,0x0,0x26,0x0,0x25,0x24,0x23,0x27,0x23,0x21,0x24,0xe,0x7,0x1a,0x2b,0x0,0x16, + 0x17,0x16,0x6,0x23,0x21,0x12,0x33,0x32,0x36,0x37,0x36,0x33,0x32,0x17,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x22,0x26,0x27,0x6,0x23,0x22,0x26,0x35,0x34, + 0x36,0x33,0x32,0x17,0x36,0x36,0x33,0x6,0x6,0x7,0x21,0x26,0x26,0x23,0x4,0x6,0x15,0x14,0x16,0x33,0x32,0x36,0x35,0x34,0x26,0x23,0x3,0xed,0x8c,0x6, + 0x1,0x1e,0x22,0xfe,0x71,0x5,0x9f,0x30,0x50,0x2a,0x11,0x16,0x1f,0x19,0x15,0x1b,0x37,0x84,0x48,0x4e,0x7a,0x28,0x4b,0x9c,0x9a,0x9e,0x9e,0x9a,0x9c,0x4b, + 0x28,0x7a,0x4e,0x45,0x4e,0xc,0x1,0x2c,0x7,0x42,0x42,0xfd,0xd8,0x4b,0x4b,0x4f,0x4f,0x4b,0x4b,0x4f,0x3,0xb2,0xe8,0xd4,0x21,0x1d,0xfe,0xc3,0x25,0x29, + 0x11,0x1f,0x19,0x19,0x1c,0x19,0x34,0x36,0x45,0x45,0x8a,0xfb,0xe9,0xe9,0xfb,0x8c,0x46,0x46,0x91,0x68,0x75,0x6c,0x71,0x3,0xa1,0xaf,0xaf,0xa1,0xa1,0xaf, + 0xaf,0xa1,0x0,0x2,0x0,0xc0,0xfe,0x72,0x4,0x41,0x3,0xb2,0x0,0x1d,0x0,0x2d,0x0,0x68,0xb6,0x1a,0x9,0x2,0x4,0x5,0x1,0x4a,0x4b,0xb0,0x28,0x50, + 0x58,0x40,0x1d,0x7,0x1,0x5,0x5,0x2,0x5b,0x6,0x3,0x2,0x2,0x2,0x11,0x4b,0x0,0x4,0x4,0x0,0x5b,0x0,0x0,0x0,0x17,0x4b,0x0,0x1,0x1,0x13, + 0x1,0x4c,0x1b,0x40,0x21,0x0,0x2,0x2,0x11,0x4b,0x7,0x1,0x5,0x5,0x3,0x5b,0x6,0x1,0x3,0x3,0x19,0x4b,0x0,0x4,0x4,0x0,0x5b,0x0,0x0,0x0, + 0x17,0x4b,0x0,0x1,0x1,0x13,0x1,0x4c,0x59,0x40,0x14,0x1e,0x1e,0x0,0x0,0x1e,0x2d,0x1e,0x2c,0x26,0x24,0x0,0x1d,0x0,0x1c,0x35,0x34,0x26,0x8,0x7, + 0x17,0x2b,0x0,0x16,0x16,0x15,0x14,0x6,0x6,0x23,0x22,0x27,0x11,0x14,0x6,0x23,0x23,0x22,0x26,0x35,0x11,0x34,0x36,0x33,0x33,0x32,0x16,0x15,0x15,0x36, + 0x36,0x33,0xe,0x2,0x15,0x14,0x16,0x16,0x33,0x32,0x36,0x36,0x35,0x34,0x26,0x26,0x23,0x3,0xf,0xc3,0x6f,0x6f,0xc3,0x7c,0xbc,0x81,0x21,0x29,0x2,0x29, + 0x21,0x1e,0x27,0x2,0x27,0x1e,0x3f,0xa6,0x62,0x63,0x8b,0x4f,0x4f,0x8b,0x59,0x59,0x81,0x44,0x44,0x81,0x59,0x3,0xb2,0x75,0xdb,0x94,0x94,0xdb,0x75,0xa5, + 0xfe,0x24,0x23,0x1e,0x1e,0x23,0x4,0xb2,0x23,0x1e,0x1e,0x23,0x5d,0x52,0x58,0x94,0x50,0x98,0x68,0x68,0x98,0x50,0x55,0x99,0x62,0x62,0x99,0x55,0x0,0x2, + 0x0,0xc0,0xfe,0x72,0x4,0x41,0x5,0x2a,0x0,0x1d,0x0,0x2d,0x0,0x3f,0x40,0x3c,0x10,0x3,0x2,0x4,0x5,0x1,0x4a,0x6,0x1,0x3,0x3,0x10,0x4b,0x7, + 0x1,0x5,0x5,0x0,0x5b,0x0,0x0,0x0,0x19,0x4b,0x0,0x4,0x4,0x1,0x5b,0x0,0x1,0x1,0x17,0x4b,0x0,0x2,0x2,0x13,0x2,0x4c,0x1e,0x1e,0x0,0x0, + 0x1e,0x2d,0x1e,0x2c,0x26,0x24,0x0,0x1d,0x0,0x1b,0x34,0x26,0x25,0x8,0x7,0x17,0x2b,0x0,0x16,0x15,0x11,0x36,0x36,0x33,0x32,0x16,0x16,0x15,0x14,0x6, + 0x6,0x23,0x22,0x27,0x11,0x14,0x6,0x23,0x23,0x22,0x26,0x35,0x11,0x34,0x36,0x33,0x33,0x0,0x6,0x6,0x15,0x14,0x16,0x16,0x33,0x32,0x36,0x36,0x35,0x34, + 0x26,0x26,0x23,0x1,0x35,0x21,0x3e,0xa1,0x5e,0x7c,0xc3,0x6f,0x6f,0xc3,0x7c,0xbc,0x81,0x21,0x29,0x2,0x29,0x21,0x21,0x29,0x2,0x1,0x24,0x8b,0x4f,0x4f, + 0x8b,0x59,0x59,0x81,0x44,0x44,0x81,0x59,0x5,0x2a,0x1e,0x23,0xfe,0x2a,0x4c,0x53,0x75,0xdb,0x94,0x94,0xdb,0x75,0xa5,0xfe,0x24,0x23,0x1e,0x1e,0x23,0x6, + 0x36,0x23,0x1e,0xfd,0xf4,0x50,0x98,0x68,0x68,0x98,0x50,0x55,0x99,0x62,0x62,0x99,0x55,0x0,0x0,0x2,0x0,0x8b,0xfe,0x72,0x4,0xc,0x3,0xb2,0x0,0x1d, + 0x0,0x2d,0x0,0x68,0xb6,0x13,0x2,0x2,0x4,0x5,0x1,0x4a,0x4b,0xb0,0x28,0x50,0x58,0x40,0x1d,0x7,0x1,0x5,0x5,0x0,0x5b,0x6,0x3,0x2,0x0,0x0, + 0x11,0x4b,0x0,0x4,0x4,0x2,0x5b,0x0,0x2,0x2,0x17,0x4b,0x0,0x1,0x1,0x13,0x1,0x4c,0x1b,0x40,0x21,0x0,0x0,0x0,0x11,0x4b,0x7,0x1,0x5,0x5, + 0x3,0x5b,0x6,0x1,0x3,0x3,0x19,0x4b,0x0,0x4,0x4,0x2,0x5b,0x0,0x2,0x2,0x17,0x4b,0x0,0x1,0x1,0x13,0x1,0x4c,0x59,0x40,0x14,0x1e,0x1e,0x0, + 0x0,0x1e,0x2d,0x1e,0x2c,0x26,0x24,0x0,0x1d,0x0,0x1c,0x24,0x35,0x35,0x8,0x7,0x17,0x2b,0x0,0x16,0x17,0x35,0x34,0x36,0x33,0x33,0x32,0x16,0x15,0x11, + 0x14,0x6,0x23,0x23,0x22,0x26,0x35,0x11,0x6,0x23,0x22,0x26,0x26,0x35,0x34,0x36,0x36,0x33,0xe,0x2,0x15,0x14,0x16,0x16,0x33,0x32,0x36,0x36,0x35,0x34, + 0x26,0x26,0x23,0x2,0x9b,0xa6,0x3f,0x1e,0x27,0x2,0x27,0x1e,0x21,0x29,0x2,0x29,0x21,0x81,0xbc,0x7c,0xc3,0x6f,0x6f,0xc3,0x7c,0x4f,0x81,0x44,0x44,0x81, + 0x59,0x59,0x8b,0x4f,0x4f,0x8b,0x59,0x3,0xb2,0x58,0x52,0x5d,0x23,0x1e,0x1e,0x23,0xfb,0x4e,0x23,0x1e,0x1e,0x23,0x1,0xdc,0xa5,0x75,0xdb,0x94,0x94,0xdb, + 0x75,0x94,0x55,0x99,0x62,0x62,0x99,0x55,0x50,0x98,0x68,0x68,0x98,0x50,0x0,0x1,0x1,0x33,0xff,0xf6,0x4,0x3d,0x3,0xb2,0x0,0x24,0x0,0x8e,0x4b,0xb0, + 0x17,0x50,0x58,0x40,0xa,0x3,0x1,0x0,0x3,0x21,0x1,0x2,0x0,0x2,0x4a,0x1b,0x40,0xa,0x3,0x1,0x1,0x3,0x21,0x1,0x2,0x0,0x2,0x4a,0x59,0x4b, + 0xb0,0x17,0x50,0x58,0x40,0x13,0x1,0x1,0x0,0x0,0x3,0x5b,0x5,0x4,0x2,0x3,0x3,0x11,0x4b,0x0,0x2,0x2,0xf,0x2,0x4c,0x1b,0x4b,0xb0,0x28,0x50, + 0x58,0x40,0x1a,0x0,0x0,0x1,0x2,0x1,0x0,0x2,0x70,0x0,0x1,0x1,0x3,0x5b,0x5,0x4,0x2,0x3,0x3,0x11,0x4b,0x0,0x2,0x2,0xf,0x2,0x4c,0x1b, + 0x40,0x1e,0x0,0x0,0x1,0x2,0x1,0x0,0x2,0x70,0x0,0x3,0x3,0x11,0x4b,0x0,0x1,0x1,0x4,0x5b,0x5,0x1,0x4,0x4,0x19,0x4b,0x0,0x2,0x2,0xf, + 0x2,0x4c,0x59,0x59,0x40,0xd,0x0,0x0,0x0,0x24,0x0,0x23,0x35,0x36,0x22,0x27,0x6,0x7,0x18,0x2b,0x0,0x17,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x22, + 0x27,0x26,0x23,0x22,0x6,0x6,0x15,0x15,0x14,0x6,0x23,0x23,0x22,0x26,0x35,0x11,0x34,0x36,0x33,0x33,0x32,0x16,0x15,0x15,0x36,0x36,0x33,0x3,0xca,0x42, + 0x31,0x4,0x9,0x19,0x13,0xb,0xb,0x38,0x36,0x73,0xca,0x7a,0x21,0x29,0x2,0x29,0x21,0x1e,0x27,0x2,0x27,0x1e,0x57,0xee,0x85,0x3,0xb2,0x15,0x10,0x30, + 0x10,0xf,0x21,0x21,0x4,0x12,0x8c,0xed,0x8b,0xd7,0x23,0x1e,0x1e,0x23,0x3,0x2e,0x23,0x1e,0x1e,0x23,0xeb,0x93,0xa5,0x0,0x0,0x0,0xff,0xff,0x1,0x33, + 0xff,0xf6,0x4,0x3d,0x5,0x87,0x0,0x22,0x1,0x80,0x50,0x0,0x0,0x2,0x0,0xd4,0x0,0x0,0x0,0x0,0xff,0xff,0x1,0x33,0xff,0xf6,0x4,0x3d,0x5,0x76, + 0x0,0x22,0x0,0xd4,0x0,0x0,0x0,0x2,0x1,0x83,0x46,0x0,0x0,0x0,0xff,0xff,0x1,0x33,0xfd,0xf2,0x4,0x3d,0x3,0xb2,0x0,0x22,0x0,0xd4,0x0,0x0, + 0x0,0x3,0x1,0x7e,0x4,0x64,0x0,0x0,0x0,0x1,0x0,0xb2,0xff,0xea,0x4,0x18,0x3,0xb2,0x0,0x3e,0x0,0x3f,0x40,0x3c,0x3,0x1,0x1,0x0,0x1,0x4a, + 0x0,0x3,0x4,0x0,0x4,0x3,0x0,0x70,0x0,0x0,0x1,0x4,0x0,0x1,0x6e,0x0,0x4,0x4,0x2,0x5b,0x0,0x2,0x2,0x19,0x4b,0x0,0x1,0x1,0x5,0x5b, + 0x6,0x1,0x5,0x5,0x17,0x5,0x4c,0x0,0x0,0x0,0x3e,0x0,0x3d,0x2c,0x2a,0x27,0x26,0x20,0x1e,0x24,0x16,0x7,0x7,0x16,0x2b,0x4,0x27,0x26,0x35,0x34, + 0x37,0x36,0x33,0x32,0x17,0x16,0x16,0x33,0x32,0x36,0x35,0x34,0x26,0x27,0x26,0x26,0x27,0x26,0x26,0x27,0x26,0x26,0x35,0x34,0x36,0x36,0x33,0x32,0x17,0x16, + 0x15,0x14,0x7,0x6,0x23,0x22,0x27,0x26,0x23,0x22,0x6,0x15,0x14,0x16,0x17,0x16,0x16,0x17,0x16,0x16,0x17,0x16,0x16,0x15,0x14,0x6,0x6,0x23,0x1,0x90, + 0xb7,0x27,0xc,0x18,0x1e,0xf,0x10,0x52,0xbc,0x63,0x7b,0x7f,0x29,0x28,0x22,0x5c,0x48,0x62,0x7c,0x35,0x47,0x4f,0x61,0xa9,0x6c,0xe5,0xad,0x28,0xa,0x17, + 0x1f,0xa,0x14,0xa3,0xb3,0x6a,0x78,0x28,0x25,0x21,0x52,0x4b,0x5d,0x7e,0x36,0x4d,0x57,0x5a,0xb1,0x7f,0x16,0x64,0x16,0x23,0x13,0x19,0x2f,0x9,0x2e,0x30, + 0x53,0x47,0x22,0x2b,0xd,0xb,0xd,0x7,0x9,0x14,0x15,0x1c,0x66,0x52,0x5c,0x81,0x41,0x57,0x14,0x25,0x14,0x17,0x32,0x9,0x53,0x48,0x45,0x1f,0x27,0xc, + 0xb,0xb,0x7,0x8,0x13,0x14,0x1d,0x6b,0x59,0x5a,0x87,0x4a,0xff,0xff,0x0,0xb2,0xff,0xea,0x4,0x18,0x5,0x87,0x0,0x22,0x0,0xd8,0x0,0x0,0x0,0x2, + 0x1,0x80,0xea,0x0,0x0,0x0,0xff,0xff,0x0,0xb2,0xff,0xea,0x4,0x18,0x5,0x76,0x0,0x22,0x0,0xd8,0x0,0x0,0x0,0x2,0x1,0x83,0x0,0x0,0x0,0x0, + 0x0,0x1,0x0,0xb2,0xfe,0x28,0x4,0x18,0x3,0xb2,0x0,0x5e,0x0,0xf8,0x40,0xe,0x27,0x1,0x7,0x6,0x23,0x1,0x0,0x7,0xf,0x1,0x2,0x4,0x3,0x4a, + 0x4b,0xb0,0xa,0x50,0x58,0x40,0x3f,0x0,0x9,0xa,0x6,0xa,0x9,0x6,0x70,0x0,0x6,0x7,0xa,0x6,0x7,0x6e,0x0,0x1,0x0,0x5,0x4,0x1,0x68,0x0, + 0x5,0x3,0x0,0x5,0x66,0x0,0x3,0x4,0x0,0x3,0x4,0x6e,0x0,0x4,0x0,0x2,0x4,0x2,0x60,0x0,0xa,0xa,0x8,0x5b,0x0,0x8,0x8,0x19,0x4b,0x0, + 0x7,0x7,0x0,0x5b,0x0,0x0,0x0,0x17,0x0,0x4c,0x1b,0x4b,0xb0,0xc,0x50,0x58,0x40,0x40,0x0,0x9,0xa,0x6,0xa,0x9,0x6,0x70,0x0,0x6,0x7,0xa, + 0x6,0x7,0x6e,0x0,0x1,0x0,0x5,0x0,0x1,0x5,0x70,0x0,0x5,0x3,0x0,0x5,0x66,0x0,0x3,0x4,0x0,0x3,0x4,0x6e,0x0,0x4,0x0,0x2,0x4,0x2, + 0x60,0x0,0xa,0xa,0x8,0x5b,0x0,0x8,0x8,0x19,0x4b,0x0,0x7,0x7,0x0,0x5b,0x0,0x0,0x0,0x17,0x0,0x4c,0x1b,0x40,0x41,0x0,0x9,0xa,0x6,0xa, + 0x9,0x6,0x70,0x0,0x6,0x7,0xa,0x6,0x7,0x6e,0x0,0x1,0x0,0x5,0x0,0x1,0x5,0x70,0x0,0x5,0x3,0x0,0x5,0x3,0x6e,0x0,0x3,0x4,0x0,0x3, + 0x4,0x6e,0x0,0x4,0x0,0x2,0x4,0x2,0x60,0x0,0xa,0xa,0x8,0x5b,0x0,0x8,0x8,0x19,0x4b,0x0,0x7,0x7,0x0,0x5b,0x0,0x0,0x0,0x17,0x0,0x4c, + 0x59,0x59,0x40,0x11,0x50,0x4e,0x4b,0x4a,0x44,0x42,0x24,0x1b,0x24,0x24,0x17,0x24,0x11,0x12,0xb,0x7,0x1c,0x2b,0x24,0x6,0x6,0x7,0x15,0x36,0x16,0x15, + 0x14,0x6,0x23,0x22,0x26,0x27,0x26,0x35,0x34,0x37,0x36,0x33,0x32,0x17,0x16,0x16,0x33,0x32,0x36,0x35,0x34,0x26,0x23,0x7,0x22,0x26,0x35,0x35,0x26,0x27, + 0x26,0x35,0x34,0x37,0x36,0x33,0x32,0x17,0x16,0x16,0x33,0x32,0x36,0x35,0x34,0x26,0x27,0x26,0x26,0x27,0x26,0x26,0x27,0x26,0x26,0x35,0x34,0x36,0x36,0x33, + 0x32,0x17,0x16,0x15,0x14,0x7,0x6,0x23,0x22,0x27,0x26,0x23,0x22,0x6,0x15,0x14,0x16,0x17,0x16,0x16,0x17,0x16,0x16,0x17,0x16,0x16,0x15,0x4,0x18,0x53, + 0xa4,0x76,0x65,0x66,0x7c,0x79,0x44,0x80,0x33,0x17,0x8,0x11,0x1d,0x8,0x5,0x2b,0x67,0x2f,0x33,0x3a,0x36,0x3e,0x27,0xf,0x13,0xbf,0x8f,0x27,0xc,0x18, + 0x1e,0xf,0x10,0x52,0xbc,0x63,0x7b,0x7f,0x29,0x28,0x22,0x5c,0x48,0x62,0x7c,0x35,0x47,0x4f,0x61,0xa9,0x6c,0xe5,0xad,0x28,0xa,0x17,0x1f,0xa,0x14,0xa3, + 0xb3,0x6a,0x78,0x28,0x25,0x21,0x52,0x4b,0x5d,0x7e,0x36,0x4d,0x57,0xbe,0x83,0x4c,0x4,0x5c,0x1,0x5e,0x4b,0x57,0x68,0x18,0x18,0xa,0x19,0xe,0x15,0x2d, + 0x2,0x10,0x16,0x21,0x1f,0x21,0x20,0x1,0x15,0x17,0x9f,0x11,0x4f,0x16,0x23,0x13,0x19,0x2f,0x9,0x2e,0x30,0x53,0x47,0x22,0x2b,0xd,0xb,0xd,0x7,0x9, + 0x14,0x15,0x1c,0x66,0x52,0x5c,0x81,0x41,0x57,0x14,0x25,0x14,0x17,0x32,0x9,0x53,0x48,0x45,0x1f,0x27,0xc,0xb,0xb,0x7,0x8,0x13,0x14,0x1d,0x6b,0x59, + 0x0,0x0,0xff,0xff,0x0,0xb2,0xff,0xea,0x4,0x18,0x5,0x74,0x0,0x22,0x1,0x92,0x0,0x0,0x0,0x2,0x0,0xd8,0x0,0x0,0x0,0x0,0xff,0xff,0x0,0xb2, + 0xfd,0xf2,0x4,0x18,0x3,0xb2,0x0,0x22,0x0,0xd8,0x0,0x0,0x0,0x3,0x1,0x7e,0x4,0xbe,0x0,0x0,0x0,0x1,0x0,0xeb,0xff,0xea,0x4,0xd,0x5,0x25, + 0x0,0x35,0x0,0x65,0xb5,0x6,0x1,0x2,0x3,0x1,0x4a,0x4b,0xb0,0x28,0x50,0x58,0x40,0x1f,0x0,0x2,0x3,0x1,0x3,0x2,0x1,0x70,0x0,0x3,0x3,0x5, + 0x5b,0x6,0x1,0x5,0x5,0x10,0x4b,0x0,0x1,0x1,0x0,0x5b,0x4,0x1,0x0,0x0,0x17,0x0,0x4c,0x1b,0x40,0x23,0x0,0x2,0x3,0x1,0x3,0x2,0x1,0x70, + 0x0,0x3,0x3,0x5,0x5b,0x6,0x1,0x5,0x5,0x10,0x4b,0x0,0x4,0x4,0xf,0x4b,0x0,0x1,0x1,0x0,0x5b,0x0,0x0,0x0,0x17,0x0,0x4c,0x59,0x40,0xe, + 0x0,0x0,0x0,0x35,0x0,0x34,0x35,0x2a,0x24,0x24,0x2c,0x7,0x7,0x19,0x2b,0x0,0x16,0x16,0x15,0x14,0x6,0x7,0x16,0x16,0x15,0x14,0x6,0x6,0x23,0x22, + 0x26,0x35,0x34,0x36,0x33,0x32,0x36,0x35,0x34,0x26,0x23,0x22,0x26,0x35,0x34,0x36,0x37,0x36,0x36,0x35,0x34,0x26,0x23,0x22,0x6,0x15,0x11,0x14,0x6,0x23, + 0x23,0x22,0x26,0x35,0x11,0x34,0x36,0x36,0x33,0x2,0xd7,0xaa,0x55,0x5a,0x52,0x6b,0x78,0x64,0xb6,0x79,0x32,0x21,0x1e,0x22,0x7e,0x8e,0x8a,0x76,0x22,0x1d, + 0x1d,0x22,0x62,0x67,0x75,0x6d,0x67,0x72,0x21,0x29,0x2,0x29,0x21,0x59,0xa8,0x72,0x5,0x25,0x5b,0x9c,0x63,0x5b,0x87,0x2b,0x29,0xb2,0x7b,0x76,0xad,0x5b, + 0x21,0x2c,0x25,0x22,0x7a,0x72,0x7a,0x87,0x21,0x24,0x24,0x20,0x3,0xa,0x70,0x5a,0x5d,0x69,0x7f,0x6e,0xfc,0x93,0x23,0x1e,0x1e,0x23,0x3,0x78,0x6e,0xa9, + 0x5f,0x0,0x0,0x1,0x0,0x75,0xff,0xea,0x4,0x48,0x4,0xb9,0x0,0x2d,0x0,0x30,0x40,0x2d,0x0,0x7,0x1,0x6,0x1,0x7,0x6,0x70,0x4,0x1,0x2,0x5, + 0x1,0x1,0x7,0x2,0x1,0x61,0x0,0x3,0x3,0x16,0x4b,0x0,0x6,0x6,0x0,0x5b,0x0,0x0,0x0,0x17,0x0,0x4c,0x23,0x23,0x24,0x23,0x23,0x24,0x23,0x25, + 0x8,0x7,0x1c,0x2b,0x24,0x15,0x14,0x7,0x6,0x6,0x23,0x22,0x26,0x35,0x11,0x23,0x22,0x26,0x35,0x34,0x36,0x33,0x33,0x11,0x34,0x36,0x33,0x32,0x16,0x15, + 0x11,0x21,0x32,0x16,0x15,0x14,0x6,0x23,0x21,0x11,0x14,0x16,0x33,0x32,0x36,0x37,0x36,0x33,0x32,0x17,0x4,0x48,0x21,0x4e,0xc0,0x53,0xaa,0x9d,0xcb,0x22, + 0x1d,0x1d,0x22,0xcb,0x22,0x29,0x29,0x22,0x1,0x9b,0x22,0x1d,0x1d,0x22,0xfe,0x65,0x50,0x61,0x43,0x94,0x45,0xe,0x11,0x22,0x18,0xa1,0x13,0x20,0x14,0x31, + 0x3f,0xb1,0xb0,0x1,0x71,0x21,0x27,0x27,0x21,0x1,0x2c,0x23,0x1e,0x1e,0x23,0xfe,0xd4,0x21,0x27,0x27,0x21,0xfe,0x9c,0x72,0x6a,0x36,0x2d,0xa,0x2e,0x0, + 0x0,0x0,0x0,0x1,0x0,0x75,0xff,0xea,0x4,0x48,0x4,0xb9,0x0,0x3f,0x0,0x41,0x40,0x3e,0x0,0xb,0x1,0xa,0x1,0xb,0xa,0x70,0x6,0x1,0x4,0x7, + 0x1,0x3,0x2,0x4,0x3,0x61,0x8,0x1,0x2,0x9,0x1,0x1,0xb,0x2,0x1,0x63,0x0,0x5,0x5,0x16,0x4b,0x0,0xa,0xa,0x0,0x5b,0x0,0x0,0x0,0x17, + 0x0,0x4c,0x3e,0x3c,0x39,0x37,0x34,0x32,0x21,0x24,0x23,0x23,0x24,0x21,0x24,0x23,0x25,0xc,0x7,0x1d,0x2b,0x24,0x15,0x14,0x7,0x6,0x6,0x23,0x22,0x26, + 0x35,0x35,0x23,0x22,0x26,0x35,0x34,0x36,0x33,0x33,0x35,0x23,0x22,0x26,0x35,0x34,0x36,0x33,0x33,0x11,0x34,0x36,0x33,0x32,0x16,0x15,0x11,0x21,0x32,0x16, + 0x15,0x14,0x6,0x23,0x21,0x15,0x33,0x32,0x16,0x15,0x14,0x6,0x23,0x23,0x15,0x14,0x16,0x33,0x32,0x36,0x37,0x36,0x33,0x32,0x17,0x4,0x48,0x21,0x4e,0xc0, + 0x53,0xaa,0x9d,0x78,0x22,0x1d,0x1d,0x22,0x78,0xcb,0x22,0x1d,0x1d,0x22,0xcb,0x22,0x29,0x29,0x22,0x1,0x9b,0x22,0x1d,0x1d,0x22,0xfe,0x65,0xfc,0x22,0x1d, + 0x1d,0x22,0xfc,0x50,0x61,0x43,0x94,0x45,0xe,0x11,0x22,0x18,0xa1,0x13,0x20,0x14,0x31,0x3f,0xb1,0xb0,0x4b,0x21,0x27,0x27,0x21,0x96,0x21,0x27,0x27,0x21, + 0x1,0x2c,0x23,0x1e,0x1e,0x23,0xfe,0xd4,0x21,0x27,0x27,0x21,0x96,0x21,0x27,0x27,0x21,0x3e,0x72,0x6a,0x36,0x2d,0xa,0x2e,0xff,0xff,0x0,0x75,0xff,0xea, + 0x4,0x48,0x5,0x78,0x0,0x22,0x0,0xdf,0x0,0x0,0x1,0x7,0x1,0x7e,0x5,0xe4,0x6,0xa,0x0,0x9,0xb1,0x1,0x1,0xb8,0x6,0xa,0xb0,0x33,0x2b,0x0, + 0x0,0x1,0x0,0x75,0xfe,0x28,0x4,0x48,0x4,0xb9,0x0,0x4e,0x0,0xf1,0x40,0xa,0x27,0x1,0x0,0xb,0x13,0x1,0x2,0x4,0x2,0x4a,0x4b,0xb0,0xa,0x50, + 0x58,0x40,0x3d,0x0,0xc,0x6,0xb,0x6,0xc,0xb,0x70,0x0,0x1,0x0,0x5,0x4,0x1,0x68,0x0,0x5,0x3,0x0,0x5,0x66,0x0,0x3,0x4,0x0,0x3,0x4, + 0x6e,0x9,0x1,0x7,0xa,0x1,0x6,0xc,0x7,0x6,0x61,0x0,0x4,0x0,0x2,0x4,0x2,0x60,0x0,0x8,0x8,0x16,0x4b,0x0,0xb,0xb,0x0,0x5b,0x0,0x0, + 0x0,0x17,0x0,0x4c,0x1b,0x4b,0xb0,0xc,0x50,0x58,0x40,0x3e,0x0,0xc,0x6,0xb,0x6,0xc,0xb,0x70,0x0,0x1,0x0,0x5,0x0,0x1,0x5,0x70,0x0,0x5, + 0x3,0x0,0x5,0x66,0x0,0x3,0x4,0x0,0x3,0x4,0x6e,0x9,0x1,0x7,0xa,0x1,0x6,0xc,0x7,0x6,0x61,0x0,0x4,0x0,0x2,0x4,0x2,0x60,0x0,0x8, + 0x8,0x16,0x4b,0x0,0xb,0xb,0x0,0x5b,0x0,0x0,0x0,0x17,0x0,0x4c,0x1b,0x40,0x3f,0x0,0xc,0x6,0xb,0x6,0xc,0xb,0x70,0x0,0x1,0x0,0x5,0x0, + 0x1,0x5,0x70,0x0,0x5,0x3,0x0,0x5,0x3,0x6e,0x0,0x3,0x4,0x0,0x3,0x4,0x6e,0x9,0x1,0x7,0xa,0x1,0x6,0xc,0x7,0x6,0x61,0x0,0x4,0x0, + 0x2,0x4,0x2,0x60,0x0,0x8,0x8,0x16,0x4b,0x0,0xb,0xb,0x0,0x5b,0x0,0x0,0x0,0x17,0x0,0x4c,0x59,0x59,0x40,0x14,0x4d,0x4b,0x48,0x46,0x43,0x41, + 0x3d,0x3b,0x23,0x24,0x28,0x24,0x24,0x17,0x24,0x12,0x15,0xd,0x7,0x1d,0x2b,0x24,0x15,0x14,0x7,0x6,0x6,0x23,0x23,0x15,0x36,0x16,0x15,0x14,0x6,0x23, + 0x22,0x26,0x27,0x26,0x35,0x34,0x37,0x36,0x33,0x32,0x17,0x16,0x16,0x33,0x32,0x36,0x35,0x34,0x26,0x23,0x7,0x22,0x26,0x35,0x35,0x26,0x26,0x35,0x11,0x23, + 0x22,0x26,0x35,0x34,0x36,0x33,0x33,0x11,0x34,0x36,0x33,0x32,0x16,0x15,0x11,0x21,0x32,0x16,0x15,0x14,0x6,0x23,0x21,0x11,0x14,0x16,0x33,0x32,0x36,0x37, + 0x36,0x33,0x32,0x17,0x4,0x48,0x21,0x4e,0xc0,0x53,0x7,0x65,0x66,0x7c,0x79,0x44,0x80,0x33,0x17,0x8,0x11,0x1d,0x8,0x5,0x2b,0x67,0x2f,0x33,0x3a,0x36, + 0x3e,0x27,0xf,0x13,0x61,0x5b,0xcb,0x22,0x1d,0x1d,0x22,0xcb,0x22,0x29,0x29,0x22,0x1,0x9b,0x22,0x1d,0x1d,0x22,0xfe,0x65,0x50,0x61,0x43,0x94,0x45,0xe, + 0x11,0x22,0x18,0xa1,0x13,0x20,0x14,0x31,0x3f,0x5b,0x1,0x5e,0x4b,0x57,0x68,0x18,0x18,0xa,0x19,0xe,0x15,0x2d,0x2,0x10,0x16,0x21,0x1f,0x21,0x20,0x1, + 0x15,0x17,0xae,0x20,0xa7,0x87,0x1,0x71,0x21,0x27,0x27,0x21,0x1,0x2c,0x23,0x1e,0x1e,0x23,0xfe,0xd4,0x21,0x27,0x27,0x21,0xfe,0x9c,0x72,0x6a,0x36,0x2d, + 0xa,0x2e,0xff,0xff,0x0,0x75,0xfd,0xf2,0x4,0x48,0x4,0xb9,0x0,0x22,0x0,0xdf,0x0,0x0,0x0,0x3,0x1,0x7e,0x4,0xfe,0x0,0x0,0x0,0x1,0x0,0xb8, + 0xff,0xea,0x4,0x2,0x3,0xa6,0x0,0x26,0x0,0x4c,0xb5,0xb,0x1,0x3,0x2,0x1,0x4a,0x4b,0xb0,0x28,0x50,0x58,0x40,0x13,0x5,0x4,0x2,0x2,0x2,0x11, + 0x4b,0x0,0x3,0x3,0x0,0x5b,0x1,0x1,0x0,0x0,0xf,0x0,0x4c,0x1b,0x40,0x17,0x5,0x4,0x2,0x2,0x2,0x11,0x4b,0x0,0x0,0x0,0xf,0x4b,0x0,0x3, + 0x3,0x1,0x5b,0x0,0x1,0x1,0x17,0x1,0x4c,0x59,0x40,0xd,0x0,0x0,0x0,0x26,0x0,0x24,0x25,0x35,0x25,0x35,0x6,0x7,0x18,0x2b,0x0,0x16,0x15,0x11, + 0x14,0x6,0x23,0x23,0x22,0x26,0x35,0x35,0x6,0x6,0x23,0x22,0x26,0x35,0x11,0x34,0x36,0x33,0x33,0x32,0x16,0x15,0x11,0x14,0x16,0x33,0x32,0x36,0x36,0x35, + 0x11,0x34,0x36,0x33,0x33,0x3,0xe1,0x21,0x1e,0x27,0x2,0x27,0x1e,0x44,0xd2,0x6b,0x97,0xa6,0x21,0x29,0x2,0x29,0x21,0x59,0x5d,0x56,0xa7,0x6b,0x21,0x29, + 0x2,0x3,0xa6,0x1e,0x23,0xfc,0xd2,0x23,0x1e,0x1e,0x23,0xa3,0x69,0x87,0xae,0x98,0x2,0x35,0x23,0x1e,0x1e,0x23,0xfd,0xda,0x5a,0x67,0x6d,0xbb,0x6e,0x1, + 0x51,0x23,0x1e,0x0,0xff,0xff,0x0,0xb8,0xff,0xea,0x4,0x2,0x5,0x87,0x0,0x22,0x0,0xe4,0x0,0x0,0x0,0x2,0x1,0x80,0xfe,0x0,0x0,0x0,0xff,0xff, + 0x0,0xb8,0xff,0xea,0x4,0x2,0x5,0x5c,0x0,0x22,0x0,0xe4,0x0,0x0,0x0,0x2,0x1,0x82,0xff,0x0,0x0,0x0,0xff,0xff,0x0,0xb8,0xff,0xea,0x4,0x2, + 0x5,0x74,0x0,0x22,0x0,0xe4,0x0,0x0,0x0,0x2,0x1,0x92,0x0,0x0,0x0,0x0,0xff,0xff,0x0,0xb8,0xff,0xea,0x4,0x2,0x5,0x53,0x0,0x22,0x0,0xe4, + 0x0,0x0,0x0,0x2,0x1,0x93,0x0,0x0,0x0,0x0,0xff,0xff,0x0,0xb8,0xff,0xea,0x4,0x2,0x5,0x87,0x0,0x22,0x0,0xe4,0x0,0x0,0x0,0x2,0x1,0x95, + 0xf8,0x0,0x0,0x0,0xff,0xff,0x0,0xb8,0xff,0xea,0x4,0x3b,0x5,0x76,0x0,0x22,0x0,0xe4,0x0,0x0,0x0,0x2,0x1,0x96,0xec,0x0,0x0,0x0,0xff,0xff, + 0x0,0xbb,0xff,0xea,0x4,0x5,0x4,0xe2,0x0,0x22,0x1,0x97,0x0,0x0,0x0,0x2,0x0,0xe4,0x3,0x0,0x0,0x0,0x0,0x1,0x0,0xb8,0xfe,0x28,0x4,0x39, + 0x3,0xa6,0x0,0x38,0x0,0x35,0x40,0x32,0x1d,0x1,0x4,0x3,0x1c,0x3,0x2,0x2,0x4,0x2,0x4a,0x0,0x0,0x0,0x1,0x0,0x1,0x60,0x6,0x5,0x2,0x3, + 0x3,0x11,0x4b,0x0,0x4,0x4,0x2,0x5b,0x0,0x2,0x2,0x17,0x2,0x4c,0x0,0x0,0x0,0x38,0x0,0x36,0x25,0x35,0x28,0x28,0x2b,0x7,0x7,0x19,0x2b,0x0, + 0x16,0x15,0x11,0x14,0x6,0x7,0x6,0x6,0x15,0x14,0x16,0x33,0x32,0x37,0x36,0x16,0x15,0x14,0x6,0x7,0x6,0x23,0x22,0x26,0x35,0x34,0x36,0x37,0x35,0x6, + 0x6,0x23,0x22,0x26,0x35,0x11,0x34,0x36,0x33,0x33,0x32,0x16,0x15,0x11,0x14,0x16,0x33,0x32,0x36,0x36,0x35,0x11,0x34,0x36,0x33,0x33,0x3,0xe1,0x21,0x7, + 0x9,0x5b,0x50,0x2a,0x23,0x37,0x3a,0x18,0x1c,0x11,0x11,0x4b,0x59,0x5d,0x6b,0x67,0x64,0x44,0xd2,0x6b,0x97,0xa6,0x21,0x29,0x2,0x29,0x21,0x59,0x5d,0x56, + 0xa7,0x6b,0x21,0x29,0x2,0x3,0xa6,0x1e,0x23,0xfc,0xd2,0x13,0x18,0x8,0x54,0x7d,0x38,0x24,0x27,0xa,0x4,0x23,0x27,0x14,0x18,0x6,0x1a,0x68,0x57,0x51, + 0x98,0x50,0xba,0x69,0x87,0xae,0x98,0x2,0x35,0x23,0x1e,0x1e,0x23,0xfd,0xda,0x5a,0x67,0x6d,0xbb,0x6e,0x1,0x51,0x23,0x1e,0xff,0xff,0x0,0xb8,0xff,0xea, + 0x4,0x2,0x6,0x15,0x0,0x22,0x0,0xe4,0x0,0x0,0x0,0x2,0x1,0x99,0xfd,0x0,0x0,0x0,0xff,0xff,0x0,0xb8,0xff,0xea,0x4,0x2,0x5,0x2d,0x0,0x22, + 0x0,0xe4,0x0,0x0,0x0,0x2,0x1,0x9a,0x0,0x0,0x0,0x0,0x0,0x1,0x0,0x7c,0xff,0xea,0x4,0x50,0x3,0xa8,0x0,0x1d,0x0,0x32,0xb5,0x17,0x1,0x0, + 0x1,0x1,0x4a,0x4b,0xb0,0xa,0x50,0x58,0x40,0xc,0x2,0x1,0x1,0x1,0x11,0x4b,0x0,0x0,0x0,0x17,0x0,0x4c,0x1b,0x40,0xc,0x2,0x1,0x1,0x1,0x19, + 0x4b,0x0,0x0,0x0,0x17,0x0,0x4c,0x59,0xb5,0x26,0x29,0x27,0x3,0x7,0x17,0x2b,0x0,0x16,0x15,0x14,0x7,0x1,0x6,0x6,0x23,0x22,0x26,0x27,0x1,0x26, + 0x35,0x34,0x36,0x37,0x36,0x33,0x32,0x16,0x17,0x1,0x1,0x36,0x36,0x33,0x32,0x17,0x4,0x36,0x1a,0x9,0xfe,0x95,0xf,0x36,0x31,0x31,0x36,0xf,0xfe,0x95, + 0x9,0x1a,0x1b,0x1b,0x13,0x11,0x18,0xa,0x1,0x54,0x1,0x54,0xa,0x18,0x11,0x13,0x1b,0x3,0x91,0x1b,0x12,0xf,0x14,0xfc,0xe7,0x21,0x1d,0x1d,0x21,0x3, + 0x19,0x13,0x10,0x12,0x1b,0xb,0xc,0x14,0x16,0xfd,0x11,0x2,0xef,0x16,0x14,0xc,0x0,0x0,0x0,0x1,0x0,0x3a,0xff,0xea,0x4,0x92,0x3,0xa6,0x0,0x2b, + 0x0,0x2d,0x40,0x2a,0x16,0x2,0x2,0x3,0x2,0x25,0x1f,0xc,0x3,0x0,0x3,0x2,0x4a,0x0,0x3,0x2,0x0,0x2,0x3,0x0,0x70,0x4,0x1,0x2,0x2,0x11, + 0x4b,0x1,0x1,0x0,0x0,0x17,0x0,0x4c,0x15,0x25,0x29,0x26,0x27,0x5,0x7,0x19,0x2b,0x0,0x16,0x15,0x14,0x7,0x3,0x6,0x6,0x23,0x22,0x26,0x27,0x3, + 0x3,0x6,0x6,0x23,0x22,0x26,0x27,0x3,0x26,0x35,0x34,0x36,0x37,0x36,0x33,0x32,0x16,0x17,0x13,0x13,0x36,0x33,0x32,0x17,0x13,0x13,0x36,0x36,0x33,0x32, + 0x17,0x4,0x76,0x1c,0x4,0xc7,0x9,0x23,0x26,0x2b,0x30,0xb,0xa9,0xa9,0xb,0x30,0x2b,0x26,0x23,0x9,0xc7,0x4,0x1c,0x20,0x11,0x10,0x17,0x19,0x6,0x9f, + 0xaf,0x10,0x3b,0x3b,0x10,0xaf,0x9f,0x6,0x19,0x17,0x10,0x11,0x3,0x9c,0x19,0x15,0xe,0xf,0xfc,0xda,0x24,0x1d,0x1f,0x22,0x2,0x0,0xfe,0x0,0x22,0x1f, + 0x1d,0x24,0x3,0x26,0xf,0xe,0x15,0x19,0x6,0x4,0x18,0x1b,0xfd,0x5f,0x2,0x15,0x2f,0x2f,0xfd,0xeb,0x2,0xa1,0x1b,0x18,0x4,0x0,0xff,0xff,0x0,0x3a, + 0xff,0xea,0x4,0x92,0x5,0x87,0x0,0x22,0x0,0xf0,0x0,0x0,0x0,0x2,0x1,0x80,0x14,0x0,0x0,0x0,0xff,0xff,0x0,0x3a,0xff,0xea,0x4,0x92,0x5,0x74, + 0x0,0x22,0x0,0xf0,0x0,0x0,0x0,0x2,0x1,0x92,0x0,0x0,0x0,0x0,0xff,0xff,0x0,0x3a,0xff,0xea,0x4,0x92,0x5,0x53,0x0,0x22,0x0,0xf0,0x0,0x0, + 0x0,0x2,0x1,0x93,0x0,0x0,0x0,0x0,0xff,0xff,0x0,0x3a,0xff,0xea,0x4,0x92,0x5,0x87,0x0,0x22,0x0,0xf0,0x0,0x0,0x0,0x2,0x1,0x95,0xec,0x0, + 0x0,0x0,0x0,0x1,0x0,0xa7,0xff,0xf1,0x4,0x31,0x3,0xab,0x0,0x27,0x0,0x22,0x40,0x1f,0x26,0x23,0x1c,0x12,0xf,0x8,0x6,0x0,0x2,0x1,0x4a,0x3, + 0x1,0x2,0x2,0x19,0x4b,0x1,0x1,0x0,0x0,0x17,0x0,0x4c,0x24,0x2c,0x24,0x24,0x4,0x7,0x18,0x2b,0x24,0x15,0x14,0x7,0x6,0x23,0x22,0x27,0x1,0x1, + 0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x1,0x1,0x26,0x35,0x34,0x37,0x36,0x33,0x32,0x17,0x1,0x1,0x36,0x33,0x32,0x17,0x16,0x15,0x14,0x7,0x1,0x1, + 0x4,0x31,0x2c,0x21,0x16,0x1d,0x1a,0xfe,0xd5,0xfe,0xd5,0x1a,0x1d,0x16,0x21,0x2c,0x11,0x1,0x48,0xfe,0xc5,0x12,0x2d,0x21,0x16,0x1d,0x1a,0x1,0x1e,0x1, + 0x1e,0x1a,0x1d,0x16,0x21,0x2d,0x12,0xfe,0xc5,0x1,0x48,0x45,0x13,0x1e,0x14,0xf,0x1e,0x1,0x60,0xfe,0xa0,0x1e,0xf,0x14,0x1e,0x13,0x14,0x1,0x7e,0x1, + 0x6c,0x15,0x12,0x1e,0x14,0xf,0x1e,0xfe,0xb4,0x1,0x4c,0x1e,0xf,0x14,0x1e,0x12,0x15,0xfe,0x94,0xfe,0x82,0x0,0x0,0x0,0x0,0x1,0x0,0x7b,0xfe,0x70, + 0x4,0x50,0x3,0xa8,0x0,0x21,0x0,0x33,0xb6,0x1b,0x10,0x2,0x0,0x1,0x1,0x4a,0x4b,0xb0,0xa,0x50,0x58,0x40,0xc,0x2,0x1,0x1,0x1,0x11,0x4b,0x0, + 0x0,0x0,0x13,0x0,0x4c,0x1b,0x40,0xc,0x2,0x1,0x1,0x1,0x19,0x4b,0x0,0x0,0x0,0x13,0x0,0x4c,0x59,0xb5,0x26,0x2d,0x27,0x3,0x7,0x17,0x2b,0x0, + 0x16,0x15,0x14,0x7,0x1,0x6,0x6,0x23,0x22,0x27,0x26,0x26,0x35,0x34,0x37,0x13,0x1,0x26,0x35,0x34,0x37,0x36,0x33,0x32,0x16,0x17,0x1,0x1,0x36,0x36, + 0x33,0x32,0x17,0x4,0x36,0x1a,0xa,0xfd,0xab,0xb,0x18,0x10,0x12,0x1b,0x1b,0x19,0xa,0xab,0xfe,0x73,0xa,0x36,0x1b,0x12,0x11,0x18,0xb,0x1,0x55,0x1, + 0x53,0xb,0x18,0x11,0x12,0x1b,0x3,0x91,0x1b,0x12,0x10,0x13,0xfb,0x59,0x16,0x14,0xc,0xb,0x1b,0x11,0x10,0x14,0x1,0x54,0x3,0x16,0x13,0x10,0x22,0x16, + 0xc,0x14,0x16,0xfd,0x55,0x2,0xab,0x16,0x14,0xc,0x0,0x0,0xff,0xff,0x0,0x7b,0xfe,0x70,0x4,0x50,0x5,0x87,0x0,0x22,0x0,0xf6,0x0,0x0,0x0,0x2, + 0x1,0x80,0x0,0x0,0x0,0x0,0xff,0xff,0x0,0x7b,0xfe,0x70,0x4,0x50,0x5,0x74,0x0,0x22,0x0,0xf6,0x0,0x0,0x0,0x2,0x1,0x92,0x10,0x0,0x0,0x0, + 0xff,0xff,0x0,0x7b,0xfe,0x70,0x4,0x50,0x5,0x53,0x0,0x22,0x0,0xf6,0x0,0x0,0x0,0x2,0x1,0x93,0x2,0x0,0x0,0x0,0xff,0xff,0x0,0x7b,0xfe,0x70, + 0x4,0x50,0x5,0x87,0x0,0x22,0x0,0xf6,0x0,0x0,0x0,0x2,0x1,0x95,0x0,0x0,0x0,0x0,0x0,0x1,0x0,0xb2,0x0,0x0,0x4,0x1a,0x3,0x9c,0x0,0x1d, + 0x0,0x25,0x40,0x22,0x0,0x1,0x1,0x2,0x59,0x0,0x2,0x2,0x11,0x4b,0x4,0x1,0x3,0x3,0x0,0x59,0x0,0x0,0x0,0xf,0x0,0x4c,0x0,0x0,0x0,0x1d, + 0x0,0x1c,0x44,0x25,0x44,0x5,0x7,0x17,0x2b,0x24,0x16,0x15,0x14,0x6,0x23,0x21,0x23,0x22,0x26,0x35,0x34,0x37,0x1,0x21,0x22,0x26,0x35,0x34,0x36,0x33, + 0x21,0x33,0x32,0x16,0x15,0x14,0x7,0x1,0x21,0x3,0xfd,0x1d,0x1d,0x22,0xfd,0x70,0x5a,0x22,0x1d,0xe,0x2,0x5c,0xfd,0xe9,0x22,0x1d,0x1d,0x22,0x2,0x74, + 0x58,0x22,0x1d,0xf,0xfd,0xa5,0x2,0x35,0x90,0x21,0x27,0x27,0x21,0x1f,0x25,0x1e,0xf,0x2,0x9b,0x21,0x27,0x27,0x21,0x1f,0x25,0x1d,0x11,0xfd,0x66,0x0, + 0xff,0xff,0x0,0xb2,0x0,0x0,0x4,0x1a,0x5,0x87,0x0,0x22,0x0,0xfb,0x0,0x0,0x0,0x2,0x1,0x80,0xf4,0x0,0x0,0x0,0xff,0xff,0x0,0xb2,0x0,0x0, + 0x4,0x1a,0x5,0x76,0x0,0x22,0x0,0xfb,0x0,0x0,0x0,0x2,0x1,0x83,0xa,0x0,0x0,0x0,0xff,0xff,0x0,0xb2,0x0,0x0,0x4,0x1a,0x5,0x53,0x0,0x22, + 0x0,0xfb,0x0,0x0,0x0,0x2,0x1,0x94,0xa,0x0,0x0,0x0,0xff,0xff,0x0,0xbb,0xff,0xf6,0x9,0x15,0x5,0x53,0x0,0x22,0x0,0x9f,0x0,0x0,0x0,0x23, + 0x0,0xa9,0x4,0xcc,0x0,0x0,0x0,0x3,0x1,0x94,0x4,0xd0,0x0,0x0,0xff,0xff,0x0,0xbb,0xff,0xf6,0x9,0xb,0x5,0x25,0x0,0x22,0x0,0x9f,0x0,0x0, + 0x0,0x3,0x0,0xb9,0x4,0xcc,0x0,0x0,0x0,0x2,0x0,0xfc,0x1,0xd1,0x3,0x9f,0x4,0xb9,0x0,0x26,0x0,0x30,0x0,0x8,0xb5,0x2a,0x27,0xc,0x0,0x2, + 0x30,0x2b,0x0,0x16,0x15,0x11,0x14,0x6,0x23,0x23,0x22,0x26,0x35,0x35,0x6,0x23,0x22,0x26,0x26,0x35,0x34,0x36,0x33,0x32,0x17,0x35,0x34,0x23,0x22,0x7, + 0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x2,0x15,0x14,0x16,0x33,0x32,0x37,0x35,0x26,0x23,0x3,0xf,0x90,0x1c,0x1f,0x2,0x1f,0x1c,0x85, + 0x98,0x4e,0x7a,0x46,0xaa,0x88,0x84,0x66,0x94,0x78,0x70,0xe,0xc,0x1c,0x14,0x10,0x17,0x3d,0x9f,0x4f,0xfb,0x47,0x3e,0x93,0x77,0x6d,0x72,0x4,0xb9,0x85, + 0x7a,0xfe,0x52,0x1a,0x18,0x18,0x1a,0x36,0x71,0x33,0x64,0x47,0x7a,0x76,0x1c,0x33,0x82,0x41,0x8,0x23,0x1c,0x13,0x18,0xf,0x26,0x2b,0xfe,0x68,0x6f,0x30, + 0x34,0x75,0x45,0x19,0x0,0x0,0x0,0x2,0x0,0xeb,0x1,0xd1,0x3,0xe1,0x4,0xb9,0x0,0xf,0x0,0x1f,0x0,0x8,0xb5,0x16,0x10,0x6,0x0,0x2,0x30,0x2b, + 0x0,0x16,0x16,0x15,0x14,0x6,0x6,0x23,0x22,0x26,0x26,0x35,0x34,0x36,0x36,0x33,0xe,0x2,0x15,0x14,0x16,0x16,0x33,0x32,0x36,0x36,0x35,0x34,0x26,0x26, + 0x23,0x2,0xd1,0xad,0x63,0x63,0xad,0x6b,0x6b,0xad,0x63,0x63,0xad,0x6b,0x46,0x6d,0x3c,0x3c,0x6d,0x46,0x47,0x6c,0x3c,0x3c,0x6c,0x47,0x4,0xb9,0x60,0xaa, + 0x6a,0x6b,0xa9,0x60,0x60,0xa9,0x6b,0x6a,0xaa,0x60,0x84,0x3e,0x6d,0x45,0x45,0x6e,0x3d,0x3d,0x6d,0x46,0x45,0x6d,0x3e,0x0,0x0,0x2,0x0,0x93,0x0,0x0, + 0x4,0x38,0x4,0xb9,0x0,0x12,0x0,0x16,0x0,0x8,0xb5,0x14,0x13,0x7,0x0,0x2,0x30,0x2b,0x0,0x16,0x17,0x1,0x16,0x15,0x14,0x6,0x23,0x21,0x22,0x26, + 0x35,0x34,0x37,0x1,0x36,0x36,0x33,0x7,0x1,0x21,0x1,0x2,0x91,0x31,0xc,0x1,0x65,0x5,0x1d,0x22,0xfc,0xd9,0x22,0x1d,0x6,0x1,0x69,0xc,0x32,0x29, + 0x2,0xfe,0xda,0x2,0x4a,0xfe,0xde,0x4,0xb9,0x1f,0x22,0xfb,0xf2,0xf,0x17,0x25,0x1f,0x1f,0x25,0x14,0x12,0x4,0xe,0x22,0x1f,0xbb,0xfc,0x8a,0x3,0x76, + 0x0,0x1,0x0,0x51,0x0,0x0,0x4,0x7b,0x4,0xb9,0x0,0x35,0x0,0x6,0xb3,0xc,0x0,0x1,0x30,0x2b,0x0,0x16,0x12,0x15,0x14,0x6,0x7,0x33,0x32,0x16, + 0x15,0x14,0x6,0x23,0x21,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x35,0x34,0x26,0x26,0x23,0x22,0x6,0x6,0x15,0x14,0x16,0x17,0x16,0x15,0x14,0x6,0x23,0x21, + 0x22,0x26,0x35,0x34,0x36,0x33,0x33,0x26,0x26,0x35,0x34,0x12,0x36,0x33,0x3,0x2,0xef,0x83,0x82,0x67,0xb1,0x22,0x1d,0x1d,0x22,0xfe,0xb2,0x22,0x1d,0x11, + 0x87,0x8f,0x5b,0xa7,0x6e,0x6e,0xa7,0x5b,0x8f,0x87,0x11,0x1d,0x22,0xfe,0xb2,0x22,0x1d,0x1d,0x22,0xb1,0x67,0x82,0x83,0xef,0x9c,0x4,0xb9,0x92,0xfe,0xf6, + 0xae,0x94,0xfb,0x50,0x21,0x27,0x27,0x21,0x24,0x2a,0x34,0xc,0x56,0xf1,0x9a,0x85,0xc6,0x6b,0x6b,0xc6,0x85,0x9a,0xf1,0x56,0xc,0x34,0x2a,0x24,0x21,0x27, + 0x27,0x21,0x50,0xfb,0x94,0xae,0x1,0xa,0x92,0x0,0x0,0x1,0x0,0xda,0xfe,0x72,0x3,0xfc,0x3,0xa6,0x0,0x2d,0x0,0x6,0xb3,0x13,0x0,0x1,0x30,0x2b, + 0x0,0x16,0x15,0x11,0x14,0x6,0x23,0x23,0x22,0x26,0x35,0x35,0x6,0x6,0x23,0x22,0x27,0x11,0x14,0x6,0x23,0x23,0x22,0x26,0x35,0x11,0x34,0x36,0x33,0x33, + 0x32,0x16,0x15,0x11,0x14,0x16,0x33,0x32,0x36,0x36,0x35,0x11,0x34,0x36,0x33,0x33,0x3,0xdb,0x21,0x1e,0x27,0x2,0x27,0x1e,0x3f,0xb5,0x65,0x64,0x43,0x21, + 0x29,0x2,0x29,0x21,0x21,0x29,0x2,0x29,0x21,0x59,0x5d,0x4c,0x95,0x5f,0x21,0x29,0x2,0x3,0xa6,0x1e,0x23,0xfc,0xd2,0x23,0x1e,0x1e,0x23,0x8f,0x62,0x7a, + 0x2f,0xfe,0x9a,0x23,0x1e,0x1e,0x23,0x4,0xb2,0x23,0x1e,0x1e,0x23,0xfd,0xda,0x5a,0x67,0x66,0xad,0x65,0x1,0x6f,0x23,0x1e,0x0,0x0,0x0,0x1,0x0,0x62, + 0xff,0xf6,0x4,0x6a,0x3,0x9c,0x0,0x21,0x0,0x6,0xb3,0x9,0x0,0x1,0x30,0x2b,0x0,0x16,0x15,0x14,0x6,0x23,0x23,0x11,0x14,0x6,0x23,0x23,0x22,0x26, + 0x35,0x11,0x21,0x11,0x14,0x6,0x23,0x23,0x22,0x26,0x35,0x11,0x23,0x22,0x26,0x35,0x34,0x36,0x33,0x21,0x4,0x4d,0x1d,0x1d,0x22,0x57,0x21,0x29,0x2,0x29, + 0x21,0xfe,0x50,0x21,0x29,0x2,0x29,0x21,0x57,0x22,0x1d,0x1d,0x22,0x3,0x8a,0x3,0x9c,0x21,0x27,0x27,0x21,0xfd,0x2b,0x23,0x1e,0x1e,0x23,0x2,0xd5,0xfd, + 0x2b,0x23,0x1e,0x1e,0x23,0x2,0xd5,0x21,0x27,0x27,0x21,0x0,0x0,0x0,0x0,0x3,0x0,0xbe,0xff,0xea,0x4,0xe,0x4,0xb9,0x0,0xf,0x0,0x19,0x0,0x23, + 0x0,0x3a,0x40,0x37,0x20,0x1f,0x16,0x15,0x4,0x3,0x2,0x1,0x4a,0x5,0x1,0x2,0x2,0x1,0x5b,0x4,0x1,0x1,0x1,0x16,0x4b,0x6,0x1,0x3,0x3,0x0, + 0x5b,0x0,0x0,0x0,0x17,0x0,0x4c,0x1a,0x1a,0x10,0x10,0x0,0x0,0x1a,0x23,0x1a,0x22,0x10,0x19,0x10,0x18,0x0,0xf,0x0,0xe,0x26,0x7,0x7,0x15,0x2b, + 0x0,0x16,0x12,0x15,0x14,0x2,0x6,0x23,0x22,0x26,0x2,0x35,0x34,0x12,0x36,0x33,0xe,0x2,0x15,0x14,0x17,0x1,0x26,0x26,0x23,0x12,0x36,0x36,0x35,0x34, + 0x27,0x1,0x16,0x16,0x33,0x2,0xe6,0xc0,0x68,0x68,0xc0,0x80,0x80,0xc0,0x68,0x68,0xc0,0x80,0x4f,0x7a,0x45,0x10,0x1,0xc9,0x25,0x68,0x3e,0x4f,0x7a,0x45, + 0x10,0xfe,0x38,0x24,0x68,0x3e,0x4,0xb9,0x99,0xfe,0xe9,0xb8,0xb8,0xfe,0xea,0x99,0x99,0x1,0x16,0xb8,0xb8,0x1,0x17,0x99,0x94,0x6f,0xd3,0x92,0x5f,0x55, + 0x1,0xff,0x43,0x46,0xfc,0x59,0x6e,0xd3,0x92,0x5f,0x55,0xfe,0x1,0x43,0x45,0x0,0x0,0x0,0x0,0x1,0x0,0xdc,0x0,0x0,0x4,0xd,0x4,0xbc,0x0,0x1f, + 0x0,0x30,0x40,0x2d,0xe,0x1,0x2,0x3,0x1,0x4a,0x0,0x2,0x3,0x1,0x3,0x2,0x1,0x70,0x0,0x3,0x3,0x16,0x4b,0x5,0x4,0x2,0x1,0x1,0x0,0x5a, + 0x0,0x0,0x0,0xf,0x0,0x4c,0x0,0x0,0x0,0x1f,0x0,0x1e,0x27,0x23,0x24,0x34,0x6,0x7,0x18,0x2b,0x24,0x16,0x15,0x14,0x6,0x23,0x21,0x22,0x26,0x35, + 0x34,0x36,0x33,0x21,0x11,0x7,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x25,0x36,0x33,0x32,0x16,0x15,0x11,0x21,0x3,0xf0,0x1d,0x1d,0x22,0xfd,0x5e,0x22, + 0x1d,0x1d,0x22,0x1,0xd,0xf9,0x11,0xe,0x20,0x17,0xe,0x1f,0x1,0x79,0x14,0x14,0x16,0x1d,0x0,0xff,0x90,0x21,0x27,0x27,0x21,0x21,0x27,0x27,0x21,0x3, + 0x52,0x9b,0xa,0x29,0x18,0x16,0x22,0x12,0xe8,0xc,0x1d,0x1a,0xfc,0xb,0x0,0x0,0x0,0x1,0x0,0xc5,0x0,0x0,0x3,0xf9,0x4,0xb9,0x0,0x2a,0x0,0x2e, + 0x40,0x2b,0x0,0x2,0x1,0x4,0x1,0x2,0x4,0x70,0x0,0x1,0x1,0x3,0x5b,0x0,0x3,0x3,0x16,0x4b,0x5,0x1,0x4,0x4,0x0,0x59,0x0,0x0,0x0,0xf, + 0x0,0x4c,0x0,0x0,0x0,0x2a,0x0,0x29,0x27,0x23,0x2a,0x44,0x6,0x7,0x18,0x2b,0x24,0x16,0x15,0x14,0x6,0x23,0x21,0x23,0x22,0x26,0x35,0x34,0x37,0x1, + 0x36,0x36,0x35,0x34,0x26,0x23,0x22,0x6,0x7,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x32,0x16,0x16,0x15,0x14,0x6,0x7,0x1,0x21,0x3, + 0xdc,0x1d,0x1d,0x22,0xfd,0xa3,0x59,0x22,0x1d,0xf,0x1,0xb8,0x5c,0x4b,0x77,0x6d,0x42,0x8e,0x3f,0x10,0x12,0x21,0x19,0x11,0x1e,0x4c,0xb8,0x59,0x74,0xad, + 0x5e,0x63,0x69,0xfe,0xb8,0x2,0x1,0x90,0x21,0x27,0x27,0x21,0x1f,0x25,0x26,0x10,0x1,0xbe,0x5d,0x7e,0x48,0x5d,0x6d,0x32,0x2c,0xc,0x29,0x1d,0x14,0x1c, + 0x16,0x36,0x3c,0x53,0x9e,0x6d,0x66,0xad,0x6b,0xfe,0xb3,0x0,0x0,0x1,0x0,0xb5,0xff,0xea,0x3,0xe6,0x4,0xb9,0x0,0x3b,0x0,0x47,0x40,0x44,0x6,0x1, + 0x3,0x4,0x1,0x4a,0x0,0x6,0x5,0x4,0x5,0x6,0x4,0x70,0x0,0x1,0x3,0x2,0x3,0x1,0x2,0x70,0x0,0x4,0x0,0x3,0x1,0x4,0x3,0x63,0x0,0x5, + 0x5,0x7,0x5b,0x8,0x1,0x7,0x7,0x16,0x4b,0x0,0x2,0x2,0x0,0x5b,0x0,0x0,0x0,0x17,0x0,0x4c,0x0,0x0,0x0,0x3b,0x0,0x3a,0x22,0x24,0x34,0x34, + 0x24,0x17,0x2c,0x9,0x7,0x1b,0x2b,0x0,0x16,0x16,0x15,0x14,0x6,0x7,0x16,0x16,0x15,0x14,0x6,0x6,0x23,0x22,0x26,0x27,0x26,0x35,0x34,0x37,0x36,0x33, + 0x32,0x17,0x16,0x16,0x33,0x32,0x36,0x35,0x34,0x26,0x23,0x23,0x22,0x26,0x35,0x34,0x36,0x33,0x33,0x32,0x36,0x35,0x34,0x26,0x23,0x22,0x7,0x6,0x23,0x22, + 0x27,0x26,0x35,0x34,0x37,0x36,0x33,0x2,0xb7,0xac,0x5e,0x50,0x49,0x5a,0x64,0x61,0xb8,0x81,0x62,0xc8,0x4f,0x1e,0x11,0x1a,0x20,0x10,0x12,0x42,0x9a,0x4e, + 0x7c,0x84,0x85,0x76,0x49,0x22,0x1d,0x1d,0x22,0x37,0x72,0x76,0x76,0x6c,0x84,0x84,0x12,0xe,0x20,0x18,0xe,0x20,0xa3,0xaa,0x4,0xb9,0x51,0x95,0x64,0x56, + 0x7d,0x25,0x25,0x93,0x66,0x6d,0xa6,0x5c,0x44,0x3a,0x16,0x1c,0x14,0x1d,0x2a,0xd,0x31,0x39,0x76,0x65,0x67,0x6b,0x1f,0x25,0x25,0x1f,0x60,0x5c,0x55,0x61, + 0x50,0xa,0x2b,0x1b,0x15,0x20,0x13,0x60,0x0,0x2,0x0,0x91,0xff,0xf6,0x4,0x27,0x4,0xb9,0x0,0x1d,0x0,0x20,0x0,0x2e,0x40,0x2b,0x20,0x1,0x4,0x3, + 0x1,0x4a,0x5,0x6,0x2,0x4,0x2,0x1,0x0,0x1,0x4,0x0,0x61,0x0,0x3,0x3,0x16,0x4b,0x0,0x1,0x1,0xf,0x1,0x4c,0x0,0x0,0x1f,0x1e,0x0,0x1d, + 0x0,0x1c,0x27,0x23,0x33,0x24,0x7,0x7,0x18,0x2b,0x0,0x16,0x15,0x14,0x6,0x23,0x23,0x15,0x14,0x6,0x23,0x23,0x22,0x26,0x35,0x35,0x21,0x22,0x26,0x35, + 0x34,0x37,0x1,0x36,0x36,0x33,0x32,0x15,0x11,0x33,0x21,0x21,0x13,0x4,0xa,0x1d,0x1d,0x22,0xa2,0x21,0x29,0x2,0x29,0x21,0xfe,0x22,0x22,0x1f,0xc,0x1, + 0xe4,0x15,0x33,0x26,0x57,0xa2,0xfd,0x6e,0x1,0x5a,0x8,0x1,0xbe,0x21,0x27,0x27,0x21,0xf7,0x23,0x1e,0x1e,0x23,0xf7,0x24,0x26,0x20,0x11,0x2,0xd3,0x20, + 0x1d,0x4e,0xfd,0x53,0x2,0x22,0x0,0x1,0x0,0xbc,0xff,0xea,0x3,0xf0,0x4,0xa3,0x0,0x35,0x0,0x46,0x40,0x43,0x33,0x1,0x3,0x7,0x1,0x4a,0x0,0x4, + 0x3,0x1,0x3,0x4,0x1,0x70,0x0,0x1,0x2,0x3,0x1,0x2,0x6e,0x8,0x1,0x7,0x0,0x3,0x4,0x7,0x3,0x63,0x0,0x6,0x6,0x5,0x59,0x0,0x5,0x5, + 0xe,0x4b,0x0,0x2,0x2,0x0,0x5b,0x0,0x0,0x0,0x17,0x0,0x4c,0x0,0x0,0x0,0x35,0x0,0x34,0x24,0x45,0x23,0x26,0x24,0x17,0x26,0x9,0x7,0x1b,0x2b, + 0x0,0x16,0x16,0x15,0x14,0x6,0x6,0x23,0x22,0x26,0x27,0x26,0x35,0x34,0x37,0x36,0x33,0x32,0x17,0x16,0x16,0x33,0x32,0x36,0x36,0x35,0x34,0x26,0x26,0x23, + 0x22,0x6,0x7,0x6,0x23,0x22,0x26,0x37,0x13,0x36,0x36,0x33,0x33,0x21,0x32,0x16,0x15,0x14,0x6,0x23,0x21,0x3,0x36,0x33,0x2,0xe5,0xaf,0x5c,0x67,0xbf, + 0x7e,0x60,0xc2,0x4f,0x1f,0x10,0x1a,0x20,0x10,0x12,0x41,0x98,0x4b,0x51,0x79,0x40,0x3c,0x70,0x4a,0x44,0x6c,0x33,0x10,0x22,0x2a,0x23,0x2,0x14,0x2,0x20, + 0x29,0x86,0x1,0x90,0x22,0x1d,0x1d,0x22,0xfe,0x2e,0xd,0x64,0x73,0x3,0x12,0x66,0xb5,0x75,0x75,0xba,0x69,0x40,0x37,0x17,0x1d,0x16,0x1a,0x2a,0xd,0x2e, + 0x36,0x41,0x75,0x4e,0x4a,0x72,0x40,0x23,0x24,0xa,0x1e,0x23,0x1,0xf4,0x23,0x1e,0x21,0x27,0x27,0x21,0xfe,0xc7,0x38,0x0,0x0,0x2,0x0,0xcb,0xff,0xea, + 0x4,0xa,0x4,0xba,0x0,0x1c,0x0,0x2c,0x0,0x3c,0x40,0x39,0x8,0x1,0x5,0x1,0x1,0x4a,0x0,0x1,0x7,0x1,0x5,0x4,0x1,0x5,0x63,0x0,0x0,0x0, + 0x3,0x5b,0x6,0x1,0x3,0x3,0x16,0x4b,0x0,0x4,0x4,0x2,0x5b,0x0,0x2,0x2,0x17,0x2,0x4c,0x1d,0x1d,0x0,0x0,0x1d,0x2c,0x1d,0x2b,0x25,0x23,0x0, + 0x1c,0x0,0x1b,0x26,0x24,0x24,0x8,0x7,0x17,0x2b,0x0,0x16,0x15,0x14,0x6,0x7,0x4,0x4,0x7,0x36,0x36,0x33,0x32,0x16,0x16,0x15,0x14,0x6,0x6,0x23, + 0x22,0x26,0x27,0x26,0x35,0x34,0x12,0x24,0x37,0x0,0x6,0x6,0x15,0x14,0x16,0x16,0x33,0x32,0x36,0x36,0x35,0x34,0x26,0x26,0x23,0x3,0xb0,0x1f,0x13,0x12, + 0xfe,0xf6,0xfe,0xe2,0x1c,0x34,0x98,0x56,0x6e,0xb0,0x64,0x65,0xba,0x79,0x6d,0xb6,0x38,0x4c,0x92,0x1,0x40,0xf8,0xfe,0x94,0x74,0x41,0x3f,0x73,0x4c,0x4c, + 0x73,0x3f,0x3e,0x72,0x4e,0x4,0xba,0x26,0x33,0x1f,0x1b,0x1,0xe,0xf7,0xd4,0x44,0x4a,0x5e,0xac,0x70,0x6c,0xab,0x60,0x64,0x5f,0x83,0xd0,0xad,0x1,0x34, + 0xcd,0xb,0xfd,0x8e,0x37,0x61,0x3f,0x46,0x6e,0x3e,0x38,0x67,0x44,0x45,0x68,0x39,0x0,0x0,0x0,0x1,0x0,0xc7,0xff,0xea,0x4,0x5,0x4,0xa3,0x0,0x17, + 0x0,0x25,0x40,0x22,0xb,0x1,0x0,0x1,0x1,0x4a,0x0,0x1,0x1,0x2,0x59,0x3,0x1,0x2,0x2,0xe,0x4b,0x0,0x0,0x0,0x17,0x0,0x4c,0x0,0x0,0x0, + 0x17,0x0,0x14,0x25,0x27,0x4,0x7,0x16,0x2b,0x0,0x16,0x15,0x14,0x7,0x1,0x6,0x6,0x23,0x22,0x26,0x35,0x34,0x37,0x1,0x21,0x22,0x26,0x35,0x34,0x36, + 0x33,0x21,0x33,0x3,0xe8,0x1d,0x6,0xfe,0x56,0x9,0x27,0x1b,0x25,0x2c,0x5,0x1,0x94,0xfd,0xb4,0x22,0x1d,0x1d,0x22,0x2,0x88,0x38,0x4,0xa3,0x1f,0x25, + 0x18,0x10,0xfb,0xe1,0x17,0x17,0x1d,0x19,0x9,0xf,0x3,0xdb,0x21,0x27,0x27,0x21,0x0,0x0,0x0,0x3,0x0,0xd2,0xff,0xea,0x3,0xfa,0x4,0xb9,0x0,0x1b, + 0x0,0x2b,0x0,0x3b,0x0,0x44,0x40,0x41,0x14,0x6,0x2,0x5,0x2,0x1,0x4a,0x0,0x2,0x8,0x1,0x5,0x4,0x2,0x5,0x63,0x7,0x1,0x3,0x3,0x1,0x5b, + 0x6,0x1,0x1,0x1,0x16,0x4b,0x0,0x4,0x4,0x0,0x5b,0x0,0x0,0x0,0x17,0x0,0x4c,0x2c,0x2c,0x1c,0x1c,0x0,0x0,0x2c,0x3b,0x2c,0x3a,0x34,0x32,0x1c, + 0x2b,0x1c,0x2a,0x24,0x22,0x0,0x1b,0x0,0x1a,0x2c,0x9,0x7,0x15,0x2b,0x0,0x16,0x16,0x15,0x14,0x6,0x7,0x16,0x16,0x15,0x14,0x6,0x6,0x23,0x22,0x26, + 0x26,0x35,0x34,0x36,0x37,0x26,0x26,0x35,0x34,0x36,0x36,0x33,0xe,0x2,0x15,0x14,0x16,0x16,0x33,0x32,0x36,0x36,0x35,0x34,0x26,0x26,0x23,0x2,0x6,0x6, + 0x15,0x14,0x16,0x16,0x33,0x32,0x36,0x36,0x35,0x34,0x26,0x26,0x23,0x2,0xce,0xa8,0x5f,0x5a,0x4a,0x5c,0x6d,0x68,0xb9,0x73,0x73,0xb9,0x68,0x6d,0x5b,0x4a, + 0x59,0x5f,0xa8,0x68,0x3e,0x61,0x38,0x38,0x63,0x3c,0x3c,0x63,0x38,0x38,0x61,0x3e,0x49,0x71,0x40,0x40,0x71,0x49,0x49,0x71,0x40,0x40,0x71,0x49,0x4,0xb9, + 0x57,0x97,0x5c,0x51,0x80,0x28,0x29,0x96,0x63,0x67,0xa5,0x5e,0x5e,0xa5,0x67,0x63,0x96,0x29,0x28,0x80,0x51,0x5c,0x97,0x57,0x94,0x2d,0x54,0x38,0x33,0x54, + 0x30,0x30,0x54,0x33,0x38,0x54,0x2d,0xfe,0x5,0x36,0x61,0x3e,0x3e,0x62,0x37,0x37,0x62,0x3e,0x3e,0x61,0x36,0x0,0x0,0x2,0x0,0xc2,0xff,0xe9,0x4,0x1, + 0x4,0xb9,0x0,0x1c,0x0,0x2c,0x0,0x3c,0x40,0x39,0x11,0x1,0x2,0x4,0x1,0x4a,0x0,0x4,0x0,0x2,0x1,0x4,0x2,0x63,0x7,0x1,0x5,0x5,0x3,0x5b, + 0x6,0x1,0x3,0x3,0x16,0x4b,0x0,0x1,0x1,0x0,0x5b,0x0,0x0,0x0,0x17,0x0,0x4c,0x1d,0x1d,0x0,0x0,0x1d,0x2c,0x1d,0x2b,0x25,0x23,0x0,0x1c,0x0, + 0x1b,0x24,0x24,0x27,0x8,0x7,0x17,0x2b,0x0,0x16,0x17,0x16,0x15,0x14,0x2,0x4,0x7,0x6,0x26,0x35,0x34,0x36,0x37,0x24,0x24,0x37,0x6,0x6,0x23,0x22, + 0x26,0x26,0x35,0x34,0x36,0x36,0x33,0xe,0x2,0x15,0x14,0x16,0x16,0x33,0x32,0x36,0x36,0x35,0x34,0x26,0x26,0x23,0x2,0xc7,0xb6,0x38,0x4c,0x92,0xfe,0xc0, + 0xf8,0x1b,0x1f,0x13,0x12,0x1,0xa,0x1,0x1e,0x1c,0x34,0x98,0x56,0x6e,0xb0,0x64,0x65,0xba,0x79,0x4c,0x73,0x3f,0x3e,0x72,0x4e,0x49,0x74,0x41,0x3f,0x73, + 0x4c,0x4,0xb9,0x64,0x5f,0x83,0xd0,0xad,0xfe,0xcc,0xcd,0xb,0x1,0x26,0x33,0x1f,0x1b,0x1,0xe,0xf7,0xd4,0x44,0x4a,0x5e,0xac,0x70,0x6c,0xab,0x60,0x94, + 0x38,0x67,0x44,0x45,0x68,0x39,0x37,0x61,0x3f,0x46,0x6e,0x3e,0x0,0x1,0x0,0x6f,0x0,0xc4,0x4,0x5d,0x3,0x56,0x0,0x11,0x0,0x17,0x40,0x14,0x5,0x1, + 0x1,0x0,0x1,0x4a,0x0,0x0,0x1,0x0,0x72,0x0,0x1,0x1,0x69,0x18,0x11,0x2,0x7,0x16,0x2b,0x0,0x33,0x32,0x17,0x16,0x15,0x14,0x7,0x1,0x6,0x23, + 0x22,0x27,0x26,0x35,0x34,0x37,0x1,0x4,0x1a,0x9,0x17,0x15,0xe,0xe,0xfc,0x6a,0x7,0x9,0x17,0x15,0xe,0xe,0x3,0x96,0x3,0x56,0x1c,0x12,0x14,0x13, + 0x8,0xfd,0xcf,0x4,0x1c,0x14,0x12,0x12,0x9,0x2,0x31,0x0,0x0,0x0,0x0,0x3,0x0,0x1,0xff,0x28,0x4,0xba,0x5,0x71,0x0,0x1e,0x0,0x30,0x0,0x58, + 0x0,0x5c,0xb1,0x6,0x64,0x44,0x40,0x51,0x24,0x1,0x1,0x4,0x1,0x4a,0x1e,0xb,0x2,0x0,0x48,0x0,0x0,0x4,0x0,0x72,0x0,0x4,0x1,0x4,0x72,0x0, + 0x5,0x8,0x6,0x8,0x5,0x6,0x70,0x3,0x1,0x1,0x0,0x2,0x9,0x1,0x2,0x61,0xa,0x1,0x9,0x0,0x8,0x5,0x9,0x8,0x63,0x0,0x6,0x7,0x7,0x6, + 0x55,0x0,0x6,0x6,0x7,0x5a,0x0,0x7,0x6,0x7,0x4e,0x31,0x31,0x31,0x58,0x31,0x57,0x29,0x34,0x2d,0x18,0x13,0x23,0x34,0x2c,0x12,0xb,0x7,0x1d,0x2b, + 0xb1,0x6,0x0,0x44,0x13,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x37,0x36,0x33,0x32,0x16,0x15,0x11,0x33,0x32,0x16,0x15,0x14,0x6,0x23,0x21,0x22,0x35, + 0x34,0x36,0x33,0x33,0x11,0x0,0x33,0x32,0x17,0x16,0x15,0x14,0x7,0x1,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x1,0x12,0x16,0x15,0x14,0x6,0x7,0x7, + 0x21,0x32,0x16,0x15,0x14,0x6,0x23,0x21,0x22,0x35,0x34,0x37,0x25,0x36,0x36,0x35,0x34,0x26,0x23,0x22,0x6,0x7,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37, + 0x36,0x36,0x33,0x44,0xe,0x5,0x1a,0xe,0x5,0x1e,0xed,0xa,0xa,0x10,0x13,0xa2,0x15,0x14,0x14,0x15,0xfe,0x43,0x2a,0x15,0x15,0xaa,0x3,0x45,0x9,0x17, + 0x15,0xe,0xe,0xfc,0x6a,0x7,0x9,0x17,0x15,0xe,0xe,0x3,0x96,0xb,0x7f,0x42,0x46,0xc7,0x1,0x43,0x15,0x14,0x14,0x15,0xfe,0x34,0x28,0x1a,0x1,0x16, + 0x2f,0x2c,0x42,0x3e,0x2b,0x62,0x2c,0x7,0x8,0x19,0x10,0x7,0x12,0x34,0x7e,0x3b,0x4,0xc1,0x4,0x26,0x11,0xa,0x1a,0xa,0x4c,0x3,0x11,0x11,0xfd,0xc5, + 0x18,0x1c,0x1b,0x19,0x34,0x1c,0x18,0x1,0xdc,0xfe,0x8e,0x1c,0x12,0x14,0x13,0x8,0xfd,0xcf,0x4,0x1c,0x14,0x12,0x12,0x9,0x2,0x31,0xfe,0x74,0x74,0x62, + 0x40,0x69,0x3a,0xa5,0x18,0x1c,0x1b,0x19,0x3c,0x21,0x15,0xea,0x28,0x43,0x29,0x30,0x3a,0x1c,0x17,0x4,0x25,0x13,0xe,0x18,0xa,0x1c,0x1f,0x0,0x0,0x4, + 0x0,0x1,0xff,0x22,0x4,0xd1,0x5,0x71,0x0,0x1e,0x0,0x30,0x0,0x4f,0x0,0x52,0x0,0x6f,0xb1,0x6,0x64,0x44,0x40,0x64,0x20,0x1,0x0,0x1,0x51,0x1, + 0x4,0x9,0x43,0x1,0x6,0xa,0x3,0x4a,0x16,0xa,0x2,0x2,0x48,0x0,0x2,0x5,0x2,0x72,0x0,0x5,0x1,0x5,0x72,0x0,0x4,0x9,0xa,0x9,0x4,0xa, + 0x70,0x3,0x1,0x1,0x0,0x0,0x9,0x1,0x0,0x61,0x0,0x9,0x4,0x7,0x9,0x57,0xd,0xb,0xc,0x3,0xa,0x8,0x1,0x6,0x7,0xa,0x6,0x63,0x0,0x9, + 0x9,0x7,0x5c,0x0,0x7,0x9,0x7,0x50,0x50,0x50,0x31,0x31,0x50,0x52,0x50,0x52,0x31,0x4f,0x31,0x4e,0x4b,0x49,0x23,0x23,0x26,0x18,0x19,0x2c,0x14,0x23, + 0x31,0xe,0x7,0x1d,0x2b,0xb1,0x6,0x0,0x44,0x0,0x6,0x23,0x21,0x22,0x35,0x34,0x36,0x33,0x33,0x11,0x7,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x37, + 0x36,0x33,0x32,0x16,0x15,0x11,0x33,0x32,0x16,0x15,0x24,0x15,0x14,0x7,0x1,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x1,0x36,0x33,0x32,0x17,0x12,0x16, + 0x15,0x14,0x6,0x23,0x23,0x15,0x14,0x6,0x23,0x22,0x26,0x35,0x35,0x21,0x22,0x26,0x35,0x34,0x36,0x37,0x1,0x36,0x36,0x33,0x32,0x16,0x15,0x11,0x33,0x23, + 0x13,0x3,0x2,0x11,0x14,0x15,0xfe,0x43,0x2a,0x15,0x15,0xaa,0x91,0xe,0x5,0x1a,0xe,0x5,0x1e,0xed,0xa,0xa,0x10,0x13,0xa2,0x15,0x14,0x2,0x4c,0xe, + 0xfc,0x6a,0x7,0x9,0x17,0x15,0xe,0xe,0x3,0x96,0x7,0x9,0x17,0x15,0x6e,0x14,0x14,0x14,0x5b,0x1a,0x1e,0x1e,0x1b,0xfe,0xc6,0x14,0x19,0x6,0x8,0x1, + 0x3d,0xf,0x1f,0x15,0x1d,0x2d,0x5b,0xcc,0x5,0xdd,0x2,0xc5,0x19,0x34,0x1c,0x18,0x1,0xdc,0x2f,0x4,0x26,0x11,0xa,0x1a,0xa,0x4c,0x3,0x11,0x11,0xfd, + 0xc5,0x18,0x1c,0x3e,0x14,0x13,0x8,0xfd,0xcf,0x4,0x1c,0x14,0x12,0x12,0x9,0x2,0x31,0x4,0x1c,0xfd,0x1c,0x18,0x1c,0x1c,0x19,0x99,0x14,0x14,0x14,0x14, + 0x99,0x21,0x20,0xf,0x13,0xb,0x1,0x79,0x13,0x12,0x1b,0x14,0xfe,0x8c,0x1,0xa,0xfe,0xf6,0x0,0x0,0x0,0x4,0x0,0x15,0xff,0x22,0x4,0xd1,0x5,0x72, + 0x0,0x39,0x0,0x4b,0x0,0x6a,0x0,0x6d,0x0,0xf0,0xb1,0x6,0x64,0x44,0x40,0x16,0x36,0x1,0x3,0x4,0x3b,0x1,0x2,0x1,0x7,0x1,0x0,0x2,0x6c,0x1, + 0x8,0xd,0x5e,0x1,0xa,0xe,0x5,0x4a,0x4b,0xb0,0x13,0x50,0x58,0x40,0x4d,0x0,0x6,0x5,0x4,0x5,0x6,0x4,0x70,0x9,0x1,0x1,0x3,0x2,0x3,0x1, + 0x2,0x70,0x0,0x8,0xd,0xe,0xd,0x8,0xe,0x70,0x0,0x7,0x0,0x5,0x6,0x7,0x5,0x63,0x0,0x4,0x0,0x3,0x1,0x4,0x3,0x63,0x0,0x2,0x0,0x0, + 0xd,0x2,0x0,0x63,0x0,0xd,0x8,0xb,0xd,0x57,0x11,0xf,0x10,0x3,0xe,0xc,0x1,0xa,0xb,0xe,0xa,0x63,0x0,0xd,0xd,0xb,0x5c,0x0,0xb,0xd, + 0xb,0x50,0x1b,0x40,0x53,0x0,0x6,0x5,0x4,0x5,0x6,0x4,0x70,0x0,0x9,0x3,0x1,0x3,0x9,0x1,0x70,0x0,0x1,0x2,0x3,0x1,0x2,0x6e,0x0,0x8, + 0xd,0xe,0xd,0x8,0xe,0x70,0x0,0x7,0x0,0x5,0x6,0x7,0x5,0x63,0x0,0x4,0x0,0x3,0x9,0x4,0x3,0x63,0x0,0x2,0x0,0x0,0xd,0x2,0x0,0x63, + 0x0,0xd,0x8,0xb,0xd,0x57,0x11,0xf,0x10,0x3,0xe,0xc,0x1,0xa,0xb,0xe,0xa,0x63,0x0,0xd,0xd,0xb,0x5c,0x0,0xb,0xd,0xb,0x50,0x59,0x40, + 0x22,0x6b,0x6b,0x4c,0x4c,0x6b,0x6d,0x6b,0x6d,0x4c,0x6a,0x4c,0x69,0x66,0x64,0x5c,0x5a,0x57,0x55,0x52,0x50,0x4a,0x49,0x1f,0x27,0x13,0x24,0x33,0x34,0x24, + 0x17,0x21,0x12,0x7,0x1d,0x2b,0xb1,0x6,0x0,0x44,0x0,0x6,0x23,0x22,0x26,0x27,0x26,0x35,0x34,0x37,0x36,0x33,0x32,0x17,0x16,0x16,0x33,0x32,0x36,0x35, + 0x34,0x26,0x23,0x23,0x22,0x35,0x34,0x36,0x33,0x33,0x32,0x36,0x35,0x34,0x26,0x23,0x22,0x7,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x32, + 0x16,0x16,0x15,0x14,0x7,0x16,0x16,0x15,0x4,0x15,0x14,0x7,0x1,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x1,0x36,0x33,0x32,0x17,0x12,0x16,0x15,0x14, + 0x6,0x23,0x23,0x15,0x14,0x6,0x23,0x22,0x26,0x35,0x35,0x21,0x22,0x26,0x35,0x34,0x36,0x37,0x1,0x36,0x36,0x33,0x32,0x16,0x15,0x11,0x33,0x23,0x13,0x3, + 0x2,0x1e,0x88,0x84,0x3e,0x7b,0x31,0x13,0x7,0xf,0x1a,0x8,0x8,0x2b,0x5f,0x2d,0x4b,0x52,0x4f,0x4a,0x2c,0x24,0x12,0x12,0x21,0x46,0x48,0x4a,0x41,0x53, + 0x59,0x7,0x9,0x19,0x10,0x6,0x12,0x30,0x77,0x3b,0x4c,0x6f,0x3c,0x5e,0x37,0x3d,0x2,0x3f,0xe,0xfc,0x6a,0x7,0x9,0x17,0x15,0xe,0xe,0x3,0x96,0x7, + 0x9,0x17,0x15,0x6e,0x14,0x14,0x14,0x5b,0x1a,0x1e,0x1e,0x1b,0xfe,0xc6,0x14,0x19,0x6,0x8,0x1,0x3d,0xf,0x1f,0x15,0x1d,0x2d,0x5b,0xcc,0x5,0xdd,0x3, + 0xe,0x71,0x1b,0x1b,0xa,0x17,0xd,0x13,0x27,0x4,0x15,0x1a,0x37,0x32,0x37,0x2e,0x33,0x18,0x1d,0x30,0x35,0x31,0x32,0x2e,0x4,0x27,0x11,0xf,0x17,0xa, + 0x1a,0x1c,0x32,0x5b,0x3a,0x64,0x33,0x12,0x53,0x3e,0x49,0x14,0x13,0x8,0xfd,0xcf,0x4,0x1c,0x14,0x12,0x12,0x9,0x2,0x31,0x4,0x1c,0xfd,0x12,0x18,0x1c, + 0x1c,0x19,0x99,0x14,0x14,0x14,0x14,0x99,0x21,0x20,0xf,0x13,0xb,0x1,0x79,0x13,0x12,0x1b,0x14,0xfe,0x8c,0x1,0xa,0xfe,0xf6,0x0,0x0,0x0,0x0,0x1, + 0x1,0x3b,0x1,0xe3,0x3,0xb3,0x5,0x36,0x0,0x1f,0x0,0x6,0xb3,0x16,0xb,0x1,0x30,0x2b,0x1,0x6,0x23,0x22,0x26,0x27,0x26,0x35,0x34,0x37,0x25,0x36, + 0x33,0x32,0x16,0x15,0x11,0x33,0x32,0x16,0x15,0x14,0x6,0x23,0x21,0x22,0x35,0x34,0x36,0x33,0x33,0x11,0x1,0x8b,0xb,0xc,0x10,0x17,0x8,0x6,0x23,0x1, + 0x1c,0xb,0xd,0x13,0x17,0xc2,0x19,0x18,0x18,0x19,0xfd,0xeb,0x32,0x19,0x19,0xcc,0x4,0x62,0x4,0x16,0x17,0x12,0xf,0x20,0xb,0x5b,0x4,0x15,0x15,0xfd, + 0x54,0x1d,0x21,0x21,0x1e,0x3f,0x21,0x1d,0x2,0x3b,0x0,0x0,0x0,0x1,0x1,0x20,0x1,0xe3,0x3,0xa7,0x5,0x36,0x0,0x29,0x0,0x6,0xb3,0xd,0x0,0x1, + 0x30,0x2b,0x0,0x16,0x16,0x15,0x14,0x6,0x7,0x7,0x21,0x32,0x16,0x15,0x14,0x6,0x23,0x21,0x22,0x35,0x34,0x37,0x1,0x36,0x36,0x35,0x34,0x26,0x23,0x22, + 0x6,0x7,0x6,0x23,0x22,0x26,0x27,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x2,0xc0,0x81,0x44,0x4f,0x54,0xef,0x1,0x83,0x19,0x18,0x18,0x19,0xfd,0xd9,0x2f, + 0x1f,0x1,0x4c,0x39,0x35,0x50,0x4a,0x33,0x75,0x35,0xc,0x7,0xf,0x19,0x8,0x9,0x16,0x3d,0x97,0x48,0x5,0x36,0x3f,0x74,0x4d,0x4d,0x7d,0x46,0xc6,0x1d, + 0x21,0x21,0x1e,0x48,0x27,0x1a,0x1,0x18,0x31,0x50,0x31,0x3a,0x45,0x22,0x1b,0x5,0x19,0x14,0x19,0xf,0x1c,0xc,0x21,0x25,0x0,0x1,0x1,0x2e,0x1,0xd1, + 0x3,0x9e,0x5,0x36,0x0,0x3a,0x0,0x6,0xb3,0xa,0x0,0x1,0x30,0x2b,0x0,0x16,0x16,0x15,0x14,0x7,0x16,0x16,0x15,0x14,0x6,0x23,0x22,0x26,0x27,0x26, + 0x35,0x34,0x37,0x36,0x36,0x33,0x32,0x17,0x16,0x16,0x33,0x32,0x36,0x35,0x34,0x26,0x23,0x23,0x22,0x35,0x34,0x36,0x33,0x33,0x32,0x36,0x35,0x34,0x26,0x23, + 0x22,0x7,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x2,0xb6,0x86,0x47,0x70,0x42,0x49,0xa3,0x9e,0x4a,0x94,0x3b,0x16,0x8,0x9,0x19,0xf, + 0x9,0xa,0x33,0x73,0x36,0x5a,0x62,0x5f,0x59,0x34,0x2c,0x15,0x17,0x27,0x54,0x56,0x58,0x4e,0x62,0x6c,0xa,0x9,0x1e,0x13,0x8,0x16,0x3a,0x8d,0x48,0x5, + 0x36,0x3c,0x6c,0x46,0x79,0x3c,0x16,0x64,0x4a,0x77,0x87,0x21,0x20,0xc,0x1b,0x11,0x16,0x16,0x19,0x5,0x1a,0x1e,0x41,0x3d,0x42,0x36,0x3e,0x1c,0x23,0x3a, + 0x3f,0x3a,0x3d,0x37,0x5,0x2f,0x15,0x10,0x1e,0xb,0x1f,0x21,0x0,0x2,0x0,0xf2,0x1,0xdb,0x3,0xc4,0x5,0x36,0x0,0x1e,0x0,0x21,0x0,0x8,0xb5,0x20, + 0x1f,0x18,0x9,0x2,0x30,0x2b,0x0,0x16,0x15,0x14,0x6,0x23,0x23,0x15,0x14,0x6,0x23,0x22,0x26,0x35,0x35,0x21,0x22,0x26,0x35,0x34,0x36,0x37,0x1,0x36, + 0x36,0x33,0x32,0x16,0x15,0x11,0x33,0x23,0x13,0x1,0x3,0xac,0x18,0x18,0x18,0x6d,0x1f,0x24,0x24,0x20,0xfe,0x88,0x19,0x1d,0x8,0x9,0x1,0x7b,0x13,0x24, + 0x1a,0x22,0x36,0x6d,0xf4,0x6,0xfe,0xf7,0x3,0x40,0x1d,0x21,0x21,0x1e,0xb8,0x18,0x18,0x18,0x18,0xb8,0x27,0x27,0x11,0x18,0xc,0x1,0xc4,0x17,0x15,0x20, + 0x18,0xfe,0x42,0x1,0x3f,0xfe,0xc1,0x0,0x0,0x2,0x3,0xb,0xff,0xf6,0x4,0x19,0x3,0xa6,0x0,0xd,0x0,0x1b,0x0,0x8,0xb5,0x13,0xe,0x5,0x0,0x2, + 0x30,0x2b,0x0,0x16,0x15,0x15,0x14,0x6,0x23,0x22,0x26,0x35,0x35,0x34,0x36,0x33,0x12,0x16,0x15,0x15,0x14,0x6,0x23,0x22,0x26,0x35,0x35,0x34,0x36,0x33, + 0x3,0xda,0x3f,0x3f,0x48,0x48,0x3f,0x3f,0x48,0x48,0x3f,0x3f,0x48,0x48,0x3f,0x3f,0x48,0x3,0xa6,0x1d,0x22,0xb8,0x22,0x1d,0x1d,0x22,0xb8,0x22,0x1d,0xfd, + 0x86,0x1d,0x22,0xb8,0x22,0x1d,0x1d,0x22,0xb8,0x22,0x1d,0x0,0x0,0x0,0xff,0xff,0x2,0xa0,0xfe,0xda,0x4,0x19,0x1,0x26,0x0,0x3,0x1,0x27,0x1,0x2c, + 0x0,0x0,0x0,0x0,0xff,0xff,0x3,0xb,0xff,0xf6,0x4,0x19,0x1,0x2c,0x0,0x3,0x1,0x2c,0x1,0x2c,0x0,0x0,0x0,0x0,0x0,0x2,0x2,0xb4,0xfe,0xda, + 0x4,0x2d,0x3,0xa6,0x0,0xd,0x0,0x1e,0x0,0x8,0xb5,0x14,0xe,0x5,0x0,0x2,0x30,0x2b,0x0,0x16,0x15,0x15,0x14,0x6,0x23,0x22,0x26,0x35,0x35,0x34, + 0x36,0x33,0x12,0x16,0x15,0x14,0x7,0x3,0x6,0x23,0x22,0x35,0x34,0x37,0x13,0x36,0x36,0x33,0x33,0x3,0xda,0x3f,0x3f,0x48,0x48,0x3f,0x3f,0x48,0x89,0x12, + 0x7,0xd4,0x12,0x44,0x48,0x2,0x5e,0x4,0x1b,0x15,0xc2,0x3,0xa6,0x1d,0x22,0xb8,0x22,0x1d,0x1d,0x22,0xb8,0x22,0x1d,0xfd,0x80,0x13,0xf,0xe,0xf,0xfe, + 0x1c,0x29,0x32,0x4,0xc,0x1,0xe2,0x13,0x15,0x0,0x0,0x0,0x0,0x3,0xfe,0x39,0xff,0xea,0x6,0x93,0x1,0x3c,0x0,0xf,0x0,0x1f,0x0,0x2f,0x0,0xa, + 0xb7,0x2a,0x22,0x1a,0x12,0xa,0x2,0x3,0x30,0x2b,0x24,0x36,0x36,0x33,0x32,0x16,0x16,0x15,0x14,0x6,0x6,0x23,0x22,0x26,0x26,0x35,0x24,0x36,0x36,0x33, + 0x32,0x16,0x16,0x15,0x14,0x6,0x6,0x23,0x22,0x26,0x26,0x35,0x24,0x36,0x36,0x33,0x32,0x16,0x16,0x15,0x14,0x6,0x6,0x23,0x22,0x26,0x26,0x35,0xfe,0x39, + 0x2e,0x4d,0x2e,0x2e,0x4d,0x2e,0x2e,0x4d,0x2e,0x2e,0x4d,0x2e,0x3,0x84,0x2e,0x4d,0x2e,0x2e,0x4d,0x2e,0x2e,0x4d,0x2e,0x2e,0x4d,0x2e,0x3,0x84,0x2e,0x4d, + 0x2e,0x2e,0x4d,0x2e,0x2e,0x4d,0x2e,0x2e,0x4d,0x2e,0xc1,0x4d,0x2e,0x2e,0x4d,0x2e,0x2e,0x4d,0x2e,0x2e,0x4d,0x2e,0x2e,0x4d,0x2e,0x2e,0x4d,0x2e,0x2e,0x4d, + 0x2e,0x2e,0x4d,0x2e,0x2e,0x4d,0x2e,0x2e,0x4d,0x2e,0x2e,0x4d,0x2e,0x2e,0x4d,0x2e,0x0,0x0,0x0,0x3,0x3,0x5,0xff,0xea,0xb,0x5f,0x1,0x3c,0x0,0xf, + 0x0,0x1f,0x0,0x2f,0x0,0xa,0xb7,0x2a,0x22,0x1a,0x12,0xa,0x2,0x3,0x30,0x2b,0x24,0x36,0x36,0x33,0x32,0x16,0x16,0x15,0x14,0x6,0x6,0x23,0x22,0x26, + 0x26,0x35,0x24,0x36,0x36,0x33,0x32,0x16,0x16,0x15,0x14,0x6,0x6,0x23,0x22,0x26,0x26,0x35,0x24,0x36,0x36,0x33,0x32,0x16,0x16,0x15,0x14,0x6,0x6,0x23, + 0x22,0x26,0x26,0x35,0x3,0x5,0x2e,0x4d,0x2e,0x2e,0x4d,0x2e,0x2e,0x4d,0x2e,0x2e,0x4d,0x2e,0x3,0x84,0x2e,0x4d,0x2e,0x2e,0x4d,0x2e,0x2e,0x4d,0x2e,0x2e, + 0x4d,0x2e,0x3,0x84,0x2e,0x4d,0x2e,0x2e,0x4d,0x2e,0x2e,0x4d,0x2e,0x2e,0x4d,0x2e,0xc1,0x4d,0x2e,0x2e,0x4d,0x2e,0x2e,0x4d,0x2e,0x2e,0x4d,0x2e,0x2e,0x4d, + 0x2e,0x2e,0x4d,0x2e,0x2e,0x4d,0x2e,0x2e,0x4d,0x2e,0x2e,0x4d,0x2e,0x2e,0x4d,0x2e,0x2e,0x4d,0x2e,0x2e,0x4d,0x2e,0x0,0x0,0x0,0x3,0x1,0x18,0xff,0xea, + 0x6,0x5c,0x0,0xf4,0x0,0xf,0x0,0x1f,0x0,0x2f,0x0,0xa,0xb7,0x26,0x20,0x16,0x10,0x6,0x0,0x3,0x30,0x2b,0x24,0x16,0x16,0x15,0x14,0x6,0x6,0x23, + 0x22,0x26,0x26,0x35,0x34,0x36,0x36,0x33,0x20,0x16,0x16,0x15,0x14,0x6,0x6,0x23,0x22,0x26,0x26,0x35,0x34,0x36,0x36,0x33,0x20,0x16,0x16,0x15,0x14,0x6, + 0x6,0x23,0x22,0x26,0x26,0x35,0x34,0x36,0x36,0x33,0x1,0xc1,0x3d,0x24,0x24,0x3d,0x24,0x24,0x3d,0x24,0x24,0x3d,0x24,0x2,0x41,0x3d,0x24,0x24,0x3d,0x24, + 0x24,0x3d,0x24,0x24,0x3d,0x24,0x2,0x41,0x3d,0x24,0x24,0x3d,0x24,0x24,0x3d,0x24,0x24,0x3d,0x24,0xf4,0x24,0x3d,0x24,0x24,0x3d,0x24,0x24,0x3d,0x24,0x24, + 0x3d,0x24,0x24,0x3d,0x24,0x24,0x3d,0x24,0x24,0x3d,0x24,0x24,0x3d,0x24,0x24,0x3d,0x24,0x24,0x3d,0x24,0x24,0x3d,0x24,0x24,0x3d,0x24,0x0,0x3,0xfe,0x20, + 0xff,0xea,0x4,0x7c,0x0,0xf4,0x0,0xf,0x0,0x1f,0x0,0x2f,0x0,0xa,0xb7,0x26,0x20,0x16,0x10,0x6,0x0,0x3,0x30,0x2b,0x24,0x16,0x16,0x15,0x14,0x6, + 0x6,0x23,0x22,0x26,0x26,0x35,0x34,0x36,0x36,0x33,0x20,0x16,0x16,0x15,0x14,0x6,0x6,0x23,0x22,0x26,0x26,0x35,0x34,0x36,0x36,0x33,0x20,0x16,0x16,0x15, + 0x14,0x6,0x6,0x23,0x22,0x26,0x26,0x35,0x34,0x36,0x36,0x33,0xfe,0xc9,0x3d,0x24,0x24,0x3d,0x24,0x24,0x3d,0x24,0x24,0x3d,0x24,0x2,0xcd,0x3d,0x24,0x24, + 0x3d,0x24,0x24,0x3d,0x24,0x24,0x3d,0x24,0x2,0xcd,0x3d,0x24,0x24,0x3d,0x24,0x24,0x3d,0x24,0x24,0x3d,0x24,0xf4,0x24,0x3d,0x24,0x24,0x3d,0x24,0x24,0x3d, + 0x24,0x24,0x3d,0x24,0x24,0x3d,0x24,0x24,0x3d,0x24,0x24,0x3d,0x24,0x24,0x3d,0x24,0x24,0x3d,0x24,0x24,0x3d,0x24,0x24,0x3d,0x24,0x24,0x3d,0x24,0x0,0x3, + 0x3,0x5,0xff,0xea,0xc,0x8b,0x1,0x3c,0x0,0xf,0x0,0x1f,0x0,0x2f,0x0,0xa,0xb7,0x2a,0x22,0x1a,0x12,0xa,0x2,0x3,0x30,0x2b,0x24,0x36,0x36,0x33, + 0x32,0x16,0x16,0x15,0x14,0x6,0x6,0x23,0x22,0x26,0x26,0x35,0x24,0x36,0x36,0x33,0x32,0x16,0x16,0x15,0x14,0x6,0x6,0x23,0x22,0x26,0x26,0x35,0x24,0x36, + 0x36,0x33,0x32,0x16,0x16,0x15,0x14,0x6,0x6,0x23,0x22,0x26,0x26,0x35,0x3,0x5,0x2e,0x4d,0x2e,0x2e,0x4d,0x2e,0x2e,0x4d,0x2e,0x2e,0x4d,0x2e,0x4,0x1a, + 0x2e,0x4d,0x2e,0x2e,0x4d,0x2e,0x2e,0x4d,0x2e,0x2e,0x4d,0x2e,0x4,0x1a,0x2e,0x4d,0x2e,0x2e,0x4d,0x2e,0x2e,0x4d,0x2e,0x2e,0x4d,0x2e,0xc1,0x4d,0x2e,0x2e, + 0x4d,0x2e,0x2e,0x4d,0x2e,0x2e,0x4d,0x2e,0x2e,0x4d,0x2e,0x2e,0x4d,0x2e,0x2e,0x4d,0x2e,0x2e,0x4d,0x2e,0x2e,0x4d,0x2e,0x2e,0x4d,0x2e,0x2e,0x4d,0x2e,0x2e, + 0x4d,0x2e,0x0,0x0,0x0,0x1,0x0,0x6a,0x0,0x91,0x4,0x62,0x4,0x67,0x0,0x37,0x0,0x2b,0x40,0x28,0x2f,0x27,0x2,0x2,0x3,0x31,0x26,0x1e,0x1a,0x10, + 0x5,0x6,0x0,0x2,0x2,0x4a,0x0,0x3,0x2,0x3,0x72,0x4,0x1,0x2,0x0,0x2,0x72,0x1,0x1,0x0,0x0,0x69,0x26,0x27,0x2e,0x25,0x2b,0x5,0x7,0x19, + 0x2b,0x0,0x15,0x14,0x6,0x7,0x5,0x13,0x16,0x15,0x14,0x7,0x6,0x23,0x22,0x26,0x27,0x3,0x3,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x13,0x25,0x26, + 0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x32,0x17,0x5,0x3,0x26,0x35,0x34,0x36,0x33,0x32,0x16,0x15,0x7,0x3,0x25,0x36,0x33,0x32,0x16,0x17,0x4,0x62,0x26, + 0x22,0xfe,0x9f,0xfb,0x1b,0x2d,0x1f,0x1d,0x16,0x25,0xe,0xb7,0xb7,0x1e,0x2b,0x1d,0x1f,0x2e,0x1b,0xfb,0xfe,0x9f,0x22,0x25,0x7,0xa,0x28,0x1b,0x16,0x15, + 0x1,0x49,0x29,0x1,0x31,0x2d,0x2e,0x30,0x1,0x2a,0x1,0x4a,0x15,0x16,0x1a,0x28,0xb,0x2,0xe5,0xf,0x1f,0x28,0x8,0x48,0xfe,0xeb,0x1c,0x1f,0x27,0x21, + 0x16,0x1a,0x17,0x1,0x40,0xfe,0xc0,0x31,0x16,0x21,0x27,0x1f,0x1c,0x1,0x15,0x48,0x8,0x28,0x1e,0x13,0x15,0x22,0x23,0xa,0x96,0x1,0x64,0x5,0xa,0x26, + 0x2c,0x2c,0x27,0xe,0xfe,0x9c,0x96,0xa,0x23,0x22,0x0,0x1,0x0,0xc6,0xfe,0x9d,0x4,0x6,0x5,0x72,0x0,0x11,0x0,0x36,0xb6,0xe,0x5,0x2,0x0,0x1, + 0x1,0x4a,0x4b,0xb0,0x17,0x50,0x58,0x40,0xc,0x2,0x1,0x1,0x0,0x1,0x72,0x0,0x0,0x0,0x13,0x0,0x4c,0x1b,0x40,0xa,0x2,0x1,0x1,0x0,0x1,0x72, + 0x0,0x0,0x0,0x69,0x59,0x40,0xa,0x0,0x0,0x0,0x11,0x0,0x10,0x27,0x3,0x7,0x15,0x2b,0x0,0x16,0x17,0x1,0x16,0x15,0x14,0x6,0x23,0x22,0x26,0x27, + 0x1,0x26,0x35,0x34,0x36,0x33,0x1,0x39,0x23,0x7,0x2,0xa0,0x3,0x34,0x28,0x17,0x23,0x7,0xfd,0x60,0x3,0x34,0x28,0x5,0x72,0x14,0x10,0xf9,0x95,0x9, + 0x8,0x17,0x1e,0x14,0x10,0x6,0x6b,0x9,0x8,0x17,0x1e,0x0,0x0,0x0,0xff,0xff,0x1,0xdf,0x1,0xa9,0x2,0xed,0x2,0xdf,0x1,0x7,0x1,0x2c,0x0,0x0, + 0x1,0xb3,0x0,0x9,0xb1,0x0,0x1,0xb8,0x1,0xb3,0xb0,0x33,0x2b,0x0,0x0,0x0,0x0,0x1,0x1,0x7e,0x1,0x63,0x3,0x4e,0x3,0x55,0x0,0xd,0x0,0x1f, + 0x40,0x1c,0x2,0x1,0x1,0x0,0x0,0x1,0x57,0x2,0x1,0x1,0x1,0x0,0x5b,0x0,0x0,0x1,0x0,0x4f,0x0,0x0,0x0,0xd,0x0,0xc,0x25,0x3,0x7,0x15, + 0x2b,0x0,0x16,0x15,0x11,0x14,0x6,0x23,0x22,0x26,0x35,0x11,0x34,0x36,0x33,0x2,0xe1,0x6d,0x6d,0x7b,0x7b,0x6d,0x6d,0x7b,0x3,0x55,0x2e,0x34,0xfe,0xd2, + 0x34,0x2e,0x2e,0x34,0x1,0x2e,0x34,0x2e,0x0,0x0,0x0,0x2,0x1,0xdf,0xff,0xf6,0x2,0xed,0x3,0xa6,0x0,0xd,0x0,0x1b,0x0,0x2c,0x40,0x29,0x0,0x0, + 0x0,0x1,0x5b,0x4,0x1,0x1,0x1,0x11,0x4b,0x5,0x1,0x3,0x3,0x2,0x5b,0x0,0x2,0x2,0xf,0x2,0x4c,0xe,0xe,0x0,0x0,0xe,0x1b,0xe,0x1a,0x15, + 0x13,0x0,0xd,0x0,0xc,0x25,0x6,0x7,0x15,0x2b,0x0,0x16,0x15,0x15,0x14,0x6,0x23,0x22,0x26,0x35,0x35,0x34,0x36,0x33,0x12,0x16,0x15,0x15,0x14,0x6, + 0x23,0x22,0x26,0x35,0x35,0x34,0x36,0x33,0x2,0xae,0x3f,0x3f,0x48,0x48,0x3f,0x3f,0x48,0x48,0x3f,0x3f,0x48,0x48,0x3f,0x3f,0x48,0x3,0xa6,0x1d,0x22,0xb8, + 0x22,0x1d,0x1d,0x22,0xb8,0x22,0x1d,0xfd,0x86,0x1d,0x22,0xb8,0x22,0x1d,0x1d,0x22,0xb8,0x22,0x1d,0x0,0x0,0x0,0x0,0x1,0x1,0x74,0xfe,0xda,0x2,0xed, + 0x1,0x26,0x0,0x10,0x0,0x1e,0x40,0x1b,0x9,0x1,0x0,0x1,0x1,0x4a,0x0,0x1,0x0,0x0,0x1,0x55,0x0,0x1,0x1,0x0,0x5b,0x0,0x0,0x1,0x0,0x4f, + 0x26,0x26,0x2,0x7,0x16,0x2b,0x0,0x16,0x15,0x14,0x7,0x3,0x6,0x23,0x22,0x35,0x34,0x37,0x13,0x36,0x36,0x33,0x33,0x2,0xdb,0x12,0x7,0xd4,0x12,0x44, + 0x48,0x2,0x5e,0x4,0x1b,0x15,0xc2,0x1,0x26,0x13,0xf,0xe,0xf,0xfe,0x1c,0x29,0x32,0x4,0xc,0x1,0xe2,0x13,0x15,0x0,0x0,0x3,0x0,0x6d,0xff,0xf6, + 0x4,0x5f,0x0,0xe6,0x0,0xd,0x0,0x1b,0x0,0x29,0x0,0x2f,0x40,0x2c,0x8,0x5,0x7,0x3,0x6,0x5,0x1,0x1,0x0,0x5b,0x4,0x2,0x2,0x0,0x0,0xf, + 0x0,0x4c,0x1c,0x1c,0xe,0xe,0x0,0x0,0x1c,0x29,0x1c,0x28,0x23,0x21,0xe,0x1b,0xe,0x1a,0x15,0x13,0x0,0xd,0x0,0xc,0x25,0x9,0x7,0x15,0x2b,0x24, + 0x16,0x15,0x15,0x14,0x6,0x23,0x22,0x26,0x35,0x35,0x34,0x36,0x33,0x20,0x16,0x15,0x15,0x14,0x6,0x23,0x22,0x26,0x35,0x35,0x34,0x36,0x33,0x20,0x16,0x15, + 0x15,0x14,0x6,0x23,0x22,0x26,0x35,0x35,0x34,0x36,0x33,0x1,0xe,0x31,0x31,0x38,0x38,0x31,0x31,0x38,0x1,0xc8,0x31,0x31,0x38,0x38,0x31,0x31,0x38,0x1, + 0xc8,0x31,0x31,0x38,0x38,0x31,0x31,0x38,0xe6,0x19,0x1c,0x86,0x1c,0x19,0x19,0x1c,0x86,0x1c,0x19,0x19,0x1c,0x86,0x1c,0x19,0x19,0x1c,0x86,0x1c,0x19,0x19, + 0x1c,0x86,0x1c,0x19,0x19,0x1c,0x86,0x1c,0x19,0x0,0x0,0x0,0x0,0x2,0x1,0xe9,0xff,0xf6,0x2,0xe3,0x4,0xf5,0x0,0xe,0x0,0x1c,0x0,0x32,0x40,0x2f, + 0xa,0x3,0x2,0x3,0x0,0x1,0x1,0x4a,0x4,0x1,0x1,0x0,0x0,0x3,0x1,0x0,0x63,0x5,0x1,0x3,0x3,0x2,0x5b,0x0,0x2,0x2,0xf,0x2,0x4c,0xf, + 0xf,0x0,0x0,0xf,0x1c,0xf,0x1b,0x16,0x14,0x0,0xe,0x0,0xc,0x25,0x6,0x7,0x15,0x2b,0x0,0x16,0x7,0x3,0x6,0x6,0x23,0x22,0x26,0x27,0x3,0x26, + 0x36,0x33,0x33,0x12,0x16,0x15,0x15,0x14,0x6,0x23,0x22,0x26,0x35,0x35,0x34,0x36,0x33,0x2,0xbe,0x14,0x2,0x31,0x2,0x1d,0x1a,0x1a,0x1f,0x1,0x30,0x2, + 0x14,0x17,0x82,0x1,0x3b,0x3b,0x42,0x42,0x3b,0x3b,0x42,0x4,0xf5,0x18,0x1a,0xfd,0x3d,0x17,0x19,0x19,0x17,0x2,0xc3,0x1a,0x18,0xfc,0x19,0x1d,0x22,0x9a, + 0x22,0x1d,0x1d,0x22,0x9a,0x22,0x1d,0x0,0x0,0x0,0x0,0x2,0x1,0xe9,0xfe,0xa7,0x2,0xe3,0x3,0xa6,0x0,0xd,0x0,0x1c,0x0,0x31,0x40,0x2e,0x18,0x11, + 0x10,0x3,0x3,0x2,0x1,0x4a,0x0,0x2,0x5,0x1,0x3,0x2,0x3,0x5f,0x4,0x1,0x1,0x1,0x0,0x5b,0x0,0x0,0x0,0x11,0x1,0x4c,0xe,0xe,0x0,0x0, + 0xe,0x1c,0xe,0x1a,0x15,0x13,0x0,0xd,0x0,0xc,0x25,0x6,0x7,0x15,0x2b,0x0,0x26,0x35,0x35,0x34,0x36,0x33,0x32,0x16,0x15,0x15,0x14,0x6,0x23,0x2, + 0x26,0x37,0x13,0x36,0x36,0x33,0x32,0x16,0x17,0x13,0x16,0x6,0x23,0x23,0x2,0x24,0x3b,0x3b,0x42,0x42,0x3b,0x3b,0x42,0x58,0x14,0x2,0x31,0x2,0x1d,0x1a, + 0x1a,0x1f,0x1,0x30,0x2,0x14,0x17,0x82,0x2,0x8e,0x1d,0x22,0x9a,0x22,0x1d,0x1d,0x22,0x9a,0x22,0x1d,0xfc,0x19,0x18,0x1a,0x2,0xc3,0x17,0x19,0x19,0x17, + 0xfd,0x3d,0x1a,0x18,0x0,0x2,0x0,0x3c,0xff,0xea,0x4,0x90,0x4,0xb9,0x0,0x4b,0x0,0x4f,0x0,0x53,0x40,0x50,0x47,0x3c,0x2,0x9,0xa,0x21,0x16,0x2, + 0x3,0x2,0x2,0x4a,0x10,0xd,0xb,0x3,0x9,0xe,0x8,0x2,0x0,0x1,0x9,0x0,0x62,0xf,0x7,0x2,0x1,0x6,0x4,0x2,0x2,0x3,0x1,0x2,0x61,0xc, + 0x1,0xa,0xa,0x16,0x4b,0x5,0x1,0x3,0x3,0x17,0x3,0x4c,0x0,0x0,0x4f,0x4e,0x4d,0x4c,0x0,0x4b,0x0,0x4a,0x45,0x43,0x40,0x3f,0x3a,0x38,0x35,0x33, + 0x21,0x24,0x25,0x23,0x15,0x23,0x24,0x21,0x24,0x11,0x7,0x1d,0x2b,0x0,0x16,0x15,0x14,0x6,0x23,0x23,0x7,0x33,0x32,0x16,0x15,0x14,0x6,0x23,0x23,0x3, + 0x6,0x6,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x21,0x3,0x6,0x6,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x23,0x22,0x26,0x35,0x34,0x36,0x33,0x33,0x37,0x23, + 0x22,0x26,0x35,0x34,0x36,0x33,0x33,0x13,0x36,0x36,0x33,0x32,0x16,0x15,0x14,0x7,0x3,0x21,0x13,0x36,0x36,0x33,0x32,0x16,0x15,0x14,0x7,0x3,0x33,0x5, + 0x21,0x7,0x21,0x4,0x73,0x1d,0x1d,0x22,0xc2,0x40,0xbc,0x22,0x1d,0x1d,0x22,0xe2,0x53,0x4,0x1f,0x13,0x1c,0x2c,0x2,0x4b,0xfe,0xfa,0x53,0x4,0x1f,0x13, + 0x1c,0x2c,0x2,0x4b,0xa0,0x22,0x1d,0x1d,0x22,0xc5,0x40,0xbf,0x22,0x1d,0x1d,0x22,0xe4,0x51,0x4,0x1f,0x13,0x1c,0x2c,0x2,0x48,0x1,0x5,0x51,0x4,0x1f, + 0x13,0x1c,0x2c,0x2,0x48,0x9d,0xfe,0xb9,0xfe,0xfb,0x40,0x1,0x5,0x3,0x61,0x21,0x27,0x27,0x21,0xf5,0x21,0x27,0x27,0x21,0xfe,0xbf,0x10,0x11,0x1e,0x18, + 0x3,0xa,0x1,0x1f,0xfe,0xbf,0x10,0x11,0x1e,0x18,0x3,0xa,0x1,0x1f,0x21,0x27,0x27,0x21,0xf5,0x21,0x27,0x27,0x21,0x1,0x37,0x10,0x11,0x1e,0x18,0x3, + 0xa,0xfe,0xeb,0x1,0x37,0x10,0x11,0x1e,0x18,0x3,0xa,0xfe,0xeb,0x90,0xf5,0x0,0x0,0x1,0x1,0xdf,0xff,0xf6,0x2,0xed,0x1,0x2c,0x0,0xd,0x0,0x19, + 0x40,0x16,0x2,0x1,0x1,0x1,0x0,0x5b,0x0,0x0,0x0,0xf,0x0,0x4c,0x0,0x0,0x0,0xd,0x0,0xc,0x25,0x3,0x7,0x15,0x2b,0x0,0x16,0x15,0x15,0x14, + 0x6,0x23,0x22,0x26,0x35,0x35,0x34,0x36,0x33,0x2,0xae,0x3f,0x3f,0x48,0x48,0x3f,0x3f,0x48,0x1,0x2c,0x1d,0x22,0xb8,0x22,0x1d,0x1d,0x22,0xb8,0x22,0x1d, + 0x0,0x0,0x0,0x2,0x1,0x6,0xff,0xf6,0x3,0xc6,0x4,0xf5,0x0,0x26,0x0,0x34,0x0,0x43,0x40,0x40,0xe,0x8,0x7,0x3,0x0,0x2,0x1,0x4a,0x0,0x2, + 0x1,0x0,0x1,0x2,0x0,0x70,0x0,0x0,0x5,0x1,0x0,0x5,0x6e,0x6,0x1,0x3,0x0,0x1,0x2,0x3,0x1,0x63,0x7,0x1,0x5,0x5,0x4,0x5b,0x0,0x4, + 0x4,0xf,0x4,0x4c,0x27,0x27,0x0,0x0,0x27,0x34,0x27,0x33,0x2e,0x2c,0x0,0x26,0x0,0x25,0x22,0x2d,0x2a,0x8,0x7,0x17,0x2b,0x0,0x16,0x16,0x15,0x14, + 0x6,0x6,0x7,0x7,0x6,0x6,0x23,0x22,0x26,0x27,0x27,0x26,0x36,0x37,0x37,0x3e,0x2,0x35,0x34,0x26,0x23,0x22,0x7,0x6,0x23,0x22,0x27,0x26,0x35,0x34, + 0x37,0x36,0x33,0x12,0x16,0x15,0x15,0x14,0x6,0x23,0x22,0x26,0x35,0x35,0x34,0x36,0x33,0x2,0xd6,0x9d,0x53,0x45,0x87,0x6d,0xa,0x2,0x24,0x1f,0x1f,0x24, + 0x2,0xa,0x1,0xe,0x11,0x45,0x50,0x5d,0x2d,0x65,0x5c,0x85,0x80,0xf,0x10,0x21,0x14,0xc,0x26,0x90,0xae,0x11,0x36,0x36,0x3d,0x3d,0x36,0x36,0x3d,0x4, + 0xf5,0x4f,0x8d,0x5d,0x56,0x7b,0x5b,0x2d,0xab,0x18,0x1b,0x1b,0x18,0xe1,0x14,0x18,0x7,0x1c,0x21,0x3a,0x48,0x31,0x4b,0x5a,0x43,0x9,0x2e,0x1b,0x13,0x21, + 0x15,0x4e,0xfb,0xf1,0x1d,0x22,0x72,0x22,0x1d,0x1d,0x22,0x72,0x22,0x1d,0x0,0x0,0x0,0x2,0x1,0x6,0xfe,0xa7,0x3,0xc6,0x3,0xa6,0x0,0xd,0x0,0x34, + 0x0,0x44,0x40,0x41,0x1c,0x16,0x15,0x3,0x4,0x2,0x1,0x4a,0x0,0x2,0x1,0x4,0x1,0x2,0x4,0x70,0x0,0x4,0x3,0x1,0x4,0x3,0x6e,0x0,0x3,0x7, + 0x1,0x5,0x3,0x5,0x60,0x6,0x1,0x1,0x1,0x0,0x5b,0x0,0x0,0x0,0x11,0x1,0x4c,0xe,0xe,0x0,0x0,0xe,0x34,0xe,0x33,0x2d,0x2b,0x29,0x27,0x1a, + 0x18,0x0,0xd,0x0,0xc,0x25,0x8,0x7,0x15,0x2b,0x0,0x26,0x35,0x35,0x34,0x36,0x33,0x32,0x16,0x15,0x15,0x14,0x6,0x23,0x2,0x26,0x26,0x35,0x34,0x36, + 0x36,0x37,0x37,0x36,0x36,0x33,0x32,0x16,0x17,0x17,0x16,0x6,0x7,0x7,0xe,0x2,0x15,0x14,0x16,0x33,0x32,0x37,0x36,0x33,0x32,0x17,0x16,0x15,0x14,0x7, + 0x6,0x23,0x2,0x51,0x36,0x36,0x3d,0x3d,0x36,0x36,0x3d,0x98,0x9d,0x53,0x45,0x87,0x6d,0xa,0x2,0x24,0x1f,0x1f,0x24,0x2,0xa,0x1,0xe,0x11,0x45,0x50, + 0x5d,0x2d,0x65,0x5c,0x85,0x80,0xf,0x10,0x21,0x14,0xc,0x26,0x90,0xae,0x2,0xb6,0x1d,0x22,0x72,0x22,0x1d,0x1d,0x22,0x72,0x22,0x1d,0xfb,0xf1,0x4f,0x8d, + 0x5d,0x56,0x7b,0x5b,0x2d,0xab,0x18,0x1b,0x1b,0x18,0xe1,0x14,0x18,0x7,0x1c,0x21,0x3a,0x48,0x31,0x4b,0x5a,0x43,0x9,0x2e,0x1b,0x13,0x21,0x15,0x4e,0x0, + 0xff,0xff,0x0,0xff,0x2,0xcb,0x3,0xc8,0x4,0xf5,0x0,0x23,0x1,0x30,0xff,0x1a,0x0,0x0,0x0,0x3,0x1,0x30,0x0,0xe6,0x0,0x0,0x0,0x0,0x0,0x1, + 0x1,0xe5,0x2,0xcb,0x2,0xe2,0x4,0xf5,0x0,0xd,0x0,0x25,0x40,0x22,0x1,0x1,0x0,0x1,0x1,0x4a,0x2,0x1,0x1,0x0,0x0,0x1,0x55,0x2,0x1,0x1, + 0x1,0x0,0x5b,0x0,0x0,0x1,0x0,0x4f,0x0,0x0,0x0,0xd,0x0,0xb,0x25,0x3,0x7,0x15,0x2b,0x0,0x15,0x7,0x3,0x6,0x6,0x23,0x22,0x26,0x27,0x3, + 0x26,0x33,0x33,0x2,0xe2,0x1,0x37,0x3,0x23,0x1e,0x1d,0x24,0x3,0x37,0x6,0x2f,0xa4,0x4,0xf5,0x27,0xb,0xfe,0x3e,0x1a,0x1c,0x1c,0x1a,0x1,0xc2,0x32, + 0x0,0x2,0x1,0x88,0xfe,0xda,0x3,0x1,0x3,0xa6,0x0,0xd,0x0,0x1e,0x0,0x2a,0x40,0x27,0x17,0x1,0x2,0x3,0x1,0x4a,0x0,0x3,0x0,0x2,0x3,0x2, + 0x5f,0x0,0x0,0x0,0x1,0x5b,0x4,0x1,0x1,0x1,0x11,0x0,0x4c,0x0,0x0,0x1e,0x1c,0x16,0x14,0x0,0xd,0x0,0xc,0x25,0x5,0x7,0x15,0x2b,0x0,0x16, + 0x15,0x15,0x14,0x6,0x23,0x22,0x26,0x35,0x35,0x34,0x36,0x33,0x12,0x16,0x15,0x14,0x7,0x3,0x6,0x23,0x22,0x35,0x34,0x37,0x13,0x36,0x36,0x33,0x33,0x2, + 0xae,0x3f,0x3f,0x48,0x48,0x3f,0x3f,0x48,0x89,0x12,0x7,0xd4,0x12,0x44,0x48,0x2,0x5e,0x4,0x1b,0x15,0xc2,0x3,0xa6,0x1d,0x22,0xb8,0x22,0x1d,0x1d,0x22, + 0xb8,0x22,0x1d,0xfd,0x80,0x13,0xf,0xe,0xf,0xfe,0x1c,0x29,0x32,0x4,0xc,0x1,0xe2,0x13,0x15,0x0,0x0,0x1,0x0,0xc6,0xfe,0x9d,0x4,0x6,0x5,0x72, + 0x0,0x11,0x0,0x36,0xb6,0xb,0x2,0x2,0x0,0x1,0x1,0x4a,0x4b,0xb0,0x17,0x50,0x58,0x40,0xc,0x2,0x1,0x1,0x0,0x1,0x72,0x0,0x0,0x0,0x13,0x0, + 0x4c,0x1b,0x40,0xa,0x2,0x1,0x1,0x0,0x1,0x72,0x0,0x0,0x0,0x69,0x59,0x40,0xa,0x0,0x0,0x0,0x11,0x0,0x10,0x27,0x3,0x7,0x15,0x2b,0x0,0x16, + 0x15,0x14,0x7,0x1,0x6,0x6,0x23,0x22,0x26,0x35,0x34,0x37,0x1,0x36,0x36,0x33,0x3,0xd2,0x34,0x3,0xfd,0x60,0x7,0x23,0x17,0x28,0x34,0x3,0x2,0xa0, + 0x7,0x23,0x17,0x5,0x72,0x1e,0x17,0x8,0x9,0xf9,0x95,0x10,0x14,0x1e,0x17,0x8,0x9,0x6,0x6b,0x10,0x14,0x0,0x0,0x0,0x0,0x1,0x0,0x46,0xfe,0x77, + 0x4,0x86,0xff,0x7,0x0,0xd,0x0,0x27,0xb1,0x6,0x64,0x44,0x40,0x1c,0x2,0x1,0x1,0x0,0x0,0x1,0x55,0x2,0x1,0x1,0x1,0x0,0x59,0x0,0x0,0x1, + 0x0,0x4d,0x0,0x0,0x0,0xd,0x0,0xb,0x34,0x3,0x7,0x15,0x2b,0xb1,0x6,0x0,0x44,0x4,0x16,0x15,0x14,0x6,0x23,0x21,0x22,0x26,0x35,0x34,0x36,0x33, + 0x21,0x4,0x69,0x1d,0x1d,0x22,0xfc,0x3e,0x22,0x1d,0x1d,0x22,0x3,0xc2,0xf9,0x21,0x27,0x27,0x21,0x21,0x27,0x27,0x21,0x0,0x0,0x0,0x0,0x1,0x0,0xb1, + 0xfe,0xb3,0x3,0xde,0x5,0x5c,0x0,0x3c,0x0,0xe0,0x40,0xa,0x37,0x1,0x4,0x0,0xe,0x1,0x3,0x4,0x2,0x4a,0x4b,0xb0,0xc,0x50,0x58,0x40,0x21,0x6, + 0x1,0x5,0x0,0x0,0x4,0x5,0x0,0x63,0x0,0x4,0x0,0x3,0x1,0x4,0x3,0x63,0x0,0x1,0x2,0x2,0x1,0x57,0x0,0x1,0x1,0x2,0x5b,0x0,0x2,0x1, + 0x2,0x4f,0x1b,0x4b,0xb0,0xe,0x50,0x58,0x40,0x1b,0x0,0x4,0x0,0x3,0x1,0x4,0x3,0x63,0x0,0x1,0x0,0x2,0x1,0x2,0x5f,0x0,0x0,0x0,0x5,0x5b, + 0x6,0x1,0x5,0x5,0x10,0x0,0x4c,0x1b,0x4b,0xb0,0x10,0x50,0x58,0x40,0x21,0x6,0x1,0x5,0x0,0x0,0x4,0x5,0x0,0x63,0x0,0x4,0x0,0x3,0x1,0x4, + 0x3,0x63,0x0,0x1,0x2,0x2,0x1,0x57,0x0,0x1,0x1,0x2,0x5b,0x0,0x2,0x1,0x2,0x4f,0x1b,0x4b,0xb0,0x15,0x50,0x58,0x40,0x1b,0x0,0x4,0x0,0x3, + 0x1,0x4,0x3,0x63,0x0,0x1,0x0,0x2,0x1,0x2,0x5f,0x0,0x0,0x0,0x5,0x5b,0x6,0x1,0x5,0x5,0x10,0x0,0x4c,0x1b,0x40,0x21,0x6,0x1,0x5,0x0, + 0x0,0x4,0x5,0x0,0x63,0x0,0x4,0x0,0x3,0x1,0x4,0x3,0x63,0x0,0x1,0x2,0x2,0x1,0x57,0x0,0x1,0x1,0x2,0x5b,0x0,0x2,0x1,0x2,0x4f,0x59, + 0x59,0x59,0x59,0x40,0x12,0x0,0x0,0x0,0x3c,0x0,0x3b,0x32,0x30,0x2c,0x2a,0x21,0x1f,0x1b,0x19,0x24,0x7,0x7,0x15,0x2b,0x0,0x16,0x15,0x14,0x6,0x23, + 0x22,0x6,0x6,0x15,0x17,0x17,0x16,0x6,0x7,0x15,0x16,0x16,0x15,0x14,0x7,0x7,0x6,0x15,0x14,0x16,0x33,0x32,0x16,0x15,0x14,0x6,0x23,0x22,0x26,0x35, + 0x34,0x37,0x37,0x36,0x35,0x34,0x26,0x23,0x22,0x26,0x35,0x34,0x36,0x33,0x32,0x36,0x35,0x34,0x27,0x27,0x26,0x35,0x34,0x36,0x33,0x3,0xc1,0x1d,0x1d,0x22, + 0x64,0x78,0x35,0x1,0x3,0x5,0x6f,0x7f,0x7e,0x71,0x1,0x8,0x1,0x83,0x8e,0x22,0x1d,0x1d,0x22,0xc9,0xdf,0x2,0xb,0x2,0x86,0x90,0x22,0x1d,0x1d,0x22, + 0x8b,0x84,0x1,0x5,0x2,0xdd,0xcb,0x5,0x5c,0x21,0x27,0x27,0x21,0x34,0x6e,0x5a,0x1d,0x48,0x78,0x9d,0x34,0xa,0x25,0x8f,0x6e,0x16,0xb,0x79,0xd,0x19, + 0x7f,0x74,0x21,0x27,0x27,0x21,0xb4,0xb4,0x23,0x12,0x7b,0x1c,0xc,0x71,0x71,0x21,0x27,0x27,0x21,0x7e,0x74,0x14,0xa,0x4a,0x1e,0xd,0xb9,0xb9,0x0,0x1, + 0x0,0xee,0xfe,0xb3,0x4,0x1b,0x5,0x5c,0x0,0x3b,0x0,0xdd,0x40,0xa,0x2c,0x1,0x1,0x0,0x15,0x1,0x3,0x1,0x2,0x4a,0x4b,0xb0,0xc,0x50,0x58,0x40, + 0x21,0x6,0x1,0x5,0x0,0x4,0x0,0x5,0x4,0x63,0x0,0x0,0x0,0x1,0x3,0x0,0x1,0x63,0x0,0x3,0x2,0x2,0x3,0x57,0x0,0x3,0x3,0x2,0x5b,0x0, + 0x2,0x3,0x2,0x4f,0x1b,0x4b,0xb0,0xe,0x50,0x58,0x40,0x1b,0x0,0x0,0x0,0x1,0x3,0x0,0x1,0x63,0x0,0x3,0x0,0x2,0x3,0x2,0x5f,0x0,0x4,0x4, + 0x5,0x5b,0x6,0x1,0x5,0x5,0x10,0x4,0x4c,0x1b,0x4b,0xb0,0x10,0x50,0x58,0x40,0x21,0x6,0x1,0x5,0x0,0x4,0x0,0x5,0x4,0x63,0x0,0x0,0x0,0x1, + 0x3,0x0,0x1,0x63,0x0,0x3,0x2,0x2,0x3,0x57,0x0,0x3,0x3,0x2,0x5b,0x0,0x2,0x3,0x2,0x4f,0x1b,0x4b,0xb0,0x15,0x50,0x58,0x40,0x1b,0x0,0x0, + 0x0,0x1,0x3,0x0,0x1,0x63,0x0,0x3,0x0,0x2,0x3,0x2,0x5f,0x0,0x4,0x4,0x5,0x5b,0x6,0x1,0x5,0x5,0x10,0x4,0x4c,0x1b,0x40,0x21,0x6,0x1, + 0x5,0x0,0x4,0x0,0x5,0x4,0x63,0x0,0x0,0x0,0x1,0x3,0x0,0x1,0x63,0x0,0x3,0x2,0x2,0x3,0x57,0x0,0x3,0x3,0x2,0x5b,0x0,0x2,0x3,0x2, + 0x4f,0x59,0x59,0x59,0x59,0x40,0xf,0x0,0x0,0x0,0x3b,0x0,0x3a,0x36,0x34,0x24,0x29,0x24,0x29,0x7,0x7,0x18,0x2b,0x0,0x16,0x15,0x14,0x7,0x7,0x6, + 0x15,0x14,0x16,0x33,0x32,0x16,0x15,0x14,0x6,0x23,0x22,0x6,0x15,0x14,0x17,0x17,0x16,0x15,0x14,0x6,0x23,0x22,0x26,0x35,0x34,0x36,0x33,0x32,0x36,0x35, + 0x34,0x27,0x27,0x26,0x35,0x34,0x36,0x37,0x35,0x26,0x26,0x37,0x37,0x36,0x26,0x26,0x23,0x22,0x26,0x35,0x34,0x36,0x33,0x1,0xf8,0xdd,0x2,0x5,0x1,0x84, + 0x8b,0x22,0x1d,0x1d,0x22,0x90,0x86,0x2,0xb,0x2,0xdf,0xc9,0x22,0x1d,0x1d,0x22,0x8e,0x83,0x1,0x8,0x1,0x71,0x7e,0x7f,0x6f,0x5,0x3,0x4,0x31,0x79, + 0x6a,0x22,0x1d,0x1d,0x22,0x5,0x5c,0xb9,0xb9,0xd,0x1e,0x4a,0xa,0x14,0x74,0x7e,0x21,0x27,0x27,0x21,0x71,0x71,0xc,0x1c,0x7b,0x12,0x23,0xb4,0xb4,0x21, + 0x27,0x27,0x21,0x74,0x7f,0x19,0xd,0x79,0xb,0x16,0x6e,0x8f,0x25,0xa,0x34,0x9d,0x78,0x48,0x64,0x7b,0x3a,0x21,0x27,0x27,0x21,0x0,0x0,0x1,0x1,0x4a, + 0xfe,0xb3,0x3,0x66,0x5,0x5c,0x0,0x19,0x0,0xa9,0x4b,0xb0,0xc,0x50,0x58,0x40,0x1a,0x0,0x1,0x0,0x2,0x3,0x1,0x2,0x61,0x4,0x1,0x3,0x0,0x0, + 0x3,0x55,0x4,0x1,0x3,0x3,0x0,0x59,0x0,0x0,0x3,0x0,0x4d,0x1b,0x4b,0xb0,0xe,0x50,0x58,0x40,0x13,0x4,0x1,0x3,0x0,0x0,0x3,0x0,0x5d,0x0, + 0x2,0x2,0x1,0x59,0x0,0x1,0x1,0x10,0x2,0x4c,0x1b,0x4b,0xb0,0x10,0x50,0x58,0x40,0x1a,0x0,0x1,0x0,0x2,0x3,0x1,0x2,0x61,0x4,0x1,0x3,0x0, + 0x0,0x3,0x55,0x4,0x1,0x3,0x3,0x0,0x59,0x0,0x0,0x3,0x0,0x4d,0x1b,0x4b,0xb0,0x15,0x50,0x58,0x40,0x13,0x4,0x1,0x3,0x0,0x0,0x3,0x0,0x5d, + 0x0,0x2,0x2,0x1,0x59,0x0,0x1,0x1,0x10,0x2,0x4c,0x1b,0x40,0x1a,0x0,0x1,0x0,0x2,0x3,0x1,0x2,0x61,0x4,0x1,0x3,0x0,0x0,0x3,0x55,0x4, + 0x1,0x3,0x3,0x0,0x59,0x0,0x0,0x3,0x0,0x4d,0x59,0x59,0x59,0x59,0x40,0xc,0x0,0x0,0x0,0x19,0x0,0x18,0x24,0x45,0x44,0x5,0x7,0x17,0x2b,0x4, + 0x16,0x15,0x14,0x6,0x23,0x21,0x23,0x22,0x26,0x35,0x11,0x34,0x36,0x33,0x33,0x21,0x32,0x16,0x15,0x14,0x6,0x23,0x21,0x11,0x21,0x3,0x49,0x1d,0x1d,0x22, + 0xfe,0xa0,0x3e,0x22,0x1d,0x1d,0x22,0x57,0x1,0x47,0x22,0x1d,0x1d,0x22,0xfe,0xb9,0x1,0x47,0xbd,0x21,0x27,0x27,0x21,0x1f,0x25,0x6,0x21,0x25,0x1f,0x21, + 0x27,0x27,0x21,0xfa,0x77,0x0,0x0,0x0,0x0,0x1,0x1,0x66,0xfe,0xb3,0x3,0x82,0x5,0x5c,0x0,0x19,0x0,0xa6,0x4b,0xb0,0xc,0x50,0x58,0x40,0x19,0x4, + 0x1,0x3,0x0,0x2,0x1,0x3,0x2,0x61,0x0,0x1,0x0,0x0,0x1,0x55,0x0,0x1,0x1,0x0,0x59,0x0,0x0,0x1,0x0,0x4d,0x1b,0x4b,0xb0,0xe,0x50,0x58, + 0x40,0x13,0x0,0x1,0x0,0x0,0x1,0x0,0x5d,0x0,0x2,0x2,0x3,0x59,0x4,0x1,0x3,0x3,0x10,0x2,0x4c,0x1b,0x4b,0xb0,0x10,0x50,0x58,0x40,0x19,0x4, + 0x1,0x3,0x0,0x2,0x1,0x3,0x2,0x61,0x0,0x1,0x0,0x0,0x1,0x55,0x0,0x1,0x1,0x0,0x59,0x0,0x0,0x1,0x0,0x4d,0x1b,0x4b,0xb0,0x15,0x50,0x58, + 0x40,0x13,0x0,0x1,0x0,0x0,0x1,0x0,0x5d,0x0,0x2,0x2,0x3,0x59,0x4,0x1,0x3,0x3,0x10,0x2,0x4c,0x1b,0x40,0x19,0x4,0x1,0x3,0x0,0x2,0x1, + 0x3,0x2,0x61,0x0,0x1,0x0,0x0,0x1,0x55,0x0,0x1,0x1,0x0,0x59,0x0,0x0,0x1,0x0,0x4d,0x59,0x59,0x59,0x59,0x40,0xc,0x0,0x0,0x0,0x19,0x0, + 0x16,0x21,0x24,0x45,0x5,0x7,0x17,0x2b,0x0,0x16,0x15,0x11,0x14,0x6,0x23,0x23,0x21,0x22,0x26,0x35,0x34,0x36,0x33,0x21,0x11,0x21,0x22,0x26,0x35,0x34, + 0x36,0x33,0x21,0x33,0x3,0x65,0x1d,0x1d,0x22,0x57,0xfe,0xb9,0x22,0x1d,0x1d,0x22,0x1,0x47,0xfe,0xb9,0x22,0x1d,0x1d,0x22,0x1,0x60,0x3e,0x5,0x5c,0x1f, + 0x25,0xf9,0xdf,0x25,0x1f,0x21,0x27,0x27,0x21,0x5,0x89,0x21,0x27,0x27,0x21,0x0,0x0,0x1,0x1,0x29,0xfe,0xa9,0x3,0x61,0x5,0x65,0x0,0x1f,0x0,0x10, + 0x40,0xd,0x1,0x1,0x0,0x48,0x0,0x0,0x0,0x69,0x16,0x14,0x1,0x7,0x14,0x2b,0x0,0x33,0x32,0x16,0x17,0x16,0x15,0x14,0x7,0x6,0x2,0x15,0x14,0x12, + 0x17,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x22,0x27,0x26,0x26,0x2,0x35,0x34,0x12,0x36,0x37,0x3,0xe,0xa,0x10,0x1d,0xb,0x11,0xd,0xad,0xea,0xe9,0xae, + 0xd,0x11,0xb,0x1d,0x10,0xa,0xb,0x7a,0xd7,0x89,0x89,0xd8,0x79,0x5,0x65,0x16,0x12,0x1d,0x19,0x14,0x9,0x72,0xfe,0x8c,0xff,0xff,0xfe,0x90,0x72,0x9, + 0x14,0x19,0x1d,0x12,0x16,0x6,0x49,0xf3,0x1,0x52,0xc8,0xc8,0x1,0x54,0xf5,0x49,0x0,0x1,0x1,0x6b,0xfe,0xa9,0x3,0xa3,0x5,0x65,0x0,0x1f,0x0,0x10, + 0x40,0xd,0x9,0x1,0x0,0x47,0x0,0x0,0x0,0x69,0x1e,0x1c,0x1,0x7,0x14,0x2b,0x0,0x16,0x12,0x15,0x14,0x2,0x6,0x7,0x6,0x23,0x22,0x26,0x27,0x26, + 0x35,0x34,0x37,0x36,0x12,0x35,0x34,0x2,0x27,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x32,0x17,0x2,0x42,0xd8,0x89,0x89,0xd7,0x7a,0xb,0xa,0x10,0x1d,0xb, + 0x11,0xd,0xae,0xe9,0xea,0xad,0xd,0x11,0xb,0x1d,0x10,0xa,0xb,0x5,0x16,0xf5,0xfe,0xac,0xc8,0xc8,0xfe,0xae,0xf3,0x49,0x6,0x16,0x12,0x1d,0x19,0x14, + 0x9,0x72,0x1,0x70,0xff,0xff,0x1,0x74,0x72,0x9,0x14,0x19,0x1d,0x12,0x16,0x6,0x0,0x1,0x1,0xe,0x1,0xf8,0x3,0xbe,0x2,0x94,0x0,0xd,0x0,0x6, + 0xb3,0x4,0x0,0x1,0x30,0x2b,0x0,0x16,0x15,0x14,0x6,0x23,0x21,0x22,0x26,0x35,0x34,0x36,0x33,0x21,0x3,0xa1,0x1d,0x1d,0x22,0xfd,0xce,0x22,0x1d,0x1d, + 0x22,0x2,0x32,0x2,0x94,0x22,0x2c,0x2c,0x22,0x22,0x2c,0x2c,0x22,0x0,0x0,0x0,0x0,0x1,0xfe,0x52,0x1,0xfe,0x6,0x7a,0x2,0x8e,0x0,0xd,0x0,0x6, + 0xb3,0x4,0x0,0x1,0x30,0x2b,0x0,0x16,0x15,0x14,0x6,0x23,0x21,0x22,0x26,0x35,0x34,0x36,0x33,0x21,0x6,0x5d,0x1d,0x1d,0x22,0xf8,0x56,0x22,0x1d,0x1d, + 0x22,0x7,0xaa,0x2,0x8e,0x21,0x27,0x27,0x21,0x21,0x27,0x27,0x21,0x0,0x0,0x0,0x0,0x1,0xff,0xf0,0x1,0xfe,0x9,0xa8,0x2,0x8e,0x0,0xd,0x0,0x6, + 0xb3,0x4,0x0,0x1,0x30,0x2b,0x0,0x16,0x15,0x14,0x6,0x23,0x21,0x22,0x26,0x35,0x34,0x36,0x33,0x21,0x9,0x8b,0x1d,0x1d,0x22,0xf6,0xc6,0x22,0x1d,0x1d, + 0x22,0x9,0x3a,0x2,0x8e,0x21,0x27,0x27,0x21,0x21,0x27,0x27,0x21,0x0,0x0,0x0,0x0,0x1,0x1,0x1c,0x1,0xfe,0x8,0x7c,0x2,0x8e,0x0,0xd,0x0,0x6, + 0xb3,0x4,0x0,0x1,0x30,0x2b,0x0,0x16,0x15,0x14,0x6,0x23,0x21,0x22,0x26,0x35,0x34,0x36,0x33,0x21,0x8,0x5f,0x1d,0x1d,0x22,0xf9,0x1e,0x22,0x1d,0x1d, + 0x22,0x6,0xe2,0x2,0x8e,0x21,0x27,0x27,0x21,0x21,0x27,0x27,0x21,0x0,0x0,0x0,0x0,0x1,0xff,0x1a,0x1,0xfe,0x5,0xb2,0x2,0x8e,0x0,0xd,0x0,0x6, + 0xb3,0x4,0x0,0x1,0x30,0x2b,0x0,0x16,0x15,0x14,0x6,0x23,0x21,0x22,0x26,0x35,0x34,0x36,0x33,0x21,0x5,0x95,0x1d,0x1d,0x22,0xf9,0xe6,0x22,0x1d,0x1d, + 0x22,0x6,0x1a,0x2,0x8e,0x21,0x27,0x27,0x21,0x21,0x27,0x27,0x21,0x0,0x0,0x0,0x0,0x1,0xff,0xe2,0x1,0xfe,0x4,0xea,0x2,0x8e,0x0,0xd,0x0,0x1f, + 0x40,0x1c,0x2,0x1,0x1,0x0,0x0,0x1,0x55,0x2,0x1,0x1,0x1,0x0,0x59,0x0,0x0,0x1,0x0,0x4d,0x0,0x0,0x0,0xd,0x0,0xb,0x34,0x3,0x7,0x15, + 0x2b,0x0,0x16,0x15,0x14,0x6,0x23,0x21,0x22,0x26,0x35,0x34,0x36,0x33,0x21,0x4,0xcd,0x1d,0x1d,0x22,0xfb,0x76,0x22,0x1d,0x1d,0x22,0x4,0x8a,0x2,0x8e, + 0x21,0x27,0x27,0x21,0x21,0x27,0x27,0x21,0x0,0x0,0x0,0x1,0x0,0xaa,0x1,0xfe,0x4,0x22,0x2,0x8e,0x0,0xd,0x0,0x1f,0x40,0x1c,0x2,0x1,0x1,0x0, + 0x0,0x1,0x55,0x2,0x1,0x1,0x1,0x0,0x59,0x0,0x0,0x1,0x0,0x4d,0x0,0x0,0x0,0xd,0x0,0xb,0x34,0x3,0x7,0x15,0x2b,0x0,0x16,0x15,0x14,0x6, + 0x23,0x21,0x22,0x26,0x35,0x34,0x36,0x33,0x21,0x4,0x5,0x1d,0x1d,0x22,0xfd,0x6,0x22,0x1d,0x1d,0x22,0x2,0xfa,0x2,0x8e,0x21,0x27,0x27,0x21,0x21,0x27, + 0x27,0x21,0x0,0x0,0x0,0x1,0x0,0xaa,0x1,0xfe,0x4,0x22,0x2,0x8e,0x0,0xd,0x0,0x1f,0x40,0x1c,0x2,0x1,0x1,0x0,0x0,0x1,0x55,0x2,0x1,0x1, + 0x1,0x0,0x59,0x0,0x0,0x1,0x0,0x4d,0x0,0x0,0x0,0xd,0x0,0xb,0x34,0x3,0x7,0x15,0x2b,0x0,0x16,0x15,0x14,0x6,0x23,0x21,0x22,0x26,0x35,0x34, + 0x36,0x33,0x21,0x4,0x5,0x1d,0x1d,0x22,0xfd,0x6,0x22,0x1d,0x1d,0x22,0x2,0xfa,0x2,0x8e,0x21,0x27,0x27,0x21,0x21,0x27,0x27,0x21,0x0,0x0,0xff,0xff, + 0x0,0xaa,0x1,0xfe,0x4,0x22,0x2,0x8e,0x0,0x2,0x1,0x41,0x0,0x0,0xff,0xff,0x0,0x99,0x0,0xb6,0x4,0x2f,0x3,0xb2,0x0,0x23,0x1,0x46,0xff,0x10, + 0x0,0x0,0x0,0x3,0x1,0x46,0x0,0xf0,0x0,0x0,0x0,0x0,0xff,0xff,0x0,0x9d,0x0,0xb6,0x4,0x33,0x3,0xb2,0x0,0x23,0x1,0x47,0xff,0x10,0x0,0x0, + 0x0,0x3,0x1,0x47,0x0,0xf0,0x0,0x0,0x0,0x0,0x0,0x1,0x1,0x89,0x0,0xb6,0x3,0x3f,0x3,0xb2,0x0,0x17,0x0,0x1a,0x40,0x17,0x4,0x1,0x2,0x0, + 0x1,0x1,0x4a,0x0,0x0,0x0,0x1,0x5b,0x0,0x1,0x1,0x19,0x0,0x4c,0x28,0x2a,0x2,0x7,0x16,0x2b,0x0,0x15,0x14,0x7,0x3,0x13,0x16,0x15,0x14,0x7, + 0x6,0x23,0x22,0x27,0x1,0x26,0x35,0x34,0x37,0x1,0x36,0x33,0x32,0x17,0x3,0x3f,0xe,0xf6,0xf6,0xe,0x1f,0x20,0x1c,0x14,0xc,0xfe,0xce,0x9,0x9,0x1, + 0x32,0xc,0x14,0x1c,0x20,0x3,0x7b,0x18,0x10,0x10,0xfe,0xf1,0xfe,0xf1,0x10,0x10,0x18,0x1b,0x1c,0xf,0x1,0x57,0xb,0xd,0xf,0x9,0x1,0x57,0xf,0x1c, + 0x0,0x0,0x0,0x1,0x1,0x8d,0x0,0xb6,0x3,0x43,0x3,0xb2,0x0,0x17,0x0,0x1b,0x40,0x18,0x12,0xf,0x6,0x3,0x1,0x0,0x1,0x4a,0x0,0x1,0x1,0x0, + 0x5b,0x0,0x0,0x0,0x19,0x1,0x4c,0x28,0x20,0x2,0x7,0x16,0x2b,0x0,0x33,0x32,0x17,0x1,0x16,0x15,0x14,0x7,0x1,0x6,0x23,0x22,0x27,0x26,0x35,0x34, + 0x37,0x13,0x3,0x26,0x35,0x34,0x37,0x1,0xcc,0x1c,0x14,0xc,0x1,0x32,0x9,0x9,0xfe,0xce,0xc,0x14,0x1c,0x20,0x1f,0xe,0xf6,0xf6,0xe,0x1f,0x3,0xb2, + 0xf,0xfe,0xa9,0x9,0xf,0xd,0xb,0xfe,0xa9,0xf,0x1c,0x1b,0x18,0x10,0x10,0x1,0xf,0x1,0xf,0x10,0x10,0x18,0x1b,0x0,0xff,0xff,0x0,0xc0,0xfe,0xfb, + 0x4,0x18,0x1,0x26,0x0,0x23,0x1,0x4d,0x0,0xf5,0x0,0x0,0x0,0x3,0x1,0x4d,0xff,0xb,0x0,0x0,0x0,0x0,0xff,0xff,0x0,0xb1,0x2,0xf0,0x4,0x9, + 0x5,0x1b,0x0,0x23,0x1,0x4b,0xff,0xb,0x0,0x0,0x0,0x3,0x1,0x4b,0x0,0xf5,0x0,0x0,0x0,0x0,0xff,0xff,0x0,0xc0,0x2,0xca,0x4,0x18,0x4,0xf5, + 0x0,0x23,0x1,0x4c,0xff,0xb,0x0,0x0,0x0,0x3,0x1,0x4c,0x0,0xf5,0x0,0x0,0x0,0x0,0x0,0x1,0x1,0xa6,0x2,0xf0,0x3,0x14,0x5,0x1b,0x0,0x10, + 0x0,0x19,0x40,0x16,0xa,0x1,0x1,0x0,0x1,0x4a,0x0,0x1,0x0,0x1,0x73,0x0,0x0,0x0,0x10,0x0,0x4c,0x26,0x26,0x2,0x7,0x16,0x2b,0x0,0x26,0x35, + 0x34,0x37,0x13,0x36,0x33,0x32,0x16,0x15,0x14,0x7,0x3,0x6,0x23,0x23,0x1,0xb9,0x13,0x8,0xdc,0x16,0x34,0x1d,0x23,0x2,0x65,0x9,0x2b,0xaf,0x2,0xf0, + 0x12,0xf,0xc,0x12,0x1,0xc1,0x2b,0x1c,0x18,0x5,0xc,0xfe,0x42,0x28,0x0,0x0,0x0,0x1,0x1,0xb5,0x2,0xca,0x3,0x23,0x4,0xf5,0x0,0x10,0x0,0x17, + 0x40,0x14,0xa,0x1,0x0,0x1,0x1,0x4a,0x0,0x1,0x0,0x1,0x72,0x0,0x0,0x0,0x69,0x26,0x26,0x2,0x7,0x16,0x2b,0x0,0x16,0x15,0x14,0x7,0x3,0x6, + 0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x36,0x33,0x33,0x3,0x10,0x13,0x8,0xdc,0x15,0x35,0x1d,0x23,0x2,0x65,0x9,0x2b,0xaf,0x4,0xf5,0x12,0xf,0xc,0x12, + 0xfe,0x3f,0x2b,0x1c,0x18,0x5,0xc,0x1,0xbe,0x28,0x0,0x1,0x1,0xb5,0xfe,0xfb,0x3,0x23,0x1,0x26,0x0,0x10,0x0,0x17,0x40,0x14,0xa,0x1,0x0,0x1, + 0x1,0x4a,0x0,0x1,0x0,0x1,0x72,0x0,0x0,0x0,0x69,0x26,0x26,0x2,0x7,0x16,0x2b,0x0,0x16,0x15,0x14,0x7,0x3,0x6,0x23,0x22,0x26,0x35,0x34,0x37, + 0x13,0x36,0x33,0x33,0x3,0x10,0x13,0x8,0xdc,0x15,0x35,0x1d,0x23,0x2,0x65,0x9,0x2b,0xaf,0x1,0x26,0x12,0xf,0xc,0x12,0xfe,0x3f,0x2b,0x1c,0x18,0x5, + 0xc,0x1,0xbe,0x28,0x0,0x2,0x0,0x93,0xff,0x83,0x4,0x31,0x5,0x20,0x0,0x31,0x0,0x3a,0x0,0x34,0x40,0x31,0x36,0x2a,0x1e,0x16,0x4,0x2,0x1,0x35, + 0x2b,0xe,0x6,0x4,0x0,0x3,0x2,0x4a,0x0,0x2,0x1,0x3,0x1,0x2,0x3,0x70,0x0,0x3,0x0,0x1,0x3,0x0,0x6e,0x0,0x0,0x0,0x1,0x5b,0x0,0x1, + 0x1,0x10,0x0,0x4c,0x28,0x1a,0x2e,0x29,0x4,0x7,0x18,0x2b,0x0,0x15,0x14,0x7,0x6,0x6,0x7,0x15,0x14,0x6,0x23,0x22,0x26,0x35,0x35,0x2e,0x2,0x35, + 0x34,0x36,0x36,0x37,0x35,0x34,0x36,0x33,0x32,0x16,0x15,0x15,0x16,0x17,0x16,0x15,0x14,0x7,0x6,0x23,0x22,0x27,0x26,0x27,0x11,0x36,0x37,0x36,0x33,0x32, + 0x17,0x24,0x16,0x16,0x17,0x11,0xe,0x2,0x15,0x4,0x31,0x20,0x43,0xa9,0x5b,0x21,0x25,0x25,0x21,0x80,0xc2,0x69,0x69,0xc2,0x80,0x21,0x25,0x25,0x21,0xa8, + 0x87,0x23,0xf,0x14,0x20,0x13,0x12,0x6b,0x7f,0x8f,0x6c,0x14,0x12,0x1f,0x16,0xfd,0x11,0x42,0x7a,0x51,0x51,0x7a,0x42,0x1,0x45,0x17,0x21,0x17,0x30,0x41, + 0xa,0xb7,0x22,0x1f,0x1f,0x22,0xb5,0xc,0x82,0xd2,0x81,0x81,0xd2,0x82,0xc,0xa4,0x22,0x1f,0x1f,0x22,0xa6,0x11,0x58,0x16,0x23,0x17,0x1b,0x29,0xd,0x4a, + 0x11,0xfd,0x6c,0x16,0x53,0xf,0x26,0xa1,0x8f,0x5a,0xc,0x2,0x98,0xc,0x5a,0x8f,0x57,0x0,0x0,0x2,0x0,0x6f,0x0,0x63,0x4,0x5d,0x4,0x51,0x0,0x37, + 0x0,0x47,0x0,0x49,0x40,0x46,0x29,0x25,0x2,0x7,0x4,0x37,0x33,0x1b,0x17,0x4,0x6,0x7,0xd,0x9,0x2,0x1,0x6,0x3,0x4a,0x5,0x1,0x3,0x4,0x0, + 0x3,0x57,0x0,0x4,0x0,0x7,0x6,0x4,0x7,0x63,0x0,0x6,0x0,0x1,0x0,0x6,0x1,0x63,0x5,0x1,0x3,0x3,0x0,0x5b,0x2,0x1,0x0,0x3,0x0,0x4f, + 0x44,0x42,0x3c,0x3a,0x2d,0x2b,0x28,0x26,0x23,0x21,0x23,0x23,0x25,0x8,0x7,0x17,0x2b,0x25,0x16,0x15,0x14,0x7,0x6,0x23,0x22,0x27,0x27,0x6,0x23,0x22, + 0x27,0x7,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x37,0x26,0x35,0x34,0x37,0x27,0x26,0x35,0x34,0x37,0x36,0x33,0x32,0x17,0x17,0x36,0x33,0x32,0x17,0x37, + 0x36,0x33,0x32,0x17,0x16,0x15,0x14,0x7,0x7,0x16,0x15,0x14,0x7,0x24,0x16,0x16,0x33,0x32,0x36,0x36,0x35,0x34,0x26,0x26,0x23,0x22,0x6,0x6,0x15,0x4, + 0x46,0x17,0x1d,0x1d,0x17,0x15,0x17,0x8f,0x67,0x84,0x84,0x67,0x8f,0x17,0x15,0x17,0x1d,0x1d,0x17,0x8e,0x48,0x48,0x8e,0x17,0x1d,0x1d,0x17,0x15,0x17,0x8f, + 0x67,0x84,0x84,0x67,0x8f,0x17,0x15,0x17,0x1d,0x1d,0x17,0x8e,0x48,0x48,0xfd,0xa8,0x42,0x77,0x4d,0x4d,0x77,0x42,0x42,0x77,0x4d,0x4d,0x77,0x42,0xe0,0x17, + 0x15,0x17,0x1d,0x1d,0x17,0x8f,0x43,0x43,0x8f,0x17,0x1d,0x1d,0x17,0x15,0x17,0x8e,0x66,0x86,0x86,0x66,0x8e,0x17,0x15,0x17,0x1d,0x1d,0x17,0x8f,0x43,0x43, + 0x8f,0x17,0x1d,0x1d,0x17,0x15,0x17,0x8e,0x66,0x86,0x86,0x66,0xa1,0x78,0x43,0x43,0x78,0x4b,0x4b,0x78,0x43,0x43,0x78,0x4b,0x0,0x1,0x0,0xae,0xff,0x1f, + 0x4,0x1c,0x5,0x84,0x0,0x4e,0x0,0x90,0x40,0xe,0x33,0x1,0x7,0x4,0x37,0x1,0x6,0x7,0xb,0x1,0x0,0x3,0x3,0x4a,0x4b,0xb0,0x9,0x50,0x58,0x40, + 0x32,0x0,0x5,0x4,0x4,0x5,0x66,0x0,0x6,0x7,0x2,0x7,0x6,0x2,0x70,0x0,0x2,0x3,0x7,0x2,0x3,0x6e,0x0,0x1,0x0,0x1,0x73,0x0,0x4,0x0, + 0x7,0x6,0x4,0x7,0x64,0x0,0x3,0x0,0x0,0x3,0x57,0x0,0x3,0x3,0x0,0x5b,0x0,0x0,0x3,0x0,0x4f,0x1b,0x40,0x31,0x0,0x5,0x4,0x5,0x72,0x0, + 0x6,0x7,0x2,0x7,0x6,0x2,0x70,0x0,0x2,0x3,0x7,0x2,0x3,0x6e,0x0,0x1,0x0,0x1,0x73,0x0,0x4,0x0,0x7,0x6,0x4,0x7,0x64,0x0,0x3,0x0, + 0x0,0x3,0x57,0x0,0x3,0x3,0x0,0x5b,0x0,0x0,0x3,0x0,0x4f,0x59,0x40,0xf,0x40,0x3e,0x3b,0x3a,0x30,0x2e,0x2b,0x2a,0x24,0x1a,0x23,0x12,0x8,0x7, + 0x18,0x2b,0x0,0x6,0x6,0x7,0x15,0x14,0x6,0x23,0x22,0x26,0x35,0x35,0x26,0x27,0x26,0x35,0x34,0x37,0x36,0x33,0x32,0x17,0x16,0x16,0x33,0x32,0x36,0x35, + 0x34,0x26,0x27,0x26,0x26,0x27,0x26,0x26,0x27,0x26,0x26,0x35,0x34,0x36,0x36,0x37,0x35,0x34,0x36,0x33,0x32,0x16,0x15,0x15,0x16,0x17,0x16,0x15,0x14,0x7, + 0x6,0x23,0x22,0x27,0x26,0x23,0x22,0x6,0x15,0x14,0x16,0x17,0x16,0x16,0x17,0x16,0x16,0x17,0x16,0x16,0x15,0x4,0x1c,0x55,0xa7,0x7a,0x21,0x25,0x25,0x21, + 0xb6,0x90,0x26,0xd,0x18,0x1f,0xe,0x11,0x50,0xbb,0x61,0x81,0x84,0x30,0x2d,0x23,0x57,0x4e,0x5b,0x7b,0x34,0x49,0x51,0x5a,0x9f,0x68,0x21,0x25,0x25,0x21, + 0xa5,0x82,0x27,0xc,0x15,0x21,0xf,0x10,0xa1,0xb6,0x6e,0x7b,0x27,0x26,0x21,0x57,0x4c,0x5a,0x79,0x35,0x53,0x5d,0x1,0x2a,0x8a,0x50,0x5,0xeb,0x22,0x1f, + 0x1f,0x22,0xef,0x12,0x52,0x15,0x23,0x14,0x1a,0x2e,0xa,0x2e,0x33,0x5b,0x4d,0x2a,0x35,0x11,0xd,0xf,0x9,0xb,0x17,0x15,0x1e,0x6c,0x57,0x5c,0x85,0x49, + 0x5,0xe3,0x22,0x1f,0x1f,0x22,0xe8,0x11,0x44,0x14,0x25,0x13,0x1a,0x30,0x9,0x56,0x52,0x4c,0x22,0x2c,0xe,0xd,0xe,0x9,0xa,0x15,0x14,0x20,0x77,0x63, + 0x0,0x0,0x0,0x1,0x0,0x5a,0xff,0xea,0x4,0x5a,0x4,0xb9,0x0,0x4f,0x0,0x58,0x40,0x55,0x29,0x1,0x7,0x8,0x1,0x4a,0x0,0x7,0x8,0x5,0x8,0x7, + 0x5,0x70,0x0,0x0,0x2,0xd,0x2,0x0,0xd,0x70,0x9,0x1,0x5,0xa,0x1,0x4,0x3,0x5,0x4,0x63,0xb,0x1,0x3,0xc,0x1,0x2,0x0,0x3,0x2,0x63, + 0x0,0x8,0x8,0x6,0x5b,0x0,0x6,0x6,0x16,0x4b,0x0,0xd,0xd,0x1,0x5b,0x0,0x1,0x1,0x17,0x1,0x4c,0x4d,0x4b,0x49,0x47,0x43,0x41,0x3d,0x3b,0x37, + 0x35,0x24,0x17,0x22,0x24,0x24,0x24,0x22,0x27,0x20,0xe,0x7,0x1d,0x2b,0x24,0x33,0x32,0x17,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x22,0x26,0x27,0x23,0x22, + 0x26,0x35,0x34,0x36,0x33,0x33,0x26,0x35,0x34,0x37,0x23,0x22,0x26,0x35,0x34,0x36,0x33,0x33,0x36,0x36,0x33,0x32,0x16,0x17,0x16,0x15,0x14,0x7,0x6,0x23, + 0x22,0x27,0x26,0x26,0x23,0x22,0x6,0x7,0x21,0x32,0x16,0x15,0x14,0x6,0x23,0x21,0x6,0x15,0x14,0x17,0x21,0x32,0x16,0x15,0x14,0x6,0x23,0x21,0x16,0x16, + 0x33,0x32,0x36,0x37,0x4,0xa,0xe,0x20,0x15,0xd,0x25,0x5a,0xac,0x65,0xb2,0xed,0x2c,0x66,0x22,0x1d,0x1d,0x22,0x50,0x3,0x3,0x50,0x22,0x1d,0x1d,0x22, + 0x67,0x2e,0xec,0xb0,0x65,0xac,0x5a,0x25,0xd,0x15,0x20,0xe,0x14,0x49,0x90,0x53,0x70,0x97,0x24,0x1,0x5e,0x22,0x1d,0x1d,0x22,0xfe,0x87,0x4,0x3,0x1, + 0x7a,0x22,0x1d,0x1d,0x22,0xfe,0xa0,0x23,0x99,0x71,0x53,0x90,0x49,0xde,0x2c,0x1d,0x13,0x22,0x16,0x33,0x2d,0xcf,0xb9,0x21,0x27,0x27,0x21,0x27,0x29,0x2c, + 0x2a,0x21,0x27,0x27,0x21,0xb6,0xcb,0x2d,0x33,0x16,0x22,0x13,0x1d,0x2c,0xb,0x2c,0x29,0x7b,0x72,0x21,0x27,0x27,0x21,0x24,0x32,0x29,0x27,0x21,0x27,0x27, + 0x21,0x75,0x7f,0x29,0x2c,0x0,0x0,0x1,0x0,0x21,0xfe,0xe9,0x4,0x80,0x5,0x25,0x0,0x37,0x0,0x47,0x40,0x44,0x1,0x1,0x1,0x9,0x1d,0x1,0x4,0x6, + 0x2,0x4a,0x0,0x0,0x1,0x2,0x1,0x0,0x2,0x70,0x0,0x5,0x3,0x6,0x3,0x5,0x6,0x70,0x8,0x1,0x2,0x7,0x1,0x3,0x5,0x2,0x3,0x61,0x0,0x6, + 0x0,0x4,0x6,0x4,0x5f,0x0,0x1,0x1,0x9,0x5b,0x0,0x9,0x9,0x10,0x1,0x4c,0x36,0x34,0x24,0x23,0x23,0x16,0x23,0x24,0x23,0x23,0x14,0xa,0x7,0x1d, + 0x2b,0x0,0x15,0x14,0x7,0x6,0x23,0x22,0x27,0x26,0x23,0x22,0x6,0x7,0x7,0x33,0x32,0x16,0x15,0x14,0x6,0x23,0x23,0x3,0x6,0x6,0x23,0x22,0x27,0x26, + 0x35,0x34,0x37,0x36,0x33,0x32,0x17,0x16,0x33,0x32,0x36,0x37,0x13,0x23,0x22,0x26,0x35,0x34,0x36,0x33,0x33,0x37,0x36,0x36,0x33,0x32,0x17,0x4,0x80,0x6, + 0x10,0x29,0xb,0xb,0x5b,0x51,0x4d,0x5e,0xa,0xe,0xea,0x22,0x1d,0x1d,0x22,0xf8,0x43,0x10,0xb0,0x8e,0x72,0x61,0x29,0x6,0x10,0x29,0xb,0xb,0x5b,0x51, + 0x4d,0x5e,0xa,0x41,0xc4,0x22,0x1d,0x1d,0x22,0xd3,0xf,0x10,0xb0,0x8e,0x72,0x61,0x4,0xf9,0x26,0x11,0x15,0x3b,0x4,0x1d,0x5d,0x5e,0x88,0x21,0x27,0x27, + 0x21,0xfd,0x69,0x99,0xa7,0x1f,0xd,0x26,0x11,0x15,0x3b,0x4,0x1d,0x5d,0x5e,0x2,0x8a,0x21,0x27,0x27,0x21,0x95,0x99,0xa7,0x1f,0x0,0x0,0x0,0x0,0x1, + 0x0,0x32,0xff,0xf6,0x4,0x10,0x4,0xa3,0x0,0x2f,0x0,0x37,0x40,0x34,0x0,0x1,0x0,0x2,0x3,0x1,0x2,0x61,0x7,0x1,0x3,0x6,0x1,0x4,0x5,0x3, + 0x4,0x63,0x0,0x0,0x0,0x8,0x59,0x9,0x1,0x8,0x8,0xe,0x4b,0x0,0x5,0x5,0xf,0x5,0x4c,0x0,0x0,0x0,0x2f,0x0,0x2c,0x24,0x23,0x33,0x24,0x21, + 0x24,0x21,0x24,0xa,0x7,0x1c,0x2b,0x0,0x16,0x15,0x14,0x6,0x23,0x21,0x11,0x21,0x32,0x16,0x15,0x14,0x6,0x23,0x21,0x15,0x21,0x32,0x16,0x15,0x14,0x6, + 0x23,0x21,0x15,0x14,0x6,0x23,0x23,0x22,0x26,0x35,0x35,0x23,0x22,0x26,0x35,0x34,0x36,0x33,0x33,0x11,0x34,0x36,0x33,0x33,0x21,0x3,0xf3,0x1d,0x1d,0x22, + 0xfd,0xba,0x1,0xd3,0x22,0x1d,0x1d,0x22,0xfe,0x2d,0x1,0x22,0x22,0x1d,0x1d,0x22,0xfe,0xde,0x21,0x29,0x2,0x29,0x21,0x84,0x22,0x1d,0x1d,0x22,0x84,0x1f, + 0x20,0x57,0x2,0x46,0x4,0xa3,0x21,0x27,0x27,0x21,0xfe,0xad,0x21,0x27,0x27,0x21,0xba,0x21,0x27,0x27,0x21,0xaf,0x23,0x1e,0x1e,0x23,0xaf,0x21,0x27,0x27, + 0x21,0x2,0xe9,0x25,0x1f,0x0,0x0,0x1,0x0,0xdc,0x0,0x0,0x3,0xd2,0x4,0xb9,0x0,0x41,0x0,0x42,0x40,0x3f,0x9,0x1,0x0,0x8,0x1,0x4a,0x0,0x4, + 0x5,0x2,0x5,0x4,0x2,0x70,0x6,0x1,0x2,0x7,0x1,0x1,0x8,0x2,0x1,0x63,0x0,0x5,0x5,0x3,0x5b,0x0,0x3,0x3,0x16,0x4b,0x9,0x1,0x8,0x8, + 0x0,0x59,0x0,0x0,0x0,0xf,0x0,0x4c,0x0,0x0,0x0,0x41,0x0,0x40,0x24,0x27,0x23,0x16,0x26,0x24,0x2a,0x34,0xa,0x7,0x1c,0x2b,0x24,0x16,0x15,0x14, + 0x6,0x23,0x21,0x22,0x26,0x35,0x34,0x36,0x37,0x36,0x36,0x35,0x34,0x27,0x23,0x22,0x26,0x35,0x34,0x36,0x33,0x33,0x26,0x26,0x35,0x34,0x36,0x36,0x33,0x32, + 0x17,0x16,0x15,0x14,0x7,0x6,0x23,0x22,0x27,0x26,0x23,0x22,0x6,0x6,0x15,0x14,0x16,0x17,0x17,0x21,0x32,0x16,0x15,0x14,0x6,0x23,0x23,0x16,0x15,0x14, + 0x7,0x21,0x3,0xb5,0x1d,0x1d,0x22,0xfd,0xa1,0x16,0x20,0x9,0xa,0x56,0x46,0x6,0x8c,0x22,0x1d,0x1d,0x22,0x52,0x27,0x24,0x54,0x9a,0x64,0x9e,0x8b,0x22, + 0x10,0x18,0x1d,0xf,0x13,0x6b,0x7a,0x37,0x54,0x2c,0x1e,0x1e,0x1d,0x1,0x1a,0x22,0x1d,0x1d,0x22,0xee,0x4,0x7d,0x1,0xcb,0x90,0x21,0x27,0x27,0x21,0x3d, + 0x22,0xd,0x14,0xa,0x55,0x8e,0x50,0x1f,0x1c,0x21,0x27,0x27,0x21,0x4d,0x63,0x3c,0x5c,0x94,0x55,0x57,0x15,0x1e,0x15,0x1d,0x2b,0xc,0x47,0x2f,0x51,0x31, + 0x2a,0x4f,0x39,0x3a,0x21,0x27,0x27,0x21,0x18,0x22,0xa0,0x8e,0x0,0x0,0x0,0x3,0xff,0xe2,0xff,0xea,0x4,0xea,0x4,0xad,0x0,0x3f,0x0,0x42,0x0,0x45, + 0x0,0x7d,0x40,0x10,0x3b,0x1d,0x2,0x7,0x5,0xd,0x1,0x0,0x4,0x45,0x42,0x2,0x1,0x0,0x3,0x4a,0x4b,0xb0,0x19,0x50,0x58,0x40,0x21,0xd,0xa,0x8, + 0x6,0x4,0x4,0xc,0xb,0x3,0x3,0x0,0x1,0x4,0x0,0x63,0x9,0x1,0x5,0x5,0xe,0x4b,0x0,0x7,0x7,0x11,0x4b,0x2,0x1,0x1,0x1,0x17,0x1,0x4c, + 0x1b,0x40,0x24,0x0,0x7,0x5,0x4,0x5,0x7,0x4,0x70,0xd,0xa,0x8,0x6,0x4,0x4,0xc,0xb,0x3,0x3,0x0,0x1,0x4,0x0,0x63,0x9,0x1,0x5,0x5, + 0xe,0x4b,0x2,0x1,0x1,0x1,0x17,0x1,0x4c,0x59,0x40,0x18,0x0,0x0,0x44,0x43,0x41,0x40,0x0,0x3f,0x0,0x3e,0x38,0x35,0x13,0x23,0x13,0x36,0x24,0x23, + 0x24,0x23,0x24,0xe,0x7,0x1d,0x2b,0x0,0x16,0x15,0x14,0x6,0x23,0x23,0x3,0x6,0x6,0x23,0x22,0x27,0x3,0x3,0x6,0x23,0x22,0x26,0x27,0x3,0x23,0x22, + 0x26,0x35,0x34,0x36,0x33,0x33,0x3,0x26,0x35,0x34,0x36,0x37,0x36,0x33,0x32,0x16,0x17,0x13,0x33,0x37,0x36,0x36,0x33,0x32,0x16,0x17,0x17,0x33,0x13,0x36, + 0x36,0x33,0x32,0x17,0x16,0x16,0x15,0x14,0x7,0x3,0x33,0x5,0x23,0x13,0x1,0x23,0x13,0x4,0xcd,0x1d,0x1d,0x22,0x72,0x4e,0x5,0x29,0x28,0x4d,0x17,0xcf, + 0xcf,0x17,0x4d,0x28,0x29,0x5,0x4e,0x6a,0x22,0x1d,0x1d,0x22,0x56,0x33,0x1,0x20,0x24,0x7,0xd,0x1e,0x1c,0x4,0x31,0xee,0x20,0x7,0x23,0x20,0x20,0x23, + 0x7,0x20,0xee,0x31,0x4,0x1c,0x1e,0xd,0x7,0x24,0x20,0x1,0x33,0x5e,0xfd,0x1a,0xa9,0x2d,0x2,0x5f,0xa9,0x7c,0x2,0xf2,0x21,0x27,0x27,0x21,0xfd,0xc9, + 0x24,0x1d,0x41,0x2,0x4a,0xfd,0xb6,0x41,0x1d,0x24,0x2,0x37,0x21,0x27,0x27,0x21,0x1,0x70,0x6,0xc,0x19,0x1a,0x5,0x1,0x1b,0x1f,0xfe,0x7f,0x5b,0x14, + 0x14,0x14,0x14,0x5b,0x1,0x81,0x1f,0x1b,0x1,0x5,0x1a,0x19,0xc,0x6,0xfe,0x90,0x90,0xfe,0xa0,0x1,0x60,0xfe,0xa0,0x0,0x0,0x1,0x0,0x7d,0xff,0xf6, + 0x4,0x4f,0x4,0xb1,0x0,0x42,0x0,0x3e,0x40,0x3b,0x3d,0x1,0x1,0x0,0x29,0xe,0x2,0x2,0x1,0x2,0x4a,0x8,0x1,0x0,0x7,0x1,0x1,0x2,0x0,0x1, + 0x62,0x6,0x1,0x2,0x5,0x1,0x3,0x4,0x2,0x3,0x61,0xa,0x1,0x9,0x9,0x16,0x4b,0x0,0x4,0x4,0xf,0x4,0x4c,0x40,0x3f,0x3b,0x39,0x24,0x22,0x24, + 0x23,0x33,0x24,0x22,0x24,0x25,0xb,0x7,0x1d,0x2b,0x0,0x16,0x15,0x14,0x7,0x3,0x33,0x32,0x16,0x15,0x14,0x6,0x23,0x23,0x7,0x15,0x21,0x32,0x16,0x15, + 0x14,0x6,0x23,0x21,0x15,0x14,0x6,0x23,0x23,0x22,0x26,0x35,0x35,0x21,0x22,0x26,0x35,0x34,0x36,0x33,0x21,0x35,0x27,0x23,0x22,0x26,0x35,0x34,0x36,0x33, + 0x33,0x3,0x26,0x35,0x34,0x36,0x37,0x36,0x33,0x32,0x17,0x1,0x1,0x36,0x33,0x32,0x17,0x4,0x38,0x17,0xf,0xe7,0x9e,0x22,0x1d,0x1d,0x22,0xfc,0x4a,0x1, + 0x32,0x22,0x1d,0x1d,0x22,0xfe,0xce,0x21,0x29,0x2,0x29,0x21,0xfe,0xce,0x22,0x1d,0x1d,0x22,0x1,0x32,0x4a,0xfc,0x22,0x1d,0x1d,0x22,0x9e,0xe7,0xf,0x17, + 0x19,0x21,0x13,0x1e,0x12,0x1,0x55,0x1,0x55,0x14,0x1c,0x13,0x21,0x4,0x98,0x19,0x10,0x10,0x17,0xfe,0xa0,0x21,0x27,0x27,0x21,0x71,0x35,0x21,0x27,0x27, + 0x21,0xeb,0x23,0x1e,0x1e,0x23,0xeb,0x21,0x27,0x27,0x21,0x35,0x71,0x21,0x27,0x27,0x21,0x1,0x60,0x17,0x10,0x10,0x19,0xb,0xe,0x1d,0xfd,0xf2,0x2,0xe, + 0x1d,0xe,0xff,0xff,0x0,0x96,0x0,0xe5,0x4,0x36,0x3,0xa7,0x0,0x27,0x1,0x59,0x0,0x0,0x0,0xc8,0x1,0x7,0x1,0x59,0x0,0x0,0xff,0x38,0x0,0x11, + 0xb1,0x0,0x1,0xb0,0xc8,0xb0,0x33,0x2b,0xb1,0x1,0x1,0xb8,0xff,0x38,0xb0,0x33,0x2b,0x0,0x0,0x0,0x0,0x1,0x0,0x96,0x1,0xad,0x4,0x36,0x2,0xdf, + 0x0,0x25,0x0,0x4c,0xb1,0x6,0x64,0x44,0x40,0x41,0xd,0x1,0x0,0x1,0x20,0x1,0x4,0x3,0x2,0x4a,0x0,0x1,0x5,0x0,0x5,0x1,0x0,0x70,0x0,0x4, + 0x3,0x2,0x3,0x4,0x2,0x70,0x0,0x0,0x3,0x2,0x0,0x57,0x6,0x1,0x5,0x0,0x3,0x4,0x5,0x3,0x63,0x0,0x0,0x0,0x2,0x5b,0x0,0x2,0x0,0x2, + 0x4f,0x0,0x0,0x0,0x25,0x0,0x24,0x22,0x24,0x27,0x22,0x24,0x7,0x7,0x19,0x2b,0xb1,0x6,0x0,0x44,0x0,0x16,0x17,0x16,0x16,0x33,0x32,0x37,0x36,0x33, + 0x32,0x17,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x22,0x26,0x27,0x26,0x26,0x23,0x22,0x7,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x1,0xdc, + 0x65,0x40,0x3a,0x48,0x27,0x55,0x46,0x10,0x16,0x16,0x15,0x20,0x7,0x2f,0x88,0x4e,0x3a,0x64,0x42,0x38,0x4a,0x26,0x55,0x46,0x10,0x16,0x16,0x15,0x20,0x7, + 0x2f,0x88,0x4e,0x2,0xdf,0x2a,0x28,0x22,0x20,0x54,0x13,0x11,0x18,0x1c,0xe,0xb,0x4c,0x5b,0x2a,0x28,0x21,0x21,0x54,0x13,0x11,0x18,0x1c,0xe,0xb,0x4c, + 0x5b,0x0,0x0,0x3,0x0,0xaa,0x0,0x6e,0x4,0x22,0x4,0x1f,0x0,0xd,0x0,0x1b,0x0,0x29,0x0,0x41,0x40,0x3e,0x6,0x1,0x1,0x0,0x0,0x3,0x1,0x0, + 0x63,0x7,0x1,0x3,0x0,0x2,0x5,0x3,0x2,0x61,0x8,0x1,0x5,0x4,0x4,0x5,0x57,0x8,0x1,0x5,0x5,0x4,0x5b,0x0,0x4,0x5,0x4,0x4f,0x1c,0x1c, + 0xe,0xe,0x0,0x0,0x1c,0x29,0x1c,0x28,0x23,0x21,0xe,0x1b,0xe,0x19,0x15,0x12,0x0,0xd,0x0,0xc,0x25,0x9,0x7,0x15,0x2b,0x0,0x16,0x15,0x15,0x14, + 0x6,0x23,0x22,0x26,0x35,0x35,0x34,0x36,0x33,0x0,0x16,0x15,0x14,0x6,0x23,0x21,0x22,0x26,0x35,0x34,0x36,0x33,0x21,0x0,0x16,0x15,0x15,0x14,0x6,0x23, + 0x22,0x26,0x35,0x35,0x34,0x36,0x33,0x2,0x9e,0x31,0x31,0x38,0x38,0x31,0x31,0x38,0x1,0x9f,0x1d,0x1d,0x22,0xfd,0x6,0x22,0x1d,0x1d,0x22,0x2,0xfa,0xfe, + 0xbb,0x31,0x31,0x38,0x38,0x31,0x31,0x38,0x4,0x1f,0x1d,0x22,0x68,0x22,0x1d,0x1d,0x22,0x68,0x22,0x1d,0xfe,0x6f,0x21,0x27,0x27,0x21,0x21,0x27,0x27,0x21, + 0xfe,0xc6,0x1d,0x22,0x68,0x22,0x1d,0x1d,0x22,0x68,0x22,0x1d,0x0,0x0,0xff,0xff,0x0,0xaa,0x1,0x36,0x4,0x22,0x3,0x56,0x0,0x27,0x1,0x63,0x0,0x0, + 0x0,0xc8,0x1,0x7,0x1,0x63,0x0,0x0,0xff,0x38,0x0,0x11,0xb1,0x0,0x1,0xb0,0xc8,0xb0,0x33,0x2b,0xb1,0x1,0x1,0xb8,0xff,0x38,0xb0,0x33,0x2b,0x0, + 0x0,0x0,0x0,0x1,0x0,0xaf,0x0,0x77,0x4,0x4f,0x4,0x17,0x0,0x19,0x0,0x1f,0x40,0x1c,0x18,0x1,0x2,0x1,0x0,0x1,0x4a,0x0,0x0,0x1,0x1,0x0, + 0x57,0x0,0x0,0x0,0x1,0x5b,0x0,0x1,0x0,0x1,0x4f,0x2b,0x14,0x2,0x7,0x16,0x2b,0x12,0x35,0x34,0x37,0x36,0x33,0x32,0x17,0x1,0x16,0x16,0x15,0x14, + 0x6,0x7,0x1,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x1,0x1,0xaf,0xc,0x17,0x23,0x10,0x12,0x3,0x9,0x19,0x16,0x16,0x19,0xfc,0xf7,0x12,0x10,0x23, + 0x17,0xc,0x29,0x2,0xa0,0xfd,0x60,0x3,0x9d,0x21,0x12,0x18,0x2f,0x9,0xfe,0x83,0xd,0x20,0x1d,0x1d,0x20,0xd,0xfe,0x83,0x9,0x2f,0x18,0x12,0x21,0x14, + 0x1,0x42,0x1,0x42,0x0,0x0,0x0,0x2,0x0,0xaa,0x0,0x6e,0x4,0x22,0x4,0x6b,0x0,0x17,0x0,0x25,0x0,0x8,0xb5,0x1c,0x18,0x14,0x6,0x2,0x30,0x2b, + 0x1,0x16,0x15,0x14,0x7,0x1,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x25,0x25,0x26,0x35,0x34,0x37,0x36,0x33,0x32,0x17,0x0,0x16,0x15,0x14,0x6,0x23, + 0x21,0x22,0x26,0x35,0x34,0x36,0x33,0x21,0x3,0xee,0x34,0x34,0xfd,0x1e,0x11,0xe,0x26,0x14,0x8,0x30,0x2,0x65,0xfd,0x9b,0x30,0x8,0x14,0x26,0xf,0x10, + 0x2,0xf9,0x1d,0x1d,0x22,0xfd,0x6,0x22,0x1d,0x1d,0x22,0x2,0xfa,0x3,0x43,0x14,0x3c,0x3c,0x14,0xfe,0xde,0x6,0x34,0x13,0x12,0x26,0x11,0xe8,0xe8,0x11, + 0x26,0x12,0x13,0x34,0x6,0xfc,0x99,0x21,0x27,0x27,0x21,0x21,0x27,0x27,0x21,0x0,0x0,0x0,0x0,0x3,0x0,0x1c,0x1,0x25,0x4,0xb0,0x3,0x67,0x0,0x1b, + 0x0,0x27,0x0,0x33,0x0,0xa,0xb7,0x2c,0x28,0x20,0x1c,0x6,0x0,0x3,0x30,0x2b,0x0,0x16,0x16,0x15,0x14,0x6,0x6,0x23,0x22,0x26,0x27,0x6,0x6,0x23, + 0x22,0x26,0x26,0x35,0x34,0x36,0x36,0x33,0x32,0x16,0x17,0x36,0x36,0x33,0x4,0x6,0x15,0x14,0x16,0x33,0x32,0x36,0x37,0x26,0x26,0x23,0x20,0x6,0x7,0x16, + 0x16,0x33,0x32,0x36,0x35,0x34,0x26,0x23,0x3,0xe7,0x80,0x49,0x49,0x80,0x4f,0x67,0x8a,0x41,0x41,0x8a,0x67,0x4f,0x80,0x49,0x49,0x80,0x4f,0x67,0x8b,0x40, + 0x40,0x8b,0x67,0xfd,0x5d,0x4f,0x4f,0x42,0x49,0x6a,0x36,0x37,0x69,0x49,0x2,0x15,0x6b,0x35,0x35,0x6b,0x49,0x42,0x4f,0x4f,0x42,0x3,0x67,0x4c,0x84,0x51, + 0x51,0x84,0x4c,0x63,0x59,0x59,0x63,0x4c,0x84,0x51,0x51,0x84,0x4c,0x63,0x5a,0x5a,0x63,0x86,0x58,0x43,0x43,0x58,0x50,0x4b,0x4b,0x50,0x51,0x4a,0x4b,0x50, + 0x58,0x43,0x43,0x58,0x0,0x0,0x0,0x1,0x0,0x5,0xfe,0x66,0x4,0xc7,0x5,0xed,0x0,0x2b,0x0,0x6,0xb3,0x14,0x0,0x1,0x30,0x2b,0x0,0x16,0x17,0x16, + 0x16,0x15,0x14,0x7,0x6,0x23,0x22,0x27,0x26,0x23,0x22,0x6,0x15,0x11,0x14,0x6,0x6,0x23,0x22,0x26,0x27,0x26,0x26,0x35,0x34,0x37,0x36,0x33,0x32,0x17, + 0x16,0x33,0x32,0x36,0x35,0x11,0x34,0x36,0x36,0x33,0x3,0xd3,0x8f,0x3f,0x14,0x12,0x6,0x12,0x22,0x4,0xe,0x84,0x6b,0x69,0x72,0x59,0xa5,0x71,0x49,0x8f, + 0x3f,0x14,0x12,0x6,0x12,0x22,0x4,0xe,0x84,0x6b,0x69,0x72,0x59,0xa5,0x71,0x5,0xed,0x1f,0x1b,0x8,0x18,0x12,0xf,0x14,0x3a,0x4,0x33,0x8f,0x83,0xfb, + 0xb5,0x7c,0xb8,0x64,0x1f,0x1b,0x8,0x18,0x12,0xf,0x14,0x3a,0x4,0x33,0x8f,0x83,0x4,0x4b,0x7c,0xb8,0x64,0x0,0x0,0x1,0x0,0x7d,0x0,0x77,0x4,0x1d, + 0x4,0x17,0x0,0x19,0x0,0x1f,0x40,0x1c,0x18,0x1,0x2,0x0,0x1,0x1,0x4a,0x0,0x1,0x0,0x0,0x1,0x57,0x0,0x1,0x1,0x0,0x5b,0x0,0x0,0x1,0x0, + 0x4f,0x2b,0x14,0x2,0x7,0x16,0x2b,0x24,0x15,0x14,0x7,0x6,0x23,0x22,0x27,0x1,0x26,0x26,0x35,0x34,0x36,0x37,0x1,0x36,0x33,0x32,0x17,0x16,0x15,0x14, + 0x7,0x1,0x1,0x4,0x1d,0xc,0x17,0x23,0x10,0x12,0xfc,0xf7,0x19,0x16,0x16,0x19,0x3,0x9,0x12,0x10,0x23,0x17,0xc,0x29,0xfd,0x60,0x2,0xa0,0xf1,0x21, + 0x12,0x18,0x2f,0x9,0x1,0x7d,0xd,0x20,0x1d,0x1d,0x20,0xd,0x1,0x7d,0x9,0x2f,0x18,0x12,0x21,0x14,0xfe,0xbe,0xfe,0xbe,0x0,0x0,0x0,0x2,0x0,0xaa, + 0x0,0x6e,0x4,0x22,0x4,0x6b,0x0,0x17,0x0,0x25,0x0,0x8,0xb5,0x1c,0x18,0xe,0x0,0x2,0x30,0x2b,0x0,0x33,0x32,0x17,0x16,0x15,0x14,0x7,0x5,0x5, + 0x16,0x15,0x14,0x7,0x6,0x23,0x22,0x27,0x1,0x26,0x35,0x34,0x37,0x1,0x12,0x16,0x15,0x14,0x6,0x23,0x21,0x22,0x26,0x35,0x34,0x36,0x33,0x21,0x3,0xd1, + 0xe,0x26,0x14,0x8,0x30,0xfd,0x9b,0x2,0x65,0x30,0x8,0x14,0x26,0xe,0x11,0xfd,0x1e,0x34,0x34,0x2,0xe2,0x45,0x1d,0x1d,0x22,0xfd,0x6,0x22,0x1d,0x1d, + 0x22,0x2,0xfa,0x4,0x6b,0x34,0x13,0x12,0x26,0x11,0xe8,0xe8,0x11,0x26,0x12,0x13,0x34,0x6,0x1,0x22,0x14,0x3c,0x3c,0x14,0x1,0x22,0xfc,0x99,0x21,0x27, + 0x27,0x21,0x21,0x27,0x27,0x21,0x0,0x0,0x0,0x1,0x0,0x8c,0x1,0x3e,0x4,0x36,0x3,0x51,0x0,0x12,0x0,0x25,0x40,0x22,0x0,0x0,0x1,0x0,0x73,0x3, + 0x1,0x2,0x1,0x1,0x2,0x55,0x3,0x1,0x2,0x2,0x1,0x59,0x0,0x1,0x2,0x1,0x4d,0x0,0x0,0x0,0x12,0x0,0x10,0x23,0x25,0x4,0x7,0x16,0x2b,0x0, + 0x16,0x15,0x11,0x14,0x6,0x23,0x22,0x26,0x35,0x11,0x21,0x22,0x26,0x35,0x34,0x36,0x33,0x21,0x4,0x19,0x1d,0x22,0x29,0x29,0x22,0xfd,0x2b,0x22,0x1d,0x1d, + 0x22,0x3,0x2c,0x3,0x51,0x1f,0x25,0xfe,0x72,0x23,0x1e,0x1e,0x23,0x1,0x42,0x21,0x27,0x27,0x21,0x0,0x0,0x0,0x0,0x1,0x0,0xaa,0x1,0xfe,0x4,0x22, + 0x2,0x8e,0x0,0xd,0x0,0x6,0xb3,0x4,0x0,0x1,0x30,0x2b,0x0,0x16,0x15,0x14,0x6,0x23,0x21,0x22,0x26,0x35,0x34,0x36,0x33,0x21,0x4,0x5,0x1d,0x1d, + 0x22,0xfd,0x6,0x22,0x1d,0x1d,0x22,0x2,0xfa,0x2,0x8e,0x21,0x27,0x27,0x21,0x21,0x27,0x27,0x21,0x0,0x0,0x0,0x0,0x1,0x0,0xe7,0x0,0xc7,0x3,0xe5, + 0x3,0xc5,0x0,0x27,0x0,0x20,0x40,0x1d,0x27,0x1d,0x13,0x9,0x4,0x0,0x2,0x1,0x4a,0x1,0x1,0x0,0x0,0x2,0x5b,0x3,0x1,0x2,0x2,0x19,0x0,0x4c, + 0x24,0x2c,0x24,0x25,0x4,0x7,0x18,0x2b,0x1,0x16,0x15,0x14,0x7,0x6,0x23,0x22,0x27,0x1,0x1,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x1,0x1,0x26, + 0x35,0x34,0x37,0x36,0x33,0x32,0x17,0x1,0x1,0x36,0x33,0x32,0x17,0x16,0x15,0x14,0x7,0x1,0x3,0xce,0x17,0x1d,0x1d,0x17,0x15,0x17,0xfe,0xfe,0xfe,0xfe, + 0x17,0x15,0x17,0x1d,0x1d,0x17,0x1,0x2,0xfe,0xfe,0x17,0x1d,0x1d,0x17,0x15,0x17,0x1,0x2,0x1,0x2,0x17,0x15,0x17,0x1d,0x1d,0x17,0xfe,0xfe,0x1,0x44, + 0x17,0x15,0x17,0x1d,0x1d,0x17,0x1,0x2,0xfe,0xfe,0x17,0x1d,0x1d,0x17,0x15,0x17,0x1,0x2,0x1,0x2,0x17,0x15,0x17,0x1d,0x1d,0x17,0xfe,0xfe,0x1,0x2, + 0x17,0x1d,0x1d,0x17,0x15,0x17,0xfe,0xfe,0x0,0x0,0x0,0x1,0x0,0xaa,0x0,0x44,0x4,0x22,0x4,0x48,0x0,0x37,0x0,0x6,0xb3,0x25,0x9,0x1,0x30,0x2b, + 0x0,0x16,0x15,0x14,0x6,0x23,0x21,0x7,0x6,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x37,0x23,0x22,0x26,0x35,0x34,0x36,0x33,0x33,0x13,0x21,0x22,0x26, + 0x35,0x34,0x36,0x33,0x21,0x37,0x36,0x36,0x33,0x32,0x17,0x16,0x15,0x14,0x7,0x7,0x33,0x32,0x16,0x15,0x14,0x6,0x23,0x23,0x3,0x21,0x4,0x5,0x1d,0x1d, + 0x22,0xfe,0x3b,0x7a,0xb,0x16,0xe,0x13,0x19,0x27,0xe,0x51,0x98,0x22,0x1d,0x1d,0x22,0xe2,0x98,0xfe,0x86,0x22,0x1d,0x1d,0x22,0x1,0xc4,0x7b,0xb,0x16, + 0xe,0x13,0x19,0x27,0xe,0x51,0x98,0x22,0x1d,0x1d,0x22,0xe3,0x97,0x1,0x7a,0x1,0xc6,0x21,0x27,0x27,0x21,0xcf,0x12,0x11,0xf,0x16,0x1c,0x11,0x17,0x89, + 0x21,0x27,0x27,0x21,0x1,0x0,0x21,0x27,0x27,0x21,0xcf,0x12,0x11,0xf,0x16,0x1c,0x11,0x17,0x89,0x21,0x27,0x27,0x21,0xff,0x0,0x0,0x0,0x0,0x0,0x2, + 0x0,0xa7,0xff,0xe9,0x4,0x19,0x4,0xb9,0x0,0x26,0x0,0x33,0x0,0x8,0xb5,0x2c,0x27,0x8,0x0,0x2,0x30,0x2b,0x0,0x16,0x16,0x15,0x14,0x2,0x7,0x6, + 0x6,0x23,0x22,0x26,0x26,0x35,0x34,0x36,0x36,0x33,0x32,0x16,0x17,0x36,0x35,0x34,0x26,0x23,0x22,0x7,0x6,0x23,0x22,0x26,0x27,0x26,0x35,0x34,0x37,0x36, + 0x33,0x2,0x6,0x6,0x15,0x14,0x16,0x33,0x36,0x36,0x37,0x26,0x26,0x23,0x2,0xf3,0xb7,0x6f,0x3c,0x36,0x43,0xc9,0x8d,0x6b,0xa3,0x59,0x67,0xb4,0x70,0x71, + 0xa8,0x2a,0x17,0x88,0x83,0x40,0x53,0x5,0xa,0x10,0x1a,0xc,0x9,0x1f,0x5b,0x6a,0x93,0x72,0x3e,0x70,0x5c,0x6d,0x97,0x31,0x13,0x89,0x67,0x4,0xb9,0x65, + 0xd2,0x9c,0x70,0xfe,0xf2,0x6f,0x8a,0x86,0x5c,0xa3,0x66,0x75,0xb5,0x63,0x6b,0x62,0x6e,0x58,0x9c,0xae,0x17,0x2,0x19,0x1e,0x17,0x10,0x21,0xe,0x27,0xfd, + 0x8e,0x3e,0x71,0x4b,0x5e,0x71,0x1,0x6f,0x73,0x65,0x81,0x0,0x0,0x5,0x0,0x32,0xff,0xea,0x4,0x9a,0x4,0xb9,0x0,0xf,0x0,0x21,0x0,0x31,0x0,0x41, + 0x0,0x51,0x0,0x62,0x40,0x5f,0x0,0x2,0x1,0x5,0x1,0x2,0x5,0x70,0x0,0x3,0x8,0x6,0x8,0x3,0x6,0x70,0x0,0x4,0x0,0x0,0x7,0x4,0x0,0x63, + 0xc,0x1,0x7,0xd,0x1,0x9,0x8,0x7,0x9,0x63,0xb,0x1,0x5,0x5,0x1,0x5b,0xa,0x1,0x1,0x1,0x16,0x4b,0x0,0x8,0x8,0x6,0x5b,0x0,0x6,0x6, + 0x17,0x6,0x4c,0x42,0x42,0x32,0x32,0x22,0x22,0x0,0x0,0x42,0x51,0x42,0x50,0x4a,0x48,0x32,0x41,0x32,0x40,0x3a,0x38,0x22,0x31,0x22,0x30,0x2a,0x28,0x1b, + 0x19,0x12,0x10,0x0,0xf,0x0,0xe,0x26,0xe,0x7,0x15,0x2b,0x0,0x16,0x16,0x15,0x14,0x6,0x6,0x23,0x22,0x26,0x26,0x35,0x34,0x36,0x36,0x33,0x4,0x33, + 0x32,0x17,0x16,0x15,0x14,0x7,0x1,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x1,0x24,0x6,0x6,0x15,0x14,0x16,0x16,0x33,0x32,0x36,0x36,0x35,0x34,0x26, + 0x26,0x23,0x0,0x16,0x16,0x15,0x14,0x6,0x6,0x23,0x22,0x26,0x26,0x35,0x34,0x36,0x36,0x33,0xe,0x2,0x15,0x14,0x16,0x16,0x33,0x32,0x36,0x36,0x35,0x34, + 0x26,0x26,0x23,0x1,0x8e,0x7e,0x4a,0x4a,0x7e,0x4a,0x4a,0x7e,0x4a,0x4a,0x7e,0x4a,0x2,0xbd,0x17,0x1a,0x18,0x19,0x18,0xfc,0x96,0x16,0x17,0x1a,0x18,0x19, + 0x18,0x3,0x6a,0xfd,0x32,0x42,0x27,0x27,0x42,0x27,0x27,0x42,0x27,0x27,0x42,0x27,0x2,0x8e,0x7e,0x4a,0x4a,0x7e,0x4a,0x4a,0x7e,0x4a,0x4a,0x7e,0x4a,0x27, + 0x42,0x27,0x27,0x42,0x27,0x27,0x42,0x27,0x27,0x42,0x27,0x4,0xb9,0x4a,0x7e,0x4a,0x4a,0x7e,0x4a,0x4a,0x7e,0x4a,0x4a,0x7e,0x4a,0x80,0x1b,0x1b,0x16,0x17, + 0x17,0xfc,0xc2,0x16,0x1b,0x1b,0x16,0x18,0x16,0x3,0x3e,0x14,0x27,0x42,0x27,0x27,0x42,0x27,0x27,0x42,0x27,0x27,0x42,0x27,0xfd,0xd7,0x4a,0x7e,0x4a,0x4a, + 0x7e,0x4a,0x4a,0x7e,0x4a,0x4a,0x7e,0x4a,0x82,0x27,0x42,0x27,0x27,0x42,0x27,0x27,0x42,0x27,0x27,0x42,0x27,0x0,0x0,0x6,0x0,0x32,0xff,0xea,0x6,0x3c, + 0x4,0xb9,0x0,0xf,0x0,0x21,0x0,0x31,0x0,0x4d,0x0,0x5d,0x0,0x6d,0x0,0x7d,0x40,0x7a,0x4a,0x1,0xb,0x8,0x3c,0x1,0x6,0x3,0x2,0x4a,0x0,0x2, + 0x1,0x5,0x1,0x2,0x5,0x70,0x0,0x3,0xa,0x6,0xa,0x3,0x6,0x70,0x0,0x4,0x0,0x0,0x8,0x4,0x0,0x63,0x10,0x9,0x2,0x8,0x12,0xd,0x11,0x3, + 0xb,0xa,0x8,0xb,0x63,0xf,0x1,0x5,0x5,0x1,0x5b,0xe,0x1,0x1,0x1,0x16,0x4b,0xc,0x1,0xa,0xa,0x6,0x5b,0x7,0x1,0x6,0x6,0x17,0x6,0x4c, + 0x5e,0x5e,0x4e,0x4e,0x32,0x32,0x22,0x22,0x0,0x0,0x5e,0x6d,0x5e,0x6c,0x66,0x64,0x4e,0x5d,0x4e,0x5c,0x56,0x54,0x32,0x4d,0x32,0x4c,0x48,0x46,0x40,0x3e, + 0x3a,0x38,0x22,0x31,0x22,0x30,0x2a,0x28,0x1b,0x19,0x12,0x10,0x0,0xf,0x0,0xe,0x26,0x13,0x7,0x15,0x2b,0x0,0x16,0x16,0x15,0x14,0x6,0x6,0x23,0x22, + 0x26,0x26,0x35,0x34,0x36,0x36,0x33,0x4,0x33,0x32,0x17,0x16,0x15,0x14,0x7,0x1,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x1,0x24,0x6,0x6,0x15,0x14, + 0x16,0x16,0x33,0x32,0x36,0x36,0x35,0x34,0x26,0x26,0x23,0x0,0x16,0x16,0x15,0x14,0x6,0x6,0x23,0x22,0x26,0x27,0x6,0x6,0x23,0x22,0x26,0x26,0x35,0x34, + 0x36,0x36,0x33,0x32,0x16,0x17,0x36,0x36,0x33,0x4,0x6,0x6,0x15,0x14,0x16,0x16,0x33,0x32,0x36,0x36,0x35,0x34,0x26,0x26,0x23,0x20,0x6,0x6,0x15,0x14, + 0x16,0x16,0x33,0x32,0x36,0x36,0x35,0x34,0x26,0x26,0x23,0x1,0x8e,0x7e,0x4a,0x4a,0x7e,0x4a,0x4a,0x7e,0x4a,0x4a,0x7e,0x4a,0x2,0xbd,0x17,0x1a,0x18,0x19, + 0x18,0xfc,0x96,0x16,0x17,0x1a,0x18,0x19,0x18,0x3,0x6a,0xfd,0x32,0x42,0x27,0x27,0x42,0x27,0x27,0x42,0x27,0x27,0x42,0x27,0x4,0x30,0x7e,0x4a,0x4a,0x7e, + 0x4a,0x40,0x6d,0x24,0x25,0x6c,0x40,0x4a,0x7e,0x4a,0x4a,0x7e,0x4a,0x40,0x6c,0x25,0x24,0x6d,0x40,0xfe,0x37,0x42,0x27,0x27,0x42,0x27,0x27,0x42,0x27,0x27, + 0x42,0x27,0x1,0x7b,0x42,0x27,0x27,0x42,0x27,0x27,0x42,0x27,0x27,0x42,0x27,0x4,0xb9,0x4a,0x7e,0x4a,0x4a,0x7e,0x4a,0x4a,0x7e,0x4a,0x4a,0x7e,0x4a,0x80, + 0x1b,0x1b,0x16,0x17,0x17,0xfc,0xc2,0x16,0x1b,0x1b,0x16,0x18,0x16,0x3,0x3e,0x14,0x27,0x42,0x27,0x27,0x42,0x27,0x27,0x42,0x27,0x27,0x42,0x27,0xfd,0xd7, + 0x4a,0x7e,0x4a,0x4a,0x7e,0x4a,0x38,0x30,0x31,0x37,0x4a,0x7e,0x4a,0x4a,0x7e,0x4a,0x38,0x31,0x31,0x38,0x82,0x27,0x42,0x27,0x27,0x42,0x27,0x27,0x42,0x27, + 0x27,0x42,0x27,0x27,0x42,0x27,0x27,0x42,0x27,0x27,0x42,0x27,0x27,0x42,0x27,0x0,0x0,0x1,0x0,0xaa,0x0,0x8a,0x4,0x22,0x4,0x2,0x0,0x1f,0x0,0x2c, + 0x40,0x29,0x0,0x4,0x3,0x1,0x4,0x57,0x6,0x5,0x2,0x3,0x2,0x1,0x0,0x1,0x3,0x0,0x61,0x0,0x4,0x4,0x1,0x5b,0x0,0x1,0x4,0x1,0x4f,0x0, + 0x0,0x0,0x1f,0x0,0x1e,0x23,0x24,0x23,0x23,0x24,0x7,0x7,0x19,0x2b,0x0,0x16,0x15,0x14,0x6,0x23,0x21,0x11,0x14,0x6,0x23,0x22,0x26,0x35,0x11,0x21, + 0x22,0x26,0x35,0x34,0x36,0x33,0x21,0x11,0x34,0x36,0x33,0x32,0x16,0x15,0x11,0x21,0x4,0x5,0x1d,0x1d,0x22,0xfe,0xcb,0x21,0x27,0x27,0x21,0xfe,0xcb,0x22, + 0x1d,0x1d,0x22,0x1,0x35,0x21,0x27,0x27,0x21,0x1,0x35,0x2,0x8e,0x21,0x27,0x27,0x21,0xfe,0xcb,0x22,0x1d,0x1d,0x22,0x1,0x35,0x21,0x27,0x27,0x21,0x1, + 0x35,0x22,0x1d,0x1d,0x22,0xfe,0xcb,0x0,0x0,0x0,0x0,0x2,0x0,0xaa,0x0,0x6e,0x4,0x22,0x4,0x66,0x0,0x1f,0x0,0x2d,0x0,0x3e,0x40,0x3b,0x2,0x1, + 0x0,0x8,0x5,0x2,0x3,0x4,0x0,0x3,0x61,0x0,0x1,0x0,0x4,0x7,0x1,0x4,0x63,0x9,0x1,0x7,0x6,0x6,0x7,0x55,0x9,0x1,0x7,0x7,0x6,0x59, + 0x0,0x6,0x7,0x6,0x4d,0x20,0x20,0x0,0x0,0x20,0x2d,0x20,0x2b,0x27,0x24,0x0,0x1f,0x0,0x1e,0x23,0x24,0x23,0x23,0x24,0xa,0x7,0x19,0x2b,0x12,0x26, + 0x35,0x34,0x36,0x33,0x21,0x35,0x34,0x36,0x33,0x32,0x16,0x15,0x15,0x21,0x32,0x16,0x15,0x14,0x6,0x23,0x21,0x15,0x14,0x6,0x23,0x22,0x26,0x35,0x35,0x21, + 0x0,0x16,0x15,0x14,0x6,0x23,0x21,0x22,0x26,0x35,0x34,0x36,0x33,0x21,0xc7,0x1d,0x1d,0x22,0x1,0x35,0x21,0x27,0x27,0x21,0x1,0x35,0x22,0x1d,0x1d,0x22, + 0xfe,0xcb,0x21,0x27,0x27,0x21,0xfe,0xcb,0x3,0x1c,0x1d,0x1d,0x22,0xfd,0x6,0x22,0x1d,0x1d,0x22,0x2,0xfa,0x2,0x9e,0x21,0x27,0x27,0x21,0xf9,0x22,0x1d, + 0x1d,0x22,0xf9,0x21,0x27,0x27,0x21,0xf9,0x22,0x1d,0x1d,0x22,0xf9,0xfe,0x60,0x21,0x27,0x27,0x21,0x21,0x27,0x27,0x21,0x0,0x0,0x0,0x0,0x1,0x0,0xc3, + 0xff,0xf6,0x4,0x9,0x4,0xa3,0x0,0x19,0x0,0x6,0xb3,0x5,0x0,0x1,0x30,0x2b,0x0,0x16,0x15,0x11,0x14,0x6,0x23,0x23,0x22,0x26,0x35,0x11,0x21,0x11, + 0x14,0x6,0x23,0x23,0x22,0x26,0x35,0x11,0x34,0x36,0x33,0x21,0x3,0xec,0x1d,0x21,0x29,0x2,0x29,0x21,0xfd,0xe6,0x21,0x29,0x2,0x29,0x21,0x1d,0x22,0x2, + 0xc8,0x4,0xa3,0x1f,0x25,0xfb,0xd8,0x23,0x1e,0x1e,0x23,0x3,0xdc,0xfc,0x24,0x23,0x1e,0x1e,0x23,0x4,0x28,0x25,0x1f,0x0,0x0,0x1,0x0,0x74,0xff,0xea, + 0x4,0x50,0x4,0xad,0x0,0x1e,0x0,0x6,0xb3,0x1b,0x7,0x1,0x30,0x2b,0x0,0x16,0x15,0x14,0x7,0x1,0x6,0x6,0x23,0x22,0x26,0x27,0x3,0x23,0x22,0x26, + 0x35,0x34,0x36,0x33,0x33,0x32,0x16,0x17,0x13,0x1,0x36,0x36,0x33,0x32,0x17,0x4,0x35,0x1b,0x6,0xfe,0x85,0xd,0x32,0x2b,0x2b,0x33,0xe,0xc1,0x85,0x22, + 0x1d,0x1d,0x22,0xc0,0x17,0x1b,0x7,0xb5,0x1,0x58,0x8,0x1a,0x14,0xe,0x1a,0x4,0x9b,0x1a,0x13,0xd,0x12,0xfb,0xdc,0x23,0x1e,0x1f,0x22,0x1,0xd3,0x21, + 0x27,0x27,0x21,0xf,0x11,0xfe,0x48,0x3,0xc9,0x18,0x16,0x8,0x0,0x3,0x0,0xa5,0xfc,0xba,0x4,0x10,0x7,0x5,0x0,0x3,0x0,0x25,0x0,0x29,0x0,0xa, + 0xb7,0x28,0x26,0x16,0x4,0x2,0x0,0x3,0x30,0x2b,0x1,0x23,0x35,0x33,0x0,0x16,0x15,0x14,0x6,0x23,0x21,0x1,0x16,0x15,0x14,0x7,0x1,0x21,0x32,0x16, + 0x15,0x14,0x6,0x23,0x21,0x22,0x26,0x35,0x34,0x37,0x1,0x1,0x26,0x35,0x34,0x36,0x33,0x21,0x1,0x23,0x35,0x33,0x2,0x66,0x1,0x1,0x1,0x79,0x1d,0x1d, + 0x22,0xfd,0xd5,0x1,0x48,0xb,0xb,0xfe,0xa4,0x2,0x53,0x22,0x1d,0x1d,0x22,0xfd,0x15,0x22,0x1f,0x10,0x1,0x85,0xfe,0x8b,0xc,0x1f,0x22,0x2,0xc3,0xfe, + 0xa9,0x1,0x1,0x7,0x4,0x1,0xfd,0x9e,0x21,0x27,0x27,0x21,0xfe,0x6b,0xe,0x10,0x11,0xe,0xfe,0x4f,0x21,0x27,0x27,0x21,0x1f,0x25,0x20,0x15,0x1,0xe6, + 0x1,0xcf,0xe,0x23,0x25,0x1f,0xf8,0x17,0x1,0x0,0x0,0x2,0x0,0xcd,0xff,0xea,0x3,0xfe,0x4,0xb9,0x0,0x17,0x0,0x1b,0x0,0x8,0xb5,0x1b,0x19,0xa, + 0x0,0x2,0x30,0x2b,0x0,0x16,0x17,0x1,0x16,0x15,0x14,0x7,0x1,0x6,0x6,0x23,0x22,0x26,0x27,0x1,0x26,0x35,0x34,0x37,0x1,0x36,0x36,0x33,0x3,0x13, + 0x13,0x3,0x2,0x8f,0x2d,0x14,0x1,0x26,0x8,0x8,0xfe,0xda,0x14,0x2d,0x29,0x29,0x2d,0x14,0xfe,0xd9,0x8,0x8,0x1,0x27,0x14,0x2d,0x29,0xf6,0xf6,0xf6, + 0xf6,0x4,0xb9,0x1e,0x23,0xfd,0xf7,0xd,0x11,0x11,0xd,0xfd,0xf8,0x23,0x1e,0x1e,0x23,0x2,0x8,0x10,0xe,0xe,0x10,0x2,0x9,0x23,0x1e,0xfd,0x98,0xfe, + 0x4d,0x1,0xb3,0x1,0xb4,0x0,0x0,0x1,0x2,0x1b,0xfe,0x9d,0x2,0xb1,0x5,0x72,0x0,0xd,0x0,0x2e,0x4b,0xb0,0x17,0x50,0x58,0x40,0xc,0x2,0x1,0x1, + 0x0,0x1,0x72,0x0,0x0,0x0,0x13,0x0,0x4c,0x1b,0x40,0xa,0x2,0x1,0x1,0x0,0x1,0x72,0x0,0x0,0x0,0x69,0x59,0x40,0xa,0x0,0x0,0x0,0xd,0x0, + 0xc,0x25,0x3,0x7,0x15,0x2b,0x0,0x16,0x15,0x11,0x14,0x6,0x23,0x22,0x26,0x35,0x11,0x34,0x36,0x33,0x2,0x8f,0x22,0x22,0x29,0x29,0x22,0x22,0x29,0x5, + 0x72,0x1e,0x23,0xf9,0xad,0x23,0x1e,0x1e,0x23,0x6,0x53,0x23,0x1e,0x0,0x0,0x0,0x0,0x2,0x2,0x1b,0xfe,0x9d,0x2,0xb1,0x5,0x72,0x0,0xd,0x0,0x1b, + 0x0,0x50,0x4b,0xb0,0x17,0x50,0x58,0x40,0x15,0x4,0x1,0x1,0x0,0x0,0x3,0x1,0x0,0x63,0x5,0x1,0x3,0x3,0x2,0x5b,0x0,0x2,0x2,0x13,0x2,0x4c, + 0x1b,0x40,0x1b,0x4,0x1,0x1,0x0,0x0,0x3,0x1,0x0,0x63,0x5,0x1,0x3,0x2,0x2,0x3,0x57,0x5,0x1,0x3,0x3,0x2,0x5b,0x0,0x2,0x3,0x2,0x4f, + 0x59,0x40,0x12,0xe,0xe,0x0,0x0,0xe,0x1b,0xe,0x1a,0x15,0x13,0x0,0xd,0x0,0xc,0x25,0x6,0x7,0x15,0x2b,0x0,0x16,0x15,0x11,0x14,0x6,0x23,0x22, + 0x26,0x35,0x11,0x34,0x36,0x33,0x12,0x16,0x15,0x11,0x14,0x6,0x23,0x22,0x26,0x35,0x11,0x34,0x36,0x33,0x2,0x8f,0x22,0x22,0x29,0x29,0x22,0x22,0x29,0x29, + 0x22,0x22,0x29,0x29,0x22,0x22,0x29,0x5,0x72,0x1e,0x23,0xfd,0xa9,0x23,0x1e,0x1e,0x23,0x2,0x57,0x23,0x1e,0xfc,0x4,0x1e,0x23,0xfd,0xa9,0x23,0x1e,0x1e, + 0x23,0x2,0x57,0x23,0x1e,0x0,0x0,0x0,0x0,0x2,0x0,0x32,0xff,0x67,0x4,0x92,0x4,0x36,0x0,0x4a,0x0,0x58,0x0,0x5b,0x40,0x58,0x1a,0x17,0x2,0xa, + 0x2,0xa,0x1,0x3,0xa,0x2,0x4a,0x0,0x6,0x0,0x5,0x0,0x6,0x5,0x70,0xb,0x1,0x8,0x0,0x4,0x2,0x8,0x4,0x63,0x0,0x2,0xc,0x1,0xa,0x3, + 0x2,0xa,0x63,0x9,0x1,0x3,0x1,0x1,0x0,0x6,0x3,0x0,0x63,0x0,0x5,0x7,0x7,0x5,0x57,0x0,0x5,0x5,0x7,0x5b,0x0,0x7,0x5,0x7,0x4f,0x4b, + 0x4b,0x0,0x0,0x4b,0x58,0x4b,0x57,0x52,0x50,0x0,0x4a,0x0,0x49,0x27,0x32,0x26,0x25,0x2e,0x26,0x24,0x26,0xd,0x7,0x1c,0x2b,0x0,0x16,0x16,0x15,0x14, + 0x6,0x6,0x23,0x22,0x26,0x27,0x6,0x6,0x23,0x22,0x26,0x26,0x35,0x34,0x36,0x36,0x33,0x32,0x17,0x37,0x36,0x33,0x32,0x17,0x16,0x16,0x7,0x3,0x6,0x15, + 0x14,0x16,0x33,0x32,0x36,0x35,0x34,0x26,0x26,0x23,0x22,0x6,0x2,0x15,0x14,0x16,0x16,0x33,0x32,0x36,0x37,0x36,0x33,0x32,0x17,0x16,0x15,0x14,0x7,0x6, + 0x6,0x23,0x22,0x26,0x26,0x35,0x34,0x12,0x24,0x33,0x2,0x6,0x6,0x15,0x14,0x16,0x33,0x32,0x36,0x36,0x35,0x34,0x26,0x23,0x3,0x2d,0xe7,0x7e,0x46,0x80, + 0x53,0x41,0x5d,0x17,0x34,0x5e,0x3f,0x31,0x51,0x2e,0x56,0x92,0x55,0x56,0x2a,0x1f,0x6,0x13,0xf,0x13,0x14,0x17,0x4,0x4d,0x7,0x28,0x23,0x4b,0x56,0x60, + 0xb1,0x79,0x88,0xdd,0x7d,0x5a,0xae,0x7b,0x45,0x85,0x35,0x8,0x3,0x1c,0xd,0x5,0x18,0x36,0x97,0x51,0x9f,0xe6,0x7b,0x9f,0x1,0x16,0xad,0x3c,0x55,0x39, + 0x24,0x1e,0x31,0x57,0x34,0x28,0x24,0x4,0x36,0x8b,0xf8,0xa0,0x74,0xb3,0x63,0x42,0x3f,0x43,0x43,0x36,0x65,0x46,0x5b,0xca,0x87,0x64,0x3f,0xb,0x7,0x7, + 0x19,0xe,0xfe,0xd2,0x1b,0x19,0x2a,0x2f,0x93,0x79,0x82,0xbf,0x67,0x95,0xfe,0xfe,0x9f,0x80,0xbd,0x66,0x14,0x11,0x2,0x2d,0x12,0xa,0x1b,0xa,0x18,0x1c, + 0x83,0xf4,0xa7,0xc2,0x1,0x3b,0xb4,0xfe,0x5c,0x59,0x8b,0x47,0x2f,0x2e,0x5b,0x8a,0x42,0x2e,0x33,0x0,0x0,0x0,0x0,0x2,0x0,0x9d,0xff,0xea,0x4,0x44, + 0x4,0xf5,0x0,0x3b,0x0,0x45,0x0,0x42,0x40,0x3f,0x3e,0x3d,0x3a,0x36,0x2c,0x11,0x8,0x7,0x6,0x5,0x1,0x4a,0x0,0x3,0x4,0x5,0x4,0x3,0x5,0x70, + 0x0,0x5,0x6,0x4,0x5,0x6,0x6e,0x0,0x2,0x0,0x4,0x3,0x2,0x4,0x63,0x7,0x1,0x6,0x6,0x0,0x5b,0x1,0x1,0x0,0x0,0x17,0x0,0x4c,0x3c,0x3c, + 0x3c,0x45,0x3c,0x44,0x1b,0x23,0x16,0x2c,0x23,0x24,0x8,0x7,0x1a,0x2b,0x24,0x15,0x14,0x7,0x6,0x23,0x22,0x27,0x27,0x6,0x23,0x22,0x26,0x26,0x35,0x34, + 0x36,0x37,0x26,0x26,0x35,0x34,0x36,0x36,0x33,0x32,0x17,0x16,0x15,0x14,0x7,0x6,0x23,0x22,0x27,0x26,0x23,0x22,0x6,0x6,0x15,0x14,0x16,0x17,0x1,0x36, + 0x37,0x36,0x36,0x33,0x32,0x17,0x16,0x16,0x15,0x14,0x7,0x6,0x7,0x17,0x24,0x37,0x1,0x6,0x6,0x15,0x14,0x16,0x16,0x33,0x4,0x44,0x2b,0x1f,0x15,0x16, + 0x15,0x78,0x89,0xb5,0x6e,0xa3,0x56,0x68,0x5f,0x2e,0x2e,0x57,0xa1,0x6a,0x9e,0x8b,0x22,0x10,0x18,0x1d,0xf,0x13,0x6b,0x7a,0x3d,0x5a,0x30,0x31,0x32,0x1, + 0x2a,0x36,0x27,0x7,0x1b,0x19,0xd,0x12,0x20,0x1c,0x5,0x38,0x4e,0x9a,0xfe,0x4f,0x5f,0xfe,0xe4,0x48,0x44,0x34,0x5d,0x3c,0x44,0x11,0x1b,0x18,0x12,0x16, + 0x84,0x9e,0x5d,0x9c,0x5d,0x6c,0xa5,0x44,0x3b,0x7c,0x4c,0x62,0x9f,0x5c,0x57,0x14,0x1e,0x16,0x1d,0x2b,0xc,0x47,0x36,0x5c,0x37,0x3f,0x66,0x38,0xfe,0xb9, + 0x6d,0x93,0x1a,0x16,0x3,0x5,0x18,0x15,0xc,0x14,0xc5,0x87,0xa9,0x24,0x76,0x1,0x37,0x35,0x6f,0x47,0x39,0x58,0x31,0x0,0x0,0x1,0x0,0x83,0xfe,0x66, + 0x4,0x5d,0x4,0xa3,0x0,0x20,0x0,0x2c,0x40,0x29,0x0,0x4,0x0,0x1,0x0,0x4,0x1,0x70,0x2,0x1,0x0,0x0,0x5,0x59,0x6,0x1,0x5,0x5,0xe,0x4b, + 0x3,0x1,0x1,0x1,0x13,0x1,0x4c,0x0,0x0,0x0,0x20,0x0,0x1e,0x13,0x23,0x13,0x23,0x24,0x7,0x7,0x19,0x2b,0x0,0x16,0x15,0x14,0x6,0x23,0x23,0x11, + 0x14,0x6,0x23,0x22,0x26,0x35,0x11,0x23,0x11,0x14,0x6,0x23,0x22,0x26,0x35,0x11,0x2e,0x2,0x35,0x34,0x36,0x36,0x33,0x21,0x4,0x40,0x1d,0x1d,0x22,0x44, + 0x22,0x29,0x29,0x22,0xa0,0x22,0x29,0x29,0x22,0x75,0xb3,0x63,0x69,0xbd,0x7b,0x1,0xfa,0x4,0xa3,0x21,0x27,0x27,0x21,0xfa,0x94,0x23,0x1e,0x1e,0x23,0x5, + 0x6c,0xfa,0x94,0x23,0x1e,0x1e,0x23,0x3,0x4f,0x4,0x53,0x98,0x67,0x6a,0x9b,0x52,0x0,0x0,0x0,0x3,0x0,0x24,0xff,0xea,0x4,0xa8,0x4,0xb9,0x0,0xf, + 0x0,0x1f,0x0,0x4b,0x0,0x67,0xb1,0x6,0x64,0x44,0x40,0x5c,0x0,0x4,0x5,0x7,0x5,0x4,0x7,0x70,0x0,0x7,0x6,0x5,0x7,0x6,0x6e,0xa,0x1,0x1, + 0xb,0x1,0x3,0x9,0x1,0x3,0x63,0xc,0x1,0x9,0x0,0x5,0x4,0x9,0x5,0x63,0x0,0x6,0x0,0x8,0x2,0x6,0x8,0x63,0x0,0x2,0x0,0x0,0x2,0x57, + 0x0,0x2,0x2,0x0,0x5b,0x0,0x0,0x2,0x0,0x4f,0x20,0x20,0x10,0x10,0x0,0x0,0x20,0x4b,0x20,0x4a,0x44,0x42,0x3a,0x38,0x35,0x33,0x2f,0x2d,0x29,0x28, + 0x10,0x1f,0x10,0x1e,0x18,0x16,0x0,0xf,0x0,0xe,0x26,0xd,0x7,0x15,0x2b,0xb1,0x6,0x0,0x44,0x0,0x4,0x12,0x15,0x14,0x2,0x4,0x23,0x22,0x24,0x2, + 0x35,0x34,0x12,0x24,0x33,0xe,0x2,0x15,0x14,0x16,0x16,0x33,0x32,0x36,0x36,0x35,0x34,0x26,0x26,0x23,0x16,0x16,0x17,0x16,0x16,0x15,0x14,0x7,0x6,0x23, + 0x22,0x27,0x26,0x26,0x23,0x22,0x6,0x15,0x14,0x16,0x33,0x32,0x36,0x37,0x36,0x33,0x32,0x17,0x16,0x15,0x14,0x6,0x7,0x6,0x6,0x23,0x22,0x26,0x26,0x35, + 0x34,0x36,0x36,0x33,0x3,0xd,0x1,0x7,0x94,0x94,0xfe,0xf8,0xa6,0xa6,0xfe,0xf8,0x94,0x94,0x1,0x7,0xa7,0x85,0xd0,0x73,0x73,0xd0,0x85,0x85,0xd0,0x73, + 0x73,0xd0,0x85,0x3c,0x5d,0x2a,0x15,0x14,0x7,0x10,0x1c,0xc,0x12,0x25,0x41,0x26,0x4f,0x66,0x66,0x4f,0x26,0x41,0x25,0x12,0xc,0x1c,0x10,0x7,0x14,0x15, + 0x2a,0x5d,0x2d,0x5b,0x8f,0x50,0x50,0x8f,0x5b,0x4,0xb9,0xa3,0xfe,0xe6,0xaa,0xaa,0xfe,0xe6,0xa4,0xa4,0x1,0x1a,0xaa,0xaa,0x1,0x1a,0xa3,0x78,0x80,0xe1, + 0x8e,0x8e,0xe2,0x80,0x80,0xe2,0x8e,0x8e,0xe1,0x80,0x98,0x15,0x13,0xa,0x19,0x12,0x10,0x11,0x28,0x8,0x11,0xf,0x71,0x69,0x69,0x71,0xf,0x11,0x8,0x28, + 0x11,0x10,0x12,0x19,0xa,0x13,0x15,0x53,0x9b,0x6a,0x6a,0x9b,0x53,0x0,0x0,0x0,0x0,0x4,0x0,0x24,0xff,0xea,0x4,0xa8,0x4,0xb9,0x0,0xf,0x0,0x1f, + 0x0,0x3d,0x0,0x46,0x0,0x6c,0xb1,0x6,0x64,0x44,0x40,0x61,0x26,0x1,0x5,0x8,0x1,0x4a,0x6,0x1,0x4,0x5,0x2,0x5,0x4,0x2,0x70,0xa,0x1,0x1, + 0xb,0x1,0x3,0x7,0x1,0x3,0x63,0xc,0x1,0x7,0xd,0x1,0x9,0x8,0x7,0x9,0x63,0x0,0x8,0x0,0x5,0x4,0x8,0x5,0x61,0x0,0x2,0x0,0x0,0x2, + 0x57,0x0,0x2,0x2,0x0,0x5b,0x0,0x0,0x2,0x0,0x4f,0x3e,0x3e,0x20,0x20,0x10,0x10,0x0,0x0,0x3e,0x46,0x3e,0x45,0x41,0x3f,0x20,0x3d,0x20,0x3b,0x36, + 0x34,0x31,0x30,0x2e,0x2c,0x10,0x1f,0x10,0x1e,0x18,0x16,0x0,0xf,0x0,0xe,0x26,0xe,0x7,0x15,0x2b,0xb1,0x6,0x0,0x44,0x0,0x4,0x12,0x15,0x14,0x2, + 0x4,0x23,0x22,0x24,0x2,0x35,0x34,0x12,0x24,0x33,0xe,0x2,0x15,0x14,0x16,0x16,0x33,0x32,0x36,0x36,0x35,0x34,0x26,0x26,0x23,0x1e,0x2,0x15,0x14,0x6, + 0x7,0x17,0x16,0x15,0x14,0x7,0x6,0x23,0x22,0x27,0x27,0x23,0x15,0x14,0x6,0x23,0x22,0x26,0x35,0x11,0x34,0x36,0x33,0x33,0x7,0x15,0x33,0x32,0x36,0x35, + 0x34,0x26,0x23,0x3,0xd,0x1,0x7,0x94,0x94,0xfe,0xf8,0xa6,0xa6,0xfe,0xf8,0x94,0x94,0x1,0x7,0xa7,0x85,0xd0,0x73,0x73,0xd0,0x85,0x85,0xd0,0x73,0x73, + 0xd0,0x85,0x60,0x6e,0x3b,0x4c,0x45,0x6b,0x1a,0x16,0x16,0x16,0x19,0x14,0x9e,0x59,0x1d,0x22,0x22,0x1b,0x1f,0x22,0xbb,0x80,0x6a,0x42,0x44,0x40,0x3d,0x4, + 0xb9,0xa3,0xfe,0xe6,0xaa,0xaa,0xfe,0xe6,0xa4,0xa4,0x1,0x1a,0xaa,0xaa,0x1,0x1a,0xa3,0x78,0x80,0xe1,0x8e,0x8e,0xe2,0x80,0x80,0xe2,0x8e,0x8e,0xe1,0x80, + 0xac,0x38,0x65,0x41,0x4a,0x68,0x17,0x74,0x1d,0x17,0x15,0x15,0x13,0x17,0xb6,0x87,0x23,0x1e,0x1e,0x23,0x2,0x7,0x21,0x1e,0x75,0xd5,0x38,0x34,0x32,0x37, + 0x0,0x0,0x0,0x2,0x0,0xc1,0xff,0x1a,0x4,0xb,0x4,0xa3,0x0,0x35,0x0,0x47,0x0,0x27,0x40,0x24,0x41,0x38,0x1d,0x2,0x4,0x1,0x3,0x1,0x4a,0x0, + 0x1,0x0,0x0,0x1,0x0,0x5d,0x0,0x3,0x3,0x2,0x59,0x0,0x2,0x2,0xe,0x3,0x4c,0x2c,0x29,0x25,0x22,0x34,0x37,0x4,0x7,0x16,0x2b,0x0,0x6,0x7, + 0x16,0x15,0x14,0x6,0x6,0x23,0x21,0x22,0x26,0x35,0x34,0x36,0x33,0x21,0x32,0x36,0x35,0x34,0x26,0x27,0x25,0x26,0x26,0x35,0x34,0x36,0x37,0x26,0x35,0x34, + 0x36,0x36,0x33,0x21,0x32,0x16,0x15,0x14,0x6,0x23,0x21,0x22,0x6,0x15,0x14,0x16,0x17,0x5,0x16,0x16,0x15,0x5,0x16,0x17,0x36,0x36,0x35,0x34,0x26,0x27, + 0x25,0x26,0x27,0x6,0x6,0x15,0x14,0x16,0x17,0x4,0xb,0x3d,0x3c,0x14,0x3f,0x7c,0x58,0xfe,0xba,0x22,0x1d,0x1d,0x22,0x1,0x32,0x46,0x47,0x2b,0x30,0xfe, + 0xb3,0x54,0x4f,0x3d,0x3c,0x14,0x3f,0x7c,0x58,0x1,0x46,0x22,0x1d,0x1d,0x22,0xfe,0xce,0x46,0x47,0x2b,0x30,0x1,0x4d,0x54,0x4f,0xff,0x1,0x12,0x20,0x19, + 0x1a,0x31,0x33,0xfe,0xb3,0x12,0x20,0x19,0x1a,0x31,0x33,0x1,0xb,0x64,0x2d,0x31,0x3f,0x47,0x6c,0x3d,0x21,0x27,0x27,0x21,0x33,0x31,0x2b,0x3c,0x1e,0xce, + 0x34,0x82,0x59,0x42,0x64,0x2d,0x31,0x3f,0x47,0x6c,0x3d,0x21,0x27,0x27,0x21,0x33,0x31,0x2b,0x3c,0x1e,0xce,0x34,0x82,0x59,0x40,0xb,0x18,0x13,0x30,0x20, + 0x2d,0x48,0x20,0xce,0xb,0x18,0x13,0x30,0x20,0x2d,0x48,0x20,0x0,0x0,0x0,0x2,0x0,0x3c,0x2,0xec,0x4,0xf9,0x5,0x2a,0x0,0x23,0x0,0x3a,0x0,0x8, + 0xb5,0x2d,0x24,0x5,0x0,0x2,0x30,0x2b,0x0,0x16,0x17,0x13,0x16,0x6,0x23,0x22,0x26,0x27,0x3,0x7,0x6,0x6,0x23,0x22,0x26,0x27,0x27,0x3,0x6,0x6, + 0x23,0x22,0x26,0x37,0x13,0x36,0x36,0x33,0x32,0x17,0x13,0x13,0x36,0x33,0x4,0x16,0x15,0x14,0x6,0x23,0x23,0x11,0x14,0x6,0x23,0x22,0x26,0x35,0x11,0x23, + 0x22,0x26,0x35,0x34,0x36,0x33,0x21,0x4,0xb6,0x21,0x2,0x1e,0x2,0x19,0x22,0x22,0x1b,0x2,0x12,0x5d,0xc,0x26,0x16,0x16,0x27,0xb,0x5e,0x11,0x2,0x1b, + 0x22,0x22,0x19,0x2,0x1e,0x2,0x21,0x22,0x32,0xc,0x8e,0x8e,0xc,0x32,0xfd,0x87,0x1d,0x1d,0x22,0x81,0x1c,0x22,0x22,0x1c,0x81,0x22,0x1d,0x1d,0x22,0x1, + 0x7e,0x5,0x2a,0x1e,0x23,0xfe,0x44,0x23,0x1e,0x1d,0x24,0x1,0x3,0xc0,0x17,0x19,0x19,0x17,0xc0,0xfe,0xfd,0x24,0x1d,0x1e,0x23,0x1,0xbc,0x23,0x1e,0x1a, + 0xfe,0xd5,0x1,0x2b,0x1a,0xa,0x1b,0x21,0x21,0x1a,0xfe,0x84,0x23,0x1e,0x1e,0x23,0x1,0x7c,0x1a,0x21,0x21,0x1b,0x0,0x2,0x0,0xf6,0x2,0x89,0x3,0x26, + 0x4,0xb9,0x0,0xf,0x0,0x1f,0x0,0x2a,0xb1,0x6,0x64,0x44,0x40,0x1f,0x0,0x0,0x0,0x3,0x2,0x0,0x3,0x63,0x0,0x2,0x1,0x1,0x2,0x57,0x0,0x2, + 0x2,0x1,0x5b,0x0,0x1,0x2,0x1,0x4f,0x26,0x26,0x26,0x22,0x4,0x7,0x18,0x2b,0xb1,0x6,0x0,0x44,0x12,0x36,0x36,0x33,0x32,0x16,0x16,0x15,0x14,0x6, + 0x6,0x23,0x22,0x26,0x26,0x35,0x1e,0x2,0x33,0x32,0x36,0x36,0x35,0x34,0x26,0x26,0x23,0x22,0x6,0x6,0x15,0xf6,0x4b,0x81,0x4c,0x4c,0x81,0x4b,0x4b,0x81, + 0x4c,0x4c,0x81,0x4b,0x87,0x27,0x43,0x27,0x27,0x43,0x27,0x27,0x43,0x27,0x27,0x43,0x27,0x3,0xed,0x81,0x4b,0x4b,0x81,0x4c,0x4c,0x81,0x4b,0x4b,0x81,0x4c, + 0x27,0x43,0x27,0x27,0x43,0x27,0x27,0x43,0x27,0x27,0x43,0x27,0x0,0x1,0x0,0xe8,0x1,0xf6,0x3,0xe3,0x4,0xb9,0x0,0x1d,0x0,0x28,0xb1,0x6,0x64,0x44, + 0x40,0x1d,0xe,0x5,0x2,0x0,0x2,0x1,0x4a,0x3,0x1,0x2,0x0,0x2,0x72,0x1,0x1,0x0,0x0,0x69,0x0,0x0,0x0,0x1d,0x0,0x1c,0x27,0x19,0x4,0x7, + 0x16,0x2b,0xb1,0x6,0x0,0x44,0x0,0x16,0x17,0x1,0x16,0x15,0x14,0x6,0x7,0x6,0x23,0x22,0x26,0x27,0x3,0x3,0x6,0x6,0x23,0x22,0x27,0x26,0x26,0x35, + 0x34,0x37,0x1,0x36,0x36,0x33,0x2,0x82,0x1e,0xa,0x1,0x34,0x5,0x1a,0x14,0x16,0x16,0x13,0x1c,0x7,0xed,0xed,0x7,0x1c,0x13,0x11,0x17,0x16,0x1d,0x6, + 0x1,0x34,0xa,0x1e,0x1c,0x4,0xb9,0x12,0x13,0xfd,0xb1,0x9,0xc,0x11,0x1a,0x7,0x8,0xd,0xc,0x1,0xe0,0xfe,0x20,0xc,0xd,0x7,0x7,0x1b,0x11,0xb, + 0xa,0x2,0x4f,0x13,0x12,0x0,0x0,0x1,0x1,0x22,0x1,0x19,0x3,0xaa,0x4,0xb9,0x0,0x1f,0x0,0x29,0x40,0x26,0x2,0x1,0x0,0x0,0x3,0x59,0x6,0x5, + 0x2,0x3,0x3,0x11,0x4b,0x0,0x1,0x1,0x4,0x5b,0x0,0x4,0x4,0x16,0x1,0x4c,0x0,0x0,0x0,0x1f,0x0,0x1e,0x23,0x24,0x23,0x23,0x24,0x7,0x7,0x19, + 0x2b,0x0,0x16,0x15,0x14,0x6,0x23,0x23,0x11,0x14,0x6,0x23,0x22,0x26,0x35,0x11,0x23,0x22,0x26,0x35,0x34,0x36,0x33,0x33,0x35,0x34,0x36,0x33,0x32,0x16, + 0x15,0x15,0x33,0x3,0x8d,0x1d,0x1d,0x22,0xbd,0x21,0x27,0x27,0x21,0xbd,0x22,0x1d,0x1d,0x22,0xbd,0x21,0x27,0x27,0x21,0xbd,0x3,0x9c,0x21,0x27,0x27,0x21, + 0xfe,0x4c,0x22,0x1d,0x1d,0x22,0x1,0xb4,0x21,0x27,0x27,0x21,0xde,0x22,0x1d,0x1d,0x22,0xde,0x0,0x1,0x1,0x22,0x0,0xdd,0x3,0xaa,0x4,0xb9,0x0,0x31, + 0x0,0xdf,0x4b,0xb0,0xc,0x50,0x58,0x40,0x20,0x7,0x1,0x5,0x8,0x1,0x4,0x3,0x5,0x4,0x61,0xa,0x9,0x2,0x3,0x2,0x1,0x0,0x1,0x3,0x0,0x61, + 0x0,0x1,0x1,0x6,0x5b,0x0,0x6,0x6,0x16,0x1,0x4c,0x1b,0x4b,0xb0,0xe,0x50,0x58,0x40,0x22,0xa,0x9,0x2,0x3,0x2,0x1,0x0,0x1,0x3,0x0,0x61, + 0x8,0x1,0x4,0x4,0x5,0x59,0x7,0x1,0x5,0x5,0x11,0x4b,0x0,0x1,0x1,0x6,0x5b,0x0,0x6,0x6,0x16,0x1,0x4c,0x1b,0x4b,0xb0,0x10,0x50,0x58,0x40, + 0x20,0x7,0x1,0x5,0x8,0x1,0x4,0x3,0x5,0x4,0x61,0xa,0x9,0x2,0x3,0x2,0x1,0x0,0x1,0x3,0x0,0x61,0x0,0x1,0x1,0x6,0x5b,0x0,0x6,0x6, + 0x16,0x1,0x4c,0x1b,0x4b,0xb0,0x15,0x50,0x58,0x40,0x22,0xa,0x9,0x2,0x3,0x2,0x1,0x0,0x1,0x3,0x0,0x61,0x8,0x1,0x4,0x4,0x5,0x59,0x7,0x1, + 0x5,0x5,0x11,0x4b,0x0,0x1,0x1,0x6,0x5b,0x0,0x6,0x6,0x16,0x1,0x4c,0x1b,0x40,0x20,0x7,0x1,0x5,0x8,0x1,0x4,0x3,0x5,0x4,0x61,0xa,0x9, + 0x2,0x3,0x2,0x1,0x0,0x1,0x3,0x0,0x61,0x0,0x1,0x1,0x6,0x5b,0x0,0x6,0x6,0x16,0x1,0x4c,0x59,0x59,0x59,0x59,0x40,0x12,0x0,0x0,0x0,0x31, + 0x0,0x30,0x24,0x23,0x23,0x24,0x21,0x24,0x23,0x23,0x24,0xb,0x7,0x1d,0x2b,0x0,0x16,0x15,0x14,0x6,0x23,0x23,0x15,0x14,0x6,0x23,0x22,0x26,0x35,0x35, + 0x23,0x22,0x26,0x35,0x34,0x36,0x33,0x33,0x35,0x23,0x22,0x26,0x35,0x34,0x36,0x33,0x33,0x35,0x34,0x36,0x33,0x32,0x16,0x15,0x15,0x33,0x32,0x16,0x15,0x14, + 0x6,0x23,0x23,0x15,0x33,0x3,0x8d,0x1d,0x1d,0x22,0xbd,0x21,0x27,0x27,0x21,0xbd,0x22,0x1d,0x1d,0x22,0xbd,0xbd,0x22,0x1d,0x1d,0x22,0xbd,0x21,0x27,0x27, + 0x21,0xbd,0x22,0x1d,0x1d,0x22,0xbd,0xbd,0x2,0x58,0x21,0x27,0x27,0x21,0xac,0x22,0x1d,0x1d,0x22,0xac,0x21,0x27,0x27,0x21,0xe6,0x21,0x27,0x27,0x21,0xac, + 0x22,0x1d,0x1d,0x22,0xac,0x21,0x27,0x27,0x21,0xe6,0x0,0x1,0xfd,0x9,0xfd,0xf2,0xfe,0x4,0xff,0x6e,0x0,0x10,0x0,0x2e,0xb1,0x6,0x64,0x44,0x40,0x23, + 0xa,0x1,0x2,0x0,0x1,0x1,0x4a,0x2,0x1,0x1,0x0,0x0,0x1,0x57,0x2,0x1,0x1,0x1,0x0,0x5b,0x0,0x0,0x1,0x0,0x4f,0x0,0x0,0x0,0x10,0x0, + 0xe,0x15,0x3,0x7,0x15,0x2b,0xb1,0x6,0x0,0x44,0x4,0x15,0x14,0x7,0x3,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x13,0x36,0x33,0x33,0xfe,0x4,0x6, + 0x75,0xf,0x2c,0xb,0x6,0x34,0x2,0x3f,0x8,0x22,0x74,0x92,0x1a,0xc,0xd,0xfe,0xdd,0x26,0x1,0x7,0x25,0x4,0xc,0x1,0x1f,0x20,0x0,0x0,0xff,0xff, + 0xfd,0x9,0xfd,0xf2,0xfe,0x4,0xff,0x6e,0x0,0x2,0x1,0x7e,0x0,0x0,0x0,0x1,0x1,0xa1,0x4,0xe,0x3,0x93,0x5,0x87,0x0,0x11,0x0,0x1f,0xb1,0x6, + 0x64,0x44,0x40,0x14,0xe,0x1,0x1,0x0,0x1,0x4a,0x0,0x0,0x1,0x0,0x72,0x0,0x1,0x1,0x69,0x27,0x20,0x2,0x7,0x16,0x2b,0xb1,0x6,0x0,0x44,0x0, + 0x33,0x32,0x17,0x16,0x15,0x14,0x7,0x5,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x1,0x3,0x1f,0x13,0x22,0x21,0x1e,0x16,0xfe,0x8a,0x12,0xe,0x18,0x16, + 0x18,0x10,0x1,0x5c,0x5,0x87,0x29,0x27,0x1d,0x1a,0xd,0xdb,0xa,0x19,0x1b,0x19,0x13,0xc,0x1,0x0,0x0,0x1,0x0,0xfa,0x3,0x5e,0x2,0x9,0x5,0x20, + 0x0,0x10,0x0,0x19,0x40,0x16,0xa,0x1,0x0,0x1,0x1,0x4a,0x0,0x0,0x0,0x1,0x5b,0x0,0x1,0x1,0x10,0x0,0x4c,0x26,0x26,0x2,0x7,0x16,0x2b,0x0, + 0x16,0x15,0x14,0x7,0x3,0x6,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x36,0x33,0x33,0x1,0xfb,0xe,0x5,0x86,0xd,0x3d,0x1c,0x1e,0x2,0x52,0x8,0x22,0x76, + 0x5,0x20,0xf,0xc,0x9,0xf,0xfe,0x95,0x24,0x16,0x14,0x4,0xa,0x1,0x6a,0x20,0x0,0x0,0x0,0x1,0x1,0x1e,0x4,0x2f,0x3,0xae,0x5,0x5c,0x0,0x1d, + 0x0,0x2e,0xb1,0x6,0x64,0x44,0x40,0x23,0xa,0x1,0x2,0x1,0x1,0x4a,0x3,0x1,0x1,0x2,0x1,0x72,0x0,0x2,0x0,0x0,0x2,0x57,0x0,0x2,0x2,0x0, + 0x5b,0x0,0x0,0x2,0x0,0x4f,0x13,0x23,0x19,0x26,0x4,0x7,0x18,0x2b,0xb1,0x6,0x0,0x44,0x0,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x22,0x26,0x27,0x26, + 0x35,0x34,0x36,0x37,0x36,0x33,0x32,0x17,0x16,0x16,0x33,0x32,0x36,0x37,0x36,0x33,0x32,0x17,0x3,0x94,0x1a,0x1,0x17,0xa7,0x89,0x89,0xa7,0x17,0x1,0x1a, + 0x1c,0xf,0xd,0x22,0x9,0x17,0x64,0x50,0x51,0x64,0x16,0x9,0x22,0xd,0xf,0x5,0x53,0x17,0x13,0x8,0x5,0x71,0x7c,0x7c,0x71,0x5,0x8,0x13,0x17,0x6, + 0x3,0x18,0x47,0x44,0x44,0x47,0x18,0x3,0x0,0x1,0x1,0x27,0x4,0x11,0x3,0xa5,0x5,0x76,0x0,0x17,0x0,0x28,0xb1,0x6,0x64,0x44,0x40,0x1d,0x12,0xb, + 0x2,0x2,0x0,0x1,0x4a,0x1,0x1,0x0,0x2,0x0,0x72,0x3,0x1,0x2,0x2,0x69,0x0,0x0,0x0,0x17,0x0,0x16,0x25,0x17,0x4,0x7,0x16,0x2b,0xb1,0x6, + 0x0,0x44,0x0,0x27,0x27,0x26,0x35,0x34,0x37,0x36,0x33,0x32,0x17,0x17,0x37,0x36,0x33,0x32,0x17,0x16,0x15,0x14,0x7,0x7,0x6,0x23,0x2,0x42,0x16,0xf5, + 0x10,0x17,0x15,0x17,0x10,0xf,0xdd,0xdd,0xf,0x10,0x17,0x15,0x17,0x10,0xf5,0x16,0x24,0x4,0x11,0x14,0xe9,0xf,0x12,0x15,0x1a,0x18,0xc,0xad,0xad,0xc, + 0x18,0x1a,0x15,0x12,0xf,0xe9,0x14,0x0,0x0,0x1,0x1,0x8e,0x5,0x1b,0x3,0xc3,0x6,0x6c,0x0,0x13,0x0,0xf,0x40,0xc,0x10,0x1,0x0,0x47,0x0,0x0, + 0x0,0x69,0x20,0x1,0x7,0x15,0x2b,0x0,0x33,0x32,0x16,0x17,0x16,0x15,0x14,0x7,0x5,0x6,0x23,0x22,0x26,0x27,0x26,0x35,0x34,0x37,0x25,0x3,0x58,0xe, + 0x10,0x21,0x13,0x19,0x19,0xfe,0x1f,0x8,0x4,0xe,0x18,0x5,0x4,0x17,0x1,0xa0,0x6,0x6c,0x1e,0x23,0x2c,0x1c,0x1b,0x8,0xa3,0x2,0x13,0xf,0x9,0xd, + 0x1b,0xd,0xe6,0x0,0x0,0x0,0x0,0x1,0x0,0xec,0x5,0x3d,0x3,0xe0,0x6,0x5c,0x0,0x19,0x0,0x2d,0x40,0x2a,0xa,0x2,0x2,0x2,0x1,0x1,0x4a,0x4, + 0x3,0x2,0x1,0x2,0x1,0x72,0x0,0x2,0x0,0x0,0x2,0x57,0x0,0x2,0x2,0x0,0x5b,0x0,0x0,0x2,0x0,0x4f,0x0,0x0,0x0,0x19,0x0,0x18,0x23,0x26, + 0x26,0x5,0x7,0x17,0x2b,0x0,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x22,0x26,0x27,0x26,0x35,0x34,0x36,0x33,0x32,0x17,0x16,0x16,0x33,0x32,0x36,0x37,0x36, + 0x33,0x3,0xb3,0x2d,0x1,0x19,0xc7,0x99,0x99,0xc7,0x19,0x1,0x2d,0x19,0x1d,0xa,0x22,0x82,0x69,0x69,0x82,0x22,0xa,0x1d,0x6,0x5c,0x1f,0x15,0x5,0x3, + 0x69,0x7a,0x7a,0x69,0x3,0x5,0x15,0x1f,0x14,0x42,0x39,0x39,0x42,0x14,0x0,0x1,0x1,0xa,0x5,0x1b,0x3,0xc2,0x6,0x71,0x0,0x19,0x0,0x1b,0x40,0x18, + 0x18,0x1,0x0,0x1,0x1,0x4a,0x1,0x1,0x1,0x48,0x0,0x1,0x0,0x1,0x72,0x0,0x0,0x0,0x69,0x28,0x2a,0x2,0x7,0x16,0x2b,0x0,0x33,0x32,0x16,0x17, + 0x16,0x15,0x14,0x7,0x5,0x6,0x23,0x22,0x27,0x25,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x32,0x17,0x5,0x25,0x3,0x84,0xc,0xc,0x15,0x7,0xa,0x10,0xfe, + 0xeb,0x16,0x21,0x20,0x17,0xfe,0xeb,0x10,0xa,0x7,0x15,0xc,0xb,0x8,0x1,0x17,0x1,0x17,0x6,0x71,0xe,0xc,0x11,0xf,0x13,0xf,0xe8,0x12,0x12,0xe8, + 0xf,0x13,0xf,0x11,0xc,0xe,0x5,0x9f,0x9f,0x0,0xff,0xff,0x1,0x5f,0xfe,0x28,0x3,0x62,0x0,0x1c,0x0,0x2,0x1,0x91,0x0,0x0,0x0,0x1,0x1,0xa, + 0x5,0x18,0x3,0xc2,0x6,0x6e,0x0,0x19,0x0,0x1b,0x40,0x18,0x18,0x1,0x1,0x0,0x1,0x4a,0x1,0x1,0x1,0x47,0x0,0x0,0x1,0x0,0x72,0x0,0x1,0x1, + 0x69,0x28,0x2a,0x2,0x7,0x16,0x2b,0x0,0x23,0x22,0x26,0x27,0x26,0x35,0x34,0x37,0x25,0x36,0x33,0x32,0x17,0x5,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x22, + 0x27,0x25,0x5,0x1,0x47,0xb,0xc,0x15,0x7,0xa,0x10,0x1,0x15,0x17,0x20,0x20,0x17,0x1,0x15,0x10,0xa,0x7,0x15,0xc,0xc,0x7,0xfe,0xe9,0xfe,0xe9, + 0x5,0x18,0xe,0xc,0x11,0xf,0x13,0xf,0xe8,0x12,0x12,0xe8,0xf,0x13,0xf,0x11,0xc,0xe,0x5,0x9f,0x9f,0x0,0x0,0x2,0x1,0x35,0x5,0x53,0x3,0x97, + 0x6,0x57,0x0,0xd,0x0,0x1b,0x0,0x1d,0x40,0x1a,0x3,0x1,0x1,0x0,0x0,0x1,0x57,0x3,0x1,0x1,0x1,0x0,0x5b,0x2,0x1,0x0,0x1,0x0,0x4f,0x25, + 0x25,0x25,0x21,0x4,0x7,0x18,0x2b,0x0,0x6,0x23,0x22,0x26,0x35,0x35,0x34,0x36,0x33,0x32,0x16,0x15,0x15,0x4,0x6,0x23,0x22,0x26,0x35,0x35,0x34,0x36, + 0x33,0x32,0x16,0x15,0x15,0x1,0xf3,0x28,0x37,0x37,0x28,0x28,0x37,0x37,0x28,0x1,0xa4,0x28,0x37,0x37,0x28,0x28,0x37,0x37,0x28,0x5,0x70,0x1d,0x1d,0x24, + 0x82,0x24,0x1d,0x1d,0x24,0x82,0x24,0x1d,0x1d,0x24,0x82,0x24,0x1d,0x1d,0x24,0x82,0x0,0x0,0x0,0x1,0x2,0x7,0x5,0x53,0x2,0xc5,0x6,0x57,0x0,0xd, + 0x0,0x18,0x40,0x15,0x0,0x1,0x0,0x0,0x1,0x57,0x0,0x1,0x1,0x0,0x5b,0x0,0x0,0x1,0x0,0x4f,0x25,0x21,0x2,0x7,0x16,0x2b,0x0,0x6,0x23,0x22, + 0x26,0x35,0x35,0x34,0x36,0x33,0x32,0x16,0x15,0x15,0x2,0xc5,0x28,0x37,0x37,0x28,0x28,0x37,0x37,0x28,0x5,0x70,0x1d,0x1d,0x24,0x82,0x24,0x1d,0x1d,0x24, + 0x82,0x0,0x0,0x0,0x0,0x1,0x1,0x9,0x5,0x1b,0x3,0x3e,0x6,0x6c,0x0,0x11,0x0,0x11,0x40,0xe,0x0,0x1,0x0,0x1,0x72,0x0,0x0,0x0,0x69,0x29, + 0x13,0x2,0x7,0x16,0x2b,0x0,0x15,0x14,0x6,0x23,0x22,0x27,0x25,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x32,0x17,0x5,0x3,0x3e,0x1b,0x14,0x4,0x8,0xfe, + 0x1f,0x19,0x19,0x13,0x21,0x10,0xe,0x13,0x1,0xa0,0x5,0x6e,0x1b,0x16,0x22,0x2,0xa3,0x8,0x1b,0x1c,0x2c,0x23,0x1e,0xb,0xe6,0x0,0x0,0x0,0x0,0x2, + 0x1,0x2d,0x5,0x2b,0x4,0x50,0x6,0x77,0x0,0x11,0x0,0x23,0x0,0x1e,0x40,0x1b,0x1c,0x13,0xa,0x1,0x4,0x0,0x1,0x1,0x4a,0x3,0x1,0x1,0x0,0x1, + 0x72,0x2,0x1,0x0,0x0,0x69,0x27,0x27,0x27,0x25,0x4,0x7,0x18,0x2b,0x0,0x15,0x14,0x7,0x5,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x37,0x36,0x33, + 0x32,0x17,0x4,0x15,0x14,0x7,0x5,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x37,0x36,0x33,0x32,0x17,0x2,0xd4,0x12,0xfe,0xd3,0x13,0x15,0x16,0x14,0x16, + 0xe,0xf1,0x18,0x1e,0x1d,0x22,0x1,0xaf,0x12,0xfe,0xd3,0x14,0x14,0x16,0x14,0x16,0xe,0xf1,0x18,0x1e,0x1d,0x22,0x6,0x40,0x1a,0x10,0xc,0xd2,0xd,0xd, + 0xd,0x16,0xf,0xe,0xe9,0x16,0x16,0x21,0x1a,0x10,0xc,0xd2,0xd,0xd,0xd,0x16,0xf,0xe,0xe9,0x16,0x16,0x0,0x0,0x1,0x1,0xe,0x5,0x60,0x3,0xbe, + 0x5,0xf0,0x0,0xd,0x0,0x1f,0x40,0x1c,0x2,0x1,0x1,0x0,0x0,0x1,0x55,0x2,0x1,0x1,0x1,0x0,0x59,0x0,0x0,0x1,0x0,0x4d,0x0,0x0,0x0,0xd, + 0x0,0xb,0x34,0x3,0x7,0x15,0x2b,0x0,0x16,0x15,0x14,0x6,0x23,0x21,0x22,0x26,0x35,0x34,0x36,0x33,0x21,0x3,0xa1,0x1d,0x1d,0x22,0xfd,0xce,0x22,0x1d, + 0x1d,0x22,0x2,0x32,0x5,0xf0,0x21,0x27,0x27,0x21,0x21,0x27,0x27,0x21,0x0,0x0,0xff,0xff,0x1,0x9b,0xfe,0x28,0x3,0x29,0x0,0x1c,0x0,0x2,0x1,0x98, + 0x0,0x0,0xff,0xff,0x1,0x70,0x5,0x37,0x3,0x5c,0x7,0x23,0x1,0x7,0x1,0x99,0x0,0x0,0x1,0xe,0x0,0x9,0xb1,0x0,0x2,0xb8,0x1,0xe,0xb0,0x33, + 0x2b,0x0,0x0,0x0,0x0,0x1,0x1,0xb,0x5,0x4c,0x3,0xc1,0x6,0x1f,0x0,0x23,0x0,0x60,0x4b,0xb0,0x22,0x50,0x58,0x40,0x1b,0x0,0x0,0x3,0x2,0x0, + 0x57,0x6,0x5,0x2,0x1,0x0,0x3,0x2,0x1,0x3,0x63,0x0,0x0,0x0,0x2,0x5b,0x4,0x1,0x2,0x0,0x2,0x4f,0x1b,0x40,0x29,0x0,0x1,0x5,0x0,0x5, + 0x1,0x0,0x70,0x0,0x4,0x3,0x2,0x3,0x4,0x2,0x70,0x0,0x0,0x3,0x2,0x0,0x57,0x6,0x1,0x5,0x0,0x3,0x4,0x5,0x3,0x63,0x0,0x0,0x0,0x2, + 0x5b,0x0,0x2,0x0,0x2,0x4f,0x59,0x40,0xe,0x0,0x0,0x0,0x23,0x0,0x22,0x13,0x24,0x26,0x13,0x24,0x7,0x7,0x19,0x2b,0x0,0x16,0x17,0x16,0x16,0x33, + 0x32,0x37,0x36,0x33,0x32,0x17,0x16,0x15,0x14,0x7,0x6,0x23,0x22,0x26,0x27,0x26,0x26,0x23,0x22,0x7,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x36,0x33, + 0x2,0x6,0x3b,0x30,0x2d,0x36,0x1d,0x41,0x46,0xa,0xa,0x17,0x13,0xb,0x11,0x6d,0x5b,0x22,0x3b,0x30,0x2d,0x36,0x1d,0x40,0x47,0xb,0x9,0x17,0x13,0xb, + 0x11,0x6d,0x5b,0x6,0x1f,0x10,0x11,0x10,0xe,0x2a,0x6,0x1f,0x12,0x13,0x18,0xd,0x5b,0x10,0x11,0x10,0xe,0x2a,0x6,0x1f,0x12,0x13,0x18,0xd,0x5b,0x0, + 0x0,0x0,0x0,0x1,0x1,0x5f,0xfe,0x28,0x3,0x62,0x0,0x1c,0x0,0x20,0x0,0x75,0xb1,0x6,0x64,0x44,0xb5,0xb,0x1,0x1,0x3,0x1,0x4a,0x4b,0xb0,0xa, + 0x50,0x58,0x40,0x27,0x0,0x0,0x5,0x4,0x3,0x0,0x68,0x0,0x2,0x4,0x3,0x4,0x2,0x3,0x70,0x0,0x5,0x0,0x4,0x2,0x5,0x4,0x63,0x0,0x3,0x1, + 0x1,0x3,0x57,0x0,0x3,0x3,0x1,0x5c,0x0,0x1,0x3,0x1,0x50,0x1b,0x40,0x28,0x0,0x0,0x5,0x4,0x5,0x0,0x4,0x70,0x0,0x2,0x4,0x3,0x4,0x2, + 0x3,0x70,0x0,0x5,0x0,0x4,0x2,0x5,0x4,0x63,0x0,0x3,0x1,0x1,0x3,0x57,0x0,0x3,0x3,0x1,0x5c,0x0,0x1,0x3,0x1,0x50,0x59,0x40,0x9,0x14, + 0x24,0x24,0x17,0x24,0x10,0x6,0x7,0x1a,0x2b,0xb1,0x6,0x0,0x44,0x5,0x36,0x16,0x15,0x14,0x6,0x23,0x22,0x26,0x27,0x26,0x35,0x34,0x37,0x36,0x33,0x32, + 0x17,0x16,0x16,0x33,0x32,0x36,0x35,0x34,0x26,0x23,0x7,0x22,0x26,0x35,0x35,0x33,0x2,0x97,0x65,0x66,0x7c,0x79,0x44,0x80,0x33,0x17,0x8,0x11,0x1d,0x8, + 0x5,0x2b,0x67,0x2f,0x33,0x3a,0x36,0x3e,0x27,0xf,0x13,0x84,0x71,0x1,0x5e,0x4b,0x57,0x68,0x18,0x18,0xa,0x19,0xe,0x15,0x2d,0x2,0x10,0x16,0x21,0x1f, + 0x21,0x20,0x1,0x15,0x17,0xcd,0x0,0x0,0x0,0x1,0x1,0x27,0x4,0xf,0x3,0xa5,0x5,0x74,0x0,0x17,0x0,0x28,0xb1,0x6,0x64,0x44,0x40,0x1d,0x12,0xb, + 0x2,0x0,0x2,0x1,0x4a,0x3,0x1,0x2,0x0,0x2,0x72,0x1,0x1,0x0,0x0,0x69,0x0,0x0,0x0,0x17,0x0,0x16,0x25,0x17,0x4,0x7,0x16,0x2b,0xb1,0x6, + 0x0,0x44,0x0,0x17,0x17,0x16,0x15,0x14,0x7,0x6,0x23,0x22,0x27,0x27,0x7,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x37,0x36,0x33,0x2,0x8a,0x16,0xf5, + 0x10,0x17,0x15,0x17,0x10,0xf,0xdd,0xdd,0xf,0x10,0x17,0x15,0x17,0x10,0xf5,0x16,0x24,0x5,0x74,0x14,0xe9,0xf,0x12,0x15,0x1a,0x18,0xc,0xad,0xad,0xc, + 0x18,0x1a,0x15,0x12,0xf,0xe9,0x14,0x0,0x0,0x2,0x1,0x35,0x4,0x4f,0x3,0x97,0x5,0x53,0x0,0xd,0x0,0x1b,0x0,0x25,0xb1,0x6,0x64,0x44,0x40,0x1a, + 0x3,0x1,0x1,0x0,0x0,0x1,0x57,0x3,0x1,0x1,0x1,0x0,0x5b,0x2,0x1,0x0,0x1,0x0,0x4f,0x25,0x25,0x25,0x21,0x4,0x7,0x18,0x2b,0xb1,0x6,0x0, + 0x44,0x0,0x6,0x23,0x22,0x26,0x35,0x35,0x34,0x36,0x33,0x32,0x16,0x15,0x15,0x4,0x6,0x23,0x22,0x26,0x35,0x35,0x34,0x36,0x33,0x32,0x16,0x15,0x15,0x1, + 0xf3,0x28,0x37,0x37,0x28,0x28,0x37,0x37,0x28,0x1,0xa4,0x28,0x37,0x37,0x28,0x28,0x37,0x37,0x28,0x4,0x6c,0x1d,0x1d,0x24,0x82,0x24,0x1d,0x1d,0x24,0x82, + 0x24,0x1d,0x1d,0x24,0x82,0x24,0x1d,0x1d,0x24,0x82,0x0,0x0,0x0,0x1,0x2,0x7,0x4,0x4f,0x2,0xc5,0x5,0x53,0x0,0xd,0x0,0x20,0xb1,0x6,0x64,0x44, + 0x40,0x15,0x0,0x1,0x0,0x0,0x1,0x57,0x0,0x1,0x1,0x0,0x5b,0x0,0x0,0x1,0x0,0x4f,0x25,0x21,0x2,0x7,0x16,0x2b,0xb1,0x6,0x0,0x44,0x0,0x6, + 0x23,0x22,0x26,0x35,0x35,0x34,0x36,0x33,0x32,0x16,0x15,0x15,0x2,0xc5,0x28,0x37,0x37,0x28,0x28,0x37,0x37,0x28,0x4,0x6c,0x1d,0x1d,0x24,0x82,0x24,0x1d, + 0x1d,0x24,0x82,0x0,0x0,0x0,0x0,0x1,0x1,0x39,0x4,0xe,0x3,0x2b,0x5,0x87,0x0,0x11,0x0,0x19,0xb1,0x6,0x64,0x44,0x40,0xe,0x0,0x1,0x0,0x1, + 0x72,0x0,0x0,0x0,0x69,0x18,0x15,0x2,0x7,0x16,0x2b,0xb1,0x6,0x0,0x44,0x1,0x16,0x15,0x14,0x7,0x6,0x23,0x22,0x27,0x25,0x26,0x35,0x34,0x37,0x36, + 0x33,0x32,0x17,0x3,0x1b,0x10,0x18,0x16,0x18,0xe,0x12,0xfe,0x8a,0x16,0x1e,0x21,0x22,0x13,0x12,0x4,0x7a,0xc,0x13,0x19,0x1b,0x19,0xa,0xdb,0xd,0x1a, + 0x1d,0x27,0x29,0xd,0x0,0x2,0x1,0x3d,0x4,0xf,0x4,0x4f,0x5,0x76,0x0,0x11,0x0,0x23,0x0,0x26,0xb1,0x6,0x64,0x44,0x40,0x1b,0x1c,0x13,0xa,0x1, + 0x4,0x0,0x1,0x1,0x4a,0x3,0x1,0x1,0x0,0x1,0x72,0x2,0x1,0x0,0x0,0x69,0x27,0x27,0x27,0x25,0x4,0x7,0x18,0x2b,0xb1,0x6,0x0,0x44,0x0,0x15, + 0x14,0x7,0x5,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x37,0x36,0x33,0x32,0x17,0x4,0x15,0x14,0x7,0x5,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x37, + 0x36,0x33,0x32,0x17,0x2,0xd3,0x13,0xfe,0xf2,0x11,0x14,0x16,0x1c,0x1e,0xa,0xed,0x17,0x20,0x1c,0x1f,0x1,0xa9,0x13,0xfe,0xf2,0x11,0x14,0x16,0x1c,0x1e, + 0xa,0xed,0x17,0x20,0x1c,0x1f,0x5,0x47,0x1d,0x13,0x11,0xe9,0xe,0x11,0x13,0x16,0xd,0xa,0xfd,0x19,0x13,0x1c,0x1d,0x13,0x11,0xe9,0xe,0x11,0x13,0x16, + 0xd,0xa,0xfd,0x19,0x13,0x0,0x0,0x1,0x1,0x22,0x4,0x52,0x3,0xaa,0x4,0xe2,0x0,0xd,0x0,0x27,0xb1,0x6,0x64,0x44,0x40,0x1c,0x2,0x1,0x1,0x0, + 0x0,0x1,0x55,0x2,0x1,0x1,0x1,0x0,0x59,0x0,0x0,0x1,0x0,0x4d,0x0,0x0,0x0,0xd,0x0,0xb,0x34,0x3,0x7,0x15,0x2b,0xb1,0x6,0x0,0x44,0x0, + 0x16,0x15,0x14,0x6,0x23,0x21,0x22,0x26,0x35,0x34,0x36,0x33,0x21,0x3,0x8d,0x1d,0x1d,0x22,0xfd,0xf6,0x22,0x1d,0x1d,0x22,0x2,0xa,0x4,0xe2,0x21,0x27, + 0x27,0x21,0x21,0x27,0x27,0x21,0x0,0x0,0x0,0x1,0x1,0x9b,0xfe,0x28,0x3,0x29,0x0,0x1c,0x0,0x19,0x0,0x4d,0xb1,0x6,0x64,0x44,0x4b,0xb0,0x1b,0x50, + 0x58,0x40,0x17,0x0,0x3,0x0,0x3,0x72,0x1,0x1,0x0,0x2,0x2,0x0,0x57,0x1,0x1,0x0,0x0,0x2,0x5c,0x0,0x2,0x0,0x2,0x50,0x1b,0x40,0x1a,0x0, + 0x3,0x1,0x3,0x72,0x0,0x1,0x0,0x1,0x72,0x0,0x0,0x2,0x2,0x0,0x57,0x0,0x0,0x0,0x2,0x5c,0x0,0x2,0x0,0x2,0x50,0x59,0xb6,0x15,0x27,0x31, + 0x24,0x4,0x7,0x18,0x2b,0xb1,0x6,0x0,0x44,0x4,0x6,0x15,0x14,0x16,0x33,0x32,0x37,0x36,0x33,0x32,0x17,0x16,0x15,0x14,0x6,0x7,0x6,0x23,0x22,0x26, + 0x35,0x34,0x36,0x37,0x33,0x2,0x92,0x5b,0x2a,0x23,0x34,0x3d,0x8,0x3,0x1e,0x9,0x2,0x11,0x11,0x4b,0x59,0x5d,0x6b,0x61,0x5b,0xa5,0x42,0x86,0x3c,0x23, + 0x26,0x11,0x2,0x36,0x12,0x7,0x16,0x19,0x6,0x1a,0x68,0x57,0x51,0x96,0x4e,0x0,0x0,0x0,0x0,0x2,0x1,0x70,0x4,0x29,0x3,0x5c,0x6,0x15,0x0,0xf, + 0x0,0x1b,0x0,0x37,0xb1,0x6,0x64,0x44,0x40,0x2c,0x4,0x1,0x1,0x5,0x1,0x3,0x2,0x1,0x3,0x63,0x0,0x2,0x0,0x0,0x2,0x57,0x0,0x2,0x2,0x0, + 0x5b,0x0,0x0,0x2,0x0,0x4f,0x10,0x10,0x0,0x0,0x10,0x1b,0x10,0x1a,0x16,0x14,0x0,0xf,0x0,0xe,0x26,0x6,0x7,0x15,0x2b,0xb1,0x6,0x0,0x44,0x0, + 0x16,0x16,0x15,0x14,0x6,0x6,0x23,0x22,0x26,0x26,0x35,0x34,0x36,0x36,0x33,0x6,0x6,0x15,0x14,0x16,0x33,0x32,0x36,0x35,0x34,0x26,0x23,0x2,0xab,0x71, + 0x40,0x40,0x71,0x45,0x45,0x71,0x40,0x40,0x71,0x45,0x34,0x3e,0x3e,0x34,0x34,0x3e,0x3e,0x34,0x6,0x15,0x40,0x71,0x45,0x45,0x71,0x40,0x40,0x71,0x45,0x45, + 0x71,0x40,0x84,0x3e,0x34,0x34,0x3e,0x3e,0x34,0x34,0x3e,0x0,0x0,0x1,0x1,0xe,0x4,0x3e,0x3,0xbe,0x5,0x2d,0x0,0x27,0x0,0x81,0xb1,0x6,0x64,0x44, + 0x4b,0xb0,0x31,0x50,0x58,0x40,0xa,0xe,0x1,0x0,0x1,0x22,0x1,0x2,0x3,0x2,0x4a,0x1b,0x40,0xa,0xe,0x1,0x0,0x5,0x22,0x1,0x2,0x3,0x2,0x4a, + 0x59,0x4b,0xb0,0x31,0x50,0x58,0x40,0x1b,0x0,0x0,0x3,0x2,0x0,0x57,0x6,0x5,0x2,0x1,0x0,0x3,0x2,0x1,0x3,0x63,0x0,0x0,0x0,0x2,0x5b,0x4, + 0x1,0x2,0x0,0x2,0x4f,0x1b,0x40,0x23,0x0,0x1,0x5,0x1,0x72,0x0,0x4,0x2,0x4,0x73,0x0,0x0,0x3,0x2,0x0,0x57,0x6,0x1,0x5,0x0,0x3,0x2, + 0x5,0x3,0x63,0x0,0x0,0x0,0x2,0x5b,0x0,0x2,0x0,0x2,0x4f,0x59,0x40,0xe,0x0,0x0,0x0,0x27,0x0,0x26,0x23,0x24,0x27,0x23,0x24,0x7,0x7,0x19, + 0x2b,0xb1,0x6,0x0,0x44,0x0,0x16,0x17,0x16,0x16,0x33,0x32,0x36,0x37,0x36,0x33,0x32,0x17,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x22,0x26,0x27,0x26,0x26, + 0x23,0x22,0x6,0x7,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x2,0x11,0x39,0x28,0x23,0x30,0x1a,0x20,0x3d,0x21,0xc,0x11,0x19,0x16,0x15, + 0xe,0x30,0x6d,0x35,0x23,0x39,0x28,0x23,0x30,0x1a,0x20,0x3d,0x21,0xc,0x11,0x19,0x16,0x15,0xe,0x30,0x6d,0x35,0x5,0x23,0x13,0x12,0x11,0x10,0x22,0x22, + 0xc,0x19,0x16,0x17,0x13,0x11,0x3e,0x3d,0x13,0x12,0x11,0x10,0x22,0x22,0xc,0x19,0x16,0x17,0x13,0x11,0x3e,0x3d,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x3, + 0x4,0x9b,0x81,0xb5,0x3,0x2d,0x5f,0xf,0x3c,0xf5,0x0,0x1,0x8,0x0,0x0,0x0,0x0,0x0,0xd2,0x42,0x4a,0x4,0x0,0x0,0x0,0x0,0xd2,0x42,0x4a,0x5a, + 0xfd,0x9,0xfc,0xba,0xc,0x8b,0x7,0x23,0x0,0x0,0x0,0x7,0x0,0x2,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0x0,0x0,0x5,0x8d,0xfd,0x7d,0x0,0x0, + 0xe,0x64,0xfd,0x9,0xfe,0x39,0xc,0x8b,0x0,0x1,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0x81,0x4,0x6c,0x0,0xd3, + 0x4,0xcc,0x0,0x0,0x4,0xcc,0x0,0x64,0x4,0xcc,0x0,0x64,0x4,0xcc,0x0,0x64,0x4,0xcc,0x0,0x64,0x4,0xcc,0x0,0x64,0x4,0xcc,0x0,0x64,0x4,0xcc, + 0x0,0x64,0x4,0xcc,0x0,0x64,0x4,0xcc,0x0,0x64,0x4,0xcc,0x0,0x64,0x4,0xcc,0x0,0x14,0x4,0xcc,0x0,0xcd,0x4,0xcc,0x0,0x96,0x4,0xcc,0x0,0x96, + 0x4,0xcc,0x0,0x96,0x4,0xcc,0x0,0x96,0x4,0xcc,0x0,0x96,0x4,0xcc,0x0,0x96,0x4,0xcc,0x0,0xaf,0x4,0xcc,0x0,0x32,0x4,0xcc,0x0,0xaf,0x4,0xcc, + 0x0,0xaf,0x4,0xcc,0x0,0xe1,0x4,0xcc,0x0,0xe1,0x4,0xcc,0x0,0xe1,0x4,0xcc,0x0,0xe1,0x4,0xcc,0x0,0xe1,0x4,0xcc,0x0,0xe1,0x4,0xcc,0x0,0xe1, + 0x4,0xcc,0x0,0xe1,0x4,0xcc,0x0,0xe1,0x4,0xcc,0x0,0xe1,0x4,0xcc,0x0,0xf5,0x4,0xcc,0x0,0x6e,0x4,0xcc,0x0,0x6e,0x4,0xcc,0x0,0x6e,0x4,0xcc, + 0x0,0x6e,0x4,0xcc,0x0,0x6e,0x4,0xcc,0x0,0xaf,0x4,0xcc,0x0,0x1e,0x4,0xcc,0x0,0xaf,0x4,0xcc,0x0,0xc8,0x4,0xcc,0x0,0xb3,0x4,0xcc,0x0,0xc8, + 0x4,0xcc,0x0,0xc8,0x4,0xcc,0x0,0xc8,0x4,0xcc,0x0,0xc8,0x4,0xcc,0x0,0xc8,0x4,0xcc,0x0,0xc8,0x4,0xcc,0x0,0xc8,0x4,0xcc,0x0,0xc8,0x4,0xcc, + 0x0,0xc8,0x4,0xcc,0x0,0xb8,0x4,0xcc,0x0,0xb8,0x4,0xcc,0x0,0xd7,0x4,0xcc,0x0,0xd7,0x4,0xcc,0x0,0xf5,0x4,0xcc,0x0,0xe1,0x4,0xcc,0x0,0xf5, + 0x4,0xcc,0x0,0xf5,0x4,0xcc,0x0,0xf5,0x4,0xcc,0x0,0x3c,0x4,0xcc,0x0,0x90,0x4,0xcc,0x0,0xaf,0x4,0xcc,0x0,0xaf,0x4,0xcc,0x0,0xaf,0x4,0xcc, + 0x0,0xaf,0x4,0xcc,0x0,0xaf,0x4,0xcc,0x0,0xaf,0x4,0xcc,0x0,0x5a,0x4,0xcc,0x0,0x5a,0x4,0xcc,0x0,0x5a,0x4,0xcc,0x0,0x5a,0x4,0xcc,0x0,0x5a, + 0x4,0xcc,0x0,0x5a,0x4,0xcc,0x0,0x5a,0x4,0xcc,0x0,0x5a,0x4,0xcc,0x0,0x5a,0x4,0xcc,0x0,0x5a,0x4,0xcc,0x0,0x54,0x4,0xcc,0x0,0xeb,0x4,0xcc, + 0x0,0xeb,0x4,0xcc,0x0,0x5a,0x4,0xcc,0x0,0xeb,0x4,0xcc,0x0,0xeb,0x4,0xcc,0x0,0xeb,0x4,0xcc,0x0,0xeb,0x4,0xcc,0x0,0xa7,0x4,0xcc,0x0,0xa7, + 0x4,0xcc,0x0,0xa7,0x4,0xcc,0x0,0xa7,0x4,0xcc,0x0,0xa7,0x4,0xcc,0x0,0xa7,0x4,0xcc,0x0,0x7a,0x4,0xcc,0x0,0x7a,0x4,0xcc,0x0,0x7a,0x4,0xcc, + 0x0,0x7a,0x4,0xcc,0x0,0x7a,0x4,0xcc,0x0,0xaf,0x4,0xcc,0x0,0xaf,0x4,0xcc,0x0,0xaf,0x4,0xcc,0x0,0xaf,0x4,0xcc,0x0,0xaf,0x4,0xcc,0x0,0xaf, + 0x4,0xcc,0x0,0xaf,0x4,0xcc,0x0,0xaf,0x4,0xcc,0x0,0xaf,0x4,0xcc,0x0,0xaf,0x4,0xcc,0x0,0xaf,0x4,0xcc,0x0,0x63,0x4,0xcc,0x0,0x43,0x4,0xcc, + 0x0,0x43,0x4,0xcc,0x0,0x43,0x4,0xcc,0x0,0x43,0x4,0xcc,0x0,0x43,0x4,0xcc,0x0,0x95,0x4,0xcc,0x0,0x7d,0x4,0xcc,0x0,0x7d,0x4,0xcc,0x0,0x7d, + 0x4,0xcc,0x0,0x7d,0x4,0xcc,0x0,0x7d,0x4,0xcc,0x0,0xa7,0x4,0xcc,0x0,0xa7,0x4,0xcc,0x0,0xa7,0x4,0xcc,0x0,0xa7,0x4,0xcc,0x0,0x9f,0x4,0xcc, + 0x0,0x9f,0x4,0xcc,0x0,0x9f,0x4,0xcc,0x0,0x9f,0x4,0xcc,0x0,0x9f,0x4,0xcc,0x0,0x9f,0x4,0xcc,0x0,0x9f,0x4,0xcc,0x0,0x9f,0x4,0xcc,0x0,0x9f, + 0x4,0xcc,0x0,0x9f,0x4,0xcc,0x0,0x4b,0x4,0xcc,0x0,0xc0,0x4,0xcc,0x0,0x93,0x4,0xcc,0x0,0x93,0x4,0xcc,0x0,0x93,0x4,0xcc,0x0,0x93,0x4,0xcc, + 0x0,0x93,0x4,0xcc,0x0,0x93,0x4,0xcc,0x0,0x8b,0x4,0xcc,0x0,0x8c,0x4,0xcc,0x0,0x89,0x4,0xcc,0x0,0x9f,0x4,0xcc,0x0,0x94,0x4,0xcc,0x0,0x94, + 0x4,0xcc,0x0,0x94,0x4,0xcc,0x0,0x94,0x4,0xcc,0x0,0x94,0x4,0xcc,0x0,0x94,0x4,0xcc,0x0,0x94,0x4,0xcc,0x0,0x94,0x4,0xcc,0x0,0x94,0x4,0xcc, + 0x0,0x94,0x4,0xcc,0x0,0xbb,0x4,0xcc,0x0,0x8b,0x4,0xcc,0x0,0x8b,0x4,0xcc,0x0,0x8b,0x4,0xcc,0x0,0x8b,0x4,0xcc,0x0,0x8b,0x4,0xcc,0x0,0xca, + 0x4,0xcc,0x0,0x14,0x4,0xcc,0x0,0xca,0x4,0xcc,0x0,0xbb,0x4,0xcc,0x0,0xbb,0x4,0xcc,0x0,0xbb,0x4,0xcc,0x0,0xbb,0x4,0xcc,0x0,0xbb,0x4,0xcc, + 0x0,0xbb,0x4,0xcc,0x0,0xbb,0x4,0xcc,0x0,0xbb,0x4,0xcc,0x0,0xde,0x4,0xcc,0x0,0xbb,0x4,0xcc,0x0,0xbb,0x4,0xcc,0x0,0xbb,0x4,0xcc,0x0,0xbe, + 0x4,0xcc,0x0,0xbe,0x4,0xcc,0x0,0xbe,0x4,0xcc,0x0,0xe8,0x4,0xcc,0x0,0xe8,0x4,0xcc,0x0,0xb1,0x4,0xcc,0x0,0xb1,0x4,0xcc,0x0,0x93,0x4,0xcc, + 0x0,0xb1,0x4,0xcc,0x0,0x93,0x4,0xcc,0x0,0xb1,0x4,0xcc,0x0,0x79,0x4,0xcc,0x0,0xca,0x4,0xcc,0x0,0xca,0x4,0xcc,0x0,0xca,0x4,0xcc,0x0,0xca, + 0x4,0xcc,0x0,0xca,0x4,0xcc,0x0,0xca,0x4,0xcc,0x0,0x86,0x4,0xcc,0x0,0x86,0x4,0xcc,0x0,0x86,0x4,0xcc,0x0,0x86,0x4,0xcc,0x0,0x86,0x4,0xcc, + 0x0,0x86,0x4,0xcc,0x0,0x86,0x4,0xcc,0x0,0x86,0x4,0xcc,0x0,0x54,0x4,0xcc,0x0,0x86,0x4,0xcc,0x0,0x46,0x4,0xcc,0x0,0xc0,0x4,0xcc,0x0,0xc0, + 0x4,0xcc,0x0,0x8b,0x4,0xcc,0x1,0x33,0x4,0xcc,0x1,0x33,0x4,0xcc,0x1,0x33,0x4,0xcc,0x1,0x33,0x4,0xcc,0x0,0xb2,0x4,0xcc,0x0,0xb2,0x4,0xcc, + 0x0,0xb2,0x4,0xcc,0x0,0xb2,0x4,0xcc,0x0,0xb2,0x4,0xcc,0x0,0xb2,0x4,0xcc,0x0,0xeb,0x4,0xcc,0x0,0x75,0x4,0xcc,0x0,0x75,0x4,0xcc,0x0,0x75, + 0x4,0xcc,0x0,0x75,0x4,0xcc,0x0,0x75,0x4,0xcc,0x0,0xb8,0x4,0xcc,0x0,0xb8,0x4,0xcc,0x0,0xb8,0x4,0xcc,0x0,0xb8,0x4,0xcc,0x0,0xb8,0x4,0xcc, + 0x0,0xb8,0x4,0xcc,0x0,0xb8,0x4,0xcc,0x0,0xbb,0x4,0xcc,0x0,0xb8,0x4,0xcc,0x0,0xb8,0x4,0xcc,0x0,0xb8,0x4,0xcc,0x0,0x7c,0x4,0xcc,0x0,0x3a, + 0x4,0xcc,0x0,0x3a,0x4,0xcc,0x0,0x3a,0x4,0xcc,0x0,0x3a,0x4,0xcc,0x0,0x3a,0x4,0xcc,0x0,0xa7,0x4,0xcc,0x0,0x7b,0x4,0xcc,0x0,0x7b,0x4,0xcc, + 0x0,0x7b,0x4,0xcc,0x0,0x7b,0x4,0xcc,0x0,0x7b,0x4,0xcc,0x0,0xb2,0x4,0xcc,0x0,0xb2,0x4,0xcc,0x0,0xb2,0x4,0xcc,0x0,0xb2,0x9,0x98,0x0,0xbb, + 0x9,0x98,0x0,0xbb,0x4,0xcc,0x0,0xfc,0x4,0xcc,0x0,0xeb,0x4,0xcc,0x0,0x93,0x4,0xcc,0x0,0x51,0x4,0xcc,0x0,0xda,0x4,0xcc,0x0,0x62,0x4,0xcc, + 0x0,0xbe,0x4,0xcc,0x0,0xdc,0x4,0xcc,0x0,0xc5,0x4,0xcc,0x0,0xb5,0x4,0xcc,0x0,0x91,0x4,0xcc,0x0,0xbc,0x4,0xcc,0x0,0xcb,0x4,0xcc,0x0,0xc7, + 0x4,0xcc,0x0,0xd2,0x4,0xcc,0x0,0xc2,0x4,0xcc,0x0,0x6f,0x4,0xcc,0x0,0x1,0x4,0xcc,0x0,0x1,0x4,0xcc,0x0,0x15,0x4,0xcc,0x1,0x3b,0x4,0xcc, + 0x1,0x20,0x4,0xcc,0x1,0x2e,0x4,0xcc,0x0,0xf2,0x4,0xcc,0x3,0xb,0x4,0xcc,0x2,0xa0,0x4,0xcc,0x3,0xb,0x4,0xcc,0x2,0xb4,0x4,0xcc,0xfe,0x39, + 0xe,0x64,0x3,0x5,0x4,0xcc,0x1,0x18,0x4,0xcc,0xfe,0x20,0xe,0x64,0x3,0x5,0x4,0xcc,0x0,0x6a,0x4,0xcc,0x0,0xc6,0x4,0xcc,0x1,0xdf,0x4,0xcc, + 0x1,0x7e,0x4,0xcc,0x1,0xdf,0x4,0xcc,0x1,0x74,0x4,0xcc,0x0,0x6d,0x4,0xcc,0x1,0xe9,0x4,0xcc,0x1,0xe9,0x4,0xcc,0x0,0x3c,0x4,0xcc,0x1,0xdf, + 0x4,0xcc,0x1,0x6,0x4,0xcc,0x1,0x6,0x4,0xcc,0x1,0x4,0x4,0xcc,0x1,0xe5,0x4,0xcc,0x1,0x88,0x4,0xcc,0x0,0xc6,0x4,0xcc,0x0,0x46,0x4,0xcc, + 0x0,0xb1,0x4,0xcc,0x0,0xee,0x4,0xcc,0x1,0x4a,0x4,0xcc,0x1,0x66,0x4,0xcc,0x1,0x29,0x4,0xcc,0x1,0x6b,0x4,0xcc,0x1,0xe,0x4,0xcc,0xfe,0x52, + 0x9,0x98,0xff,0xf0,0x9,0x98,0x1,0x1c,0x4,0xcc,0xff,0x1a,0x4,0xcc,0xff,0xe2,0x4,0xcc,0x0,0xaa,0x4,0xcc,0x0,0xaa,0x4,0xcc,0x0,0xaa,0x0,0x0, + 0x0,0x0,0x4,0xcc,0x0,0x99,0x4,0xcc,0x0,0x9d,0x4,0xcc,0x1,0x89,0x4,0xcc,0x1,0x8d,0x4,0xcc,0x0,0xc0,0x4,0xcc,0x0,0xb1,0x4,0xcc,0x0,0xc0, + 0x4,0xcc,0x1,0xa6,0x4,0xcc,0x1,0xb5,0x4,0xcc,0x1,0xb5,0x4,0xcc,0x0,0x0,0x4,0xcc,0x0,0x93,0x4,0xcc,0x0,0x6f,0x4,0xcc,0x0,0xae,0x4,0xcc, + 0x0,0x5a,0x4,0xcc,0x0,0x21,0x4,0xcc,0x0,0x32,0x4,0xcc,0x0,0xdc,0x4,0xcc,0xff,0xe2,0x4,0xcc,0x0,0x7d,0x4,0xcc,0x0,0x96,0x4,0xcc,0x0,0x96, + 0x4,0xcc,0x0,0xaa,0x4,0xcc,0x0,0xaa,0x4,0xcc,0x0,0xaf,0x4,0xcc,0x0,0xaa,0x4,0xcc,0x0,0x1c,0x4,0xcc,0x0,0x5,0x4,0xcc,0x0,0x7d,0x4,0xcc, + 0x0,0xaa,0x4,0xcc,0x0,0x8c,0x4,0xcc,0x0,0xaa,0x4,0xcc,0x0,0xe7,0x4,0xcc,0x0,0xaa,0x4,0xcc,0x0,0xa7,0x4,0xcc,0x0,0x32,0x4,0xcc,0x0,0x32, + 0x4,0xcc,0x0,0xaa,0x4,0xcc,0x0,0xaa,0x4,0xcc,0x0,0xc3,0x4,0xcc,0x0,0x74,0x4,0xcc,0x0,0xa5,0x4,0xcc,0x0,0xcd,0x4,0xcc,0x2,0x1b,0x4,0xcc, + 0x2,0x1b,0x4,0xcc,0x0,0x32,0x4,0xcc,0x0,0x9d,0x4,0xcc,0x0,0x83,0x4,0xcc,0x0,0x24,0x4,0xcc,0x0,0x24,0x4,0xcc,0x0,0xc1,0x4,0xcc,0x0,0x3c, + 0x4,0xcc,0x0,0xf6,0x4,0xcc,0x0,0xe8,0x4,0xcc,0x1,0x22,0x4,0xcc,0x1,0x22,0x2,0x58,0x0,0x0,0x2,0x58,0x0,0x0,0x0,0x0,0xfd,0x9,0x0,0x0, + 0xfd,0x9,0x4,0xcc,0x1,0xa1,0x0,0xfa,0x1,0x1e,0x1,0x27,0x1,0x8e,0x0,0xec,0x1,0xa,0x1,0x5f,0x1,0xa,0x1,0x35,0x2,0x7,0x1,0x9,0x1,0x2d, + 0x1,0xe,0x1,0x9b,0x1,0x70,0x1,0xb,0x1,0x5f,0x1,0x27,0x1,0x35,0x2,0x7,0x1,0x39,0x1,0x3d,0x1,0x22,0x1,0x9b,0x1,0x70,0x1,0xe,0x0,0x0, + 0x0,0x78,0x0,0x78,0x0,0xc8,0x0,0xda,0x0,0xec,0x0,0xfe,0x1,0x10,0x1,0x22,0x1,0x34,0x1,0xc2,0x1,0xd4,0x1,0xe6,0x2,0x74,0x2,0xd6,0x3,0x34, + 0x3,0x46,0x3,0x58,0x4,0xc,0x4,0x1e,0x4,0x30,0x4,0x74,0x4,0xd6,0x4,0xe8,0x4,0xfa,0x5,0x46,0x5,0x58,0x5,0x6a,0x5,0x7c,0x5,0x8e,0x5,0xa0, + 0x5,0xb2,0x5,0xc4,0x5,0xd6,0x6,0x62,0x6,0xa4,0x7,0xe,0x7,0x20,0x7,0x32,0x7,0x3e,0x7,0x50,0x7,0x98,0x8,0x1c,0x8,0x2e,0x8,0x74,0x8,0xea, + 0x8,0xfc,0x9,0xe,0x9,0x20,0x9,0x32,0x9,0x44,0x9,0x56,0x9,0x68,0x9,0xee,0xa,0x0,0xa,0x4a,0xa,0x5c,0xa,0xb6,0xa,0xc2,0xa,0xf4,0xb,0x6, + 0xb,0x18,0xb,0x24,0xb,0x36,0xb,0x92,0xb,0xf0,0xc,0x4e,0xc,0x60,0xc,0x72,0xc,0x7e,0xc,0xe2,0xc,0xf4,0xd,0x3e,0xd,0x50,0xd,0x62,0xd,0x74, + 0xd,0x86,0xd,0x98,0xd,0xaa,0xd,0xbc,0xe,0x38,0xe,0x4a,0xe,0xaa,0xe,0xf4,0xf,0x5a,0xf,0xe0,0x10,0x34,0x10,0x46,0x10,0x58,0x10,0x64,0x10,0xde, + 0x10,0xf0,0x11,0x2,0x12,0x2,0x12,0x14,0x12,0x20,0x12,0x58,0x12,0xac,0x12,0xbe,0x13,0x48,0x13,0x54,0x13,0x94,0x13,0xa6,0x13,0xb8,0x13,0xca,0x13,0xdc, + 0x13,0xee,0x14,0x0,0x14,0x12,0x14,0x8e,0x14,0xa0,0x14,0xb2,0x14,0xf4,0x15,0x62,0x15,0x74,0x15,0x86,0x15,0x98,0x15,0xaa,0x16,0x0,0x16,0x44,0x16,0x56, + 0x16,0x68,0x16,0x7a,0x16,0x8c,0x16,0xce,0x16,0xe0,0x16,0xf2,0x17,0x4,0x17,0x94,0x17,0xa0,0x17,0xac,0x17,0xb8,0x17,0xc4,0x17,0xd0,0x17,0xdc,0x18,0x6e, + 0x18,0x7a,0x18,0x86,0x19,0x26,0x19,0x9e,0x19,0xf8,0x1a,0x4,0x1a,0x10,0x1a,0xc4,0x1a,0xd0,0x1a,0xdc,0x1b,0x54,0x1c,0xe,0x1c,0xc2,0x1d,0x74,0x1d,0xd4, + 0x1d,0xe0,0x1d,0xec,0x1d,0xf8,0x1e,0x4,0x1e,0x10,0x1e,0x1c,0x1e,0x28,0x1e,0x34,0x1e,0xb8,0x1f,0x10,0x1f,0xc0,0x1f,0xcc,0x1f,0xd8,0x20,0xb6,0x20,0xc2, + 0x21,0x12,0x21,0x92,0x21,0xa4,0x21,0xb0,0x21,0xf0,0x21,0xfc,0x22,0x8,0x22,0x14,0x22,0x20,0x22,0x2c,0x22,0x38,0x22,0xe0,0x22,0xec,0x23,0xa8,0x23,0xb4, + 0x23,0xc0,0x24,0xe,0x24,0x1a,0x24,0x68,0x24,0x74,0x24,0xb4,0x24,0xc6,0x24,0xd2,0x24,0xde,0x24,0xf0,0x25,0x5a,0x25,0xd8,0x26,0x36,0x26,0x42,0x26,0x4e, + 0x26,0x5a,0x26,0xdc,0x26,0xe8,0x27,0x30,0x27,0x3c,0x27,0x48,0x27,0x54,0x27,0x60,0x27,0x6c,0x27,0x78,0x27,0x84,0x28,0x14,0x28,0x20,0x28,0xa2,0x29,0x18, + 0x29,0x7c,0x29,0xf2,0x2a,0x70,0x2a,0x7c,0x2a,0x88,0x2a,0x94,0x2b,0xe,0x2b,0x1a,0x2b,0x26,0x2c,0x26,0x2c,0x32,0x2c,0x3e,0x2c,0xbc,0x2d,0x16,0x2d,0x8a, + 0x2d,0x9c,0x2e,0x7e,0x2e,0x8a,0x2e,0xe8,0x2e,0xf4,0x2f,0x0,0x2f,0xc,0x2f,0x18,0x2f,0x24,0x2f,0x30,0x2f,0x3c,0x2f,0xa6,0x2f,0xb2,0x2f,0xbe,0x30,0xc, + 0x30,0x6a,0x30,0x76,0x30,0x82,0x30,0x8e,0x30,0x9a,0x30,0xf0,0x31,0x44,0x31,0x50,0x31,0x5c,0x31,0x68,0x31,0x74,0x31,0xb6,0x31,0xc2,0x31,0xce,0x31,0xda, + 0x31,0xea,0x31,0xf6,0x32,0x40,0x32,0x76,0x32,0xa6,0x32,0xf6,0x33,0x3a,0x33,0x70,0x33,0xcc,0x34,0x16,0x34,0x6e,0x34,0xe4,0x35,0x2e,0x35,0xa0,0x36,0x6, + 0x36,0x42,0x36,0xbc,0x37,0x20,0x37,0x4e,0x37,0xfa,0x38,0xaa,0x39,0xbc,0x39,0xf0,0x3a,0x32,0x3a,0x86,0x3a,0xc0,0x3a,0xf0,0x3a,0xfa,0x3b,0x4,0x3b,0x3a, + 0x3b,0x88,0x3b,0xd6,0x3c,0x22,0x3c,0x6e,0x3c,0xbc,0x3d,0x28,0x3d,0x66,0x3d,0x76,0x3d,0xa0,0x3d,0xe2,0x3e,0x10,0x3e,0x64,0x3e,0xae,0x3e,0xf6,0x3f,0x92, + 0x3f,0xb8,0x40,0x28,0x40,0x98,0x40,0xa6,0x40,0xd4,0x41,0x1a,0x41,0x58,0x41,0x86,0x42,0x4a,0x43,0xc,0x43,0x8a,0x44,0x6,0x44,0x42,0x44,0x7e,0x44,0x9c, + 0x44,0xba,0x44,0xd8,0x44,0xf6,0x45,0x14,0x45,0x3e,0x45,0x68,0x45,0x92,0x45,0x9a,0x45,0x9a,0x45,0xa8,0x45,0xb6,0x45,0xee,0x46,0x26,0x46,0x34,0x46,0x42, + 0x46,0x50,0x46,0x7c,0x46,0xa6,0x46,0xd0,0x46,0xd0,0x47,0x40,0x47,0xca,0x48,0x82,0x49,0x1a,0x49,0x8e,0x49,0xec,0x4a,0x68,0x4b,0x12,0x4b,0x8e,0x4b,0xa6, + 0x4c,0x6,0x4c,0x66,0x4c,0x7e,0x4c,0xbc,0x4c,0xfe,0x4d,0x52,0x4d,0x96,0x4d,0xd4,0x4e,0x16,0x4e,0x4a,0x4e,0x68,0x4e,0xbc,0x4f,0x10,0x4f,0x62,0x50,0xc, + 0x50,0xea,0x51,0x32,0x51,0x94,0x51,0xc0,0x51,0xf6,0x52,0x40,0x52,0x7a,0x52,0xac,0x53,0x2,0x53,0xae,0x54,0x36,0x54,0x7e,0x55,0x22,0x55,0xc0,0x56,0x3e, + 0x56,0x9c,0x56,0xe2,0x57,0x2a,0x57,0x6c,0x58,0x1c,0x58,0x1c,0x58,0x1c,0x58,0x52,0x58,0x5a,0x58,0x8a,0x58,0xb6,0x58,0xfc,0x59,0x38,0x59,0x64,0x59,0xa4, + 0x59,0xde,0x59,0xe6,0x5a,0x20,0x5a,0x5a,0x5a,0x80,0x5a,0xaa,0x5a,0xf2,0x5b,0x1c,0x5b,0x24,0x5b,0x34,0x5b,0x9c,0x5c,0x8,0x5c,0x44,0x5c,0x82,0x5c,0xac, + 0x5c,0xd8,0x5d,0x24,0x5d,0x52,0x5d,0xa2,0x5d,0xea,0x5e,0x67,0x0,0x1,0x0,0x0,0x1,0x9b,0x0,0x6e,0x0,0xa,0x0,0x5e,0x0,0x4,0x0,0x2,0x0,0x26, + 0x0,0x36,0x0,0x77,0x0,0x0,0x0,0x9f,0xb,0xe2,0x0,0x3,0x0,0x1,0x0,0x0,0x0,0x1a,0x1,0x3e,0x0,0x1,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x26, + 0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x0,0x0,0x1,0x0,0x12,0x0,0x26,0x0,0x1,0x0,0x0,0x0,0x0,0x0,0x2,0x0,0x7,0x0,0x38,0x0,0x1,0x0,0x0, + 0x0,0x0,0x0,0x3,0x0,0x23,0x0,0x3f,0x0,0x1,0x0,0x0,0x0,0x0,0x0,0x4,0x0,0x12,0x0,0x62,0x0,0x1,0x0,0x0,0x0,0x0,0x0,0x5,0x0,0xe, + 0x0,0x74,0x0,0x1,0x0,0x0,0x0,0x0,0x0,0x6,0x0,0x18,0x0,0x82,0x0,0x1,0x0,0x0,0x0,0x0,0x0,0x8,0x0,0x12,0x0,0x9a,0x0,0x1,0x0,0x0, + 0x0,0x0,0x0,0x9,0x0,0x11,0x0,0xac,0x0,0x1,0x0,0x0,0x0,0x0,0x0,0xb,0x0,0x1b,0x0,0xbd,0x0,0x1,0x0,0x0,0x0,0x0,0x0,0xc,0x0,0x16, + 0x0,0xd8,0x0,0x1,0x0,0x0,0x0,0x0,0x0,0xd,0x11,0x3,0x0,0xee,0x0,0x1,0x0,0x0,0x0,0x0,0x0,0xe,0x0,0x1a,0x11,0xf1,0x0,0x3,0x0,0x1, + 0x4,0x9,0x0,0x0,0x0,0x4c,0x12,0xb,0x0,0x3,0x0,0x1,0x4,0x9,0x0,0x1,0x0,0x24,0x12,0x57,0x0,0x3,0x0,0x1,0x4,0x9,0x0,0x2,0x0,0xe, + 0x12,0x7b,0x0,0x3,0x0,0x1,0x4,0x9,0x0,0x3,0x0,0x46,0x12,0x89,0x0,0x3,0x0,0x1,0x4,0x9,0x0,0x4,0x0,0x30,0x12,0xcf,0x0,0x3,0x0,0x1, + 0x4,0x9,0x0,0x5,0x0,0x1c,0x12,0xff,0x0,0x3,0x0,0x1,0x4,0x9,0x0,0x6,0x0,0x30,0x13,0x1b,0x0,0x3,0x0,0x1,0x4,0x9,0x0,0x8,0x0,0x24, + 0x13,0x4b,0x0,0x3,0x0,0x1,0x4,0x9,0x0,0x9,0x0,0x22,0x13,0x6f,0x0,0x3,0x0,0x1,0x4,0x9,0x0,0xb,0x0,0x36,0x13,0x91,0x0,0x3,0x0,0x1, + 0x4,0x9,0x0,0xc,0x0,0x2c,0x13,0xc7,0x0,0x3,0x0,0x1,0x4,0x9,0x0,0xd,0x22,0x6,0x13,0xf3,0x0,0x3,0x0,0x1,0x4,0x9,0x0,0xe,0x0,0x34, + 0x35,0xf9,0x43,0x6f,0x70,0x79,0x72,0x69,0x67,0x68,0x74,0x20,0x28,0x63,0x29,0x20,0x32,0x30,0x31,0x35,0x20,0x51,0x75,0x6f,0x74,0x65,0x2d,0x55,0x6e,0x71, + 0x75,0x6f,0x74,0x65,0x20,0x41,0x70,0x70,0x73,0x2e,0x43,0x6f,0x75,0x72,0x69,0x65,0x72,0x20,0x50,0x72,0x69,0x6d,0x65,0x20,0x43,0x6f,0x64,0x65,0x52,0x65, + 0x67,0x75,0x6c,0x61,0x72,0x33,0x2e,0x30,0x31,0x38,0x3b,0x51,0x55,0x51,0x41,0x3b,0x43,0x6f,0x75,0x72,0x69,0x65,0x72,0x50,0x72,0x69,0x6d,0x65,0x43,0x6f, + 0x64,0x65,0x2d,0x52,0x65,0x67,0x75,0x6c,0x61,0x72,0x43,0x6f,0x75,0x72,0x69,0x65,0x72,0x20,0x50,0x72,0x69,0x6d,0x65,0x20,0x43,0x6f,0x64,0x65,0x56,0x65, + 0x72,0x73,0x69,0x6f,0x6e,0x20,0x33,0x2e,0x30,0x33,0x31,0x38,0x43,0x6f,0x75,0x72,0x69,0x65,0x72,0x50,0x72,0x69,0x6d,0x65,0x43,0x6f,0x64,0x65,0x2d,0x52, + 0x65,0x67,0x75,0x6c,0x61,0x72,0x51,0x75,0x6f,0x74,0x65,0x2d,0x55,0x6e,0x71,0x75,0x6f,0x74,0x65,0x20,0x41,0x70,0x70,0x73,0x41,0x6c,0x61,0x6e,0x20,0x44, + 0x61,0x67,0x75,0x65,0x2d,0x47,0x72,0x65,0x65,0x6e,0x65,0x68,0x74,0x74,0x70,0x3a,0x2f,0x2f,0x71,0x75,0x6f,0x74,0x65,0x75,0x6e,0x71,0x75,0x6f,0x74,0x65, + 0x61,0x70,0x70,0x73,0x2e,0x63,0x6f,0x6d,0x68,0x74,0x74,0x70,0x3a,0x2f,0x2f,0x62,0x61,0x73,0x69,0x63,0x72,0x65,0x63,0x69,0x70,0x65,0x2e,0x63,0x6f,0x6d, + 0x43,0x6f,0x70,0x79,0x72,0x69,0x67,0x68,0x74,0x20,0x28,0x63,0x29,0x20,0x32,0x30,0x31,0x35,0xa,0x51,0x75,0x6f,0x74,0x65,0x2d,0x55,0x6e,0x71,0x75,0x6f, + 0x74,0x65,0x20,0x41,0x70,0x70,0x73,0x20,0x28,0x68,0x74,0x74,0x70,0x3a,0x2f,0x2f,0x71,0x75,0x6f,0x74,0x65,0x75,0x6e,0x71,0x75,0x6f,0x74,0x65,0x61,0x70, + 0x70,0x73,0x2e,0x63,0x6f,0x6d,0x29,0xa,0x77,0x69,0x74,0x68,0x20,0x52,0x65,0x73,0x65,0x72,0x76,0x65,0x64,0x20,0x46,0x6f,0x6e,0x74,0x20,0x4e,0x61,0x6d, + 0x65,0x20,0x43,0x6f,0x75,0x72,0x69,0x65,0x72,0x20,0x50,0x72,0x69,0x6d,0x65,0x20,0x43,0x6f,0x64,0x65,0x2e,0xa,0xa,0x54,0x68,0x69,0x73,0x20,0x46,0x6f, + 0x6e,0x74,0x20,0x53,0x6f,0x66,0x74,0x77,0x61,0x72,0x65,0x20,0x69,0x73,0x20,0x6c,0x69,0x63,0x65,0x6e,0x73,0x65,0x64,0x20,0x75,0x6e,0x64,0x65,0x72,0x20, + 0x74,0x68,0x65,0x20,0x53,0x49,0x4c,0x20,0x4f,0x70,0x65,0x6e,0x20,0x46,0x6f,0x6e,0x74,0x20,0x4c,0x69,0x63,0x65,0x6e,0x73,0x65,0xa,0x56,0x65,0x72,0x73, + 0x69,0x6f,0x6e,0x20,0x31,0x2e,0x31,0x2e,0x20,0x54,0x68,0x69,0x73,0x20,0x6c,0x69,0x63,0x65,0x6e,0x73,0x65,0x20,0x69,0x73,0x20,0x63,0x6f,0x70,0x69,0x65, + 0x64,0x20,0x62,0x65,0x6c,0x6f,0x77,0xa,0x61,0x6e,0x64,0x20,0x69,0x73,0x20,0x61,0x6c,0x73,0x6f,0x20,0x61,0x76,0x61,0x69,0x6c,0x61,0x62,0x6c,0x65,0x20, + 0x77,0x69,0x74,0x68,0x20,0x61,0x20,0x46,0x41,0x51,0x20,0x61,0x74,0x3a,0x20,0x68,0x74,0x74,0x70,0x3a,0x2f,0x2f,0x73,0x63,0x72,0x69,0x70,0x74,0x73,0x2e, + 0x73,0x69,0x6c,0x2e,0x6f,0x72,0x67,0x2f,0x4f,0x46,0x4c,0xa,0xa,0xa,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d, + 0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d, + 0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0xa,0x53,0x49,0x4c,0x20,0x4f,0x50,0x45,0x4e,0x20,0x46,0x4f,0x4e,0x54,0x20,0x4c,0x49, + 0x43,0x45,0x4e,0x53,0x45,0x20,0x56,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x31,0x2e,0x31,0x20,0x2d,0x20,0x32,0x36,0x20,0x46,0x65,0x62,0x72,0x75,0x61,0x72, + 0x79,0x20,0x32,0x30,0x30,0x37,0xa,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d, + 0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d, + 0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0xa,0xa,0x50,0x52,0x45,0x41,0x4d,0x42,0x4c,0x45,0xa,0x54,0x68,0x65,0x20,0x67,0x6f,0x61,0x6c,0x73,0x20,0x6f,0x66,0x20, + 0x74,0x68,0x65,0x20,0x4f,0x70,0x65,0x6e,0x20,0x46,0x6f,0x6e,0x74,0x20,0x4c,0x69,0x63,0x65,0x6e,0x73,0x65,0x20,0x28,0x4f,0x46,0x4c,0x29,0x20,0x61,0x72, + 0x65,0x20,0x74,0x6f,0x20,0x73,0x74,0x69,0x6d,0x75,0x6c,0x61,0x74,0x65,0x20,0x77,0x6f,0x72,0x6c,0x64,0x77,0x69,0x64,0x65,0x20,0x64,0x65,0x76,0x65,0x6c, + 0x6f,0x70,0x6d,0x65,0x6e,0x74,0x20,0x6f,0x66,0x20,0x63,0x6f,0x6c,0x6c,0x61,0x62,0x6f,0x72,0x61,0x74,0x69,0x76,0x65,0x20,0x66,0x6f,0x6e,0x74,0x20,0x70, + 0x72,0x6f,0x6a,0x65,0x63,0x74,0x73,0xa,0x74,0x6f,0x20,0x73,0x75,0x70,0x70,0x6f,0x72,0x74,0x20,0x74,0x68,0x65,0x20,0x66,0x6f,0x6e,0x74,0x20,0x63,0x72, + 0x65,0x61,0x74,0x69,0x6f,0x6e,0x20,0x65,0x66,0x66,0x6f,0x72,0x74,0x73,0x20,0x6f,0x66,0x20,0x61,0x63,0x61,0x64,0x65,0x6d,0x69,0x63,0x20,0x61,0x6e,0x64, + 0x20,0x6c,0x69,0x6e,0x67,0x75,0x69,0x73,0x74,0x69,0x63,0x20,0x63,0x6f,0x6d,0x6d,0x75,0x6e,0x69,0x74,0x69,0x65,0x73,0xa,0x61,0x6e,0x64,0x20,0x74,0x6f, + 0x20,0x70,0x72,0x6f,0x76,0x69,0x64,0x65,0x20,0x61,0x20,0x66,0x72,0x65,0x65,0x20,0x61,0x6e,0x64,0x20,0x6f,0x70,0x65,0x6e,0x20,0x66,0x72,0x61,0x6d,0x65, + 0x77,0x6f,0x72,0x6b,0x20,0x69,0x6e,0x20,0x77,0x68,0x69,0x63,0x68,0x20,0x66,0x6f,0x6e,0x74,0x73,0x20,0x6d,0x61,0x79,0x20,0x62,0x65,0x20,0x73,0x68,0x61, + 0x72,0x65,0x64,0x20,0x61,0x6e,0x64,0x20,0x69,0x6d,0x70,0x72,0x6f,0x76,0x65,0x64,0x20,0x69,0x6e,0x20,0x70,0x61,0x72,0x74,0x6e,0x65,0x72,0x73,0x68,0x69, + 0x70,0x20,0x77,0x69,0x74,0x68,0x20,0x6f,0x74,0x68,0x65,0x72,0x73,0x2e,0xa,0xa,0x54,0x68,0x65,0x20,0x4f,0x46,0x4c,0x20,0x61,0x6c,0x6c,0x6f,0x77,0x73, + 0x20,0x74,0x68,0x65,0x20,0x6c,0x69,0x63,0x65,0x6e,0x73,0x65,0x64,0x20,0x66,0x6f,0x6e,0x74,0x73,0x20,0x74,0x6f,0x20,0x62,0x65,0x20,0x75,0x73,0x65,0x64, + 0xa,0x73,0x74,0x75,0x64,0x69,0x65,0x64,0xa,0x6d,0x6f,0x64,0x69,0x66,0x69,0x65,0x64,0x20,0x61,0x6e,0x64,0x20,0x72,0x65,0x64,0x69,0x73,0x74,0x72,0x69, + 0x62,0x75,0x74,0x65,0x64,0x20,0x66,0x72,0x65,0x65,0x6c,0x79,0x20,0x61,0x73,0x20,0x6c,0x6f,0x6e,0x67,0x20,0x61,0x73,0x20,0x74,0x68,0x65,0x79,0x20,0x61, + 0x72,0x65,0x20,0x6e,0x6f,0x74,0x20,0x73,0x6f,0x6c,0x64,0x20,0x62,0x79,0x20,0x74,0x68,0x65,0x6d,0x73,0x65,0x6c,0x76,0x65,0x73,0x2e,0x20,0x54,0x68,0x65, + 0x20,0x66,0x6f,0x6e,0x74,0x73,0xa,0x69,0x6e,0x63,0x6c,0x75,0x64,0x69,0x6e,0x67,0x20,0x61,0x6e,0x79,0x20,0x64,0x65,0x72,0x69,0x76,0x61,0x74,0x69,0x76, + 0x65,0x20,0x77,0x6f,0x72,0x6b,0x73,0xa,0x63,0x61,0x6e,0x20,0x62,0x65,0x20,0x62,0x75,0x6e,0x64,0x6c,0x65,0x64,0xa,0x65,0x6d,0x62,0x65,0x64,0x64,0x65, + 0x64,0xa,0x72,0x65,0x64,0x69,0x73,0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x64,0x20,0x61,0x6e,0x64,0x2f,0x6f,0x72,0x20,0x73,0x6f,0x6c,0x64,0x20,0x77,0x69, + 0x74,0x68,0x20,0x61,0x6e,0x79,0x20,0x73,0x6f,0x66,0x74,0x77,0x61,0x72,0x65,0x20,0x70,0x72,0x6f,0x76,0x69,0x64,0x65,0x64,0x20,0x74,0x68,0x61,0x74,0x20, + 0x61,0x6e,0x79,0x20,0x72,0x65,0x73,0x65,0x72,0x76,0x65,0x64,0x20,0x6e,0x61,0x6d,0x65,0x73,0x20,0x61,0x72,0x65,0x20,0x6e,0x6f,0x74,0x20,0x75,0x73,0x65, + 0x64,0x20,0x62,0x79,0x20,0x64,0x65,0x72,0x69,0x76,0x61,0x74,0x69,0x76,0x65,0x20,0x77,0x6f,0x72,0x6b,0x73,0x2e,0x20,0x54,0x68,0x65,0x20,0x66,0x6f,0x6e, + 0x74,0x73,0x20,0x61,0x6e,0x64,0x20,0x64,0x65,0x72,0x69,0x76,0x61,0x74,0x69,0x76,0x65,0x73,0xa,0x68,0x6f,0x77,0x65,0x76,0x65,0x72,0xa,0x63,0x61,0x6e, + 0x6e,0x6f,0x74,0x20,0x62,0x65,0x20,0x72,0x65,0x6c,0x65,0x61,0x73,0x65,0x64,0x20,0x75,0x6e,0x64,0x65,0x72,0x20,0x61,0x6e,0x79,0x20,0x6f,0x74,0x68,0x65, + 0x72,0x20,0x74,0x79,0x70,0x65,0x20,0x6f,0x66,0x20,0x6c,0x69,0x63,0x65,0x6e,0x73,0x65,0x2e,0x20,0x54,0x68,0x65,0x20,0x72,0x65,0x71,0x75,0x69,0x72,0x65, + 0x6d,0x65,0x6e,0x74,0x20,0x66,0x6f,0x72,0x20,0x66,0x6f,0x6e,0x74,0x73,0x20,0x74,0x6f,0x20,0x72,0x65,0x6d,0x61,0x69,0x6e,0x20,0x75,0x6e,0x64,0x65,0x72, + 0x20,0x74,0x68,0x69,0x73,0x20,0x6c,0x69,0x63,0x65,0x6e,0x73,0x65,0x20,0x64,0x6f,0x65,0x73,0x20,0x6e,0x6f,0x74,0x20,0x61,0x70,0x70,0x6c,0x79,0x20,0x74, + 0x6f,0x20,0x61,0x6e,0x79,0x20,0x64,0x6f,0x63,0x75,0x6d,0x65,0x6e,0x74,0x20,0x63,0x72,0x65,0x61,0x74,0x65,0x64,0x20,0x75,0x73,0x69,0x6e,0x67,0x20,0x74, + 0x68,0x65,0x20,0x66,0x6f,0x6e,0x74,0x73,0x20,0x6f,0x72,0x20,0x74,0x68,0x65,0x69,0x72,0x20,0x64,0x65,0x72,0x69,0x76,0x61,0x74,0x69,0x76,0x65,0x73,0x2e, + 0xa,0xa,0x44,0x45,0x46,0x49,0x4e,0x49,0x54,0x49,0x4f,0x4e,0x53,0xa,0x22,0x46,0x6f,0x6e,0x74,0x20,0x53,0x6f,0x66,0x74,0x77,0x61,0x72,0x65,0x22,0x20, + 0x72,0x65,0x66,0x65,0x72,0x73,0x20,0x74,0x6f,0x20,0x74,0x68,0x65,0x20,0x73,0x65,0x74,0x20,0x6f,0x66,0x20,0x66,0x69,0x6c,0x65,0x73,0x20,0x72,0x65,0x6c, + 0x65,0x61,0x73,0x65,0x64,0x20,0x62,0x79,0x20,0x74,0x68,0x65,0x20,0x43,0x6f,0x70,0x79,0x72,0x69,0x67,0x68,0x74,0x20,0x48,0x6f,0x6c,0x64,0x65,0x72,0x28, + 0x73,0x29,0x20,0x75,0x6e,0x64,0x65,0x72,0x20,0x74,0x68,0x69,0x73,0x20,0x6c,0x69,0x63,0x65,0x6e,0x73,0x65,0x20,0x61,0x6e,0x64,0x20,0x63,0x6c,0x65,0x61, + 0x72,0x6c,0x79,0x20,0x6d,0x61,0x72,0x6b,0x65,0x64,0x20,0x61,0x73,0x20,0x73,0x75,0x63,0x68,0x2e,0x20,0x54,0x68,0x69,0x73,0x20,0x6d,0x61,0x79,0x20,0x69, + 0x6e,0x63,0x6c,0x75,0x64,0x65,0x20,0x73,0x6f,0x75,0x72,0x63,0x65,0x20,0x66,0x69,0x6c,0x65,0x73,0xa,0x62,0x75,0x69,0x6c,0x64,0x20,0x73,0x63,0x72,0x69, + 0x70,0x74,0x73,0x20,0x61,0x6e,0x64,0x20,0x64,0x6f,0x63,0x75,0x6d,0x65,0x6e,0x74,0x61,0x74,0x69,0x6f,0x6e,0x2e,0xa,0xa,0x22,0x52,0x65,0x73,0x65,0x72, + 0x76,0x65,0x64,0x20,0x46,0x6f,0x6e,0x74,0x20,0x4e,0x61,0x6d,0x65,0x22,0x20,0x72,0x65,0x66,0x65,0x72,0x73,0x20,0x74,0x6f,0x20,0x61,0x6e,0x79,0x20,0x6e, + 0x61,0x6d,0x65,0x73,0x20,0x73,0x70,0x65,0x63,0x69,0x66,0x69,0x65,0x64,0x20,0x61,0x73,0x20,0x73,0x75,0x63,0x68,0x20,0x61,0x66,0x74,0x65,0x72,0x20,0x74, + 0x68,0x65,0x20,0x63,0x6f,0x70,0x79,0x72,0x69,0x67,0x68,0x74,0x20,0x73,0x74,0x61,0x74,0x65,0x6d,0x65,0x6e,0x74,0x28,0x73,0x29,0x2e,0xa,0xa,0x22,0x4f, + 0x72,0x69,0x67,0x69,0x6e,0x61,0x6c,0x20,0x56,0x65,0x72,0x73,0x69,0x6f,0x6e,0x22,0x20,0x72,0x65,0x66,0x65,0x72,0x73,0x20,0x74,0x6f,0x20,0x74,0x68,0x65, + 0x20,0x63,0x6f,0x6c,0x6c,0x65,0x63,0x74,0x69,0x6f,0x6e,0x20,0x6f,0x66,0x20,0x46,0x6f,0x6e,0x74,0x20,0x53,0x6f,0x66,0x74,0x77,0x61,0x72,0x65,0x20,0x63, + 0x6f,0x6d,0x70,0x6f,0x6e,0x65,0x6e,0x74,0x73,0x20,0x61,0x73,0x20,0x64,0x69,0x73,0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x64,0x20,0x62,0x79,0x20,0x74,0x68, + 0x65,0x20,0x43,0x6f,0x70,0x79,0x72,0x69,0x67,0x68,0x74,0x20,0x48,0x6f,0x6c,0x64,0x65,0x72,0x28,0x73,0x29,0x2e,0xa,0xa,0x22,0x4d,0x6f,0x64,0x69,0x66, + 0x69,0x65,0x64,0x20,0x56,0x65,0x72,0x73,0x69,0x6f,0x6e,0x22,0x20,0x72,0x65,0x66,0x65,0x72,0x73,0x20,0x74,0x6f,0x20,0x61,0x6e,0x79,0x20,0x64,0x65,0x72, + 0x69,0x76,0x61,0x74,0x69,0x76,0x65,0x20,0x6d,0x61,0x64,0x65,0x20,0x62,0x79,0x20,0x61,0x64,0x64,0x69,0x6e,0x67,0x20,0x74,0x6f,0xa,0x64,0x65,0x6c,0x65, + 0x74,0x69,0x6e,0x67,0xa,0x6f,0x72,0x20,0x73,0x75,0x62,0x73,0x74,0x69,0x74,0x75,0x74,0x69,0x6e,0x67,0x20,0x2d,0x2d,0x20,0x69,0x6e,0x20,0x70,0x61,0x72, + 0x74,0x20,0x6f,0x72,0x20,0x69,0x6e,0x20,0x77,0x68,0x6f,0x6c,0x65,0x20,0x2d,0x2d,0x20,0x61,0x6e,0x79,0x20,0x6f,0x66,0x20,0x74,0x68,0x65,0x20,0x63,0x6f, + 0x6d,0x70,0x6f,0x6e,0x65,0x6e,0x74,0x73,0x20,0x6f,0x66,0x20,0x74,0x68,0x65,0x20,0x4f,0x72,0x69,0x67,0x69,0x6e,0x61,0x6c,0x20,0x56,0x65,0x72,0x73,0x69, + 0x6f,0x6e,0xa,0x62,0x79,0x20,0x63,0x68,0x61,0x6e,0x67,0x69,0x6e,0x67,0x20,0x66,0x6f,0x72,0x6d,0x61,0x74,0x73,0x20,0x6f,0x72,0x20,0x62,0x79,0x20,0x70, + 0x6f,0x72,0x74,0x69,0x6e,0x67,0x20,0x74,0x68,0x65,0x20,0x46,0x6f,0x6e,0x74,0x20,0x53,0x6f,0x66,0x74,0x77,0x61,0x72,0x65,0x20,0x74,0x6f,0x20,0x61,0x20, + 0x6e,0x65,0x77,0x20,0x65,0x6e,0x76,0x69,0x72,0x6f,0x6e,0x6d,0x65,0x6e,0x74,0x2e,0xa,0xa,0x22,0x41,0x75,0x74,0x68,0x6f,0x72,0x22,0x20,0x72,0x65,0x66, + 0x65,0x72,0x73,0x20,0x74,0x6f,0x20,0x61,0x6e,0x79,0x20,0x64,0x65,0x73,0x69,0x67,0x6e,0x65,0x72,0xa,0x65,0x6e,0x67,0x69,0x6e,0x65,0x65,0x72,0xa,0x70, + 0x72,0x6f,0x67,0x72,0x61,0x6d,0x6d,0x65,0x72,0xa,0x74,0x65,0x63,0x68,0x6e,0x69,0x63,0x61,0x6c,0x20,0x77,0x72,0x69,0x74,0x65,0x72,0x20,0x6f,0x72,0x20, + 0x6f,0x74,0x68,0x65,0x72,0x20,0x70,0x65,0x72,0x73,0x6f,0x6e,0x20,0x77,0x68,0x6f,0x20,0x63,0x6f,0x6e,0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x64,0x20,0x74, + 0x6f,0x20,0x74,0x68,0x65,0x20,0x46,0x6f,0x6e,0x74,0x20,0x53,0x6f,0x66,0x74,0x77,0x61,0x72,0x65,0x2e,0xa,0xa,0x50,0x45,0x52,0x4d,0x49,0x53,0x53,0x49, + 0x4f,0x4e,0x20,0x26,0x20,0x43,0x4f,0x4e,0x44,0x49,0x54,0x49,0x4f,0x4e,0x53,0xa,0x50,0x65,0x72,0x6d,0x69,0x73,0x73,0x69,0x6f,0x6e,0x20,0x69,0x73,0x20, + 0x68,0x65,0x72,0x65,0x62,0x79,0x20,0x67,0x72,0x61,0x6e,0x74,0x65,0x64,0xa,0x66,0x72,0x65,0x65,0x20,0x6f,0x66,0x20,0x63,0x68,0x61,0x72,0x67,0x65,0xa, + 0x74,0x6f,0x20,0x61,0x6e,0x79,0x20,0x70,0x65,0x72,0x73,0x6f,0x6e,0x20,0x6f,0x62,0x74,0x61,0x69,0x6e,0x69,0x6e,0x67,0x20,0x61,0x20,0x63,0x6f,0x70,0x79, + 0x20,0x6f,0x66,0x20,0x74,0x68,0x65,0x20,0x46,0x6f,0x6e,0x74,0x20,0x53,0x6f,0x66,0x74,0x77,0x61,0x72,0x65,0xa,0x74,0x6f,0x20,0x75,0x73,0x65,0xa,0x73, + 0x74,0x75,0x64,0x79,0xa,0x63,0x6f,0x70,0x79,0xa,0x6d,0x65,0x72,0x67,0x65,0xa,0x65,0x6d,0x62,0x65,0x64,0xa,0x6d,0x6f,0x64,0x69,0x66,0x79,0xa,0x72, + 0x65,0x64,0x69,0x73,0x74,0x72,0x69,0x62,0x75,0x74,0x65,0xa,0x61,0x6e,0x64,0x20,0x73,0x65,0x6c,0x6c,0x20,0x6d,0x6f,0x64,0x69,0x66,0x69,0x65,0x64,0x20, + 0x61,0x6e,0x64,0x20,0x75,0x6e,0x6d,0x6f,0x64,0x69,0x66,0x69,0x65,0x64,0x20,0x63,0x6f,0x70,0x69,0x65,0x73,0x20,0x6f,0x66,0x20,0x74,0x68,0x65,0x20,0x46, + 0x6f,0x6e,0x74,0x20,0x53,0x6f,0x66,0x74,0x77,0x61,0x72,0x65,0xa,0x73,0x75,0x62,0x6a,0x65,0x63,0x74,0x20,0x74,0x6f,0x20,0x74,0x68,0x65,0x20,0x66,0x6f, + 0x6c,0x6c,0x6f,0x77,0x69,0x6e,0x67,0x20,0x63,0x6f,0x6e,0x64,0x69,0x74,0x69,0x6f,0x6e,0x73,0x3a,0xa,0xa,0x31,0x29,0x20,0x4e,0x65,0x69,0x74,0x68,0x65, + 0x72,0x20,0x74,0x68,0x65,0x20,0x46,0x6f,0x6e,0x74,0x20,0x53,0x6f,0x66,0x74,0x77,0x61,0x72,0x65,0x20,0x6e,0x6f,0x72,0x20,0x61,0x6e,0x79,0x20,0x6f,0x66, + 0x20,0x69,0x74,0x73,0x20,0x69,0x6e,0x64,0x69,0x76,0x69,0x64,0x75,0x61,0x6c,0x20,0x63,0x6f,0x6d,0x70,0x6f,0x6e,0x65,0x6e,0x74,0x73,0xa,0x69,0x6e,0x20, + 0x4f,0x72,0x69,0x67,0x69,0x6e,0x61,0x6c,0x20,0x6f,0x72,0x20,0x4d,0x6f,0x64,0x69,0x66,0x69,0x65,0x64,0x20,0x56,0x65,0x72,0x73,0x69,0x6f,0x6e,0x73,0xa, + 0x6d,0x61,0x79,0x20,0x62,0x65,0x20,0x73,0x6f,0x6c,0x64,0x20,0x62,0x79,0x20,0x69,0x74,0x73,0x65,0x6c,0x66,0x2e,0xa,0xa,0x32,0x29,0x20,0x4f,0x72,0x69, + 0x67,0x69,0x6e,0x61,0x6c,0x20,0x6f,0x72,0x20,0x4d,0x6f,0x64,0x69,0x66,0x69,0x65,0x64,0x20,0x56,0x65,0x72,0x73,0x69,0x6f,0x6e,0x73,0x20,0x6f,0x66,0x20, + 0x74,0x68,0x65,0x20,0x46,0x6f,0x6e,0x74,0x20,0x53,0x6f,0x66,0x74,0x77,0x61,0x72,0x65,0x20,0x6d,0x61,0x79,0x20,0x62,0x65,0x20,0x62,0x75,0x6e,0x64,0x6c, + 0x65,0x64,0xa,0x72,0x65,0x64,0x69,0x73,0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x64,0x20,0x61,0x6e,0x64,0x2f,0x6f,0x72,0x20,0x73,0x6f,0x6c,0x64,0x20,0x77, + 0x69,0x74,0x68,0x20,0x61,0x6e,0x79,0x20,0x73,0x6f,0x66,0x74,0x77,0x61,0x72,0x65,0xa,0x70,0x72,0x6f,0x76,0x69,0x64,0x65,0x64,0x20,0x74,0x68,0x61,0x74, + 0x20,0x65,0x61,0x63,0x68,0x20,0x63,0x6f,0x70,0x79,0x20,0x63,0x6f,0x6e,0x74,0x61,0x69,0x6e,0x73,0x20,0x74,0x68,0x65,0x20,0x61,0x62,0x6f,0x76,0x65,0x20, + 0x63,0x6f,0x70,0x79,0x72,0x69,0x67,0x68,0x74,0x20,0x6e,0x6f,0x74,0x69,0x63,0x65,0x20,0x61,0x6e,0x64,0x20,0x74,0x68,0x69,0x73,0x20,0x6c,0x69,0x63,0x65, + 0x6e,0x73,0x65,0x2e,0x20,0x54,0x68,0x65,0x73,0x65,0x20,0x63,0x61,0x6e,0x20,0x62,0x65,0x20,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65,0x64,0x20,0x65,0x69,0x74, + 0x68,0x65,0x72,0x20,0x61,0x73,0x20,0x73,0x74,0x61,0x6e,0x64,0x2d,0x61,0x6c,0x6f,0x6e,0x65,0x20,0x74,0x65,0x78,0x74,0x20,0x66,0x69,0x6c,0x65,0x73,0xa, + 0x68,0x75,0x6d,0x61,0x6e,0x2d,0x72,0x65,0x61,0x64,0x61,0x62,0x6c,0x65,0x20,0x68,0x65,0x61,0x64,0x65,0x72,0x73,0x20,0x6f,0x72,0x20,0x69,0x6e,0x20,0x74, + 0x68,0x65,0x20,0x61,0x70,0x70,0x72,0x6f,0x70,0x72,0x69,0x61,0x74,0x65,0x20,0x6d,0x61,0x63,0x68,0x69,0x6e,0x65,0x2d,0x72,0x65,0x61,0x64,0x61,0x62,0x6c, + 0x65,0x20,0x6d,0x65,0x74,0x61,0x64,0x61,0x74,0x61,0x20,0x66,0x69,0x65,0x6c,0x64,0x73,0x20,0x77,0x69,0x74,0x68,0x69,0x6e,0x20,0x74,0x65,0x78,0x74,0x20, + 0x6f,0x72,0x20,0x62,0x69,0x6e,0x61,0x72,0x79,0x20,0x66,0x69,0x6c,0x65,0x73,0x20,0x61,0x73,0x20,0x6c,0x6f,0x6e,0x67,0x20,0x61,0x73,0x20,0x74,0x68,0x6f, + 0x73,0x65,0x20,0x66,0x69,0x65,0x6c,0x64,0x73,0x20,0x63,0x61,0x6e,0x20,0x62,0x65,0x20,0x65,0x61,0x73,0x69,0x6c,0x79,0x20,0x76,0x69,0x65,0x77,0x65,0x64, + 0x20,0x62,0x79,0x20,0x74,0x68,0x65,0x20,0x75,0x73,0x65,0x72,0x2e,0xa,0xa,0x33,0x29,0x20,0x4e,0x6f,0x20,0x4d,0x6f,0x64,0x69,0x66,0x69,0x65,0x64,0x20, + 0x56,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x6f,0x66,0x20,0x74,0x68,0x65,0x20,0x46,0x6f,0x6e,0x74,0x20,0x53,0x6f,0x66,0x74,0x77,0x61,0x72,0x65,0x20,0x6d, + 0x61,0x79,0x20,0x75,0x73,0x65,0x20,0x74,0x68,0x65,0x20,0x52,0x65,0x73,0x65,0x72,0x76,0x65,0x64,0x20,0x46,0x6f,0x6e,0x74,0x20,0x4e,0x61,0x6d,0x65,0x28, + 0x73,0x29,0x20,0x75,0x6e,0x6c,0x65,0x73,0x73,0x20,0x65,0x78,0x70,0x6c,0x69,0x63,0x69,0x74,0x20,0x77,0x72,0x69,0x74,0x74,0x65,0x6e,0x20,0x70,0x65,0x72, + 0x6d,0x69,0x73,0x73,0x69,0x6f,0x6e,0x20,0x69,0x73,0x20,0x67,0x72,0x61,0x6e,0x74,0x65,0x64,0x20,0x62,0x79,0x20,0x74,0x68,0x65,0x20,0x63,0x6f,0x72,0x72, + 0x65,0x73,0x70,0x6f,0x6e,0x64,0x69,0x6e,0x67,0x20,0x43,0x6f,0x70,0x79,0x72,0x69,0x67,0x68,0x74,0x20,0x48,0x6f,0x6c,0x64,0x65,0x72,0x2e,0x20,0x54,0x68, + 0x69,0x73,0x20,0x72,0x65,0x73,0x74,0x72,0x69,0x63,0x74,0x69,0x6f,0x6e,0x20,0x6f,0x6e,0x6c,0x79,0x20,0x61,0x70,0x70,0x6c,0x69,0x65,0x73,0x20,0x74,0x6f, + 0x20,0x74,0x68,0x65,0x20,0x70,0x72,0x69,0x6d,0x61,0x72,0x79,0x20,0x66,0x6f,0x6e,0x74,0x20,0x6e,0x61,0x6d,0x65,0x20,0x61,0x73,0x20,0x70,0x72,0x65,0x73, + 0x65,0x6e,0x74,0x65,0x64,0x20,0x74,0x6f,0x20,0x74,0x68,0x65,0x20,0x75,0x73,0x65,0x72,0x73,0x2e,0xa,0xa,0x34,0x29,0x20,0x54,0x68,0x65,0x20,0x6e,0x61, + 0x6d,0x65,0x28,0x73,0x29,0x20,0x6f,0x66,0x20,0x74,0x68,0x65,0x20,0x43,0x6f,0x70,0x79,0x72,0x69,0x67,0x68,0x74,0x20,0x48,0x6f,0x6c,0x64,0x65,0x72,0x28, + 0x73,0x29,0x20,0x6f,0x72,0x20,0x74,0x68,0x65,0x20,0x41,0x75,0x74,0x68,0x6f,0x72,0x28,0x73,0x29,0x20,0x6f,0x66,0x20,0x74,0x68,0x65,0x20,0x46,0x6f,0x6e, + 0x74,0x20,0x53,0x6f,0x66,0x74,0x77,0x61,0x72,0x65,0x20,0x73,0x68,0x61,0x6c,0x6c,0x20,0x6e,0x6f,0x74,0x20,0x62,0x65,0x20,0x75,0x73,0x65,0x64,0x20,0x74, + 0x6f,0x20,0x70,0x72,0x6f,0x6d,0x6f,0x74,0x65,0xa,0x65,0x6e,0x64,0x6f,0x72,0x73,0x65,0x20,0x6f,0x72,0x20,0x61,0x64,0x76,0x65,0x72,0x74,0x69,0x73,0x65, + 0x20,0x61,0x6e,0x79,0x20,0x4d,0x6f,0x64,0x69,0x66,0x69,0x65,0x64,0x20,0x56,0x65,0x72,0x73,0x69,0x6f,0x6e,0xa,0x65,0x78,0x63,0x65,0x70,0x74,0x20,0x74, + 0x6f,0x20,0x61,0x63,0x6b,0x6e,0x6f,0x77,0x6c,0x65,0x64,0x67,0x65,0x20,0x74,0x68,0x65,0x20,0x63,0x6f,0x6e,0x74,0x72,0x69,0x62,0x75,0x74,0x69,0x6f,0x6e, + 0x28,0x73,0x29,0x20,0x6f,0x66,0x20,0x74,0x68,0x65,0x20,0x43,0x6f,0x70,0x79,0x72,0x69,0x67,0x68,0x74,0x20,0x48,0x6f,0x6c,0x64,0x65,0x72,0x28,0x73,0x29, + 0x20,0x61,0x6e,0x64,0x20,0x74,0x68,0x65,0x20,0x41,0x75,0x74,0x68,0x6f,0x72,0x28,0x73,0x29,0x20,0x6f,0x72,0x20,0x77,0x69,0x74,0x68,0x20,0x74,0x68,0x65, + 0x69,0x72,0x20,0x65,0x78,0x70,0x6c,0x69,0x63,0x69,0x74,0x20,0x77,0x72,0x69,0x74,0x74,0x65,0x6e,0x20,0x70,0x65,0x72,0x6d,0x69,0x73,0x73,0x69,0x6f,0x6e, + 0x2e,0xa,0xa,0x35,0x29,0x20,0x54,0x68,0x65,0x20,0x46,0x6f,0x6e,0x74,0x20,0x53,0x6f,0x66,0x74,0x77,0x61,0x72,0x65,0xa,0x6d,0x6f,0x64,0x69,0x66,0x69, + 0x65,0x64,0x20,0x6f,0x72,0x20,0x75,0x6e,0x6d,0x6f,0x64,0x69,0x66,0x69,0x65,0x64,0xa,0x69,0x6e,0x20,0x70,0x61,0x72,0x74,0x20,0x6f,0x72,0x20,0x69,0x6e, + 0x20,0x77,0x68,0x6f,0x6c,0x65,0xa,0x6d,0x75,0x73,0x74,0x20,0x62,0x65,0x20,0x64,0x69,0x73,0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x64,0x20,0x65,0x6e,0x74, + 0x69,0x72,0x65,0x6c,0x79,0x20,0x75,0x6e,0x64,0x65,0x72,0x20,0x74,0x68,0x69,0x73,0x20,0x6c,0x69,0x63,0x65,0x6e,0x73,0x65,0xa,0x61,0x6e,0x64,0x20,0x6d, + 0x75,0x73,0x74,0x20,0x6e,0x6f,0x74,0x20,0x62,0x65,0x20,0x64,0x69,0x73,0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x64,0x20,0x75,0x6e,0x64,0x65,0x72,0x20,0x61, + 0x6e,0x79,0x20,0x6f,0x74,0x68,0x65,0x72,0x20,0x6c,0x69,0x63,0x65,0x6e,0x73,0x65,0x2e,0x20,0x54,0x68,0x65,0x20,0x72,0x65,0x71,0x75,0x69,0x72,0x65,0x6d, + 0x65,0x6e,0x74,0x20,0x66,0x6f,0x72,0x20,0x66,0x6f,0x6e,0x74,0x73,0x20,0x74,0x6f,0x20,0x72,0x65,0x6d,0x61,0x69,0x6e,0x20,0x75,0x6e,0x64,0x65,0x72,0x20, + 0x74,0x68,0x69,0x73,0x20,0x6c,0x69,0x63,0x65,0x6e,0x73,0x65,0x20,0x64,0x6f,0x65,0x73,0x20,0x6e,0x6f,0x74,0x20,0x61,0x70,0x70,0x6c,0x79,0x20,0x74,0x6f, + 0x20,0x61,0x6e,0x79,0x20,0x64,0x6f,0x63,0x75,0x6d,0x65,0x6e,0x74,0x20,0x63,0x72,0x65,0x61,0x74,0x65,0x64,0x20,0x75,0x73,0x69,0x6e,0x67,0x20,0x74,0x68, + 0x65,0x20,0x46,0x6f,0x6e,0x74,0x20,0x53,0x6f,0x66,0x74,0x77,0x61,0x72,0x65,0x2e,0xa,0xa,0x54,0x45,0x52,0x4d,0x49,0x4e,0x41,0x54,0x49,0x4f,0x4e,0xa, + 0x54,0x68,0x69,0x73,0x20,0x6c,0x69,0x63,0x65,0x6e,0x73,0x65,0x20,0x62,0x65,0x63,0x6f,0x6d,0x65,0x73,0x20,0x6e,0x75,0x6c,0x6c,0x20,0x61,0x6e,0x64,0x20, + 0x76,0x6f,0x69,0x64,0x20,0x69,0x66,0x20,0x61,0x6e,0x79,0x20,0x6f,0x66,0x20,0x74,0x68,0x65,0x20,0x61,0x62,0x6f,0x76,0x65,0x20,0x63,0x6f,0x6e,0x64,0x69, + 0x74,0x69,0x6f,0x6e,0x73,0x20,0x61,0x72,0x65,0x20,0x6e,0x6f,0x74,0x20,0x6d,0x65,0x74,0x2e,0xa,0xa,0x44,0x49,0x53,0x43,0x4c,0x41,0x49,0x4d,0x45,0x52, + 0xa,0x54,0x48,0x45,0x20,0x46,0x4f,0x4e,0x54,0x20,0x53,0x4f,0x46,0x54,0x57,0x41,0x52,0x45,0x20,0x49,0x53,0x20,0x50,0x52,0x4f,0x56,0x49,0x44,0x45,0x44, + 0x20,0x22,0x41,0x53,0x20,0x49,0x53,0x22,0xa,0x57,0x49,0x54,0x48,0x4f,0x55,0x54,0x20,0x57,0x41,0x52,0x52,0x41,0x4e,0x54,0x59,0x20,0x4f,0x46,0x20,0x41, + 0x4e,0x59,0x20,0x4b,0x49,0x4e,0x44,0xa,0x45,0x58,0x50,0x52,0x45,0x53,0x53,0x20,0x4f,0x52,0x20,0x49,0x4d,0x50,0x4c,0x49,0x45,0x44,0xa,0x49,0x4e,0x43, + 0x4c,0x55,0x44,0x49,0x4e,0x47,0x20,0x42,0x55,0x54,0x20,0x4e,0x4f,0x54,0x20,0x4c,0x49,0x4d,0x49,0x54,0x45,0x44,0x20,0x54,0x4f,0x20,0x41,0x4e,0x59,0x20, + 0x57,0x41,0x52,0x52,0x41,0x4e,0x54,0x49,0x45,0x53,0x20,0x4f,0x46,0x20,0x4d,0x45,0x52,0x43,0x48,0x41,0x4e,0x54,0x41,0x42,0x49,0x4c,0x49,0x54,0x59,0xa, + 0x46,0x49,0x54,0x4e,0x45,0x53,0x53,0x20,0x46,0x4f,0x52,0x20,0x41,0x20,0x50,0x41,0x52,0x54,0x49,0x43,0x55,0x4c,0x41,0x52,0x20,0x50,0x55,0x52,0x50,0x4f, + 0x53,0x45,0x20,0x41,0x4e,0x44,0x20,0x4e,0x4f,0x4e,0x49,0x4e,0x46,0x52,0x49,0x4e,0x47,0x45,0x4d,0x45,0x4e,0x54,0x20,0x4f,0x46,0x20,0x43,0x4f,0x50,0x59, + 0x52,0x49,0x47,0x48,0x54,0xa,0x50,0x41,0x54,0x45,0x4e,0x54,0xa,0x54,0x52,0x41,0x44,0x45,0x4d,0x41,0x52,0x4b,0xa,0x4f,0x52,0x20,0x4f,0x54,0x48,0x45, + 0x52,0x20,0x52,0x49,0x47,0x48,0x54,0x2e,0x20,0x49,0x4e,0x20,0x4e,0x4f,0x20,0x45,0x56,0x45,0x4e,0x54,0x20,0x53,0x48,0x41,0x4c,0x4c,0x20,0x54,0x48,0x45, + 0x20,0x43,0x4f,0x50,0x59,0x52,0x49,0x47,0x48,0x54,0x20,0x48,0x4f,0x4c,0x44,0x45,0x52,0x20,0x42,0x45,0x20,0x4c,0x49,0x41,0x42,0x4c,0x45,0x20,0x46,0x4f, + 0x52,0x20,0x41,0x4e,0x59,0x20,0x43,0x4c,0x41,0x49,0x4d,0xa,0x44,0x41,0x4d,0x41,0x47,0x45,0x53,0x20,0x4f,0x52,0x20,0x4f,0x54,0x48,0x45,0x52,0x20,0x4c, + 0x49,0x41,0x42,0x49,0x4c,0x49,0x54,0x59,0xa,0x49,0x4e,0x43,0x4c,0x55,0x44,0x49,0x4e,0x47,0x20,0x41,0x4e,0x59,0x20,0x47,0x45,0x4e,0x45,0x52,0x41,0x4c, + 0xa,0x53,0x50,0x45,0x43,0x49,0x41,0x4c,0xa,0x49,0x4e,0x44,0x49,0x52,0x45,0x43,0x54,0xa,0x49,0x4e,0x43,0x49,0x44,0x45,0x4e,0x54,0x41,0x4c,0xa,0x4f, + 0x52,0x20,0x43,0x4f,0x4e,0x53,0x45,0x51,0x55,0x45,0x4e,0x54,0x49,0x41,0x4c,0x20,0x44,0x41,0x4d,0x41,0x47,0x45,0x53,0xa,0x57,0x48,0x45,0x54,0x48,0x45, + 0x52,0x20,0x49,0x4e,0x20,0x41,0x4e,0x20,0x41,0x43,0x54,0x49,0x4f,0x4e,0x20,0x4f,0x46,0x20,0x43,0x4f,0x4e,0x54,0x52,0x41,0x43,0x54,0xa,0x54,0x4f,0x52, + 0x54,0x20,0x4f,0x52,0x20,0x4f,0x54,0x48,0x45,0x52,0x57,0x49,0x53,0x45,0xa,0x41,0x52,0x49,0x53,0x49,0x4e,0x47,0x20,0x46,0x52,0x4f,0x4d,0xa,0x4f,0x55, + 0x54,0x20,0x4f,0x46,0x20,0x54,0x48,0x45,0x20,0x55,0x53,0x45,0x20,0x4f,0x52,0x20,0x49,0x4e,0x41,0x42,0x49,0x4c,0x49,0x54,0x59,0x20,0x54,0x4f,0x20,0x55, + 0x53,0x45,0x20,0x54,0x48,0x45,0x20,0x46,0x4f,0x4e,0x54,0x20,0x53,0x4f,0x46,0x54,0x57,0x41,0x52,0x45,0x20,0x4f,0x52,0x20,0x46,0x52,0x4f,0x4d,0x20,0x4f, + 0x54,0x48,0x45,0x52,0x20,0x44,0x45,0x41,0x4c,0x49,0x4e,0x47,0x53,0x20,0x49,0x4e,0x20,0x54,0x48,0x45,0x20,0x46,0x4f,0x4e,0x54,0x20,0x53,0x4f,0x46,0x54, + 0x57,0x41,0x52,0x45,0x2e,0x68,0x74,0x74,0x70,0x3a,0x2f,0x2f,0x73,0x63,0x72,0x69,0x70,0x74,0x73,0x2e,0x73,0x69,0x6c,0x2e,0x6f,0x72,0x67,0x2f,0x4f,0x46, + 0x4c,0x0,0x43,0x0,0x6f,0x0,0x70,0x0,0x79,0x0,0x72,0x0,0x69,0x0,0x67,0x0,0x68,0x0,0x74,0x0,0x20,0x0,0x28,0x0,0x63,0x0,0x29,0x0,0x20,0x0, + 0x32,0x0,0x30,0x0,0x31,0x0,0x35,0x0,0x20,0x0,0x51,0x0,0x75,0x0,0x6f,0x0,0x74,0x0,0x65,0x0,0x2d,0x0,0x55,0x0,0x6e,0x0,0x71,0x0,0x75,0x0, + 0x6f,0x0,0x74,0x0,0x65,0x0,0x20,0x0,0x41,0x0,0x70,0x0,0x70,0x0,0x73,0x0,0x2e,0x0,0x43,0x0,0x6f,0x0,0x75,0x0,0x72,0x0,0x69,0x0,0x65,0x0, + 0x72,0x0,0x20,0x0,0x50,0x0,0x72,0x0,0x69,0x0,0x6d,0x0,0x65,0x0,0x20,0x0,0x43,0x0,0x6f,0x0,0x64,0x0,0x65,0x0,0x52,0x0,0x65,0x0,0x67,0x0, + 0x75,0x0,0x6c,0x0,0x61,0x0,0x72,0x0,0x33,0x0,0x2e,0x0,0x30,0x0,0x31,0x0,0x38,0x0,0x3b,0x0,0x51,0x0,0x55,0x0,0x51,0x0,0x41,0x0,0x3b,0x0, + 0x43,0x0,0x6f,0x0,0x75,0x0,0x72,0x0,0x69,0x0,0x65,0x0,0x72,0x0,0x50,0x0,0x72,0x0,0x69,0x0,0x6d,0x0,0x65,0x0,0x43,0x0,0x6f,0x0,0x64,0x0, + 0x65,0x0,0x2d,0x0,0x52,0x0,0x65,0x0,0x67,0x0,0x75,0x0,0x6c,0x0,0x61,0x0,0x72,0x0,0x43,0x0,0x6f,0x0,0x75,0x0,0x72,0x0,0x69,0x0,0x65,0x0, + 0x72,0x0,0x50,0x0,0x72,0x0,0x69,0x0,0x6d,0x0,0x65,0x0,0x43,0x0,0x6f,0x0,0x64,0x0,0x65,0x0,0x2d,0x0,0x52,0x0,0x65,0x0,0x67,0x0,0x75,0x0, + 0x6c,0x0,0x61,0x0,0x72,0x0,0x56,0x0,0x65,0x0,0x72,0x0,0x73,0x0,0x69,0x0,0x6f,0x0,0x6e,0x0,0x20,0x0,0x33,0x0,0x2e,0x0,0x30,0x0,0x33,0x0, + 0x31,0x0,0x38,0x0,0x43,0x0,0x6f,0x0,0x75,0x0,0x72,0x0,0x69,0x0,0x65,0x0,0x72,0x0,0x50,0x0,0x72,0x0,0x69,0x0,0x6d,0x0,0x65,0x0,0x43,0x0, + 0x6f,0x0,0x64,0x0,0x65,0x0,0x2d,0x0,0x52,0x0,0x65,0x0,0x67,0x0,0x75,0x0,0x6c,0x0,0x61,0x0,0x72,0x0,0x51,0x0,0x75,0x0,0x6f,0x0,0x74,0x0, + 0x65,0x0,0x2d,0x0,0x55,0x0,0x6e,0x0,0x71,0x0,0x75,0x0,0x6f,0x0,0x74,0x0,0x65,0x0,0x20,0x0,0x41,0x0,0x70,0x0,0x70,0x0,0x73,0x0,0x41,0x0, + 0x6c,0x0,0x61,0x0,0x6e,0x0,0x20,0x0,0x44,0x0,0x61,0x0,0x67,0x0,0x75,0x0,0x65,0x0,0x2d,0x0,0x47,0x0,0x72,0x0,0x65,0x0,0x65,0x0,0x6e,0x0, + 0x65,0x0,0x68,0x0,0x74,0x0,0x74,0x0,0x70,0x0,0x3a,0x0,0x2f,0x0,0x2f,0x0,0x71,0x0,0x75,0x0,0x6f,0x0,0x74,0x0,0x65,0x0,0x75,0x0,0x6e,0x0, + 0x71,0x0,0x75,0x0,0x6f,0x0,0x74,0x0,0x65,0x0,0x61,0x0,0x70,0x0,0x70,0x0,0x73,0x0,0x2e,0x0,0x63,0x0,0x6f,0x0,0x6d,0x0,0x68,0x0,0x74,0x0, + 0x74,0x0,0x70,0x0,0x3a,0x0,0x2f,0x0,0x2f,0x0,0x62,0x0,0x61,0x0,0x73,0x0,0x69,0x0,0x63,0x0,0x72,0x0,0x65,0x0,0x63,0x0,0x69,0x0,0x70,0x0, + 0x65,0x0,0x2e,0x0,0x63,0x0,0x6f,0x0,0x6d,0x0,0x43,0x0,0x6f,0x0,0x70,0x0,0x79,0x0,0x72,0x0,0x69,0x0,0x67,0x0,0x68,0x0,0x74,0x0,0x20,0x0, + 0x28,0x0,0x63,0x0,0x29,0x0,0x20,0x0,0x32,0x0,0x30,0x0,0x31,0x0,0x35,0x0,0xa,0x0,0x51,0x0,0x75,0x0,0x6f,0x0,0x74,0x0,0x65,0x0,0x2d,0x0, + 0x55,0x0,0x6e,0x0,0x71,0x0,0x75,0x0,0x6f,0x0,0x74,0x0,0x65,0x0,0x20,0x0,0x41,0x0,0x70,0x0,0x70,0x0,0x73,0x0,0x20,0x0,0x28,0x0,0x68,0x0, + 0x74,0x0,0x74,0x0,0x70,0x0,0x3a,0x0,0x2f,0x0,0x2f,0x0,0x71,0x0,0x75,0x0,0x6f,0x0,0x74,0x0,0x65,0x0,0x75,0x0,0x6e,0x0,0x71,0x0,0x75,0x0, + 0x6f,0x0,0x74,0x0,0x65,0x0,0x61,0x0,0x70,0x0,0x70,0x0,0x73,0x0,0x2e,0x0,0x63,0x0,0x6f,0x0,0x6d,0x0,0x29,0x0,0xa,0x0,0x77,0x0,0x69,0x0, + 0x74,0x0,0x68,0x0,0x20,0x0,0x52,0x0,0x65,0x0,0x73,0x0,0x65,0x0,0x72,0x0,0x76,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x46,0x0,0x6f,0x0,0x6e,0x0, + 0x74,0x0,0x20,0x0,0x4e,0x0,0x61,0x0,0x6d,0x0,0x65,0x0,0x20,0x0,0x43,0x0,0x6f,0x0,0x75,0x0,0x72,0x0,0x69,0x0,0x65,0x0,0x72,0x0,0x20,0x0, + 0x50,0x0,0x72,0x0,0x69,0x0,0x6d,0x0,0x65,0x0,0x20,0x0,0x43,0x0,0x6f,0x0,0x64,0x0,0x65,0x0,0x2e,0x0,0xa,0x0,0xa,0x0,0x54,0x0,0x68,0x0, + 0x69,0x0,0x73,0x0,0x20,0x0,0x46,0x0,0x6f,0x0,0x6e,0x0,0x74,0x0,0x20,0x0,0x53,0x0,0x6f,0x0,0x66,0x0,0x74,0x0,0x77,0x0,0x61,0x0,0x72,0x0, + 0x65,0x0,0x20,0x0,0x69,0x0,0x73,0x0,0x20,0x0,0x6c,0x0,0x69,0x0,0x63,0x0,0x65,0x0,0x6e,0x0,0x73,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x75,0x0, + 0x6e,0x0,0x64,0x0,0x65,0x0,0x72,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x53,0x0,0x49,0x0,0x4c,0x0,0x20,0x0,0x4f,0x0,0x70,0x0, + 0x65,0x0,0x6e,0x0,0x20,0x0,0x46,0x0,0x6f,0x0,0x6e,0x0,0x74,0x0,0x20,0x0,0x4c,0x0,0x69,0x0,0x63,0x0,0x65,0x0,0x6e,0x0,0x73,0x0,0x65,0x0, + 0xa,0x0,0x56,0x0,0x65,0x0,0x72,0x0,0x73,0x0,0x69,0x0,0x6f,0x0,0x6e,0x0,0x20,0x0,0x31,0x0,0x2e,0x0,0x31,0x0,0x2e,0x0,0x20,0x0,0x54,0x0, + 0x68,0x0,0x69,0x0,0x73,0x0,0x20,0x0,0x6c,0x0,0x69,0x0,0x63,0x0,0x65,0x0,0x6e,0x0,0x73,0x0,0x65,0x0,0x20,0x0,0x69,0x0,0x73,0x0,0x20,0x0, + 0x63,0x0,0x6f,0x0,0x70,0x0,0x69,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x62,0x0,0x65,0x0,0x6c,0x0,0x6f,0x0,0x77,0x0,0xa,0x0,0x61,0x0,0x6e,0x0, + 0x64,0x0,0x20,0x0,0x69,0x0,0x73,0x0,0x20,0x0,0x61,0x0,0x6c,0x0,0x73,0x0,0x6f,0x0,0x20,0x0,0x61,0x0,0x76,0x0,0x61,0x0,0x69,0x0,0x6c,0x0, + 0x61,0x0,0x62,0x0,0x6c,0x0,0x65,0x0,0x20,0x0,0x77,0x0,0x69,0x0,0x74,0x0,0x68,0x0,0x20,0x0,0x61,0x0,0x20,0x0,0x46,0x0,0x41,0x0,0x51,0x0, + 0x20,0x0,0x61,0x0,0x74,0x0,0x3a,0x0,0x20,0x0,0x68,0x0,0x74,0x0,0x74,0x0,0x70,0x0,0x3a,0x0,0x2f,0x0,0x2f,0x0,0x73,0x0,0x63,0x0,0x72,0x0, + 0x69,0x0,0x70,0x0,0x74,0x0,0x73,0x0,0x2e,0x0,0x73,0x0,0x69,0x0,0x6c,0x0,0x2e,0x0,0x6f,0x0,0x72,0x0,0x67,0x0,0x2f,0x0,0x4f,0x0,0x46,0x0, + 0x4c,0x0,0xa,0x0,0xa,0x0,0xa,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0, + 0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0, + 0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0, + 0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0, + 0x2d,0x0,0x2d,0x0,0x2d,0x0,0xa,0x0,0x53,0x0,0x49,0x0,0x4c,0x0,0x20,0x0,0x4f,0x0,0x50,0x0,0x45,0x0,0x4e,0x0,0x20,0x0,0x46,0x0,0x4f,0x0, + 0x4e,0x0,0x54,0x0,0x20,0x0,0x4c,0x0,0x49,0x0,0x43,0x0,0x45,0x0,0x4e,0x0,0x53,0x0,0x45,0x0,0x20,0x0,0x56,0x0,0x65,0x0,0x72,0x0,0x73,0x0, + 0x69,0x0,0x6f,0x0,0x6e,0x0,0x20,0x0,0x31,0x0,0x2e,0x0,0x31,0x0,0x20,0x0,0x2d,0x0,0x20,0x0,0x32,0x0,0x36,0x0,0x20,0x0,0x46,0x0,0x65,0x0, + 0x62,0x0,0x72,0x0,0x75,0x0,0x61,0x0,0x72,0x0,0x79,0x0,0x20,0x0,0x32,0x0,0x30,0x0,0x30,0x0,0x37,0x0,0xa,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0, + 0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0, + 0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0, + 0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0, + 0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0xa,0x0,0xa,0x0,0x50,0x0,0x52,0x0, + 0x45,0x0,0x41,0x0,0x4d,0x0,0x42,0x0,0x4c,0x0,0x45,0x0,0xa,0x0,0x54,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x67,0x0,0x6f,0x0,0x61,0x0,0x6c,0x0, + 0x73,0x0,0x20,0x0,0x6f,0x0,0x66,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x4f,0x0,0x70,0x0,0x65,0x0,0x6e,0x0,0x20,0x0,0x46,0x0, + 0x6f,0x0,0x6e,0x0,0x74,0x0,0x20,0x0,0x4c,0x0,0x69,0x0,0x63,0x0,0x65,0x0,0x6e,0x0,0x73,0x0,0x65,0x0,0x20,0x0,0x28,0x0,0x4f,0x0,0x46,0x0, + 0x4c,0x0,0x29,0x0,0x20,0x0,0x61,0x0,0x72,0x0,0x65,0x0,0x20,0x0,0x74,0x0,0x6f,0x0,0x20,0x0,0x73,0x0,0x74,0x0,0x69,0x0,0x6d,0x0,0x75,0x0, + 0x6c,0x0,0x61,0x0,0x74,0x0,0x65,0x0,0x20,0x0,0x77,0x0,0x6f,0x0,0x72,0x0,0x6c,0x0,0x64,0x0,0x77,0x0,0x69,0x0,0x64,0x0,0x65,0x0,0x20,0x0, + 0x64,0x0,0x65,0x0,0x76,0x0,0x65,0x0,0x6c,0x0,0x6f,0x0,0x70,0x0,0x6d,0x0,0x65,0x0,0x6e,0x0,0x74,0x0,0x20,0x0,0x6f,0x0,0x66,0x0,0x20,0x0, + 0x63,0x0,0x6f,0x0,0x6c,0x0,0x6c,0x0,0x61,0x0,0x62,0x0,0x6f,0x0,0x72,0x0,0x61,0x0,0x74,0x0,0x69,0x0,0x76,0x0,0x65,0x0,0x20,0x0,0x66,0x0, + 0x6f,0x0,0x6e,0x0,0x74,0x0,0x20,0x0,0x70,0x0,0x72,0x0,0x6f,0x0,0x6a,0x0,0x65,0x0,0x63,0x0,0x74,0x0,0x73,0x0,0xa,0x0,0x74,0x0,0x6f,0x0, + 0x20,0x0,0x73,0x0,0x75,0x0,0x70,0x0,0x70,0x0,0x6f,0x0,0x72,0x0,0x74,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x66,0x0,0x6f,0x0, + 0x6e,0x0,0x74,0x0,0x20,0x0,0x63,0x0,0x72,0x0,0x65,0x0,0x61,0x0,0x74,0x0,0x69,0x0,0x6f,0x0,0x6e,0x0,0x20,0x0,0x65,0x0,0x66,0x0,0x66,0x0, + 0x6f,0x0,0x72,0x0,0x74,0x0,0x73,0x0,0x20,0x0,0x6f,0x0,0x66,0x0,0x20,0x0,0x61,0x0,0x63,0x0,0x61,0x0,0x64,0x0,0x65,0x0,0x6d,0x0,0x69,0x0, + 0x63,0x0,0x20,0x0,0x61,0x0,0x6e,0x0,0x64,0x0,0x20,0x0,0x6c,0x0,0x69,0x0,0x6e,0x0,0x67,0x0,0x75,0x0,0x69,0x0,0x73,0x0,0x74,0x0,0x69,0x0, + 0x63,0x0,0x20,0x0,0x63,0x0,0x6f,0x0,0x6d,0x0,0x6d,0x0,0x75,0x0,0x6e,0x0,0x69,0x0,0x74,0x0,0x69,0x0,0x65,0x0,0x73,0x0,0xa,0x0,0x61,0x0, + 0x6e,0x0,0x64,0x0,0x20,0x0,0x74,0x0,0x6f,0x0,0x20,0x0,0x70,0x0,0x72,0x0,0x6f,0x0,0x76,0x0,0x69,0x0,0x64,0x0,0x65,0x0,0x20,0x0,0x61,0x0, + 0x20,0x0,0x66,0x0,0x72,0x0,0x65,0x0,0x65,0x0,0x20,0x0,0x61,0x0,0x6e,0x0,0x64,0x0,0x20,0x0,0x6f,0x0,0x70,0x0,0x65,0x0,0x6e,0x0,0x20,0x0, + 0x66,0x0,0x72,0x0,0x61,0x0,0x6d,0x0,0x65,0x0,0x77,0x0,0x6f,0x0,0x72,0x0,0x6b,0x0,0x20,0x0,0x69,0x0,0x6e,0x0,0x20,0x0,0x77,0x0,0x68,0x0, + 0x69,0x0,0x63,0x0,0x68,0x0,0x20,0x0,0x66,0x0,0x6f,0x0,0x6e,0x0,0x74,0x0,0x73,0x0,0x20,0x0,0x6d,0x0,0x61,0x0,0x79,0x0,0x20,0x0,0x62,0x0, + 0x65,0x0,0x20,0x0,0x73,0x0,0x68,0x0,0x61,0x0,0x72,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x61,0x0,0x6e,0x0,0x64,0x0,0x20,0x0,0x69,0x0,0x6d,0x0, + 0x70,0x0,0x72,0x0,0x6f,0x0,0x76,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x69,0x0,0x6e,0x0,0x20,0x0,0x70,0x0,0x61,0x0,0x72,0x0,0x74,0x0,0x6e,0x0, + 0x65,0x0,0x72,0x0,0x73,0x0,0x68,0x0,0x69,0x0,0x70,0x0,0x20,0x0,0x77,0x0,0x69,0x0,0x74,0x0,0x68,0x0,0x20,0x0,0x6f,0x0,0x74,0x0,0x68,0x0, + 0x65,0x0,0x72,0x0,0x73,0x0,0x2e,0x0,0xa,0x0,0xa,0x0,0x54,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x4f,0x0,0x46,0x0,0x4c,0x0,0x20,0x0,0x61,0x0, + 0x6c,0x0,0x6c,0x0,0x6f,0x0,0x77,0x0,0x73,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x6c,0x0,0x69,0x0,0x63,0x0,0x65,0x0,0x6e,0x0, + 0x73,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x66,0x0,0x6f,0x0,0x6e,0x0,0x74,0x0,0x73,0x0,0x20,0x0,0x74,0x0,0x6f,0x0,0x20,0x0,0x62,0x0,0x65,0x0, + 0x20,0x0,0x75,0x0,0x73,0x0,0x65,0x0,0x64,0x0,0xa,0x0,0x73,0x0,0x74,0x0,0x75,0x0,0x64,0x0,0x69,0x0,0x65,0x0,0x64,0x0,0xa,0x0,0x6d,0x0, + 0x6f,0x0,0x64,0x0,0x69,0x0,0x66,0x0,0x69,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x61,0x0,0x6e,0x0,0x64,0x0,0x20,0x0,0x72,0x0,0x65,0x0,0x64,0x0, + 0x69,0x0,0x73,0x0,0x74,0x0,0x72,0x0,0x69,0x0,0x62,0x0,0x75,0x0,0x74,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x66,0x0,0x72,0x0,0x65,0x0,0x65,0x0, + 0x6c,0x0,0x79,0x0,0x20,0x0,0x61,0x0,0x73,0x0,0x20,0x0,0x6c,0x0,0x6f,0x0,0x6e,0x0,0x67,0x0,0x20,0x0,0x61,0x0,0x73,0x0,0x20,0x0,0x74,0x0, + 0x68,0x0,0x65,0x0,0x79,0x0,0x20,0x0,0x61,0x0,0x72,0x0,0x65,0x0,0x20,0x0,0x6e,0x0,0x6f,0x0,0x74,0x0,0x20,0x0,0x73,0x0,0x6f,0x0,0x6c,0x0, + 0x64,0x0,0x20,0x0,0x62,0x0,0x79,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x6d,0x0,0x73,0x0,0x65,0x0,0x6c,0x0,0x76,0x0,0x65,0x0,0x73,0x0, + 0x2e,0x0,0x20,0x0,0x54,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x66,0x0,0x6f,0x0,0x6e,0x0,0x74,0x0,0x73,0x0,0xa,0x0,0x69,0x0,0x6e,0x0,0x63,0x0, + 0x6c,0x0,0x75,0x0,0x64,0x0,0x69,0x0,0x6e,0x0,0x67,0x0,0x20,0x0,0x61,0x0,0x6e,0x0,0x79,0x0,0x20,0x0,0x64,0x0,0x65,0x0,0x72,0x0,0x69,0x0, + 0x76,0x0,0x61,0x0,0x74,0x0,0x69,0x0,0x76,0x0,0x65,0x0,0x20,0x0,0x77,0x0,0x6f,0x0,0x72,0x0,0x6b,0x0,0x73,0x0,0xa,0x0,0x63,0x0,0x61,0x0, + 0x6e,0x0,0x20,0x0,0x62,0x0,0x65,0x0,0x20,0x0,0x62,0x0,0x75,0x0,0x6e,0x0,0x64,0x0,0x6c,0x0,0x65,0x0,0x64,0x0,0xa,0x0,0x65,0x0,0x6d,0x0, + 0x62,0x0,0x65,0x0,0x64,0x0,0x64,0x0,0x65,0x0,0x64,0x0,0xa,0x0,0x72,0x0,0x65,0x0,0x64,0x0,0x69,0x0,0x73,0x0,0x74,0x0,0x72,0x0,0x69,0x0, + 0x62,0x0,0x75,0x0,0x74,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x61,0x0,0x6e,0x0,0x64,0x0,0x2f,0x0,0x6f,0x0,0x72,0x0,0x20,0x0,0x73,0x0,0x6f,0x0, + 0x6c,0x0,0x64,0x0,0x20,0x0,0x77,0x0,0x69,0x0,0x74,0x0,0x68,0x0,0x20,0x0,0x61,0x0,0x6e,0x0,0x79,0x0,0x20,0x0,0x73,0x0,0x6f,0x0,0x66,0x0, + 0x74,0x0,0x77,0x0,0x61,0x0,0x72,0x0,0x65,0x0,0x20,0x0,0x70,0x0,0x72,0x0,0x6f,0x0,0x76,0x0,0x69,0x0,0x64,0x0,0x65,0x0,0x64,0x0,0x20,0x0, + 0x74,0x0,0x68,0x0,0x61,0x0,0x74,0x0,0x20,0x0,0x61,0x0,0x6e,0x0,0x79,0x0,0x20,0x0,0x72,0x0,0x65,0x0,0x73,0x0,0x65,0x0,0x72,0x0,0x76,0x0, + 0x65,0x0,0x64,0x0,0x20,0x0,0x6e,0x0,0x61,0x0,0x6d,0x0,0x65,0x0,0x73,0x0,0x20,0x0,0x61,0x0,0x72,0x0,0x65,0x0,0x20,0x0,0x6e,0x0,0x6f,0x0, + 0x74,0x0,0x20,0x0,0x75,0x0,0x73,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x62,0x0,0x79,0x0,0x20,0x0,0x64,0x0,0x65,0x0,0x72,0x0,0x69,0x0,0x76,0x0, + 0x61,0x0,0x74,0x0,0x69,0x0,0x76,0x0,0x65,0x0,0x20,0x0,0x77,0x0,0x6f,0x0,0x72,0x0,0x6b,0x0,0x73,0x0,0x2e,0x0,0x20,0x0,0x54,0x0,0x68,0x0, + 0x65,0x0,0x20,0x0,0x66,0x0,0x6f,0x0,0x6e,0x0,0x74,0x0,0x73,0x0,0x20,0x0,0x61,0x0,0x6e,0x0,0x64,0x0,0x20,0x0,0x64,0x0,0x65,0x0,0x72,0x0, + 0x69,0x0,0x76,0x0,0x61,0x0,0x74,0x0,0x69,0x0,0x76,0x0,0x65,0x0,0x73,0x0,0xa,0x0,0x68,0x0,0x6f,0x0,0x77,0x0,0x65,0x0,0x76,0x0,0x65,0x0, + 0x72,0x0,0xa,0x0,0x63,0x0,0x61,0x0,0x6e,0x0,0x6e,0x0,0x6f,0x0,0x74,0x0,0x20,0x0,0x62,0x0,0x65,0x0,0x20,0x0,0x72,0x0,0x65,0x0,0x6c,0x0, + 0x65,0x0,0x61,0x0,0x73,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x75,0x0,0x6e,0x0,0x64,0x0,0x65,0x0,0x72,0x0,0x20,0x0,0x61,0x0,0x6e,0x0,0x79,0x0, + 0x20,0x0,0x6f,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x72,0x0,0x20,0x0,0x74,0x0,0x79,0x0,0x70,0x0,0x65,0x0,0x20,0x0,0x6f,0x0,0x66,0x0,0x20,0x0, + 0x6c,0x0,0x69,0x0,0x63,0x0,0x65,0x0,0x6e,0x0,0x73,0x0,0x65,0x0,0x2e,0x0,0x20,0x0,0x54,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x72,0x0,0x65,0x0, + 0x71,0x0,0x75,0x0,0x69,0x0,0x72,0x0,0x65,0x0,0x6d,0x0,0x65,0x0,0x6e,0x0,0x74,0x0,0x20,0x0,0x66,0x0,0x6f,0x0,0x72,0x0,0x20,0x0,0x66,0x0, + 0x6f,0x0,0x6e,0x0,0x74,0x0,0x73,0x0,0x20,0x0,0x74,0x0,0x6f,0x0,0x20,0x0,0x72,0x0,0x65,0x0,0x6d,0x0,0x61,0x0,0x69,0x0,0x6e,0x0,0x20,0x0, + 0x75,0x0,0x6e,0x0,0x64,0x0,0x65,0x0,0x72,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x69,0x0,0x73,0x0,0x20,0x0,0x6c,0x0,0x69,0x0,0x63,0x0,0x65,0x0, + 0x6e,0x0,0x73,0x0,0x65,0x0,0x20,0x0,0x64,0x0,0x6f,0x0,0x65,0x0,0x73,0x0,0x20,0x0,0x6e,0x0,0x6f,0x0,0x74,0x0,0x20,0x0,0x61,0x0,0x70,0x0, + 0x70,0x0,0x6c,0x0,0x79,0x0,0x20,0x0,0x74,0x0,0x6f,0x0,0x20,0x0,0x61,0x0,0x6e,0x0,0x79,0x0,0x20,0x0,0x64,0x0,0x6f,0x0,0x63,0x0,0x75,0x0, + 0x6d,0x0,0x65,0x0,0x6e,0x0,0x74,0x0,0x20,0x0,0x63,0x0,0x72,0x0,0x65,0x0,0x61,0x0,0x74,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x75,0x0,0x73,0x0, + 0x69,0x0,0x6e,0x0,0x67,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x66,0x0,0x6f,0x0,0x6e,0x0,0x74,0x0,0x73,0x0,0x20,0x0,0x6f,0x0, + 0x72,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x69,0x0,0x72,0x0,0x20,0x0,0x64,0x0,0x65,0x0,0x72,0x0,0x69,0x0,0x76,0x0,0x61,0x0,0x74,0x0, + 0x69,0x0,0x76,0x0,0x65,0x0,0x73,0x0,0x2e,0x0,0xa,0x0,0xa,0x0,0x44,0x0,0x45,0x0,0x46,0x0,0x49,0x0,0x4e,0x0,0x49,0x0,0x54,0x0,0x49,0x0, + 0x4f,0x0,0x4e,0x0,0x53,0x0,0xa,0x0,0x22,0x0,0x46,0x0,0x6f,0x0,0x6e,0x0,0x74,0x0,0x20,0x0,0x53,0x0,0x6f,0x0,0x66,0x0,0x74,0x0,0x77,0x0, + 0x61,0x0,0x72,0x0,0x65,0x0,0x22,0x0,0x20,0x0,0x72,0x0,0x65,0x0,0x66,0x0,0x65,0x0,0x72,0x0,0x73,0x0,0x20,0x0,0x74,0x0,0x6f,0x0,0x20,0x0, + 0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x73,0x0,0x65,0x0,0x74,0x0,0x20,0x0,0x6f,0x0,0x66,0x0,0x20,0x0,0x66,0x0,0x69,0x0,0x6c,0x0,0x65,0x0, + 0x73,0x0,0x20,0x0,0x72,0x0,0x65,0x0,0x6c,0x0,0x65,0x0,0x61,0x0,0x73,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x62,0x0,0x79,0x0,0x20,0x0,0x74,0x0, + 0x68,0x0,0x65,0x0,0x20,0x0,0x43,0x0,0x6f,0x0,0x70,0x0,0x79,0x0,0x72,0x0,0x69,0x0,0x67,0x0,0x68,0x0,0x74,0x0,0x20,0x0,0x48,0x0,0x6f,0x0, + 0x6c,0x0,0x64,0x0,0x65,0x0,0x72,0x0,0x28,0x0,0x73,0x0,0x29,0x0,0x20,0x0,0x75,0x0,0x6e,0x0,0x64,0x0,0x65,0x0,0x72,0x0,0x20,0x0,0x74,0x0, + 0x68,0x0,0x69,0x0,0x73,0x0,0x20,0x0,0x6c,0x0,0x69,0x0,0x63,0x0,0x65,0x0,0x6e,0x0,0x73,0x0,0x65,0x0,0x20,0x0,0x61,0x0,0x6e,0x0,0x64,0x0, + 0x20,0x0,0x63,0x0,0x6c,0x0,0x65,0x0,0x61,0x0,0x72,0x0,0x6c,0x0,0x79,0x0,0x20,0x0,0x6d,0x0,0x61,0x0,0x72,0x0,0x6b,0x0,0x65,0x0,0x64,0x0, + 0x20,0x0,0x61,0x0,0x73,0x0,0x20,0x0,0x73,0x0,0x75,0x0,0x63,0x0,0x68,0x0,0x2e,0x0,0x20,0x0,0x54,0x0,0x68,0x0,0x69,0x0,0x73,0x0,0x20,0x0, + 0x6d,0x0,0x61,0x0,0x79,0x0,0x20,0x0,0x69,0x0,0x6e,0x0,0x63,0x0,0x6c,0x0,0x75,0x0,0x64,0x0,0x65,0x0,0x20,0x0,0x73,0x0,0x6f,0x0,0x75,0x0, + 0x72,0x0,0x63,0x0,0x65,0x0,0x20,0x0,0x66,0x0,0x69,0x0,0x6c,0x0,0x65,0x0,0x73,0x0,0xa,0x0,0x62,0x0,0x75,0x0,0x69,0x0,0x6c,0x0,0x64,0x0, + 0x20,0x0,0x73,0x0,0x63,0x0,0x72,0x0,0x69,0x0,0x70,0x0,0x74,0x0,0x73,0x0,0x20,0x0,0x61,0x0,0x6e,0x0,0x64,0x0,0x20,0x0,0x64,0x0,0x6f,0x0, + 0x63,0x0,0x75,0x0,0x6d,0x0,0x65,0x0,0x6e,0x0,0x74,0x0,0x61,0x0,0x74,0x0,0x69,0x0,0x6f,0x0,0x6e,0x0,0x2e,0x0,0xa,0x0,0xa,0x0,0x22,0x0, + 0x52,0x0,0x65,0x0,0x73,0x0,0x65,0x0,0x72,0x0,0x76,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x46,0x0,0x6f,0x0,0x6e,0x0,0x74,0x0,0x20,0x0,0x4e,0x0, + 0x61,0x0,0x6d,0x0,0x65,0x0,0x22,0x0,0x20,0x0,0x72,0x0,0x65,0x0,0x66,0x0,0x65,0x0,0x72,0x0,0x73,0x0,0x20,0x0,0x74,0x0,0x6f,0x0,0x20,0x0, + 0x61,0x0,0x6e,0x0,0x79,0x0,0x20,0x0,0x6e,0x0,0x61,0x0,0x6d,0x0,0x65,0x0,0x73,0x0,0x20,0x0,0x73,0x0,0x70,0x0,0x65,0x0,0x63,0x0,0x69,0x0, + 0x66,0x0,0x69,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x61,0x0,0x73,0x0,0x20,0x0,0x73,0x0,0x75,0x0,0x63,0x0,0x68,0x0,0x20,0x0,0x61,0x0,0x66,0x0, + 0x74,0x0,0x65,0x0,0x72,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x63,0x0,0x6f,0x0,0x70,0x0,0x79,0x0,0x72,0x0,0x69,0x0,0x67,0x0, + 0x68,0x0,0x74,0x0,0x20,0x0,0x73,0x0,0x74,0x0,0x61,0x0,0x74,0x0,0x65,0x0,0x6d,0x0,0x65,0x0,0x6e,0x0,0x74,0x0,0x28,0x0,0x73,0x0,0x29,0x0, + 0x2e,0x0,0xa,0x0,0xa,0x0,0x22,0x0,0x4f,0x0,0x72,0x0,0x69,0x0,0x67,0x0,0x69,0x0,0x6e,0x0,0x61,0x0,0x6c,0x0,0x20,0x0,0x56,0x0,0x65,0x0, + 0x72,0x0,0x73,0x0,0x69,0x0,0x6f,0x0,0x6e,0x0,0x22,0x0,0x20,0x0,0x72,0x0,0x65,0x0,0x66,0x0,0x65,0x0,0x72,0x0,0x73,0x0,0x20,0x0,0x74,0x0, + 0x6f,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x63,0x0,0x6f,0x0,0x6c,0x0,0x6c,0x0,0x65,0x0,0x63,0x0,0x74,0x0,0x69,0x0,0x6f,0x0, + 0x6e,0x0,0x20,0x0,0x6f,0x0,0x66,0x0,0x20,0x0,0x46,0x0,0x6f,0x0,0x6e,0x0,0x74,0x0,0x20,0x0,0x53,0x0,0x6f,0x0,0x66,0x0,0x74,0x0,0x77,0x0, + 0x61,0x0,0x72,0x0,0x65,0x0,0x20,0x0,0x63,0x0,0x6f,0x0,0x6d,0x0,0x70,0x0,0x6f,0x0,0x6e,0x0,0x65,0x0,0x6e,0x0,0x74,0x0,0x73,0x0,0x20,0x0, + 0x61,0x0,0x73,0x0,0x20,0x0,0x64,0x0,0x69,0x0,0x73,0x0,0x74,0x0,0x72,0x0,0x69,0x0,0x62,0x0,0x75,0x0,0x74,0x0,0x65,0x0,0x64,0x0,0x20,0x0, + 0x62,0x0,0x79,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x43,0x0,0x6f,0x0,0x70,0x0,0x79,0x0,0x72,0x0,0x69,0x0,0x67,0x0,0x68,0x0, + 0x74,0x0,0x20,0x0,0x48,0x0,0x6f,0x0,0x6c,0x0,0x64,0x0,0x65,0x0,0x72,0x0,0x28,0x0,0x73,0x0,0x29,0x0,0x2e,0x0,0xa,0x0,0xa,0x0,0x22,0x0, + 0x4d,0x0,0x6f,0x0,0x64,0x0,0x69,0x0,0x66,0x0,0x69,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x56,0x0,0x65,0x0,0x72,0x0,0x73,0x0,0x69,0x0,0x6f,0x0, + 0x6e,0x0,0x22,0x0,0x20,0x0,0x72,0x0,0x65,0x0,0x66,0x0,0x65,0x0,0x72,0x0,0x73,0x0,0x20,0x0,0x74,0x0,0x6f,0x0,0x20,0x0,0x61,0x0,0x6e,0x0, + 0x79,0x0,0x20,0x0,0x64,0x0,0x65,0x0,0x72,0x0,0x69,0x0,0x76,0x0,0x61,0x0,0x74,0x0,0x69,0x0,0x76,0x0,0x65,0x0,0x20,0x0,0x6d,0x0,0x61,0x0, + 0x64,0x0,0x65,0x0,0x20,0x0,0x62,0x0,0x79,0x0,0x20,0x0,0x61,0x0,0x64,0x0,0x64,0x0,0x69,0x0,0x6e,0x0,0x67,0x0,0x20,0x0,0x74,0x0,0x6f,0x0, + 0xa,0x0,0x64,0x0,0x65,0x0,0x6c,0x0,0x65,0x0,0x74,0x0,0x69,0x0,0x6e,0x0,0x67,0x0,0xa,0x0,0x6f,0x0,0x72,0x0,0x20,0x0,0x73,0x0,0x75,0x0, + 0x62,0x0,0x73,0x0,0x74,0x0,0x69,0x0,0x74,0x0,0x75,0x0,0x74,0x0,0x69,0x0,0x6e,0x0,0x67,0x0,0x20,0x0,0x2d,0x0,0x2d,0x0,0x20,0x0,0x69,0x0, + 0x6e,0x0,0x20,0x0,0x70,0x0,0x61,0x0,0x72,0x0,0x74,0x0,0x20,0x0,0x6f,0x0,0x72,0x0,0x20,0x0,0x69,0x0,0x6e,0x0,0x20,0x0,0x77,0x0,0x68,0x0, + 0x6f,0x0,0x6c,0x0,0x65,0x0,0x20,0x0,0x2d,0x0,0x2d,0x0,0x20,0x0,0x61,0x0,0x6e,0x0,0x79,0x0,0x20,0x0,0x6f,0x0,0x66,0x0,0x20,0x0,0x74,0x0, + 0x68,0x0,0x65,0x0,0x20,0x0,0x63,0x0,0x6f,0x0,0x6d,0x0,0x70,0x0,0x6f,0x0,0x6e,0x0,0x65,0x0,0x6e,0x0,0x74,0x0,0x73,0x0,0x20,0x0,0x6f,0x0, + 0x66,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x4f,0x0,0x72,0x0,0x69,0x0,0x67,0x0,0x69,0x0,0x6e,0x0,0x61,0x0,0x6c,0x0,0x20,0x0, + 0x56,0x0,0x65,0x0,0x72,0x0,0x73,0x0,0x69,0x0,0x6f,0x0,0x6e,0x0,0xa,0x0,0x62,0x0,0x79,0x0,0x20,0x0,0x63,0x0,0x68,0x0,0x61,0x0,0x6e,0x0, + 0x67,0x0,0x69,0x0,0x6e,0x0,0x67,0x0,0x20,0x0,0x66,0x0,0x6f,0x0,0x72,0x0,0x6d,0x0,0x61,0x0,0x74,0x0,0x73,0x0,0x20,0x0,0x6f,0x0,0x72,0x0, + 0x20,0x0,0x62,0x0,0x79,0x0,0x20,0x0,0x70,0x0,0x6f,0x0,0x72,0x0,0x74,0x0,0x69,0x0,0x6e,0x0,0x67,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0, + 0x20,0x0,0x46,0x0,0x6f,0x0,0x6e,0x0,0x74,0x0,0x20,0x0,0x53,0x0,0x6f,0x0,0x66,0x0,0x74,0x0,0x77,0x0,0x61,0x0,0x72,0x0,0x65,0x0,0x20,0x0, + 0x74,0x0,0x6f,0x0,0x20,0x0,0x61,0x0,0x20,0x0,0x6e,0x0,0x65,0x0,0x77,0x0,0x20,0x0,0x65,0x0,0x6e,0x0,0x76,0x0,0x69,0x0,0x72,0x0,0x6f,0x0, + 0x6e,0x0,0x6d,0x0,0x65,0x0,0x6e,0x0,0x74,0x0,0x2e,0x0,0xa,0x0,0xa,0x0,0x22,0x0,0x41,0x0,0x75,0x0,0x74,0x0,0x68,0x0,0x6f,0x0,0x72,0x0, + 0x22,0x0,0x20,0x0,0x72,0x0,0x65,0x0,0x66,0x0,0x65,0x0,0x72,0x0,0x73,0x0,0x20,0x0,0x74,0x0,0x6f,0x0,0x20,0x0,0x61,0x0,0x6e,0x0,0x79,0x0, + 0x20,0x0,0x64,0x0,0x65,0x0,0x73,0x0,0x69,0x0,0x67,0x0,0x6e,0x0,0x65,0x0,0x72,0x0,0xa,0x0,0x65,0x0,0x6e,0x0,0x67,0x0,0x69,0x0,0x6e,0x0, + 0x65,0x0,0x65,0x0,0x72,0x0,0xa,0x0,0x70,0x0,0x72,0x0,0x6f,0x0,0x67,0x0,0x72,0x0,0x61,0x0,0x6d,0x0,0x6d,0x0,0x65,0x0,0x72,0x0,0xa,0x0, + 0x74,0x0,0x65,0x0,0x63,0x0,0x68,0x0,0x6e,0x0,0x69,0x0,0x63,0x0,0x61,0x0,0x6c,0x0,0x20,0x0,0x77,0x0,0x72,0x0,0x69,0x0,0x74,0x0,0x65,0x0, + 0x72,0x0,0x20,0x0,0x6f,0x0,0x72,0x0,0x20,0x0,0x6f,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x72,0x0,0x20,0x0,0x70,0x0,0x65,0x0,0x72,0x0,0x73,0x0, + 0x6f,0x0,0x6e,0x0,0x20,0x0,0x77,0x0,0x68,0x0,0x6f,0x0,0x20,0x0,0x63,0x0,0x6f,0x0,0x6e,0x0,0x74,0x0,0x72,0x0,0x69,0x0,0x62,0x0,0x75,0x0, + 0x74,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x74,0x0,0x6f,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x46,0x0,0x6f,0x0,0x6e,0x0,0x74,0x0, + 0x20,0x0,0x53,0x0,0x6f,0x0,0x66,0x0,0x74,0x0,0x77,0x0,0x61,0x0,0x72,0x0,0x65,0x0,0x2e,0x0,0xa,0x0,0xa,0x0,0x50,0x0,0x45,0x0,0x52,0x0, + 0x4d,0x0,0x49,0x0,0x53,0x0,0x53,0x0,0x49,0x0,0x4f,0x0,0x4e,0x0,0x20,0x0,0x26,0x0,0x20,0x0,0x43,0x0,0x4f,0x0,0x4e,0x0,0x44,0x0,0x49,0x0, + 0x54,0x0,0x49,0x0,0x4f,0x0,0x4e,0x0,0x53,0x0,0xa,0x0,0x50,0x0,0x65,0x0,0x72,0x0,0x6d,0x0,0x69,0x0,0x73,0x0,0x73,0x0,0x69,0x0,0x6f,0x0, + 0x6e,0x0,0x20,0x0,0x69,0x0,0x73,0x0,0x20,0x0,0x68,0x0,0x65,0x0,0x72,0x0,0x65,0x0,0x62,0x0,0x79,0x0,0x20,0x0,0x67,0x0,0x72,0x0,0x61,0x0, + 0x6e,0x0,0x74,0x0,0x65,0x0,0x64,0x0,0xa,0x0,0x66,0x0,0x72,0x0,0x65,0x0,0x65,0x0,0x20,0x0,0x6f,0x0,0x66,0x0,0x20,0x0,0x63,0x0,0x68,0x0, + 0x61,0x0,0x72,0x0,0x67,0x0,0x65,0x0,0xa,0x0,0x74,0x0,0x6f,0x0,0x20,0x0,0x61,0x0,0x6e,0x0,0x79,0x0,0x20,0x0,0x70,0x0,0x65,0x0,0x72,0x0, + 0x73,0x0,0x6f,0x0,0x6e,0x0,0x20,0x0,0x6f,0x0,0x62,0x0,0x74,0x0,0x61,0x0,0x69,0x0,0x6e,0x0,0x69,0x0,0x6e,0x0,0x67,0x0,0x20,0x0,0x61,0x0, + 0x20,0x0,0x63,0x0,0x6f,0x0,0x70,0x0,0x79,0x0,0x20,0x0,0x6f,0x0,0x66,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x46,0x0,0x6f,0x0, + 0x6e,0x0,0x74,0x0,0x20,0x0,0x53,0x0,0x6f,0x0,0x66,0x0,0x74,0x0,0x77,0x0,0x61,0x0,0x72,0x0,0x65,0x0,0xa,0x0,0x74,0x0,0x6f,0x0,0x20,0x0, + 0x75,0x0,0x73,0x0,0x65,0x0,0xa,0x0,0x73,0x0,0x74,0x0,0x75,0x0,0x64,0x0,0x79,0x0,0xa,0x0,0x63,0x0,0x6f,0x0,0x70,0x0,0x79,0x0,0xa,0x0, + 0x6d,0x0,0x65,0x0,0x72,0x0,0x67,0x0,0x65,0x0,0xa,0x0,0x65,0x0,0x6d,0x0,0x62,0x0,0x65,0x0,0x64,0x0,0xa,0x0,0x6d,0x0,0x6f,0x0,0x64,0x0, + 0x69,0x0,0x66,0x0,0x79,0x0,0xa,0x0,0x72,0x0,0x65,0x0,0x64,0x0,0x69,0x0,0x73,0x0,0x74,0x0,0x72,0x0,0x69,0x0,0x62,0x0,0x75,0x0,0x74,0x0, + 0x65,0x0,0xa,0x0,0x61,0x0,0x6e,0x0,0x64,0x0,0x20,0x0,0x73,0x0,0x65,0x0,0x6c,0x0,0x6c,0x0,0x20,0x0,0x6d,0x0,0x6f,0x0,0x64,0x0,0x69,0x0, + 0x66,0x0,0x69,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x61,0x0,0x6e,0x0,0x64,0x0,0x20,0x0,0x75,0x0,0x6e,0x0,0x6d,0x0,0x6f,0x0,0x64,0x0,0x69,0x0, + 0x66,0x0,0x69,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x63,0x0,0x6f,0x0,0x70,0x0,0x69,0x0,0x65,0x0,0x73,0x0,0x20,0x0,0x6f,0x0,0x66,0x0,0x20,0x0, + 0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x46,0x0,0x6f,0x0,0x6e,0x0,0x74,0x0,0x20,0x0,0x53,0x0,0x6f,0x0,0x66,0x0,0x74,0x0,0x77,0x0,0x61,0x0, + 0x72,0x0,0x65,0x0,0xa,0x0,0x73,0x0,0x75,0x0,0x62,0x0,0x6a,0x0,0x65,0x0,0x63,0x0,0x74,0x0,0x20,0x0,0x74,0x0,0x6f,0x0,0x20,0x0,0x74,0x0, + 0x68,0x0,0x65,0x0,0x20,0x0,0x66,0x0,0x6f,0x0,0x6c,0x0,0x6c,0x0,0x6f,0x0,0x77,0x0,0x69,0x0,0x6e,0x0,0x67,0x0,0x20,0x0,0x63,0x0,0x6f,0x0, + 0x6e,0x0,0x64,0x0,0x69,0x0,0x74,0x0,0x69,0x0,0x6f,0x0,0x6e,0x0,0x73,0x0,0x3a,0x0,0xa,0x0,0xa,0x0,0x31,0x0,0x29,0x0,0x20,0x0,0x4e,0x0, + 0x65,0x0,0x69,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x72,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x46,0x0,0x6f,0x0,0x6e,0x0,0x74,0x0, + 0x20,0x0,0x53,0x0,0x6f,0x0,0x66,0x0,0x74,0x0,0x77,0x0,0x61,0x0,0x72,0x0,0x65,0x0,0x20,0x0,0x6e,0x0,0x6f,0x0,0x72,0x0,0x20,0x0,0x61,0x0, + 0x6e,0x0,0x79,0x0,0x20,0x0,0x6f,0x0,0x66,0x0,0x20,0x0,0x69,0x0,0x74,0x0,0x73,0x0,0x20,0x0,0x69,0x0,0x6e,0x0,0x64,0x0,0x69,0x0,0x76,0x0, + 0x69,0x0,0x64,0x0,0x75,0x0,0x61,0x0,0x6c,0x0,0x20,0x0,0x63,0x0,0x6f,0x0,0x6d,0x0,0x70,0x0,0x6f,0x0,0x6e,0x0,0x65,0x0,0x6e,0x0,0x74,0x0, + 0x73,0x0,0xa,0x0,0x69,0x0,0x6e,0x0,0x20,0x0,0x4f,0x0,0x72,0x0,0x69,0x0,0x67,0x0,0x69,0x0,0x6e,0x0,0x61,0x0,0x6c,0x0,0x20,0x0,0x6f,0x0, + 0x72,0x0,0x20,0x0,0x4d,0x0,0x6f,0x0,0x64,0x0,0x69,0x0,0x66,0x0,0x69,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x56,0x0,0x65,0x0,0x72,0x0,0x73,0x0, + 0x69,0x0,0x6f,0x0,0x6e,0x0,0x73,0x0,0xa,0x0,0x6d,0x0,0x61,0x0,0x79,0x0,0x20,0x0,0x62,0x0,0x65,0x0,0x20,0x0,0x73,0x0,0x6f,0x0,0x6c,0x0, + 0x64,0x0,0x20,0x0,0x62,0x0,0x79,0x0,0x20,0x0,0x69,0x0,0x74,0x0,0x73,0x0,0x65,0x0,0x6c,0x0,0x66,0x0,0x2e,0x0,0xa,0x0,0xa,0x0,0x32,0x0, + 0x29,0x0,0x20,0x0,0x4f,0x0,0x72,0x0,0x69,0x0,0x67,0x0,0x69,0x0,0x6e,0x0,0x61,0x0,0x6c,0x0,0x20,0x0,0x6f,0x0,0x72,0x0,0x20,0x0,0x4d,0x0, + 0x6f,0x0,0x64,0x0,0x69,0x0,0x66,0x0,0x69,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x56,0x0,0x65,0x0,0x72,0x0,0x73,0x0,0x69,0x0,0x6f,0x0,0x6e,0x0, + 0x73,0x0,0x20,0x0,0x6f,0x0,0x66,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x46,0x0,0x6f,0x0,0x6e,0x0,0x74,0x0,0x20,0x0,0x53,0x0, + 0x6f,0x0,0x66,0x0,0x74,0x0,0x77,0x0,0x61,0x0,0x72,0x0,0x65,0x0,0x20,0x0,0x6d,0x0,0x61,0x0,0x79,0x0,0x20,0x0,0x62,0x0,0x65,0x0,0x20,0x0, + 0x62,0x0,0x75,0x0,0x6e,0x0,0x64,0x0,0x6c,0x0,0x65,0x0,0x64,0x0,0xa,0x0,0x72,0x0,0x65,0x0,0x64,0x0,0x69,0x0,0x73,0x0,0x74,0x0,0x72,0x0, + 0x69,0x0,0x62,0x0,0x75,0x0,0x74,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x61,0x0,0x6e,0x0,0x64,0x0,0x2f,0x0,0x6f,0x0,0x72,0x0,0x20,0x0,0x73,0x0, + 0x6f,0x0,0x6c,0x0,0x64,0x0,0x20,0x0,0x77,0x0,0x69,0x0,0x74,0x0,0x68,0x0,0x20,0x0,0x61,0x0,0x6e,0x0,0x79,0x0,0x20,0x0,0x73,0x0,0x6f,0x0, + 0x66,0x0,0x74,0x0,0x77,0x0,0x61,0x0,0x72,0x0,0x65,0x0,0xa,0x0,0x70,0x0,0x72,0x0,0x6f,0x0,0x76,0x0,0x69,0x0,0x64,0x0,0x65,0x0,0x64,0x0, + 0x20,0x0,0x74,0x0,0x68,0x0,0x61,0x0,0x74,0x0,0x20,0x0,0x65,0x0,0x61,0x0,0x63,0x0,0x68,0x0,0x20,0x0,0x63,0x0,0x6f,0x0,0x70,0x0,0x79,0x0, + 0x20,0x0,0x63,0x0,0x6f,0x0,0x6e,0x0,0x74,0x0,0x61,0x0,0x69,0x0,0x6e,0x0,0x73,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x61,0x0, + 0x62,0x0,0x6f,0x0,0x76,0x0,0x65,0x0,0x20,0x0,0x63,0x0,0x6f,0x0,0x70,0x0,0x79,0x0,0x72,0x0,0x69,0x0,0x67,0x0,0x68,0x0,0x74,0x0,0x20,0x0, + 0x6e,0x0,0x6f,0x0,0x74,0x0,0x69,0x0,0x63,0x0,0x65,0x0,0x20,0x0,0x61,0x0,0x6e,0x0,0x64,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x69,0x0,0x73,0x0, + 0x20,0x0,0x6c,0x0,0x69,0x0,0x63,0x0,0x65,0x0,0x6e,0x0,0x73,0x0,0x65,0x0,0x2e,0x0,0x20,0x0,0x54,0x0,0x68,0x0,0x65,0x0,0x73,0x0,0x65,0x0, + 0x20,0x0,0x63,0x0,0x61,0x0,0x6e,0x0,0x20,0x0,0x62,0x0,0x65,0x0,0x20,0x0,0x69,0x0,0x6e,0x0,0x63,0x0,0x6c,0x0,0x75,0x0,0x64,0x0,0x65,0x0, + 0x64,0x0,0x20,0x0,0x65,0x0,0x69,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x72,0x0,0x20,0x0,0x61,0x0,0x73,0x0,0x20,0x0,0x73,0x0,0x74,0x0,0x61,0x0, + 0x6e,0x0,0x64,0x0,0x2d,0x0,0x61,0x0,0x6c,0x0,0x6f,0x0,0x6e,0x0,0x65,0x0,0x20,0x0,0x74,0x0,0x65,0x0,0x78,0x0,0x74,0x0,0x20,0x0,0x66,0x0, + 0x69,0x0,0x6c,0x0,0x65,0x0,0x73,0x0,0xa,0x0,0x68,0x0,0x75,0x0,0x6d,0x0,0x61,0x0,0x6e,0x0,0x2d,0x0,0x72,0x0,0x65,0x0,0x61,0x0,0x64,0x0, + 0x61,0x0,0x62,0x0,0x6c,0x0,0x65,0x0,0x20,0x0,0x68,0x0,0x65,0x0,0x61,0x0,0x64,0x0,0x65,0x0,0x72,0x0,0x73,0x0,0x20,0x0,0x6f,0x0,0x72,0x0, + 0x20,0x0,0x69,0x0,0x6e,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x61,0x0,0x70,0x0,0x70,0x0,0x72,0x0,0x6f,0x0,0x70,0x0,0x72,0x0, + 0x69,0x0,0x61,0x0,0x74,0x0,0x65,0x0,0x20,0x0,0x6d,0x0,0x61,0x0,0x63,0x0,0x68,0x0,0x69,0x0,0x6e,0x0,0x65,0x0,0x2d,0x0,0x72,0x0,0x65,0x0, + 0x61,0x0,0x64,0x0,0x61,0x0,0x62,0x0,0x6c,0x0,0x65,0x0,0x20,0x0,0x6d,0x0,0x65,0x0,0x74,0x0,0x61,0x0,0x64,0x0,0x61,0x0,0x74,0x0,0x61,0x0, + 0x20,0x0,0x66,0x0,0x69,0x0,0x65,0x0,0x6c,0x0,0x64,0x0,0x73,0x0,0x20,0x0,0x77,0x0,0x69,0x0,0x74,0x0,0x68,0x0,0x69,0x0,0x6e,0x0,0x20,0x0, + 0x74,0x0,0x65,0x0,0x78,0x0,0x74,0x0,0x20,0x0,0x6f,0x0,0x72,0x0,0x20,0x0,0x62,0x0,0x69,0x0,0x6e,0x0,0x61,0x0,0x72,0x0,0x79,0x0,0x20,0x0, + 0x66,0x0,0x69,0x0,0x6c,0x0,0x65,0x0,0x73,0x0,0x20,0x0,0x61,0x0,0x73,0x0,0x20,0x0,0x6c,0x0,0x6f,0x0,0x6e,0x0,0x67,0x0,0x20,0x0,0x61,0x0, + 0x73,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x6f,0x0,0x73,0x0,0x65,0x0,0x20,0x0,0x66,0x0,0x69,0x0,0x65,0x0,0x6c,0x0,0x64,0x0,0x73,0x0,0x20,0x0, + 0x63,0x0,0x61,0x0,0x6e,0x0,0x20,0x0,0x62,0x0,0x65,0x0,0x20,0x0,0x65,0x0,0x61,0x0,0x73,0x0,0x69,0x0,0x6c,0x0,0x79,0x0,0x20,0x0,0x76,0x0, + 0x69,0x0,0x65,0x0,0x77,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x62,0x0,0x79,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x75,0x0,0x73,0x0, + 0x65,0x0,0x72,0x0,0x2e,0x0,0xa,0x0,0xa,0x0,0x33,0x0,0x29,0x0,0x20,0x0,0x4e,0x0,0x6f,0x0,0x20,0x0,0x4d,0x0,0x6f,0x0,0x64,0x0,0x69,0x0, + 0x66,0x0,0x69,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x56,0x0,0x65,0x0,0x72,0x0,0x73,0x0,0x69,0x0,0x6f,0x0,0x6e,0x0,0x20,0x0,0x6f,0x0,0x66,0x0, + 0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x46,0x0,0x6f,0x0,0x6e,0x0,0x74,0x0,0x20,0x0,0x53,0x0,0x6f,0x0,0x66,0x0,0x74,0x0,0x77,0x0, + 0x61,0x0,0x72,0x0,0x65,0x0,0x20,0x0,0x6d,0x0,0x61,0x0,0x79,0x0,0x20,0x0,0x75,0x0,0x73,0x0,0x65,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0, + 0x20,0x0,0x52,0x0,0x65,0x0,0x73,0x0,0x65,0x0,0x72,0x0,0x76,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x46,0x0,0x6f,0x0,0x6e,0x0,0x74,0x0,0x20,0x0, + 0x4e,0x0,0x61,0x0,0x6d,0x0,0x65,0x0,0x28,0x0,0x73,0x0,0x29,0x0,0x20,0x0,0x75,0x0,0x6e,0x0,0x6c,0x0,0x65,0x0,0x73,0x0,0x73,0x0,0x20,0x0, + 0x65,0x0,0x78,0x0,0x70,0x0,0x6c,0x0,0x69,0x0,0x63,0x0,0x69,0x0,0x74,0x0,0x20,0x0,0x77,0x0,0x72,0x0,0x69,0x0,0x74,0x0,0x74,0x0,0x65,0x0, + 0x6e,0x0,0x20,0x0,0x70,0x0,0x65,0x0,0x72,0x0,0x6d,0x0,0x69,0x0,0x73,0x0,0x73,0x0,0x69,0x0,0x6f,0x0,0x6e,0x0,0x20,0x0,0x69,0x0,0x73,0x0, + 0x20,0x0,0x67,0x0,0x72,0x0,0x61,0x0,0x6e,0x0,0x74,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x62,0x0,0x79,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0, + 0x20,0x0,0x63,0x0,0x6f,0x0,0x72,0x0,0x72,0x0,0x65,0x0,0x73,0x0,0x70,0x0,0x6f,0x0,0x6e,0x0,0x64,0x0,0x69,0x0,0x6e,0x0,0x67,0x0,0x20,0x0, + 0x43,0x0,0x6f,0x0,0x70,0x0,0x79,0x0,0x72,0x0,0x69,0x0,0x67,0x0,0x68,0x0,0x74,0x0,0x20,0x0,0x48,0x0,0x6f,0x0,0x6c,0x0,0x64,0x0,0x65,0x0, + 0x72,0x0,0x2e,0x0,0x20,0x0,0x54,0x0,0x68,0x0,0x69,0x0,0x73,0x0,0x20,0x0,0x72,0x0,0x65,0x0,0x73,0x0,0x74,0x0,0x72,0x0,0x69,0x0,0x63,0x0, + 0x74,0x0,0x69,0x0,0x6f,0x0,0x6e,0x0,0x20,0x0,0x6f,0x0,0x6e,0x0,0x6c,0x0,0x79,0x0,0x20,0x0,0x61,0x0,0x70,0x0,0x70,0x0,0x6c,0x0,0x69,0x0, + 0x65,0x0,0x73,0x0,0x20,0x0,0x74,0x0,0x6f,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x70,0x0,0x72,0x0,0x69,0x0,0x6d,0x0,0x61,0x0, + 0x72,0x0,0x79,0x0,0x20,0x0,0x66,0x0,0x6f,0x0,0x6e,0x0,0x74,0x0,0x20,0x0,0x6e,0x0,0x61,0x0,0x6d,0x0,0x65,0x0,0x20,0x0,0x61,0x0,0x73,0x0, + 0x20,0x0,0x70,0x0,0x72,0x0,0x65,0x0,0x73,0x0,0x65,0x0,0x6e,0x0,0x74,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x74,0x0,0x6f,0x0,0x20,0x0,0x74,0x0, + 0x68,0x0,0x65,0x0,0x20,0x0,0x75,0x0,0x73,0x0,0x65,0x0,0x72,0x0,0x73,0x0,0x2e,0x0,0xa,0x0,0xa,0x0,0x34,0x0,0x29,0x0,0x20,0x0,0x54,0x0, + 0x68,0x0,0x65,0x0,0x20,0x0,0x6e,0x0,0x61,0x0,0x6d,0x0,0x65,0x0,0x28,0x0,0x73,0x0,0x29,0x0,0x20,0x0,0x6f,0x0,0x66,0x0,0x20,0x0,0x74,0x0, + 0x68,0x0,0x65,0x0,0x20,0x0,0x43,0x0,0x6f,0x0,0x70,0x0,0x79,0x0,0x72,0x0,0x69,0x0,0x67,0x0,0x68,0x0,0x74,0x0,0x20,0x0,0x48,0x0,0x6f,0x0, + 0x6c,0x0,0x64,0x0,0x65,0x0,0x72,0x0,0x28,0x0,0x73,0x0,0x29,0x0,0x20,0x0,0x6f,0x0,0x72,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0, + 0x41,0x0,0x75,0x0,0x74,0x0,0x68,0x0,0x6f,0x0,0x72,0x0,0x28,0x0,0x73,0x0,0x29,0x0,0x20,0x0,0x6f,0x0,0x66,0x0,0x20,0x0,0x74,0x0,0x68,0x0, + 0x65,0x0,0x20,0x0,0x46,0x0,0x6f,0x0,0x6e,0x0,0x74,0x0,0x20,0x0,0x53,0x0,0x6f,0x0,0x66,0x0,0x74,0x0,0x77,0x0,0x61,0x0,0x72,0x0,0x65,0x0, + 0x20,0x0,0x73,0x0,0x68,0x0,0x61,0x0,0x6c,0x0,0x6c,0x0,0x20,0x0,0x6e,0x0,0x6f,0x0,0x74,0x0,0x20,0x0,0x62,0x0,0x65,0x0,0x20,0x0,0x75,0x0, + 0x73,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x74,0x0,0x6f,0x0,0x20,0x0,0x70,0x0,0x72,0x0,0x6f,0x0,0x6d,0x0,0x6f,0x0,0x74,0x0,0x65,0x0,0xa,0x0, + 0x65,0x0,0x6e,0x0,0x64,0x0,0x6f,0x0,0x72,0x0,0x73,0x0,0x65,0x0,0x20,0x0,0x6f,0x0,0x72,0x0,0x20,0x0,0x61,0x0,0x64,0x0,0x76,0x0,0x65,0x0, + 0x72,0x0,0x74,0x0,0x69,0x0,0x73,0x0,0x65,0x0,0x20,0x0,0x61,0x0,0x6e,0x0,0x79,0x0,0x20,0x0,0x4d,0x0,0x6f,0x0,0x64,0x0,0x69,0x0,0x66,0x0, + 0x69,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x56,0x0,0x65,0x0,0x72,0x0,0x73,0x0,0x69,0x0,0x6f,0x0,0x6e,0x0,0xa,0x0,0x65,0x0,0x78,0x0,0x63,0x0, + 0x65,0x0,0x70,0x0,0x74,0x0,0x20,0x0,0x74,0x0,0x6f,0x0,0x20,0x0,0x61,0x0,0x63,0x0,0x6b,0x0,0x6e,0x0,0x6f,0x0,0x77,0x0,0x6c,0x0,0x65,0x0, + 0x64,0x0,0x67,0x0,0x65,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x63,0x0,0x6f,0x0,0x6e,0x0,0x74,0x0,0x72,0x0,0x69,0x0,0x62,0x0, + 0x75,0x0,0x74,0x0,0x69,0x0,0x6f,0x0,0x6e,0x0,0x28,0x0,0x73,0x0,0x29,0x0,0x20,0x0,0x6f,0x0,0x66,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0, + 0x20,0x0,0x43,0x0,0x6f,0x0,0x70,0x0,0x79,0x0,0x72,0x0,0x69,0x0,0x67,0x0,0x68,0x0,0x74,0x0,0x20,0x0,0x48,0x0,0x6f,0x0,0x6c,0x0,0x64,0x0, + 0x65,0x0,0x72,0x0,0x28,0x0,0x73,0x0,0x29,0x0,0x20,0x0,0x61,0x0,0x6e,0x0,0x64,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x41,0x0, + 0x75,0x0,0x74,0x0,0x68,0x0,0x6f,0x0,0x72,0x0,0x28,0x0,0x73,0x0,0x29,0x0,0x20,0x0,0x6f,0x0,0x72,0x0,0x20,0x0,0x77,0x0,0x69,0x0,0x74,0x0, + 0x68,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x69,0x0,0x72,0x0,0x20,0x0,0x65,0x0,0x78,0x0,0x70,0x0,0x6c,0x0,0x69,0x0,0x63,0x0,0x69,0x0, + 0x74,0x0,0x20,0x0,0x77,0x0,0x72,0x0,0x69,0x0,0x74,0x0,0x74,0x0,0x65,0x0,0x6e,0x0,0x20,0x0,0x70,0x0,0x65,0x0,0x72,0x0,0x6d,0x0,0x69,0x0, + 0x73,0x0,0x73,0x0,0x69,0x0,0x6f,0x0,0x6e,0x0,0x2e,0x0,0xa,0x0,0xa,0x0,0x35,0x0,0x29,0x0,0x20,0x0,0x54,0x0,0x68,0x0,0x65,0x0,0x20,0x0, + 0x46,0x0,0x6f,0x0,0x6e,0x0,0x74,0x0,0x20,0x0,0x53,0x0,0x6f,0x0,0x66,0x0,0x74,0x0,0x77,0x0,0x61,0x0,0x72,0x0,0x65,0x0,0xa,0x0,0x6d,0x0, + 0x6f,0x0,0x64,0x0,0x69,0x0,0x66,0x0,0x69,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x6f,0x0,0x72,0x0,0x20,0x0,0x75,0x0,0x6e,0x0,0x6d,0x0,0x6f,0x0, + 0x64,0x0,0x69,0x0,0x66,0x0,0x69,0x0,0x65,0x0,0x64,0x0,0xa,0x0,0x69,0x0,0x6e,0x0,0x20,0x0,0x70,0x0,0x61,0x0,0x72,0x0,0x74,0x0,0x20,0x0, + 0x6f,0x0,0x72,0x0,0x20,0x0,0x69,0x0,0x6e,0x0,0x20,0x0,0x77,0x0,0x68,0x0,0x6f,0x0,0x6c,0x0,0x65,0x0,0xa,0x0,0x6d,0x0,0x75,0x0,0x73,0x0, + 0x74,0x0,0x20,0x0,0x62,0x0,0x65,0x0,0x20,0x0,0x64,0x0,0x69,0x0,0x73,0x0,0x74,0x0,0x72,0x0,0x69,0x0,0x62,0x0,0x75,0x0,0x74,0x0,0x65,0x0, + 0x64,0x0,0x20,0x0,0x65,0x0,0x6e,0x0,0x74,0x0,0x69,0x0,0x72,0x0,0x65,0x0,0x6c,0x0,0x79,0x0,0x20,0x0,0x75,0x0,0x6e,0x0,0x64,0x0,0x65,0x0, + 0x72,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x69,0x0,0x73,0x0,0x20,0x0,0x6c,0x0,0x69,0x0,0x63,0x0,0x65,0x0,0x6e,0x0,0x73,0x0,0x65,0x0,0xa,0x0, + 0x61,0x0,0x6e,0x0,0x64,0x0,0x20,0x0,0x6d,0x0,0x75,0x0,0x73,0x0,0x74,0x0,0x20,0x0,0x6e,0x0,0x6f,0x0,0x74,0x0,0x20,0x0,0x62,0x0,0x65,0x0, + 0x20,0x0,0x64,0x0,0x69,0x0,0x73,0x0,0x74,0x0,0x72,0x0,0x69,0x0,0x62,0x0,0x75,0x0,0x74,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x75,0x0,0x6e,0x0, + 0x64,0x0,0x65,0x0,0x72,0x0,0x20,0x0,0x61,0x0,0x6e,0x0,0x79,0x0,0x20,0x0,0x6f,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x72,0x0,0x20,0x0,0x6c,0x0, + 0x69,0x0,0x63,0x0,0x65,0x0,0x6e,0x0,0x73,0x0,0x65,0x0,0x2e,0x0,0x20,0x0,0x54,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x72,0x0,0x65,0x0,0x71,0x0, + 0x75,0x0,0x69,0x0,0x72,0x0,0x65,0x0,0x6d,0x0,0x65,0x0,0x6e,0x0,0x74,0x0,0x20,0x0,0x66,0x0,0x6f,0x0,0x72,0x0,0x20,0x0,0x66,0x0,0x6f,0x0, + 0x6e,0x0,0x74,0x0,0x73,0x0,0x20,0x0,0x74,0x0,0x6f,0x0,0x20,0x0,0x72,0x0,0x65,0x0,0x6d,0x0,0x61,0x0,0x69,0x0,0x6e,0x0,0x20,0x0,0x75,0x0, + 0x6e,0x0,0x64,0x0,0x65,0x0,0x72,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x69,0x0,0x73,0x0,0x20,0x0,0x6c,0x0,0x69,0x0,0x63,0x0,0x65,0x0,0x6e,0x0, + 0x73,0x0,0x65,0x0,0x20,0x0,0x64,0x0,0x6f,0x0,0x65,0x0,0x73,0x0,0x20,0x0,0x6e,0x0,0x6f,0x0,0x74,0x0,0x20,0x0,0x61,0x0,0x70,0x0,0x70,0x0, + 0x6c,0x0,0x79,0x0,0x20,0x0,0x74,0x0,0x6f,0x0,0x20,0x0,0x61,0x0,0x6e,0x0,0x79,0x0,0x20,0x0,0x64,0x0,0x6f,0x0,0x63,0x0,0x75,0x0,0x6d,0x0, + 0x65,0x0,0x6e,0x0,0x74,0x0,0x20,0x0,0x63,0x0,0x72,0x0,0x65,0x0,0x61,0x0,0x74,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x75,0x0,0x73,0x0,0x69,0x0, + 0x6e,0x0,0x67,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x46,0x0,0x6f,0x0,0x6e,0x0,0x74,0x0,0x20,0x0,0x53,0x0,0x6f,0x0,0x66,0x0, + 0x74,0x0,0x77,0x0,0x61,0x0,0x72,0x0,0x65,0x0,0x2e,0x0,0xa,0x0,0xa,0x0,0x54,0x0,0x45,0x0,0x52,0x0,0x4d,0x0,0x49,0x0,0x4e,0x0,0x41,0x0, + 0x54,0x0,0x49,0x0,0x4f,0x0,0x4e,0x0,0xa,0x0,0x54,0x0,0x68,0x0,0x69,0x0,0x73,0x0,0x20,0x0,0x6c,0x0,0x69,0x0,0x63,0x0,0x65,0x0,0x6e,0x0, + 0x73,0x0,0x65,0x0,0x20,0x0,0x62,0x0,0x65,0x0,0x63,0x0,0x6f,0x0,0x6d,0x0,0x65,0x0,0x73,0x0,0x20,0x0,0x6e,0x0,0x75,0x0,0x6c,0x0,0x6c,0x0, + 0x20,0x0,0x61,0x0,0x6e,0x0,0x64,0x0,0x20,0x0,0x76,0x0,0x6f,0x0,0x69,0x0,0x64,0x0,0x20,0x0,0x69,0x0,0x66,0x0,0x20,0x0,0x61,0x0,0x6e,0x0, + 0x79,0x0,0x20,0x0,0x6f,0x0,0x66,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x61,0x0,0x62,0x0,0x6f,0x0,0x76,0x0,0x65,0x0,0x20,0x0, + 0x63,0x0,0x6f,0x0,0x6e,0x0,0x64,0x0,0x69,0x0,0x74,0x0,0x69,0x0,0x6f,0x0,0x6e,0x0,0x73,0x0,0x20,0x0,0x61,0x0,0x72,0x0,0x65,0x0,0x20,0x0, + 0x6e,0x0,0x6f,0x0,0x74,0x0,0x20,0x0,0x6d,0x0,0x65,0x0,0x74,0x0,0x2e,0x0,0xa,0x0,0xa,0x0,0x44,0x0,0x49,0x0,0x53,0x0,0x43,0x0,0x4c,0x0, + 0x41,0x0,0x49,0x0,0x4d,0x0,0x45,0x0,0x52,0x0,0xa,0x0,0x54,0x0,0x48,0x0,0x45,0x0,0x20,0x0,0x46,0x0,0x4f,0x0,0x4e,0x0,0x54,0x0,0x20,0x0, + 0x53,0x0,0x4f,0x0,0x46,0x0,0x54,0x0,0x57,0x0,0x41,0x0,0x52,0x0,0x45,0x0,0x20,0x0,0x49,0x0,0x53,0x0,0x20,0x0,0x50,0x0,0x52,0x0,0x4f,0x0, + 0x56,0x0,0x49,0x0,0x44,0x0,0x45,0x0,0x44,0x0,0x20,0x0,0x22,0x0,0x41,0x0,0x53,0x0,0x20,0x0,0x49,0x0,0x53,0x0,0x22,0x0,0xa,0x0,0x57,0x0, + 0x49,0x0,0x54,0x0,0x48,0x0,0x4f,0x0,0x55,0x0,0x54,0x0,0x20,0x0,0x57,0x0,0x41,0x0,0x52,0x0,0x52,0x0,0x41,0x0,0x4e,0x0,0x54,0x0,0x59,0x0, + 0x20,0x0,0x4f,0x0,0x46,0x0,0x20,0x0,0x41,0x0,0x4e,0x0,0x59,0x0,0x20,0x0,0x4b,0x0,0x49,0x0,0x4e,0x0,0x44,0x0,0xa,0x0,0x45,0x0,0x58,0x0, + 0x50,0x0,0x52,0x0,0x45,0x0,0x53,0x0,0x53,0x0,0x20,0x0,0x4f,0x0,0x52,0x0,0x20,0x0,0x49,0x0,0x4d,0x0,0x50,0x0,0x4c,0x0,0x49,0x0,0x45,0x0, + 0x44,0x0,0xa,0x0,0x49,0x0,0x4e,0x0,0x43,0x0,0x4c,0x0,0x55,0x0,0x44,0x0,0x49,0x0,0x4e,0x0,0x47,0x0,0x20,0x0,0x42,0x0,0x55,0x0,0x54,0x0, + 0x20,0x0,0x4e,0x0,0x4f,0x0,0x54,0x0,0x20,0x0,0x4c,0x0,0x49,0x0,0x4d,0x0,0x49,0x0,0x54,0x0,0x45,0x0,0x44,0x0,0x20,0x0,0x54,0x0,0x4f,0x0, + 0x20,0x0,0x41,0x0,0x4e,0x0,0x59,0x0,0x20,0x0,0x57,0x0,0x41,0x0,0x52,0x0,0x52,0x0,0x41,0x0,0x4e,0x0,0x54,0x0,0x49,0x0,0x45,0x0,0x53,0x0, + 0x20,0x0,0x4f,0x0,0x46,0x0,0x20,0x0,0x4d,0x0,0x45,0x0,0x52,0x0,0x43,0x0,0x48,0x0,0x41,0x0,0x4e,0x0,0x54,0x0,0x41,0x0,0x42,0x0,0x49,0x0, + 0x4c,0x0,0x49,0x0,0x54,0x0,0x59,0x0,0xa,0x0,0x46,0x0,0x49,0x0,0x54,0x0,0x4e,0x0,0x45,0x0,0x53,0x0,0x53,0x0,0x20,0x0,0x46,0x0,0x4f,0x0, + 0x52,0x0,0x20,0x0,0x41,0x0,0x20,0x0,0x50,0x0,0x41,0x0,0x52,0x0,0x54,0x0,0x49,0x0,0x43,0x0,0x55,0x0,0x4c,0x0,0x41,0x0,0x52,0x0,0x20,0x0, + 0x50,0x0,0x55,0x0,0x52,0x0,0x50,0x0,0x4f,0x0,0x53,0x0,0x45,0x0,0x20,0x0,0x41,0x0,0x4e,0x0,0x44,0x0,0x20,0x0,0x4e,0x0,0x4f,0x0,0x4e,0x0, + 0x49,0x0,0x4e,0x0,0x46,0x0,0x52,0x0,0x49,0x0,0x4e,0x0,0x47,0x0,0x45,0x0,0x4d,0x0,0x45,0x0,0x4e,0x0,0x54,0x0,0x20,0x0,0x4f,0x0,0x46,0x0, + 0x20,0x0,0x43,0x0,0x4f,0x0,0x50,0x0,0x59,0x0,0x52,0x0,0x49,0x0,0x47,0x0,0x48,0x0,0x54,0x0,0xa,0x0,0x50,0x0,0x41,0x0,0x54,0x0,0x45,0x0, + 0x4e,0x0,0x54,0x0,0xa,0x0,0x54,0x0,0x52,0x0,0x41,0x0,0x44,0x0,0x45,0x0,0x4d,0x0,0x41,0x0,0x52,0x0,0x4b,0x0,0xa,0x0,0x4f,0x0,0x52,0x0, + 0x20,0x0,0x4f,0x0,0x54,0x0,0x48,0x0,0x45,0x0,0x52,0x0,0x20,0x0,0x52,0x0,0x49,0x0,0x47,0x0,0x48,0x0,0x54,0x0,0x2e,0x0,0x20,0x0,0x49,0x0, + 0x4e,0x0,0x20,0x0,0x4e,0x0,0x4f,0x0,0x20,0x0,0x45,0x0,0x56,0x0,0x45,0x0,0x4e,0x0,0x54,0x0,0x20,0x0,0x53,0x0,0x48,0x0,0x41,0x0,0x4c,0x0, + 0x4c,0x0,0x20,0x0,0x54,0x0,0x48,0x0,0x45,0x0,0x20,0x0,0x43,0x0,0x4f,0x0,0x50,0x0,0x59,0x0,0x52,0x0,0x49,0x0,0x47,0x0,0x48,0x0,0x54,0x0, + 0x20,0x0,0x48,0x0,0x4f,0x0,0x4c,0x0,0x44,0x0,0x45,0x0,0x52,0x0,0x20,0x0,0x42,0x0,0x45,0x0,0x20,0x0,0x4c,0x0,0x49,0x0,0x41,0x0,0x42,0x0, + 0x4c,0x0,0x45,0x0,0x20,0x0,0x46,0x0,0x4f,0x0,0x52,0x0,0x20,0x0,0x41,0x0,0x4e,0x0,0x59,0x0,0x20,0x0,0x43,0x0,0x4c,0x0,0x41,0x0,0x49,0x0, + 0x4d,0x0,0xa,0x0,0x44,0x0,0x41,0x0,0x4d,0x0,0x41,0x0,0x47,0x0,0x45,0x0,0x53,0x0,0x20,0x0,0x4f,0x0,0x52,0x0,0x20,0x0,0x4f,0x0,0x54,0x0, + 0x48,0x0,0x45,0x0,0x52,0x0,0x20,0x0,0x4c,0x0,0x49,0x0,0x41,0x0,0x42,0x0,0x49,0x0,0x4c,0x0,0x49,0x0,0x54,0x0,0x59,0x0,0xa,0x0,0x49,0x0, + 0x4e,0x0,0x43,0x0,0x4c,0x0,0x55,0x0,0x44,0x0,0x49,0x0,0x4e,0x0,0x47,0x0,0x20,0x0,0x41,0x0,0x4e,0x0,0x59,0x0,0x20,0x0,0x47,0x0,0x45,0x0, + 0x4e,0x0,0x45,0x0,0x52,0x0,0x41,0x0,0x4c,0x0,0xa,0x0,0x53,0x0,0x50,0x0,0x45,0x0,0x43,0x0,0x49,0x0,0x41,0x0,0x4c,0x0,0xa,0x0,0x49,0x0, + 0x4e,0x0,0x44,0x0,0x49,0x0,0x52,0x0,0x45,0x0,0x43,0x0,0x54,0x0,0xa,0x0,0x49,0x0,0x4e,0x0,0x43,0x0,0x49,0x0,0x44,0x0,0x45,0x0,0x4e,0x0, + 0x54,0x0,0x41,0x0,0x4c,0x0,0xa,0x0,0x4f,0x0,0x52,0x0,0x20,0x0,0x43,0x0,0x4f,0x0,0x4e,0x0,0x53,0x0,0x45,0x0,0x51,0x0,0x55,0x0,0x45,0x0, + 0x4e,0x0,0x54,0x0,0x49,0x0,0x41,0x0,0x4c,0x0,0x20,0x0,0x44,0x0,0x41,0x0,0x4d,0x0,0x41,0x0,0x47,0x0,0x45,0x0,0x53,0x0,0xa,0x0,0x57,0x0, + 0x48,0x0,0x45,0x0,0x54,0x0,0x48,0x0,0x45,0x0,0x52,0x0,0x20,0x0,0x49,0x0,0x4e,0x0,0x20,0x0,0x41,0x0,0x4e,0x0,0x20,0x0,0x41,0x0,0x43,0x0, + 0x54,0x0,0x49,0x0,0x4f,0x0,0x4e,0x0,0x20,0x0,0x4f,0x0,0x46,0x0,0x20,0x0,0x43,0x0,0x4f,0x0,0x4e,0x0,0x54,0x0,0x52,0x0,0x41,0x0,0x43,0x0, + 0x54,0x0,0xa,0x0,0x54,0x0,0x4f,0x0,0x52,0x0,0x54,0x0,0x20,0x0,0x4f,0x0,0x52,0x0,0x20,0x0,0x4f,0x0,0x54,0x0,0x48,0x0,0x45,0x0,0x52,0x0, + 0x57,0x0,0x49,0x0,0x53,0x0,0x45,0x0,0xa,0x0,0x41,0x0,0x52,0x0,0x49,0x0,0x53,0x0,0x49,0x0,0x4e,0x0,0x47,0x0,0x20,0x0,0x46,0x0,0x52,0x0, + 0x4f,0x0,0x4d,0x0,0xa,0x0,0x4f,0x0,0x55,0x0,0x54,0x0,0x20,0x0,0x4f,0x0,0x46,0x0,0x20,0x0,0x54,0x0,0x48,0x0,0x45,0x0,0x20,0x0,0x55,0x0, + 0x53,0x0,0x45,0x0,0x20,0x0,0x4f,0x0,0x52,0x0,0x20,0x0,0x49,0x0,0x4e,0x0,0x41,0x0,0x42,0x0,0x49,0x0,0x4c,0x0,0x49,0x0,0x54,0x0,0x59,0x0, + 0x20,0x0,0x54,0x0,0x4f,0x0,0x20,0x0,0x55,0x0,0x53,0x0,0x45,0x0,0x20,0x0,0x54,0x0,0x48,0x0,0x45,0x0,0x20,0x0,0x46,0x0,0x4f,0x0,0x4e,0x0, + 0x54,0x0,0x20,0x0,0x53,0x0,0x4f,0x0,0x46,0x0,0x54,0x0,0x57,0x0,0x41,0x0,0x52,0x0,0x45,0x0,0x20,0x0,0x4f,0x0,0x52,0x0,0x20,0x0,0x46,0x0, + 0x52,0x0,0x4f,0x0,0x4d,0x0,0x20,0x0,0x4f,0x0,0x54,0x0,0x48,0x0,0x45,0x0,0x52,0x0,0x20,0x0,0x44,0x0,0x45,0x0,0x41,0x0,0x4c,0x0,0x49,0x0, + 0x4e,0x0,0x47,0x0,0x53,0x0,0x20,0x0,0x49,0x0,0x4e,0x0,0x20,0x0,0x54,0x0,0x48,0x0,0x45,0x0,0x20,0x0,0x46,0x0,0x4f,0x0,0x4e,0x0,0x54,0x0, + 0x20,0x0,0x53,0x0,0x4f,0x0,0x46,0x0,0x54,0x0,0x57,0x0,0x41,0x0,0x52,0x0,0x45,0x0,0x2e,0x0,0x68,0x0,0x74,0x0,0x74,0x0,0x70,0x0,0x3a,0x0, + 0x2f,0x0,0x2f,0x0,0x73,0x0,0x63,0x0,0x72,0x0,0x69,0x0,0x70,0x0,0x74,0x0,0x73,0x0,0x2e,0x0,0x73,0x0,0x69,0x0,0x6c,0x0,0x2e,0x0,0x6f,0x0, + 0x72,0x0,0x67,0x0,0x2f,0x0,0x4f,0x0,0x46,0x0,0x4c,0x0,0x0,0x2,0x0,0x0,0x0,0x0,0x0,0x0,0xff,0x47,0x0,0x82,0x0,0x0,0x0,0x1,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0x9b,0x0,0x0,0x0,0x3,0x0,0x24,0x0,0xc9,0x1,0x2,0x0,0xc7,0x0,0x62, + 0x0,0xad,0x1,0x3,0x1,0x4,0x0,0x63,0x0,0xae,0x0,0x90,0x0,0x25,0x0,0x26,0x0,0xfd,0x0,0xff,0x0,0x64,0x1,0x5,0x1,0x6,0x0,0x27,0x0,0xe9, + 0x1,0x7,0x1,0x8,0x0,0x28,0x0,0x65,0x1,0x9,0x1,0xa,0x0,0xc8,0x0,0xca,0x1,0xb,0x0,0xcb,0x1,0xc,0x1,0xd,0x0,0x29,0x0,0x2a,0x0,0xf8, + 0x1,0xe,0x1,0xf,0x1,0x10,0x0,0x2b,0x1,0x11,0x1,0x12,0x0,0x2c,0x1,0x13,0x0,0xcc,0x1,0x14,0x0,0xcd,0x0,0xce,0x0,0xfa,0x0,0xcf,0x1,0x15, + 0x1,0x16,0x1,0x17,0x0,0x2d,0x1,0x18,0x0,0x2e,0x1,0x19,0x0,0x2f,0x1,0x1a,0x1,0x1b,0x1,0x1c,0x1,0x1d,0x0,0xe2,0x0,0x30,0x0,0x31,0x1,0x1e, + 0x1,0x1f,0x1,0x20,0x1,0x21,0x0,0x66,0x0,0x32,0x0,0xd0,0x1,0x22,0x0,0xd1,0x0,0x67,0x0,0xd3,0x1,0x23,0x1,0x24,0x0,0x91,0x0,0xaf,0x0,0xb0, + 0x0,0x33,0x0,0xed,0x0,0x34,0x0,0x35,0x1,0x25,0x1,0x26,0x1,0x27,0x0,0x36,0x1,0x28,0x0,0xe4,0x0,0xfb,0x1,0x29,0x1,0x2a,0x0,0x37,0x1,0x2b, + 0x1,0x2c,0x1,0x2d,0x1,0x2e,0x0,0x38,0x0,0xd4,0x1,0x2f,0x0,0xd5,0x0,0x68,0x0,0xd6,0x1,0x30,0x1,0x31,0x1,0x32,0x1,0x33,0x1,0x34,0x0,0x39, + 0x0,0x3a,0x1,0x35,0x1,0x36,0x1,0x37,0x1,0x38,0x0,0x3b,0x0,0x3c,0x0,0xeb,0x1,0x39,0x0,0xbb,0x1,0x3a,0x0,0x3d,0x1,0x3b,0x0,0xe6,0x1,0x3c, + 0x0,0x44,0x0,0x69,0x1,0x3d,0x0,0x6b,0x0,0x6c,0x0,0x6a,0x1,0x3e,0x1,0x3f,0x0,0x6e,0x0,0x6d,0x0,0xa0,0x0,0x45,0x0,0x46,0x0,0xfe,0x1,0x0, + 0x0,0x6f,0x1,0x40,0x1,0x41,0x0,0x47,0x0,0xea,0x1,0x42,0x1,0x1,0x0,0x48,0x0,0x70,0x1,0x43,0x1,0x44,0x0,0x72,0x0,0x73,0x1,0x45,0x0,0x71, + 0x1,0x46,0x1,0x47,0x0,0x49,0x0,0x4a,0x0,0xf9,0x1,0x48,0x1,0x49,0x1,0x4a,0x0,0x4b,0x1,0x4b,0x1,0x4c,0x0,0x4c,0x0,0xd7,0x0,0x74,0x1,0x4d, + 0x0,0x76,0x0,0x77,0x1,0x4e,0x0,0x75,0x1,0x4f,0x1,0x50,0x1,0x51,0x1,0x52,0x0,0x4d,0x1,0x53,0x1,0x54,0x0,0x4e,0x1,0x55,0x0,0x4f,0x1,0x56, + 0x1,0x57,0x1,0x58,0x1,0x59,0x0,0xe3,0x0,0x50,0x0,0x51,0x1,0x5a,0x1,0x5b,0x1,0x5c,0x1,0x5d,0x0,0x78,0x0,0x52,0x0,0x79,0x1,0x5e,0x0,0x7b, + 0x0,0x7c,0x0,0x7a,0x1,0x5f,0x1,0x60,0x0,0xa1,0x0,0x7d,0x0,0xb1,0x0,0x53,0x0,0xee,0x0,0x54,0x0,0x55,0x1,0x61,0x1,0x62,0x1,0x63,0x0,0x56, + 0x1,0x64,0x0,0xe5,0x0,0xfc,0x1,0x65,0x1,0x66,0x0,0x89,0x0,0x57,0x1,0x67,0x1,0x68,0x1,0x69,0x1,0x6a,0x0,0x58,0x0,0x7e,0x1,0x6b,0x0,0x80, + 0x0,0x81,0x0,0x7f,0x1,0x6c,0x1,0x6d,0x1,0x6e,0x1,0x6f,0x1,0x70,0x0,0x59,0x0,0x5a,0x1,0x71,0x1,0x72,0x1,0x73,0x1,0x74,0x0,0x5b,0x0,0x5c, + 0x0,0xec,0x1,0x75,0x0,0xba,0x1,0x76,0x0,0x5d,0x1,0x77,0x0,0xe7,0x1,0x78,0x0,0xc0,0x0,0xc1,0x0,0x9d,0x0,0x9e,0x1,0x79,0x1,0x7a,0x1,0x7b, + 0x0,0x9b,0x0,0x13,0x0,0x14,0x0,0x15,0x0,0x16,0x0,0x17,0x0,0x18,0x0,0x19,0x0,0x1a,0x0,0x1b,0x0,0x1c,0x0,0xbc,0x0,0xf4,0x0,0xf5,0x0,0xf6, + 0x1,0x7c,0x1,0x7d,0x1,0x7e,0x1,0x7f,0x1,0x80,0x1,0x81,0x1,0x82,0x1,0x83,0x1,0x84,0x1,0x85,0x1,0x86,0x1,0x87,0x1,0x88,0x0,0xd,0x0,0x3f, + 0x0,0xc3,0x0,0x87,0x0,0x1d,0x0,0xf,0x0,0xab,0x0,0x4,0x0,0xa3,0x0,0x6,0x0,0x11,0x0,0x22,0x0,0xa2,0x0,0x5,0x0,0xa,0x0,0x1e,0x0,0x12, + 0x0,0x42,0x0,0x5e,0x0,0x60,0x0,0x3e,0x0,0x40,0x0,0xb,0x0,0xc,0x1,0x89,0x1,0x8a,0x1,0x8b,0x1,0x8c,0x1,0x8d,0x0,0xb3,0x0,0xb2,0x0,0x10, + 0x1,0x8e,0x1,0x8f,0x0,0xa9,0x0,0xaa,0x0,0xbe,0x0,0xbf,0x0,0xc5,0x0,0xb4,0x0,0xb5,0x0,0xb6,0x0,0xb7,0x0,0xc4,0x1,0x90,0x0,0x84,0x0,0xbd, + 0x0,0x7,0x1,0x91,0x0,0xa6,0x0,0xf7,0x0,0x85,0x1,0x92,0x0,0x96,0x0,0xa7,0x0,0x61,0x0,0xb8,0x0,0x20,0x0,0x21,0x0,0x95,0x0,0x92,0x0,0x9c, + 0x0,0x1f,0x0,0x94,0x0,0xa4,0x0,0xef,0x0,0xf0,0x0,0x8f,0x0,0x98,0x0,0x8,0x0,0xc6,0x0,0xe,0x0,0x93,0x0,0x9a,0x0,0xa5,0x0,0x99,0x0,0xb9, + 0x0,0x5f,0x0,0xe8,0x0,0x23,0x0,0x9,0x0,0x88,0x0,0x8b,0x0,0x8a,0x0,0x86,0x0,0x8c,0x0,0x83,0x0,0x41,0x0,0x82,0x0,0xc2,0x1,0x93,0x1,0x94, + 0x1,0x95,0x1,0x96,0x0,0x8d,0x1,0x97,0x0,0xdb,0x0,0xe1,0x1,0x98,0x1,0x99,0x1,0x9a,0x1,0x9b,0x1,0x9c,0x1,0x9d,0x1,0x9e,0x1,0x9f,0x1,0xa0, + 0x1,0xa1,0x1,0xa2,0x1,0xa3,0x1,0xa4,0x0,0xde,0x0,0xd8,0x0,0x8e,0x0,0xdc,0x0,0x43,0x0,0xdf,0x0,0xda,0x0,0xe0,0x0,0xdd,0x0,0xd9,0x6,0x41, + 0x62,0x72,0x65,0x76,0x65,0x7,0x41,0x6d,0x61,0x63,0x72,0x6f,0x6e,0x7,0x41,0x6f,0x67,0x6f,0x6e,0x65,0x6b,0xb,0x43,0x63,0x69,0x72,0x63,0x75,0x6d,0x66, + 0x6c,0x65,0x78,0xa,0x43,0x64,0x6f,0x74,0x61,0x63,0x63,0x65,0x6e,0x74,0x6,0x44,0x63,0x61,0x72,0x6f,0x6e,0x6,0x44,0x63,0x72,0x6f,0x61,0x74,0x6,0x45, + 0x62,0x72,0x65,0x76,0x65,0x6,0x45,0x63,0x61,0x72,0x6f,0x6e,0xa,0x45,0x64,0x6f,0x74,0x61,0x63,0x63,0x65,0x6e,0x74,0x7,0x45,0x6d,0x61,0x63,0x72,0x6f, + 0x6e,0x7,0x45,0x6f,0x67,0x6f,0x6e,0x65,0x6b,0xb,0x47,0x63,0x69,0x72,0x63,0x75,0x6d,0x66,0x6c,0x65,0x78,0xc,0x47,0x63,0x6f,0x6d,0x6d,0x61,0x61,0x63, + 0x63,0x65,0x6e,0x74,0xa,0x47,0x64,0x6f,0x74,0x61,0x63,0x63,0x65,0x6e,0x74,0x4,0x48,0x62,0x61,0x72,0xb,0x48,0x63,0x69,0x72,0x63,0x75,0x6d,0x66,0x6c, + 0x65,0x78,0x2,0x49,0x4a,0x6,0x49,0x62,0x72,0x65,0x76,0x65,0x7,0x49,0x6d,0x61,0x63,0x72,0x6f,0x6e,0x7,0x49,0x6f,0x67,0x6f,0x6e,0x65,0x6b,0x6,0x49, + 0x74,0x69,0x6c,0x64,0x65,0xb,0x4a,0x63,0x69,0x72,0x63,0x75,0x6d,0x66,0x6c,0x65,0x78,0xc,0x4b,0x63,0x6f,0x6d,0x6d,0x61,0x61,0x63,0x63,0x65,0x6e,0x74, + 0x6,0x4c,0x61,0x63,0x75,0x74,0x65,0x6,0x4c,0x63,0x61,0x72,0x6f,0x6e,0xc,0x4c,0x63,0x6f,0x6d,0x6d,0x61,0x61,0x63,0x63,0x65,0x6e,0x74,0x4,0x4c,0x64, + 0x6f,0x74,0x6,0x4e,0x61,0x63,0x75,0x74,0x65,0x6,0x4e,0x63,0x61,0x72,0x6f,0x6e,0xc,0x4e,0x63,0x6f,0x6d,0x6d,0x61,0x61,0x63,0x63,0x65,0x6e,0x74,0x3, + 0x45,0x6e,0x67,0x6,0x4f,0x62,0x72,0x65,0x76,0x65,0xd,0x4f,0x68,0x75,0x6e,0x67,0x61,0x72,0x75,0x6d,0x6c,0x61,0x75,0x74,0x7,0x4f,0x6d,0x61,0x63,0x72, + 0x6f,0x6e,0x6,0x52,0x61,0x63,0x75,0x74,0x65,0x6,0x52,0x63,0x61,0x72,0x6f,0x6e,0xc,0x52,0x63,0x6f,0x6d,0x6d,0x61,0x61,0x63,0x63,0x65,0x6e,0x74,0x6, + 0x53,0x61,0x63,0x75,0x74,0x65,0xb,0x53,0x63,0x69,0x72,0x63,0x75,0x6d,0x66,0x6c,0x65,0x78,0xc,0x53,0x63,0x6f,0x6d,0x6d,0x61,0x61,0x63,0x63,0x65,0x6e, + 0x74,0x4,0x54,0x62,0x61,0x72,0x6,0x54,0x63,0x61,0x72,0x6f,0x6e,0x7,0x75,0x6e,0x69,0x30,0x31,0x36,0x32,0x7,0x75,0x6e,0x69,0x30,0x32,0x31,0x41,0x6, + 0x55,0x62,0x72,0x65,0x76,0x65,0xd,0x55,0x68,0x75,0x6e,0x67,0x61,0x72,0x75,0x6d,0x6c,0x61,0x75,0x74,0x7,0x55,0x6d,0x61,0x63,0x72,0x6f,0x6e,0x7,0x55, + 0x6f,0x67,0x6f,0x6e,0x65,0x6b,0x5,0x55,0x72,0x69,0x6e,0x67,0x6,0x55,0x74,0x69,0x6c,0x64,0x65,0x6,0x57,0x61,0x63,0x75,0x74,0x65,0xb,0x57,0x63,0x69, + 0x72,0x63,0x75,0x6d,0x66,0x6c,0x65,0x78,0x9,0x57,0x64,0x69,0x65,0x72,0x65,0x73,0x69,0x73,0x6,0x57,0x67,0x72,0x61,0x76,0x65,0xb,0x59,0x63,0x69,0x72, + 0x63,0x75,0x6d,0x66,0x6c,0x65,0x78,0x6,0x59,0x67,0x72,0x61,0x76,0x65,0x6,0x5a,0x61,0x63,0x75,0x74,0x65,0xa,0x5a,0x64,0x6f,0x74,0x61,0x63,0x63,0x65, + 0x6e,0x74,0x6,0x61,0x62,0x72,0x65,0x76,0x65,0x7,0x61,0x6d,0x61,0x63,0x72,0x6f,0x6e,0x7,0x61,0x6f,0x67,0x6f,0x6e,0x65,0x6b,0xb,0x63,0x63,0x69,0x72, + 0x63,0x75,0x6d,0x66,0x6c,0x65,0x78,0xa,0x63,0x64,0x6f,0x74,0x61,0x63,0x63,0x65,0x6e,0x74,0x6,0x64,0x63,0x61,0x72,0x6f,0x6e,0x6,0x65,0x62,0x72,0x65, + 0x76,0x65,0x6,0x65,0x63,0x61,0x72,0x6f,0x6e,0xa,0x65,0x64,0x6f,0x74,0x61,0x63,0x63,0x65,0x6e,0x74,0x7,0x65,0x6d,0x61,0x63,0x72,0x6f,0x6e,0x7,0x65, + 0x6f,0x67,0x6f,0x6e,0x65,0x6b,0xb,0x67,0x63,0x69,0x72,0x63,0x75,0x6d,0x66,0x6c,0x65,0x78,0xc,0x67,0x63,0x6f,0x6d,0x6d,0x61,0x61,0x63,0x63,0x65,0x6e, + 0x74,0xa,0x67,0x64,0x6f,0x74,0x61,0x63,0x63,0x65,0x6e,0x74,0x4,0x68,0x62,0x61,0x72,0xb,0x68,0x63,0x69,0x72,0x63,0x75,0x6d,0x66,0x6c,0x65,0x78,0x6, + 0x69,0x62,0x72,0x65,0x76,0x65,0x9,0x69,0x2e,0x6c,0x6f,0x63,0x6c,0x54,0x52,0x4b,0x2,0x69,0x6a,0x7,0x69,0x6d,0x61,0x63,0x72,0x6f,0x6e,0x7,0x69,0x6f, + 0x67,0x6f,0x6e,0x65,0x6b,0x6,0x69,0x74,0x69,0x6c,0x64,0x65,0x7,0x75,0x6e,0x69,0x30,0x32,0x33,0x37,0xb,0x6a,0x63,0x69,0x72,0x63,0x75,0x6d,0x66,0x6c, + 0x65,0x78,0xc,0x6b,0x63,0x6f,0x6d,0x6d,0x61,0x61,0x63,0x63,0x65,0x6e,0x74,0x6,0x6c,0x61,0x63,0x75,0x74,0x65,0x6,0x6c,0x63,0x61,0x72,0x6f,0x6e,0xc, + 0x6c,0x63,0x6f,0x6d,0x6d,0x61,0x61,0x63,0x63,0x65,0x6e,0x74,0x4,0x6c,0x64,0x6f,0x74,0x6,0x6e,0x61,0x63,0x75,0x74,0x65,0x6,0x6e,0x63,0x61,0x72,0x6f, + 0x6e,0xc,0x6e,0x63,0x6f,0x6d,0x6d,0x61,0x61,0x63,0x63,0x65,0x6e,0x74,0x3,0x65,0x6e,0x67,0x6,0x6f,0x62,0x72,0x65,0x76,0x65,0xd,0x6f,0x68,0x75,0x6e, + 0x67,0x61,0x72,0x75,0x6d,0x6c,0x61,0x75,0x74,0x7,0x6f,0x6d,0x61,0x63,0x72,0x6f,0x6e,0x6,0x72,0x61,0x63,0x75,0x74,0x65,0x6,0x72,0x63,0x61,0x72,0x6f, + 0x6e,0xc,0x72,0x63,0x6f,0x6d,0x6d,0x61,0x61,0x63,0x63,0x65,0x6e,0x74,0x6,0x73,0x61,0x63,0x75,0x74,0x65,0xb,0x73,0x63,0x69,0x72,0x63,0x75,0x6d,0x66, + 0x6c,0x65,0x78,0xc,0x73,0x63,0x6f,0x6d,0x6d,0x61,0x61,0x63,0x63,0x65,0x6e,0x74,0x4,0x74,0x62,0x61,0x72,0x6,0x74,0x63,0x61,0x72,0x6f,0x6e,0x7,0x75, + 0x6e,0x69,0x30,0x31,0x36,0x33,0x7,0x75,0x6e,0x69,0x30,0x32,0x31,0x42,0x6,0x75,0x62,0x72,0x65,0x76,0x65,0xd,0x75,0x68,0x75,0x6e,0x67,0x61,0x72,0x75, + 0x6d,0x6c,0x61,0x75,0x74,0x7,0x75,0x6d,0x61,0x63,0x72,0x6f,0x6e,0x7,0x75,0x6f,0x67,0x6f,0x6e,0x65,0x6b,0x5,0x75,0x72,0x69,0x6e,0x67,0x6,0x75,0x74, + 0x69,0x6c,0x64,0x65,0x6,0x77,0x61,0x63,0x75,0x74,0x65,0xb,0x77,0x63,0x69,0x72,0x63,0x75,0x6d,0x66,0x6c,0x65,0x78,0x9,0x77,0x64,0x69,0x65,0x72,0x65, + 0x73,0x69,0x73,0x6,0x77,0x67,0x72,0x61,0x76,0x65,0xb,0x79,0x63,0x69,0x72,0x63,0x75,0x6d,0x66,0x6c,0x65,0x78,0x6,0x79,0x67,0x72,0x61,0x76,0x65,0x6, + 0x7a,0x61,0x63,0x75,0x74,0x65,0xa,0x7a,0x64,0x6f,0x74,0x61,0x63,0x63,0x65,0x6e,0x74,0x7,0x75,0x6e,0x69,0x30,0x33,0x39,0x34,0x7,0x75,0x6e,0x69,0x30, + 0x33,0x41,0x39,0x7,0x75,0x6e,0x69,0x30,0x33,0x42,0x43,0x7,0x75,0x6e,0x69,0x30,0x30,0x42,0x39,0x7,0x75,0x6e,0x69,0x30,0x30,0x42,0x32,0x7,0x75,0x6e, + 0x69,0x30,0x30,0x42,0x33,0x7,0x75,0x6e,0x69,0x32,0x30,0x37,0x34,0x9,0x63,0x6f,0x6c,0x6f,0x6e,0x2e,0x61,0x6c,0x74,0x9,0x63,0x6f,0x6d,0x6d,0x61,0x2e, + 0x61,0x6c,0x74,0xa,0x70,0x65,0x72,0x69,0x6f,0x64,0x2e,0x61,0x6c,0x74,0xd,0x73,0x65,0x6d,0x69,0x63,0x6f,0x6c,0x6f,0x6e,0x2e,0x61,0x6c,0x74,0xd,0x65, + 0x6c,0x6c,0x69,0x70,0x73,0x69,0x73,0x2e,0x61,0x6c,0x74,0x31,0xd,0x65,0x6c,0x6c,0x69,0x70,0x73,0x69,0x73,0x2e,0x61,0x6c,0x74,0x32,0xd,0x65,0x6c,0x6c, + 0x69,0x70,0x73,0x69,0x73,0x2e,0x61,0x6c,0x74,0x33,0xd,0x65,0x6c,0x6c,0x69,0x70,0x73,0x69,0x73,0x2e,0x61,0x6c,0x74,0x34,0xd,0x65,0x6c,0x6c,0x69,0x70, + 0x73,0x69,0x73,0x2e,0x61,0x6c,0x74,0x35,0xa,0x68,0x79,0x70,0x68,0x65,0x6e,0x2e,0x61,0x6c,0x74,0xb,0x65,0x6d,0x64,0x61,0x73,0x68,0x2e,0x61,0x6c,0x74, + 0x31,0xb,0x65,0x6d,0x64,0x61,0x73,0x68,0x2e,0x61,0x6c,0x74,0x32,0xb,0x65,0x6d,0x64,0x61,0x73,0x68,0x2e,0x61,0x6c,0x74,0x33,0xb,0x65,0x6d,0x64,0x61, + 0x73,0x68,0x2e,0x61,0x6c,0x74,0x34,0x7,0x75,0x6e,0x69,0x32,0x30,0x31,0x31,0x7,0x75,0x6e,0x69,0x30,0x30,0x41,0x44,0x7,0x75,0x6e,0x69,0x30,0x30,0x41, + 0x30,0x4,0x45,0x75,0x72,0x6f,0x7,0x75,0x6e,0x69,0x32,0x30,0x41,0x39,0x6,0x6d,0x69,0x6e,0x75,0x74,0x65,0x6,0x73,0x65,0x63,0x6f,0x6e,0x64,0x7,0x75, + 0x6e,0x69,0x30,0x33,0x32,0x36,0xc,0x75,0x6e,0x69,0x30,0x33,0x32,0x36,0x2e,0x63,0x61,0x73,0x65,0x9,0x63,0x61,0x72,0x6f,0x6e,0x2e,0x61,0x6c,0x74,0xa, + 0x61,0x63,0x75,0x74,0x65,0x2e,0x63,0x61,0x73,0x65,0xa,0x62,0x72,0x65,0x76,0x65,0x2e,0x63,0x61,0x73,0x65,0xa,0x63,0x61,0x72,0x6f,0x6e,0x2e,0x63,0x61, + 0x73,0x65,0xc,0x63,0x65,0x64,0x69,0x6c,0x6c,0x61,0x2e,0x63,0x61,0x73,0x65,0xf,0x63,0x69,0x72,0x63,0x75,0x6d,0x66,0x6c,0x65,0x78,0x2e,0x63,0x61,0x73, + 0x65,0xd,0x64,0x69,0x65,0x72,0x65,0x73,0x69,0x73,0x2e,0x63,0x61,0x73,0x65,0xe,0x64,0x6f,0x74,0x61,0x63,0x63,0x65,0x6e,0x74,0x2e,0x63,0x61,0x73,0x65, + 0xa,0x67,0x72,0x61,0x76,0x65,0x2e,0x63,0x61,0x73,0x65,0x11,0x68,0x75,0x6e,0x67,0x61,0x72,0x75,0x6d,0x6c,0x61,0x75,0x74,0x2e,0x63,0x61,0x73,0x65,0xb, + 0x6d,0x61,0x63,0x72,0x6f,0x6e,0x2e,0x63,0x61,0x73,0x65,0xb,0x6f,0x67,0x6f,0x6e,0x65,0x6b,0x2e,0x63,0x61,0x73,0x65,0x9,0x72,0x69,0x6e,0x67,0x2e,0x63, + 0x61,0x73,0x65,0xa,0x74,0x69,0x6c,0x64,0x65,0x2e,0x63,0x61,0x73,0x65,0x0,0x1,0x0,0x1,0xff,0xff,0x0,0xf,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x9a,0x0,0x9a,0x0,0x94,0x0,0x94,0x4,0xa3,0x0,0x0,0x5,0x2a,0x3,0x9c,0x0,0x0, + 0xfe,0x70,0x7,0x5,0xfc,0xb9,0x4,0xb9,0xff,0xea,0x5,0x2a,0x3,0xb2,0xff,0xea,0xfe,0x70,0x7,0x5,0xfc,0xb9,0xb0,0x0,0x2c,0x20,0xb0,0x0,0x55,0x58, + 0x45,0x59,0x20,0x20,0x4b,0xb8,0x0,0xe,0x51,0x4b,0xb0,0x6,0x53,0x5a,0x58,0xb0,0x34,0x1b,0xb0,0x28,0x59,0x60,0x66,0x20,0x8a,0x55,0x58,0xb0,0x2,0x25, + 0x61,0xb9,0x8,0x0,0x8,0x0,0x63,0x63,0x23,0x62,0x1b,0x21,0x21,0xb0,0x0,0x59,0xb0,0x0,0x43,0x23,0x44,0xb2,0x0,0x1,0x0,0x43,0x60,0x42,0x2d,0xb0, + 0x1,0x2c,0xb0,0x20,0x60,0x66,0x2d,0xb0,0x2,0x2c,0x20,0x64,0x20,0xb0,0xc0,0x50,0xb0,0x4,0x26,0x5a,0xb2,0x28,0x1,0xa,0x43,0x45,0x63,0x45,0xb0,0x6, + 0x45,0x58,0x21,0xb0,0x3,0x25,0x59,0x52,0x5b,0x58,0x21,0x23,0x21,0x1b,0x8a,0x58,0x20,0xb0,0x50,0x50,0x58,0x21,0xb0,0x40,0x59,0x1b,0x20,0xb0,0x38,0x50, + 0x58,0x21,0xb0,0x38,0x59,0x59,0x20,0xb1,0x1,0xa,0x43,0x45,0x63,0x45,0x61,0x64,0xb0,0x28,0x50,0x58,0x21,0xb1,0x1,0xa,0x43,0x45,0x63,0x45,0x20,0xb0, + 0x30,0x50,0x58,0x21,0xb0,0x30,0x59,0x1b,0x20,0xb0,0xc0,0x50,0x58,0x20,0x66,0x20,0x8a,0x8a,0x61,0x20,0xb0,0xa,0x50,0x58,0x60,0x1b,0x20,0xb0,0x20,0x50, + 0x58,0x21,0xb0,0xa,0x60,0x1b,0x20,0xb0,0x36,0x50,0x58,0x21,0xb0,0x36,0x60,0x1b,0x60,0x59,0x59,0x59,0x1b,0xb0,0x1,0x2b,0x59,0x59,0x23,0xb0,0x0,0x50, + 0x58,0x65,0x59,0x59,0x2d,0xb0,0x3,0x2c,0x20,0x45,0x20,0xb0,0x4,0x25,0x61,0x64,0x20,0xb0,0x5,0x43,0x50,0x58,0xb0,0x5,0x23,0x42,0xb0,0x6,0x23,0x42, + 0x1b,0x21,0x21,0x59,0xb0,0x1,0x60,0x2d,0xb0,0x4,0x2c,0x23,0x21,0x23,0x21,0x20,0x64,0xb1,0x5,0x62,0x42,0x20,0xb0,0x6,0x23,0x42,0xb0,0x6,0x45,0x58, + 0x1b,0xb1,0x1,0xa,0x43,0x45,0x63,0xb1,0x1,0xa,0x43,0xb0,0x1,0x60,0x45,0x63,0xb0,0x3,0x2a,0x21,0x20,0xb0,0x6,0x43,0x20,0x8a,0x20,0x8a,0xb0,0x1, + 0x2b,0xb1,0x30,0x5,0x25,0xb0,0x4,0x26,0x51,0x58,0x60,0x50,0x1b,0x61,0x52,0x59,0x58,0x23,0x59,0x21,0x59,0x20,0xb0,0x40,0x53,0x58,0xb0,0x1,0x2b,0x1b, + 0x21,0xb0,0x40,0x59,0x23,0xb0,0x0,0x50,0x58,0x65,0x59,0x2d,0xb0,0x5,0x2c,0xb0,0x7,0x43,0x2b,0xb2,0x0,0x2,0x0,0x43,0x60,0x42,0x2d,0xb0,0x6,0x2c, + 0xb0,0x7,0x23,0x42,0x23,0x20,0xb0,0x0,0x23,0x42,0x61,0xb0,0x2,0x62,0x66,0xb0,0x1,0x63,0xb0,0x1,0x60,0xb0,0x5,0x2a,0x2d,0xb0,0x7,0x2c,0x20,0x20, + 0x45,0x20,0xb0,0xb,0x43,0x63,0xb8,0x4,0x0,0x62,0x20,0xb0,0x0,0x50,0x58,0xb0,0x40,0x60,0x59,0x66,0xb0,0x1,0x63,0x60,0x44,0xb0,0x1,0x60,0x2d,0xb0, + 0x8,0x2c,0xb2,0x7,0xb,0x0,0x43,0x45,0x42,0x2a,0x21,0xb2,0x0,0x1,0x0,0x43,0x60,0x42,0x2d,0xb0,0x9,0x2c,0xb0,0x0,0x43,0x23,0x44,0xb2,0x0,0x1, + 0x0,0x43,0x60,0x42,0x2d,0xb0,0xa,0x2c,0x20,0x20,0x45,0x20,0xb0,0x1,0x2b,0x23,0xb0,0x0,0x43,0xb0,0x4,0x25,0x60,0x20,0x45,0x8a,0x23,0x61,0x20,0x64, + 0x20,0xb0,0x20,0x50,0x58,0x21,0xb0,0x0,0x1b,0xb0,0x30,0x50,0x58,0xb0,0x20,0x1b,0xb0,0x40,0x59,0x59,0x23,0xb0,0x0,0x50,0x58,0x65,0x59,0xb0,0x3,0x25, + 0x23,0x61,0x44,0x44,0xb0,0x1,0x60,0x2d,0xb0,0xb,0x2c,0x20,0x20,0x45,0x20,0xb0,0x1,0x2b,0x23,0xb0,0x0,0x43,0xb0,0x4,0x25,0x60,0x20,0x45,0x8a,0x23, + 0x61,0x20,0x64,0xb0,0x24,0x50,0x58,0xb0,0x0,0x1b,0xb0,0x40,0x59,0x23,0xb0,0x0,0x50,0x58,0x65,0x59,0xb0,0x3,0x25,0x23,0x61,0x44,0x44,0xb0,0x1,0x60, + 0x2d,0xb0,0xc,0x2c,0x20,0xb0,0x0,0x23,0x42,0xb2,0xb,0xa,0x3,0x45,0x58,0x21,0x1b,0x23,0x21,0x59,0x2a,0x21,0x2d,0xb0,0xd,0x2c,0xb1,0x2,0x2,0x45, + 0xb0,0x64,0x61,0x44,0x2d,0xb0,0xe,0x2c,0xb0,0x1,0x60,0x20,0x20,0xb0,0xc,0x43,0x4a,0xb0,0x0,0x50,0x58,0x20,0xb0,0xc,0x23,0x42,0x59,0xb0,0xd,0x43, + 0x4a,0xb0,0x0,0x52,0x58,0x20,0xb0,0xd,0x23,0x42,0x59,0x2d,0xb0,0xf,0x2c,0x20,0xb0,0x10,0x62,0x66,0xb0,0x1,0x63,0x20,0xb8,0x4,0x0,0x63,0x8a,0x23, + 0x61,0xb0,0xe,0x43,0x60,0x20,0x8a,0x60,0x20,0xb0,0xe,0x23,0x42,0x23,0x2d,0xb0,0x10,0x2c,0x4b,0x54,0x58,0xb1,0x4,0x64,0x44,0x59,0x24,0xb0,0xd,0x65, + 0x23,0x78,0x2d,0xb0,0x11,0x2c,0x4b,0x51,0x58,0x4b,0x53,0x58,0xb1,0x4,0x64,0x44,0x59,0x1b,0x21,0x59,0x24,0xb0,0x13,0x65,0x23,0x78,0x2d,0xb0,0x12,0x2c, + 0xb1,0x0,0xf,0x43,0x55,0x58,0xb1,0xf,0xf,0x43,0xb0,0x1,0x61,0x42,0xb0,0xf,0x2b,0x59,0xb0,0x0,0x43,0xb0,0x2,0x25,0x42,0xb1,0xc,0x2,0x25,0x42, + 0xb1,0xd,0x2,0x25,0x42,0xb0,0x1,0x16,0x23,0x20,0xb0,0x3,0x25,0x50,0x58,0xb1,0x1,0x0,0x43,0x60,0xb0,0x4,0x25,0x42,0x8a,0x8a,0x20,0x8a,0x23,0x61, + 0xb0,0xe,0x2a,0x21,0x23,0xb0,0x1,0x61,0x20,0x8a,0x23,0x61,0xb0,0xe,0x2a,0x21,0x1b,0xb1,0x1,0x0,0x43,0x60,0xb0,0x2,0x25,0x42,0xb0,0x2,0x25,0x61, + 0xb0,0xe,0x2a,0x21,0x59,0xb0,0xc,0x43,0x47,0xb0,0xd,0x43,0x47,0x60,0xb0,0x2,0x62,0x20,0xb0,0x0,0x50,0x58,0xb0,0x40,0x60,0x59,0x66,0xb0,0x1,0x63, + 0x20,0xb0,0xb,0x43,0x63,0xb8,0x4,0x0,0x62,0x20,0xb0,0x0,0x50,0x58,0xb0,0x40,0x60,0x59,0x66,0xb0,0x1,0x63,0x60,0xb1,0x0,0x0,0x13,0x23,0x44,0xb0, + 0x1,0x43,0xb0,0x0,0x3e,0xb2,0x1,0x1,0x1,0x43,0x60,0x42,0x2d,0xb0,0x13,0x2c,0x0,0xb1,0x0,0x2,0x45,0x54,0x58,0xb0,0xf,0x23,0x42,0x20,0x45,0xb0, + 0xb,0x23,0x42,0xb0,0xa,0x23,0xb0,0x1,0x60,0x42,0x20,0x60,0xb0,0x1,0x61,0xb5,0x10,0x10,0x1,0x0,0xe,0x0,0x42,0x42,0x8a,0x60,0xb1,0x12,0x6,0x2b, + 0xb0,0x75,0x2b,0x1b,0x22,0x59,0x2d,0xb0,0x14,0x2c,0xb1,0x0,0x13,0x2b,0x2d,0xb0,0x15,0x2c,0xb1,0x1,0x13,0x2b,0x2d,0xb0,0x16,0x2c,0xb1,0x2,0x13,0x2b, + 0x2d,0xb0,0x17,0x2c,0xb1,0x3,0x13,0x2b,0x2d,0xb0,0x18,0x2c,0xb1,0x4,0x13,0x2b,0x2d,0xb0,0x19,0x2c,0xb1,0x5,0x13,0x2b,0x2d,0xb0,0x1a,0x2c,0xb1,0x6, + 0x13,0x2b,0x2d,0xb0,0x1b,0x2c,0xb1,0x7,0x13,0x2b,0x2d,0xb0,0x1c,0x2c,0xb1,0x8,0x13,0x2b,0x2d,0xb0,0x1d,0x2c,0xb1,0x9,0x13,0x2b,0x2d,0xb0,0x29,0x2c, + 0x23,0x20,0xb0,0x10,0x62,0x66,0xb0,0x1,0x63,0xb0,0x6,0x60,0x4b,0x54,0x58,0x23,0x20,0x2e,0xb0,0x1,0x5d,0x1b,0x21,0x21,0x59,0x2d,0xb0,0x2a,0x2c,0x23, + 0x20,0xb0,0x10,0x62,0x66,0xb0,0x1,0x63,0xb0,0x16,0x60,0x4b,0x54,0x58,0x23,0x20,0x2e,0xb0,0x1,0x71,0x1b,0x21,0x21,0x59,0x2d,0xb0,0x2b,0x2c,0x23,0x20, + 0xb0,0x10,0x62,0x66,0xb0,0x1,0x63,0xb0,0x26,0x60,0x4b,0x54,0x58,0x23,0x20,0x2e,0xb0,0x1,0x72,0x1b,0x21,0x21,0x59,0x2d,0xb0,0x1e,0x2c,0x0,0xb0,0xd, + 0x2b,0xb1,0x0,0x2,0x45,0x54,0x58,0xb0,0xf,0x23,0x42,0x20,0x45,0xb0,0xb,0x23,0x42,0xb0,0xa,0x23,0xb0,0x1,0x60,0x42,0x20,0x60,0xb0,0x1,0x61,0xb5, + 0x10,0x10,0x1,0x0,0xe,0x0,0x42,0x42,0x8a,0x60,0xb1,0x12,0x6,0x2b,0xb0,0x75,0x2b,0x1b,0x22,0x59,0x2d,0xb0,0x1f,0x2c,0xb1,0x0,0x1e,0x2b,0x2d,0xb0, + 0x20,0x2c,0xb1,0x1,0x1e,0x2b,0x2d,0xb0,0x21,0x2c,0xb1,0x2,0x1e,0x2b,0x2d,0xb0,0x22,0x2c,0xb1,0x3,0x1e,0x2b,0x2d,0xb0,0x23,0x2c,0xb1,0x4,0x1e,0x2b, + 0x2d,0xb0,0x24,0x2c,0xb1,0x5,0x1e,0x2b,0x2d,0xb0,0x25,0x2c,0xb1,0x6,0x1e,0x2b,0x2d,0xb0,0x26,0x2c,0xb1,0x7,0x1e,0x2b,0x2d,0xb0,0x27,0x2c,0xb1,0x8, + 0x1e,0x2b,0x2d,0xb0,0x28,0x2c,0xb1,0x9,0x1e,0x2b,0x2d,0xb0,0x2c,0x2c,0x20,0x3c,0xb0,0x1,0x60,0x2d,0xb0,0x2d,0x2c,0x20,0x60,0xb0,0x10,0x60,0x20,0x43, + 0x23,0xb0,0x1,0x60,0x43,0xb0,0x2,0x25,0x61,0xb0,0x1,0x60,0xb0,0x2c,0x2a,0x21,0x2d,0xb0,0x2e,0x2c,0xb0,0x2d,0x2b,0xb0,0x2d,0x2a,0x2d,0xb0,0x2f,0x2c, + 0x20,0x20,0x47,0x20,0x20,0xb0,0xb,0x43,0x63,0xb8,0x4,0x0,0x62,0x20,0xb0,0x0,0x50,0x58,0xb0,0x40,0x60,0x59,0x66,0xb0,0x1,0x63,0x60,0x23,0x61,0x38, + 0x23,0x20,0x8a,0x55,0x58,0x20,0x47,0x20,0x20,0xb0,0xb,0x43,0x63,0xb8,0x4,0x0,0x62,0x20,0xb0,0x0,0x50,0x58,0xb0,0x40,0x60,0x59,0x66,0xb0,0x1,0x63, + 0x60,0x23,0x61,0x38,0x1b,0x21,0x59,0x2d,0xb0,0x30,0x2c,0x0,0xb1,0x0,0x2,0x45,0x54,0x58,0xb0,0x1,0x16,0xb0,0x2f,0x2a,0xb1,0x5,0x1,0x15,0x45,0x58, + 0x30,0x59,0x1b,0x22,0x59,0x2d,0xb0,0x31,0x2c,0x0,0xb0,0xd,0x2b,0xb1,0x0,0x2,0x45,0x54,0x58,0xb0,0x1,0x16,0xb0,0x2f,0x2a,0xb1,0x5,0x1,0x15,0x45, + 0x58,0x30,0x59,0x1b,0x22,0x59,0x2d,0xb0,0x32,0x2c,0x20,0x35,0xb0,0x1,0x60,0x2d,0xb0,0x33,0x2c,0x0,0xb0,0x1,0x45,0x63,0xb8,0x4,0x0,0x62,0x20,0xb0, + 0x0,0x50,0x58,0xb0,0x40,0x60,0x59,0x66,0xb0,0x1,0x63,0xb0,0x1,0x2b,0xb0,0xb,0x43,0x63,0xb8,0x4,0x0,0x62,0x20,0xb0,0x0,0x50,0x58,0xb0,0x40,0x60, + 0x59,0x66,0xb0,0x1,0x63,0xb0,0x1,0x2b,0xb0,0x0,0x16,0xb4,0x0,0x0,0x0,0x0,0x0,0x44,0x3e,0x23,0x38,0xb1,0x32,0x1,0x15,0x2a,0x2d,0xb0,0x34,0x2c, + 0x20,0x3c,0x20,0x47,0x20,0xb0,0xb,0x43,0x63,0xb8,0x4,0x0,0x62,0x20,0xb0,0x0,0x50,0x58,0xb0,0x40,0x60,0x59,0x66,0xb0,0x1,0x63,0x60,0xb0,0x0,0x43, + 0x61,0x38,0x2d,0xb0,0x35,0x2c,0x2e,0x17,0x3c,0x2d,0xb0,0x36,0x2c,0x20,0x3c,0x20,0x47,0x20,0xb0,0xb,0x43,0x63,0xb8,0x4,0x0,0x62,0x20,0xb0,0x0,0x50, + 0x58,0xb0,0x40,0x60,0x59,0x66,0xb0,0x1,0x63,0x60,0xb0,0x0,0x43,0x61,0xb0,0x1,0x43,0x63,0x38,0x2d,0xb0,0x37,0x2c,0xb1,0x2,0x0,0x16,0x25,0x20,0x2e, + 0x20,0x47,0xb0,0x0,0x23,0x42,0xb0,0x2,0x25,0x49,0x8a,0x8a,0x47,0x23,0x47,0x23,0x61,0x20,0x58,0x62,0x1b,0x21,0x59,0xb0,0x1,0x23,0x42,0xb2,0x36,0x1, + 0x1,0x15,0x14,0x2a,0x2d,0xb0,0x38,0x2c,0xb0,0x0,0x16,0xb0,0x4,0x25,0xb0,0x4,0x25,0x47,0x23,0x47,0x23,0x61,0xb0,0x9,0x43,0x2b,0x65,0x8a,0x2e,0x23, + 0x20,0x20,0x3c,0x8a,0x38,0x2d,0xb0,0x39,0x2c,0xb0,0x0,0x16,0xb0,0x4,0x25,0xb0,0x4,0x25,0x20,0x2e,0x47,0x23,0x47,0x23,0x61,0x20,0xb0,0x4,0x23,0x42, + 0xb0,0x9,0x43,0x2b,0x20,0xb0,0x60,0x50,0x58,0x20,0xb0,0x40,0x51,0x58,0xb3,0x2,0x20,0x3,0x20,0x1b,0xb3,0x2,0x26,0x3,0x1a,0x59,0x42,0x42,0x23,0x20, + 0xb0,0x8,0x43,0x20,0x8a,0x23,0x47,0x23,0x47,0x23,0x61,0x23,0x46,0x60,0xb0,0x4,0x43,0xb0,0x2,0x62,0x20,0xb0,0x0,0x50,0x58,0xb0,0x40,0x60,0x59,0x66, + 0xb0,0x1,0x63,0x60,0x20,0xb0,0x1,0x2b,0x20,0x8a,0x8a,0x61,0x20,0xb0,0x2,0x43,0x60,0x64,0x23,0xb0,0x3,0x43,0x61,0x64,0x50,0x58,0xb0,0x2,0x43,0x61, + 0x1b,0xb0,0x3,0x43,0x60,0x59,0xb0,0x3,0x25,0xb0,0x2,0x62,0x20,0xb0,0x0,0x50,0x58,0xb0,0x40,0x60,0x59,0x66,0xb0,0x1,0x63,0x61,0x23,0x20,0x20,0xb0, + 0x4,0x26,0x23,0x46,0x61,0x38,0x1b,0x23,0xb0,0x8,0x43,0x46,0xb0,0x2,0x25,0xb0,0x8,0x43,0x47,0x23,0x47,0x23,0x61,0x60,0x20,0xb0,0x4,0x43,0xb0,0x2, + 0x62,0x20,0xb0,0x0,0x50,0x58,0xb0,0x40,0x60,0x59,0x66,0xb0,0x1,0x63,0x60,0x23,0x20,0xb0,0x1,0x2b,0x23,0xb0,0x4,0x43,0x60,0xb0,0x1,0x2b,0xb0,0x5, + 0x25,0x61,0xb0,0x5,0x25,0xb0,0x2,0x62,0x20,0xb0,0x0,0x50,0x58,0xb0,0x40,0x60,0x59,0x66,0xb0,0x1,0x63,0xb0,0x4,0x26,0x61,0x20,0xb0,0x4,0x25,0x60, + 0x64,0x23,0xb0,0x3,0x25,0x60,0x64,0x50,0x58,0x21,0x1b,0x23,0x21,0x59,0x23,0x20,0x20,0xb0,0x4,0x26,0x23,0x46,0x61,0x38,0x59,0x2d,0xb0,0x3a,0x2c,0xb0, + 0x0,0x16,0x20,0x20,0x20,0xb0,0x5,0x26,0x20,0x2e,0x47,0x23,0x47,0x23,0x61,0x23,0x3c,0x38,0x2d,0xb0,0x3b,0x2c,0xb0,0x0,0x16,0x20,0xb0,0x8,0x23,0x42, + 0x20,0x20,0x20,0x46,0x23,0x47,0xb0,0x1,0x2b,0x23,0x61,0x38,0x2d,0xb0,0x3c,0x2c,0xb0,0x0,0x16,0xb0,0x3,0x25,0xb0,0x2,0x25,0x47,0x23,0x47,0x23,0x61, + 0xb0,0x0,0x54,0x58,0x2e,0x20,0x3c,0x23,0x21,0x1b,0xb0,0x2,0x25,0xb0,0x2,0x25,0x47,0x23,0x47,0x23,0x61,0x20,0xb0,0x5,0x25,0xb0,0x4,0x25,0x47,0x23, + 0x47,0x23,0x61,0xb0,0x6,0x25,0xb0,0x5,0x25,0x49,0xb0,0x2,0x25,0x61,0xb9,0x8,0x0,0x8,0x0,0x63,0x63,0x23,0x20,0x58,0x62,0x1b,0x21,0x59,0x63,0xb8, + 0x4,0x0,0x62,0x20,0xb0,0x0,0x50,0x58,0xb0,0x40,0x60,0x59,0x66,0xb0,0x1,0x63,0x60,0x23,0x2e,0x23,0x20,0x20,0x3c,0x8a,0x38,0x23,0x21,0x59,0x2d,0xb0, + 0x3d,0x2c,0xb0,0x0,0x16,0x20,0xb0,0x8,0x43,0x20,0x2e,0x47,0x23,0x47,0x23,0x61,0x20,0x60,0xb0,0x20,0x60,0x66,0xb0,0x2,0x62,0x20,0xb0,0x0,0x50,0x58, + 0xb0,0x40,0x60,0x59,0x66,0xb0,0x1,0x63,0x23,0x20,0x20,0x3c,0x8a,0x38,0x2d,0xb0,0x3e,0x2c,0x23,0x20,0x2e,0x46,0xb0,0x2,0x25,0x46,0x52,0x58,0x20,0x3c, + 0x59,0x2e,0xb1,0x2e,0x1,0x14,0x2b,0x2d,0xb0,0x3f,0x2c,0x23,0x20,0x2e,0x46,0xb0,0x2,0x25,0x46,0x50,0x58,0x20,0x3c,0x59,0x2e,0xb1,0x2e,0x1,0x14,0x2b, + 0x2d,0xb0,0x40,0x2c,0x23,0x20,0x2e,0x46,0xb0,0x2,0x25,0x46,0x52,0x58,0x20,0x3c,0x59,0x23,0x20,0x2e,0x46,0xb0,0x2,0x25,0x46,0x50,0x58,0x20,0x3c,0x59, + 0x2e,0xb1,0x2e,0x1,0x14,0x2b,0x2d,0xb0,0x41,0x2c,0xb0,0x38,0x2b,0x23,0x20,0x2e,0x46,0xb0,0x2,0x25,0x46,0x52,0x58,0x20,0x3c,0x59,0x2e,0xb1,0x2e,0x1, + 0x14,0x2b,0x2d,0xb0,0x42,0x2c,0xb0,0x39,0x2b,0x8a,0x20,0x20,0x3c,0xb0,0x4,0x23,0x42,0x8a,0x38,0x23,0x20,0x2e,0x46,0xb0,0x2,0x25,0x46,0x52,0x58,0x20, + 0x3c,0x59,0x2e,0xb1,0x2e,0x1,0x14,0x2b,0xb0,0x4,0x43,0x2e,0xb0,0x2e,0x2b,0x2d,0xb0,0x43,0x2c,0xb0,0x0,0x16,0xb0,0x4,0x25,0xb0,0x4,0x26,0x20,0x2e, + 0x47,0x23,0x47,0x23,0x61,0xb0,0x9,0x43,0x2b,0x23,0x20,0x3c,0x20,0x2e,0x23,0x38,0xb1,0x2e,0x1,0x14,0x2b,0x2d,0xb0,0x44,0x2c,0xb1,0x8,0x4,0x25,0x42, + 0xb0,0x0,0x16,0xb0,0x4,0x25,0xb0,0x4,0x25,0x20,0x2e,0x47,0x23,0x47,0x23,0x61,0x20,0xb0,0x4,0x23,0x42,0xb0,0x9,0x43,0x2b,0x20,0xb0,0x60,0x50,0x58, + 0x20,0xb0,0x40,0x51,0x58,0xb3,0x2,0x20,0x3,0x20,0x1b,0xb3,0x2,0x26,0x3,0x1a,0x59,0x42,0x42,0x23,0x20,0x47,0xb0,0x4,0x43,0xb0,0x2,0x62,0x20,0xb0, + 0x0,0x50,0x58,0xb0,0x40,0x60,0x59,0x66,0xb0,0x1,0x63,0x60,0x20,0xb0,0x1,0x2b,0x20,0x8a,0x8a,0x61,0x20,0xb0,0x2,0x43,0x60,0x64,0x23,0xb0,0x3,0x43, + 0x61,0x64,0x50,0x58,0xb0,0x2,0x43,0x61,0x1b,0xb0,0x3,0x43,0x60,0x59,0xb0,0x3,0x25,0xb0,0x2,0x62,0x20,0xb0,0x0,0x50,0x58,0xb0,0x40,0x60,0x59,0x66, + 0xb0,0x1,0x63,0x61,0xb0,0x2,0x25,0x46,0x61,0x38,0x23,0x20,0x3c,0x23,0x38,0x1b,0x21,0x20,0x20,0x46,0x23,0x47,0xb0,0x1,0x2b,0x23,0x61,0x38,0x21,0x59, + 0xb1,0x2e,0x1,0x14,0x2b,0x2d,0xb0,0x45,0x2c,0xb0,0x38,0x2b,0x2e,0xb1,0x2e,0x1,0x14,0x2b,0x2d,0xb0,0x46,0x2c,0xb0,0x39,0x2b,0x21,0x23,0x20,0x20,0x3c, + 0xb0,0x4,0x23,0x42,0x23,0x38,0xb1,0x2e,0x1,0x14,0x2b,0xb0,0x4,0x43,0x2e,0xb0,0x2e,0x2b,0x2d,0xb0,0x47,0x2c,0xb0,0x0,0x15,0x20,0x47,0xb0,0x0,0x23, + 0x42,0xb2,0x0,0x1,0x1,0x15,0x14,0x13,0x2e,0xb0,0x34,0x2a,0x2d,0xb0,0x48,0x2c,0xb0,0x0,0x15,0x20,0x47,0xb0,0x0,0x23,0x42,0xb2,0x0,0x1,0x1,0x15, + 0x14,0x13,0x2e,0xb0,0x34,0x2a,0x2d,0xb0,0x49,0x2c,0xb1,0x0,0x1,0x14,0x13,0xb0,0x35,0x2a,0x2d,0xb0,0x4a,0x2c,0xb0,0x37,0x2a,0x2d,0xb0,0x4b,0x2c,0xb0, + 0x0,0x16,0x45,0x23,0x20,0x2e,0x20,0x46,0x8a,0x23,0x61,0x38,0xb1,0x2e,0x1,0x14,0x2b,0x2d,0xb0,0x4c,0x2c,0xb0,0x8,0x23,0x42,0xb0,0x4b,0x2b,0x2d,0xb0, + 0x4d,0x2c,0xb2,0x0,0x0,0x44,0x2b,0x2d,0xb0,0x4e,0x2c,0xb2,0x0,0x1,0x44,0x2b,0x2d,0xb0,0x4f,0x2c,0xb2,0x1,0x0,0x44,0x2b,0x2d,0xb0,0x50,0x2c,0xb2, + 0x1,0x1,0x44,0x2b,0x2d,0xb0,0x51,0x2c,0xb2,0x0,0x0,0x45,0x2b,0x2d,0xb0,0x52,0x2c,0xb2,0x0,0x1,0x45,0x2b,0x2d,0xb0,0x53,0x2c,0xb2,0x1,0x0,0x45, + 0x2b,0x2d,0xb0,0x54,0x2c,0xb2,0x1,0x1,0x45,0x2b,0x2d,0xb0,0x55,0x2c,0xb2,0x0,0x0,0x41,0x2b,0x2d,0xb0,0x56,0x2c,0xb2,0x0,0x1,0x41,0x2b,0x2d,0xb0, + 0x57,0x2c,0xb2,0x1,0x0,0x41,0x2b,0x2d,0xb0,0x58,0x2c,0xb2,0x1,0x1,0x41,0x2b,0x2d,0xb0,0x59,0x2c,0xb2,0x0,0x0,0x43,0x2b,0x2d,0xb0,0x5a,0x2c,0xb2, + 0x0,0x1,0x43,0x2b,0x2d,0xb0,0x5b,0x2c,0xb2,0x1,0x0,0x43,0x2b,0x2d,0xb0,0x5c,0x2c,0xb2,0x1,0x1,0x43,0x2b,0x2d,0xb0,0x5d,0x2c,0xb2,0x0,0x0,0x46, + 0x2b,0x2d,0xb0,0x5e,0x2c,0xb2,0x0,0x1,0x46,0x2b,0x2d,0xb0,0x5f,0x2c,0xb2,0x1,0x0,0x46,0x2b,0x2d,0xb0,0x60,0x2c,0xb2,0x1,0x1,0x46,0x2b,0x2d,0xb0, + 0x61,0x2c,0xb2,0x0,0x0,0x42,0x2b,0x2d,0xb0,0x62,0x2c,0xb2,0x0,0x1,0x42,0x2b,0x2d,0xb0,0x63,0x2c,0xb2,0x1,0x0,0x42,0x2b,0x2d,0xb0,0x64,0x2c,0xb2, + 0x1,0x1,0x42,0x2b,0x2d,0xb0,0x65,0x2c,0xb0,0x3a,0x2b,0x2e,0xb1,0x2e,0x1,0x14,0x2b,0x2d,0xb0,0x66,0x2c,0xb0,0x3a,0x2b,0xb0,0x3e,0x2b,0x2d,0xb0,0x67, + 0x2c,0xb0,0x3a,0x2b,0xb0,0x3f,0x2b,0x2d,0xb0,0x68,0x2c,0xb0,0x0,0x16,0xb0,0x3a,0x2b,0xb0,0x40,0x2b,0x2d,0xb0,0x69,0x2c,0xb0,0x3b,0x2b,0x2e,0xb1,0x2e, + 0x1,0x14,0x2b,0x2d,0xb0,0x6a,0x2c,0xb0,0x3b,0x2b,0xb0,0x3e,0x2b,0x2d,0xb0,0x6b,0x2c,0xb0,0x3b,0x2b,0xb0,0x3f,0x2b,0x2d,0xb0,0x6c,0x2c,0xb0,0x3b,0x2b, + 0xb0,0x40,0x2b,0x2d,0xb0,0x6d,0x2c,0xb0,0x3c,0x2b,0x2e,0xb1,0x2e,0x1,0x14,0x2b,0x2d,0xb0,0x6e,0x2c,0xb0,0x3c,0x2b,0xb0,0x3e,0x2b,0x2d,0xb0,0x6f,0x2c, + 0xb0,0x3c,0x2b,0xb0,0x3f,0x2b,0x2d,0xb0,0x70,0x2c,0xb0,0x3c,0x2b,0xb0,0x40,0x2b,0x2d,0xb0,0x71,0x2c,0xb0,0x3d,0x2b,0x2e,0xb1,0x2e,0x1,0x14,0x2b,0x2d, + 0xb0,0x72,0x2c,0xb0,0x3d,0x2b,0xb0,0x3e,0x2b,0x2d,0xb0,0x73,0x2c,0xb0,0x3d,0x2b,0xb0,0x3f,0x2b,0x2d,0xb0,0x74,0x2c,0xb0,0x3d,0x2b,0xb0,0x40,0x2b,0x2d, + 0xb0,0x75,0x2c,0xb3,0x9,0x4,0x2,0x3,0x45,0x58,0x21,0x1b,0x23,0x21,0x59,0x42,0x2b,0xb0,0x8,0x65,0xb0,0x3,0x24,0x50,0x78,0xb1,0x5,0x1,0x15,0x45, + 0x58,0x30,0x59,0x2d,0x0,0x0,0x0,0x4b,0xb8,0x0,0xc8,0x52,0x58,0xb1,0x1,0x1,0x8e,0x59,0xb0,0x1,0xb9,0x8,0x0,0x8,0x0,0x63,0x70,0xb1,0x0,0x7, + 0x42,0xb2,0x19,0x1,0x0,0x2a,0xb1,0x0,0x7,0x42,0xb3,0xc,0x8,0x1,0x8,0x2a,0xb1,0x0,0x7,0x42,0xb3,0x16,0x6,0x1,0x8,0x2a,0xb1,0x0,0x8,0x42, + 0xba,0x3,0x40,0x0,0x1,0x0,0x9,0x2a,0xb1,0x0,0x9,0x42,0xba,0x0,0x40,0x0,0x1,0x0,0x9,0x2a,0xb1,0x3,0x0,0x44,0xb1,0x24,0x1,0x88,0x51,0x58, + 0xb0,0x40,0x88,0x58,0xb1,0x3,0x64,0x44,0xb1,0x26,0x1,0x88,0x51,0x58,0xba,0x8,0x80,0x0,0x1,0x4,0x40,0x88,0x63,0x54,0x58,0xb1,0x3,0x0,0x44,0x59, + 0x59,0x59,0x59,0xb3,0xe,0x8,0x1,0xc,0x2a,0xb8,0x1,0xff,0x85,0xb0,0x4,0x8d,0xb1,0x2,0x0,0x44,0xb3,0x5,0x64,0x6,0x0,0x44,0x44,0x0,0x0,0x0, + 0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x0 +}; + + +static const unsigned char courier_prime_code_italic[] = { + 0x0,0x1,0x0,0x0,0x0,0x10,0x1,0x0,0x0,0x4,0x0,0x0,0x44,0x53,0x49,0x47,0x0,0x0,0x0,0x1,0x0,0x1,0x2f,0x68,0x0,0x0,0x0,0x8,0x47,0x53, + 0x55,0x42,0xc9,0x7f,0xcd,0x51,0x0,0x0,0x1,0xc,0x0,0x0,0x5,0x90,0x4f,0x53,0x2f,0x32,0x71,0x19,0x89,0xb2,0x0,0x0,0x6,0x9c,0x0,0x0,0x0,0x60, + 0x63,0x6d,0x61,0x70,0xd7,0xce,0xb9,0xf9,0x0,0x0,0x6,0xfc,0x0,0x0,0x6,0x46,0x63,0x76,0x74,0x20,0x22,0xd5,0xfe,0xb1,0x0,0x1,0x22,0xb8,0x0,0x0, + 0x0,0x3c,0x66,0x70,0x67,0x6d,0x3f,0xae,0x1b,0x9f,0x0,0x1,0x22,0xf4,0x0,0x0,0xb,0xe2,0x67,0x61,0x73,0x70,0x0,0x0,0x0,0x10,0x0,0x1,0x22,0xb0, + 0x0,0x0,0x0,0x8,0x67,0x6c,0x79,0x66,0xc4,0x78,0x68,0xc5,0x0,0x0,0xd,0x44,0x0,0x0,0xca,0xce,0x68,0x65,0x61,0x64,0xc,0x51,0xe2,0x51,0x0,0x0, + 0xd8,0x14,0x0,0x0,0x0,0x36,0x68,0x68,0x65,0x61,0xc,0x71,0xf,0x79,0x0,0x0,0xd8,0x4c,0x0,0x0,0x0,0x24,0x68,0x6d,0x74,0x78,0x5e,0x3b,0xb,0xb9, + 0x0,0x0,0xd8,0x70,0x0,0x0,0x6,0x38,0x6c,0x6f,0x63,0x61,0xa6,0x2b,0xdc,0x25,0x0,0x0,0xde,0xa8,0x0,0x0,0x3,0x38,0x6d,0x61,0x78,0x70,0x2,0xde, + 0xc,0xf6,0x0,0x0,0xe1,0xe0,0x0,0x0,0x0,0x20,0x6e,0x61,0x6d,0x65,0x23,0xb0,0x4b,0xa,0x0,0x0,0xe2,0x0,0x0,0x0,0x37,0x6b,0x70,0x6f,0x73,0x74, + 0xae,0xbe,0xe9,0xb5,0x0,0x1,0x19,0x6c,0x0,0x0,0x9,0x44,0x70,0x72,0x65,0x70,0x19,0x50,0x2,0x10,0x0,0x1,0x2e,0xd8,0x0,0x0,0x0,0x8d,0x0,0x1, + 0x0,0x0,0x0,0xa,0x0,0xf2,0x2,0xf8,0x0,0x3,0x44,0x46,0x4c,0x54,0x0,0x14,0x67,0x72,0x65,0x6b,0x0,0x28,0x6c,0x61,0x74,0x6e,0x0,0x3c,0x0,0x4, + 0x0,0x0,0x0,0x0,0xff,0xff,0x0,0x5,0x0,0x0,0x0,0x7,0x0,0xe,0x0,0x1d,0x0,0x24,0x0,0x4,0x0,0x0,0x0,0x0,0xff,0xff,0x0,0x5,0x0,0x1, + 0x0,0x8,0x0,0xf,0x0,0x1e,0x0,0x25,0x0,0x34,0x0,0x8,0x41,0x5a,0x45,0x20,0x0,0x44,0x43,0x41,0x54,0x20,0x0,0x4c,0x43,0x52,0x54,0x20,0x0,0x5e, + 0x4b,0x41,0x5a,0x20,0x0,0x66,0x4d,0x4f,0x4c,0x20,0x0,0x6e,0x52,0x4f,0x4d,0x20,0x0,0x80,0x54,0x41,0x54,0x20,0x0,0x92,0x54,0x52,0x4b,0x20,0x0,0x9a, + 0x0,0x0,0xff,0xff,0x0,0x5,0x0,0x2,0x0,0x9,0x0,0x10,0x0,0x1f,0x0,0x26,0x0,0x0,0xff,0xff,0x0,0x1,0x0,0x15,0x0,0x0,0xff,0xff,0x0,0x6, + 0x0,0x3,0x0,0xa,0x0,0x11,0x0,0x16,0x0,0x20,0x0,0x27,0x0,0x0,0xff,0xff,0x0,0x1,0x0,0x17,0x0,0x0,0xff,0xff,0x0,0x1,0x0,0x18,0x0,0x0, + 0xff,0xff,0x0,0x6,0x0,0x4,0x0,0xb,0x0,0x12,0x0,0x19,0x0,0x21,0x0,0x28,0x0,0x0,0xff,0xff,0x0,0x6,0x0,0x5,0x0,0xc,0x0,0x13,0x0,0x1a, + 0x0,0x22,0x0,0x29,0x0,0x0,0xff,0xff,0x0,0x1,0x0,0x1b,0x0,0x0,0xff,0xff,0x0,0x6,0x0,0x6,0x0,0xd,0x0,0x14,0x0,0x1c,0x0,0x23,0x0,0x2a, + 0x0,0x2b,0x61,0x61,0x6c,0x74,0x1,0x4,0x61,0x61,0x6c,0x74,0x1,0xa,0x61,0x61,0x6c,0x74,0x1,0x10,0x61,0x61,0x6c,0x74,0x1,0x16,0x61,0x61,0x6c,0x74, + 0x1,0x1c,0x61,0x61,0x6c,0x74,0x1,0x22,0x61,0x61,0x6c,0x74,0x1,0x28,0x63,0x61,0x73,0x65,0x1,0x2e,0x63,0x61,0x73,0x65,0x1,0x34,0x63,0x61,0x73,0x65, + 0x1,0x3a,0x63,0x61,0x73,0x65,0x1,0x40,0x63,0x61,0x73,0x65,0x1,0x46,0x63,0x61,0x73,0x65,0x1,0x4c,0x63,0x61,0x73,0x65,0x1,0x52,0x66,0x72,0x61,0x63, + 0x1,0x58,0x66,0x72,0x61,0x63,0x1,0x5e,0x66,0x72,0x61,0x63,0x1,0x64,0x66,0x72,0x61,0x63,0x1,0x6a,0x66,0x72,0x61,0x63,0x1,0x70,0x66,0x72,0x61,0x63, + 0x1,0x76,0x66,0x72,0x61,0x63,0x1,0x7c,0x6c,0x6f,0x63,0x6c,0x1,0x82,0x6c,0x6f,0x63,0x6c,0x1,0x88,0x6c,0x6f,0x63,0x6c,0x1,0x8e,0x6c,0x6f,0x63,0x6c, + 0x1,0x94,0x6c,0x6f,0x63,0x6c,0x1,0x9a,0x6c,0x6f,0x63,0x6c,0x1,0xa0,0x6c,0x6f,0x63,0x6c,0x1,0xa6,0x6c,0x6f,0x63,0x6c,0x1,0xac,0x6f,0x72,0x64,0x6e, + 0x1,0xb2,0x6f,0x72,0x64,0x6e,0x1,0xb8,0x6f,0x72,0x64,0x6e,0x1,0xbe,0x6f,0x72,0x64,0x6e,0x1,0xc4,0x6f,0x72,0x64,0x6e,0x1,0xca,0x6f,0x72,0x64,0x6e, + 0x1,0xd0,0x6f,0x72,0x64,0x6e,0x1,0xd6,0x73,0x75,0x70,0x73,0x1,0xdc,0x73,0x75,0x70,0x73,0x1,0xe2,0x73,0x75,0x70,0x73,0x1,0xe8,0x73,0x75,0x70,0x73, + 0x1,0xee,0x73,0x75,0x70,0x73,0x1,0xf4,0x73,0x75,0x70,0x73,0x1,0xfa,0x73,0x75,0x70,0x73,0x2,0x0,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x0,0x0,0x1, + 0x0,0x0,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x0,0x0,0x1, + 0x0,0x0,0x0,0x0,0x0,0x1,0x0,0xc,0x0,0x0,0x0,0x1,0x0,0xc,0x0,0x0,0x0,0x1,0x0,0xc,0x0,0x0,0x0,0x1,0x0,0xc,0x0,0x0,0x0,0x1, + 0x0,0xc,0x0,0x0,0x0,0x1,0x0,0xc,0x0,0x0,0x0,0x1,0x0,0xc,0x0,0x0,0x0,0x1,0x0,0xa,0x0,0x0,0x0,0x1,0x0,0xa,0x0,0x0,0x0,0x1, + 0x0,0xa,0x0,0x0,0x0,0x1,0x0,0xa,0x0,0x0,0x0,0x1,0x0,0xa,0x0,0x0,0x0,0x1,0x0,0xa,0x0,0x0,0x0,0x1,0x0,0xa,0x0,0x0,0x0,0x1, + 0x0,0x8,0x0,0x0,0x0,0x1,0x0,0x1,0x0,0x0,0x0,0x1,0x0,0x7,0x0,0x0,0x0,0x1,0x0,0x4,0x0,0x0,0x0,0x1,0x0,0x3,0x0,0x0,0x0,0x1, + 0x0,0x2,0x0,0x0,0x0,0x1,0x0,0x5,0x0,0x0,0x0,0x1,0x0,0x6,0x0,0x0,0x0,0x1,0x0,0xb,0x0,0x0,0x0,0x1,0x0,0xb,0x0,0x0,0x0,0x1, + 0x0,0xb,0x0,0x0,0x0,0x1,0x0,0xb,0x0,0x0,0x0,0x1,0x0,0xb,0x0,0x0,0x0,0x1,0x0,0xb,0x0,0x0,0x0,0x1,0x0,0xb,0x0,0x0,0x0,0x1, + 0x0,0x9,0x0,0x0,0x0,0x1,0x0,0x9,0x0,0x0,0x0,0x1,0x0,0x9,0x0,0x0,0x0,0x1,0x0,0x9,0x0,0x0,0x0,0x1,0x0,0x9,0x0,0x0,0x0,0x1, + 0x0,0x9,0x0,0x0,0x0,0x1,0x0,0x9,0x0,0xf,0x0,0x20,0x0,0x28,0x0,0x32,0x0,0x3a,0x0,0x42,0x0,0x4a,0x0,0x52,0x0,0x5a,0x0,0x62,0x0,0x6a, + 0x0,0x72,0x0,0x7a,0x0,0x84,0x0,0x8c,0x0,0x94,0x0,0x1,0x0,0x0,0x0,0x1,0x1,0x56,0x0,0x6,0x0,0x0,0x0,0x2,0x0,0x74,0x0,0x88,0x0,0x1, + 0x0,0x0,0x0,0x1,0x0,0x92,0x0,0x1,0x0,0x0,0x0,0x1,0x0,0x98,0x0,0x1,0x0,0x0,0x0,0x1,0x0,0x9e,0x0,0x1,0x0,0x0,0x0,0x1,0x0,0x9c, + 0x0,0x1,0x0,0x0,0x0,0x1,0x0,0x9a,0x0,0x1,0x0,0x0,0x0,0x1,0x0,0x98,0x0,0x1,0x0,0x0,0x0,0x1,0x0,0x96,0x0,0x1,0x0,0x0,0x0,0x1, + 0x0,0x94,0x0,0x4,0x0,0x0,0x0,0x1,0x0,0x92,0x0,0x6,0x0,0x0,0x0,0x2,0x0,0xb6,0x0,0xc8,0x0,0x1,0x0,0x0,0x0,0x1,0x0,0xd0,0x0,0x4, + 0x0,0x0,0x0,0x1,0x1,0x26,0x0,0x1,0x0,0x0,0x0,0x1,0x1,0x3c,0x0,0x3,0x0,0x0,0x0,0x2,0x1,0x42,0x1,0x48,0x0,0x1,0x1,0x42,0x0,0x1, + 0x0,0x0,0x0,0xd,0x0,0x3,0x0,0x0,0x0,0x2,0x1,0x3a,0x1,0x34,0x0,0x1,0x1,0x3a,0x0,0x1,0x0,0x0,0x0,0xd,0x0,0x2,0x1,0x2c,0x0,0x4, + 0x0,0x5e,0x0,0x63,0x0,0xdd,0x0,0xe3,0x0,0x2,0x1,0x1e,0x0,0x4,0x0,0x5e,0x0,0x63,0x0,0xdd,0x0,0xe3,0x0,0x1,0x1,0x1c,0x0,0x6,0x0,0x1, + 0x1,0x16,0x0,0x6,0x0,0x1,0x1,0x10,0x0,0x6,0x0,0x1,0x1,0xa,0x0,0x6,0x0,0x1,0x1,0x4,0x0,0x6,0x0,0x1,0x1,0x4,0x0,0xd,0x0,0x1, + 0x1,0x8,0x0,0x2,0x0,0xa,0x0,0x20,0x0,0x2,0x0,0x6,0x0,0xe,0x1,0x12,0x0,0x3,0x1,0x32,0x1,0x9,0x1,0x13,0x0,0x3,0x1,0x32,0x1,0xb, + 0x0,0x1,0x0,0x4,0x1,0x14,0x0,0x3,0x1,0x32,0x1,0xb,0x0,0x3,0x0,0x1,0x0,0xe4,0x0,0x1,0x0,0xee,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0xe, + 0x0,0x3,0x0,0x1,0x0,0xd2,0x0,0x1,0x0,0xe4,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0xe,0x0,0x2,0x0,0xda,0x0,0xe,0x1,0x7f,0x1,0x84,0x1,0x85, + 0x1,0x86,0x1,0x87,0x1,0x88,0x1,0x89,0x1,0x8a,0x1,0x8b,0x1,0x8c,0x1,0x8d,0x1,0x8e,0x1,0x8f,0x1,0x90,0x0,0x2,0x0,0xd4,0x0,0x1b,0x1,0x1, + 0x1,0x2,0x0,0x5e,0x0,0x63,0x1,0x1,0x0,0xae,0x1,0x2,0x0,0xdd,0x0,0xe3,0x1,0x15,0x1,0x16,0x1,0x17,0x1,0x18,0x1,0x7f,0x1,0x84,0x1,0x85, + 0x1,0x86,0x1,0x87,0x1,0x88,0x1,0x89,0x1,0x8a,0x1,0x8b,0x1,0x8c,0x1,0x8d,0x1,0x8e,0x1,0x8f,0x1,0x90,0x0,0x1,0x0,0xd2,0x0,0x2,0x0,0xa, + 0x0,0x14,0x0,0x1,0x0,0x4,0x0,0x3e,0x0,0x2,0x1,0x24,0x0,0x1,0x0,0x4,0x0,0xbd,0x0,0x2,0x1,0x24,0x0,0x2,0x0,0xbc,0x0,0x4,0x1,0x1, + 0x1,0x2,0x1,0x1,0x1,0x2,0x0,0x1,0x0,0x1,0x0,0xb9,0x0,0x1,0x0,0x1,0x1,0x24,0x0,0x1,0x0,0x1,0x0,0x3a,0x0,0x1,0x0,0x4,0x0,0x5c, + 0x0,0x62,0x0,0xdb,0x0,0xe2,0x0,0x1,0x0,0x1,0x0,0xa8,0x0,0x2,0x0,0x1,0x1,0x8,0x1,0xb,0x0,0x0,0x0,0x1,0x0,0x2,0x1,0x8,0x1,0xa, + 0x0,0x2,0x0,0x1,0x1,0x7,0x1,0x10,0x0,0x0,0x0,0x1,0x0,0x2,0x0,0x2,0x0,0x7f,0x0,0x1,0x0,0x2,0x0,0x47,0x0,0xc6,0x0,0x2,0x0,0x4, + 0x1,0x7e,0x1,0x7e,0x0,0x0,0x1,0x80,0x1,0x80,0x0,0x1,0x1,0x82,0x1,0x83,0x0,0x2,0x1,0x91,0x1,0x9a,0x0,0x4,0x0,0x1,0x0,0x1b,0x0,0x2, + 0x0,0x47,0x0,0x5c,0x0,0x62,0x0,0x7f,0x0,0xa8,0x0,0xc6,0x0,0xdb,0x0,0xe2,0x1,0x8,0x1,0x9,0x1,0xa,0x1,0xb,0x1,0x7e,0x1,0x80,0x1,0x82, + 0x1,0x83,0x1,0x91,0x1,0x92,0x1,0x93,0x1,0x94,0x1,0x95,0x1,0x96,0x1,0x97,0x1,0x98,0x1,0x99,0x1,0x9a,0x0,0x1,0x0,0x2,0x0,0x3a,0x0,0xb9, + 0x0,0x1,0x0,0x4,0x0,0x2,0x0,0x47,0x0,0x7f,0x0,0xc6,0x0,0x3,0x4,0xe0,0x1,0x90,0x0,0x5,0x0,0x8,0x5,0x33,0x4,0xcc,0xff,0xe5,0x0,0x99, + 0x5,0x33,0x4,0xcc,0x0,0x7e,0x2,0xcc,0x0,0x82,0x2,0x2a,0x0,0x0,0x0,0x0,0x5,0x9,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x51,0x55,0x51,0x41,0x0,0x1,0x0,0x20,0xfb,0x2,0x5,0x8d,0xfd,0x7d,0x0,0xc2,0x7,0x5,0x3,0x47, + 0x20,0x0,0x0,0x93,0x0,0x0,0x0,0x0,0x3,0x9c,0x4,0xa3,0x0,0x0,0x0,0x20,0x0,0x3,0x0,0x0,0x0,0x3,0x0,0x0,0x0,0x3,0x0,0x0,0x2,0x14, + 0x0,0x1,0x0,0x0,0x0,0x0,0x0,0x1c,0x0,0x3,0x0,0x1,0x0,0x0,0x2,0x14,0x0,0x6,0x1,0xf8,0x0,0x0,0x0,0x9,0x0,0xf7,0x0,0x1,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0x1,0x29,0x1,0x2f,0x1,0x2b,0x1,0x51,0x1,0x67,0x1,0x72,0x1,0x30,0x1,0x38, + 0x1,0x39,0x1,0x22,0x1,0x69,0x1,0x27,0x1,0x41,0x1,0x2c,0x1,0x32,0x1,0x7,0x1,0x8,0x1,0x9,0x1,0xa,0x1,0xb,0x1,0xc,0x1,0xd,0x1,0xe, + 0x1,0xf,0x1,0x10,0x1,0x26,0x1,0x31,0x1,0x60,0x1,0x5b,0x1,0x5c,0x1,0x2d,0x1,0x71,0x0,0x2,0x0,0xd,0x0,0xe,0x0,0x14,0x0,0x18,0x0,0x22, + 0x0,0x23,0x0,0x28,0x0,0x2b,0x0,0x36,0x0,0x38,0x0,0x3a,0x0,0x40,0x0,0x41,0x0,0x47,0x0,0x52,0x0,0x54,0x0,0x55,0x0,0x59,0x0,0x5f,0x0,0x64, + 0x0,0x6f,0x0,0x70,0x0,0x75,0x0,0x76,0x0,0x7b,0x1,0x36,0x1,0x23,0x1,0x37,0x1,0x79,0x1,0x33,0x1,0x95,0x0,0x7f,0x0,0x8a,0x0,0x8b,0x0,0x91, + 0x0,0x95,0x0,0x9f,0x0,0xa0,0x0,0xa5,0x0,0xa8,0x0,0xb4,0x0,0xb7,0x0,0xb9,0x0,0xbf,0x0,0xc0,0x0,0xc6,0x0,0xd1,0x0,0xd3,0x0,0xd4,0x0,0xd8, + 0x0,0xdf,0x0,0xe4,0x0,0xef,0x0,0xf0,0x0,0xf5,0x0,0xf6,0x0,0xfb,0x1,0x34,0x1,0x6f,0x1,0x35,0x1,0x59,0x0,0x0,0x0,0x6,0x0,0xa,0x0,0x11, + 0x0,0x19,0x0,0x46,0x0,0x4b,0x0,0x68,0x0,0x80,0x0,0x84,0x0,0x82,0x0,0x83,0x0,0x88,0x0,0x87,0x0,0x8e,0x0,0x96,0x0,0x9c,0x0,0x99,0x0,0x9a, + 0x0,0xaa,0x0,0xaf,0x0,0xac,0x0,0xad,0x0,0xc5,0x0,0xc7,0x0,0xcb,0x0,0xc9,0x0,0xca,0x0,0xcf,0x0,0xe5,0x0,0xe9,0x0,0xe7,0x0,0xe8,0x1,0x7a, + 0x1,0x78,0x1,0x4f,0x1,0x55,0x1,0x76,0x1,0x25,0x1,0x73,0x0,0xde,0x1,0x75,0x1,0x74,0x1,0x77,0x1,0x80,0x1,0x93,0x1,0x65,0x0,0xc,0x0,0x4f, + 0x1,0x5e,0x1,0x6a,0x1,0x61,0x1,0x5d,0x1,0x57,0x0,0x0,0x1,0x66,0x1,0x6d,0x1,0x6b,0x1,0x6,0x1,0x5f,0x1,0x1,0x1,0x2,0x1,0x4,0x0,0x89, + 0x0,0xce,0x1,0x2e,0x1,0x2a,0x1,0x62,0x1,0x6c,0x1,0x53,0x1,0x58,0x0,0x0,0x1,0x44,0x1,0x45,0x1,0x28,0x1,0x4e,0x0,0x7,0x0,0xb,0x0,0x50, + 0x0,0x51,0x0,0xd0,0x1,0x40,0x1,0x3f,0x1,0x49,0x1,0x4a,0x1,0x4b,0x1,0x4c,0x1,0x5a,0x1,0x6e,0x0,0xf9,0x0,0x79,0x1,0x11,0x1,0x52,0x1,0x46, + 0x1,0x47,0x0,0xff,0x1,0x0,0x1,0x7b,0x1,0x24,0x1,0x4d,0x1,0x48,0x1,0x68,0x0,0x5,0x0,0x1c,0x0,0x3,0x0,0x1d,0x0,0x1f,0x0,0x2d,0x0,0x2f, + 0x0,0x30,0x0,0x32,0x0,0x48,0x0,0x4a,0x0,0x0,0x0,0x4c,0x0,0x65,0x0,0x67,0x0,0x69,0x0,0xa9,0x1,0x92,0x1,0x9a,0x1,0x97,0x1,0x82,0x1,0x94, + 0x1,0x99,0x1,0x91,0x1,0x96,0x1,0x98,0x1,0x83,0x0,0x4,0x4,0x32,0x0,0x0,0x0,0x5a,0x0,0x40,0x0,0x5,0x0,0x1a,0x0,0x2f,0x0,0x7e,0x0,0xb4, + 0x1,0x37,0x1,0x48,0x1,0x7e,0x1,0x92,0x2,0x1b,0x2,0x37,0x2,0xc7,0x2,0xdd,0x3,0x26,0x3,0x94,0x3,0xa9,0x3,0xbc,0x3,0xc0,0x1e,0x85,0x1e,0xf3, + 0x20,0x11,0x20,0x14,0x20,0x1a,0x20,0x1e,0x20,0x22,0x20,0x26,0x20,0x30,0x20,0x33,0x20,0x3a,0x20,0x44,0x20,0x74,0x20,0xa3,0x20,0xa9,0x20,0xac,0x21,0x22, + 0x22,0x2,0x22,0xf,0x22,0x12,0x22,0x1a,0x22,0x1e,0x22,0x2b,0x22,0x48,0x22,0x60,0x22,0x65,0x25,0xca,0xfb,0x2,0xff,0xff,0x0,0x0,0x0,0x20,0x0,0x30, + 0x0,0xa0,0x0,0xb6,0x1,0x39,0x1,0x4a,0x1,0x92,0x2,0x18,0x2,0x37,0x2,0xc6,0x2,0xd8,0x3,0x26,0x3,0x94,0x3,0xa9,0x3,0xbc,0x3,0xc0,0x1e,0x80, + 0x1e,0xf2,0x20,0x11,0x20,0x13,0x20,0x18,0x20,0x1c,0x20,0x20,0x20,0x26,0x20,0x30,0x20,0x32,0x20,0x39,0x20,0x44,0x20,0x74,0x20,0xa3,0x20,0xa9,0x20,0xac, + 0x21,0x22,0x22,0x2,0x22,0xf,0x22,0x11,0x22,0x1a,0x22,0x1e,0x22,0x2b,0x22,0x48,0x22,0x60,0x22,0x64,0x25,0xca,0xfb,0x1,0xff,0xff,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xff,0xc1,0x0,0x0,0xfe,0x7e,0x0,0x0,0x0,0x0,0xfe,0x58,0xfd,0x6f,0xfd,0x5b,0xfd,0x49,0xfd,0x46,0x0,0x0, + 0x0,0x0,0xe1,0x31,0x0,0x0,0xe1,0x33,0x0,0x0,0x0,0x0,0xe1,0x2,0xe1,0x38,0xe1,0x4a,0xe1,0xd,0xe0,0xcd,0xe0,0xa4,0xe0,0xb1,0xe0,0xad,0xe0,0xa6, + 0xe0,0x55,0xdf,0x64,0xdf,0x5c,0x0,0x0,0xdf,0x52,0xdf,0x40,0xdf,0x34,0xdf,0x10,0xdf,0x5,0x0,0x0,0xdb,0xa4,0x0,0x0,0x0,0x1,0x0,0x5a,0x0,0x78, + 0x1,0x14,0x1,0x3c,0x2,0x3e,0x2,0x5c,0x0,0x0,0x2,0xc2,0x0,0x0,0x2,0xc6,0x2,0xc8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x2,0xc8, + 0x2,0xd2,0x0,0x0,0x2,0xd2,0x0,0x0,0x2,0xd2,0x2,0xd6,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x2,0xc2,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x2,0xba,0x0,0x0,0x2,0xba,0x0,0x0,0x0,0x1,0x1,0x29, + 0x1,0x2f,0x1,0x2b,0x1,0x51,0x1,0x67,0x1,0x72,0x1,0x30,0x1,0x38,0x1,0x39,0x1,0x22,0x1,0x69,0x1,0x27,0x1,0x41,0x1,0x2c,0x1,0x32,0x1,0x7, + 0x1,0x8,0x1,0x9,0x1,0xa,0x1,0xb,0x1,0xc,0x1,0xd,0x1,0xe,0x1,0xf,0x1,0x10,0x1,0x26,0x1,0x31,0x1,0x60,0x1,0x5b,0x1,0x5c,0x1,0x2d, + 0x1,0x71,0x0,0x2,0x0,0xd,0x0,0xe,0x0,0x14,0x0,0x18,0x0,0x22,0x0,0x23,0x0,0x28,0x0,0x2b,0x0,0x36,0x0,0x38,0x0,0x3a,0x0,0x40,0x0,0x41, + 0x0,0x47,0x0,0x52,0x0,0x54,0x0,0x55,0x0,0x59,0x0,0x5f,0x0,0x64,0x0,0x6f,0x0,0x70,0x0,0x75,0x0,0x76,0x0,0x7b,0x1,0x36,0x1,0x23,0x1,0x37, + 0x1,0x79,0x1,0x33,0x1,0x95,0x0,0x7f,0x0,0x8a,0x0,0x8b,0x0,0x91,0x0,0x95,0x0,0x9f,0x0,0xa0,0x0,0xa5,0x0,0xa8,0x0,0xb4,0x0,0xb7,0x0,0xb9, + 0x0,0xbf,0x0,0xc0,0x0,0xc6,0x0,0xd1,0x0,0xd3,0x0,0xd4,0x0,0xd8,0x0,0xdf,0x0,0xe4,0x0,0xef,0x0,0xf0,0x0,0xf5,0x0,0xf6,0x0,0xfb,0x1,0x34, + 0x1,0x6f,0x1,0x35,0x1,0x59,0x1,0x4e,0x1,0x2a,0x1,0x4f,0x1,0x55,0x1,0x50,0x1,0x57,0x1,0x70,0x1,0x76,0x1,0x93,0x1,0x74,0x1,0x1,0x1,0x44, + 0x1,0x62,0x1,0x43,0x1,0x75,0x1,0x97,0x1,0x78,0x1,0x6a,0x1,0x16,0x1,0x17,0x1,0x80,0x1,0x73,0x1,0x24,0x1,0x91,0x1,0x15,0x1,0x2,0x1,0x45, + 0x1,0x13,0x1,0x12,0x1,0x14,0x1,0x2e,0x0,0x7,0x0,0x3,0x0,0x5,0x0,0xb,0x0,0x6,0x0,0xa,0x0,0xc,0x0,0x11,0x0,0x1f,0x0,0x19,0x0,0x1c, + 0x0,0x1d,0x0,0x32,0x0,0x2d,0x0,0x2f,0x0,0x30,0x0,0x15,0x0,0x46,0x0,0x4c,0x0,0x48,0x0,0x4a,0x0,0x50,0x0,0x4b,0x1,0x64,0x0,0x4f,0x0,0x69, + 0x0,0x65,0x0,0x67,0x0,0x68,0x0,0x77,0x0,0x53,0x0,0xde,0x0,0x84,0x0,0x80,0x0,0x82,0x0,0x88,0x0,0x83,0x0,0x87,0x0,0x89,0x0,0x8e,0x0,0x9c, + 0x0,0x96,0x0,0x99,0x0,0x9a,0x0,0xaf,0x0,0xaa,0x0,0xac,0x0,0xad,0x0,0x92,0x0,0xc5,0x0,0xcb,0x0,0xc7,0x0,0xc9,0x0,0xcf,0x0,0xca,0x1,0x5a, + 0x0,0xce,0x0,0xe9,0x0,0xe5,0x0,0xe7,0x0,0xe8,0x0,0xf7,0x0,0xd2,0x0,0xf9,0x0,0x8,0x0,0x85,0x0,0x4,0x0,0x81,0x0,0x9,0x0,0x86,0x0,0xf, + 0x0,0x8c,0x0,0x12,0x0,0x8f,0x0,0x13,0x0,0x90,0x0,0x10,0x0,0x8d,0x0,0x16,0x0,0x93,0x0,0x17,0x0,0x94,0x0,0x20,0x0,0x9d,0x0,0x1a,0x0,0x97, + 0x0,0x1e,0x0,0x9b,0x0,0x21,0x0,0x9e,0x0,0x1b,0x0,0x98,0x0,0x25,0x0,0xa2,0x0,0x24,0x0,0xa1,0x0,0x27,0x0,0xa4,0x0,0x26,0x0,0xa3,0x0,0x2a, + 0x0,0xa7,0x0,0x29,0x0,0xa6,0x0,0x35,0x0,0xb3,0x0,0x33,0x0,0xb1,0x0,0x2e,0x0,0xab,0x0,0x34,0x0,0xb2,0x0,0x31,0x0,0xa9,0x0,0x2c,0x0,0xb0, + 0x0,0x37,0x0,0xb6,0x0,0x39,0x0,0xb8,0x0,0x3b,0x0,0xba,0x0,0x3d,0x0,0xbc,0x0,0x3c,0x0,0xbb,0x0,0x3e,0x0,0xbd,0x0,0x3f,0x0,0xbe,0x0,0x42, + 0x0,0xc1,0x0,0x44,0x0,0xc3,0x0,0x43,0x0,0xc2,0x0,0x45,0x0,0xc4,0x0,0x4e,0x0,0xcd,0x0,0x49,0x0,0xc8,0x0,0x4d,0x0,0xcc,0x0,0x51,0x0,0xd0, + 0x0,0x56,0x0,0xd5,0x0,0x58,0x0,0xd7,0x0,0x57,0x0,0xd6,0x0,0x5a,0x0,0xd9,0x0,0x5d,0x0,0xdc,0x0,0x5c,0x0,0xdb,0x0,0x5b,0x0,0xda,0x0,0x62, + 0x0,0xe2,0x0,0x61,0x0,0xe1,0x0,0x60,0x0,0xe0,0x0,0x6e,0x0,0xee,0x0,0x6b,0x0,0xeb,0x0,0x66,0x0,0xe6,0x0,0x6d,0x0,0xed,0x0,0x6a,0x0,0xea, + 0x0,0x6c,0x0,0xec,0x0,0x72,0x0,0xf2,0x0,0x78,0x0,0xf8,0x0,0x79,0x0,0x7c,0x0,0xfc,0x0,0x7e,0x0,0xfe,0x0,0x7d,0x0,0xfd,0x0,0x5e,0x0,0xdd, + 0x0,0x63,0x0,0xe3,0x1,0x92,0x1,0x83,0x1,0x82,0x1,0x94,0x1,0x99,0x1,0x98,0x1,0x9a,0x1,0x96,0x0,0x74,0x0,0xf4,0x0,0x71,0x0,0xf1,0x0,0x73, + 0x0,0xf3,0x0,0x7a,0x0,0xfa,0x1,0x40,0x1,0x3f,0x1,0x49,0x1,0x4a,0x1,0x48,0x1,0x7a,0x1,0x7b,0x1,0x25,0x1,0x6d,0x1,0x63,0x1,0x61,0x1,0x5d, + 0x0,0xff,0x1,0x0,0x0,0x0,0x0,0xa,0x0,0xd3,0xfd,0x7d,0x3,0xa0,0x6,0x55,0x0,0x3,0x0,0xf,0x0,0x15,0x0,0x19,0x0,0x23,0x0,0x29,0x0,0x35, + 0x0,0x39,0x0,0x3d,0x0,0x48,0x0,0x19,0x40,0x16,0x42,0x3e,0x3c,0x3a,0x37,0x36,0x33,0x2a,0x27,0x24,0x1f,0x1a,0x18,0x16,0x11,0x10,0x9,0x4,0x2,0x0, + 0xa,0x30,0x2b,0x1,0x21,0x11,0x21,0x5,0x15,0x33,0x15,0x23,0x15,0x21,0x35,0x23,0x35,0x33,0x35,0x1,0x15,0x21,0x35,0x23,0x35,0x7,0x23,0x35,0x33,0x7, + 0x15,0x33,0x15,0x23,0x15,0x33,0x35,0x33,0x35,0x7,0x15,0x21,0x15,0x21,0x35,0x3,0x15,0x33,0x35,0x33,0x15,0x23,0x35,0x23,0x11,0x21,0x11,0x1,0x11,0x21, + 0x11,0x7,0x23,0x35,0x33,0x5,0x15,0x33,0x7,0x15,0x21,0x35,0x23,0x37,0x33,0x35,0x3,0xa0,0xfd,0x33,0x2,0xcd,0xfd,0xdc,0x94,0x96,0x1,0x78,0x96,0x96, + 0xfe,0x88,0x1,0x78,0x96,0x4c,0x4b,0x4b,0x96,0x96,0x96,0xe2,0x96,0x4b,0xfe,0xd3,0x1,0x78,0xe2,0x4c,0x4b,0xe2,0x4b,0x1,0x78,0xfe,0x88,0x1,0x78,0x4b, + 0xe2,0xe2,0xfe,0xd3,0x9f,0x9f,0x1,0x78,0xe7,0x9e,0x49,0xfd,0x7d,0x8,0xd8,0x98,0x4a,0x54,0x4b,0x4b,0x54,0x4a,0xfe,0xdc,0xeb,0x4d,0x9e,0x9e,0x51,0xdb, + 0x4b,0x54,0x4b,0x9f,0x4b,0x88,0x96,0x4d,0xe3,0xfe,0xec,0x7f,0x34,0x6a,0xb5,0xff,0x0,0x1,0x0,0xfe,0x78,0xff,0x0,0x1,0x0,0xb5,0x6a,0xe9,0x4b,0x6a, + 0x4b,0x4b,0x6a,0x4b,0x0,0x0,0x0,0x2,0x0,0x29,0xff,0xf3,0x4,0x2d,0x4,0xb9,0x0,0x1d,0x0,0x21,0x0,0x29,0x40,0x26,0x20,0x1,0x4,0x3,0x1,0x1, + 0x0,0x1,0x2,0x4a,0x0,0x4,0x0,0x1,0x0,0x4,0x1,0x62,0x0,0x3,0x3,0x16,0x4b,0x2,0x1,0x0,0x0,0xf,0x0,0x4c,0x14,0x2a,0x12,0x13,0x25,0x5, + 0x7,0x19,0x2b,0x24,0x15,0x14,0x6,0x7,0x6,0x23,0x22,0x26,0x27,0x3,0x21,0x3,0x6,0x23,0x22,0x27,0x26,0x26,0x35,0x34,0x37,0x1,0x36,0x36,0x33,0x32, + 0x16,0x17,0x13,0x1,0x21,0x3,0x23,0x4,0x2d,0x1d,0x24,0x14,0x8,0x1d,0x1a,0x5,0x37,0xfe,0x7,0xa4,0x16,0x24,0x12,0x1d,0x18,0x16,0xd,0x2,0x41,0x13, + 0x3a,0x2f,0x2f,0x2e,0x7,0xd3,0xfd,0x86,0x1,0x91,0x64,0x4,0x32,0xa,0x15,0x17,0x4,0x2,0x17,0x1c,0x1,0x21,0xfe,0xd2,0x29,0xd,0xb,0x18,0x10,0x11, + 0x17,0x4,0x1d,0x23,0x1e,0x1e,0x23,0xfb,0xc7,0x1,0x9b,0x2,0x39,0x0,0x0,0x0,0xff,0xff,0x0,0x29,0xff,0xf3,0x4,0x8a,0x6,0x6c,0x0,0x22,0x0,0x2, + 0x0,0x0,0x1,0x7,0x1,0x80,0x0,0x42,0x1,0xe,0x0,0x9,0xb1,0x2,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0x29,0xff,0xf3,0x4,0x9e, + 0x6,0x5c,0x0,0x22,0x0,0x2,0x0,0x0,0x1,0x7,0x1,0x82,0x0,0x19,0x1,0xe,0x0,0x9,0xb1,0x2,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff, + 0x0,0x29,0xff,0xf3,0x4,0x54,0x6,0x6e,0x0,0x22,0x0,0x2,0x0,0x0,0x1,0x7,0x1,0x92,0x0,0x17,0x1,0xe,0x0,0x9,0xb1,0x2,0x1,0xb8,0x1,0xe, + 0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0x29,0xff,0xf3,0x4,0x68,0x6,0x36,0x0,0x22,0x0,0x2,0x0,0x0,0x1,0x7,0x1,0x93,0x0,0x1c,0x1,0xe,0x0,0x9, + 0xb1,0x2,0x2,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0x29,0xff,0xf3,0x4,0x2d,0x6,0x6c,0x0,0x22,0x0,0x2,0x0,0x0,0x1,0x7,0x1,0x95, + 0xff,0xf6,0x1,0xe,0x0,0x9,0xb1,0x2,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0x29,0xff,0xf3,0x4,0x64,0x5,0xf0,0x0,0x22,0x0,0x2, + 0x0,0x0,0x1,0x7,0x1,0x97,0x0,0x1a,0x1,0xe,0x0,0x9,0xb1,0x2,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0x0,0x2,0x0,0x29,0xfe,0x28,0x4,0x2d, + 0x4,0xb9,0x0,0x2e,0x0,0x31,0x0,0x6a,0x40,0xb,0x31,0x1,0x6,0x5,0x1e,0x5,0x2,0x4,0x3,0x2,0x4a,0x4b,0xb0,0x31,0x50,0x58,0x40,0x1c,0x0,0x6, + 0x0,0x3,0x4,0x6,0x3,0x62,0x1,0x1,0x0,0x0,0x2,0x0,0x2,0x5f,0x7,0x1,0x5,0x5,0x16,0x4b,0x0,0x4,0x4,0x17,0x4,0x4c,0x1b,0x40,0x23,0x0, + 0x1,0x4,0x0,0x4,0x1,0x0,0x70,0x0,0x6,0x0,0x3,0x4,0x6,0x3,0x62,0x0,0x0,0x0,0x2,0x0,0x2,0x5f,0x7,0x1,0x5,0x5,0x16,0x4b,0x0,0x4, + 0x4,0x17,0x4,0x4c,0x59,0x40,0x10,0x0,0x0,0x30,0x2f,0x0,0x2e,0x0,0x2d,0x12,0x16,0x26,0x21,0x2c,0x8,0x7,0x19,0x2b,0x0,0x16,0x17,0x13,0x16,0x15, + 0x14,0x7,0x6,0x6,0x15,0x14,0x16,0x33,0x32,0x37,0x37,0x32,0x15,0x14,0x6,0x7,0x6,0x6,0x23,0x22,0x26,0x35,0x34,0x36,0x37,0x3,0x21,0x3,0x6,0x23, + 0x22,0x27,0x26,0x26,0x35,0x34,0x37,0x1,0x36,0x36,0x33,0x1,0x21,0x3,0x3,0x22,0x2e,0x7,0xd3,0x3,0xe,0x74,0x6e,0x22,0x1f,0x33,0x3c,0x8,0x28,0x12, + 0x13,0x26,0x5e,0x2e,0x4f,0x56,0x7c,0x79,0x3a,0xfe,0x8,0xa4,0x16,0x24,0x12,0x1d,0x18,0x16,0xd,0x2,0x41,0x13,0x3a,0x2f,0xfe,0xbe,0x1,0x90,0x66,0x4, + 0xb9,0x1e,0x23,0xfb,0xc7,0xd,0xb,0x14,0xb,0x5b,0x8b,0x37,0x1d,0x1e,0xa,0x1,0x42,0x14,0x19,0x7,0xd,0x10,0x58,0x4a,0x59,0xa7,0x57,0x1,0x29,0xfe, + 0xd2,0x29,0xd,0xb,0x18,0x10,0x11,0x17,0x4,0x1d,0x23,0x1e,0xfd,0x21,0x2,0x39,0xff,0xff,0x0,0x29,0xff,0xf3,0x4,0x2d,0x7,0x5,0x0,0x22,0x0,0x2, + 0x0,0x0,0x1,0x7,0x1,0x99,0x0,0x16,0x0,0xf0,0x0,0x8,0xb1,0x2,0x2,0xb0,0xf0,0xb0,0x33,0x2b,0x0,0x0,0xff,0xff,0x0,0x29,0xff,0xf3,0x4,0x5c, + 0x6,0x15,0x0,0x22,0x0,0x2,0x0,0x0,0x1,0x7,0x1,0x9a,0x0,0x10,0x1,0x4,0x0,0x9,0xb1,0x2,0x1,0xb8,0x1,0x4,0xb0,0x33,0x2b,0x0,0x0,0x2, + 0xff,0xd8,0xff,0xf3,0x4,0xff,0x4,0xa3,0x0,0x33,0x0,0x37,0x0,0x88,0x40,0x10,0x2,0x1,0x0,0x7,0xd,0x1,0x2,0x1,0x2c,0x21,0x18,0x3,0x4,0x3, + 0x3,0x4a,0x4b,0xb0,0x26,0x50,0x58,0x40,0x28,0x0,0x1,0x0,0x2,0x9,0x1,0x2,0x61,0x0,0x9,0x0,0x5,0x3,0x9,0x5,0x61,0x8,0x1,0x0,0x0,0x7, + 0x59,0xa,0x1,0x7,0x7,0xe,0x4b,0x0,0x3,0x3,0x4,0x5b,0x6,0x1,0x4,0x4,0xf,0x4,0x4c,0x1b,0x40,0x2c,0x0,0x1,0x0,0x2,0x9,0x1,0x2,0x61, + 0x0,0x9,0x0,0x5,0x3,0x9,0x5,0x61,0x8,0x1,0x0,0x0,0x7,0x59,0xa,0x1,0x7,0x7,0xe,0x4b,0x0,0x3,0x3,0x4,0x59,0x0,0x4,0x4,0xf,0x4b, + 0x0,0x6,0x6,0x17,0x6,0x4c,0x59,0x40,0x14,0x0,0x0,0x37,0x36,0x35,0x34,0x0,0x33,0x0,0x31,0x12,0x15,0x36,0x21,0x26,0x21,0x26,0xb,0x7,0x1b,0x2b, + 0x0,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x21,0x3,0x21,0x32,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x21,0x3,0x21,0x32,0x16,0x15,0x14,0x7,0x6,0x6,0x23, + 0x21,0x22,0x26,0x35,0x34,0x37,0x13,0x21,0x3,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x1,0x36,0x36,0x33,0x21,0x5,0x23,0x1,0x33,0x4,0xe5,0x1a,0x3, + 0x7,0x23,0x22,0xfe,0x96,0x3f,0x1,0x3d,0x1b,0x1a,0x3,0x7,0x22,0x22,0xfe,0xc3,0x46,0x1,0x7e,0x1b,0x1a,0x3,0x7,0x22,0x22,0xfe,0x2d,0x1d,0x1b,0x2, + 0x35,0xfe,0xf6,0xc1,0x17,0x24,0x12,0x1d,0x2c,0xe,0x2,0x47,0x15,0x2e,0x22,0x2,0x38,0xfd,0xe7,0x14,0xfe,0xe1,0xd5,0x4,0xa3,0x16,0x18,0xb,0xf,0x27, + 0x21,0xfe,0x99,0x15,0x18,0xc,0xf,0x27,0x21,0xfe,0x74,0x16,0x18,0xb,0xf,0x27,0x21,0x15,0x19,0xe,0x8,0x1,0x2e,0xfe,0xaa,0x29,0xd,0x15,0x1c,0x11, + 0x19,0x4,0x4,0x25,0x1f,0x90,0xfd,0xef,0x0,0x0,0x0,0x3,0x0,0x90,0x0,0x0,0x4,0x46,0x4,0xa3,0x0,0x17,0x0,0x20,0x0,0x28,0x0,0x47,0x40,0x44, + 0x5,0x1,0x5,0x2,0x10,0x1,0x0,0x4,0x2,0x4a,0x0,0x2,0x8,0x1,0x5,0x4,0x2,0x5,0x61,0x7,0x1,0x3,0x3,0x1,0x59,0x6,0x1,0x1,0x1,0xe, + 0x4b,0x0,0x4,0x4,0x0,0x59,0x0,0x0,0x0,0xf,0x0,0x4c,0x21,0x21,0x18,0x18,0x0,0x0,0x21,0x28,0x21,0x27,0x24,0x22,0x18,0x20,0x18,0x1f,0x1b,0x19, + 0x0,0x17,0x0,0x15,0x3b,0x9,0x7,0x15,0x2b,0x0,0x16,0x15,0x14,0x6,0x7,0x16,0x16,0x15,0x14,0x6,0x6,0x23,0x21,0x22,0x26,0x35,0x34,0x37,0x13,0x36, + 0x36,0x33,0x21,0x5,0x3,0x21,0x32,0x36,0x35,0x34,0x26,0x23,0x1,0x3,0x21,0x32,0x36,0x35,0x34,0x23,0x3,0xa5,0xa1,0x5f,0x5a,0x4c,0x51,0x61,0xc4,0x8f, + 0xfe,0x52,0x1d,0x1b,0x2,0xba,0x7,0x20,0x22,0x1,0x74,0xfe,0xcb,0x40,0x1,0xc,0x7f,0x8e,0x5c,0x66,0xfe,0x8f,0x44,0x1,0x36,0x87,0x9a,0xd0,0x4,0xa3, + 0x93,0x7e,0x6c,0x92,0x30,0x1d,0x80,0x59,0x64,0xa6,0x64,0x15,0x19,0xe,0x8,0x4,0x1b,0x26,0x1e,0x90,0xfe,0x90,0x79,0x65,0x4a,0x48,0xfe,0x0,0xfe,0x7d, + 0x74,0x6c,0xa3,0x0,0x0,0x1,0x0,0xaa,0xff,0xea,0x4,0x97,0x4,0xb9,0x0,0x28,0x0,0x36,0x40,0x33,0x0,0x0,0x1,0x3,0x1,0x0,0x3,0x70,0x0,0x3, + 0x2,0x1,0x3,0x2,0x6e,0x0,0x1,0x1,0x5,0x5b,0x6,0x1,0x5,0x5,0x16,0x4b,0x0,0x2,0x2,0x4,0x5b,0x0,0x4,0x4,0x17,0x4,0x4c,0x0,0x0,0x0, + 0x28,0x0,0x27,0x26,0x23,0x25,0x24,0x16,0x7,0x7,0x19,0x2b,0x0,0x17,0x16,0x15,0x14,0x7,0x6,0x23,0x22,0x27,0x26,0x26,0x23,0x22,0x6,0x2,0x15,0x14, + 0x16,0x33,0x32,0x36,0x37,0x36,0x33,0x32,0x17,0x16,0x15,0x14,0x7,0x6,0x23,0x22,0x26,0x26,0x35,0x34,0x12,0x24,0x33,0x3,0xd4,0x9f,0x24,0xe,0x17,0x23, + 0x11,0xe,0x3b,0x95,0x4b,0x8a,0xd4,0x73,0x9a,0x88,0x54,0xa5,0x47,0xe,0x12,0x22,0x17,0xe,0x24,0xbd,0xd1,0x85,0xc4,0x68,0x9d,0x1,0x1f,0xba,0x4,0xb9, + 0x5d,0x15,0x20,0x14,0x1c,0x2d,0xa,0x28,0x29,0xad,0xfe,0xeb,0x96,0x9e,0xb1,0x32,0x2e,0xa,0x2d,0x1c,0x14,0x20,0x15,0x6c,0x75,0xd7,0x8f,0xc4,0x1,0x5c, + 0xd4,0x0,0x0,0x0,0xff,0xff,0x0,0xaa,0xff,0xea,0x4,0x97,0x6,0x6c,0x0,0x22,0x0,0xe,0x0,0x0,0x1,0x7,0x1,0x80,0x0,0x3c,0x1,0xe,0x0,0x9, + 0xb1,0x1,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0xaa,0xff,0xea,0x4,0xa8,0x6,0x72,0x0,0x22,0x0,0xe,0x0,0x0,0x1,0x7,0x1,0x83, + 0x0,0x46,0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0x0,0x1,0x0,0xaa,0xfe,0x28,0x4,0x97,0x4,0xb9,0x0,0x4b,0x0,0x58, + 0x40,0x55,0x41,0x1,0x4,0x2,0x3e,0x1,0x9,0x5,0x2,0x4a,0x0,0x0,0x1,0x3,0x1,0x0,0x3,0x70,0x0,0x3,0x2,0x1,0x3,0x2,0x6e,0x0,0x7,0x9, + 0x8,0x9,0x7,0x8,0x70,0x0,0x5,0x0,0x9,0x7,0x5,0x9,0x63,0x0,0x8,0x0,0x6,0x8,0x6,0x5f,0x0,0x1,0x1,0xa,0x5b,0x0,0xa,0xa,0x16,0x4b, + 0x0,0x2,0x2,0x4,0x5b,0x0,0x4,0x4,0x17,0x4,0x4c,0x4a,0x48,0x3d,0x3a,0x23,0x28,0x26,0x11,0x16,0x23,0x25,0x24,0x14,0xb,0x7,0x1d,0x2b,0x0,0x15, + 0x14,0x7,0x6,0x23,0x22,0x27,0x26,0x26,0x23,0x22,0x6,0x2,0x15,0x14,0x16,0x33,0x32,0x36,0x37,0x36,0x33,0x32,0x17,0x16,0x15,0x14,0x7,0x6,0x7,0x7, + 0x36,0x16,0x15,0x14,0x6,0x7,0x6,0x23,0x22,0x26,0x27,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x32,0x17,0x16,0x16,0x33,0x32,0x36,0x35,0x34,0x23,0x7,0x22, + 0x35,0x34,0x37,0x37,0x2e,0x2,0x35,0x34,0x12,0x24,0x33,0x32,0x17,0x4,0x97,0xe,0x17,0x23,0x11,0xe,0x3b,0x95,0x4b,0x8a,0xd4,0x73,0x9a,0x88,0x54,0xa5, + 0x47,0xe,0x12,0x22,0x17,0xe,0x24,0xab,0xbf,0x10,0x58,0x5a,0x1c,0x1a,0x45,0x97,0x44,0x7c,0x2e,0x11,0xa,0x9,0x19,0xe,0x7,0x4,0x26,0x64,0x31,0x3a, + 0x3a,0x67,0x23,0x1d,0x1,0x1c,0x69,0x97,0x50,0x9d,0x1,0x1f,0xba,0xb4,0x9f,0x4,0x47,0x20,0x14,0x1c,0x2d,0xa,0x28,0x29,0xad,0xfe,0xeb,0x96,0x9e,0xb1, + 0x32,0x2e,0xa,0x2d,0x1c,0x14,0x20,0x15,0x62,0x9,0x5d,0x2,0x52,0x40,0x22,0x4f,0x1b,0x4a,0x18,0x18,0x8,0x18,0x15,0x11,0x11,0x15,0x2,0xf,0x13,0x2d, + 0x24,0x37,0x1,0x1c,0x9,0x5,0xa2,0x14,0x7d,0xc4,0x7d,0xc4,0x1,0x5c,0xd4,0x5d,0xff,0xff,0x0,0xaa,0xff,0xea,0x4,0x97,0x6,0x6e,0x0,0x22,0x0,0xe, + 0x0,0x0,0x1,0x7,0x1,0x92,0x0,0x5a,0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0xaa,0xff,0xea,0x4,0x97, + 0x6,0x5f,0x0,0x22,0x0,0xe,0x0,0x0,0x1,0x7,0x1,0x94,0x0,0x46,0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0x0,0x2, + 0x0,0x72,0x0,0x0,0x4,0x7a,0x4,0xa3,0x0,0x14,0x0,0x20,0x0,0x32,0x40,0x2f,0xd,0x1,0x0,0x2,0x1,0x4a,0x5,0x1,0x3,0x3,0x1,0x59,0x4,0x1, + 0x1,0x1,0xe,0x4b,0x0,0x2,0x2,0x0,0x59,0x0,0x0,0x0,0xf,0x0,0x4c,0x15,0x15,0x0,0x0,0x15,0x20,0x15,0x1f,0x18,0x16,0x0,0x14,0x0,0x12,0x38, + 0x6,0x7,0x15,0x2b,0x0,0x16,0x16,0x15,0x14,0x2,0x7,0x6,0x6,0x23,0x21,0x22,0x26,0x35,0x34,0x37,0x13,0x36,0x36,0x33,0x21,0x5,0x3,0x33,0x32,0x36, + 0x37,0x36,0x36,0x35,0x34,0x26,0x23,0x3,0x45,0xc9,0x6c,0x54,0x56,0x4f,0xe2,0x95,0xfe,0xa0,0x1d,0x1b,0x2,0xba,0x7,0x20,0x22,0x1,0x46,0xfe,0xf9,0x9e, + 0xe6,0x75,0xb0,0x3d,0x41,0x40,0xab,0x9a,0x4,0xa3,0x70,0xd1,0x8e,0x7f,0xfe,0xeb,0x6b,0x63,0x72,0x15,0x19,0xe,0x8,0x4,0x1b,0x26,0x1e,0x90,0xfc,0x7d, + 0x5a,0x4f,0x54,0xda,0x68,0xa0,0xa4,0x0,0x0,0x2,0x0,0x54,0x0,0x0,0x4,0x7a,0x4,0xa3,0x0,0x1f,0x0,0x36,0x0,0x42,0x40,0x3f,0x2b,0x14,0x2,0x1, + 0x2,0xd,0x1,0x0,0x7,0x2,0x4a,0x5,0x1,0x2,0x6,0x1,0x1,0x7,0x2,0x1,0x63,0x0,0x4,0x4,0x3,0x59,0x8,0x1,0x3,0x3,0xe,0x4b,0x0,0x7, + 0x7,0x0,0x59,0x0,0x0,0x0,0xf,0x0,0x4c,0x0,0x0,0x34,0x32,0x31,0x2f,0x29,0x27,0x26,0x24,0x0,0x1f,0x0,0x1d,0x26,0x25,0x38,0x9,0x7,0x17,0x2b, + 0x0,0x16,0x16,0x15,0x14,0x2,0x7,0x6,0x6,0x23,0x21,0x22,0x26,0x35,0x34,0x37,0x13,0x23,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x33,0x13,0x36,0x36, + 0x33,0x21,0x12,0x36,0x35,0x34,0x26,0x23,0x23,0x3,0x33,0x32,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x23,0x3,0x33,0x32,0x36,0x37,0x3,0x45,0xc9,0x6c,0x54, + 0x56,0x4f,0xe2,0x95,0xfe,0xd2,0x1d,0x1b,0x2,0x54,0x71,0x1b,0x1a,0x3,0x7,0x23,0x22,0x70,0x4d,0x7,0x20,0x22,0x1,0x14,0xe4,0x40,0xab,0x9a,0xb4,0x3f, + 0xdc,0x1b,0x1a,0x3,0x7,0x23,0x22,0xdb,0x46,0xb4,0x75,0xb0,0x3d,0x4,0xa3,0x70,0xd1,0x8e,0x7f,0xfe,0xeb,0x6b,0x63,0x72,0x15,0x19,0xe,0x8,0x1,0xd8, + 0x16,0x18,0xb,0xf,0x27,0x21,0x1,0xb3,0x26,0x1e,0xfc,0xea,0xda,0x68,0xa0,0xa4,0xfe,0x99,0x16,0x18,0xb,0xf,0x27,0x21,0xfe,0x74,0x5a,0x4f,0x0,0x0, + 0xff,0xff,0x0,0x72,0x0,0x0,0x4,0x7a,0x6,0x68,0x0,0x22,0x0,0x14,0x0,0x0,0x1,0x7,0x1,0x83,0xff,0xec,0x1,0x4,0x0,0x9,0xb1,0x2,0x1,0xb8, + 0x1,0x4,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0x72,0x0,0x0,0x4,0x7a,0x5,0xf0,0x0,0x22,0x0,0x14,0x0,0x0,0x1,0x7,0x1,0x97,0x0,0x0,0x1,0xe, + 0x0,0x9,0xb1,0x2,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0x0,0x1,0x0,0xa4,0x0,0x0,0x4,0x7d,0x4,0xa3,0x0,0x28,0x0,0x3e,0x40,0x3b,0x2,0x1, + 0x0,0x5,0xd,0x1,0x2,0x1,0x21,0x18,0x2,0x4,0x3,0x3,0x4a,0x0,0x1,0x0,0x2,0x3,0x1,0x2,0x61,0x0,0x0,0x0,0x5,0x59,0x6,0x1,0x5,0x5, + 0xe,0x4b,0x0,0x3,0x3,0x4,0x59,0x0,0x4,0x4,0xf,0x4,0x4c,0x0,0x0,0x0,0x28,0x0,0x26,0x36,0x21,0x26,0x21,0x26,0x7,0x7,0x19,0x2b,0x0,0x16, + 0x15,0x14,0x7,0x6,0x6,0x23,0x21,0x3,0x21,0x32,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x21,0x3,0x21,0x32,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x21,0x22, + 0x26,0x35,0x34,0x37,0x13,0x36,0x36,0x33,0x21,0x4,0x63,0x1a,0x3,0x7,0x23,0x22,0xfd,0xba,0x3f,0x1,0xc9,0x1b,0x1a,0x3,0x7,0x22,0x22,0xfe,0x37,0x46, + 0x2,0x5a,0x1b,0x1a,0x3,0x7,0x22,0x22,0xfd,0x51,0x1d,0x1b,0x2,0xba,0x7,0x20,0x22,0x2,0x9f,0x4,0xa3,0x16,0x18,0xb,0xf,0x27,0x21,0xfe,0x99,0x15, + 0x18,0xc,0xf,0x27,0x21,0xfe,0x74,0x16,0x18,0xb,0xf,0x27,0x21,0x15,0x19,0xe,0x8,0x4,0x1b,0x26,0x1e,0x0,0x0,0x0,0xff,0xff,0x0,0xa4,0x0,0x0, + 0x4,0x7d,0x6,0x6c,0x0,0x22,0x0,0x18,0x0,0x0,0x1,0x7,0x1,0x80,0x0,0x6,0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0, + 0xff,0xff,0x0,0xa4,0x0,0x0,0x4,0xa3,0x6,0x5c,0x0,0x22,0x0,0x18,0x0,0x0,0x1,0x7,0x1,0x82,0x0,0x1e,0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8, + 0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0xa4,0x0,0x0,0x4,0x85,0x6,0x68,0x0,0x22,0x0,0x18,0x0,0x0,0x1,0x7,0x1,0x83,0x0,0x23,0x1,0x4, + 0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0x4,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0xa4,0x0,0x0,0x4,0x7d,0x6,0x6e,0x0,0x22,0x0,0x18,0x0,0x0,0x1,0x7, + 0x1,0x92,0x0,0x30,0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0xa4,0x0,0x0,0x4,0x7d,0x6,0x36,0x0,0x22, + 0x0,0x18,0x0,0x0,0x1,0x7,0x1,0x93,0x0,0x30,0x1,0xe,0x0,0x9,0xb1,0x1,0x2,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0xa4,0x0,0x0, + 0x4,0x7d,0x6,0x5f,0x0,0x22,0x0,0x18,0x0,0x0,0x1,0x7,0x1,0x94,0x0,0x28,0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0, + 0xff,0xff,0x0,0xa4,0x0,0x0,0x4,0x7d,0x6,0x6c,0x0,0x22,0x0,0x18,0x0,0x0,0x1,0x7,0x1,0x95,0x0,0x3c,0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8, + 0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0xa4,0x0,0x0,0x4,0x7d,0x5,0xf0,0x0,0x22,0x0,0x18,0x0,0x0,0x1,0x7,0x1,0x97,0x0,0x2e,0x1,0xe, + 0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0x0,0x1,0x0,0xa4,0xfe,0x28,0x4,0x7d,0x4,0xa3,0x0,0x40,0x0,0x86,0x40,0xf,0x2,0x1, + 0x0,0x9,0xd,0x1,0x2,0x1,0x39,0x18,0x2,0x4,0x3,0x3,0x4a,0x4b,0xb0,0x31,0x50,0x58,0x40,0x27,0x0,0x1,0x0,0x2,0x3,0x1,0x2,0x61,0x6,0x1, + 0x5,0x0,0x7,0x5,0x7,0x5f,0x0,0x0,0x0,0x9,0x59,0xa,0x1,0x9,0x9,0xe,0x4b,0x0,0x3,0x3,0x4,0x5b,0x8,0x1,0x4,0x4,0xf,0x4,0x4c,0x1b, + 0x40,0x2e,0x0,0x6,0x4,0x5,0x4,0x6,0x5,0x70,0x0,0x1,0x0,0x2,0x3,0x1,0x2,0x61,0x0,0x5,0x0,0x7,0x5,0x7,0x5f,0x0,0x0,0x0,0x9,0x59, + 0xa,0x1,0x9,0x9,0xe,0x4b,0x0,0x3,0x3,0x4,0x5b,0x8,0x1,0x4,0x4,0xf,0x4,0x4c,0x59,0x40,0x12,0x0,0x0,0x0,0x40,0x0,0x3e,0x25,0x26,0x21, + 0x26,0x16,0x21,0x26,0x21,0x26,0xb,0x7,0x1d,0x2b,0x0,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x21,0x3,0x21,0x32,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x21, + 0x3,0x21,0x32,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x23,0x6,0x6,0x15,0x14,0x16,0x33,0x32,0x37,0x37,0x32,0x15,0x14,0x6,0x7,0x6,0x6,0x23,0x22,0x26, + 0x35,0x34,0x36,0x37,0x21,0x22,0x26,0x35,0x34,0x37,0x13,0x36,0x36,0x33,0x21,0x4,0x63,0x1a,0x3,0x7,0x23,0x22,0xfd,0xba,0x3f,0x1,0xc9,0x1b,0x1a,0x3, + 0x7,0x22,0x22,0xfe,0x37,0x46,0x2,0x5a,0x1b,0x1a,0x3,0x7,0x22,0x22,0x4,0x6f,0x69,0x22,0x1f,0x33,0x3c,0x8,0x28,0x12,0x13,0x26,0x5e,0x2e,0x4f,0x56, + 0x66,0x63,0xfe,0x0,0x1d,0x1b,0x2,0xba,0x7,0x20,0x22,0x2,0x9f,0x4,0xa3,0x16,0x18,0xb,0xf,0x27,0x21,0xfe,0x99,0x15,0x18,0xc,0xf,0x27,0x21,0xfe, + 0x74,0x16,0x18,0xb,0xf,0x27,0x21,0x58,0x88,0x35,0x1d,0x1e,0xa,0x1,0x42,0x14,0x19,0x7,0xd,0x10,0x58,0x4a,0x50,0x98,0x4e,0x15,0x19,0xe,0x8,0x4, + 0x1b,0x26,0x1e,0x0,0x0,0x0,0x0,0x1,0x0,0xb6,0xff,0xf6,0x4,0x91,0x4,0xa3,0x0,0x22,0x0,0x37,0x40,0x34,0x2,0x1,0x0,0x4,0xd,0x1,0x2,0x1, + 0x1b,0x1,0x3,0x2,0x3,0x4a,0x0,0x1,0x0,0x2,0x3,0x1,0x2,0x61,0x0,0x0,0x0,0x4,0x59,0x5,0x1,0x4,0x4,0xe,0x4b,0x0,0x3,0x3,0xf,0x3, + 0x4c,0x0,0x0,0x0,0x22,0x0,0x20,0x33,0x26,0x21,0x26,0x6,0x7,0x18,0x2b,0x0,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x21,0x3,0x21,0x32,0x16,0x15,0x14, + 0x7,0x6,0x6,0x23,0x21,0x3,0x6,0x6,0x23,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x36,0x36,0x33,0x21,0x4,0x77,0x1a,0x3,0x7,0x23,0x22,0xfd,0xba,0x46, + 0x1,0xd3,0x1b,0x1a,0x3,0x7,0x22,0x22,0xfe,0x2c,0x4e,0x6,0x27,0x28,0x2,0x23,0x1e,0x2,0xbc,0x7,0x20,0x22,0x2,0x9f,0x4,0xa3,0x16,0x18,0xb,0xf, + 0x27,0x21,0xfe,0x71,0x15,0x18,0xc,0xf,0x27,0x21,0xfe,0x43,0x23,0x1e,0x15,0x18,0x4,0x10,0x4,0x28,0x26,0x1e,0x0,0x0,0x0,0x1,0x0,0x83,0xff,0xea, + 0x4,0x8f,0x4,0xb9,0x0,0x35,0x0,0x3f,0x40,0x3c,0x25,0x1c,0x2,0x3,0x4,0x1,0x4a,0x0,0x0,0x1,0x4,0x1,0x0,0x4,0x70,0x0,0x4,0x0,0x3,0x2, + 0x4,0x3,0x61,0x0,0x1,0x1,0x6,0x5b,0x7,0x1,0x6,0x6,0x16,0x4b,0x0,0x2,0x2,0x5,0x5b,0x0,0x5,0x5,0x17,0x5,0x4c,0x0,0x0,0x0,0x35,0x0, + 0x34,0x29,0x36,0x22,0x26,0x24,0x17,0x8,0x7,0x1a,0x2b,0x0,0x16,0x17,0x16,0x15,0x14,0x7,0x6,0x23,0x22,0x27,0x26,0x26,0x23,0x22,0x6,0x2,0x15,0x14, + 0x16,0x16,0x33,0x32,0x37,0x13,0x21,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x21,0x32,0x16,0x15,0x14,0x7,0x3,0x6,0x6,0x7,0x6,0x23,0x22,0x26,0x26, + 0x35,0x34,0x12,0x24,0x33,0x3,0x63,0xb8,0x50,0x24,0xe,0x17,0x22,0xe,0x12,0x41,0x9e,0x50,0x91,0xd7,0x71,0x47,0x86,0x5d,0xaf,0x86,0x2f,0xfe,0xf5,0x1b, + 0x1a,0x3,0x7,0x22,0x22,0x1,0x62,0x1c,0x19,0x2,0x45,0x5,0x18,0x13,0xb2,0xeb,0x86,0xc9,0x6d,0xa3,0x1,0x24,0xba,0x4,0xb9,0x2e,0x2e,0x15,0x20,0x14, + 0x1c,0x2d,0xa,0x27,0x29,0xb0,0xfe,0xed,0x91,0x69,0x99,0x51,0x39,0x1,0xb,0x15,0x18,0xc,0xf,0x27,0x21,0x14,0x18,0xf,0x9,0xfe,0x78,0x18,0x24,0x9, + 0x57,0x77,0xda,0x91,0xc2,0x1,0x59,0xd2,0xff,0xff,0x0,0x83,0xff,0xea,0x4,0xb7,0x6,0x5c,0x0,0x22,0x0,0x23,0x0,0x0,0x1,0x7,0x1,0x82,0x0,0x32, + 0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0x83,0xff,0xea,0x4,0x8f,0x6,0x6e,0x0,0x22,0x0,0x23,0x0,0x0, + 0x1,0x7,0x1,0x92,0x0,0x3c,0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0x83,0xfd,0xf3,0x4,0x8f,0x4,0xb9, + 0x0,0x22,0x0,0x23,0x0,0x0,0x0,0x3,0x1,0x7e,0x4,0xf4,0x0,0x0,0xff,0xff,0x0,0x83,0xff,0xea,0x4,0x8f,0x6,0x5f,0x0,0x22,0x0,0x23,0x0,0x0, + 0x1,0x7,0x1,0x94,0x0,0x28,0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0x0,0x1,0x0,0x70,0xff,0xf6,0x4,0xa0,0x4,0xad, + 0x0,0x2b,0x0,0x33,0x40,0x30,0x22,0x2,0x2,0x4,0x3,0x18,0xc,0x2,0x0,0x1,0x2,0x4a,0x0,0x4,0x0,0x1,0x0,0x4,0x1,0x62,0x6,0x5,0x2,0x3, + 0x3,0xe,0x4b,0x2,0x1,0x0,0x0,0xf,0x0,0x4c,0x0,0x0,0x0,0x2b,0x0,0x29,0x15,0x37,0x33,0x15,0x37,0x7,0x7,0x19,0x2b,0x0,0x16,0x15,0x14,0x7, + 0x3,0x6,0x6,0x23,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x21,0x3,0x6,0x6,0x23,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x36,0x36,0x33,0x33,0x32,0x16,0x15, + 0x14,0x7,0x3,0x21,0x13,0x36,0x36,0x33,0x33,0x4,0x82,0x1e,0x2,0xbe,0x6,0x27,0x28,0x2,0x23,0x1e,0x2,0x56,0xfd,0xbe,0x56,0x6,0x27,0x28,0x2,0x23, + 0x1e,0x2,0xbe,0x6,0x27,0x28,0x2,0x23,0x1e,0x2,0x4f,0x2,0x42,0x4f,0x6,0x27,0x28,0x2,0x4,0xad,0x15,0x18,0x4,0x10,0xfb,0xcb,0x23,0x1e,0x15,0x18, + 0x4,0x10,0x1,0xe5,0xfe,0x1b,0x23,0x1e,0x15,0x18,0x4,0x10,0x4,0x35,0x23,0x1e,0x15,0x18,0x4,0x10,0xfe,0x40,0x1,0xc0,0x23,0x1e,0x0,0x0,0x0,0x2, + 0x0,0x70,0xff,0xf6,0x5,0xa,0x4,0xad,0x0,0x41,0x0,0x45,0x0,0x7d,0x40,0x11,0x3d,0x31,0x2,0x5,0x6,0x23,0x2,0x2,0x0,0x5,0x1c,0x10,0x2,0x1, + 0x2,0x3,0x4a,0x4b,0xb0,0x24,0x50,0x58,0x40,0x24,0x0,0xb,0x0,0x2,0x1,0xb,0x2,0x61,0x8,0x1,0x6,0x6,0xe,0x4b,0xa,0x4,0x2,0x0,0x0,0x5, + 0x5b,0xc,0x9,0x7,0x3,0x5,0x5,0x19,0x4b,0x3,0x1,0x1,0x1,0xf,0x1,0x4c,0x1b,0x40,0x22,0xc,0x9,0x7,0x3,0x5,0xa,0x4,0x2,0x0,0xb,0x5, + 0x0,0x64,0x0,0xb,0x0,0x2,0x1,0xb,0x2,0x61,0x8,0x1,0x6,0x6,0xe,0x4b,0x3,0x1,0x1,0x1,0xf,0x1,0x4c,0x59,0x40,0x16,0x0,0x0,0x45,0x44, + 0x43,0x42,0x0,0x41,0x0,0x40,0x33,0x15,0x33,0x26,0x25,0x33,0x15,0x33,0x26,0xd,0x7,0x1d,0x2b,0x0,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x23,0x3,0x6, + 0x6,0x23,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x21,0x3,0x6,0x6,0x23,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x23,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33, + 0x33,0x37,0x36,0x36,0x33,0x33,0x32,0x16,0x15,0x14,0x7,0x7,0x21,0x37,0x36,0x36,0x33,0x33,0x32,0x16,0x15,0x14,0x7,0x7,0x33,0x5,0x21,0x7,0x21,0x4, + 0xf0,0x1a,0x3,0x7,0x23,0x22,0x52,0x89,0x6,0x27,0x28,0x2,0x23,0x1e,0x2,0x56,0xfd,0xbe,0x56,0x6,0x27,0x28,0x2,0x23,0x1e,0x2,0x89,0x52,0x1b,0x1a, + 0x3,0x7,0x23,0x22,0x51,0x1c,0x6,0x27,0x28,0x2,0x23,0x1e,0x2,0x1c,0x2,0x42,0x1c,0x6,0x27,0x28,0x2,0x23,0x1e,0x2,0x1c,0x53,0xfe,0xfe,0xfd,0xbe, + 0x1a,0x2,0x42,0x3,0xce,0x16,0x18,0xb,0xf,0x27,0x21,0xfc,0xf9,0x23,0x1e,0x15,0x18,0x4,0x10,0x1,0xe5,0xfe,0x1b,0x23,0x1e,0x15,0x18,0x4,0x10,0x3, + 0x7,0x16,0x18,0xb,0xf,0x27,0x21,0x9e,0x23,0x1e,0x15,0x18,0x4,0x10,0x9e,0x9e,0x23,0x1e,0x15,0x18,0x4,0x10,0x9e,0x90,0x92,0x0,0xff,0xff,0x0,0x70, + 0xff,0xf6,0x4,0xa0,0x6,0x6e,0x0,0x22,0x0,0x28,0x0,0x0,0x1,0x7,0x1,0x92,0x0,0x1e,0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0xe,0xb0,0x33, + 0x2b,0x0,0x0,0x1,0x0,0x96,0x0,0x0,0x4,0x67,0x4,0xa3,0x0,0x27,0x0,0x35,0x40,0x32,0x21,0x2,0x2,0x0,0x5,0x16,0xd,0x2,0x2,0x1,0x2,0x4a, + 0x4,0x1,0x0,0x0,0x5,0x59,0x6,0x1,0x5,0x5,0xe,0x4b,0x3,0x1,0x1,0x1,0x2,0x59,0x0,0x2,0x2,0xf,0x2,0x4c,0x0,0x0,0x0,0x27,0x0,0x25, + 0x21,0x26,0x36,0x21,0x26,0x7,0x7,0x19,0x2b,0x0,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x23,0x3,0x21,0x32,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x21,0x22, + 0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x21,0x13,0x21,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x21,0x4,0x4d,0x1a,0x3,0x7,0x23,0x22,0xf6,0x9e,0x1,0x1e, + 0x1b,0x1a,0x3,0x7,0x22,0x22,0xfd,0x42,0x1c,0x19,0x2,0x7,0x23,0x22,0x1,0xa,0x9e,0xfe,0xf6,0x1b,0x1a,0x3,0x7,0x23,0x22,0x2,0x96,0x4,0xa3,0x16, + 0x18,0xb,0xf,0x27,0x21,0xfc,0x7d,0x16,0x18,0xb,0xf,0x27,0x21,0x16,0x19,0xf,0xa,0x27,0x21,0x3,0x83,0x16,0x18,0xb,0xf,0x27,0x21,0x0,0x0,0x2, + 0x0,0x74,0xff,0xea,0x4,0xa1,0x4,0xad,0x0,0x13,0x0,0x31,0x0,0x6d,0x40,0xb,0x16,0x2,0x2,0x3,0x1,0xc,0x1,0x0,0x4,0x2,0x4a,0x4b,0xb0,0x28, + 0x50,0x58,0x40,0x1c,0x0,0x3,0x1,0x4,0x1,0x3,0x4,0x70,0x7,0x5,0x6,0x3,0x1,0x1,0xe,0x4b,0x0,0x4,0x4,0x0,0x5b,0x2,0x1,0x0,0x0,0xf, + 0x0,0x4c,0x1b,0x40,0x20,0x0,0x3,0x1,0x4,0x1,0x3,0x4,0x70,0x7,0x5,0x6,0x3,0x1,0x1,0xe,0x4b,0x0,0x0,0x0,0xf,0x4b,0x0,0x4,0x4,0x2, + 0x5b,0x0,0x2,0x2,0x17,0x2,0x4c,0x59,0x40,0x16,0x14,0x14,0x0,0x0,0x14,0x31,0x14,0x2f,0x2a,0x28,0x24,0x23,0x1c,0x1a,0x0,0x13,0x0,0x11,0x37,0x8, + 0x7,0x15,0x2b,0x0,0x16,0x15,0x14,0x7,0x3,0x6,0x6,0x23,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x36,0x36,0x33,0x33,0x20,0x16,0x15,0x14,0x7,0x3,0x2, + 0x21,0x22,0x26,0x27,0x26,0x35,0x34,0x37,0x36,0x33,0x32,0x17,0x16,0x16,0x33,0x32,0x36,0x37,0x13,0x36,0x36,0x33,0x33,0x1,0xae,0x1e,0x2,0xbe,0x6,0x27, + 0x28,0x2,0x23,0x1e,0x2,0xbe,0x6,0x27,0x28,0x2,0x2,0xf8,0x1e,0x2,0x9b,0x31,0xfe,0xfe,0x37,0x7c,0x2c,0x21,0xc,0x16,0x20,0xc,0xe,0x26,0x5e,0x2d, + 0x3f,0x47,0xf,0x96,0x6,0x27,0x28,0x2,0x4,0xad,0x15,0x18,0x4,0x10,0xfb,0xcb,0x23,0x1e,0x15,0x18,0x4,0x10,0x4,0x35,0x23,0x1e,0x15,0x18,0x4,0x10, + 0xfc,0x91,0xfe,0xed,0x1a,0x18,0x12,0x1f,0x14,0x1c,0x31,0x7,0x13,0x16,0x4a,0x53,0x3,0x51,0x23,0x1e,0x0,0x0,0xff,0xff,0x0,0x96,0x0,0x0,0x4,0x67, + 0x6,0x6c,0x0,0x22,0x0,0x2b,0x0,0x0,0x1,0x7,0x1,0x80,0x0,0x10,0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff, + 0x0,0x96,0x0,0x0,0x4,0x99,0x6,0x5c,0x0,0x22,0x0,0x2b,0x0,0x0,0x1,0x7,0x1,0x82,0x0,0x14,0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0xe, + 0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0x96,0x0,0x0,0x4,0x67,0x6,0x6e,0x0,0x22,0x0,0x2b,0x0,0x0,0x1,0x7,0x1,0x92,0x0,0x1c,0x1,0xe,0x0,0x9, + 0xb1,0x1,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0x96,0x0,0x0,0x4,0x68,0x6,0x36,0x0,0x22,0x0,0x2b,0x0,0x0,0x1,0x7,0x1,0x93, + 0x0,0x1c,0x1,0xe,0x0,0x9,0xb1,0x1,0x2,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0x96,0x0,0x0,0x4,0x67,0x6,0x5f,0x0,0x22,0x0,0x2b, + 0x0,0x0,0x1,0x7,0x1,0x94,0x0,0x1e,0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0x96,0x0,0x0,0x4,0x67, + 0x6,0x6c,0x0,0x22,0x0,0x2b,0x0,0x0,0x1,0x7,0x1,0x95,0x0,0x32,0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff, + 0x0,0x96,0x0,0x0,0x4,0x67,0x5,0xf0,0x0,0x22,0x0,0x2b,0x0,0x0,0x1,0x7,0x1,0x97,0x0,0x19,0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0xe, + 0xb0,0x33,0x2b,0x0,0x0,0x1,0x0,0x96,0xfe,0x28,0x4,0x67,0x4,0xa3,0x0,0x3f,0x0,0x77,0x40,0xc,0x39,0x2,0x2,0x0,0x9,0x2e,0xd,0x2,0x2,0x1, + 0x2,0x4a,0x4b,0xb0,0x31,0x50,0x58,0x40,0x21,0x4,0x1,0x3,0x0,0x5,0x3,0x5,0x5f,0x8,0x1,0x0,0x0,0x9,0x59,0xa,0x1,0x9,0x9,0xe,0x4b,0x7, + 0x1,0x1,0x1,0x2,0x59,0x6,0x1,0x2,0x2,0xf,0x2,0x4c,0x1b,0x40,0x28,0x0,0x4,0x2,0x3,0x2,0x4,0x3,0x70,0x0,0x3,0x0,0x5,0x3,0x5,0x5f, + 0x8,0x1,0x0,0x0,0x9,0x59,0xa,0x1,0x9,0x9,0xe,0x4b,0x7,0x1,0x1,0x1,0x2,0x59,0x6,0x1,0x2,0x2,0xf,0x2,0x4c,0x59,0x40,0x12,0x0,0x0, + 0x0,0x3f,0x0,0x3d,0x21,0x26,0x25,0x26,0x21,0x25,0x26,0x21,0x26,0xb,0x7,0x1d,0x2b,0x0,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x23,0x3,0x21,0x32,0x16, + 0x15,0x14,0x7,0x6,0x6,0x23,0x21,0x6,0x6,0x15,0x14,0x16,0x33,0x32,0x37,0x37,0x32,0x15,0x14,0x6,0x7,0x6,0x6,0x23,0x22,0x26,0x35,0x34,0x36,0x37, + 0x23,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x21,0x13,0x21,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x21,0x4,0x4d,0x1a,0x3,0x7,0x23,0x22,0xf6,0x9e, + 0x1,0x1e,0x1b,0x1a,0x3,0x7,0x22,0x22,0xfe,0xe8,0x6f,0x69,0x22,0x1f,0x33,0x3c,0x8,0x28,0x12,0x13,0x26,0x5e,0x2e,0x4f,0x56,0x66,0x63,0xfb,0x1c,0x19, + 0x2,0x7,0x23,0x22,0x1,0xa,0x9e,0xfe,0xf6,0x1b,0x1a,0x3,0x7,0x23,0x22,0x2,0x96,0x4,0xa3,0x16,0x18,0xb,0xf,0x27,0x21,0xfc,0x7d,0x16,0x18,0xb, + 0xf,0x27,0x21,0x58,0x88,0x35,0x1d,0x1e,0xa,0x1,0x42,0x14,0x19,0x7,0xd,0x10,0x58,0x4a,0x50,0x98,0x4e,0x16,0x19,0xf,0xa,0x27,0x21,0x3,0x83,0x16, + 0x18,0xb,0xf,0x27,0x21,0x0,0x0,0x0,0xff,0xff,0x0,0x96,0x0,0x0,0x4,0x67,0x6,0x15,0x0,0x22,0x0,0x2b,0x0,0x0,0x1,0x7,0x1,0x9a,0x0,0x14, + 0x1,0x4,0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0x4,0xb0,0x33,0x2b,0x0,0x0,0x1,0x0,0x90,0xff,0xea,0x4,0x3b,0x4,0xa3,0x0,0x24,0x0,0x35,0x40,0x32, + 0x1e,0x2,0x2,0x3,0x4,0x1,0x4a,0x0,0x1,0x3,0x2,0x3,0x1,0x2,0x70,0x0,0x3,0x3,0x4,0x59,0x5,0x1,0x4,0x4,0xe,0x4b,0x0,0x2,0x2,0x0, + 0x5b,0x0,0x0,0x0,0x17,0x0,0x4c,0x0,0x0,0x0,0x24,0x0,0x22,0x23,0x24,0x17,0x27,0x6,0x7,0x18,0x2b,0x0,0x16,0x15,0x14,0x7,0x3,0x6,0x6,0x23, + 0x22,0x26,0x27,0x26,0x35,0x34,0x37,0x36,0x33,0x32,0x17,0x16,0x16,0x33,0x32,0x36,0x37,0x13,0x21,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x21,0x4,0x21, + 0x1a,0x3,0x8a,0x1e,0xda,0xb1,0x65,0xb6,0x40,0x1a,0x15,0x1b,0x1c,0xf,0x12,0x37,0x8b,0x4d,0x73,0x8b,0x14,0x77,0xfe,0x97,0x1b,0x1a,0x3,0x7,0x23,0x22, + 0x1,0xc0,0x4,0xa3,0x16,0x18,0xb,0xf,0xfc,0xf2,0xac,0xb7,0x3b,0x37,0x15,0x1a,0x19,0x1f,0x29,0xe,0x2e,0x32,0x79,0x74,0x2,0xa8,0x16,0x18,0xb,0xf, + 0x27,0x21,0xff,0xff,0x0,0x90,0xff,0xea,0x4,0xf1,0x6,0x6e,0x0,0x22,0x0,0x36,0x0,0x0,0x1,0x7,0x1,0x92,0x0,0xb4,0x1,0xe,0x0,0x9,0xb1,0x1, + 0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0x0,0x1,0x0,0x98,0xff,0xef,0x4,0xb1,0x4,0xb9,0x0,0x2a,0x0,0x3e,0x40,0xa,0x25,0x22,0x18,0xf,0x4,0x5, + 0x0,0x2,0x1,0x4a,0x4b,0xb0,0x28,0x50,0x58,0x40,0xd,0x3,0x1,0x2,0x2,0xe,0x4b,0x1,0x1,0x0,0x0,0x17,0x0,0x4c,0x1b,0x40,0x11,0x0,0x3,0x3, + 0x16,0x4b,0x0,0x2,0x2,0xe,0x4b,0x1,0x1,0x0,0x0,0x17,0x0,0x4c,0x59,0xb6,0x27,0x37,0x36,0x2b,0x4,0x7,0x18,0x2b,0x0,0x15,0x14,0x7,0x1,0x1, + 0x16,0x15,0x14,0x6,0x7,0x6,0x23,0x22,0x27,0x1,0x7,0x3,0x6,0x6,0x23,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x36,0x36,0x33,0x33,0x32,0x16,0x15,0x14, + 0x7,0x3,0x1,0x36,0x33,0x32,0x17,0x4,0xb1,0x1a,0xfe,0x1b,0x1,0x4e,0xa,0x17,0x18,0x1d,0x17,0x1c,0xd,0xfe,0xbe,0xd5,0x37,0x6,0x27,0x28,0x2,0x23, + 0x1e,0x2,0xbe,0x6,0x27,0x28,0x2,0x23,0x1e,0x2,0x5f,0x2,0x9b,0x14,0x18,0x1c,0x1d,0x4,0x8a,0x18,0x15,0x17,0xfe,0x4f,0xfd,0xc7,0x11,0x10,0x10,0x1d, + 0xe,0x11,0x17,0x2,0x25,0xbf,0xfe,0xcb,0x23,0x1e,0x15,0x18,0x4,0x10,0x4,0x35,0x23,0x1e,0x15,0x18,0x4,0x10,0xfd,0xe7,0x2,0x54,0x12,0x16,0xff,0xff, + 0x0,0x98,0xfd,0xf3,0x4,0xb1,0x4,0xb9,0x0,0x22,0x0,0x38,0x0,0x0,0x0,0x3,0x1,0x7e,0x4,0xee,0x0,0x0,0x0,0x1,0x0,0xb8,0x0,0x0,0x3,0xd9, + 0x4,0xad,0x0,0x19,0x0,0x2a,0x40,0x27,0x2,0x1,0x0,0x2,0x12,0x9,0x2,0x1,0x0,0x2,0x4a,0x3,0x1,0x2,0x2,0xe,0x4b,0x0,0x0,0x0,0x1,0x5a, + 0x0,0x1,0x1,0xf,0x1,0x4c,0x0,0x0,0x0,0x19,0x0,0x17,0x36,0x25,0x4,0x7,0x16,0x2b,0x0,0x16,0x15,0x14,0x7,0x3,0x21,0x32,0x16,0x15,0x14,0x7, + 0x6,0x6,0x23,0x21,0x22,0x26,0x35,0x34,0x37,0x13,0x36,0x36,0x33,0x33,0x1,0xf0,0x1e,0x2,0xae,0x2,0x46,0x1b,0x1a,0x3,0x7,0x22,0x22,0xfd,0x65,0x1d, + 0x1b,0x2,0xbc,0x6,0x27,0x28,0x2,0x4,0xad,0x15,0x18,0x4,0x10,0xfc,0x24,0x16,0x18,0xb,0xf,0x27,0x21,0x15,0x19,0xe,0x8,0x4,0x28,0x23,0x1e,0x0, + 0xff,0xff,0x0,0xb8,0x0,0x0,0x3,0xd9,0x6,0x6c,0x0,0x22,0x0,0x3a,0x0,0x0,0x1,0x7,0x1,0x80,0xff,0x6a,0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8, + 0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0xb8,0x0,0x0,0x4,0x70,0x4,0xad,0x0,0x22,0x0,0x3a,0x0,0x0,0x1,0x7,0x1,0x81,0x1,0xb1,0xff,0x83, + 0x0,0x9,0xb1,0x1,0x1,0xb8,0xff,0x83,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0xb8,0xfd,0xf3,0x3,0xd9,0x4,0xad,0x0,0x22,0x0,0x3a,0x0,0x0,0x0,0x3, + 0x1,0x7e,0x4,0xe0,0x0,0x0,0xff,0xff,0x0,0xb8,0x0,0x0,0x3,0xd9,0x4,0xad,0x0,0x22,0x0,0x3a,0x0,0x0,0x1,0x7,0x1,0x94,0x0,0x29,0xfe,0x2, + 0x0,0x9,0xb1,0x1,0x1,0xb8,0xfe,0x2,0xb0,0x33,0x2b,0x0,0x0,0x1,0x0,0x4e,0x0,0x0,0x3,0xd9,0x4,0xad,0x0,0x2f,0x0,0x40,0x40,0x3d,0x20,0x1, + 0x3,0x2,0x23,0xe,0x2,0x1,0x3,0xb,0x2,0x2,0x0,0x4,0x3,0x4a,0x0,0x3,0x2,0x1,0x2,0x3,0x1,0x70,0x0,0x1,0x4,0x2,0x1,0x4,0x6e,0x0, + 0x2,0x2,0xe,0x4b,0x5,0x1,0x4,0x4,0x0,0x5a,0x0,0x0,0x0,0xf,0x0,0x4c,0x0,0x0,0x0,0x2f,0x0,0x2e,0x27,0x39,0x27,0x36,0x6,0x7,0x18,0x2b, + 0x24,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x21,0x22,0x26,0x35,0x34,0x37,0x13,0x7,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x37,0x13,0x36,0x36,0x33,0x33, + 0x32,0x16,0x15,0x14,0x7,0x3,0x37,0x36,0x33,0x32,0x17,0x16,0x15,0x14,0x7,0x5,0x3,0x21,0x3,0xbf,0x1a,0x3,0x7,0x22,0x22,0xfd,0xa1,0x1d,0x1b,0x2, + 0x42,0x7d,0x18,0x10,0x1a,0x19,0x12,0x20,0xeb,0x59,0x6,0x27,0x28,0x2,0x23,0x1e,0x2,0x44,0xa3,0x18,0x10,0x1a,0x19,0x12,0x20,0xfe,0xef,0x49,0x2,0xa, + 0x90,0x16,0x18,0xb,0xf,0x27,0x21,0x15,0x19,0xe,0x8,0x1,0x75,0x55,0x10,0x25,0x1b,0x12,0x19,0x15,0xa0,0x1,0xf8,0x23,0x1e,0x15,0x18,0x4,0x10,0xfe, + 0x7c,0x6f,0x10,0x25,0x1b,0x12,0x19,0x15,0xba,0xfe,0x63,0x0,0x0,0x0,0x0,0x1,0x0,0x52,0xff,0xf6,0x4,0x9f,0x4,0xad,0x0,0x2f,0x0,0x34,0x40,0x31, + 0x2a,0x17,0xf,0x2,0x4,0x1,0x3,0x1f,0xc,0x2,0x0,0x1,0x2,0x4a,0x0,0x1,0x3,0x0,0x3,0x1,0x0,0x70,0x5,0x4,0x2,0x3,0x3,0xe,0x4b,0x2, + 0x1,0x0,0x0,0xf,0x0,0x4c,0x0,0x0,0x0,0x2f,0x0,0x2d,0x37,0x36,0x28,0x37,0x6,0x7,0x18,0x2b,0x0,0x16,0x15,0x14,0x7,0x3,0x6,0x6,0x23,0x23, + 0x22,0x26,0x35,0x34,0x37,0x13,0x1,0x6,0x6,0x23,0x22,0x26,0x27,0x3,0x3,0x6,0x6,0x23,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x36,0x36,0x33,0x33,0x32, + 0x16,0x17,0x13,0x1,0x36,0x36,0x33,0x33,0x4,0x7b,0x24,0x1,0xa0,0x5,0x25,0x28,0x2,0x24,0x1f,0x2,0x7b,0xfe,0xe2,0x17,0x2c,0x22,0x22,0x27,0xa,0x80, + 0xa5,0x7,0x23,0x29,0x2,0x22,0x20,0x2,0xdc,0x7,0x28,0x25,0x2,0x23,0x24,0xa,0xa5,0x1,0x6d,0x16,0x35,0x23,0x2,0x4,0xad,0x17,0x1b,0xa,0x5,0xfb, + 0xcb,0x23,0x1e,0x16,0x19,0x4,0xe,0x3,0x2d,0xfe,0x42,0x23,0x1d,0x1d,0x23,0x1,0xbe,0xfc,0xd3,0x24,0x1d,0x16,0x18,0x5,0xe,0x4,0x35,0x24,0x1d,0x1e, + 0x23,0xfd,0xc6,0x2,0x3a,0x22,0x1f,0x0,0x0,0x1,0x0,0x70,0xff,0xea,0x4,0xa0,0x4,0xad,0x0,0x25,0x0,0x57,0x4b,0xb0,0x28,0x50,0x58,0x40,0x9,0x1f, + 0x15,0xc,0x2,0x4,0x0,0x2,0x1,0x4a,0x1b,0x40,0x9,0x1f,0x15,0xc,0x2,0x4,0x1,0x2,0x1,0x4a,0x59,0x4b,0xb0,0x28,0x50,0x58,0x40,0xe,0x4,0x3, + 0x2,0x2,0x2,0xe,0x4b,0x1,0x1,0x0,0x0,0x17,0x0,0x4c,0x1b,0x40,0x12,0x4,0x3,0x2,0x2,0x2,0xe,0x4b,0x0,0x1,0x1,0xf,0x4b,0x0,0x0,0x0, + 0x17,0x0,0x4c,0x59,0x40,0xc,0x0,0x0,0x0,0x25,0x0,0x23,0x37,0x37,0x27,0x5,0x7,0x17,0x2b,0x0,0x16,0x15,0x14,0x7,0x3,0x6,0x6,0x23,0x22,0x26, + 0x27,0x1,0x23,0x3,0x6,0x6,0x23,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x36,0x36,0x33,0x33,0x32,0x17,0x1,0x33,0x13,0x36,0x36,0x33,0x33,0x4,0x83,0x1d, + 0x2,0xc1,0x5,0x2a,0x26,0x1b,0x23,0xd,0xfe,0x66,0x4,0x9b,0x6,0x27,0x26,0x2,0x22,0x1d,0x2,0xbe,0x6,0x27,0x2a,0x1b,0x2d,0xc,0x1,0x97,0x4,0x96, + 0x6,0x27,0x26,0x2,0x4,0xad,0x15,0x17,0x5,0x10,0xfb,0xb9,0x1b,0x20,0x1c,0x1b,0x3,0x82,0xfc,0x94,0x23,0x1e,0x15,0x17,0x5,0x10,0x4,0x35,0x23,0x1e, + 0x1c,0xfc,0x84,0x3,0x57,0x23,0x1e,0x0,0x0,0x0,0xff,0xff,0x0,0x70,0xff,0xea,0x4,0xa0,0x6,0x6c,0x0,0x22,0x0,0x41,0x0,0x0,0x1,0x7,0x1,0x80, + 0x0,0x1e,0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0x70,0xff,0xea,0x4,0xa0,0x6,0x68,0x0,0x22,0x0,0x41, + 0x0,0x0,0x1,0x7,0x1,0x83,0x0,0x1e,0x1,0x4,0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0x4,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0x70,0xfd,0xf3,0x4,0xa0, + 0x4,0xad,0x0,0x22,0x0,0x41,0x0,0x0,0x0,0x3,0x1,0x7e,0x4,0xda,0x0,0x0,0x0,0x1,0x0,0x70,0xfe,0x66,0x4,0xa0,0x4,0xad,0x0,0x33,0x0,0x3a, + 0x40,0x37,0x2d,0x23,0x1a,0x19,0x2,0x5,0x3,0x4,0x1,0x4a,0x0,0x1,0x3,0x2,0x3,0x1,0x2,0x70,0x6,0x5,0x2,0x4,0x4,0xe,0x4b,0x0,0x3,0x3, + 0xf,0x4b,0x0,0x2,0x2,0x0,0x5b,0x0,0x0,0x0,0x13,0x0,0x4c,0x0,0x0,0x0,0x33,0x0,0x31,0x37,0x38,0x23,0x17,0x27,0x7,0x7,0x19,0x2b,0x0,0x16, + 0x15,0x14,0x7,0x3,0x6,0x6,0x23,0x22,0x26,0x27,0x26,0x35,0x34,0x37,0x36,0x33,0x32,0x17,0x16,0x33,0x32,0x36,0x37,0x37,0x1,0x23,0x3,0x6,0x6,0x23, + 0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x36,0x36,0x33,0x33,0x32,0x17,0x1,0x33,0x13,0x36,0x36,0x33,0x33,0x4,0x83,0x1d,0x2,0xdf,0x18,0x9d,0x83,0x3a,0x73, + 0x31,0x21,0xd,0x16,0x26,0xb,0xc,0x59,0x51,0x40,0x4d,0xf,0x16,0xfe,0x5f,0x4,0x9b,0x6,0x27,0x26,0x2,0x22,0x1d,0x2,0xbe,0x6,0x27,0x2a,0x1b,0x2d, + 0xc,0x1,0x97,0x4,0x96,0x6,0x27,0x26,0x2,0x4,0xad,0x15,0x17,0x5,0x10,0xfb,0xd,0x87,0x8c,0x1b,0x17,0x10,0x1e,0x15,0x1b,0x2f,0x5,0x28,0x4b,0x52, + 0x7d,0x3,0x91,0xfc,0x94,0x23,0x1e,0x15,0x17,0x5,0x10,0x4,0x35,0x23,0x1e,0x1c,0xfc,0x84,0x3,0x57,0x23,0x1e,0xff,0xff,0x0,0x70,0xff,0xea,0x4,0xa0, + 0x6,0x15,0x0,0x22,0x0,0x41,0x0,0x0,0x1,0x7,0x1,0x9a,0x0,0x20,0x1,0x4,0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0x4,0xb0,0x33,0x2b,0x0,0x0,0x2, + 0x0,0x72,0xff,0xea,0x4,0x9f,0x4,0xb9,0x0,0x13,0x0,0x27,0x0,0x2c,0x40,0x29,0x5,0x1,0x3,0x3,0x1,0x5b,0x4,0x1,0x1,0x1,0x16,0x4b,0x0,0x2, + 0x2,0x0,0x5b,0x0,0x0,0x0,0x17,0x0,0x4c,0x14,0x14,0x0,0x0,0x14,0x27,0x14,0x26,0x1e,0x1c,0x0,0x13,0x0,0x12,0x28,0x6,0x7,0x15,0x2b,0x0,0x16, + 0x16,0x15,0x14,0x2,0x7,0x6,0x6,0x23,0x22,0x26,0x26,0x35,0x34,0x12,0x37,0x36,0x36,0x33,0x6,0x6,0x7,0x6,0x6,0x15,0x14,0x16,0x16,0x33,0x32,0x36, + 0x37,0x36,0x36,0x35,0x34,0x26,0x26,0x23,0x3,0x6c,0xc8,0x6b,0x4f,0x4c,0x53,0xf0,0x96,0x86,0xc8,0x6b,0x4f,0x4c,0x53,0xf0,0x96,0x74,0xaa,0x3d,0x3e,0x42, + 0x48,0x86,0x5d,0x69,0xaa,0x3d,0x3e,0x42,0x48,0x86,0x5d,0x4,0xb9,0x77,0xda,0x90,0x87,0xfe,0xfa,0x69,0x74,0x84,0x77,0xda,0x90,0x87,0x1,0x6,0x69,0x74, + 0x84,0x94,0x62,0x55,0x56,0xda,0x6d,0x69,0x99,0x51,0x62,0x55,0x56,0xda,0x6d,0x69,0x99,0x51,0x0,0x0,0xff,0xff,0x0,0x72,0xff,0xea,0x4,0x9f,0x6,0x6c, + 0x0,0x22,0x0,0x47,0x0,0x0,0x1,0x7,0x1,0x80,0x0,0x6,0x1,0xe,0x0,0x9,0xb1,0x2,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0x72, + 0xff,0xea,0x4,0xa3,0x6,0x5c,0x0,0x22,0x0,0x47,0x0,0x0,0x1,0x7,0x1,0x82,0x0,0x1e,0x1,0xe,0x0,0x9,0xb1,0x2,0x1,0xb8,0x1,0xe,0xb0,0x33, + 0x2b,0x0,0xff,0xff,0x0,0x72,0xff,0xea,0x4,0x9f,0x6,0x6e,0x0,0x22,0x0,0x47,0x0,0x0,0x1,0x7,0x1,0x92,0x0,0x1c,0x1,0xe,0x0,0x9,0xb1,0x2, + 0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0x72,0xff,0xea,0x4,0x9f,0x6,0x36,0x0,0x22,0x0,0x47,0x0,0x0,0x1,0x7,0x1,0x93,0x0,0x26, + 0x1,0xe,0x0,0x9,0xb1,0x2,0x2,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0x72,0xff,0xea,0x4,0x9f,0x6,0x6c,0x0,0x22,0x0,0x47,0x0,0x0, + 0x1,0x7,0x1,0x95,0x0,0x3c,0x1,0xe,0x0,0x9,0xb1,0x2,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0x72,0xff,0xea,0x4,0xfa,0x6,0x77, + 0x0,0x22,0x0,0x47,0x0,0x0,0x1,0x7,0x1,0x96,0x0,0x1e,0x1,0xe,0x0,0x9,0xb1,0x2,0x2,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0x72, + 0xff,0xea,0x4,0x9f,0x5,0xf0,0x0,0x22,0x0,0x47,0x0,0x0,0x1,0x7,0x1,0x97,0x0,0x1e,0x1,0xe,0x0,0x9,0xb1,0x2,0x1,0xb8,0x1,0xe,0xb0,0x33, + 0x2b,0x0,0x0,0x3,0x0,0x3e,0xff,0xc2,0x4,0xd3,0x4,0xe3,0x0,0x28,0x0,0x33,0x0,0x3e,0x0,0x72,0x40,0x13,0x23,0x1,0x2,0x4,0x2,0x3e,0x31,0x30, + 0x18,0x4,0x5,0x5,0x4,0xe,0x1,0x0,0x5,0x3,0x4a,0x4b,0xb0,0x19,0x50,0x58,0x40,0x20,0x0,0x3,0x3,0x16,0x4b,0x6,0x1,0x4,0x4,0x2,0x5b,0x0, + 0x2,0x2,0x16,0x4b,0x0,0x5,0x5,0x0,0x5b,0x0,0x0,0x0,0x17,0x4b,0x0,0x1,0x1,0x17,0x1,0x4c,0x1b,0x40,0x20,0x0,0x3,0x2,0x3,0x72,0x0,0x1, + 0x0,0x1,0x73,0x6,0x1,0x4,0x4,0x2,0x5b,0x0,0x2,0x2,0x16,0x4b,0x0,0x5,0x5,0x0,0x5b,0x0,0x0,0x0,0x17,0x0,0x4c,0x59,0x40,0xf,0x29,0x29, + 0x37,0x35,0x29,0x33,0x29,0x32,0x24,0x2d,0x23,0x2b,0x7,0x7,0x18,0x2b,0x0,0x15,0x14,0x7,0x7,0x16,0x15,0x14,0x2,0x7,0x6,0x6,0x23,0x22,0x27,0x7, + 0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x37,0x26,0x35,0x34,0x12,0x37,0x36,0x36,0x33,0x32,0x16,0x17,0x37,0x36,0x33,0x32,0x17,0x4,0x6,0x7,0x6,0x6, + 0x15,0x14,0x17,0x1,0x26,0x23,0x1,0x16,0x33,0x32,0x36,0x37,0x36,0x36,0x35,0x34,0x27,0x4,0xd3,0x15,0x69,0x4a,0x4f,0x4c,0x53,0xf0,0x96,0xa3,0x70,0x53, + 0x1d,0x1c,0x16,0x18,0x20,0x14,0x69,0x49,0x4f,0x4c,0x53,0xf0,0x96,0x51,0x8a,0x36,0x54,0x1d,0x1d,0x17,0x18,0xfd,0xbe,0xaa,0x3d,0x3e,0x42,0x1e,0x2,0x68, + 0x49,0x6d,0xfe,0xa3,0x4a,0x6e,0x69,0xaa,0x3d,0x3e,0x42,0x1f,0x4,0xb7,0x18,0x12,0x1a,0x79,0x78,0xaa,0x87,0xfe,0xfa,0x69,0x74,0x84,0x57,0x5f,0x20,0x13, + 0x18,0x1a,0x14,0x17,0x79,0x78,0xa8,0x87,0x1,0x6,0x69,0x74,0x84,0x2c,0x2a,0x60,0x20,0x13,0xab,0x62,0x55,0x56,0xda,0x6d,0x60,0x48,0x2,0xc4,0x38,0xfc, + 0x92,0x39,0x62,0x55,0x56,0xda,0x6d,0x60,0x4a,0x0,0xff,0xff,0x0,0x72,0xff,0xea,0x4,0x9f,0x6,0x15,0x0,0x22,0x0,0x47,0x0,0x0,0x1,0x7,0x1,0x9a, + 0x0,0x1a,0x1,0x4,0x0,0x9,0xb1,0x2,0x1,0xb8,0x1,0x4,0xb0,0x33,0x2b,0x0,0x0,0x2,0x0,0x6b,0x0,0x0,0x4,0xe2,0x4,0xa3,0x0,0x29,0x0,0x33, + 0x0,0x43,0x40,0x40,0x2,0x1,0x0,0x5,0xd,0x1,0x2,0x1,0x18,0x1,0x4,0x3,0x3,0x4a,0x0,0x1,0x0,0x2,0x3,0x1,0x2,0x61,0x6,0x1,0x0,0x0, + 0x5,0x59,0x8,0x1,0x5,0x5,0xe,0x4b,0x7,0x1,0x3,0x3,0x4,0x59,0x0,0x4,0x4,0xf,0x4,0x4c,0x0,0x0,0x33,0x32,0x2b,0x2a,0x0,0x29,0x0,0x27, + 0x36,0x21,0x26,0x21,0x26,0x9,0x7,0x19,0x2b,0x0,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x21,0x3,0x33,0x32,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x23,0x3, + 0x21,0x32,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x21,0x22,0x26,0x26,0x35,0x34,0x36,0x37,0x36,0x36,0x33,0x21,0x5,0x6,0x6,0x7,0x6,0x6,0x15,0x14,0x16, + 0x17,0x4,0xc8,0x1a,0x3,0x7,0x23,0x22,0xfe,0xbe,0x3f,0xf7,0x1b,0x1a,0x3,0x7,0x22,0x22,0xf7,0x46,0x1,0x56,0x1b,0x1a,0x3,0x7,0x22,0x22,0xfe,0x26, + 0x84,0xc0,0x67,0x42,0x42,0x4f,0xf2,0xa3,0x1,0xda,0xfe,0x10,0x6a,0xa5,0x39,0x38,0x39,0x93,0x84,0x4,0xa3,0x16,0x18,0xb,0xf,0x27,0x21,0xfe,0x99,0x15, + 0x18,0xc,0xf,0x27,0x21,0xfe,0x74,0x16,0x18,0xb,0xf,0x27,0x21,0x6d,0xcc,0x8d,0x76,0xf7,0x66,0x7b,0x8f,0x89,0x3,0x64,0x56,0x54,0xd4,0x68,0x98,0xa8, + 0x5,0x0,0x0,0x0,0x0,0x2,0x0,0xac,0xff,0xf6,0x4,0xa3,0x4,0xa3,0x0,0x17,0x0,0x21,0x0,0x36,0x40,0x33,0x10,0x1,0x1,0x0,0x1,0x4a,0x0,0x3, + 0x0,0x0,0x1,0x3,0x0,0x61,0x6,0x1,0x4,0x4,0x2,0x59,0x5,0x1,0x2,0x2,0xe,0x4b,0x0,0x1,0x1,0xf,0x1,0x4c,0x18,0x18,0x0,0x0,0x18,0x21, + 0x18,0x20,0x1b,0x19,0x0,0x17,0x0,0x15,0x33,0x26,0x7,0x7,0x16,0x2b,0x0,0x16,0x16,0x15,0x14,0x6,0x6,0x23,0x21,0x3,0x6,0x6,0x23,0x23,0x22,0x26, + 0x35,0x34,0x37,0x13,0x36,0x36,0x33,0x21,0x5,0x3,0x21,0x32,0x36,0x36,0x35,0x34,0x26,0x23,0x3,0x9b,0xae,0x5a,0x6a,0xe4,0xac,0xfe,0xd7,0x3c,0x6,0x27, + 0x28,0x2,0x23,0x1e,0x2,0xbc,0x7,0x20,0x22,0x1,0x6d,0xfe,0xd2,0x59,0x1,0xf,0x7b,0x9e,0x48,0x85,0x85,0x4,0xa3,0x52,0x94,0x62,0x77,0xd4,0x87,0xfe, + 0xae,0x23,0x1e,0x15,0x18,0x4,0x10,0x4,0x28,0x26,0x1e,0x90,0xfe,0x6,0x57,0x8b,0x51,0x61,0x66,0x0,0x0,0x0,0x0,0x2,0x0,0xac,0xff,0xf6,0x4,0x4a, + 0x4,0xad,0x0,0x1e,0x0,0x28,0x0,0x6a,0x40,0xa,0x1a,0x1,0x3,0x2,0x10,0x1,0x1,0x0,0x2,0x4a,0x4b,0xb0,0x2f,0x50,0x58,0x40,0x1f,0x7,0x1,0x5, + 0x0,0x0,0x1,0x5,0x0,0x61,0x0,0x2,0x2,0xe,0x4b,0x0,0x4,0x4,0x3,0x59,0x6,0x1,0x3,0x3,0x11,0x4b,0x0,0x1,0x1,0xf,0x1,0x4c,0x1b,0x40, + 0x1d,0x6,0x1,0x3,0x0,0x4,0x5,0x3,0x4,0x62,0x7,0x1,0x5,0x0,0x0,0x1,0x5,0x0,0x61,0x0,0x2,0x2,0xe,0x4b,0x0,0x1,0x1,0xf,0x1,0x4c, + 0x59,0x40,0x14,0x1f,0x1f,0x0,0x0,0x1f,0x28,0x1f,0x27,0x26,0x24,0x0,0x1e,0x0,0x1d,0x37,0x33,0x26,0x8,0x7,0x17,0x2b,0x0,0x16,0x16,0x15,0x14,0x6, + 0x6,0x23,0x21,0x7,0x6,0x6,0x23,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x36,0x36,0x33,0x33,0x32,0x16,0x15,0x14,0x7,0x7,0x21,0x2,0x36,0x36,0x35,0x34, + 0x26,0x23,0x21,0x3,0x21,0x3,0x72,0x90,0x48,0x51,0xba,0x92,0xfe,0xba,0x23,0x6,0x27,0x28,0x2,0x23,0x1e,0x2,0xbe,0x6,0x27,0x28,0x2,0x23,0x1e,0x2, + 0x21,0x1,0x2b,0x8,0x75,0x33,0x61,0x62,0xfe,0xdf,0x47,0x1,0x25,0x3,0xb2,0x4c,0x84,0x54,0x5d,0xb7,0x7c,0xc7,0x23,0x1e,0x15,0x18,0x4,0x10,0x4,0x35, + 0x23,0x1e,0x15,0x18,0x4,0x10,0xba,0xfd,0xdc,0x49,0x6e,0x3c,0x4a,0x57,0xfe,0x6c,0x0,0x0,0x0,0x2,0x0,0x72,0xff,0x58,0x4,0x9f,0x4,0xb9,0x0,0x20, + 0x0,0x40,0x0,0x43,0x40,0x40,0x3e,0x33,0x2,0x4,0x5,0x14,0x8,0x2,0x1,0x4,0x2,0x4a,0x0,0x5,0x3,0x4,0x3,0x5,0x4,0x70,0x0,0x0,0x1,0x0, + 0x73,0x0,0x3,0x3,0x2,0x5b,0x6,0x1,0x2,0x2,0x16,0x4b,0x0,0x4,0x4,0x1,0x5b,0x0,0x1,0x1,0x17,0x1,0x4c,0x0,0x0,0x3c,0x3a,0x32,0x30,0x28, + 0x26,0x0,0x20,0x0,0x1f,0x24,0x2f,0x7,0x7,0x16,0x2b,0x0,0x16,0x16,0x15,0x14,0x2,0x7,0x6,0x7,0x17,0x16,0x15,0x14,0x6,0x7,0x6,0x23,0x22,0x26, + 0x27,0x27,0x6,0x23,0x22,0x26,0x26,0x35,0x34,0x12,0x37,0x36,0x36,0x33,0x12,0x36,0x35,0x34,0x26,0x26,0x23,0x22,0x6,0x7,0x6,0x6,0x15,0x14,0x16,0x16, + 0x33,0x32,0x37,0x27,0x26,0x35,0x34,0x36,0x37,0x36,0x33,0x32,0x17,0x17,0x36,0x37,0x3,0x6c,0xc8,0x6b,0x4f,0x4c,0x48,0x61,0x3b,0x9,0x18,0x1a,0x1b,0x12, + 0x11,0x18,0x9,0x3b,0x4e,0x5a,0x86,0xc8,0x6b,0x4f,0x4c,0x53,0xf0,0x96,0xde,0x42,0x48,0x86,0x5d,0x69,0xaa,0x3d,0x3e,0x42,0x48,0x86,0x5d,0x30,0x2f,0x50, + 0x9,0x18,0x1a,0x1d,0x10,0x1f,0x13,0x4f,0x3a,0x30,0x4,0xb9,0x77,0xda,0x90,0x87,0xfe,0xfa,0x69,0x64,0x3e,0x82,0x14,0x10,0x10,0x1a,0xc,0xc,0x13,0x15, + 0x82,0x18,0x77,0xda,0x90,0x87,0x1,0x6,0x69,0x74,0x84,0xfc,0xd2,0xda,0x6d,0x69,0x99,0x51,0x62,0x55,0x56,0xda,0x6d,0x69,0x99,0x51,0xb,0xae,0x14,0xf, + 0x11,0x1b,0xc,0xd,0x2a,0xac,0x29,0x43,0x0,0x2,0x0,0xac,0xff,0xf1,0x4,0x5a,0x4,0xa3,0x0,0x21,0x0,0x2a,0x0,0x35,0x40,0x32,0x2,0x1,0x1,0x5, + 0x16,0x1,0x0,0x1,0x2,0x4a,0x6,0x1,0x5,0x0,0x1,0x0,0x5,0x1,0x61,0x0,0x4,0x4,0x3,0x59,0x0,0x3,0x3,0xe,0x4b,0x2,0x1,0x0,0x0,0x17, + 0x0,0x4c,0x22,0x22,0x22,0x2a,0x22,0x29,0x28,0x37,0x33,0x12,0x29,0x7,0x7,0x19,0x2b,0x0,0x6,0x7,0x13,0x16,0x15,0x14,0x6,0x7,0x6,0x23,0x22,0x27, + 0x3,0x23,0x3,0x6,0x6,0x23,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x36,0x36,0x33,0x21,0x32,0x16,0x16,0x15,0x0,0x36,0x35,0x34,0x26,0x23,0x23,0x3,0x33, + 0x4,0x5a,0xa6,0xa5,0xc3,0xa,0x17,0x1a,0x21,0x13,0x19,0xe,0xda,0xe8,0x4a,0x6,0x27,0x28,0x2,0x23,0x1e,0x2,0xbc,0x7,0x20,0x22,0x1,0x31,0x76,0xa9, + 0x57,0xfe,0xbe,0xa8,0x7d,0x76,0xdb,0x4b,0xd0,0x2,0xe5,0xcb,0x2e,0xfe,0x6a,0x16,0xd,0xf,0x19,0xb,0xf,0x1d,0x1,0xcb,0xfe,0x5e,0x23,0x1e,0x15,0x18, + 0x4,0x10,0x4,0x28,0x26,0x1e,0x4b,0x87,0x59,0xfe,0xf1,0x87,0x78,0x53,0x58,0xfe,0x56,0x0,0x0,0x0,0xff,0xff,0x0,0xac,0xff,0xf1,0x4,0x5a,0x6,0x6c, + 0x0,0x22,0x0,0x55,0x0,0x0,0x1,0x7,0x1,0x80,0x0,0xa,0x1,0xe,0x0,0x9,0xb1,0x2,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0xac, + 0xff,0xf1,0x4,0x7b,0x6,0x68,0x0,0x22,0x0,0x55,0x0,0x0,0x1,0x7,0x1,0x83,0x0,0x19,0x1,0x4,0x0,0x9,0xb1,0x2,0x1,0xb8,0x1,0x4,0xb0,0x33, + 0x2b,0x0,0xff,0xff,0x0,0xac,0xfd,0xf3,0x4,0x5a,0x4,0xa3,0x0,0x22,0x0,0x55,0x0,0x0,0x0,0x3,0x1,0x7e,0x4,0xea,0x0,0x0,0x0,0x1,0x0,0x7f, + 0xff,0xea,0x4,0x70,0x4,0xb9,0x0,0x40,0x0,0x39,0x40,0x36,0x0,0x0,0x1,0x3,0x1,0x0,0x3,0x70,0x0,0x3,0x4,0x1,0x3,0x4,0x6e,0x0,0x1,0x1, + 0x5,0x5b,0x6,0x1,0x5,0x5,0x16,0x4b,0x0,0x4,0x4,0x2,0x5b,0x0,0x2,0x2,0x17,0x2,0x4c,0x0,0x0,0x0,0x40,0x0,0x3f,0x2f,0x2d,0x29,0x28,0x21, + 0x1f,0x24,0x16,0x7,0x7,0x16,0x2b,0x0,0x17,0x16,0x15,0x14,0x7,0x6,0x23,0x22,0x27,0x26,0x26,0x23,0x22,0x6,0x6,0x15,0x14,0x16,0x17,0x16,0x16,0x17, + 0x16,0x16,0x17,0x16,0x16,0x15,0x14,0x6,0x6,0x23,0x22,0x26,0x27,0x26,0x35,0x34,0x37,0x36,0x33,0x32,0x17,0x16,0x16,0x33,0x32,0x36,0x36,0x35,0x34,0x27, + 0x26,0x26,0x27,0x26,0x26,0x27,0x26,0x35,0x34,0x36,0x36,0x33,0x3,0xa4,0xab,0x21,0x11,0x1a,0x1f,0x10,0xd,0x46,0xb7,0x5f,0x59,0x7d,0x3f,0x30,0x33,0x1e, + 0x53,0x29,0x55,0x6e,0x32,0x47,0x50,0x61,0xdb,0xad,0x73,0xd9,0x4f,0x1e,0x12,0x1b,0x1e,0xf,0xd,0x46,0xba,0x62,0x66,0x8d,0x46,0x5c,0x23,0x56,0x49,0x43, + 0x65,0x2b,0x98,0x64,0xcd,0x97,0x4,0xb9,0x64,0x14,0x1d,0x14,0x20,0x30,0x9,0x2a,0x32,0x3d,0x62,0x37,0x2c,0x35,0x12,0xa,0x13,0x9,0x12,0x1d,0x17,0x20, + 0x7e,0x56,0x66,0xb6,0x76,0x3d,0x37,0x15,0x1d,0x16,0x1f,0x2d,0xa,0x32,0x38,0x3f,0x6b,0x41,0x5c,0x2a,0x10,0x17,0x10,0xe,0x1b,0x12,0x41,0x9f,0x5b,0xad, + 0x70,0x0,0x0,0x0,0xff,0xff,0x0,0x7f,0xff,0xea,0x4,0x70,0x6,0x6c,0x0,0x22,0x0,0x59,0x0,0x0,0x1,0x7,0x1,0x80,0x0,0xa,0x1,0xe,0x0,0x9, + 0xb1,0x1,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0x7f,0xff,0xea,0x4,0x71,0x6,0x72,0x0,0x22,0x0,0x59,0x0,0x0,0x1,0x7,0x1,0x83, + 0x0,0xf,0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0x0,0x1,0x0,0x7f,0xfe,0x28,0x4,0x70,0x4,0xb9,0x0,0x63,0x0,0x5f, + 0x40,0x5c,0x41,0x1,0x2,0x9,0x3e,0x1,0x7,0x3,0x2,0x4a,0x0,0x0,0x1,0x8,0x1,0x0,0x8,0x70,0x0,0x8,0x9,0x1,0x8,0x9,0x6e,0x0,0x5,0x7, + 0x6,0x7,0x5,0x6,0x70,0x0,0x3,0x0,0x7,0x5,0x3,0x7,0x63,0x0,0x6,0x0,0x4,0x6,0x4,0x5f,0x0,0x1,0x1,0xa,0x5b,0x0,0xa,0xa,0x16,0x4b, + 0x0,0x9,0x9,0x2,0x5b,0x0,0x2,0x2,0x17,0x2,0x4c,0x62,0x60,0x50,0x4e,0x4a,0x49,0x3d,0x3a,0x37,0x35,0x32,0x30,0x28,0x26,0x20,0x1f,0x1e,0x1d,0x24, + 0x14,0xb,0x7,0x16,0x2b,0x0,0x15,0x14,0x7,0x6,0x23,0x22,0x27,0x26,0x26,0x23,0x22,0x6,0x6,0x15,0x14,0x16,0x17,0x16,0x16,0x17,0x16,0x16,0x17,0x16, + 0x16,0x15,0x14,0x6,0x6,0x7,0x7,0x36,0x16,0x15,0x14,0x6,0x7,0x6,0x23,0x22,0x26,0x27,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x32,0x17,0x16,0x16,0x33, + 0x32,0x36,0x35,0x34,0x23,0x7,0x22,0x35,0x34,0x37,0x37,0x26,0x26,0x27,0x26,0x35,0x34,0x37,0x36,0x33,0x32,0x17,0x16,0x16,0x33,0x32,0x36,0x36,0x35,0x34, + 0x27,0x26,0x26,0x27,0x26,0x26,0x27,0x26,0x35,0x34,0x36,0x36,0x33,0x32,0x17,0x4,0x70,0x11,0x1a,0x1f,0x10,0xd,0x46,0xb7,0x5f,0x59,0x7d,0x3f,0x30,0x33, + 0x1e,0x53,0x29,0x55,0x6e,0x32,0x47,0x50,0x5b,0xcd,0xa2,0x10,0x58,0x5a,0x1c,0x1a,0x45,0x97,0x44,0x7c,0x2e,0x11,0xa,0x9,0x19,0xe,0x7,0x4,0x26,0x64, + 0x31,0x3a,0x3a,0x67,0x23,0x1d,0x1,0x1b,0x58,0xa0,0x3c,0x1e,0x12,0x1b,0x1e,0xf,0xd,0x46,0xba,0x62,0x66,0x8d,0x46,0x5c,0x23,0x56,0x49,0x43,0x65,0x2b, + 0x98,0x64,0xcd,0x97,0xe4,0xab,0x4,0x41,0x1d,0x14,0x20,0x30,0x9,0x2a,0x32,0x3d,0x62,0x37,0x2c,0x35,0x12,0xa,0x13,0x9,0x12,0x1d,0x17,0x20,0x7e,0x56, + 0x62,0xb2,0x77,0x6,0x5d,0x2,0x52,0x40,0x22,0x4f,0x1b,0x4a,0x18,0x18,0x8,0x18,0x15,0x11,0x11,0x15,0x2,0xf,0x13,0x2d,0x24,0x37,0x1,0x1c,0x9,0x5, + 0x9f,0xb,0x39,0x2a,0x15,0x1d,0x16,0x1f,0x2d,0xa,0x32,0x38,0x3f,0x6b,0x41,0x5c,0x2a,0x10,0x17,0x10,0xe,0x1b,0x12,0x41,0x9f,0x5b,0xad,0x70,0x64,0x0, + 0x0,0x0,0xff,0xff,0x0,0x7f,0xff,0xea,0x4,0x70,0x6,0x6e,0x0,0x22,0x0,0x59,0x0,0x0,0x1,0x7,0x1,0x92,0x0,0x24,0x1,0xe,0x0,0x9,0xb1,0x1, + 0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0x7f,0xfd,0xf3,0x4,0x70,0x4,0xb9,0x0,0x22,0x0,0x59,0x0,0x0,0x0,0x3,0x1,0x7e,0x4,0xd6, + 0x0,0x0,0x0,0x1,0x0,0xeb,0xff,0xf6,0x4,0xc9,0x4,0xa3,0x0,0x1d,0x0,0x2c,0x40,0x29,0x17,0x2,0x2,0x0,0x3,0x10,0x1,0x1,0x0,0x2,0x4a,0x2, + 0x1,0x0,0x0,0x3,0x59,0x4,0x1,0x3,0x3,0xe,0x4b,0x0,0x1,0x1,0xf,0x1,0x4c,0x0,0x0,0x0,0x1d,0x0,0x1b,0x25,0x33,0x26,0x5,0x7,0x17,0x2b, + 0x0,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x21,0x3,0x6,0x6,0x23,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x21,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x21, + 0x4,0xaf,0x1a,0x3,0x7,0x23,0x22,0xfe,0xa8,0xae,0x6,0x27,0x28,0x2,0x23,0x1e,0x2,0xae,0xfe,0x94,0x1b,0x1a,0x3,0x7,0x23,0x22,0x3,0x5a,0x4,0xa3, + 0x16,0x18,0xb,0xf,0x27,0x21,0xfc,0x24,0x23,0x1e,0x15,0x18,0x4,0x10,0x3,0xdc,0x16,0x18,0xb,0xf,0x27,0x21,0x0,0x1,0x0,0xeb,0xff,0xf6,0x4,0xc9, + 0x4,0xa3,0x0,0x33,0x0,0x3f,0x40,0x3c,0x2d,0x2,0x2,0x0,0x7,0x22,0xd,0x2,0x2,0x1,0x1b,0x1,0x3,0x2,0x3,0x4a,0x5,0x1,0x1,0x4,0x1,0x2, + 0x3,0x1,0x2,0x61,0x6,0x1,0x0,0x0,0x7,0x59,0x8,0x1,0x7,0x7,0xe,0x4b,0x0,0x3,0x3,0xf,0x3,0x4c,0x0,0x0,0x0,0x33,0x0,0x31,0x21,0x26, + 0x25,0x33,0x26,0x21,0x26,0x9,0x7,0x1b,0x2b,0x0,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x21,0x3,0x33,0x32,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x23,0x3, + 0x6,0x6,0x23,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x23,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x33,0x13,0x21,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33, + 0x21,0x4,0xaf,0x1a,0x3,0x7,0x23,0x22,0xfe,0xa8,0x50,0xd1,0x1b,0x1a,0x3,0x7,0x23,0x22,0xd0,0x45,0x6,0x27,0x28,0x2,0x23,0x1e,0x2,0x45,0xcc,0x1b, + 0x1a,0x3,0x7,0x23,0x22,0xcb,0x50,0xfe,0x94,0x1b,0x1a,0x3,0x7,0x23,0x22,0x3,0x5a,0x4,0xa3,0x16,0x18,0xb,0xf,0x27,0x21,0xfe,0x3b,0x16,0x18,0xb, + 0xf,0x27,0x21,0xfe,0x79,0x23,0x1e,0x15,0x18,0x4,0x10,0x1,0x87,0x16,0x18,0xb,0xf,0x27,0x21,0x1,0xc5,0x16,0x18,0xb,0xf,0x27,0x21,0x0,0xff,0xff, + 0x0,0xeb,0xff,0xf6,0x4,0xc9,0x6,0x68,0x0,0x22,0x0,0x5f,0x0,0x0,0x1,0x7,0x1,0x83,0x0,0x14,0x1,0x4,0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0x4, + 0xb0,0x33,0x2b,0x0,0x0,0x1,0x0,0xeb,0xfe,0x28,0x4,0xc9,0x4,0xa3,0x0,0x3d,0x0,0x42,0x40,0x3f,0x37,0x2,0x2,0x0,0x7,0x2b,0x1,0x5,0x1,0x2, + 0x4a,0x0,0x3,0x5,0x4,0x5,0x3,0x4,0x70,0x0,0x1,0x0,0x5,0x3,0x1,0x5,0x63,0x0,0x4,0x0,0x2,0x4,0x2,0x5f,0x6,0x1,0x0,0x0,0x7,0x59, + 0x8,0x1,0x7,0x7,0xe,0x0,0x4c,0x0,0x0,0x0,0x3d,0x0,0x3b,0x29,0x33,0x23,0x28,0x26,0x14,0x26,0x9,0x7,0x1b,0x2b,0x0,0x16,0x15,0x14,0x7,0x6, + 0x6,0x23,0x21,0x3,0x6,0x7,0x7,0x36,0x16,0x15,0x14,0x6,0x7,0x6,0x23,0x22,0x26,0x27,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x32,0x17,0x16,0x16,0x33, + 0x32,0x36,0x35,0x34,0x23,0x7,0x22,0x35,0x34,0x37,0x37,0x26,0x35,0x34,0x37,0x13,0x21,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x21,0x4,0xaf,0x1a,0x3, + 0x7,0x23,0x22,0xfe,0xa8,0xae,0x4,0xb,0x17,0x58,0x5a,0x1c,0x1a,0x45,0x97,0x44,0x7c,0x2e,0x11,0xa,0x9,0x19,0xe,0x7,0x4,0x26,0x64,0x31,0x3a,0x3a, + 0x67,0x23,0x1d,0x1,0x21,0x2,0x2,0xae,0xfe,0x94,0x1b,0x1a,0x3,0x7,0x23,0x22,0x3,0x5a,0x4,0xa3,0x16,0x18,0xb,0xf,0x27,0x21,0xfc,0x24,0x18,0xf, + 0x82,0x2,0x52,0x40,0x22,0x4f,0x1b,0x4a,0x18,0x18,0x8,0x18,0x15,0x11,0x11,0x15,0x2,0xf,0x13,0x2d,0x24,0x37,0x1,0x1c,0x9,0x5,0xc1,0xa,0x7,0x6, + 0xe,0x3,0xdc,0x16,0x18,0xb,0xf,0x27,0x21,0x0,0x0,0x0,0xff,0xff,0x0,0xeb,0xfd,0xf3,0x4,0xc9,0x4,0xa3,0x0,0x22,0x0,0x5f,0x0,0x0,0x0,0x3, + 0x1,0x7e,0x4,0xcc,0x0,0x0,0x0,0x1,0x0,0xa8,0xff,0xea,0x4,0xa0,0x4,0xad,0x0,0x25,0x0,0x28,0x40,0x25,0x15,0x2,0x2,0x2,0x1,0x1,0x4a,0x4, + 0x3,0x2,0x1,0x1,0xe,0x4b,0x0,0x2,0x2,0x0,0x5c,0x0,0x0,0x0,0x17,0x0,0x4c,0x0,0x0,0x0,0x25,0x0,0x23,0x29,0x37,0x27,0x5,0x7,0x17,0x2b, + 0x0,0x16,0x15,0x14,0x7,0x3,0x6,0x6,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x36,0x36,0x33,0x33,0x32,0x16,0x15,0x14,0x7,0x3,0x6,0x15,0x14,0x16,0x33, + 0x32,0x36,0x37,0x13,0x36,0x36,0x33,0x33,0x4,0x82,0x1e,0x2,0x81,0x27,0xfa,0xd1,0xb6,0xcd,0xb,0x7d,0x6,0x27,0x28,0x2,0x23,0x1e,0x2,0x7b,0xa,0x7b, + 0x7b,0x93,0xa6,0x1d,0x7b,0x6,0x27,0x28,0x2,0x4,0xad,0x15,0x18,0x4,0x10,0xfd,0x28,0xdc,0xce,0xac,0xa6,0x30,0x3d,0x2,0xc3,0x23,0x1e,0x15,0x18,0x4, + 0x10,0xfd,0x47,0x3c,0x24,0x70,0x65,0x8f,0xa4,0x2,0xbb,0x23,0x1e,0x0,0x0,0x0,0xff,0xff,0x0,0xa8,0xff,0xea,0x4,0xa0,0x6,0x6c,0x0,0x22,0x0,0x64, + 0x0,0x0,0x1,0x7,0x1,0x80,0x0,0x24,0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0xa8,0xff,0xea,0x4,0xa3, + 0x6,0x5c,0x0,0x22,0x0,0x64,0x0,0x0,0x1,0x7,0x1,0x82,0x0,0x1e,0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff, + 0x0,0xa8,0xff,0xea,0x4,0xa0,0x6,0x6e,0x0,0x22,0x0,0x64,0x0,0x0,0x1,0x7,0x1,0x92,0x0,0x24,0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0xe, + 0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0xa8,0xff,0xea,0x4,0xa0,0x6,0x36,0x0,0x22,0x0,0x64,0x0,0x0,0x1,0x7,0x1,0x93,0x0,0x1c,0x1,0xe,0x0,0x9, + 0xb1,0x1,0x2,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0xa8,0xff,0xea,0x4,0xa0,0x6,0x6c,0x0,0x22,0x0,0x64,0x0,0x0,0x1,0x7,0x1,0x95, + 0x0,0x32,0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0xa8,0xff,0xea,0x4,0xf0,0x6,0x77,0x0,0x22,0x0,0x64, + 0x0,0x0,0x1,0x7,0x1,0x96,0x0,0x14,0x1,0xe,0x0,0x9,0xb1,0x1,0x2,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0xa8,0xff,0xea,0x4,0xa0, + 0x5,0xf0,0x0,0x22,0x0,0x64,0x0,0x0,0x1,0x7,0x1,0x97,0x0,0x1e,0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0x0,0x1, + 0x0,0xa8,0xfe,0x28,0x4,0xa0,0x4,0xad,0x0,0x3d,0x0,0x60,0xb6,0x2d,0x2,0x2,0x5,0x4,0x1,0x4a,0x4b,0xb0,0x31,0x50,0x58,0x40,0x1a,0x1,0x1,0x0, + 0x0,0x2,0x0,0x2,0x5f,0x7,0x6,0x2,0x4,0x4,0xe,0x4b,0x0,0x5,0x5,0x3,0x5c,0x0,0x3,0x3,0x17,0x3,0x4c,0x1b,0x40,0x21,0x0,0x1,0x3,0x0, + 0x3,0x1,0x0,0x70,0x0,0x0,0x0,0x2,0x0,0x2,0x5f,0x7,0x6,0x2,0x4,0x4,0xe,0x4b,0x0,0x5,0x5,0x3,0x5c,0x0,0x3,0x3,0x17,0x3,0x4c,0x59, + 0x40,0xf,0x0,0x0,0x0,0x3d,0x0,0x3b,0x29,0x37,0x25,0x26,0x21,0x2d,0x8,0x7,0x1a,0x2b,0x0,0x16,0x15,0x14,0x7,0x3,0x6,0x6,0x7,0x6,0x6,0x15, + 0x14,0x16,0x33,0x32,0x37,0x37,0x32,0x15,0x14,0x6,0x7,0x6,0x6,0x23,0x22,0x26,0x35,0x34,0x36,0x37,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x36,0x36,0x33, + 0x33,0x32,0x16,0x15,0x14,0x7,0x3,0x6,0x15,0x14,0x16,0x33,0x32,0x36,0x37,0x13,0x36,0x36,0x33,0x33,0x4,0x82,0x1e,0x2,0x81,0x1b,0x8e,0x71,0x77,0x71, + 0x22,0x1f,0x33,0x3c,0x8,0x28,0x12,0x13,0x26,0x5e,0x2e,0x4f,0x56,0x58,0x56,0x2,0xb6,0xcd,0xb,0x7d,0x6,0x27,0x28,0x2,0x23,0x1e,0x2,0x7b,0xa,0x7b, + 0x7b,0x93,0xa6,0x1d,0x7b,0x6,0x27,0x28,0x2,0x4,0xad,0x15,0x18,0x4,0x10,0xfd,0x28,0x9c,0xc0,0x2b,0x5d,0x8d,0x38,0x1d,0x1e,0xa,0x1,0x42,0x14,0x19, + 0x7,0xd,0x10,0x58,0x4a,0x4a,0x8e,0x48,0xac,0xa6,0x30,0x3d,0x2,0xc3,0x23,0x1e,0x15,0x18,0x4,0x10,0xfd,0x47,0x3c,0x24,0x70,0x65,0x8f,0xa4,0x2,0xbb, + 0x23,0x1e,0x0,0x0,0xff,0xff,0x0,0xa8,0xff,0xea,0x4,0xa0,0x7,0x5,0x0,0x22,0x0,0x64,0x0,0x0,0x1,0x7,0x1,0x99,0x0,0x14,0x0,0xf0,0x0,0x8, + 0xb1,0x1,0x2,0xb0,0xf0,0xb0,0x33,0x2b,0x0,0x0,0xff,0xff,0x0,0xa8,0xff,0xea,0x4,0xa0,0x6,0x15,0x0,0x22,0x0,0x64,0x0,0x0,0x1,0x7,0x1,0x9a, + 0x0,0x28,0x1,0x4,0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0x4,0xb0,0x33,0x2b,0x0,0x0,0x1,0x0,0xdf,0xff,0xea,0x4,0xe3,0x4,0xb0,0x0,0x1c,0x0,0x1c, + 0x40,0x19,0x19,0x5,0x2,0x2,0x0,0x1,0x4a,0x1,0x1,0x0,0x0,0xe,0x4b,0x0,0x2,0x2,0x17,0x2,0x4c,0x2a,0x15,0x20,0x3,0x7,0x17,0x2b,0x0,0x33, + 0x32,0x16,0x17,0x13,0x1,0x36,0x33,0x32,0x17,0x16,0x16,0x15,0x14,0x7,0x1,0x6,0x6,0x23,0x22,0x26,0x27,0x3,0x26,0x35,0x34,0x36,0x37,0x1,0x35,0x8, + 0x1c,0x1b,0x5,0xbe,0x2,0x17,0x16,0x23,0x12,0x1d,0x17,0x16,0xd,0xfd,0xba,0x13,0x38,0x2b,0x2b,0x2e,0x7,0xd8,0x3,0x1e,0x24,0x4,0xad,0x17,0x1c,0xfc, + 0x3c,0x3,0xd1,0x29,0xd,0xb,0x18,0xf,0x12,0x17,0xfb,0xe3,0x23,0x1e,0x1e,0x23,0x4,0x39,0xc,0xb,0x15,0x17,0x4,0x0,0x0,0x1,0x0,0x99,0xff,0xea, + 0x5,0x3,0x4,0xae,0x0,0x27,0x0,0x4d,0xb7,0x1c,0xa,0x3,0x3,0x2,0x0,0x1,0x4a,0x4b,0xb0,0x19,0x50,0x58,0x40,0x13,0x5,0x4,0x2,0x1,0x1,0xe, + 0x4b,0x0,0x0,0x0,0x11,0x4b,0x3,0x1,0x2,0x2,0x17,0x2,0x4c,0x1b,0x40,0x16,0x0,0x0,0x1,0x2,0x1,0x0,0x2,0x70,0x5,0x4,0x2,0x1,0x1,0xe, + 0x4b,0x3,0x1,0x2,0x2,0x17,0x2,0x4c,0x59,0x40,0xd,0x0,0x0,0x0,0x27,0x0,0x26,0x25,0x2a,0x15,0x26,0x6,0x7,0x18,0x2b,0x0,0x16,0x7,0x3,0x1, + 0x36,0x36,0x33,0x32,0x17,0x13,0x1,0x36,0x36,0x33,0x32,0x17,0x16,0x16,0x15,0x14,0x7,0x1,0x6,0x6,0x23,0x22,0x27,0x3,0x1,0x6,0x6,0x23,0x22,0x26, + 0x37,0x13,0x36,0x36,0x33,0x1,0x3a,0x1f,0x2,0x2b,0x1,0x36,0xb,0x24,0x20,0x3f,0x6,0x68,0x1,0xc,0x9,0x1e,0x1b,0xe,0x10,0x1e,0x1b,0x8,0xfe,0xb1, + 0xb,0x2c,0x27,0x50,0xc,0x68,0xfe,0xca,0x12,0x34,0x26,0x28,0x27,0x1,0x2a,0x2,0x1d,0x2b,0x4,0xac,0x1b,0x24,0xfc,0x95,0x2,0x4b,0x14,0x14,0x28,0xfd, + 0xb5,0x3,0x73,0x1e,0x1b,0x3,0x5,0x16,0x13,0xf,0x18,0xfb,0xd5,0x24,0x1d,0x41,0x2,0x4a,0xfd,0xb6,0x22,0x1f,0x1e,0x23,0x4,0x45,0x22,0x1a,0x0,0x0, + 0xff,0xff,0x0,0x99,0xff,0xea,0x5,0x3,0x6,0x6c,0x0,0x22,0x0,0x70,0x0,0x0,0x1,0x7,0x1,0x80,0x0,0x14,0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8, + 0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0x99,0xff,0xea,0x5,0x3,0x6,0x6e,0x0,0x22,0x0,0x70,0x0,0x0,0x1,0x7,0x1,0x92,0x0,0x14,0x1,0xe, + 0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0x99,0xff,0xea,0x5,0x3,0x6,0x36,0x0,0x22,0x0,0x70,0x0,0x0,0x1,0x7, + 0x1,0x93,0x0,0x17,0x1,0xe,0x0,0x9,0xb1,0x1,0x2,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0x99,0xff,0xea,0x5,0x3,0x6,0x6c,0x0,0x22, + 0x0,0x70,0x0,0x0,0x1,0x7,0x1,0x95,0x0,0xa,0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0x0,0x1,0x0,0x5d,0xff,0xeb, + 0x4,0xa8,0x4,0xb8,0x0,0x2b,0x0,0x20,0x40,0x1d,0x26,0x1a,0x10,0x4,0x4,0x0,0x2,0x1,0x4a,0x3,0x1,0x2,0x2,0x16,0x4b,0x1,0x1,0x0,0x0,0x17, + 0x0,0x4c,0x25,0x2d,0x25,0x2b,0x4,0x7,0x18,0x2b,0x0,0x15,0x14,0x7,0x1,0x1,0x16,0x15,0x14,0x6,0x7,0x6,0x23,0x22,0x26,0x27,0x3,0x1,0x6,0x23, + 0x22,0x27,0x26,0x35,0x34,0x37,0x1,0x3,0x26,0x35,0x34,0x36,0x37,0x36,0x33,0x32,0x16,0x17,0x13,0x1,0x36,0x33,0x32,0x17,0x4,0xa8,0x17,0xfe,0x5b,0x1, + 0xc,0x9,0x19,0x1b,0x22,0x13,0xe,0x14,0x9,0xec,0xfe,0x6f,0x18,0x1f,0x18,0x22,0x22,0x17,0x1,0xc1,0xfa,0x9,0x19,0x1b,0x22,0x13,0xe,0x14,0x9,0xdb, + 0x1,0x74,0x18,0x1f,0x18,0x22,0x4,0x8e,0x1b,0x15,0x1c,0xfe,0x12,0xfe,0x1,0x12,0xd,0x10,0x19,0xd,0x11,0x10,0x12,0x1,0xd9,0xfe,0x1f,0x1e,0x15,0x15, + 0x1b,0x15,0x1c,0x2,0x7,0x1,0xe6,0x12,0xd,0x10,0x19,0xd,0x11,0x10,0x12,0xfe,0x44,0x1,0xc4,0x1e,0x15,0x0,0x0,0x0,0x0,0x1,0x1,0x5,0xff,0xf6, + 0x4,0xcb,0x4,0xb2,0x0,0x20,0x0,0x1d,0x40,0x1a,0x1b,0xf,0xc,0x3,0x0,0x1,0x1,0x4a,0x2,0x1,0x1,0x1,0xe,0x4b,0x0,0x0,0x0,0xf,0x0,0x4c, + 0x26,0x1c,0x37,0x3,0x7,0x17,0x2b,0x0,0x15,0x14,0x7,0x1,0x3,0x6,0x6,0x23,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x1,0x26,0x35,0x34,0x36,0x37,0x36, + 0x33,0x32,0x16,0x17,0x13,0x1,0x36,0x33,0x32,0x17,0x4,0xcb,0x15,0xfe,0xa,0x4c,0x6,0x27,0x28,0x2,0x23,0x1e,0x2,0x4e,0xfe,0xe3,0xa,0x19,0x1c,0x1a, + 0x14,0x10,0x14,0x8,0xf5,0x1,0xb2,0x18,0x20,0x17,0x19,0x4,0x8b,0x1b,0x14,0x1a,0xfd,0xa4,0xfe,0x51,0x23,0x1e,0x15,0x18,0x4,0x10,0x1,0xbc,0x2,0x5a, + 0x16,0xc,0x10,0x19,0xb,0xb,0x10,0x11,0xfd,0xf5,0x2,0x12,0x1e,0xf,0xff,0xff,0x1,0x5,0xff,0xf6,0x4,0xcb,0x6,0x6c,0x0,0x22,0x0,0x76,0x0,0x0, + 0x1,0x7,0x1,0x80,0x0,0x28,0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x1,0x5,0xff,0xf6,0x4,0xcb,0x6,0x6e, + 0x0,0x22,0x0,0x76,0x0,0x0,0x1,0x7,0x1,0x92,0x0,0x28,0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff,0x1,0x5, + 0xff,0xf6,0x4,0xcb,0x6,0x36,0x0,0x22,0x0,0x76,0x0,0x0,0x1,0x7,0x1,0x93,0x0,0x1c,0x1,0xe,0x0,0x9,0xb1,0x1,0x2,0xb8,0x1,0xe,0xb0,0x33, + 0x2b,0x0,0xff,0xff,0x1,0x5,0xff,0xf6,0x4,0xcb,0x6,0x6c,0x0,0x22,0x0,0x76,0x0,0x0,0x1,0x7,0x1,0x95,0x0,0x14,0x1,0xe,0x0,0x9,0xb1,0x1, + 0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0x0,0x1,0x0,0x74,0x0,0x0,0x4,0x88,0x4,0xa3,0x0,0x23,0x0,0x31,0x40,0x2e,0x14,0xb,0x2,0x0,0x1,0x1d, + 0x2,0x2,0x3,0x2,0x2,0x4a,0x0,0x0,0x0,0x1,0x59,0x0,0x1,0x1,0xe,0x4b,0x0,0x2,0x2,0x3,0x59,0x4,0x1,0x3,0x3,0xf,0x3,0x4c,0x0,0x0, + 0x0,0x23,0x0,0x21,0x27,0x36,0x27,0x5,0x7,0x17,0x2b,0x32,0x26,0x35,0x34,0x37,0x36,0x37,0x1,0x21,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x21,0x32, + 0x16,0x15,0x14,0x7,0x6,0x7,0x1,0x21,0x32,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x21,0x8e,0x1a,0x2,0x4,0x12,0x3,0x9,0xfd,0xd2,0x1b,0x1a,0x3,0x7, + 0x23,0x22,0x2,0xd0,0x1c,0x1b,0x2,0x4,0xd,0xfc,0xf2,0x2,0x5a,0x1b,0x1a,0x3,0x7,0x22,0x22,0xfd,0x2,0x14,0x18,0xf,0x9,0x15,0x16,0x3,0xa4,0x16, + 0x18,0xb,0xf,0x27,0x21,0x15,0x18,0xe,0x9,0x17,0x10,0xfc,0x58,0x16,0x18,0xb,0xf,0x27,0x21,0x0,0x0,0x0,0xff,0xff,0x0,0x74,0x0,0x0,0x4,0x88, + 0x6,0x6c,0x0,0x22,0x0,0x7b,0x0,0x0,0x1,0x7,0x1,0x80,0x0,0xa,0x1,0xe,0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0xff,0xff, + 0x0,0x74,0x0,0x0,0x4,0x88,0x6,0x68,0x0,0x22,0x0,0x7b,0x0,0x0,0x1,0x7,0x1,0x83,0x0,0x1e,0x1,0x4,0x0,0x9,0xb1,0x1,0x1,0xb8,0x1,0x4, + 0xb0,0x33,0x2b,0x0,0xff,0xff,0x0,0x74,0x0,0x0,0x4,0x88,0x6,0x5f,0x0,0x22,0x0,0x7b,0x0,0x0,0x1,0x7,0x1,0x94,0x0,0x1e,0x1,0xe,0x0,0x9, + 0xb1,0x1,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0x0,0x2,0x0,0x8f,0xff,0xea,0x4,0x6b,0x3,0xb2,0x0,0x2a,0x0,0x38,0x0,0xfe,0x4b,0xb0,0x1b,0x50, + 0x58,0x40,0xf,0x21,0x1,0x5,0x2,0x19,0xb,0x2,0x4,0x5,0x2,0x1,0x0,0x4,0x3,0x4a,0x1b,0x4b,0xb0,0x28,0x50,0x58,0x40,0xf,0x21,0x1,0x5,0x2, + 0x19,0xb,0x2,0x4,0x5,0x2,0x1,0x0,0x6,0x3,0x4a,0x1b,0x40,0xf,0x21,0x1,0x5,0x3,0x19,0xb,0x2,0x4,0x5,0x2,0x1,0x0,0x6,0x3,0x4a,0x59, + 0x59,0x4b,0xb0,0x17,0x50,0x58,0x40,0x1a,0x0,0x5,0x5,0x2,0x5b,0x3,0x1,0x2,0x2,0x19,0x4b,0x8,0x6,0x7,0x3,0x4,0x4,0x0,0x5c,0x1,0x1,0x0, + 0x0,0xf,0x0,0x4c,0x1b,0x4b,0xb0,0x1b,0x50,0x58,0x40,0x26,0x0,0x5,0x5,0x2,0x5b,0x3,0x1,0x2,0x2,0x19,0x4b,0x8,0x6,0x7,0x3,0x4,0x4,0x0, + 0x5c,0x0,0x0,0x0,0xf,0x4b,0x8,0x6,0x7,0x3,0x4,0x4,0x1,0x5c,0x0,0x1,0x1,0x17,0x1,0x4c,0x1b,0x4b,0xb0,0x28,0x50,0x58,0x40,0x22,0x0,0x5, + 0x5,0x2,0x5b,0x3,0x1,0x2,0x2,0x19,0x4b,0x7,0x1,0x4,0x4,0x0,0x5c,0x0,0x0,0x0,0xf,0x4b,0x8,0x1,0x6,0x6,0x1,0x5b,0x0,0x1,0x1,0x17, + 0x1,0x4c,0x1b,0x40,0x26,0x0,0x3,0x3,0x11,0x4b,0x0,0x5,0x5,0x2,0x5b,0x0,0x2,0x2,0x19,0x4b,0x7,0x1,0x4,0x4,0x0,0x5c,0x0,0x0,0x0,0xf, + 0x4b,0x8,0x1,0x6,0x6,0x1,0x5b,0x0,0x1,0x1,0x17,0x1,0x4c,0x59,0x59,0x59,0x40,0x15,0x2b,0x2b,0x0,0x0,0x2b,0x38,0x2b,0x37,0x32,0x30,0x0,0x2a, + 0x0,0x28,0x35,0x26,0x24,0x36,0x9,0x7,0x18,0x2b,0x24,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x23,0x22,0x26,0x27,0x6,0x6,0x23,0x22,0x26,0x26,0x35,0x34, + 0x12,0x36,0x33,0x32,0x16,0x17,0x37,0x36,0x36,0x33,0x33,0x32,0x16,0x15,0x14,0x7,0x3,0x6,0x15,0x14,0x16,0x33,0x33,0x4,0x36,0x36,0x35,0x34,0x26,0x23, + 0x22,0x6,0x6,0x15,0x14,0x16,0x33,0x4,0x52,0x19,0x2,0x8,0x22,0x22,0xb,0x50,0x62,0x4,0x48,0xb0,0x6e,0x68,0xa3,0x5c,0x73,0xe6,0xa3,0x5d,0x92,0x2e, + 0x13,0x6,0x21,0x27,0x2,0x21,0x1d,0x2,0x68,0x4,0x2e,0x28,0x5,0xfe,0x3a,0xa3,0x5d,0x7f,0x6c,0x69,0x9f,0x55,0x78,0x6c,0x90,0x16,0x19,0xf,0xa,0x27, + 0x21,0x5d,0x55,0x63,0x65,0x5f,0xb0,0x75,0x8a,0x1,0xb,0xaf,0x60,0x54,0x67,0x24,0x1d,0x15,0x17,0x5,0x10,0xfd,0xb2,0x13,0x15,0x2f,0x30,0x12,0x79,0xc0, + 0x66,0x79,0x88,0x7a,0xc4,0x69,0x72,0x87,0x0,0x0,0xff,0xff,0x0,0x8f,0xff,0xea,0x4,0x6b,0x5,0x5e,0x0,0x22,0x0,0x7f,0x0,0x0,0x0,0x2,0x1,0x80, + 0xcc,0x0,0x0,0x0,0xff,0xff,0x0,0x8f,0xff,0xea,0x4,0x6b,0x5,0x4e,0x0,0x22,0x1,0x82,0xe2,0x0,0x0,0x2,0x0,0x7f,0x0,0x0,0x0,0x0,0xff,0xff, + 0x0,0x8f,0xff,0xea,0x4,0x6b,0x5,0x60,0x0,0x22,0x0,0x7f,0x0,0x0,0x0,0x2,0x1,0x92,0xec,0x0,0x0,0x0,0xff,0xff,0x0,0x8f,0xff,0xea,0x4,0x6b, + 0x5,0x28,0x0,0x22,0x0,0x7f,0x0,0x0,0x0,0x2,0x1,0x93,0xe2,0x0,0x0,0x0,0xff,0xff,0x0,0x8f,0xff,0xea,0x4,0x6b,0x5,0x5e,0x0,0x22,0x0,0x7f, + 0x0,0x0,0x0,0x2,0x1,0x95,0xc,0x0,0x0,0x0,0xff,0xff,0x0,0x8f,0xff,0xea,0x4,0x6b,0x4,0xe2,0x0,0x22,0x0,0x7f,0x0,0x0,0x0,0x2,0x1,0x97, + 0xe9,0x0,0x0,0x0,0x0,0x2,0x0,0x8f,0xfe,0x28,0x4,0x6b,0x3,0xb2,0x0,0x3f,0x0,0x4d,0x1,0x23,0x4b,0xb0,0x1b,0x50,0x58,0x40,0x10,0x36,0x1,0x7, + 0x4,0x2e,0x20,0x2,0x6,0x7,0x1e,0x2,0x2,0x3,0x6,0x3,0x4a,0x1b,0x4b,0xb0,0x28,0x50,0x58,0x40,0x10,0x36,0x1,0x7,0x4,0x2e,0x20,0x2,0x6,0x7, + 0x1e,0x2,0x2,0x3,0x8,0x3,0x4a,0x1b,0x40,0x10,0x36,0x1,0x7,0x5,0x2e,0x20,0x2,0x6,0x7,0x1e,0x2,0x2,0x3,0x8,0x3,0x4a,0x59,0x59,0x4b,0xb0, + 0x1b,0x50,0x58,0x40,0x21,0x1,0x1,0x0,0x0,0x2,0x0,0x2,0x5f,0x0,0x7,0x7,0x4,0x5b,0x5,0x1,0x4,0x4,0x19,0x4b,0xa,0x8,0x9,0x3,0x6,0x6, + 0x3,0x5b,0x0,0x3,0x3,0x17,0x3,0x4c,0x1b,0x4b,0xb0,0x28,0x50,0x58,0x40,0x28,0x9,0x1,0x6,0x7,0x8,0x7,0x6,0x8,0x70,0x1,0x1,0x0,0x0,0x2, + 0x0,0x2,0x5f,0x0,0x7,0x7,0x4,0x5b,0x5,0x1,0x4,0x4,0x19,0x4b,0xa,0x1,0x8,0x8,0x3,0x5b,0x0,0x3,0x3,0x17,0x3,0x4c,0x1b,0x4b,0xb0,0x31, + 0x50,0x58,0x40,0x2c,0x9,0x1,0x6,0x7,0x8,0x7,0x6,0x8,0x70,0x1,0x1,0x0,0x0,0x2,0x0,0x2,0x5f,0x0,0x5,0x5,0x11,0x4b,0x0,0x7,0x7,0x4, + 0x5b,0x0,0x4,0x4,0x19,0x4b,0xa,0x1,0x8,0x8,0x3,0x5b,0x0,0x3,0x3,0x17,0x3,0x4c,0x1b,0x40,0x33,0x9,0x1,0x6,0x7,0x8,0x7,0x6,0x8,0x70, + 0x0,0x1,0x3,0x0,0x3,0x1,0x0,0x70,0x0,0x0,0x0,0x2,0x0,0x2,0x5f,0x0,0x5,0x5,0x11,0x4b,0x0,0x7,0x7,0x4,0x5b,0x0,0x4,0x4,0x19,0x4b, + 0xa,0x1,0x8,0x8,0x3,0x5b,0x0,0x3,0x3,0x17,0x3,0x4c,0x59,0x59,0x59,0x40,0x17,0x40,0x40,0x0,0x0,0x40,0x4d,0x40,0x4c,0x47,0x45,0x0,0x3f,0x0, + 0x3d,0x35,0x26,0x29,0x26,0x21,0x2c,0xb,0x7,0x1a,0x2b,0x24,0x16,0x15,0x14,0x7,0x6,0x6,0x7,0x6,0x6,0x15,0x14,0x16,0x33,0x32,0x37,0x37,0x32,0x15, + 0x14,0x6,0x7,0x6,0x6,0x23,0x22,0x26,0x35,0x34,0x36,0x37,0x26,0x27,0x6,0x6,0x23,0x22,0x26,0x26,0x35,0x34,0x12,0x36,0x33,0x32,0x16,0x17,0x37,0x36, + 0x36,0x33,0x33,0x32,0x16,0x15,0x14,0x7,0x3,0x6,0x15,0x14,0x16,0x33,0x33,0x4,0x36,0x36,0x35,0x34,0x26,0x23,0x22,0x6,0x6,0x15,0x14,0x16,0x33,0x4, + 0x52,0x19,0x2,0x6,0x19,0x16,0x70,0x6b,0x22,0x1f,0x33,0x3c,0x8,0x28,0x12,0x13,0x26,0x5e,0x2e,0x4f,0x56,0x78,0x75,0x48,0x6,0x48,0xb0,0x6e,0x68,0xa3, + 0x5c,0x73,0xe6,0xa3,0x5d,0x92,0x2e,0x13,0x6,0x21,0x27,0x2,0x21,0x1d,0x2,0x68,0x4,0x2e,0x28,0x5,0xfe,0x3a,0xa3,0x5d,0x7f,0x6c,0x69,0x9f,0x55,0x78, + 0x6c,0x90,0x16,0x19,0xf,0xa,0x1f,0x22,0x5,0x59,0x88,0x36,0x1d,0x1e,0xa,0x1,0x42,0x14,0x19,0x7,0xd,0x10,0x58,0x4a,0x57,0xa5,0x55,0x2d,0x6a,0x63, + 0x65,0x5f,0xb0,0x75,0x8a,0x1,0xb,0xaf,0x60,0x54,0x67,0x24,0x1d,0x15,0x17,0x5,0x10,0xfd,0xb2,0x13,0x15,0x2f,0x30,0x12,0x79,0xc0,0x66,0x79,0x88,0x7a, + 0xc4,0x69,0x72,0x87,0x0,0x0,0xff,0xff,0x0,0x8f,0xff,0xea,0x4,0x6b,0x5,0xf8,0x0,0x22,0x0,0x7f,0x0,0x0,0x1,0x6,0x1,0x99,0xe4,0xe3,0x0,0x9, + 0xb1,0x2,0x2,0xb8,0xff,0xe3,0xb0,0x33,0x2b,0x0,0x0,0x0,0xff,0xff,0x0,0x8f,0xff,0xea,0x4,0x6b,0x5,0x11,0x0,0x22,0x0,0x7f,0x0,0x0,0x0,0x2, + 0x1,0x9a,0xe2,0x0,0x0,0x0,0x0,0x3,0x0,0x2c,0xff,0xea,0x4,0x9d,0x3,0xb2,0x0,0x41,0x0,0x4a,0x0,0x57,0x0,0x65,0x40,0x62,0x3e,0x1,0x6,0x8, + 0x1d,0x1,0x3,0x1,0x2,0x4a,0x0,0x7,0x6,0x5,0x6,0x7,0x5,0x70,0x0,0x2,0x0,0x1,0x0,0x2,0x1,0x70,0xa,0x1,0x5,0x10,0xd,0x2,0x0,0x2, + 0x5,0x0,0x63,0xf,0xb,0x2,0x6,0x6,0x8,0x5b,0xe,0x9,0x2,0x8,0x8,0x19,0x4b,0xc,0x1,0x1,0x1,0x3,0x5b,0x4,0x1,0x3,0x3,0x17,0x3,0x4c, + 0x4b,0x4b,0x42,0x42,0x0,0x0,0x4b,0x57,0x4b,0x57,0x53,0x51,0x42,0x4a,0x42,0x49,0x45,0x44,0x0,0x41,0x0,0x40,0x27,0x22,0x25,0x17,0x23,0x29,0x13,0x23, + 0x26,0x11,0x7,0x1d,0x2b,0x0,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x21,0x6,0x15,0x14,0x33,0x32,0x36,0x37,0x36,0x33,0x32,0x16,0x17,0x16,0x15,0x14,0x7, + 0x6,0x6,0x23,0x22,0x27,0x6,0x6,0x23,0x22,0x26,0x26,0x35,0x34,0x36,0x37,0x36,0x33,0x37,0x36,0x35,0x34,0x26,0x23,0x22,0x7,0x6,0x23,0x22,0x27,0x26, + 0x35,0x34,0x37,0x36,0x36,0x33,0x32,0x17,0x36,0x36,0x33,0x6,0x6,0x7,0x21,0x36,0x35,0x34,0x26,0x23,0x0,0x7,0x6,0x6,0x15,0x14,0x16,0x33,0x32,0x36, + 0x36,0x37,0x37,0x4,0x27,0x76,0xc,0x5,0x23,0x22,0xfe,0x71,0xc,0x78,0x30,0x56,0x32,0x14,0x16,0xe,0x1b,0x9,0xd,0x25,0x41,0x84,0x46,0xa3,0x38,0x3c, + 0x9f,0x50,0x44,0x67,0x38,0x65,0x5b,0x84,0xcb,0x4,0x9,0x31,0x38,0x57,0x6a,0x13,0x13,0x1d,0x17,0x10,0x1f,0x47,0x8b,0x49,0x99,0x30,0x31,0x7c,0x47,0x54, + 0x61,0x20,0x1,0x2c,0x5,0x32,0x37,0xfd,0xf1,0x53,0x39,0x3e,0x31,0x2b,0x38,0x66,0x46,0xb,0x12,0x3,0xb2,0x9c,0x8f,0x43,0x4e,0x21,0x1d,0x4c,0x38,0xb9, + 0x25,0x29,0x11,0x10,0xf,0x14,0x14,0x22,0x1d,0x35,0x35,0x90,0x43,0x4d,0x36,0x62,0x41,0x62,0xa9,0x30,0x46,0x16,0x32,0x25,0x38,0x38,0x48,0xd,0x20,0x18, + 0x14,0x1d,0x17,0x35,0x31,0x74,0x39,0x3b,0x91,0x68,0x75,0x2a,0x1b,0x4a,0x4e,0xfe,0x97,0x2a,0x1d,0x64,0x3c,0x2b,0x2f,0x39,0x65,0x3f,0x64,0x0,0x0,0x2, + 0x0,0x81,0xff,0xea,0x4,0x53,0x5,0x2a,0x0,0x22,0x0,0x30,0x0,0x8f,0x4b,0xb0,0x28,0x50,0x58,0x40,0x12,0x2,0x1,0x0,0x3,0x5,0x1,0x5,0x0,0x13, + 0x1,0x4,0x5,0x1b,0x1,0x1,0x4,0x4,0x4a,0x1b,0x40,0x12,0x2,0x1,0x0,0x3,0x5,0x1,0x5,0x0,0x13,0x1,0x4,0x5,0x1b,0x1,0x2,0x4,0x4,0x4a, + 0x59,0x4b,0xb0,0x28,0x50,0x58,0x40,0x1d,0x6,0x1,0x3,0x3,0x10,0x4b,0x7,0x1,0x5,0x5,0x0,0x5b,0x0,0x0,0x0,0x19,0x4b,0x0,0x4,0x4,0x1,0x5b, + 0x2,0x1,0x1,0x1,0x17,0x1,0x4c,0x1b,0x40,0x21,0x6,0x1,0x3,0x3,0x10,0x4b,0x7,0x1,0x5,0x5,0x0,0x5b,0x0,0x0,0x0,0x19,0x4b,0x0,0x2,0x2, + 0xf,0x4b,0x0,0x4,0x4,0x1,0x5b,0x0,0x1,0x1,0x17,0x1,0x4c,0x59,0x40,0x14,0x23,0x23,0x0,0x0,0x23,0x30,0x23,0x2f,0x2a,0x28,0x0,0x22,0x0,0x20, + 0x35,0x26,0x27,0x8,0x7,0x17,0x2b,0x0,0x16,0x15,0x14,0x7,0x3,0x36,0x36,0x33,0x32,0x16,0x16,0x15,0x14,0x2,0x6,0x23,0x22,0x26,0x27,0x7,0x6,0x6, + 0x23,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x36,0x36,0x33,0x33,0x12,0x6,0x6,0x15,0x14,0x16,0x33,0x32,0x36,0x36,0x35,0x34,0x26,0x23,0x1,0xd1,0x1e,0x2, + 0x50,0x47,0xa7,0x5e,0x6b,0xa4,0x5b,0x72,0xe8,0xa6,0x65,0x9b,0x31,0x13,0x6,0x21,0x27,0x2,0x21,0x1d,0x2,0xd4,0x6,0x27,0x28,0x2,0xaf,0xa9,0x5a,0x8a, + 0x75,0x6c,0xa0,0x55,0x7a,0x71,0x5,0x2a,0x15,0x18,0x4,0x10,0xfe,0x39,0x46,0x4a,0x5f,0xaf,0x76,0x8a,0xfe,0xf4,0xae,0x5d,0x57,0x67,0x24,0x1d,0x15,0x17, + 0x5,0x10,0x4,0xb2,0x23,0x1e,0xfd,0xf4,0x76,0xbf,0x6a,0x79,0x88,0x7a,0xc3,0x6a,0x72,0x87,0x0,0x0,0x0,0x1,0x0,0x9a,0xff,0xea,0x4,0x5a,0x3,0xb2, + 0x0,0x2b,0x0,0x36,0x40,0x33,0x0,0x0,0x1,0x3,0x1,0x0,0x3,0x70,0x0,0x3,0x2,0x1,0x3,0x2,0x6e,0x0,0x1,0x1,0x5,0x5b,0x6,0x1,0x5,0x5, + 0x19,0x4b,0x0,0x2,0x2,0x4,0x5b,0x0,0x4,0x4,0x17,0x4,0x4c,0x0,0x0,0x0,0x2b,0x0,0x2a,0x27,0x23,0x26,0x24,0x17,0x7,0x7,0x19,0x2b,0x0,0x16, + 0x17,0x16,0x15,0x14,0x7,0x6,0x23,0x22,0x27,0x26,0x26,0x23,0x22,0x6,0x6,0x15,0x14,0x16,0x16,0x33,0x32,0x36,0x37,0x36,0x33,0x32,0x17,0x16,0x15,0x14, + 0x7,0x6,0x6,0x23,0x22,0x26,0x26,0x35,0x34,0x12,0x24,0x33,0x3,0x2f,0xba,0x4b,0x26,0xd,0x17,0x21,0x11,0x14,0x40,0x9c,0x4f,0x70,0xb7,0x69,0x41,0x7b, + 0x54,0x58,0xb1,0x4a,0x13,0x11,0x23,0x15,0xc,0x29,0x55,0xe0,0x6d,0x7e,0xba,0x63,0x8f,0x1,0x2,0xa5,0x3,0xb2,0x39,0x36,0x1b,0x23,0x15,0x17,0x28,0xd, + 0x2f,0x31,0x70,0xbc,0x6b,0x4e,0x78,0x43,0x2f,0x2b,0xb,0x2c,0x18,0x13,0x25,0x16,0x30,0x37,0x68,0xb7,0x73,0x94,0x1,0x4,0x9e,0x0,0x0,0x0,0xff,0xff, + 0x0,0x9a,0xff,0xea,0x4,0x5a,0x5,0x5e,0x0,0x22,0x1,0x80,0xf6,0x0,0x0,0x2,0x0,0x8b,0x0,0x0,0x0,0x0,0xff,0xff,0x0,0x9a,0xff,0xea,0x4,0x62, + 0x5,0x6e,0x0,0x26,0x1,0x83,0x0,0xa,0x1,0x2,0x0,0x8b,0x0,0x0,0x0,0x8,0xb1,0x0,0x1,0xb0,0xa,0xb0,0x33,0x2b,0x0,0x1,0x0,0x9a,0xfe,0x28, + 0x4,0x5a,0x3,0xb2,0x0,0x4e,0x0,0x58,0x40,0x55,0x43,0x1,0x4,0x2,0x40,0x1,0x9,0x5,0x2,0x4a,0x0,0x0,0x1,0x3,0x1,0x0,0x3,0x70,0x0,0x3, + 0x2,0x1,0x3,0x2,0x6e,0x0,0x7,0x9,0x8,0x9,0x7,0x8,0x70,0x0,0x5,0x0,0x9,0x7,0x5,0x9,0x63,0x0,0x8,0x0,0x6,0x8,0x6,0x5f,0x0,0x1, + 0x1,0xa,0x5b,0x0,0xa,0xa,0x19,0x4b,0x0,0x2,0x2,0x4,0x5b,0x0,0x4,0x4,0x17,0x4,0x4c,0x4c,0x4a,0x3f,0x3c,0x23,0x28,0x26,0x11,0x17,0x23,0x26, + 0x24,0x14,0xb,0x7,0x1d,0x2b,0x0,0x15,0x14,0x7,0x6,0x23,0x22,0x27,0x26,0x26,0x23,0x22,0x6,0x6,0x15,0x14,0x16,0x16,0x33,0x32,0x36,0x37,0x36,0x33, + 0x32,0x17,0x16,0x15,0x14,0x7,0x6,0x6,0x7,0x7,0x36,0x16,0x15,0x14,0x6,0x7,0x6,0x23,0x22,0x26,0x27,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x32,0x17, + 0x16,0x16,0x33,0x32,0x36,0x35,0x34,0x23,0x7,0x22,0x35,0x34,0x37,0x37,0x2e,0x2,0x35,0x34,0x12,0x24,0x33,0x32,0x16,0x17,0x4,0x5a,0xd,0x17,0x21,0x11, + 0x14,0x40,0x9c,0x4f,0x70,0xb7,0x69,0x41,0x7b,0x54,0x58,0xb1,0x4a,0x13,0x11,0x23,0x15,0xc,0x29,0x4b,0xbf,0x61,0x11,0x58,0x5a,0x1c,0x1a,0x45,0x97,0x44, + 0x7c,0x2e,0x11,0xa,0x9,0x19,0xe,0x7,0x4,0x26,0x64,0x31,0x3a,0x3a,0x67,0x23,0x1d,0x1,0x1b,0x66,0x96,0x4f,0x8f,0x1,0x2,0xa5,0x5f,0xba,0x4b,0x3, + 0x28,0x23,0x15,0x17,0x28,0xd,0x2f,0x31,0x70,0xbc,0x6b,0x4e,0x78,0x43,0x2f,0x2b,0xb,0x2c,0x18,0x13,0x25,0x16,0x2a,0x35,0x6,0x5e,0x2,0x52,0x40,0x22, + 0x4f,0x1b,0x4a,0x18,0x18,0x8,0x18,0x15,0x11,0x11,0x15,0x2,0xf,0x13,0x2d,0x24,0x37,0x1,0x1c,0x9,0x5,0x9f,0x10,0x6d,0xa8,0x67,0x94,0x1,0x4,0x9e, + 0x39,0x36,0xff,0xff,0x0,0x9a,0xff,0xea,0x4,0x5a,0x5,0x60,0x0,0x22,0x0,0x8b,0x0,0x0,0x0,0x2,0x1,0x92,0xa,0x0,0x0,0x0,0xff,0xff,0x0,0x9a, + 0xff,0xea,0x4,0x5a,0x5,0x51,0x0,0x22,0x0,0x8b,0x0,0x0,0x0,0x2,0x1,0x94,0xf6,0x0,0x0,0x0,0x0,0x2,0x0,0x8f,0xff,0xea,0x4,0xa5,0x5,0x2a, + 0x0,0x21,0x0,0x2f,0x0,0x71,0x40,0xf,0x2,0x1,0x2,0x3,0x1c,0xf,0x2,0x5,0x4,0xc,0x1,0x0,0x5,0x3,0x4a,0x4b,0xb0,0x28,0x50,0x58,0x40,0x1d, + 0x6,0x1,0x3,0x3,0x10,0x4b,0x0,0x4,0x4,0x2,0x5b,0x0,0x2,0x2,0x19,0x4b,0x7,0x1,0x5,0x5,0x0,0x5b,0x1,0x1,0x0,0x0,0xf,0x0,0x4c,0x1b, + 0x40,0x21,0x6,0x1,0x3,0x3,0x10,0x4b,0x0,0x4,0x4,0x2,0x5b,0x0,0x2,0x2,0x19,0x4b,0x0,0x0,0x0,0xf,0x4b,0x7,0x1,0x5,0x5,0x1,0x5b,0x0, + 0x1,0x1,0x17,0x1,0x4c,0x59,0x40,0x14,0x22,0x22,0x0,0x0,0x22,0x2f,0x22,0x2e,0x29,0x27,0x0,0x21,0x0,0x1f,0x26,0x26,0x37,0x8,0x7,0x17,0x2b,0x0, + 0x16,0x15,0x14,0x7,0x3,0x6,0x6,0x23,0x23,0x22,0x26,0x35,0x34,0x37,0x37,0x6,0x23,0x22,0x26,0x26,0x35,0x34,0x12,0x36,0x33,0x32,0x16,0x17,0x13,0x36, + 0x36,0x33,0x33,0x0,0x36,0x36,0x35,0x34,0x26,0x23,0x22,0x6,0x6,0x15,0x14,0x16,0x33,0x4,0x87,0x1e,0x2,0xd4,0x6,0x21,0x27,0x2,0x21,0x1d,0x2,0xe, + 0x97,0xc1,0x6b,0xa4,0x5b,0x72,0xe8,0xa6,0x63,0x98,0x2f,0x54,0x6,0x27,0x28,0x2,0xfe,0x21,0xa9,0x5a,0x8a,0x75,0x6c,0xa0,0x55,0x7a,0x71,0x5,0x2a,0x15, + 0x18,0x4,0x10,0xfb,0x4e,0x24,0x1d,0x15,0x17,0x5,0x10,0x4f,0x9c,0x5f,0xaf,0x76,0x8a,0x1,0xc,0xae,0x58,0x51,0x1,0xe0,0x23,0x1e,0xfb,0x54,0x76,0xbf, + 0x6a,0x79,0x88,0x7a,0xc3,0x6a,0x72,0x87,0x0,0x0,0x0,0x2,0x0,0x8e,0xff,0xea,0x4,0x7c,0x5,0x35,0x0,0x35,0x0,0x43,0x0,0xab,0x40,0xd,0x2f,0x23, + 0x19,0x4,0x4,0x2,0x4,0x17,0x1,0x5,0x1,0x2,0x4a,0x4b,0xb0,0x1b,0x50,0x58,0x40,0x28,0x0,0x2,0x4,0x1,0x4,0x2,0x1,0x70,0x0,0x3,0x3,0x10, + 0x4b,0x0,0x4,0x4,0x10,0x4b,0x0,0x5,0x5,0x1,0x5b,0x0,0x1,0x1,0x11,0x4b,0x7,0x1,0x6,0x6,0x0,0x5c,0x0,0x0,0x0,0x17,0x0,0x4c,0x1b,0x4b, + 0xb0,0x20,0x50,0x58,0x40,0x26,0x0,0x2,0x4,0x1,0x4,0x2,0x1,0x70,0x0,0x1,0x0,0x5,0x6,0x1,0x5,0x63,0x0,0x3,0x3,0x10,0x4b,0x0,0x4,0x4, + 0x10,0x4b,0x7,0x1,0x6,0x6,0x0,0x5c,0x0,0x0,0x0,0x17,0x0,0x4c,0x1b,0x40,0x28,0x0,0x4,0x3,0x2,0x3,0x4,0x2,0x70,0x0,0x2,0x1,0x3,0x2, + 0x1,0x6e,0x0,0x1,0x0,0x5,0x6,0x1,0x5,0x63,0x0,0x3,0x3,0x10,0x4b,0x7,0x1,0x6,0x6,0x0,0x5c,0x0,0x0,0x0,0x17,0x0,0x4c,0x59,0x59,0x40, + 0xf,0x36,0x36,0x36,0x43,0x36,0x42,0x28,0x26,0x1d,0x26,0x26,0x2b,0x8,0x7,0x1a,0x2b,0x0,0x15,0x14,0x7,0x7,0x16,0x11,0x14,0x7,0xe,0x2,0x23,0x22, + 0x26,0x26,0x35,0x34,0x36,0x36,0x33,0x32,0x16,0x17,0x26,0x27,0x7,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x37,0x26,0x27,0x26,0x35,0x34,0x37,0x36,0x33, + 0x32,0x17,0x16,0x17,0x37,0x36,0x33,0x32,0x16,0x17,0x0,0x36,0x36,0x35,0x34,0x26,0x23,0x22,0x6,0x6,0x15,0x14,0x16,0x33,0x4,0x7c,0x2e,0xb9,0xbd,0x10, + 0x18,0x97,0xe5,0x88,0x77,0xba,0x67,0x86,0xfa,0xa4,0x43,0x7a,0x32,0x33,0x72,0xfe,0x12,0xd,0x27,0xf,0x7,0x2f,0xab,0x30,0x37,0x23,0xf,0x1a,0x20,0x12, + 0x11,0x72,0x56,0xfb,0x15,0xc,0x13,0x19,0x8,0xfe,0x30,0xab,0x5d,0x8f,0x7d,0x6d,0xab,0x60,0x8f,0x7d,0x4,0xc6,0xf,0x25,0x10,0x41,0xd5,0xfe,0xdf,0x56, + 0x59,0x86,0xc4,0x68,0x62,0xaf,0x70,0x8c,0xef,0x90,0x2c,0x27,0x81,0x67,0x59,0x6,0x30,0x17,0xb,0x24,0x10,0x3c,0x20,0x1d,0x12,0x1e,0x14,0x19,0x2d,0xa, + 0x3c,0x45,0x58,0x7,0x18,0x19,0xfb,0xa6,0x68,0xaa,0x60,0x70,0x82,0x64,0xaa,0x65,0x6f,0x82,0x0,0x3,0x0,0x85,0xff,0xea,0x5,0xe6,0x5,0x2a,0x0,0x22, + 0x0,0x34,0x0,0x42,0x0,0xd6,0x4b,0xb0,0x31,0x50,0x58,0x40,0xf,0x2,0x1,0x2,0x3,0x1d,0x1,0x7,0x6,0xf,0xc,0x2,0x0,0x7,0x3,0x4a,0x1b,0x40, + 0xf,0x2,0x1,0x2,0x5,0x1d,0x1,0x7,0x6,0xf,0xc,0x2,0x0,0x7,0x3,0x4a,0x59,0x4b,0xb0,0x28,0x50,0x58,0x40,0x26,0x0,0x4,0x2,0x6,0x2,0x4, + 0x6,0x70,0x5,0x8,0x2,0x3,0x3,0x10,0x4b,0x0,0x6,0x6,0x2,0x5b,0x0,0x2,0x2,0x19,0x4b,0x9,0x1,0x7,0x7,0x0,0x5b,0x1,0x1,0x0,0x0,0xf, + 0x0,0x4c,0x1b,0x4b,0xb0,0x31,0x50,0x58,0x40,0x2a,0x0,0x4,0x2,0x6,0x2,0x4,0x6,0x70,0x5,0x8,0x2,0x3,0x3,0x10,0x4b,0x0,0x6,0x6,0x2,0x5b, + 0x0,0x2,0x2,0x19,0x4b,0x0,0x0,0x0,0xf,0x4b,0x9,0x1,0x7,0x7,0x1,0x5b,0x0,0x1,0x1,0x17,0x1,0x4c,0x1b,0x40,0x2e,0x0,0x4,0x2,0x6,0x2, + 0x4,0x6,0x70,0x8,0x1,0x3,0x3,0x10,0x4b,0x0,0x5,0x5,0x10,0x4b,0x0,0x6,0x6,0x2,0x5b,0x0,0x2,0x2,0x19,0x4b,0x0,0x0,0x0,0xf,0x4b,0x9, + 0x1,0x7,0x7,0x1,0x5b,0x0,0x1,0x1,0x17,0x1,0x4c,0x59,0x59,0x40,0x18,0x35,0x35,0x0,0x0,0x35,0x42,0x35,0x41,0x3c,0x3a,0x34,0x32,0x2b,0x29,0x0, + 0x22,0x0,0x20,0x26,0x27,0x37,0xa,0x7,0x17,0x2b,0x0,0x16,0x15,0x14,0x7,0x3,0x6,0x6,0x23,0x23,0x22,0x26,0x35,0x34,0x37,0x37,0x6,0x6,0x23,0x22, + 0x26,0x26,0x35,0x34,0x12,0x36,0x33,0x32,0x16,0x17,0x13,0x36,0x36,0x33,0x33,0x4,0x16,0x15,0x14,0x7,0x3,0x6,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x36, + 0x36,0x33,0x33,0x0,0x36,0x36,0x35,0x34,0x26,0x23,0x22,0x6,0x6,0x15,0x14,0x16,0x33,0x4,0x19,0x1e,0x2,0xd4,0x6,0x21,0x27,0x2,0x21,0x1d,0x2,0xa, + 0x3c,0x90,0x4c,0x5f,0x92,0x51,0x69,0xd6,0x99,0x4d,0x80,0x24,0x51,0x6,0x27,0x28,0x2,0x1,0xe0,0x10,0x8,0xf4,0x14,0x25,0x19,0x1e,0x4,0x93,0x6,0x12, + 0x12,0x8e,0xfc,0x78,0x8e,0x4b,0x69,0x5a,0x60,0x8e,0x4b,0x66,0x5d,0x5,0x2a,0x15,0x18,0x4,0x10,0xfb,0x4e,0x24,0x1d,0x15,0x17,0x5,0x10,0x3a,0x42,0x45, + 0x5f,0xaf,0x76,0x8a,0x1,0xc,0xae,0x51,0x44,0x1,0xcc,0x23,0x1e,0xa,0x11,0xd,0xc,0xe,0xfe,0x94,0x1e,0x18,0x13,0x6,0xc,0x1,0x6a,0xf,0xc,0xfb, + 0x5e,0x76,0xbf,0x6a,0x79,0x88,0x7a,0xc3,0x6a,0x72,0x87,0x0,0x0,0x0,0x0,0x2,0x0,0x9f,0xff,0xea,0x5,0x3d,0x5,0x2a,0x0,0x38,0x0,0x46,0x0,0xbc, + 0x40,0x14,0x34,0x1,0x5,0x6,0x26,0x2,0x2,0x0,0x5,0x21,0x13,0x2,0x9,0x8,0x10,0x1,0x1,0x9,0x4,0x4a,0x4b,0xb0,0x1b,0x50,0x58,0x40,0x29,0x0, + 0x6,0x6,0x10,0x4b,0x4,0x1,0x0,0x0,0x5,0x5b,0xa,0x7,0x2,0x5,0x5,0xe,0x4b,0x0,0x8,0x8,0x3,0x5b,0x0,0x3,0x3,0x11,0x4b,0xb,0x1,0x9, + 0x9,0x1,0x5b,0x2,0x1,0x1,0x1,0xf,0x1,0x4c,0x1b,0x4b,0xb0,0x28,0x50,0x58,0x40,0x25,0xa,0x7,0x2,0x5,0x4,0x1,0x0,0x3,0x5,0x0,0x64,0x0, + 0x3,0x0,0x8,0x9,0x3,0x8,0x63,0x0,0x6,0x6,0x10,0x4b,0xb,0x1,0x9,0x9,0x1,0x5b,0x2,0x1,0x1,0x1,0xf,0x1,0x4c,0x1b,0x40,0x29,0xa,0x7, + 0x2,0x5,0x4,0x1,0x0,0x3,0x5,0x0,0x64,0x0,0x3,0x0,0x8,0x9,0x3,0x8,0x63,0x0,0x6,0x6,0x10,0x4b,0x0,0x1,0x1,0xf,0x4b,0xb,0x1,0x9, + 0x9,0x2,0x5b,0x0,0x2,0x2,0x17,0x2,0x4c,0x59,0x59,0x40,0x18,0x39,0x39,0x0,0x0,0x39,0x46,0x39,0x45,0x40,0x3e,0x0,0x38,0x0,0x37,0x33,0x26,0x23, + 0x26,0x27,0x33,0x26,0xc,0x7,0x1b,0x2b,0x0,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x23,0x3,0x6,0x6,0x23,0x23,0x22,0x26,0x35,0x34,0x37,0x37,0x6,0x6, + 0x23,0x22,0x26,0x26,0x35,0x34,0x36,0x36,0x33,0x32,0x16,0x17,0x13,0x23,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x33,0x37,0x36,0x36,0x33,0x33,0x32,0x16, + 0x15,0x14,0x7,0x7,0x33,0x0,0x36,0x36,0x35,0x34,0x26,0x23,0x22,0x6,0x6,0x15,0x14,0x16,0x33,0x5,0x23,0x1a,0x3,0x7,0x23,0x22,0x8a,0xa8,0x6,0x24, + 0x27,0x2,0x20,0x1b,0x2,0xc,0x44,0xa0,0x59,0x68,0x9f,0x59,0x79,0xdc,0x8d,0x5d,0x91,0x2f,0x30,0xfe,0x1b,0x1a,0x3,0x7,0x23,0x22,0xfd,0x13,0x6,0x26, + 0x29,0x2,0x23,0x1e,0x2,0x13,0x8b,0xfd,0x78,0x9e,0x51,0x82,0x6e,0x67,0x94,0x4c,0x71,0x6a,0x4,0x7e,0x16,0x18,0xb,0xf,0x27,0x21,0xfc,0x49,0x23,0x1e, + 0x14,0x16,0x7,0x10,0x48,0x47,0x4e,0x58,0xa7,0x72,0x93,0xf7,0x91,0x50,0x47,0x1,0xf,0x16,0x18,0xb,0xf,0x27,0x21,0x6b,0x23,0x1e,0x15,0x18,0x4,0x10, + 0x6b,0xfc,0x0,0x6f,0xae,0x5c,0x6f,0x7c,0x71,0xb1,0x5c,0x69,0x7d,0x0,0x0,0x0,0x0,0x2,0x0,0x9a,0xff,0xea,0x4,0x39,0x3,0xb2,0x0,0x20,0x0,0x2c, + 0x0,0x35,0x40,0x32,0x24,0x1a,0x2,0x0,0x4,0x1,0x4a,0x0,0x0,0x4,0x3,0x4,0x0,0x3,0x70,0x5,0x1,0x4,0x4,0x2,0x5b,0x0,0x2,0x2,0x19,0x4b, + 0x0,0x3,0x3,0x1,0x5b,0x0,0x1,0x1,0x17,0x1,0x4c,0x21,0x21,0x21,0x2c,0x21,0x2b,0x28,0x26,0x28,0x20,0x6,0x7,0x18,0x2b,0x24,0x33,0x32,0x17,0x16, + 0x15,0x14,0x6,0x7,0x6,0x6,0x23,0x22,0x26,0x26,0x35,0x34,0x12,0x24,0x33,0x32,0x16,0x16,0x15,0x14,0x7,0x5,0x16,0x16,0x33,0x32,0x36,0x37,0x0,0x6, + 0x6,0x7,0x25,0x36,0x36,0x35,0x34,0x26,0x26,0x23,0x3,0xb5,0x9,0x26,0x12,0x6,0x17,0x18,0x59,0xe5,0x70,0x7c,0xaf,0x5a,0x90,0x1,0x3,0xa6,0x6d,0xa2, + 0x57,0xe0,0xfd,0xd9,0x2,0x83,0x77,0x60,0xc6,0x51,0xfe,0xba,0x9f,0x68,0x16,0x1,0xf8,0x38,0x2d,0x32,0x63,0x46,0xde,0x38,0x15,0xf,0x17,0x1e,0xb,0x27, + 0x31,0x5b,0xa9,0x74,0xa4,0x1,0xf,0x9d,0x4b,0x85,0x53,0xdc,0x18,0x39,0x76,0x71,0x33,0x29,0x2,0x4a,0x4f,0x8c,0x5a,0x3a,0x6,0x30,0x2e,0x2b,0x44,0x28, + 0x0,0x0,0xff,0xff,0x0,0x9a,0xff,0xea,0x4,0x39,0x5,0x5e,0x0,0x22,0x0,0x95,0x0,0x0,0x0,0x2,0x1,0x80,0xcc,0x0,0x0,0x0,0xff,0xff,0x0,0x9a, + 0xff,0xea,0x4,0x71,0x5,0x4e,0x0,0x22,0x1,0x82,0xec,0x0,0x0,0x2,0x0,0x95,0x0,0x0,0x0,0x0,0xff,0xff,0x0,0x9a,0xff,0xea,0x4,0x4e,0x5,0x6e, + 0x0,0x26,0x1,0x83,0xec,0xa,0x1,0x2,0x0,0x95,0x0,0x0,0x0,0x8,0xb1,0x0,0x1,0xb0,0xa,0xb0,0x33,0x2b,0xff,0xff,0x0,0x9a,0xff,0xea,0x4,0x39, + 0x5,0x60,0x0,0x22,0x0,0x95,0x0,0x0,0x0,0x2,0x1,0x92,0xf6,0x0,0x0,0x0,0xff,0xff,0x0,0x9a,0xff,0xea,0x4,0x39,0x5,0x28,0x0,0x22,0x0,0x95, + 0x0,0x0,0x0,0x2,0x1,0x93,0xec,0x0,0x0,0x0,0xff,0xff,0x0,0x9a,0xff,0xea,0x4,0x39,0x5,0x51,0x0,0x22,0x0,0x95,0x0,0x0,0x0,0x2,0x1,0x94, + 0xec,0x0,0x0,0x0,0xff,0xff,0x0,0x9a,0xff,0xea,0x4,0x39,0x5,0x5e,0x0,0x22,0x0,0x95,0x0,0x0,0x0,0x2,0x1,0x95,0xc,0x0,0x0,0x0,0xff,0xff, + 0x0,0x9a,0xff,0xea,0x4,0x40,0x4,0xe2,0x0,0x22,0x0,0x95,0x0,0x0,0x0,0x2,0x1,0x97,0xf6,0x0,0x0,0x0,0x0,0x2,0x0,0x9a,0xfe,0x28,0x4,0x39, + 0x3,0xb2,0x0,0x35,0x0,0x41,0x0,0x85,0x40,0xb,0x39,0x6,0x2,0x1,0x7,0x2b,0x1,0x5,0x0,0x2,0x4a,0x4b,0xb0,0x31,0x50,0x58,0x40,0x27,0x0,0x1, + 0x7,0x0,0x7,0x1,0x0,0x70,0x3,0x1,0x2,0x0,0x4,0x2,0x4,0x5f,0x9,0x1,0x7,0x7,0x6,0x5b,0x8,0x1,0x6,0x6,0x19,0x4b,0x0,0x0,0x0,0x5, + 0x5b,0x0,0x5,0x5,0x17,0x5,0x4c,0x1b,0x40,0x2e,0x0,0x1,0x7,0x0,0x7,0x1,0x0,0x70,0x0,0x3,0x5,0x2,0x5,0x3,0x2,0x70,0x0,0x2,0x0,0x4, + 0x2,0x4,0x5f,0x9,0x1,0x7,0x7,0x6,0x5b,0x8,0x1,0x6,0x6,0x19,0x4b,0x0,0x0,0x0,0x5,0x5b,0x0,0x5,0x5,0x17,0x5,0x4c,0x59,0x40,0x15,0x36, + 0x36,0x0,0x0,0x36,0x41,0x36,0x40,0x0,0x35,0x0,0x34,0x26,0x26,0x21,0x2a,0x23,0x28,0xa,0x7,0x1a,0x2b,0x0,0x16,0x16,0x15,0x14,0x7,0x5,0x16,0x16, + 0x33,0x32,0x36,0x37,0x36,0x33,0x32,0x17,0x16,0x15,0x14,0x7,0x6,0x6,0x15,0x14,0x16,0x33,0x32,0x37,0x37,0x32,0x15,0x14,0x6,0x7,0x6,0x6,0x23,0x22, + 0x26,0x35,0x34,0x36,0x37,0x6,0x23,0x22,0x26,0x26,0x35,0x34,0x12,0x24,0x33,0xe,0x2,0x7,0x25,0x36,0x36,0x35,0x34,0x26,0x26,0x23,0x3,0x40,0xa2,0x57, + 0xe0,0xfd,0xd9,0x2,0x83,0x77,0x60,0xc6,0x51,0x10,0x9,0x26,0x12,0x6,0x18,0xad,0x9b,0x22,0x1f,0x33,0x3c,0x8,0x28,0x12,0x13,0x26,0x5e,0x2e,0x4f,0x56, + 0x60,0x5d,0x4e,0x50,0x7c,0xaf,0x5a,0x90,0x1,0x3,0xa6,0x74,0x9f,0x68,0x16,0x1,0xf8,0x38,0x2d,0x32,0x63,0x46,0x3,0xb2,0x4b,0x85,0x53,0xdc,0x18,0x39, + 0x76,0x71,0x33,0x29,0x7,0x38,0x13,0xf,0x1c,0x12,0x7b,0xb0,0x40,0x1d,0x1e,0xa,0x1,0x42,0x14,0x19,0x7,0xd,0x10,0x58,0x4a,0x4d,0x94,0x4b,0xc,0x5b, + 0xa9,0x74,0xa4,0x1,0xf,0x9d,0x91,0x4f,0x8c,0x5a,0x3a,0x6,0x30,0x2e,0x2b,0x44,0x28,0x0,0x0,0x0,0x0,0x1,0x0,0xfb,0xfe,0x72,0x4,0xe5,0x5,0x25, + 0x0,0x31,0x0,0x3f,0x40,0x3c,0x1,0x1,0x1,0x7,0x25,0x10,0x2,0x3,0x2,0x1e,0x1,0x4,0x3,0x3,0x4a,0x0,0x0,0x1,0x2,0x1,0x0,0x2,0x70,0x6, + 0x1,0x2,0x5,0x1,0x3,0x4,0x2,0x3,0x61,0x0,0x1,0x1,0x7,0x5b,0x0,0x7,0x7,0x10,0x4b,0x0,0x4,0x4,0x13,0x4,0x4c,0x23,0x26,0x25,0x33,0x26, + 0x22,0x23,0x14,0x8,0x7,0x1c,0x2b,0x0,0x15,0x14,0x7,0x6,0x23,0x22,0x27,0x26,0x23,0x22,0x7,0x7,0x21,0x32,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x21, + 0x3,0x6,0x6,0x23,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x23,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x33,0x37,0x36,0x36,0x33,0x32,0x17,0x4,0xe5,0x6, + 0x13,0x26,0xa,0xf,0x75,0x6f,0xca,0x26,0x13,0x1,0x59,0x1c,0x19,0x2,0x8,0x22,0x22,0xfe,0xa6,0xb6,0x6,0x27,0x28,0x2,0x23,0x1e,0x2,0xb6,0xc6,0x1b, + 0x1a,0x3,0x7,0x22,0x22,0xc6,0x16,0x1e,0xd0,0xa6,0x86,0x7b,0x4,0xdf,0x27,0xe,0x14,0x37,0x5,0x2f,0xd9,0x6e,0x16,0x19,0xf,0xa,0x27,0x21,0xfb,0xf7, + 0x23,0x1e,0x15,0x18,0x4,0x10,0x4,0x9,0x15,0x18,0xc,0xf,0x27,0x21,0x7b,0xa6,0xb8,0x33,0x0,0x2,0x0,0x4c,0xfe,0x66,0x4,0x61,0x3,0xb2,0x0,0x2c, + 0x0,0x3a,0x0,0xbf,0x40,0x12,0xa,0x1,0x7,0x0,0x2,0x1,0x6,0x7,0x21,0x1,0x4,0x6,0x14,0x1,0x1,0x3,0x4,0x4a,0x4b,0xb0,0x24,0x50,0x58,0x40, + 0x2a,0x0,0x2,0x4,0x3,0x4,0x2,0x3,0x70,0x9,0x1,0x7,0x7,0x0,0x5b,0x8,0x5,0x2,0x0,0x0,0x11,0x4b,0x0,0x6,0x6,0x4,0x5b,0x0,0x4,0x4, + 0xf,0x4b,0x0,0x3,0x3,0x1,0x5b,0x0,0x1,0x1,0x13,0x1,0x4c,0x1b,0x4b,0xb0,0x28,0x50,0x58,0x40,0x28,0x0,0x2,0x4,0x3,0x4,0x2,0x3,0x70,0x0, + 0x6,0x0,0x4,0x2,0x6,0x4,0x63,0x9,0x1,0x7,0x7,0x0,0x5b,0x8,0x5,0x2,0x0,0x0,0x11,0x4b,0x0,0x3,0x3,0x1,0x5b,0x0,0x1,0x1,0x13,0x1, + 0x4c,0x1b,0x40,0x2c,0x0,0x2,0x4,0x3,0x4,0x2,0x3,0x70,0x0,0x6,0x0,0x4,0x2,0x6,0x4,0x63,0x0,0x0,0x0,0x11,0x4b,0x9,0x1,0x7,0x7,0x5, + 0x5b,0x8,0x1,0x5,0x5,0x19,0x4b,0x0,0x3,0x3,0x1,0x5b,0x0,0x1,0x1,0x13,0x1,0x4c,0x59,0x59,0x40,0x16,0x2d,0x2d,0x0,0x0,0x2d,0x3a,0x2d,0x39, + 0x34,0x32,0x0,0x2c,0x0,0x2b,0x25,0x24,0x16,0x27,0x35,0xa,0x7,0x19,0x2b,0x0,0x16,0x17,0x37,0x36,0x36,0x33,0x33,0x32,0x16,0x15,0x14,0x7,0x3,0x6, + 0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x36,0x33,0x32,0x17,0x16,0x16,0x33,0x32,0x36,0x37,0x37,0x6,0x6,0x23,0x22,0x26,0x26,0x35,0x34,0x36,0x36,0x33, + 0xe,0x2,0x15,0x14,0x16,0x33,0x32,0x36,0x36,0x35,0x34,0x26,0x23,0x2,0xfc,0x96,0x2e,0x13,0x6,0x21,0x27,0x2,0x21,0x1d,0x2,0xa1,0x20,0xf5,0xc9,0xc9, + 0xa7,0x24,0x9,0x15,0x25,0xe,0xa,0x49,0xac,0x54,0x87,0xa3,0x13,0x20,0x49,0xa2,0x61,0x6b,0xa6,0x5b,0x7c,0xea,0x9e,0x7b,0x9e,0x52,0x7c,0x72,0x76,0xa6, + 0x54,0x8d,0x76,0x3,0xb2,0x5e,0x56,0x67,0x24,0x1d,0x15,0x17,0x5,0x10,0xfc,0x73,0xb3,0xbf,0x46,0xe,0x25,0x13,0x18,0x37,0x5,0x20,0x22,0x84,0x71,0xb9, + 0x47,0x45,0x5b,0xa9,0x70,0x91,0xfa,0x97,0x94,0x6f,0xb1,0x62,0x6c,0x80,0x71,0xb0,0x5b,0x72,0x80,0x0,0x0,0x0,0xff,0xff,0x0,0x4c,0xfe,0x66,0x4,0x67, + 0x5,0x4e,0x0,0x22,0x1,0x82,0xe2,0x0,0x0,0x2,0x0,0xa0,0x0,0x0,0x0,0x0,0xff,0xff,0x0,0x4c,0xfe,0x66,0x4,0x61,0x5,0x60,0x0,0x22,0x1,0x92, + 0xec,0x0,0x0,0x2,0x0,0xa0,0x0,0x0,0x0,0x0,0x0,0x3,0x0,0x4c,0xfe,0x66,0x4,0x61,0x5,0xa3,0x0,0x11,0x0,0x3e,0x0,0x4c,0x0,0xe3,0x40,0x16, + 0xe,0x1,0x1,0x0,0x1c,0x1,0x9,0x2,0x14,0x1,0x8,0x9,0x33,0x1,0x6,0x8,0x26,0x1,0x3,0x5,0x5,0x4a,0x4b,0xb0,0x24,0x50,0x58,0x40,0x34,0x0, + 0x0,0x1,0x0,0x72,0x0,0x1,0x2,0x1,0x72,0x0,0x4,0x6,0x5,0x6,0x4,0x5,0x70,0xb,0x1,0x9,0x9,0x2,0x5b,0xa,0x7,0x2,0x2,0x2,0x11,0x4b, + 0x0,0x8,0x8,0x6,0x5b,0x0,0x6,0x6,0xf,0x4b,0x0,0x5,0x5,0x3,0x5b,0x0,0x3,0x3,0x13,0x3,0x4c,0x1b,0x4b,0xb0,0x28,0x50,0x58,0x40,0x32,0x0, + 0x0,0x1,0x0,0x72,0x0,0x1,0x2,0x1,0x72,0x0,0x4,0x6,0x5,0x6,0x4,0x5,0x70,0x0,0x8,0x0,0x6,0x4,0x8,0x6,0x63,0xb,0x1,0x9,0x9,0x2, + 0x5b,0xa,0x7,0x2,0x2,0x2,0x11,0x4b,0x0,0x5,0x5,0x3,0x5b,0x0,0x3,0x3,0x13,0x3,0x4c,0x1b,0x40,0x36,0x0,0x0,0x1,0x0,0x72,0x0,0x1,0x7, + 0x1,0x72,0x0,0x4,0x6,0x5,0x6,0x4,0x5,0x70,0x0,0x8,0x0,0x6,0x4,0x8,0x6,0x63,0x0,0x2,0x2,0x11,0x4b,0xb,0x1,0x9,0x9,0x7,0x5b,0xa, + 0x1,0x7,0x7,0x19,0x4b,0x0,0x5,0x5,0x3,0x5b,0x0,0x3,0x3,0x13,0x3,0x4c,0x59,0x59,0x40,0x18,0x3f,0x3f,0x12,0x12,0x3f,0x4c,0x3f,0x4b,0x46,0x44, + 0x12,0x3e,0x12,0x3d,0x25,0x24,0x16,0x27,0x3a,0x37,0x21,0xc,0x7,0x1b,0x2b,0x0,0x36,0x33,0x32,0x16,0x15,0x14,0x7,0x3,0x6,0x6,0x23,0x23,0x22,0x35, + 0x34,0x37,0x13,0x2,0x16,0x17,0x37,0x36,0x36,0x33,0x33,0x32,0x16,0x15,0x14,0x7,0x3,0x6,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x36,0x33,0x32,0x17, + 0x16,0x16,0x33,0x32,0x36,0x37,0x37,0x6,0x6,0x23,0x22,0x26,0x26,0x35,0x34,0x36,0x36,0x33,0xe,0x2,0x15,0x14,0x16,0x33,0x32,0x36,0x36,0x35,0x34,0x26, + 0x23,0x3,0x25,0x28,0x17,0x16,0x17,0x4,0x6e,0x6,0x19,0x11,0x8e,0x18,0xb,0xc6,0x1e,0x96,0x2e,0x13,0x6,0x21,0x27,0x2,0x21,0x1d,0x2,0xa1,0x20,0xf5, + 0xc9,0xc9,0xa7,0x24,0x9,0x15,0x25,0xe,0xa,0x49,0xac,0x54,0x87,0xa3,0x13,0x20,0x49,0xa2,0x61,0x6b,0xa6,0x5b,0x7c,0xea,0x9e,0x7b,0x9e,0x52,0x7c,0x72, + 0x76,0xa6,0x54,0x8d,0x76,0x5,0x8f,0x14,0x12,0x11,0xa,0xb,0xfe,0xdd,0x10,0x10,0x15,0xf,0xf,0x1,0x23,0xfe,0x34,0x5e,0x56,0x67,0x24,0x1d,0x15,0x17, + 0x5,0x10,0xfc,0x73,0xb3,0xbf,0x46,0xe,0x25,0x13,0x18,0x37,0x5,0x20,0x22,0x84,0x71,0xb9,0x47,0x45,0x5b,0xa9,0x70,0x91,0xfa,0x97,0x94,0x6f,0xb1,0x62, + 0x6c,0x80,0x71,0xb0,0x5b,0x72,0x80,0x0,0xff,0xff,0x0,0x4c,0xfe,0x66,0x4,0x61,0x5,0x51,0x0,0x22,0x0,0xa0,0x0,0x0,0x0,0x2,0x1,0x94,0xe2,0x0, + 0x0,0x0,0x0,0x1,0x0,0x8b,0xff,0xf6,0x4,0x40,0x5,0x2a,0x0,0x31,0x0,0x33,0x40,0x30,0x2,0x1,0x0,0x4,0x2a,0x16,0x5,0x3,0x1,0x2,0x2,0x4a, + 0x5,0x1,0x4,0x4,0x10,0x4b,0x0,0x2,0x2,0x0,0x5b,0x0,0x0,0x0,0x19,0x4b,0x3,0x1,0x1,0x1,0xf,0x1,0x4c,0x0,0x0,0x0,0x31,0x0,0x2f,0x36, + 0x29,0x38,0x27,0x6,0x7,0x18,0x2b,0x0,0x16,0x15,0x14,0x7,0x3,0x36,0x36,0x33,0x32,0x16,0x16,0x15,0x14,0x7,0x3,0x6,0x6,0x23,0x23,0x22,0x26,0x35, + 0x34,0x37,0x13,0x36,0x35,0x34,0x26,0x23,0x22,0x6,0x6,0x7,0x3,0x6,0x6,0x23,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x36,0x36,0x33,0x33,0x1,0xdb,0x1e, + 0x2,0x5d,0x54,0xd8,0x67,0x55,0x7c,0x42,0x7,0x62,0x6,0x27,0x28,0x2,0x23,0x1e,0x2,0x61,0x5,0x4c,0x4d,0x56,0xba,0x8c,0x13,0x3c,0x6,0x27,0x28,0x2, + 0x23,0x1e,0x2,0xd4,0x6,0x27,0x28,0x2,0x5,0x2a,0x15,0x18,0x4,0x10,0xfd,0xf2,0x61,0x76,0x40,0x75,0x4f,0x26,0x26,0xfd,0xd5,0x23,0x1e,0x15,0x18,0x4, + 0x10,0x2,0x26,0x20,0x16,0x42,0x49,0x6d,0xbb,0x6e,0xfe,0xaf,0x23,0x1e,0x15,0x18,0x4,0x10,0x4,0xb2,0x23,0x1e,0x0,0x1,0x0,0x88,0xff,0xf6,0x4,0x21, + 0x5,0x2a,0x0,0x46,0x0,0x74,0x40,0x11,0x35,0x1,0x4,0x5,0x3c,0x27,0x2,0x3,0x4,0x43,0x20,0xc,0x3,0x0,0x1,0x3,0x4a,0x4b,0xb0,0x1b,0x50,0x58, + 0x40,0x23,0x0,0x5,0x5,0x10,0x4b,0x7,0x1,0x3,0x3,0x4,0x5b,0x6,0x1,0x4,0x4,0xe,0x4b,0x0,0x1,0x1,0x8,0x5b,0x9,0x1,0x8,0x8,0x11,0x4b, + 0x2,0x1,0x0,0x0,0xf,0x0,0x4c,0x1b,0x40,0x1f,0x6,0x1,0x4,0x7,0x1,0x3,0x8,0x4,0x3,0x64,0x9,0x1,0x8,0x0,0x1,0x0,0x8,0x1,0x63,0x0, + 0x5,0x5,0x10,0x4b,0x2,0x1,0x0,0x0,0xf,0x0,0x4c,0x59,0x40,0x11,0x0,0x0,0x0,0x46,0x0,0x45,0x26,0x25,0x33,0x26,0x25,0x36,0x29,0x37,0xa,0x7, + 0x1c,0x2b,0x0,0x16,0x15,0x14,0x7,0x3,0x6,0x6,0x23,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x36,0x35,0x34,0x26,0x23,0x22,0x6,0x6,0x7,0x3,0x6,0x6, + 0x23,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x23,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x33,0x37,0x36,0x36,0x33,0x33,0x32,0x16,0x15,0x14,0x7,0x7,0x33, + 0x32,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x23,0x3,0x36,0x36,0x33,0x3,0x96,0x8b,0x7,0x57,0x6,0x27,0x28,0x2,0x23,0x1e,0x2,0x56,0x5,0x46,0x49,0x4e, + 0xac,0x82,0x12,0x34,0x6,0x27,0x28,0x2,0x23,0x1e,0x2,0xa8,0x8c,0x1b,0x1a,0x3,0x7,0x23,0x22,0x8b,0x13,0x6,0x27,0x28,0x2,0x23,0x1e,0x2,0x13,0xfd, + 0x1b,0x1a,0x3,0x7,0x23,0x22,0xfc,0x38,0x53,0xc2,0x5c,0x3,0x76,0x8b,0x78,0x27,0x27,0xfe,0x12,0x23,0x1e,0x15,0x18,0x4,0x10,0x1,0xea,0x1c,0x1b,0x42, + 0x48,0x68,0xb2,0x68,0xfe,0xd7,0x23,0x1e,0x15,0x18,0x4,0x10,0x3,0xb7,0x16,0x18,0xb,0xf,0x27,0x21,0x6b,0x23,0x1e,0x15,0x18,0x4,0x10,0x6b,0x16,0x18, + 0xb,0xf,0x27,0x21,0xfe,0xc8,0x59,0x67,0xff,0xff,0x0,0x8b,0xff,0xf6,0x4,0x65,0x6,0xd2,0x0,0x27,0x1,0x92,0x0,0x28,0x1,0x72,0x1,0x2,0x0,0xa5, + 0x0,0x0,0x0,0x9,0xb1,0x0,0x1,0xb8,0x1,0x72,0xb0,0x33,0x2b,0x0,0x0,0x2,0x0,0xf4,0xff,0xea,0x4,0x10,0x5,0x53,0x0,0x11,0x0,0x39,0x0,0x88, + 0x40,0x10,0xb,0x2,0x2,0x0,0x1,0x33,0x14,0x2,0x5,0x6,0x24,0x1,0x4,0x2,0x3,0x4a,0x4b,0xb0,0x19,0x50,0x58,0x40,0x29,0x0,0x3,0x5,0x2,0x5, + 0x3,0x2,0x70,0x0,0x0,0x0,0x1,0x5b,0x7,0x1,0x1,0x1,0x10,0x4b,0x0,0x5,0x5,0x6,0x59,0x8,0x1,0x6,0x6,0x11,0x4b,0x0,0x2,0x2,0x4,0x5b, + 0x0,0x4,0x4,0x17,0x4,0x4c,0x1b,0x40,0x27,0x0,0x3,0x5,0x2,0x5,0x3,0x2,0x70,0x7,0x1,0x1,0x0,0x0,0x6,0x1,0x0,0x63,0x0,0x5,0x5,0x6, + 0x59,0x8,0x1,0x6,0x6,0x11,0x4b,0x0,0x2,0x2,0x4,0x5b,0x0,0x4,0x4,0x17,0x4,0x4c,0x59,0x40,0x18,0x12,0x12,0x0,0x0,0x12,0x39,0x12,0x37,0x31, + 0x2f,0x2a,0x28,0x21,0x1f,0x1d,0x1b,0x0,0x11,0x0,0x10,0x27,0x9,0x7,0x15,0x2b,0x0,0x16,0x15,0x14,0x7,0x7,0x6,0x6,0x23,0x22,0x26,0x35,0x34,0x37, + 0x37,0x36,0x36,0x33,0x2,0x16,0x15,0x14,0x7,0x3,0x6,0x15,0x14,0x16,0x33,0x32,0x37,0x36,0x33,0x32,0x17,0x16,0x15,0x14,0x6,0x7,0x6,0x23,0x22,0x26, + 0x35,0x34,0x37,0x13,0x23,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x21,0x2,0xf3,0x26,0x2,0x17,0x6,0x2d,0x37,0x30,0x26,0x2,0x17,0x6,0x2e,0x37,0x14, + 0x1b,0x2,0x60,0x7,0x40,0x46,0x6a,0x70,0x10,0x6,0x29,0xc,0x3,0x18,0x1a,0x87,0x7e,0x81,0x8c,0x7,0x53,0xfd,0x1b,0x1a,0x3,0x7,0x22,0x22,0x1,0x52, + 0x5,0x53,0x15,0x19,0x5,0xe,0x82,0x24,0x1d,0x15,0x19,0x5,0xe,0x82,0x24,0x1d,0xfe,0x49,0x15,0x18,0xe,0x9,0xfd,0xde,0x2a,0x18,0x3f,0x39,0x22,0x4, + 0x39,0xf,0xd,0x19,0x1d,0x8,0x25,0x85,0x76,0x28,0x26,0x1,0xd9,0x16,0x18,0xb,0xf,0x27,0x21,0x0,0x0,0x0,0x0,0x1,0x0,0xf4,0xff,0xea,0x4,0x10, + 0x3,0x9c,0x0,0x27,0x0,0x39,0x40,0x36,0x21,0x2,0x2,0x3,0x4,0x12,0x1,0x2,0x0,0x2,0x4a,0x0,0x1,0x3,0x0,0x3,0x1,0x0,0x70,0x0,0x3,0x3, + 0x4,0x59,0x5,0x1,0x4,0x4,0x11,0x4b,0x0,0x0,0x0,0x2,0x5b,0x0,0x2,0x2,0x17,0x2,0x4c,0x0,0x0,0x0,0x27,0x0,0x25,0x25,0x27,0x22,0x29,0x6, + 0x7,0x18,0x2b,0x0,0x16,0x15,0x14,0x7,0x3,0x6,0x15,0x14,0x16,0x33,0x32,0x37,0x36,0x33,0x32,0x17,0x16,0x15,0x14,0x6,0x7,0x6,0x23,0x22,0x26,0x35, + 0x34,0x37,0x13,0x23,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x21,0x2,0xb0,0x1b,0x2,0x60,0x7,0x40,0x46,0x6a,0x70,0x10,0x6,0x29,0xc,0x3,0x18,0x1a, + 0x87,0x7e,0x81,0x8c,0x7,0x53,0xfd,0x1b,0x1a,0x3,0x7,0x22,0x22,0x1,0x52,0x3,0x9c,0x15,0x18,0xe,0x9,0xfd,0xde,0x2a,0x18,0x3f,0x39,0x22,0x4,0x39, + 0xf,0xd,0x19,0x1d,0x8,0x25,0x85,0x76,0x28,0x26,0x1,0xd9,0x16,0x18,0xb,0xf,0x27,0x21,0xff,0xff,0x0,0xf4,0xff,0xea,0x4,0x10,0x5,0x5e,0x0,0x22, + 0x0,0xa9,0x0,0x0,0x0,0x2,0x1,0x80,0x86,0x0,0x0,0x0,0xff,0xff,0x0,0xf4,0xff,0xea,0x4,0x10,0x5,0x4e,0x0,0x22,0x0,0xa9,0x0,0x0,0x0,0x3, + 0x1,0x82,0xff,0x74,0x0,0x0,0xff,0xff,0x0,0xf4,0xff,0xea,0x4,0x10,0x5,0x60,0x0,0x22,0x0,0xa9,0x0,0x0,0x0,0x2,0x1,0x92,0x88,0x0,0x0,0x0, + 0xff,0xff,0x0,0xf4,0xff,0xea,0x4,0x10,0x5,0x28,0x0,0x22,0x0,0xa9,0x0,0x0,0x0,0x2,0x1,0x93,0x9c,0x0,0x0,0x0,0xff,0xff,0x0,0xf4,0xff,0xea, + 0x4,0x10,0x5,0x51,0x0,0x22,0x0,0xa9,0x0,0x0,0x0,0x2,0x1,0x94,0xc4,0x0,0x0,0x0,0xff,0xff,0x0,0xf4,0xff,0xea,0x4,0x10,0x5,0x5e,0x0,0x22, + 0x0,0xa9,0x0,0x0,0x0,0x3,0x1,0x95,0xff,0x6c,0x0,0x0,0x0,0x4,0x0,0xb3,0xfe,0x66,0x4,0x84,0x5,0x53,0x0,0x11,0x0,0x23,0x0,0x37,0x0,0x56, + 0x0,0xa3,0x40,0x13,0x1d,0x14,0xb,0x2,0x4,0x0,0x1,0x3a,0x30,0x26,0x3,0x4,0x5,0x45,0x1,0x6,0x8,0x3,0x4a,0x4b,0xb0,0x19,0x50,0x58,0x40,0x2e, + 0x0,0x7,0x4,0x8,0x4,0x7,0x8,0x70,0x2,0x1,0x0,0x0,0x1,0x5b,0xb,0x3,0xa,0x3,0x1,0x1,0x10,0x4b,0xd,0x9,0xc,0x3,0x5,0x5,0x11,0x4b, + 0x0,0x4,0x4,0xf,0x4b,0x0,0x8,0x8,0x6,0x5b,0x0,0x6,0x6,0x13,0x6,0x4c,0x1b,0x40,0x2c,0x0,0x7,0x4,0x8,0x4,0x7,0x8,0x70,0xb,0x3,0xa, + 0x3,0x1,0x2,0x1,0x0,0x5,0x1,0x0,0x63,0xd,0x9,0xc,0x3,0x5,0x5,0x11,0x4b,0x0,0x4,0x4,0xf,0x4b,0x0,0x8,0x8,0x6,0x5b,0x0,0x6,0x6, + 0x13,0x6,0x4c,0x59,0x40,0x26,0x38,0x38,0x24,0x24,0x12,0x12,0x0,0x0,0x38,0x56,0x38,0x54,0x4f,0x4d,0x49,0x48,0x41,0x3f,0x24,0x37,0x24,0x35,0x2e,0x2b, + 0x12,0x23,0x12,0x22,0x1b,0x19,0x0,0x11,0x0,0x10,0x27,0xe,0x7,0x15,0x2b,0x0,0x16,0x15,0x14,0x7,0x7,0x6,0x6,0x23,0x22,0x26,0x35,0x34,0x37,0x37, + 0x36,0x36,0x33,0x20,0x16,0x15,0x14,0x7,0x7,0x6,0x6,0x23,0x22,0x26,0x35,0x34,0x37,0x37,0x36,0x36,0x33,0x0,0x16,0x15,0x14,0x7,0x3,0x6,0x6,0x23, + 0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x36,0x36,0x33,0x33,0x20,0x16,0x15,0x14,0x7,0x3,0x6,0x6,0x23,0x22,0x26,0x27,0x26,0x35,0x34,0x37,0x36,0x33,0x32, + 0x17,0x16,0x16,0x33,0x32,0x36,0x37,0x13,0x36,0x36,0x33,0x33,0x2,0xc,0x26,0x2,0x17,0x6,0x2d,0x37,0x30,0x26,0x2,0x17,0x6,0x2e,0x37,0x2,0x81,0x26, + 0x2,0x17,0x6,0x2d,0x37,0x30,0x26,0x2,0x17,0x6,0x2e,0x37,0xfd,0x90,0x1e,0x2,0x90,0x6,0x27,0x28,0x2,0x23,0x1e,0x2,0x90,0x6,0x27,0x28,0x2,0x2, + 0x75,0x1e,0x2,0xa3,0x1f,0xb4,0xa9,0x4a,0x92,0x3e,0x21,0xc,0x17,0x22,0xd,0xe,0x3a,0x79,0x38,0x62,0x62,0x14,0xa1,0x6,0x27,0x28,0x2,0x5,0x53,0x15, + 0x19,0x5,0xe,0x82,0x24,0x1d,0x15,0x19,0x5,0xe,0x82,0x24,0x1d,0x15,0x19,0x5,0xe,0x82,0x24,0x1d,0x15,0x19,0x5,0xe,0x82,0x24,0x1d,0xfe,0x53,0x15, + 0x18,0x4,0x10,0xfc,0xd2,0x23,0x1e,0x15,0x18,0x4,0x10,0x3,0x2e,0x23,0x1e,0x15,0x18,0x4,0x10,0xfc,0x62,0xb1,0xb0,0x26,0x23,0x13,0x1e,0x13,0x1b,0x33, + 0x8,0x1f,0x22,0x6a,0x72,0x3,0x91,0x23,0x1e,0x0,0x0,0x0,0xff,0xff,0x0,0xf4,0xff,0xea,0x4,0x10,0x4,0xe2,0x0,0x22,0x0,0xa9,0x0,0x0,0x0,0x3, + 0x1,0x97,0xff,0x7e,0x0,0x0,0x0,0x2,0x0,0xf4,0xfe,0x28,0x4,0x10,0x5,0x53,0x0,0x11,0x0,0x50,0x0,0xd7,0x40,0x11,0xb,0x2,0x2,0x1,0x0,0x42, + 0x39,0x2,0x6,0x7,0x2f,0x13,0x2,0x2,0x8,0x3,0x4a,0x4b,0xb0,0x19,0x50,0x58,0x40,0x30,0x0,0x9,0x6,0x8,0x6,0x9,0x8,0x70,0x4,0x1,0x3,0x0, + 0x5,0x3,0x5,0x5f,0xa,0x1,0x1,0x1,0x0,0x5b,0x0,0x0,0x0,0x10,0x4b,0x0,0x6,0x6,0x7,0x59,0x0,0x7,0x7,0x11,0x4b,0x0,0x8,0x8,0x2,0x5b, + 0x0,0x2,0x2,0x17,0x2,0x4c,0x1b,0x4b,0xb0,0x31,0x50,0x58,0x40,0x2e,0x0,0x9,0x6,0x8,0x6,0x9,0x8,0x70,0x0,0x0,0xa,0x1,0x1,0x7,0x0,0x1, + 0x63,0x4,0x1,0x3,0x0,0x5,0x3,0x5,0x5f,0x0,0x6,0x6,0x7,0x59,0x0,0x7,0x7,0x11,0x4b,0x0,0x8,0x8,0x2,0x5b,0x0,0x2,0x2,0x17,0x2,0x4c, + 0x1b,0x40,0x35,0x0,0x9,0x6,0x8,0x6,0x9,0x8,0x70,0x0,0x4,0x2,0x3,0x2,0x4,0x3,0x70,0x0,0x0,0xa,0x1,0x1,0x7,0x0,0x1,0x63,0x0,0x3, + 0x0,0x5,0x3,0x5,0x5f,0x0,0x6,0x6,0x7,0x59,0x0,0x7,0x7,0x11,0x4b,0x0,0x8,0x8,0x2,0x5b,0x0,0x2,0x2,0x17,0x2,0x4c,0x59,0x59,0x40,0x1a, + 0x0,0x0,0x4f,0x4d,0x4b,0x49,0x40,0x3d,0x37,0x35,0x2a,0x28,0x22,0x20,0x1f,0x1d,0x18,0x17,0x0,0x11,0x0,0x10,0x27,0xb,0x7,0x15,0x2b,0x0,0x26,0x35, + 0x34,0x37,0x37,0x36,0x36,0x33,0x32,0x16,0x15,0x14,0x7,0x7,0x6,0x6,0x23,0x0,0x15,0x14,0x6,0x7,0x6,0x7,0x6,0x6,0x15,0x14,0x16,0x33,0x32,0x37, + 0x37,0x32,0x15,0x14,0x6,0x7,0x6,0x6,0x23,0x22,0x26,0x35,0x34,0x36,0x37,0x26,0x26,0x35,0x34,0x37,0x13,0x23,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33, + 0x21,0x32,0x16,0x15,0x14,0x7,0x3,0x6,0x15,0x14,0x16,0x33,0x32,0x37,0x36,0x33,0x32,0x17,0x2,0x66,0x26,0x2,0x17,0x6,0x2e,0x37,0x2f,0x26,0x2,0x17, + 0x6,0x2d,0x37,0x1,0x7a,0x18,0x1a,0x6a,0x68,0x62,0x5d,0x22,0x1f,0x33,0x3c,0x8,0x28,0x12,0x13,0x26,0x5e,0x2e,0x4f,0x56,0x60,0x5f,0x4f,0x55,0x7,0x53, + 0xfd,0x1b,0x1a,0x3,0x7,0x22,0x22,0x1,0x52,0x1c,0x1b,0x2,0x60,0x7,0x40,0x46,0x6a,0x70,0x10,0x6,0x29,0xc,0x4,0x4f,0x15,0x19,0x5,0xe,0x82,0x24, + 0x1d,0x15,0x19,0x5,0xe,0x82,0x24,0x1d,0xfc,0xb,0xd,0x19,0x1d,0x8,0x1d,0x6,0x51,0x7e,0x32,0x1d,0x1e,0xa,0x1,0x42,0x14,0x19,0x7,0xd,0x10,0x58, + 0x4a,0x4e,0x94,0x4c,0x18,0x7a,0x5b,0x28,0x26,0x1,0xd9,0x16,0x18,0xb,0xf,0x27,0x21,0x15,0x18,0xe,0x9,0xfd,0xde,0x2a,0x18,0x3f,0x39,0x22,0x4,0x39, + 0xff,0xff,0x0,0xf4,0xff,0xea,0x4,0x10,0x5,0x11,0x0,0x22,0x1,0x9a,0x9c,0x0,0x0,0x2,0x0,0xa9,0x0,0x0,0x0,0x0,0x0,0x2,0x0,0x53,0xfe,0x66, + 0x4,0x27,0x5,0x53,0x0,0x11,0x0,0x36,0x0,0x84,0x40,0xc,0xb,0x2,0x2,0x0,0x1,0x30,0x14,0x2,0x5,0x6,0x2,0x4a,0x4b,0xb0,0x19,0x50,0x58,0x40, + 0x29,0x0,0x3,0x5,0x4,0x5,0x3,0x4,0x70,0x0,0x0,0x0,0x1,0x5b,0x7,0x1,0x1,0x1,0x10,0x4b,0x0,0x5,0x5,0x6,0x59,0x8,0x1,0x6,0x6,0x11, + 0x4b,0x0,0x4,0x4,0x2,0x5b,0x0,0x2,0x2,0x13,0x2,0x4c,0x1b,0x40,0x27,0x0,0x3,0x5,0x4,0x5,0x3,0x4,0x70,0x7,0x1,0x1,0x0,0x0,0x6,0x1, + 0x0,0x63,0x0,0x5,0x5,0x6,0x59,0x8,0x1,0x6,0x6,0x11,0x4b,0x0,0x4,0x4,0x2,0x5b,0x0,0x2,0x2,0x13,0x2,0x4c,0x59,0x40,0x18,0x12,0x12,0x0, + 0x0,0x12,0x36,0x12,0x34,0x2e,0x2c,0x29,0x27,0x23,0x22,0x1b,0x19,0x0,0x11,0x0,0x10,0x27,0x9,0x7,0x15,0x2b,0x0,0x16,0x15,0x14,0x7,0x7,0x6,0x6, + 0x23,0x22,0x26,0x35,0x34,0x37,0x37,0x36,0x36,0x33,0x2,0x16,0x15,0x14,0x7,0x3,0x6,0x6,0x23,0x22,0x26,0x27,0x26,0x35,0x34,0x37,0x36,0x33,0x32,0x17, + 0x16,0x16,0x33,0x32,0x36,0x37,0x13,0x21,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x21,0x4,0x1,0x26,0x2,0x17,0x6,0x2d,0x37,0x30,0x26,0x2,0x17,0x6, + 0x2e,0x37,0x12,0x19,0x2,0xa0,0x1f,0xb4,0xa9,0x53,0xb3,0x46,0x1c,0x11,0x1a,0x20,0x12,0xf,0x3e,0x8b,0x43,0x62,0x62,0x14,0x91,0xfe,0x59,0x1b,0x1a,0x3, + 0x7,0x22,0x22,0x1,0xfe,0x5,0x53,0x15,0x19,0x5,0xe,0x82,0x24,0x1d,0x15,0x19,0x5,0xe,0x82,0x24,0x1d,0xfe,0x49,0x14,0x18,0xf,0x9,0xfc,0x6f,0xb1, + 0xb0,0x3f,0x32,0x14,0x1b,0x15,0x1e,0x2c,0xc,0x2c,0x35,0x6a,0x72,0x3,0x38,0x16,0x18,0xb,0xf,0x27,0x21,0x0,0x0,0x0,0x0,0x1,0x0,0x53,0xfe,0x66, + 0x3,0xd9,0x3,0x9c,0x0,0x24,0x0,0x35,0x40,0x32,0x1e,0x2,0x2,0x3,0x4,0x1,0x4a,0x0,0x1,0x3,0x2,0x3,0x1,0x2,0x70,0x0,0x3,0x3,0x4,0x59, + 0x5,0x1,0x4,0x4,0x11,0x4b,0x0,0x2,0x2,0x0,0x5b,0x0,0x0,0x0,0x13,0x0,0x4c,0x0,0x0,0x0,0x24,0x0,0x22,0x23,0x24,0x17,0x27,0x6,0x7,0x18, + 0x2b,0x0,0x16,0x15,0x14,0x7,0x3,0x6,0x6,0x23,0x22,0x26,0x27,0x26,0x35,0x34,0x37,0x36,0x33,0x32,0x17,0x16,0x16,0x33,0x32,0x36,0x37,0x13,0x21,0x22, + 0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x21,0x3,0xc0,0x19,0x2,0xa0,0x1f,0xb4,0xa9,0x53,0xb3,0x46,0x1c,0x11,0x1a,0x20,0x12,0xf,0x3e,0x8b,0x43,0x62,0x62, + 0x14,0x91,0xfe,0x59,0x1b,0x1a,0x3,0x7,0x22,0x22,0x1,0xfe,0x3,0x9c,0x14,0x18,0xf,0x9,0xfc,0x6f,0xb1,0xb0,0x3f,0x32,0x14,0x1b,0x15,0x1e,0x2c,0xc, + 0x2c,0x35,0x6a,0x72,0x3,0x38,0x16,0x18,0xb,0xf,0x27,0x21,0xff,0xff,0x0,0x53,0xfe,0x66,0x4,0xa1,0x5,0x60,0x0,0x22,0x0,0xb5,0x0,0x0,0x0,0x2, + 0x1,0x92,0x64,0x0,0x0,0x0,0x0,0x1,0x0,0xa9,0xff,0xf3,0x4,0x70,0x5,0x2a,0x0,0x29,0x0,0x28,0x40,0x25,0x21,0x1,0x3,0x2,0x24,0x17,0xe,0x4, + 0x4,0x0,0x3,0x2,0x4a,0x0,0x2,0x2,0x10,0x4b,0x0,0x3,0x3,0x19,0x4b,0x1,0x1,0x0,0x0,0x17,0x0,0x4c,0x27,0x37,0x36,0x2a,0x4,0x7,0x18,0x2b, + 0x0,0x15,0x14,0x7,0x1,0x1,0x16,0x15,0x14,0x7,0x6,0x23,0x22,0x27,0x1,0x7,0x7,0x6,0x6,0x23,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x36,0x36,0x33, + 0x33,0x32,0x16,0x15,0x14,0x7,0x3,0x1,0x36,0x33,0x32,0x17,0x4,0x70,0x19,0xfe,0x60,0x1,0x3a,0xe,0x2f,0x20,0x17,0x19,0x12,0xfe,0xd2,0xdb,0x24,0x6, + 0x27,0x28,0x2,0x23,0x1e,0x2,0xd4,0x6,0x27,0x28,0x2,0x23,0x1e,0x2,0x8c,0x2,0x56,0x17,0x1c,0x1c,0x1a,0x3,0x81,0x19,0x14,0x13,0xfe,0xc3,0xfe,0x57, + 0x13,0xe,0x1c,0x19,0x12,0x18,0x1,0x9e,0xa7,0xcb,0x23,0x1e,0x15,0x18,0x4,0x10,0x4,0xb2,0x23,0x1e,0x15,0x18,0x4,0x10,0xfc,0xe5,0x1,0xce,0x12,0x12, + 0x0,0x0,0xff,0xff,0x0,0xa9,0xfd,0xf3,0x4,0x70,0x5,0x2a,0x0,0x22,0x0,0xb7,0x0,0x0,0x0,0x3,0x1,0x7e,0x4,0xea,0x0,0x0,0x0,0x1,0x1,0x10, + 0xff,0xea,0x3,0xfc,0x5,0x20,0x0,0x27,0x0,0x39,0x40,0x36,0x21,0x2,0x2,0x3,0x4,0x12,0x1,0x2,0x0,0x2,0x4a,0x0,0x1,0x3,0x0,0x3,0x1,0x0, + 0x70,0x0,0x3,0x3,0x4,0x59,0x5,0x1,0x4,0x4,0x10,0x4b,0x0,0x0,0x0,0x2,0x5b,0x0,0x2,0x2,0x17,0x2,0x4c,0x0,0x0,0x0,0x27,0x0,0x25,0x25, + 0x27,0x22,0x29,0x6,0x7,0x18,0x2b,0x0,0x16,0x15,0x14,0x7,0x3,0x6,0x15,0x14,0x16,0x33,0x32,0x37,0x36,0x33,0x32,0x17,0x16,0x15,0x14,0x6,0x7,0x6, + 0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x21,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x21,0x2,0xe1,0x1b,0x2,0xa5,0x7,0x41,0x45,0x6a,0x70,0x10,0x6,0x29, + 0xc,0x3,0x18,0x1a,0x87,0x7e,0x81,0x8c,0x7,0x97,0xfe,0xef,0x1b,0x1a,0x3,0x7,0x23,0x22,0x1,0x66,0x5,0x20,0x15,0x18,0xe,0x9,0xfc,0x5a,0x2c,0x17, + 0x3e,0x39,0x22,0x4,0x39,0xf,0xd,0x19,0x1d,0x8,0x25,0x85,0x76,0x28,0x26,0x3,0x5d,0x16,0x18,0xb,0xf,0x27,0x21,0x0,0x0,0x0,0xff,0xff,0x1,0x10, + 0xff,0xea,0x4,0xc,0x6,0xd0,0x0,0x27,0x1,0x80,0xff,0xc4,0x1,0x72,0x1,0x2,0x0,0xb9,0x0,0x0,0x0,0x9,0xb1,0x0,0x1,0xb8,0x1,0x72,0xb0,0x33, + 0x2b,0x0,0xff,0xff,0x0,0xf2,0xff,0xea,0x4,0xe2,0x5,0x20,0x0,0x22,0x0,0xb9,0xe2,0x0,0x0,0x3,0x1,0x81,0x2,0x23,0x0,0x0,0xff,0xff,0x1,0x10, + 0xfd,0xf3,0x3,0xfc,0x5,0x20,0x0,0x22,0x0,0xb9,0x0,0x0,0x0,0x3,0x1,0x7e,0x5,0x30,0x0,0x0,0xff,0xff,0x0,0xf2,0xff,0xea,0x4,0x4f,0x5,0x20, + 0x0,0x22,0x0,0xb9,0xe2,0x0,0x1,0x7,0x1,0x94,0x0,0xca,0xfe,0xc,0x0,0x9,0xb1,0x1,0x1,0xb8,0xfe,0xc,0xb0,0x33,0x2b,0x0,0x0,0x1,0x0,0xf3, + 0xff,0xea,0x3,0xfc,0x5,0x20,0x0,0x3d,0x0,0x46,0x40,0x43,0x24,0x1b,0x2,0x2,0x3,0x27,0xc,0x2,0x1,0x4,0x1,0x1,0x0,0x5,0x3,0x4a,0x0,0x1, + 0x4,0x6,0x4,0x1,0x6,0x70,0x0,0x6,0x5,0x4,0x6,0x5,0x6e,0x0,0x2,0x2,0x3,0x59,0x0,0x3,0x3,0x10,0x4b,0x0,0x4,0x4,0x19,0x4b,0x0,0x5, + 0x5,0x0,0x5b,0x0,0x0,0x0,0x17,0x0,0x4c,0x22,0x2b,0x27,0x36,0x27,0x27,0x25,0x7,0x7,0x1b,0x2b,0x24,0x15,0x14,0x6,0x7,0x6,0x23,0x22,0x26,0x35, + 0x34,0x37,0x37,0x7,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x37,0x13,0x21,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x21,0x32,0x16,0x15,0x14,0x7,0x3, + 0x37,0x36,0x33,0x32,0x17,0x16,0x15,0x14,0x7,0x5,0x3,0x6,0x15,0x14,0x16,0x33,0x32,0x37,0x36,0x33,0x32,0x17,0x3,0xfc,0x18,0x1a,0x87,0x7e,0x81,0x8c, + 0x7,0x27,0x86,0x18,0x10,0x1a,0x19,0x12,0x20,0xf4,0x4f,0xfe,0xef,0x1b,0x1a,0x3,0x7,0x23,0x22,0x1,0x66,0x1c,0x1b,0x2,0x49,0x9b,0x18,0x10,0x1a,0x19, + 0x12,0x20,0xfe,0xf7,0x3b,0x7,0x41,0x45,0x6a,0x70,0x10,0x6,0x29,0xc,0x5a,0xd,0x19,0x1d,0x8,0x25,0x85,0x76,0x28,0x26,0xde,0x5b,0x10,0x25,0x1b,0x12, + 0x19,0x15,0xa6,0x1,0xc4,0x16,0x18,0xb,0xf,0x27,0x21,0x15,0x18,0xe,0x9,0xfe,0x64,0x69,0x10,0x25,0x1b,0x12,0x19,0x15,0xb4,0xfe,0xb1,0x2c,0x17,0x3e, + 0x39,0x22,0x4,0x39,0x0,0x1,0x0,0x3a,0xff,0xf6,0x4,0x8b,0x3,0xb2,0x0,0x49,0x0,0x5e,0x40,0xe,0x3d,0x1,0x1,0x5,0x46,0x40,0x33,0x20,0xd,0x5, + 0x0,0x1,0x2,0x4a,0x4b,0xb0,0x28,0x50,0x58,0x40,0x16,0x3,0x1,0x1,0x1,0x5,0x5b,0x8,0x7,0x6,0x3,0x5,0x5,0x11,0x4b,0x4,0x2,0x2,0x0,0x0, + 0xf,0x0,0x4c,0x1b,0x40,0x1a,0x0,0x5,0x5,0x11,0x4b,0x3,0x1,0x1,0x1,0x6,0x5b,0x8,0x7,0x2,0x6,0x6,0x19,0x4b,0x4,0x2,0x2,0x0,0x0,0xf, + 0x0,0x4c,0x59,0x40,0x10,0x0,0x0,0x0,0x49,0x0,0x48,0x27,0x37,0x36,0x28,0x36,0x28,0x38,0x9,0x7,0x1b,0x2b,0x0,0x16,0x16,0x15,0x14,0x7,0x3,0x6, + 0x6,0x23,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x36,0x35,0x34,0x23,0x22,0x6,0x6,0x7,0x3,0x6,0x6,0x23,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x36,0x35, + 0x34,0x23,0x22,0x6,0x6,0x7,0x3,0x6,0x6,0x23,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x36,0x36,0x33,0x33,0x32,0x16,0x15,0x14,0x7,0x7,0x36,0x36,0x33, + 0x32,0x16,0x17,0x36,0x36,0x33,0x4,0xb,0x51,0x2f,0x7,0x6c,0x6,0x27,0x28,0x2,0x23,0x1e,0x2,0x6b,0x5,0x4c,0x2f,0x61,0x4d,0x11,0x43,0x6,0x27,0x28, + 0x2,0x23,0x1e,0x2,0x6b,0x5,0x4c,0x2f,0x61,0x4d,0x11,0x43,0x6,0x27,0x28,0x2,0x23,0x1e,0x2,0x90,0x6,0x1f,0x24,0x2,0x1f,0x1a,0x2,0x14,0x37,0x87, + 0x40,0x43,0x57,0x4,0x37,0x89,0x43,0x3,0xb2,0x30,0x5d,0x41,0x24,0x24,0xfd,0x9b,0x23,0x1e,0x15,0x18,0x4,0x10,0x2,0x5d,0x20,0x14,0x56,0x60,0xa6,0x63, + 0xfe,0x82,0x23,0x1e,0x15,0x18,0x4,0x10,0x2,0x5d,0x20,0x14,0x56,0x60,0xa6,0x63,0xfe,0x82,0x23,0x1e,0x15,0x18,0x4,0x10,0x3,0x2e,0x24,0x1d,0x14,0x17, + 0x6,0x10,0x70,0x5d,0x60,0x5e,0x57,0x5a,0x5b,0x0,0x0,0x0,0x0,0x1,0x0,0x8b,0xff,0xf6,0x4,0x40,0x3,0xb2,0x0,0x31,0x0,0x53,0x40,0xc,0x2b,0x1, + 0x1,0x3,0x2e,0x21,0xd,0x3,0x0,0x1,0x2,0x4a,0x4b,0xb0,0x28,0x50,0x58,0x40,0x13,0x0,0x1,0x1,0x3,0x5b,0x5,0x4,0x2,0x3,0x3,0x11,0x4b,0x2, + 0x1,0x0,0x0,0xf,0x0,0x4c,0x1b,0x40,0x17,0x0,0x3,0x3,0x11,0x4b,0x0,0x1,0x1,0x4,0x5b,0x5,0x1,0x4,0x4,0x19,0x4b,0x2,0x1,0x0,0x0,0xf, + 0x0,0x4c,0x59,0x40,0xd,0x0,0x0,0x0,0x31,0x0,0x30,0x37,0x36,0x29,0x38,0x6,0x7,0x18,0x2b,0x0,0x16,0x16,0x15,0x14,0x7,0x3,0x6,0x6,0x23,0x23, + 0x22,0x26,0x35,0x34,0x37,0x13,0x36,0x35,0x34,0x26,0x23,0x22,0x6,0x6,0x7,0x3,0x6,0x6,0x23,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x36,0x36,0x33,0x33, + 0x32,0x16,0x15,0x14,0x7,0x7,0x36,0x36,0x33,0x3,0x82,0x7c,0x42,0x7,0x62,0x6,0x27,0x28,0x2,0x23,0x1e,0x2,0x61,0x5,0x4c,0x4d,0x56,0xba,0x8c,0x13, + 0x3c,0x6,0x27,0x28,0x2,0x23,0x1e,0x2,0x90,0x6,0x21,0x27,0x2,0x21,0x1d,0x2,0x1b,0x54,0xe0,0x6b,0x3,0xb2,0x40,0x75,0x4f,0x26,0x26,0xfd,0xd5,0x23, + 0x1e,0x15,0x18,0x4,0x10,0x2,0x26,0x20,0x16,0x42,0x49,0x6d,0xbb,0x6e,0xfe,0xaf,0x23,0x1e,0x15,0x18,0x4,0x10,0x3,0x2e,0x24,0x1d,0x15,0x17,0x5,0x10, + 0x99,0x67,0x7f,0x0,0xff,0xff,0x0,0x8b,0xff,0xf6,0x4,0x40,0x5,0x5e,0x0,0x22,0x1,0x80,0xf6,0x0,0x0,0x2,0x0,0xc0,0x0,0x0,0x0,0x0,0xff,0xff, + 0x0,0x8b,0xff,0xf6,0x4,0x62,0x5,0x6e,0x0,0x26,0x1,0x83,0x0,0xa,0x1,0x2,0x0,0xc0,0x0,0x0,0x0,0x8,0xb1,0x0,0x1,0xb0,0xa,0xb0,0x33,0x2b, + 0xff,0xff,0x0,0x8b,0xfd,0xf3,0x4,0x40,0x3,0xb2,0x0,0x22,0x0,0xc0,0x0,0x0,0x0,0x3,0x1,0x7e,0x4,0xd6,0x0,0x0,0x0,0x1,0x0,0x8b,0xfe,0x66, + 0x4,0x40,0x3,0xb2,0x0,0x3b,0x0,0x76,0x40,0xb,0x35,0x1,0x3,0x5,0x38,0x2b,0x2,0x4,0x3,0x2,0x4a,0x4b,0xb0,0x28,0x50,0x58,0x40,0x24,0x0,0x1, + 0x4,0x2,0x4,0x1,0x2,0x70,0x0,0x3,0x3,0x5,0x5b,0x7,0x6,0x2,0x5,0x5,0x11,0x4b,0x0,0x4,0x4,0xf,0x4b,0x0,0x2,0x2,0x0,0x5b,0x0,0x0, + 0x0,0x13,0x0,0x4c,0x1b,0x40,0x28,0x0,0x1,0x4,0x2,0x4,0x1,0x2,0x70,0x0,0x5,0x5,0x11,0x4b,0x0,0x3,0x3,0x6,0x5b,0x7,0x1,0x6,0x6,0x19, + 0x4b,0x0,0x4,0x4,0xf,0x4b,0x0,0x2,0x2,0x0,0x5b,0x0,0x0,0x0,0x13,0x0,0x4c,0x59,0x40,0xf,0x0,0x0,0x0,0x3b,0x0,0x3a,0x37,0x36,0x27,0x23, + 0x17,0x28,0x8,0x7,0x1a,0x2b,0x0,0x16,0x16,0x15,0x14,0x7,0x3,0x6,0x6,0x23,0x22,0x26,0x27,0x26,0x35,0x34,0x37,0x36,0x33,0x32,0x17,0x16,0x33,0x32, + 0x36,0x37,0x13,0x36,0x35,0x34,0x26,0x23,0x22,0x6,0x6,0x7,0x3,0x6,0x6,0x23,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x36,0x36,0x33,0x33,0x32,0x16,0x15, + 0x14,0x7,0x7,0x36,0x36,0x33,0x3,0x82,0x7c,0x42,0x7,0x84,0x18,0x9d,0x83,0x3a,0x73,0x31,0x21,0xd,0x16,0x26,0xb,0xc,0x59,0x51,0x40,0x4d,0xf,0x7e, + 0x5,0x4c,0x4d,0x56,0xba,0x8c,0x13,0x3c,0x6,0x27,0x28,0x2,0x23,0x1e,0x2,0x90,0x6,0x21,0x27,0x2,0x21,0x1d,0x2,0x1b,0x54,0xe0,0x6b,0x3,0xb2,0x40, + 0x75,0x4f,0x26,0x26,0xfd,0x17,0x87,0x8c,0x1b,0x17,0x10,0x1e,0x15,0x1b,0x2f,0x5,0x28,0x4b,0x52,0x2,0xc8,0x20,0x16,0x42,0x49,0x6d,0xbb,0x6e,0xfe,0xaf, + 0x23,0x1e,0x15,0x18,0x4,0x10,0x3,0x2e,0x24,0x1d,0x15,0x17,0x5,0x10,0x99,0x67,0x7f,0x0,0x0,0x0,0xff,0xff,0x0,0x8b,0xff,0xf6,0x4,0x4c,0x5,0x11, + 0x0,0x22,0x0,0xc0,0x0,0x0,0x0,0x2,0x1,0x9a,0x0,0x0,0x0,0x0,0x0,0x2,0x0,0x8c,0xff,0xea,0x4,0x56,0x3,0xb2,0x0,0xf,0x0,0x1f,0x0,0x2c, + 0x40,0x29,0x5,0x1,0x3,0x3,0x1,0x5b,0x4,0x1,0x1,0x1,0x19,0x4b,0x0,0x2,0x2,0x0,0x5b,0x0,0x0,0x0,0x17,0x0,0x4c,0x10,0x10,0x0,0x0,0x10, + 0x1f,0x10,0x1e,0x18,0x16,0x0,0xf,0x0,0xe,0x26,0x6,0x7,0x15,0x2b,0x0,0x16,0x16,0x15,0x14,0x2,0x6,0x23,0x22,0x26,0x26,0x35,0x34,0x12,0x36,0x33, + 0xe,0x2,0x15,0x14,0x16,0x16,0x33,0x32,0x36,0x36,0x35,0x34,0x26,0x26,0x23,0x3,0x2d,0xbf,0x6a,0x8b,0xfb,0xa0,0x7b,0xbf,0x6a,0x8b,0xfb,0xa0,0x7c,0xaf, + 0x62,0x43,0x80,0x58,0x6c,0xaf,0x62,0x43,0x80,0x58,0x3,0xb2,0x69,0xba,0x75,0x93,0xfe,0xfe,0x9b,0x69,0xba,0x75,0x93,0x1,0x2,0x9b,0x94,0x70,0xba,0x6a, + 0x50,0x79,0x43,0x70,0xba,0x6a,0x50,0x79,0x43,0x0,0x0,0x0,0xff,0xff,0x0,0x8c,0xff,0xea,0x4,0x56,0x5,0x5e,0x0,0x22,0x0,0xc6,0x0,0x0,0x0,0x2, + 0x1,0x80,0xd6,0x0,0x0,0x0,0xff,0xff,0x0,0x8c,0xff,0xea,0x4,0x71,0x5,0x4e,0x0,0x22,0x1,0x82,0xec,0x0,0x0,0x2,0x0,0xc6,0x0,0x0,0x0,0x0, + 0xff,0xff,0x0,0x8c,0xff,0xea,0x4,0x56,0x5,0x60,0x0,0x22,0x0,0xc6,0x0,0x0,0x0,0x2,0x1,0x92,0xec,0x0,0x0,0x0,0xff,0xff,0x0,0x8c,0xff,0xea, + 0x4,0x56,0x5,0x28,0x0,0x22,0x0,0xc6,0x0,0x0,0x0,0x2,0x1,0x93,0xec,0x0,0x0,0x0,0xff,0xff,0x0,0x8c,0xff,0xea,0x4,0x56,0x5,0x5e,0x0,0x22, + 0x0,0xc6,0x0,0x0,0x0,0x2,0x1,0x95,0x2,0x0,0x0,0x0,0xff,0xff,0x0,0x8c,0xff,0xea,0x4,0xc8,0x5,0x69,0x0,0x22,0x0,0xc6,0x0,0x0,0x0,0x2, + 0x1,0x96,0xec,0x0,0x0,0x0,0xff,0xff,0x0,0x8c,0xff,0xea,0x4,0x56,0x4,0xe2,0x0,0x22,0x0,0xc6,0x0,0x0,0x0,0x2,0x1,0x97,0xec,0x0,0x0,0x0, + 0x0,0x3,0x0,0x1a,0xff,0xd7,0x4,0xc7,0x3,0xc5,0x0,0x25,0x0,0x2e,0x0,0x37,0x0,0x62,0x40,0x13,0x20,0x1,0x4,0x2,0x31,0x30,0x28,0x27,0x17,0x4, + 0x6,0x5,0x4,0xd,0x1,0x0,0x5,0x3,0x4a,0x4b,0xb0,0x1b,0x50,0x58,0x40,0x17,0x0,0x4,0x4,0x2,0x5b,0x3,0x1,0x2,0x2,0x19,0x4b,0x0,0x5,0x5, + 0x0,0x5b,0x1,0x1,0x0,0x0,0x17,0x0,0x4c,0x1b,0x40,0x1f,0x0,0x3,0x3,0x19,0x4b,0x0,0x4,0x4,0x2,0x5b,0x0,0x2,0x2,0x19,0x4b,0x0,0x5,0x5, + 0x0,0x5b,0x0,0x0,0x0,0x17,0x4b,0x0,0x1,0x1,0x17,0x1,0x4c,0x59,0x40,0x9,0x27,0x25,0x24,0x2b,0x24,0x29,0x6,0x7,0x1a,0x2b,0x0,0x15,0x14,0x7, + 0x7,0x16,0x15,0x14,0x2,0x6,0x23,0x22,0x26,0x27,0x7,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x37,0x26,0x35,0x34,0x12,0x36,0x33,0x32,0x16,0x17,0x37, + 0x36,0x33,0x32,0x17,0x0,0x17,0x1,0x26,0x23,0x22,0x6,0x6,0x15,0x24,0x27,0x1,0x16,0x33,0x32,0x36,0x36,0x35,0x4,0xc7,0x1e,0x81,0x2e,0x8b,0xfb,0xa0, + 0x56,0x94,0x37,0x78,0x1a,0x19,0x1a,0x19,0x17,0x1d,0x83,0x2e,0x8b,0xfb,0xa0,0x56,0x93,0x38,0x77,0x1a,0x19,0x1b,0x19,0xfc,0x74,0x11,0x2,0x24,0x48,0x70, + 0x6c,0xaf,0x62,0x2,0x98,0x11,0xfd,0xdb,0x49,0x70,0x6c,0xaf,0x62,0x3,0x8e,0x13,0x17,0x1a,0x6b,0x57,0x6e,0x93,0xfe,0xfe,0x9b,0x35,0x31,0x63,0x16,0x1c, + 0x1a,0x15,0x18,0x17,0x6c,0x57,0x6e,0x93,0x1,0x2,0x9b,0x34,0x31,0x62,0x16,0x1c,0xfd,0xaa,0x30,0x1,0xc6,0x35,0x70,0xba,0x6a,0xbf,0x30,0xfe,0x3b,0x36, + 0x70,0xba,0x6a,0x0,0x0,0x0,0xff,0xff,0x0,0x8c,0xff,0xea,0x4,0x56,0x5,0x11,0x0,0x22,0x0,0xc6,0x0,0x0,0x0,0x2,0x1,0x9a,0xe7,0x0,0x0,0x0, + 0x0,0x3,0x0,0x42,0xff,0xea,0x4,0x9d,0x3,0xb2,0x0,0x2c,0x0,0x35,0x0,0x46,0x0,0x58,0x40,0x55,0x29,0x1,0x8,0x5,0x1d,0x1,0x3,0x1,0x2,0x4a, + 0x0,0x2,0x0,0x1,0x0,0x2,0x1,0x70,0x0,0x7,0x0,0x0,0x2,0x7,0x0,0x61,0xd,0xa,0xc,0x3,0x8,0x8,0x5,0x5b,0xb,0x6,0x2,0x5,0x5,0x19, + 0x4b,0x9,0x1,0x1,0x1,0x3,0x5b,0x4,0x1,0x3,0x3,0x17,0x3,0x4c,0x36,0x36,0x2d,0x2d,0x0,0x0,0x36,0x46,0x36,0x45,0x3e,0x3c,0x2d,0x35,0x2d,0x34, + 0x30,0x2f,0x0,0x2c,0x0,0x2b,0x26,0x22,0x29,0x13,0x23,0x26,0xe,0x7,0x1a,0x2b,0x0,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x21,0x6,0x15,0x14,0x33,0x32, + 0x36,0x37,0x36,0x33,0x32,0x16,0x17,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x22,0x27,0x6,0x23,0x22,0x26,0x35,0x34,0x12,0x37,0x36,0x33,0x32,0x17,0x36,0x36, + 0x33,0x6,0x6,0x7,0x21,0x36,0x35,0x34,0x26,0x23,0x4,0x7,0x6,0x6,0x15,0x14,0x16,0x33,0x32,0x36,0x37,0x36,0x36,0x35,0x34,0x26,0x23,0x4,0x27,0x76, + 0xc,0x5,0x23,0x22,0xfe,0x71,0xc,0x78,0x30,0x56,0x32,0x14,0x16,0xe,0x1b,0x9,0xd,0x25,0x41,0x84,0x46,0x9f,0x39,0x64,0x9b,0x7c,0x80,0x3a,0x31,0x69, + 0xbe,0x9c,0x33,0x37,0x87,0x4a,0x54,0x61,0x20,0x1,0x2c,0x5,0x32,0x37,0xfd,0xbd,0x3a,0x1c,0x28,0x38,0x39,0x34,0x50,0x1e,0x1c,0x28,0x37,0x39,0x3,0xb2, + 0x9c,0x8f,0x43,0x4e,0x21,0x1d,0x4c,0x38,0xb9,0x25,0x29,0x11,0x10,0xf,0x14,0x14,0x22,0x1d,0x35,0x35,0x8a,0x8a,0xa6,0x9b,0x69,0x1,0x2,0x5b,0xc1,0x8c, + 0x47,0x45,0x91,0x68,0x75,0x2a,0x1b,0x4a,0x4e,0x3,0x8e,0x43,0xd7,0x50,0x56,0x52,0x45,0x49,0x43,0xd7,0x50,0x56,0x52,0x0,0x0,0x0,0x0,0x2,0x0,0x3d, + 0xfe,0x72,0x4,0x53,0x3,0xb2,0x0,0x21,0x0,0x2f,0x0,0x71,0x40,0xf,0x1c,0x1,0x4,0x2,0x1f,0xa,0x2,0x5,0x4,0x12,0x1,0x1,0x0,0x3,0x4a,0x4b, + 0xb0,0x28,0x50,0x58,0x40,0x1d,0x0,0x4,0x4,0x2,0x5b,0x6,0x3,0x2,0x2,0x2,0x11,0x4b,0x7,0x1,0x5,0x5,0x0,0x5b,0x0,0x0,0x0,0x17,0x4b,0x0, + 0x1,0x1,0x13,0x1,0x4c,0x1b,0x40,0x21,0x0,0x2,0x2,0x11,0x4b,0x0,0x4,0x4,0x3,0x5b,0x6,0x1,0x3,0x3,0x19,0x4b,0x7,0x1,0x5,0x5,0x0,0x5b, + 0x0,0x0,0x0,0x17,0x4b,0x0,0x1,0x1,0x13,0x1,0x4c,0x59,0x40,0x14,0x22,0x22,0x0,0x0,0x22,0x2f,0x22,0x2e,0x29,0x27,0x0,0x21,0x0,0x20,0x37,0x35, + 0x26,0x8,0x7,0x17,0x2b,0x0,0x16,0x16,0x15,0x14,0x2,0x6,0x23,0x22,0x26,0x27,0x3,0x6,0x6,0x23,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x36,0x36,0x33, + 0x33,0x32,0x16,0x15,0x14,0x7,0x7,0x36,0x33,0x2,0x36,0x36,0x35,0x34,0x26,0x23,0x22,0x6,0x6,0x15,0x14,0x16,0x33,0x3,0x54,0xa4,0x5b,0x72,0xe8,0xa6, + 0x62,0x98,0x2f,0x55,0x6,0x27,0x28,0x2,0x23,0x1e,0x2,0xd4,0x6,0x21,0x27,0x2,0x21,0x1d,0x2,0xe,0x97,0xc1,0x24,0xa0,0x55,0x7a,0x71,0x72,0xa9,0x5a, + 0x8a,0x75,0x3,0xb2,0x5f,0xaf,0x76,0x8a,0xfe,0xf4,0xae,0x57,0x51,0xfe,0x21,0x23,0x1e,0x15,0x18,0x4,0x10,0x4,0xb2,0x24,0x1d,0x15,0x17,0x5,0x10,0x4f, + 0x9c,0xfc,0xcc,0x7a,0xc3,0x6a,0x72,0x87,0x76,0xbf,0x6a,0x79,0x88,0x0,0x0,0x0,0x0,0x2,0x0,0x3d,0xfe,0x72,0x4,0x53,0x5,0x2a,0x0,0x21,0x0,0x2f, + 0x0,0x4a,0x40,0x47,0x1c,0x1,0x3,0x2,0x1f,0x1,0x4,0x3,0xa,0x1,0x5,0x4,0x12,0x1,0x1,0x0,0x4,0x4a,0x0,0x2,0x2,0x10,0x4b,0x0,0x4,0x4, + 0x3,0x5b,0x6,0x1,0x3,0x3,0x19,0x4b,0x7,0x1,0x5,0x5,0x0,0x5b,0x0,0x0,0x0,0x17,0x4b,0x0,0x1,0x1,0x13,0x1,0x4c,0x22,0x22,0x0,0x0,0x22, + 0x2f,0x22,0x2e,0x29,0x27,0x0,0x21,0x0,0x20,0x37,0x35,0x26,0x8,0x7,0x17,0x2b,0x0,0x16,0x16,0x15,0x14,0x2,0x6,0x23,0x22,0x26,0x27,0x3,0x6,0x6, + 0x23,0x23,0x22,0x26,0x35,0x34,0x37,0x1,0x36,0x36,0x33,0x33,0x32,0x16,0x15,0x14,0x7,0x3,0x36,0x33,0x2,0x36,0x36,0x35,0x34,0x26,0x23,0x22,0x6,0x6, + 0x15,0x14,0x16,0x33,0x3,0x54,0xa4,0x5b,0x72,0xe8,0xa6,0x62,0x98,0x2f,0x55,0x6,0x27,0x28,0x2,0x23,0x1e,0x2,0x1,0x18,0x6,0x27,0x28,0x2,0x23,0x1e, + 0x2,0x51,0x94,0xb9,0x24,0xa0,0x55,0x7a,0x71,0x72,0xa9,0x5a,0x8a,0x75,0x3,0xb2,0x5f,0xaf,0x76,0x8a,0xfe,0xf4,0xae,0x57,0x51,0xfe,0x21,0x23,0x1e,0x15, + 0x18,0x4,0x10,0x6,0x36,0x23,0x1e,0x15,0x18,0x4,0x10,0xfe,0x38,0x91,0xfc,0xcc,0x7a,0xc3,0x6a,0x72,0x87,0x76,0xbf,0x6a,0x79,0x88,0x0,0x2,0x0,0x8f, + 0xfe,0x72,0x4,0x61,0x3,0xb2,0x0,0x22,0x0,0x30,0x0,0x74,0x40,0x12,0xa,0x1,0x5,0x0,0x2,0x1,0x4,0x5,0x17,0x1,0x2,0x4,0x14,0x1,0x1,0x2, + 0x4,0x4a,0x4b,0xb0,0x28,0x50,0x58,0x40,0x1d,0x7,0x1,0x5,0x5,0x0,0x5b,0x6,0x3,0x2,0x0,0x0,0x11,0x4b,0x0,0x4,0x4,0x2,0x5b,0x0,0x2,0x2, + 0x17,0x4b,0x0,0x1,0x1,0x13,0x1,0x4c,0x1b,0x40,0x21,0x0,0x0,0x0,0x11,0x4b,0x7,0x1,0x5,0x5,0x3,0x5b,0x6,0x1,0x3,0x3,0x19,0x4b,0x0,0x4, + 0x4,0x2,0x5b,0x0,0x2,0x2,0x17,0x4b,0x0,0x1,0x1,0x13,0x1,0x4c,0x59,0x40,0x14,0x23,0x23,0x0,0x0,0x23,0x30,0x23,0x2f,0x2a,0x28,0x0,0x22,0x0, + 0x21,0x27,0x37,0x35,0x8,0x7,0x17,0x2b,0x0,0x16,0x17,0x37,0x36,0x36,0x33,0x33,0x32,0x16,0x15,0x14,0x7,0x3,0x6,0x6,0x23,0x23,0x22,0x26,0x35,0x34, + 0x37,0x13,0x6,0x6,0x23,0x22,0x26,0x26,0x35,0x34,0x12,0x36,0x33,0xe,0x2,0x15,0x14,0x16,0x33,0x32,0x36,0x36,0x35,0x34,0x26,0x23,0x2,0xf4,0x9b,0x31, + 0x13,0x6,0x21,0x27,0x2,0x21,0x1d,0x2,0xd4,0x6,0x27,0x28,0x2,0x23,0x1e,0x2,0x50,0x47,0xa7,0x5e,0x6b,0xa4,0x5b,0x72,0xe8,0xa6,0x72,0xa0,0x55,0x7a, + 0x71,0x72,0xa9,0x5a,0x8a,0x75,0x3,0xb2,0x5d,0x57,0x67,0x24,0x1d,0x15,0x17,0x5,0x10,0xfb,0x4e,0x23,0x1e,0x15,0x18,0x4,0x10,0x1,0xc7,0x46,0x4a,0x5f, + 0xaf,0x76,0x8a,0x1,0xc,0xae,0x94,0x7a,0xc3,0x6a,0x72,0x87,0x76,0xbf,0x6a,0x79,0x88,0x0,0x0,0x0,0x0,0x1,0x0,0xf4,0xff,0xf6,0x4,0x87,0x3,0xb2, + 0x0,0x27,0x0,0x92,0x4b,0xb0,0x17,0x50,0x58,0x40,0xc,0x21,0x3,0x2,0x0,0x3,0x24,0x17,0x2,0x2,0x0,0x2,0x4a,0x1b,0x40,0xc,0x21,0x3,0x2,0x1, + 0x3,0x24,0x17,0x2,0x2,0x0,0x2,0x4a,0x59,0x4b,0xb0,0x17,0x50,0x58,0x40,0x13,0x1,0x1,0x0,0x0,0x3,0x5b,0x5,0x4,0x2,0x3,0x3,0x11,0x4b,0x0, + 0x2,0x2,0xf,0x2,0x4c,0x1b,0x4b,0xb0,0x28,0x50,0x58,0x40,0x1a,0x0,0x0,0x1,0x2,0x1,0x0,0x2,0x70,0x0,0x1,0x1,0x3,0x5b,0x5,0x4,0x2,0x3, + 0x3,0x11,0x4b,0x0,0x2,0x2,0xf,0x2,0x4c,0x1b,0x40,0x1e,0x0,0x0,0x1,0x2,0x1,0x0,0x2,0x70,0x0,0x3,0x3,0x11,0x4b,0x0,0x1,0x1,0x4,0x5b, + 0x5,0x1,0x4,0x4,0x19,0x4b,0x0,0x2,0x2,0xf,0x2,0x4c,0x59,0x59,0x40,0xd,0x0,0x0,0x0,0x27,0x0,0x26,0x37,0x36,0x23,0x16,0x6,0x7,0x18,0x2b, + 0x0,0x17,0x16,0x15,0x14,0x7,0x6,0x23,0x22,0x27,0x26,0x23,0x22,0x6,0x6,0x7,0x7,0x6,0x6,0x23,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x36,0x36,0x33, + 0x33,0x32,0x16,0x15,0x14,0x7,0x7,0x36,0x24,0x33,0x4,0x20,0x3f,0x28,0x7,0x12,0x29,0xb,0xb,0x34,0x37,0x73,0xe3,0xa4,0x18,0x26,0x6,0x27,0x28,0x2, + 0x23,0x1e,0x2,0x90,0x6,0x21,0x27,0x2,0x21,0x1d,0x2,0x28,0x71,0x1,0x0,0x85,0x3,0xb2,0x15,0xe,0x29,0xf,0x1a,0x41,0x4,0x12,0x8c,0xed,0x8b,0xd7, + 0x23,0x1e,0x15,0x18,0x4,0x10,0x3,0x2e,0x24,0x1d,0x15,0x17,0x5,0x10,0xdc,0x8f,0x9a,0x0,0xff,0xff,0x0,0xf4,0xff,0xf6,0x4,0x87,0x5,0x5e,0x0,0x22, + 0x1,0x80,0x3c,0x0,0x0,0x2,0x0,0xd4,0x0,0x0,0x0,0x0,0xff,0xff,0x0,0xf4,0xff,0xf6,0x4,0x94,0x5,0x6e,0x0,0x26,0x1,0x83,0x32,0xa,0x1,0x2, + 0x0,0xd4,0x0,0x0,0x0,0x8,0xb1,0x0,0x1,0xb0,0xa,0xb0,0x33,0x2b,0xff,0xff,0x0,0xf4,0xfd,0xf3,0x4,0x87,0x3,0xb2,0x0,0x22,0x0,0xd4,0x0,0x0, + 0x0,0x3,0x1,0x7e,0x4,0xae,0x0,0x0,0x0,0x1,0x0,0x88,0xff,0xea,0x4,0x30,0x3,0xb2,0x0,0x3d,0x0,0x39,0x40,0x36,0x0,0x0,0x1,0x3,0x1,0x0, + 0x3,0x70,0x0,0x3,0x4,0x1,0x3,0x4,0x6e,0x0,0x1,0x1,0x5,0x5b,0x6,0x1,0x5,0x5,0x19,0x4b,0x0,0x4,0x4,0x2,0x5b,0x0,0x2,0x2,0x17,0x2, + 0x4c,0x0,0x0,0x0,0x3d,0x0,0x3c,0x2c,0x2a,0x26,0x25,0x1f,0x1d,0x24,0x16,0x7,0x7,0x16,0x2b,0x0,0x17,0x16,0x15,0x14,0x7,0x6,0x23,0x22,0x27,0x26, + 0x26,0x23,0x22,0x6,0x6,0x15,0x14,0x17,0x16,0x17,0x16,0x16,0x17,0x16,0x16,0x15,0x14,0x6,0x6,0x23,0x20,0x27,0x26,0x35,0x34,0x37,0x36,0x33,0x32,0x17, + 0x16,0x16,0x33,0x32,0x36,0x36,0x35,0x34,0x27,0x26,0x27,0x26,0x26,0x27,0x26,0x26,0x35,0x34,0x36,0x36,0x33,0x3,0x6c,0xa1,0x23,0xd,0x1a,0x23,0xc,0x12, + 0x48,0xad,0x5e,0x4f,0x6c,0x35,0x6d,0x3c,0x60,0x4f,0x5b,0x2e,0x4a,0x57,0x53,0xc6,0xa2,0xff,0x0,0xa2,0x22,0xf,0x1a,0x22,0xf,0x11,0x4a,0xb8,0x66,0x62, + 0x78,0x32,0x6c,0x35,0x6c,0x49,0x61,0x2f,0x48,0x54,0x5c,0xb9,0x84,0x3,0xb2,0x57,0x13,0x22,0x15,0x1a,0x32,0x9,0x29,0x2a,0x29,0x43,0x26,0x47,0x15,0xc, + 0x7,0x8,0xc,0xe,0x16,0x68,0x54,0x49,0x93,0x66,0x64,0x15,0x20,0x15,0x1b,0x30,0xa,0x2e,0x30,0x34,0x49,0x23,0x4c,0x17,0xb,0xa,0x6,0xd,0xf,0x16, + 0x65,0x4f,0x53,0x8c,0x54,0x0,0x0,0x0,0xff,0xff,0x0,0x88,0xff,0xea,0x4,0x30,0x5,0x5e,0x0,0x22,0x1,0x80,0xe2,0x0,0x0,0x2,0x0,0xd8,0x0,0x0, + 0x0,0x0,0xff,0xff,0x0,0x88,0xff,0xea,0x4,0x3d,0x5,0x6e,0x0,0x26,0x1,0x83,0xdb,0xa,0x1,0x2,0x0,0xd8,0x0,0x0,0x0,0x8,0xb1,0x0,0x1,0xb0, + 0xa,0xb0,0x33,0x2b,0x0,0x1,0x0,0x88,0xfe,0x28,0x4,0x30,0x3,0xb2,0x0,0x60,0x0,0x5f,0x40,0x5c,0x3f,0x1,0x2,0x9,0x3c,0x1,0x7,0x3,0x2,0x4a, + 0x0,0x0,0x1,0x8,0x1,0x0,0x8,0x70,0x0,0x8,0x9,0x1,0x8,0x9,0x6e,0x0,0x5,0x7,0x6,0x7,0x5,0x6,0x70,0x0,0x3,0x0,0x7,0x5,0x3,0x7, + 0x63,0x0,0x6,0x0,0x4,0x6,0x4,0x5f,0x0,0x1,0x1,0xa,0x5b,0x0,0xa,0xa,0x19,0x4b,0x0,0x9,0x9,0x2,0x5b,0x0,0x2,0x2,0x17,0x2,0x4c,0x5f, + 0x5d,0x4d,0x4b,0x47,0x46,0x3b,0x38,0x35,0x33,0x30,0x2e,0x26,0x24,0x1e,0x1d,0x1c,0x1b,0x24,0x14,0xb,0x7,0x16,0x2b,0x0,0x15,0x14,0x7,0x6,0x23,0x22, + 0x27,0x26,0x26,0x23,0x22,0x6,0x6,0x15,0x14,0x17,0x16,0x17,0x16,0x16,0x17,0x16,0x16,0x15,0x14,0x6,0x6,0x7,0x7,0x36,0x16,0x15,0x14,0x6,0x7,0x6, + 0x23,0x22,0x26,0x27,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x32,0x17,0x16,0x16,0x33,0x32,0x36,0x35,0x34,0x23,0x7,0x22,0x35,0x34,0x37,0x37,0x26,0x27,0x26, + 0x35,0x34,0x37,0x36,0x33,0x32,0x17,0x16,0x16,0x33,0x32,0x36,0x36,0x35,0x34,0x27,0x26,0x27,0x26,0x26,0x27,0x26,0x26,0x35,0x34,0x36,0x36,0x33,0x32,0x17, + 0x4,0x30,0xd,0x1a,0x23,0xc,0x12,0x48,0xad,0x5e,0x4f,0x6c,0x35,0x6d,0x3c,0x60,0x4f,0x5b,0x2e,0x4a,0x57,0x4f,0xbd,0x9a,0x10,0x58,0x5a,0x1c,0x1a,0x45, + 0x97,0x44,0x7c,0x2e,0x11,0xa,0x9,0x19,0xe,0x7,0x4,0x26,0x64,0x31,0x3a,0x3a,0x67,0x23,0x1d,0x1,0x1b,0xb4,0x7d,0x22,0xf,0x1a,0x22,0xf,0x11,0x4a, + 0xb8,0x66,0x62,0x78,0x32,0x6c,0x35,0x6c,0x49,0x61,0x2f,0x48,0x54,0x5c,0xb9,0x84,0xee,0xa1,0x3,0x48,0x22,0x15,0x1a,0x32,0x9,0x29,0x2a,0x29,0x43,0x26, + 0x47,0x15,0xc,0x7,0x8,0xc,0xe,0x16,0x68,0x54,0x47,0x90,0x67,0x4,0x5c,0x2,0x52,0x40,0x22,0x4f,0x1b,0x4a,0x18,0x18,0x8,0x18,0x15,0x11,0x11,0x15, + 0x2,0xf,0x13,0x2d,0x24,0x37,0x1,0x1c,0x9,0x5,0x9e,0x11,0x4e,0x15,0x20,0x15,0x1b,0x30,0xa,0x2e,0x30,0x34,0x49,0x23,0x4c,0x17,0xb,0xa,0x6,0xd, + 0xf,0x16,0x65,0x4f,0x53,0x8c,0x54,0x57,0xff,0xff,0x0,0x88,0xff,0xea,0x4,0x30,0x5,0x60,0x0,0x22,0x1,0x92,0xf0,0x0,0x0,0x2,0x0,0xd8,0x0,0x0, + 0x0,0x0,0xff,0xff,0x0,0x88,0xfd,0xf3,0x4,0x30,0x3,0xb2,0x0,0x22,0x0,0xd8,0x0,0x0,0x0,0x3,0x1,0x7e,0x4,0xcc,0x0,0x0,0x0,0x1,0x0,0xac, + 0xff,0xea,0x4,0x41,0x5,0x25,0x0,0x3b,0x0,0x7f,0x4b,0xb0,0x28,0x50,0x58,0x40,0xb,0x1d,0x6,0x2,0x2,0x3,0x34,0x1,0x0,0x1,0x2,0x4a,0x1b,0x40, + 0xb,0x1d,0x6,0x2,0x2,0x3,0x34,0x1,0x4,0x1,0x2,0x4a,0x59,0x4b,0xb0,0x28,0x50,0x58,0x40,0x1f,0x0,0x2,0x3,0x1,0x3,0x2,0x1,0x70,0x0,0x3, + 0x3,0x5,0x5b,0x6,0x1,0x5,0x5,0x10,0x4b,0x0,0x1,0x1,0x0,0x5b,0x4,0x1,0x0,0x0,0x17,0x0,0x4c,0x1b,0x40,0x23,0x0,0x2,0x3,0x1,0x3,0x2, + 0x1,0x70,0x0,0x3,0x3,0x5,0x5b,0x6,0x1,0x5,0x5,0x10,0x4b,0x0,0x4,0x4,0xf,0x4b,0x0,0x1,0x1,0x0,0x5b,0x0,0x0,0x0,0x17,0x0,0x4c,0x59, + 0x40,0xe,0x0,0x0,0x0,0x3b,0x0,0x3a,0x35,0x2d,0x25,0x24,0x2c,0x7,0x7,0x19,0x2b,0x0,0x16,0x16,0x15,0x14,0x6,0x7,0x16,0x16,0x15,0x14,0x6,0x6, + 0x23,0x22,0x26,0x35,0x34,0x36,0x33,0x32,0x36,0x36,0x35,0x34,0x26,0x23,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x37,0x3e,0x2,0x35,0x34,0x26,0x23,0x22,0x6, + 0x7,0x3,0x6,0x6,0x23,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x3e,0x2,0x33,0x3,0x5d,0x97,0x4d,0x75,0x6e,0x54,0x5c,0x5b,0xd2,0xa7,0x2d,0x21,0x24,0x27, + 0x6f,0x8e,0x3e,0x72,0x66,0x1c,0x19,0x2,0x6,0x23,0x22,0x53,0x6d,0x33,0x64,0x5f,0x66,0x89,0x14,0x9b,0x6,0x27,0x28,0x2,0x23,0x1e,0x2,0x9d,0x13,0x70, + 0xb1,0x72,0x5,0x25,0x4b,0x82,0x53,0x6f,0xaa,0x35,0x26,0x8f,0x60,0x62,0xca,0x8c,0x19,0x21,0x30,0x2a,0x58,0x86,0x4a,0x5f,0x66,0x16,0x17,0xf,0x9,0x24, + 0x20,0x3,0x8,0x4e,0x6e,0x3b,0x4b,0x53,0x81,0x6f,0xfc,0x93,0x23,0x1e,0x15,0x18,0x4,0x10,0x3,0x78,0x6f,0xa9,0x5e,0x0,0x0,0x0,0x0,0x1,0x0,0xb4, + 0xff,0xea,0x4,0x33,0x4,0xb9,0x0,0x38,0x0,0x3b,0x40,0x38,0x2b,0x1,0x4,0x5,0x32,0x1e,0x2,0x3,0x4,0x2,0x4a,0x0,0x1,0x3,0x0,0x3,0x1,0x0, + 0x70,0x6,0x1,0x4,0x7,0x1,0x3,0x1,0x4,0x3,0x62,0x0,0x5,0x5,0x16,0x4b,0x0,0x0,0x0,0x2,0x5b,0x0,0x2,0x2,0x17,0x2,0x4c,0x26,0x25,0x23, + 0x26,0x25,0x28,0x23,0x24,0x8,0x7,0x1c,0x2b,0x1,0x6,0x15,0x14,0x16,0x33,0x32,0x36,0x37,0x36,0x33,0x32,0x16,0x17,0x16,0x15,0x14,0x7,0x6,0x6,0x23, + 0x22,0x26,0x35,0x34,0x37,0x13,0x23,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x33,0x13,0x36,0x36,0x33,0x32,0x16,0x15,0x14,0x7,0x3,0x21,0x32,0x16,0x15, + 0x14,0x7,0x6,0x6,0x23,0x21,0x2,0xb,0x9,0x45,0x4e,0x42,0x9d,0x4e,0xf,0xf,0x10,0x1c,0x9,0x8,0x26,0x58,0xc4,0x55,0x8f,0x8c,0xa,0x3f,0xcb,0x1b, + 0x1a,0x3,0x7,0x22,0x22,0xcb,0x35,0x6,0x26,0x29,0x23,0x20,0x2,0x35,0x1,0x9b,0x1c,0x19,0x2,0x8,0x22,0x22,0xfe,0x65,0x1,0x58,0x2e,0x25,0x46,0x43, + 0x37,0x2e,0x9,0x18,0x18,0x14,0x14,0x23,0x15,0x32,0x3e,0x85,0x7e,0x2a,0x3e,0x1,0x67,0x15,0x18,0xc,0xf,0x27,0x21,0x1,0x2c,0x23,0x1e,0x16,0x18,0x5, + 0xe,0xfe,0xd4,0x16,0x19,0xf,0xa,0x27,0x21,0x0,0x0,0x1,0x0,0xb4,0xff,0xea,0x4,0x33,0x4,0xb9,0x0,0x4e,0x0,0x56,0x40,0x53,0x42,0x1,0x8,0x9, + 0x49,0x35,0x2,0x7,0x8,0x2a,0x5,0x2,0x1,0x0,0x3,0x4a,0x0,0x3,0x1,0x2,0x1,0x3,0x2,0x70,0xa,0x1,0x8,0xc,0xb,0x2,0x7,0x0,0x8,0x7, + 0x62,0x6,0x1,0x0,0x5,0x1,0x1,0x3,0x0,0x1,0x63,0x0,0x9,0x9,0x16,0x4b,0x0,0x2,0x2,0x4,0x5b,0x0,0x4,0x4,0x17,0x4,0x4c,0x0,0x0,0x0, + 0x4e,0x0,0x4d,0x47,0x45,0x40,0x3e,0x26,0x21,0x26,0x25,0x28,0x23,0x25,0x26,0x21,0xd,0x7,0x1d,0x2b,0x1,0x7,0x21,0x32,0x16,0x15,0x14,0x7,0x6,0x6, + 0x23,0x21,0x7,0x6,0x15,0x14,0x16,0x33,0x32,0x36,0x37,0x36,0x33,0x32,0x16,0x17,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x22,0x26,0x35,0x34,0x37,0x37,0x23, + 0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x33,0x37,0x23,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x33,0x13,0x36,0x36,0x33,0x32,0x16,0x15,0x14,0x7,0x3, + 0x21,0x32,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x2,0x4a,0x15,0x1,0x10,0x1b,0x1a,0x3,0x7,0x23,0x22,0xfe,0xf0,0x10,0x9,0x45,0x4e,0x42,0x9d,0x4e,0xf, + 0xf,0x10,0x1c,0x9,0x8,0x26,0x58,0xc4,0x55,0x8f,0x8c,0xa,0x11,0x8d,0x1b,0x1a,0x3,0x7,0x23,0x22,0x8c,0x15,0xcb,0x1b,0x1a,0x3,0x7,0x22,0x22,0xcb, + 0x35,0x6,0x26,0x29,0x23,0x20,0x2,0x35,0x1,0x9b,0x1c,0x19,0x2,0x8,0x22,0x22,0x2,0xbc,0x78,0x16,0x18,0xb,0xf,0x27,0x21,0x5c,0x2e,0x25,0x46,0x43, + 0x37,0x2e,0x9,0x18,0x18,0x14,0x14,0x23,0x15,0x32,0x3e,0x85,0x7e,0x2a,0x3e,0x5f,0x16,0x18,0xb,0xf,0x27,0x21,0x78,0x15,0x18,0xc,0xf,0x27,0x21,0x1, + 0x2c,0x23,0x1e,0x16,0x18,0x5,0xe,0xfe,0xd4,0x16,0x19,0xf,0xa,0x27,0x21,0x0,0x0,0x0,0xff,0xff,0x0,0xb4,0xff,0xea,0x4,0x9e,0x5,0x78,0x0,0x22, + 0x0,0xdf,0x0,0x0,0x1,0x7,0x1,0x7e,0x6,0xf0,0x6,0xa,0x0,0x9,0xb1,0x1,0x1,0xb8,0x6,0xa,0xb0,0x33,0x2b,0x0,0x0,0x1,0x0,0xb4,0xfe,0x28, + 0x4,0x33,0x4,0xb9,0x0,0x5c,0x0,0x63,0x40,0x60,0x4f,0x1,0x9,0xa,0x56,0x42,0x2,0x8,0x9,0x38,0x1,0x2,0x0,0x35,0x1,0x7,0x3,0x4,0x4a,0x0, + 0x1,0x8,0x0,0x8,0x1,0x0,0x70,0x0,0x5,0x7,0x6,0x7,0x5,0x6,0x70,0xb,0x1,0x9,0xc,0x1,0x8,0x1,0x9,0x8,0x62,0x0,0x3,0x0,0x7,0x5, + 0x3,0x7,0x63,0x0,0x6,0x0,0x4,0x6,0x4,0x5f,0x0,0xa,0xa,0x16,0x4b,0x0,0x0,0x0,0x2,0x5b,0x0,0x2,0x2,0x17,0x2,0x4c,0x5c,0x5a,0x54,0x52, + 0x4d,0x4b,0x48,0x46,0x2a,0x33,0x23,0x28,0x26,0x12,0x18,0x23,0x24,0xd,0x7,0x1d,0x2b,0x1,0x6,0x15,0x14,0x16,0x33,0x32,0x36,0x37,0x36,0x33,0x32,0x16, + 0x17,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x23,0x7,0x36,0x16,0x15,0x14,0x6,0x7,0x6,0x23,0x22,0x26,0x27,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x32,0x17, + 0x16,0x16,0x33,0x32,0x36,0x35,0x34,0x23,0x7,0x22,0x35,0x34,0x37,0x37,0x26,0x26,0x35,0x34,0x37,0x13,0x23,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x33, + 0x13,0x36,0x36,0x33,0x32,0x16,0x15,0x14,0x7,0x3,0x21,0x32,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x21,0x2,0xb,0x9,0x45,0x4e,0x42,0x9d,0x4e,0xf,0xf, + 0x10,0x1c,0x9,0x8,0x26,0x58,0xc4,0x55,0x6,0x10,0x58,0x5a,0x1c,0x1a,0x45,0x97,0x44,0x7c,0x2e,0x11,0xa,0x9,0x19,0xe,0x7,0x4,0x26,0x64,0x31,0x3a, + 0x3a,0x67,0x23,0x1d,0x1,0x1e,0x49,0x49,0xa,0x3f,0xcb,0x1b,0x1a,0x3,0x7,0x22,0x22,0xcb,0x35,0x6,0x26,0x29,0x23,0x20,0x2,0x35,0x1,0x9b,0x1c,0x19, + 0x2,0x8,0x22,0x22,0xfe,0x65,0x1,0x58,0x2e,0x25,0x46,0x43,0x37,0x2e,0x9,0x18,0x18,0x14,0x14,0x23,0x15,0x32,0x3e,0x5c,0x2,0x52,0x40,0x22,0x4f,0x1b, + 0x4a,0x18,0x18,0x8,0x18,0x15,0x11,0x11,0x15,0x2,0xf,0x13,0x2d,0x24,0x37,0x1,0x1c,0x9,0x5,0xae,0x1b,0x79,0x5a,0x2a,0x3e,0x1,0x67,0x15,0x18,0xc, + 0xf,0x27,0x21,0x1,0x2c,0x23,0x1e,0x16,0x18,0x5,0xe,0xfe,0xd4,0x16,0x19,0xf,0xa,0x27,0x21,0x0,0xff,0xff,0x0,0xb4,0xfd,0xf3,0x4,0x33,0x4,0xb9, + 0x0,0x22,0x0,0xdf,0x0,0x0,0x0,0x3,0x1,0x7e,0x5,0x1c,0x0,0x0,0x0,0x1,0x0,0xa2,0xff,0xea,0x4,0x57,0x3,0xa6,0x0,0x31,0x0,0x53,0x40,0xc, + 0x20,0xf,0x2,0x3,0x3,0x2,0xc,0x1,0x0,0x3,0x2,0x4a,0x4b,0xb0,0x28,0x50,0x58,0x40,0x13,0x5,0x4,0x2,0x2,0x2,0x11,0x4b,0x0,0x3,0x3,0x0, + 0x5c,0x1,0x1,0x0,0x0,0xf,0x0,0x4c,0x1b,0x40,0x17,0x5,0x4,0x2,0x2,0x2,0x11,0x4b,0x0,0x0,0x0,0xf,0x4b,0x0,0x3,0x3,0x1,0x5c,0x0,0x1, + 0x1,0x17,0x1,0x4c,0x59,0x40,0xd,0x0,0x0,0x0,0x31,0x0,0x2f,0x29,0x38,0x27,0x37,0x6,0x7,0x18,0x2b,0x0,0x16,0x15,0x14,0x7,0x3,0x6,0x6,0x23, + 0x23,0x22,0x26,0x35,0x34,0x37,0x37,0x6,0x6,0x23,0x22,0x26,0x26,0x35,0x34,0x37,0x13,0x36,0x36,0x33,0x33,0x32,0x16,0x15,0x14,0x7,0x3,0x6,0x15,0x14, + 0x16,0x33,0x32,0x36,0x36,0x37,0x13,0x36,0x36,0x33,0x33,0x4,0x39,0x1e,0x2,0x90,0x6,0x21,0x27,0x2,0x21,0x1d,0x2,0x1b,0x54,0xe0,0x6b,0x55,0x7c,0x42, + 0x7,0x62,0x6,0x27,0x28,0x2,0x23,0x1e,0x2,0x61,0x5,0x4c,0x4d,0x56,0xba,0x8c,0x13,0x3c,0x6,0x27,0x28,0x2,0x3,0xa6,0x15,0x18,0x4,0x10,0xfc,0xd2, + 0x24,0x1d,0x15,0x17,0x5,0x10,0x99,0x67,0x7f,0x40,0x75,0x4f,0x26,0x26,0x2,0x2b,0x23,0x1e,0x15,0x18,0x4,0x10,0xfd,0xda,0x20,0x16,0x42,0x49,0x6d,0xbb, + 0x6e,0x1,0x51,0x23,0x1e,0x0,0xff,0xff,0x0,0xa2,0xff,0xea,0x4,0x57,0x5,0x5e,0x0,0x22,0x0,0xe4,0x0,0x0,0x0,0x2,0x1,0x80,0xf4,0x0,0x0,0x0, + 0xff,0xff,0x0,0xa2,0xff,0xea,0x4,0x71,0x5,0x4e,0x0,0x22,0x1,0x82,0xec,0x0,0x0,0x2,0x0,0xe4,0x0,0x0,0x0,0x0,0xff,0xff,0x0,0xa2,0xff,0xea, + 0x4,0x57,0x5,0x60,0x0,0x22,0x0,0xe4,0x0,0x0,0x0,0x2,0x1,0x92,0x0,0x0,0x0,0x0,0xff,0xff,0x0,0xa2,0xff,0xea,0x4,0x57,0x5,0x28,0x0,0x22, + 0x0,0xe4,0x0,0x0,0x0,0x2,0x1,0x93,0xf6,0x0,0x0,0x0,0xff,0xff,0x0,0xa2,0xff,0xea,0x4,0x57,0x5,0x5e,0x0,0x22,0x0,0xe4,0x0,0x0,0x0,0x2, + 0x1,0x95,0x2,0x0,0x0,0x0,0xff,0xff,0x0,0xa2,0xff,0xea,0x4,0xbe,0x5,0x69,0x0,0x22,0x0,0xe4,0x0,0x0,0x0,0x2,0x1,0x96,0xe2,0x0,0x0,0x0, + 0xff,0xff,0x0,0xa2,0xff,0xea,0x4,0x57,0x4,0xe2,0x0,0x22,0x0,0xe4,0x0,0x0,0x0,0x2,0x1,0x97,0xec,0x0,0x0,0x0,0x0,0x1,0x0,0xa2,0xfe,0x28, + 0x4,0x57,0x3,0xa6,0x0,0x42,0x0,0x61,0xb7,0x31,0x20,0x2,0x3,0x5,0x4,0x1,0x4a,0x4b,0xb0,0x31,0x50,0x58,0x40,0x1a,0x1,0x1,0x0,0x0,0x2,0x0, + 0x2,0x5f,0x7,0x6,0x2,0x4,0x4,0x11,0x4b,0x0,0x5,0x5,0x3,0x5c,0x0,0x3,0x3,0x17,0x3,0x4c,0x1b,0x40,0x21,0x0,0x1,0x3,0x0,0x3,0x1,0x0, + 0x70,0x0,0x0,0x0,0x2,0x0,0x2,0x5f,0x7,0x6,0x2,0x4,0x4,0x11,0x4b,0x0,0x5,0x5,0x3,0x5c,0x0,0x3,0x3,0x17,0x3,0x4c,0x59,0x40,0xf,0x0, + 0x0,0x0,0x42,0x0,0x40,0x29,0x38,0x28,0x26,0x21,0x2d,0x8,0x7,0x1a,0x2b,0x0,0x16,0x15,0x14,0x7,0x3,0x6,0x6,0x7,0x6,0x6,0x15,0x14,0x16,0x33, + 0x32,0x37,0x37,0x32,0x15,0x14,0x6,0x7,0x6,0x6,0x23,0x22,0x26,0x35,0x34,0x36,0x37,0x37,0x6,0x6,0x23,0x22,0x26,0x26,0x35,0x34,0x37,0x13,0x36,0x36, + 0x33,0x33,0x32,0x16,0x15,0x14,0x7,0x3,0x6,0x15,0x14,0x16,0x33,0x32,0x36,0x36,0x37,0x13,0x36,0x36,0x33,0x33,0x4,0x39,0x1e,0x2,0x90,0x4,0xd,0xb, + 0x6d,0x68,0x22,0x1f,0x33,0x3c,0x8,0x28,0x12,0x13,0x26,0x5e,0x2e,0x4f,0x56,0x80,0x7e,0x1e,0x54,0xe0,0x6b,0x55,0x7c,0x42,0x7,0x62,0x6,0x27,0x28,0x2, + 0x23,0x1e,0x2,0x61,0x5,0x4c,0x4d,0x56,0xba,0x8c,0x13,0x3c,0x6,0x27,0x28,0x2,0x3,0xa6,0x15,0x18,0x4,0x10,0xfc,0xd2,0x14,0x19,0x9,0x58,0x88,0x36, + 0x1d,0x1e,0xa,0x1,0x42,0x14,0x19,0x7,0xd,0x10,0x58,0x4a,0x5a,0xa9,0x59,0xaa,0x67,0x7f,0x40,0x75,0x4f,0x26,0x26,0x2,0x2b,0x23,0x1e,0x15,0x18,0x4, + 0x10,0xfd,0xda,0x20,0x16,0x42,0x49,0x6d,0xbb,0x6e,0x1,0x51,0x23,0x1e,0x0,0x0,0xff,0xff,0x0,0xa2,0xff,0xea,0x4,0x57,0x6,0x15,0x0,0x22,0x0,0xe4, + 0x0,0x0,0x0,0x2,0x1,0x99,0xe0,0x0,0x0,0x0,0xff,0xff,0x0,0xa2,0xff,0xea,0x4,0x57,0x5,0x11,0x0,0x22,0x1,0x9a,0xec,0x0,0x0,0x2,0x0,0xe4, + 0x0,0x0,0x0,0x0,0x0,0x1,0x0,0xcb,0xff,0xea,0x4,0xa0,0x3,0xa9,0x0,0x1c,0x0,0x1d,0x40,0x1a,0x16,0xd,0x1,0x3,0x0,0x1,0x1,0x4a,0x2,0x1, + 0x1,0x1,0x11,0x4b,0x0,0x0,0x0,0x17,0x0,0x4c,0x26,0x29,0x26,0x3,0x7,0x17,0x2b,0x0,0x15,0x14,0x7,0x1,0x6,0x6,0x23,0x22,0x26,0x27,0x3,0x26, + 0x35,0x34,0x36,0x37,0x36,0x33,0x32,0x16,0x17,0x13,0x1,0x36,0x36,0x33,0x32,0x17,0x4,0xa0,0xf,0xfe,0xc,0x15,0x3b,0x31,0x31,0x31,0x9,0xe2,0x4,0x1c, + 0x21,0x19,0x13,0x13,0x16,0x6,0xcf,0x1,0xda,0xd,0x19,0x10,0x12,0x1d,0x3,0x85,0x1d,0x12,0x18,0xfc,0xea,0x21,0x1d,0x1d,0x21,0x3,0x20,0xd,0xe,0x15, + 0x1b,0x9,0x7,0x13,0x16,0xfd,0x15,0x2,0xf3,0x14,0x13,0xd,0x0,0x1,0x0,0x8a,0xff,0xea,0x4,0xe5,0x3,0xa5,0x0,0x28,0x0,0x28,0x40,0x25,0x22,0x1b, + 0xc,0x3,0x0,0x3,0x1,0x4a,0x0,0x3,0x2,0x0,0x2,0x3,0x0,0x70,0x4,0x1,0x2,0x2,0x11,0x4b,0x1,0x1,0x0,0x0,0x17,0x0,0x4c,0x16,0x25,0x25, + 0x26,0x27,0x5,0x7,0x19,0x2b,0x0,0x16,0x15,0x14,0x7,0x1,0x6,0x6,0x23,0x22,0x26,0x27,0x3,0x1,0x6,0x6,0x23,0x22,0x26,0x27,0x3,0x26,0x36,0x33, + 0x32,0x16,0x17,0x13,0x1,0x36,0x33,0x32,0x16,0x17,0x13,0x1,0x36,0x36,0x33,0x32,0x17,0x4,0xce,0x17,0xb,0xfe,0xae,0xf,0x28,0x26,0x2b,0x2b,0x5,0x4f, + 0xfe,0xfd,0x11,0x32,0x2a,0x26,0x23,0x2,0x3a,0x2,0x22,0x32,0x21,0x19,0x2,0x28,0x1,0xd,0x18,0x3f,0x21,0x23,0x3,0x51,0x1,0x15,0xb,0x1b,0x16,0x13, + 0x10,0x3,0x99,0x15,0x11,0x10,0x19,0xfc,0xe1,0x24,0x1d,0x1f,0x22,0x2,0x0,0xfe,0x0,0x23,0x1e,0x1e,0x23,0x3,0x35,0x25,0x1d,0x18,0x21,0xfd,0x69,0x2, + 0x15,0x2f,0x1a,0x15,0xfd,0xeb,0x2,0xa1,0x1a,0x18,0x5,0x0,0xff,0xff,0x0,0x8a,0xff,0xea,0x4,0xe5,0x5,0x5e,0x0,0x22,0x0,0xf0,0x0,0x0,0x0,0x2, + 0x1,0x80,0xf6,0x0,0x0,0x0,0xff,0xff,0x0,0x8a,0xff,0xea,0x4,0xe5,0x5,0x60,0x0,0x22,0x1,0x92,0xe6,0x0,0x0,0x2,0x0,0xf0,0x0,0x0,0x0,0x0, + 0xff,0xff,0x0,0x8a,0xff,0xea,0x4,0xe5,0x5,0x28,0x0,0x22,0x0,0xf0,0x0,0x0,0x0,0x2,0x1,0x93,0xe7,0x0,0x0,0x0,0xff,0xff,0x0,0x8a,0xff,0xea, + 0x4,0xe5,0x5,0x5e,0x0,0x22,0x0,0xf0,0x0,0x0,0x0,0x2,0x1,0x95,0xce,0x0,0x0,0x0,0x0,0x1,0x0,0x6b,0xff,0xf1,0x4,0x77,0x3,0xab,0x0,0x29, + 0x0,0x20,0x40,0x1d,0x24,0x19,0xf,0x4,0x4,0x0,0x2,0x1,0x4a,0x3,0x1,0x2,0x2,0x19,0x4b,0x1,0x1,0x0,0x0,0x17,0x0,0x4c,0x26,0x1c,0x25,0x2a, + 0x4,0x7,0x18,0x2b,0x0,0x15,0x14,0x7,0x1,0x1,0x16,0x15,0x14,0x7,0x6,0x23,0x22,0x26,0x27,0x3,0x1,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x1, + 0x3,0x26,0x35,0x34,0x37,0x36,0x33,0x32,0x16,0x17,0x13,0x1,0x36,0x33,0x32,0x17,0x4,0x77,0x17,0xfe,0x82,0x1,0x7,0xc,0x31,0x24,0x13,0xe,0x15,0xb, + 0xf3,0xfe,0x9d,0x1e,0x23,0x18,0x1e,0x27,0x16,0x1,0x8e,0xfd,0xd,0x32,0x22,0x15,0xe,0x15,0xb,0xe9,0x1,0x53,0x1e,0x22,0x19,0x1e,0x3,0x89,0x1c,0x14, + 0x16,0xfe,0x8d,0xfe,0x89,0x12,0x10,0x22,0x15,0xf,0xe,0x10,0x1,0x5f,0xfe,0xa1,0x1e,0xf,0x13,0x1b,0x15,0x16,0x1,0x84,0x1,0x66,0x11,0x12,0x21,0x15, + 0xf,0xe,0x10,0xfe,0xb4,0x1,0x4c,0x1e,0xf,0x0,0x0,0x1,0x0,0xc9,0xfe,0x6d,0x4,0xa0,0x3,0xa9,0x0,0x21,0x0,0x1c,0x40,0x19,0x1b,0xf,0x2,0x0, + 0x1,0x1,0x4a,0x2,0x1,0x1,0x1,0x11,0x4b,0x0,0x0,0x0,0x13,0x0,0x4c,0x26,0x2e,0x26,0x3,0x7,0x17,0x2b,0x0,0x16,0x15,0x14,0x7,0x1,0x6,0x23, + 0x22,0x27,0x26,0x26,0x35,0x34,0x37,0x13,0x1,0x26,0x35,0x34,0x36,0x37,0x36,0x33,0x32,0x16,0x17,0x13,0x1,0x36,0x36,0x33,0x32,0x17,0x4,0x88,0x18,0x10, + 0xfc,0xd8,0x1b,0x1f,0x14,0x1a,0x15,0x15,0x11,0xe9,0xfe,0xfe,0x5,0x1d,0x21,0x1e,0xd,0x12,0x15,0x8,0xdc,0x1,0xce,0xe,0x19,0x10,0x11,0x1d,0x3,0x90, + 0x19,0x10,0x11,0x18,0xfb,0x56,0x27,0xf,0xc,0x1a,0xf,0x12,0x1a,0x1,0x56,0x3,0x15,0x11,0xb,0x15,0x1b,0x8,0x7,0x13,0x16,0xfd,0x59,0x2,0xaf,0x14, + 0x13,0xd,0x0,0x0,0xff,0xff,0x0,0xc9,0xfe,0x6d,0x4,0xa0,0x5,0x5e,0x0,0x22,0x0,0xf6,0x0,0x0,0x0,0x2,0x1,0x80,0xfe,0x0,0x0,0x0,0xff,0xff, + 0x0,0xc9,0xfe,0x6d,0x4,0xa0,0x5,0x60,0x0,0x22,0x0,0xf6,0x0,0x0,0x0,0x2,0x1,0x92,0xf1,0x0,0x0,0x0,0xff,0xff,0x0,0xc9,0xfe,0x6d,0x4,0xa0, + 0x5,0x28,0x0,0x22,0x0,0xf6,0x0,0x0,0x0,0x2,0x1,0x93,0xec,0x0,0x0,0x0,0xff,0xff,0x0,0xc9,0xfe,0x6d,0x4,0xa0,0x5,0x5e,0x0,0x22,0x0,0xf6, + 0x0,0x0,0x0,0x2,0x1,0x95,0xd8,0x0,0x0,0x0,0x0,0x1,0x0,0x7f,0x0,0x0,0x4,0x58,0x3,0x9c,0x0,0x23,0x0,0x31,0x40,0x2e,0x14,0xb,0x2,0x0, + 0x1,0x1d,0x2,0x2,0x3,0x2,0x2,0x4a,0x0,0x0,0x0,0x1,0x59,0x0,0x1,0x1,0x11,0x4b,0x0,0x2,0x2,0x3,0x59,0x4,0x1,0x3,0x3,0xf,0x3,0x4c, + 0x0,0x0,0x0,0x23,0x0,0x21,0x27,0x36,0x27,0x5,0x7,0x17,0x2b,0x32,0x26,0x35,0x34,0x37,0x36,0x37,0x1,0x21,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33, + 0x21,0x32,0x16,0x15,0x14,0x7,0x6,0x7,0x1,0x21,0x32,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x21,0x99,0x1a,0x2,0x5,0x11,0x2,0xb6,0xfd,0xf1,0x1b,0x1a, + 0x3,0x7,0x22,0x22,0x2,0xcc,0x1c,0x19,0x2,0x4,0x13,0xfd,0x4c,0x2,0x2d,0x1b,0x1a,0x3,0x7,0x22,0x22,0xfd,0x16,0x15,0x18,0x7,0x10,0x1d,0x10,0x2, + 0x9b,0x16,0x18,0xb,0xf,0x27,0x21,0x15,0x19,0x6,0x10,0x1d,0x11,0xfd,0x66,0x16,0x18,0xb,0xf,0x27,0x21,0x0,0x0,0x0,0xff,0xff,0x0,0x7f,0x0,0x0, + 0x4,0x58,0x5,0x5e,0x0,0x22,0x1,0x80,0xe2,0x0,0x0,0x2,0x0,0xfb,0x0,0x0,0x0,0x0,0xff,0xff,0x0,0x7f,0x0,0x0,0x4,0x58,0x5,0x64,0x0,0x22, + 0x0,0xfb,0x0,0x0,0x0,0x2,0x1,0x83,0xe2,0x0,0x0,0x0,0xff,0xff,0x0,0x7f,0x0,0x0,0x4,0x58,0x5,0x51,0x0,0x22,0x0,0xfb,0x0,0x0,0x0,0x2, + 0x1,0x94,0xe9,0x0,0x0,0x0,0xff,0xff,0x0,0xfb,0xfe,0x72,0x8,0xdc,0x5,0x53,0x0,0x22,0x0,0x9f,0x0,0x0,0x0,0x3,0x0,0xa8,0x4,0xcc,0x0,0x0, + 0xff,0xff,0x0,0xfb,0xfe,0x72,0x8,0xc8,0x5,0x25,0x0,0x22,0x0,0x9f,0x0,0x0,0x0,0x3,0x0,0xb9,0x4,0xcc,0x0,0x0,0x0,0x2,0x1,0x35,0x1,0xd1, + 0x4,0x59,0x4,0xb9,0x0,0x29,0x0,0x37,0x0,0x8,0xb5,0x2f,0x2a,0x15,0xd,0x2,0x30,0x2b,0x0,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x23,0x22,0x26,0x27, + 0x6,0x6,0x23,0x22,0x26,0x26,0x35,0x34,0x36,0x36,0x33,0x32,0x16,0x17,0x37,0x36,0x36,0x33,0x32,0x16,0x15,0x14,0x7,0x3,0x6,0x15,0x14,0x16,0x33,0x33, + 0x4,0x36,0x36,0x35,0x34,0x26,0x23,0x22,0x6,0x6,0x15,0x14,0x16,0x33,0x4,0x45,0x14,0x2,0x7,0x1d,0x1b,0xa,0x3b,0x53,0xd,0x3d,0x89,0x56,0x53,0x84, + 0x4b,0x5d,0xb8,0x80,0x4e,0x6d,0x1d,0xd,0x6,0x1c,0x26,0x20,0x1a,0x2,0x4d,0x3,0x28,0x23,0x4,0xfe,0x94,0x79,0x3c,0x5e,0x58,0x4a,0x73,0x40,0x59,0x4e, + 0x2,0x64,0x15,0x15,0x7,0x10,0x21,0x1f,0x3d,0x39,0x46,0x42,0x49,0x87,0x5a,0x69,0xce,0x87,0x43,0x3b,0x43,0x1c,0x16,0x10,0x12,0x4,0xc,0xfe,0x4c,0x10, + 0xe,0x24,0x24,0xf,0x52,0x89,0x52,0x5a,0x59,0x56,0x8d,0x50,0x52,0x5b,0x0,0x0,0x0,0x2,0x1,0x3e,0x1,0xd1,0x4,0x28,0x4,0xb9,0x0,0xf,0x0,0x1d, + 0x0,0x8,0xb5,0x15,0x10,0x6,0x0,0x2,0x30,0x2b,0x0,0x16,0x16,0x15,0x14,0x6,0x6,0x23,0x22,0x26,0x26,0x35,0x34,0x36,0x36,0x33,0xe,0x2,0x15,0x14, + 0x16,0x33,0x32,0x36,0x36,0x35,0x34,0x26,0x23,0x3,0x43,0x93,0x52,0x67,0xc1,0x80,0x5e,0x92,0x52,0x68,0xc1,0x7f,0x60,0x7b,0x42,0x6a,0x5b,0x4a,0x7c,0x48, + 0x69,0x5b,0x4,0xb9,0x51,0x8f,0x5a,0x6a,0xc7,0x7d,0x50,0x8f,0x5a,0x6c,0xc7,0x7c,0x84,0x55,0x86,0x47,0x56,0x68,0x4c,0x85,0x51,0x56,0x68,0x0,0x0,0x0, + 0x0,0x2,0x0,0x5a,0x0,0x0,0x4,0x0,0x4,0xb9,0x0,0x14,0x0,0x18,0x0,0x8,0xb5,0x16,0x15,0x9,0x0,0x2,0x30,0x2b,0x0,0x16,0x17,0x13,0x16,0x15, + 0x14,0x7,0x6,0x6,0x23,0x21,0x22,0x26,0x35,0x34,0x37,0x1,0x36,0x36,0x33,0x7,0x1,0x21,0x3,0x3,0x20,0x2b,0x6,0xae,0x1,0x3,0x7,0x21,0x22,0xfc, + 0xd9,0x1f,0x13,0xb,0x2,0x20,0x12,0x38,0x29,0x1e,0xfe,0x3d,0x2,0x4a,0x85,0x4,0xb9,0x1f,0x22,0xfb,0xf2,0x5,0x8,0xb,0xf,0x25,0x1e,0x1a,0x28,0x14, + 0x14,0x4,0xe,0x22,0x1f,0xbb,0xfc,0x8a,0x3,0x76,0x0,0x1,0x0,0x19,0x0,0x0,0x4,0x9f,0x4,0xb9,0x0,0x3f,0x0,0x6,0xb3,0xe,0x0,0x1,0x30,0x2b, + 0x0,0x16,0x16,0x15,0x14,0x2,0x7,0x33,0x32,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x21,0x22,0x35,0x34,0x37,0x36,0x37,0x36,0x12,0x35,0x34,0x26,0x26,0x23, + 0x22,0x6,0x7,0x6,0x6,0x15,0x14,0x17,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x21,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x33,0x26,0x26,0x35,0x34,0x36, + 0x37,0x36,0x36,0x33,0x3,0x6c,0xc8,0x6b,0xa4,0xa4,0xbc,0x1b,0x1a,0x3,0x7,0x22,0x22,0xfe,0xa7,0x33,0x2,0x8,0x14,0xc9,0xb1,0x48,0x86,0x5d,0x69,0xaa, + 0x3d,0x37,0x39,0xbd,0x9,0x3,0x7,0x24,0x22,0xfe,0xbd,0x1b,0x1a,0x3,0x7,0x22,0x22,0xc5,0x4e,0x59,0x43,0x45,0x53,0xf0,0x96,0x4,0xb9,0x77,0xda,0x90, + 0xa6,0xfe,0xca,0x6c,0x16,0x18,0xb,0xf,0x27,0x21,0x34,0x10,0xa,0x34,0xc,0x70,0x1,0x2e,0xa6,0x69,0x99,0x51,0x62,0x55,0x4d,0xbf,0x60,0xde,0x95,0x6, + 0x19,0x10,0x12,0x2a,0x24,0x16,0x18,0xb,0xf,0x27,0x21,0x47,0xbe,0x72,0x78,0xe2,0x60,0x74,0x84,0x0,0x0,0x0,0x0,0x1,0x0,0x57,0xfe,0x72,0x4,0x51, + 0x3,0xa6,0x0,0x38,0x0,0x6,0xb3,0x18,0x0,0x1,0x30,0x2b,0x0,0x16,0x15,0x14,0x7,0x3,0x6,0x6,0x23,0x23,0x22,0x26,0x35,0x34,0x37,0x37,0x6,0x6, + 0x23,0x22,0x26,0x27,0x3,0x6,0x6,0x23,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x36,0x36,0x33,0x33,0x32,0x16,0x15,0x14,0x7,0x3,0x6,0x15,0x14,0x16,0x33, + 0x32,0x36,0x36,0x37,0x13,0x36,0x36,0x33,0x33,0x4,0x33,0x1e,0x2,0x8f,0x6,0x21,0x27,0x2,0x21,0x1d,0x2,0x19,0x4e,0xc3,0x61,0x35,0x57,0x1f,0x41,0x6, + 0x27,0x28,0x2,0x23,0x1e,0x2,0xd4,0x6,0x27,0x28,0x2,0x23,0x1e,0x2,0x61,0x5,0x4b,0x4e,0x4c,0xa7,0x7e,0x12,0x40,0x6,0x27,0x28,0x2,0x3,0xa6,0x15, + 0x18,0x4,0x10,0xfc,0xd2,0x24,0x1d,0x15,0x17,0x5,0x10,0x8f,0x62,0x7a,0x1e,0x1c,0xfe,0x8f,0x23,0x1e,0x15,0x18,0x4,0x10,0x4,0xb2,0x23,0x1e,0x15,0x18, + 0x4,0x10,0xfd,0xda,0x1e,0x17,0x42,0x4a,0x66,0xad,0x65,0x1,0x6f,0x23,0x1e,0x0,0x0,0x0,0x0,0x1,0x0,0xaf,0xff,0xf6,0x4,0xbc,0x3,0x9c,0x0,0x29, + 0x0,0x6,0xb3,0xb,0x0,0x1,0x30,0x2b,0x0,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x23,0x3,0x6,0x6,0x23,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x21,0x3, + 0x6,0x6,0x23,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x23,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x21,0x4,0xa3,0x19,0x2,0x8,0x22,0x22,0x56,0x80,0x6, + 0x27,0x28,0x2,0x23,0x1e,0x2,0x80,0xfe,0x50,0x80,0x6,0x27,0x28,0x2,0x23,0x1e,0x2,0x80,0x58,0x1b,0x1a,0x3,0x7,0x22,0x22,0x3,0x8a,0x3,0x9c,0x16, + 0x19,0xf,0xa,0x27,0x21,0xfd,0x2b,0x23,0x1e,0x15,0x18,0x4,0x10,0x2,0xd5,0xfd,0x2b,0x23,0x1e,0x15,0x18,0x4,0x10,0x2,0xd5,0x15,0x18,0xc,0xf,0x27, + 0x21,0x0,0x0,0x0,0x0,0x3,0x0,0xd4,0xff,0xea,0x4,0x3d,0x4,0xb9,0x0,0xf,0x0,0x18,0x0,0x21,0x0,0x3a,0x40,0x37,0x1e,0x1c,0x15,0x13,0x4,0x3, + 0x2,0x1,0x4a,0x5,0x1,0x2,0x2,0x1,0x5b,0x4,0x1,0x1,0x1,0x16,0x4b,0x6,0x1,0x3,0x3,0x0,0x5b,0x0,0x0,0x0,0x17,0x0,0x4c,0x19,0x19,0x10, + 0x10,0x0,0x0,0x19,0x21,0x19,0x20,0x10,0x18,0x10,0x17,0x0,0xf,0x0,0xe,0x26,0x7,0x7,0x15,0x2b,0x0,0x16,0x16,0x15,0x14,0x2,0x6,0x23,0x22,0x26, + 0x26,0x35,0x34,0x12,0x36,0x33,0x6,0x6,0x2,0x15,0x15,0x1,0x26,0x26,0x23,0x2,0x36,0x12,0x35,0x35,0x1,0x16,0x16,0x33,0x3,0x52,0x9a,0x51,0x75,0xf1, + 0xae,0x6a,0x9a,0x51,0x75,0xf1,0xae,0x7c,0xa7,0x5a,0x2,0x1f,0x19,0x5a,0x3c,0x36,0xa7,0x5a,0xfd,0xe1,0x19,0x59,0x3d,0x4,0xb9,0x70,0xcc,0x89,0xac,0xfe, + 0x97,0xf5,0x70,0xcc,0x89,0xac,0x1,0x69,0xf5,0x94,0xc3,0xfe,0xd9,0x91,0xf,0x2,0x9,0x3f,0x42,0xfc,0x59,0xc3,0x1,0x27,0x91,0xd,0xfd,0xf7,0x3e,0x41, + 0x0,0x1,0x0,0xc5,0x0,0x0,0x3,0xea,0x4,0xbc,0x0,0x25,0x0,0x35,0x40,0x32,0x12,0x1,0x2,0x3,0xb,0x2,0x2,0x0,0x1,0x2,0x4a,0x0,0x2,0x3, + 0x1,0x3,0x2,0x1,0x70,0x0,0x3,0x3,0x16,0x4b,0x5,0x4,0x2,0x1,0x1,0x0,0x5a,0x0,0x0,0x0,0xf,0x0,0x4c,0x0,0x0,0x0,0x25,0x0,0x24,0x17, + 0x23,0x26,0x36,0x6,0x7,0x18,0x2b,0x24,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x21,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x33,0x13,0x5,0x6,0x23,0x22, + 0x27,0x26,0x35,0x34,0x37,0x25,0x36,0x33,0x32,0x16,0x15,0x14,0x7,0x3,0x21,0x3,0xd0,0x1a,0x3,0x7,0x22,0x22,0xfd,0x5e,0x1c,0x19,0x2,0x7,0x23,0x22, + 0xf9,0x94,0xfe,0xf4,0x10,0xd,0x20,0x17,0xd,0x21,0x1,0xae,0x15,0x15,0x12,0x18,0x1,0xb3,0x1,0x13,0x90,0x16,0x18,0xb,0xf,0x27,0x21,0x16,0x19,0xf, + 0xa,0x27,0x21,0x3,0x48,0x97,0x8,0x2c,0x19,0x13,0x1f,0x11,0xef,0xc,0x16,0x13,0x9,0x5,0xfc,0xb,0x0,0x0,0x0,0x1,0x0,0x85,0x0,0x0,0x4,0x24, + 0x4,0xb9,0x0,0x2f,0x0,0x35,0x40,0x32,0x15,0xc,0x2,0x1,0x0,0x1,0x4a,0x0,0x3,0x2,0x0,0x2,0x3,0x0,0x70,0x0,0x2,0x2,0x4,0x5b,0x5,0x1, + 0x4,0x4,0x16,0x4b,0x0,0x0,0x0,0x1,0x59,0x0,0x1,0x1,0xf,0x1,0x4c,0x0,0x0,0x0,0x2f,0x0,0x2e,0x23,0x2d,0x36,0x28,0x6,0x7,0x18,0x2b,0x0, + 0x16,0x16,0x15,0x14,0x6,0x6,0x7,0x1,0x21,0x32,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x21,0x22,0x26,0x35,0x34,0x37,0x36,0x37,0x1,0x3e,0x2,0x35,0x34, + 0x26,0x23,0x22,0x6,0x7,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x3,0x36,0x9a,0x54,0x3a,0x74,0x62,0xfe,0x7c,0x2,0x11,0x1b,0x1a,0x3, + 0x7,0x22,0x22,0xfd,0x33,0x1c,0x1a,0x2,0x7,0x12,0x2,0x7,0x50,0x5f,0x2f,0x64,0x60,0x42,0x96,0x48,0x11,0x12,0x22,0x14,0x9,0x28,0x53,0xbc,0x5a,0x4, + 0xb9,0x48,0x87,0x5b,0x52,0x89,0x83,0x54,0xfe,0xb3,0x16,0x18,0xb,0xf,0x27,0x21,0x15,0x18,0x7,0x10,0x26,0x10,0x1,0xbe,0x45,0x68,0x66,0x3a,0x4b,0x55, + 0x2c,0x29,0xa,0x2a,0x15,0x12,0x25,0x17,0x32,0x34,0x0,0x0,0x0,0x1,0x0,0x93,0xff,0xea,0x4,0x16,0x4,0xb9,0x0,0x41,0x0,0x48,0x40,0x45,0x27,0x6, + 0x2,0x3,0x4,0x1,0x4a,0x0,0x6,0x5,0x4,0x5,0x6,0x4,0x70,0x0,0x1,0x3,0x2,0x3,0x1,0x2,0x70,0x0,0x4,0x0,0x3,0x1,0x4,0x3,0x63,0x0, + 0x5,0x5,0x7,0x5b,0x8,0x1,0x7,0x7,0x16,0x4b,0x0,0x2,0x2,0x0,0x5b,0x0,0x0,0x0,0x17,0x0,0x4c,0x0,0x0,0x0,0x41,0x0,0x40,0x22,0x25,0x36, + 0x35,0x23,0x28,0x2c,0x9,0x7,0x1b,0x2b,0x0,0x16,0x16,0x15,0x14,0x6,0x7,0x16,0x16,0x15,0x14,0x6,0x6,0x23,0x22,0x26,0x27,0x26,0x35,0x34,0x37,0x36, + 0x36,0x33,0x32,0x17,0x16,0x16,0x33,0x32,0x36,0x36,0x35,0x34,0x26,0x23,0x23,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x33,0x32,0x36,0x36,0x35,0x34,0x26, + 0x23,0x22,0x7,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x3,0x31,0x96,0x4f,0x62,0x64,0x45,0x4a,0x5e,0xd2,0xa3,0x62,0xbc,0x45,0x16,0x12, + 0xd,0x20,0x10,0x11,0xf,0x39,0x93,0x4e,0x67,0x86,0x3d,0x72,0x68,0x49,0x1c,0x19,0x2,0x7,0x22,0x22,0x37,0x60,0x78,0x34,0x65,0x5f,0x8c,0x8b,0x12,0xc, + 0x20,0x14,0xa,0x29,0x50,0xb2,0x57,0x4,0xb9,0x47,0x7f,0x54,0x66,0x9c,0x2c,0x20,0x7b,0x57,0x5f,0xb9,0x7d,0x44,0x3a,0x13,0x18,0x17,0x1b,0x14,0x1a,0xe, + 0x30,0x37,0x4d,0x77,0x3f,0x53,0x57,0x15,0x18,0x5,0x12,0x25,0x1f,0x41,0x66,0x3b,0x44,0x4c,0x49,0x8,0x2c,0x15,0x13,0x25,0x16,0x2b,0x2b,0x0,0x0,0x2, + 0x0,0x8b,0xff,0xf6,0x4,0x25,0x4,0xb9,0x0,0x25,0x0,0x28,0x0,0x38,0x40,0x35,0x28,0x1,0x2,0x0,0x4,0x1d,0x8,0x2,0x1,0x0,0x16,0x1,0x2,0x1, + 0x3,0x4a,0x5,0x1,0x0,0x3,0x1,0x1,0x2,0x0,0x1,0x62,0x6,0x1,0x4,0x4,0x16,0x4b,0x0,0x2,0x2,0xf,0x2,0x4c,0x0,0x0,0x27,0x26,0x0,0x25, + 0x0,0x24,0x25,0x33,0x26,0x24,0x7,0x7,0x18,0x2b,0x0,0x15,0x14,0x7,0x3,0x33,0x32,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x23,0x7,0x6,0x6,0x23,0x23, + 0x22,0x26,0x35,0x34,0x37,0x37,0x21,0x22,0x26,0x35,0x34,0x37,0x36,0x37,0x1,0x36,0x36,0x33,0x1,0x21,0x13,0x3,0xc9,0x2,0x79,0xa2,0x1b,0x1a,0x3,0x7, + 0x22,0x22,0xa2,0x2c,0x6,0x27,0x28,0x2,0x23,0x1e,0x2,0x2c,0xfe,0x22,0x1c,0x1a,0x2,0x6,0xe,0x2,0x64,0x1a,0x37,0x26,0xfd,0xe9,0x1,0x53,0x66,0x4, + 0xb9,0x3c,0x6,0xc,0xfd,0x53,0x15,0x18,0xc,0xf,0x27,0x21,0xf7,0x23,0x1e,0x15,0x18,0x4,0x10,0xf7,0x18,0x1a,0xf,0x9,0x20,0x11,0x2,0xd3,0x20,0x1d, + 0xfd,0x5,0x2,0x17,0x0,0x0,0x0,0x1,0x0,0x9d,0xff,0xea,0x4,0x35,0x4,0xa3,0x0,0x35,0x0,0x4a,0x40,0x47,0x2,0x1,0x0,0x7,0x9,0x1,0x5,0x1, + 0x2,0x4a,0x0,0x6,0x5,0x3,0x5,0x6,0x3,0x70,0x0,0x3,0x4,0x5,0x3,0x4,0x6e,0x0,0x1,0x0,0x5,0x6,0x1,0x5,0x63,0x0,0x0,0x0,0x7,0x59, + 0x8,0x1,0x7,0x7,0xe,0x4b,0x0,0x4,0x4,0x2,0x5b,0x0,0x2,0x2,0x17,0x2,0x4c,0x0,0x0,0x0,0x35,0x0,0x33,0x23,0x25,0x24,0x17,0x26,0x22,0x26, + 0x9,0x7,0x1b,0x2b,0x0,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x21,0x3,0x36,0x33,0x32,0x16,0x16,0x15,0x14,0x6,0x6,0x23,0x22,0x26,0x27,0x26,0x35,0x34, + 0x37,0x36,0x33,0x32,0x17,0x16,0x16,0x33,0x32,0x36,0x36,0x35,0x34,0x26,0x23,0x22,0x7,0x6,0x6,0x23,0x22,0x26,0x37,0x13,0x36,0x36,0x33,0x21,0x4,0x1b, + 0x1a,0x3,0x7,0x23,0x22,0xfe,0x2d,0x42,0x62,0x74,0x68,0x96,0x4e,0x69,0xda,0x9f,0x5f,0xb7,0x46,0x18,0x15,0x1d,0x1c,0xe,0x10,0x39,0x8f,0x4b,0x68,0x8f, + 0x46,0x6e,0x61,0x7b,0x6d,0xd,0x18,0x12,0x23,0x2a,0x4,0x71,0x8,0x26,0x29,0x2,0x16,0x4,0xa3,0x16,0x18,0xb,0xf,0x27,0x21,0xfe,0xcc,0x33,0x56,0x99, + 0x64,0x6e,0xd8,0x8f,0x40,0x37,0x12,0x1a,0x18,0x1f,0x2b,0xd,0x2e,0x36,0x5e,0x92,0x4d,0x59,0x6a,0x43,0x8,0x6,0x16,0x14,0x2,0xb,0x23,0x1e,0x0,0x2, + 0x0,0xd7,0xff,0xea,0x4,0x56,0x4,0xba,0x0,0x1a,0x0,0x28,0x0,0x3c,0x40,0x39,0x7,0x1,0x5,0x1,0x1,0x4a,0x0,0x1,0x7,0x1,0x5,0x4,0x1,0x5, + 0x63,0x0,0x0,0x0,0x3,0x5b,0x6,0x1,0x3,0x3,0x16,0x4b,0x0,0x4,0x4,0x2,0x5b,0x0,0x2,0x2,0x17,0x2,0x4c,0x1b,0x1b,0x0,0x0,0x1b,0x28,0x1b, + 0x27,0x22,0x20,0x0,0x1a,0x0,0x19,0x26,0x24,0x23,0x8,0x7,0x17,0x2b,0x0,0x16,0x15,0x14,0x7,0x4,0x4,0x7,0x36,0x36,0x33,0x32,0x16,0x16,0x15,0x14, + 0x6,0x6,0x23,0x22,0x26,0x26,0x35,0x34,0x12,0x24,0x37,0x0,0x6,0x6,0x15,0x14,0x16,0x33,0x32,0x36,0x36,0x35,0x34,0x26,0x23,0x4,0x3a,0x1c,0x30,0xfe, + 0xf4,0xfe,0xb4,0x41,0x41,0x9e,0x57,0x60,0x95,0x53,0x6d,0xd1,0x8d,0x7a,0xa2,0x4d,0xd0,0x1,0x7d,0xfd,0xfe,0x34,0x8f,0x57,0x72,0x65,0x61,0x87,0x43,0x69, + 0x61,0x4,0xba,0x1e,0x26,0x4e,0x2,0xe,0xf4,0xcc,0x40,0x43,0x4f,0x91,0x5e,0x6d,0xc8,0x7e,0x66,0xa9,0x67,0xf7,0x1,0x7c,0xda,0xc,0xfd,0x8e,0x40,0x78, + 0x50,0x58,0x69,0x51,0x80,0x44,0x56,0x5e,0x0,0x0,0x0,0x1,0x1,0x42,0xff,0xea,0x4,0x86,0x4,0xa3,0x0,0x1a,0x0,0x2a,0x40,0x27,0x14,0x2,0x2,0x1, + 0x2,0xd,0x1,0x0,0x1,0x2,0x4a,0x0,0x1,0x1,0x2,0x59,0x3,0x1,0x2,0x2,0xe,0x4b,0x0,0x0,0x0,0x17,0x0,0x4c,0x0,0x0,0x0,0x1a,0x0,0x18, + 0x25,0x29,0x4,0x7,0x16,0x2b,0x0,0x16,0x15,0x14,0x7,0x6,0x7,0x1,0x6,0x6,0x23,0x22,0x26,0x35,0x34,0x37,0x1,0x21,0x22,0x26,0x35,0x34,0x37,0x36, + 0x36,0x33,0x21,0x4,0x6d,0x19,0x2,0x4,0x9,0xfd,0x9c,0xe,0x30,0x1b,0x1d,0x2a,0x9,0x2,0x42,0xfd,0xb9,0x1b,0x1a,0x3,0x7,0x23,0x22,0x2,0xc0,0x4, + 0xa3,0x14,0x18,0xf,0x9,0x16,0x12,0xfb,0xe1,0x17,0x17,0x1a,0x16,0x10,0xe,0x3,0xdb,0x16,0x18,0xb,0xf,0x27,0x21,0x0,0x0,0x0,0x0,0x3,0x0,0xc2, + 0xff,0xea,0x4,0x2c,0x4,0xb9,0x0,0x1b,0x0,0x2a,0x0,0x39,0x0,0x44,0x40,0x41,0x14,0x6,0x2,0x5,0x2,0x1,0x4a,0x0,0x2,0x8,0x1,0x5,0x4,0x2, + 0x5,0x63,0x7,0x1,0x3,0x3,0x1,0x5b,0x6,0x1,0x1,0x1,0x16,0x4b,0x0,0x4,0x4,0x0,0x5b,0x0,0x0,0x0,0x17,0x0,0x4c,0x2b,0x2b,0x1c,0x1c,0x0, + 0x0,0x2b,0x39,0x2b,0x38,0x33,0x31,0x1c,0x2a,0x1c,0x29,0x24,0x22,0x0,0x1b,0x0,0x1a,0x2c,0x9,0x7,0x15,0x2b,0x0,0x16,0x16,0x15,0x14,0x6,0x7,0x16, + 0x16,0x15,0x14,0x6,0x6,0x23,0x22,0x26,0x26,0x35,0x34,0x36,0x37,0x26,0x26,0x35,0x34,0x36,0x36,0x33,0xe,0x2,0x15,0x14,0x16,0x16,0x33,0x32,0x36,0x36, + 0x35,0x34,0x26,0x23,0x2,0x6,0x6,0x15,0x14,0x16,0x16,0x33,0x32,0x36,0x36,0x35,0x34,0x26,0x23,0x3,0x48,0x93,0x51,0x73,0x60,0x48,0x53,0x6c,0xd2,0x90, + 0x68,0xa2,0x5a,0x8e,0x74,0x38,0x43,0x63,0xbe,0x81,0x5c,0x73,0x3c,0x2f,0x54,0x37,0x43,0x73,0x44,0x66,0x53,0xb2,0x83,0x46,0x34,0x61,0x41,0x57,0x83,0x47, + 0x74,0x62,0x4,0xb9,0x4c,0x82,0x50,0x65,0x96,0x2f,0x23,0x7e,0x50,0x60,0xbc,0x7a,0x51,0x8e,0x59,0x78,0xb1,0x30,0x22,0x6e,0x43,0x51,0xa9,0x71,0x94,0x3e, + 0x63,0x35,0x2c,0x46,0x28,0x3b,0x64,0x3a,0x46,0x51,0xfe,0x5,0x49,0x75,0x3f,0x34,0x4f,0x2c,0x48,0x75,0x40,0x4f,0x60,0x0,0x0,0x0,0x0,0x2,0x0,0xba, + 0xff,0xe9,0x4,0x39,0x4,0xb9,0x0,0x1a,0x0,0x28,0x0,0x3c,0x40,0x39,0xf,0x1,0x2,0x4,0x1,0x4a,0x0,0x4,0x0,0x2,0x1,0x4,0x2,0x63,0x7,0x1, + 0x5,0x5,0x3,0x5b,0x6,0x1,0x3,0x3,0x16,0x4b,0x0,0x1,0x1,0x0,0x5b,0x0,0x0,0x0,0x17,0x0,0x4c,0x1b,0x1b,0x0,0x0,0x1b,0x28,0x1b,0x27,0x22, + 0x20,0x0,0x1a,0x0,0x19,0x24,0x23,0x26,0x8,0x7,0x17,0x2b,0x0,0x16,0x16,0x15,0x14,0x2,0x4,0x7,0x6,0x26,0x35,0x34,0x37,0x24,0x24,0x37,0x6,0x6, + 0x23,0x22,0x26,0x26,0x35,0x34,0x36,0x36,0x33,0xe,0x2,0x15,0x14,0x16,0x33,0x32,0x36,0x36,0x35,0x34,0x26,0x23,0x3,0x4a,0xa2,0x4d,0xd0,0xfe,0x83,0xfd, + 0x19,0x1c,0x30,0x1,0xc,0x1,0x4c,0x41,0x41,0x9e,0x57,0x60,0x95,0x53,0x6d,0xd1,0x8d,0x67,0x87,0x43,0x69,0x61,0x52,0x8f,0x57,0x72,0x65,0x4,0xb9,0x66, + 0xa9,0x67,0xf7,0xfe,0x84,0xda,0xc,0x1,0x1e,0x26,0x4e,0x2,0xe,0xf4,0xcc,0x40,0x43,0x4f,0x91,0x5e,0x6d,0xc8,0x7e,0x94,0x51,0x80,0x44,0x56,0x5e,0x40, + 0x78,0x50,0x58,0x69,0x0,0x1,0x0,0x54,0x0,0xc4,0x4,0xa1,0x3,0x56,0x0,0x11,0x0,0x11,0x40,0xe,0x0,0x0,0x1,0x0,0x72,0x0,0x1,0x1,0x69,0x18, + 0x11,0x2,0x7,0x16,0x2b,0x0,0x33,0x32,0x17,0x16,0x15,0x14,0x7,0x1,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x1,0x4,0x68,0x9,0x19,0xf,0x8,0x13, + 0xfc,0x7,0x7,0x9,0x19,0xf,0x9,0x14,0x3,0xf9,0x3,0x56,0x1c,0xd,0x11,0x19,0xa,0xfd,0xcf,0x4,0x1c,0xf,0xf,0x18,0xb,0x2,0x31,0x0,0x0,0x3, + 0x0,0x3a,0xff,0x28,0x4,0xa1,0x5,0x72,0x0,0x21,0x0,0x33,0x0,0x5d,0x0,0x6a,0xb1,0x6,0x64,0x44,0x40,0x5f,0xd,0x6,0x2,0x1,0x0,0x3e,0x1,0x6, + 0x5,0x2,0x4a,0x21,0x1b,0x13,0x3,0x3,0x48,0x0,0x3,0x0,0x3,0x72,0x0,0x8,0x7,0x4,0x7,0x8,0x4,0x70,0x0,0x4,0x5,0x7,0x4,0x5,0x6e,0x2, + 0x1,0x0,0x0,0x1,0x9,0x0,0x1,0x61,0xa,0x1,0x9,0x0,0x7,0x8,0x9,0x7,0x63,0x0,0x5,0x6,0x6,0x5,0x55,0x0,0x5,0x5,0x6,0x59,0x0,0x6, + 0x5,0x6,0x4d,0x34,0x34,0x34,0x5d,0x34,0x5c,0x55,0x54,0x50,0x4e,0x45,0x42,0x3b,0x3a,0x2d,0x2c,0x24,0x23,0x24,0x34,0x23,0xb,0x7,0x17,0x2b,0xb1,0x6, + 0x0,0x44,0x0,0x16,0x7,0x3,0x33,0x32,0x15,0x14,0x7,0x6,0x23,0x21,0x22,0x35,0x34,0x37,0x36,0x33,0x33,0x13,0x7,0x6,0x23,0x22,0x26,0x27,0x26,0x35, + 0x34,0x36,0x37,0x37,0x36,0x33,0x0,0x33,0x32,0x17,0x16,0x15,0x14,0x7,0x1,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x1,0x2,0x16,0x15,0x14,0x6,0x7, + 0x7,0x21,0x32,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x21,0x22,0x35,0x34,0x37,0x25,0x36,0x36,0x35,0x34,0x26,0x23,0x22,0x6,0x7,0x6,0x23,0x22,0x27,0x26, + 0x35,0x34,0x37,0x36,0x36,0x33,0x1,0xe2,0xf,0x2,0x65,0xa2,0x22,0x2,0x8,0x2a,0xfe,0x42,0x22,0x2,0x8,0x2a,0xab,0x54,0x99,0x10,0x5,0xc,0xf,0x4, + 0x3,0x11,0x15,0xfa,0xb,0xa,0x2,0x96,0x9,0x19,0xf,0x8,0x13,0xfc,0x7,0x7,0x9,0x19,0xf,0x9,0x14,0x3,0xf9,0x44,0x71,0x51,0x5d,0xe5,0x1,0x3a, + 0x12,0x11,0x1,0x4,0x15,0x14,0xfe,0x2f,0x1f,0x26,0x1,0x3e,0x40,0x38,0x39,0x37,0x2c,0x66,0x2d,0x8,0x9,0x17,0xd,0x7,0x13,0x39,0x87,0x3c,0x5,0x72, + 0x13,0x10,0xfd,0xc5,0x22,0x4,0xe,0x34,0x22,0x4,0xe,0x34,0x1,0xdc,0x2f,0x4,0x11,0x13,0xe,0xa,0x10,0x13,0x6,0x4c,0x4,0xfe,0xc,0x1c,0xd,0x11, + 0x19,0xa,0xfd,0xcf,0x4,0x1c,0xf,0xf,0x18,0xb,0x2,0x31,0xfe,0x74,0x64,0x56,0x49,0x73,0x43,0xa5,0x11,0x14,0x9,0x6,0x1b,0x19,0x26,0x30,0x1c,0xea, + 0x2f,0x49,0x2e,0x29,0x2f,0x19,0x14,0x4,0x23,0xf,0xf,0x13,0xb,0x1e,0x20,0x0,0x0,0x0,0x0,0x4,0x0,0x3a,0xff,0x22,0x4,0xa1,0x5,0x72,0x0,0x21, + 0x0,0x33,0x0,0x52,0x0,0x55,0x0,0xb4,0xb1,0x6,0x64,0x44,0x40,0x15,0xd,0x6,0x2,0x1,0x0,0x55,0x1,0x4,0x9,0x44,0x1,0x7,0x6,0x3,0x4a,0x21, + 0x1b,0x13,0x3,0x3,0x48,0x4b,0xb0,0xe,0x50,0x58,0x40,0x37,0x0,0x3,0x0,0x3,0x72,0xb,0x1,0x9,0x1,0x4,0x1,0x9,0x4,0x70,0x0,0x4,0x5,0x1, + 0x4,0x5,0x6e,0x0,0x7,0x6,0x6,0x7,0x67,0x2,0x1,0x0,0x0,0x1,0x9,0x0,0x1,0x61,0xa,0x1,0x5,0x6,0x6,0x5,0x57,0xa,0x1,0x5,0x5,0x6, + 0x5c,0x8,0x1,0x6,0x5,0x6,0x50,0x1b,0x40,0x36,0x0,0x3,0x0,0x3,0x72,0xb,0x1,0x9,0x1,0x4,0x1,0x9,0x4,0x70,0x0,0x4,0x5,0x1,0x4,0x5, + 0x6e,0x0,0x7,0x6,0x7,0x73,0x2,0x1,0x0,0x0,0x1,0x9,0x0,0x1,0x61,0xa,0x1,0x5,0x6,0x6,0x5,0x57,0xa,0x1,0x5,0x5,0x6,0x5c,0x8,0x1, + 0x6,0x5,0x6,0x50,0x59,0x40,0x1a,0x34,0x34,0x54,0x53,0x34,0x52,0x34,0x51,0x48,0x47,0x43,0x41,0x3e,0x3c,0x39,0x37,0x2d,0x2c,0x24,0x23,0x24,0x34,0x23, + 0xc,0x7,0x17,0x2b,0xb1,0x6,0x0,0x44,0x0,0x16,0x7,0x3,0x33,0x32,0x15,0x14,0x7,0x6,0x23,0x21,0x22,0x35,0x34,0x37,0x36,0x33,0x33,0x13,0x7,0x6, + 0x23,0x22,0x26,0x27,0x26,0x35,0x34,0x36,0x37,0x37,0x36,0x33,0x0,0x33,0x32,0x17,0x16,0x15,0x14,0x7,0x1,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x1, + 0x2,0x16,0x7,0x3,0x33,0x32,0x15,0x14,0x6,0x23,0x23,0x7,0x6,0x6,0x23,0x22,0x35,0x34,0x37,0x37,0x21,0x22,0x26,0x35,0x34,0x36,0x37,0x1,0x36,0x36, + 0x33,0x1,0x33,0x13,0x1,0xe2,0xf,0x2,0x65,0xa2,0x22,0x2,0x8,0x2a,0xfe,0x42,0x22,0x2,0x8,0x2a,0xab,0x54,0x99,0x10,0x5,0xc,0xf,0x4,0x3,0x11, + 0x15,0xfa,0xb,0xa,0x2,0x96,0x9,0x19,0xf,0x8,0x13,0xfc,0x7,0x7,0x9,0x19,0xf,0x9,0x14,0x3,0xf9,0x2e,0x29,0x4,0x42,0x5c,0x22,0x1a,0x1a,0x5c, + 0x1b,0x4,0x1d,0x1e,0x33,0x1,0x1b,0xfe,0xc6,0x11,0x13,0xb,0xd,0x1,0x7f,0x13,0x22,0x15,0xfe,0xb7,0xd8,0x35,0x5,0x72,0x13,0x10,0xfd,0xc5,0x22,0x4, + 0xe,0x34,0x22,0x4,0xe,0x34,0x1,0xdc,0x2f,0x4,0x11,0x13,0xe,0xa,0x10,0x13,0x6,0x4c,0x4,0xfd,0xda,0x1c,0xd,0x11,0x19,0xa,0xfd,0xcf,0x4,0x1c, + 0xf,0xf,0x18,0xb,0x2,0x31,0xfe,0xa7,0x1a,0x14,0xfe,0x8b,0x22,0x22,0x25,0x99,0x14,0x14,0x1f,0x6,0x3,0x99,0x18,0x17,0x19,0x18,0xe,0x1,0x7a,0x13, + 0x11,0xfe,0x5d,0x1,0xa,0x0,0x0,0x0,0x0,0x4,0x0,0x54,0xff,0x22,0x4,0xa1,0x5,0x72,0x0,0x3a,0x0,0x4c,0x0,0x6b,0x0,0x6e,0x1,0x28,0xb1,0x6, + 0x64,0x44,0x40,0x13,0x36,0x1,0x5,0x6,0x23,0x5,0x2,0x3,0x4,0x6e,0x1,0x8,0xd,0x5d,0x1,0xb,0xa,0x4,0x4a,0x4b,0xb0,0xe,0x50,0x58,0x40,0x44, + 0x10,0x1,0xd,0x0,0x8,0x0,0xd,0x8,0x70,0x0,0xb,0xa,0xa,0xb,0x67,0xf,0x1,0x6,0x0,0x5,0x4,0x6,0x5,0x63,0x0,0x4,0x0,0x3,0x1,0x4, + 0x3,0x63,0x0,0x2,0x0,0x0,0xd,0x2,0x0,0x63,0x7,0x1,0x1,0x0,0x8,0x9,0x1,0x8,0x63,0xe,0x1,0x9,0xa,0xa,0x9,0x57,0xe,0x1,0x9,0x9, + 0xa,0x5c,0xc,0x1,0xa,0x9,0xa,0x50,0x1b,0x4b,0xb0,0x13,0x50,0x58,0x40,0x43,0x10,0x1,0xd,0x0,0x8,0x0,0xd,0x8,0x70,0x0,0xb,0xa,0xb,0x73, + 0xf,0x1,0x6,0x0,0x5,0x4,0x6,0x5,0x63,0x0,0x4,0x0,0x3,0x1,0x4,0x3,0x63,0x0,0x2,0x0,0x0,0xd,0x2,0x0,0x63,0x7,0x1,0x1,0x0,0x8, + 0x9,0x1,0x8,0x63,0xe,0x1,0x9,0xa,0xa,0x9,0x57,0xe,0x1,0x9,0x9,0xa,0x5c,0xc,0x1,0xa,0x9,0xa,0x50,0x1b,0x40,0x4a,0x0,0x7,0x3,0x1, + 0x3,0x7,0x1,0x70,0x10,0x1,0xd,0x0,0x8,0x0,0xd,0x8,0x70,0x0,0xb,0xa,0xb,0x73,0xf,0x1,0x6,0x0,0x5,0x4,0x6,0x5,0x63,0x0,0x4,0x0, + 0x3,0x7,0x4,0x3,0x63,0x0,0x2,0x0,0x0,0xd,0x2,0x0,0x63,0x0,0x1,0x0,0x8,0x9,0x1,0x8,0x63,0xe,0x1,0x9,0xa,0xa,0x9,0x57,0xe,0x1, + 0x9,0x9,0xa,0x5c,0xc,0x1,0xa,0x9,0xa,0x50,0x59,0x59,0x40,0x23,0x4d,0x4d,0x0,0x0,0x6d,0x6c,0x4d,0x6b,0x4d,0x6a,0x61,0x60,0x5c,0x5a,0x57,0x55, + 0x52,0x50,0x46,0x45,0x3d,0x3c,0x0,0x3a,0x0,0x39,0x23,0x35,0x34,0x24,0x17,0x2b,0x11,0x7,0x1a,0x2b,0xb1,0x6,0x0,0x44,0x0,0x16,0x15,0x14,0x6,0x7, + 0x16,0x16,0x15,0x14,0x6,0x6,0x23,0x22,0x26,0x27,0x26,0x35,0x34,0x37,0x36,0x33,0x32,0x17,0x16,0x16,0x33,0x32,0x36,0x35,0x34,0x26,0x23,0x23,0x22,0x35, + 0x34,0x37,0x36,0x36,0x33,0x33,0x32,0x35,0x34,0x26,0x23,0x22,0x7,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x36,0x33,0x0,0x33,0x32,0x17,0x16,0x15,0x14, + 0x7,0x1,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x1,0x2,0x16,0x7,0x3,0x33,0x32,0x15,0x14,0x6,0x23,0x23,0x7,0x6,0x6,0x23,0x22,0x35,0x34,0x37, + 0x37,0x21,0x22,0x26,0x35,0x34,0x36,0x37,0x1,0x36,0x36,0x33,0x1,0x33,0x13,0x2,0x25,0x74,0x41,0x3b,0x2c,0x2f,0x45,0x87,0x60,0x40,0x77,0x30,0x10,0xb, + 0x14,0x16,0x9,0x8,0x2c,0x61,0x29,0x53,0x5e,0x48,0x42,0x2c,0x1d,0x2,0x4,0x15,0x15,0x20,0xa2,0x3e,0x35,0x59,0x63,0x8,0x6,0x19,0xc,0x3,0x1a,0x6c, + 0x7c,0x2,0xaa,0x9,0x19,0xf,0x8,0x13,0xfc,0x7,0x7,0x9,0x19,0xf,0x9,0x14,0x3,0xf9,0x2e,0x29,0x4,0x42,0x5c,0x22,0x1a,0x1a,0x5c,0x1b,0x4,0x1d, + 0x1e,0x33,0x1,0x1b,0xfe,0xc6,0x11,0x13,0xb,0xd,0x1,0x7f,0x13,0x22,0x15,0xfe,0xb7,0xd8,0x35,0x5,0x72,0x60,0x52,0x39,0x5a,0x1b,0x12,0x45,0x30,0x46, + 0x6c,0x3c,0x1d,0x1b,0xa,0x12,0xe,0x16,0x24,0x4,0x15,0x18,0x41,0x3a,0x2d,0x26,0x23,0x5,0xe,0x18,0x1a,0x74,0x29,0x2b,0x2a,0x3,0x29,0xc,0x9,0x1d, + 0xc,0x32,0xfd,0xe4,0x1c,0xd,0x11,0x19,0xa,0xfd,0xcf,0x4,0x1c,0xf,0xf,0x18,0xb,0x2,0x31,0xfe,0x9d,0x1a,0x14,0xfe,0x8b,0x22,0x22,0x25,0x99,0x14, + 0x14,0x1f,0x6,0x3,0x99,0x18,0x17,0x19,0x18,0xe,0x1,0x7a,0x13,0x11,0xfe,0x5d,0x1,0xa,0x0,0x0,0x0,0x1,0x1,0x53,0x1,0xe3,0x3,0xd0,0x5,0x36, + 0x0,0x24,0x0,0x6,0xb3,0xb,0x0,0x1,0x30,0x2b,0x0,0x16,0x15,0x7,0x3,0x33,0x32,0x16,0x15,0x14,0x7,0x6,0x23,0x21,0x22,0x26,0x35,0x34,0x37,0x36, + 0x33,0x33,0x13,0x7,0x6,0x23,0x22,0x26,0x27,0x26,0x35,0x34,0x36,0x37,0x25,0x36,0x33,0x3,0x4c,0x13,0x1,0x79,0xc2,0x16,0x13,0x2,0x9,0x33,0xfd,0xea, + 0x16,0x13,0x2,0x9,0x33,0xcd,0x65,0xb8,0x11,0x8,0xe,0x12,0x5,0x4,0x15,0x19,0x1,0x2b,0xd,0xc,0x5,0x36,0x11,0x10,0x9,0xfd,0x54,0x15,0x14,0x6, + 0x10,0x3e,0x15,0x14,0x6,0x10,0x3e,0x2,0x3b,0x39,0x5,0x15,0x17,0xf,0xe,0x13,0x17,0x7,0x5b,0x4,0x0,0x1,0x1,0x38,0x1,0xe3,0x4,0x2,0x5,0x36, + 0x0,0x29,0x0,0x6,0xb3,0xe,0x0,0x1,0x30,0x2b,0x0,0x16,0x15,0x14,0x6,0x7,0x5,0x21,0x32,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x21,0x22,0x35,0x34, + 0x37,0x1,0x36,0x36,0x35,0x34,0x26,0x23,0x22,0x6,0x7,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x3,0x7b,0x87,0x61,0x6f,0xfe,0xee,0x1, + 0x78,0x15,0x15,0x1,0x4,0x1b,0x18,0xfd,0xd4,0x26,0x2e,0x1,0x7d,0x4d,0x43,0x45,0x41,0x34,0x7b,0x36,0xc,0x8,0x1b,0x11,0x9,0x18,0x44,0xa1,0x49,0x5, + 0x36,0x77,0x68,0x57,0x8a,0x50,0xc6,0x15,0x17,0xb,0x7,0x21,0x1e,0x2e,0x3a,0x21,0x1,0x18,0x38,0x58,0x37,0x31,0x39,0x1e,0x18,0x5,0x2a,0x17,0xd,0x17, + 0xd,0x24,0x26,0x0,0x0,0x0,0x0,0x1,0x1,0x4c,0x1,0xd1,0x4,0x2,0x5,0x36,0x0,0x39,0x0,0x6,0xb3,0xb,0x0,0x1,0x30,0x2b,0x0,0x16,0x15,0x14, + 0x6,0x7,0x16,0x16,0x15,0x14,0x6,0x6,0x23,0x22,0x26,0x27,0x26,0x35,0x34,0x37,0x36,0x33,0x32,0x17,0x16,0x16,0x33,0x32,0x36,0x35,0x34,0x26,0x23,0x23, + 0x22,0x35,0x34,0x37,0x36,0x33,0x33,0x32,0x35,0x34,0x26,0x23,0x22,0x7,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x36,0x33,0x3,0x78,0x8a,0x4e,0x46,0x34, + 0x39,0x53,0xa1,0x73,0x4c,0x90,0x39,0x13,0xd,0x18,0x1a,0x8,0xc,0x35,0x74,0x31,0x64,0x70,0x55,0x50,0x34,0x23,0x2,0xa,0x2d,0x27,0xc2,0x4b,0x3f,0x6b, + 0x76,0x8,0x9,0x1f,0xd,0x4,0x1f,0x80,0x96,0x5,0x36,0x72,0x63,0x44,0x6b,0x21,0x16,0x53,0x39,0x54,0x82,0x48,0x23,0x20,0xb,0x17,0x12,0x19,0x2c,0x5, + 0x19,0x1d,0x4d,0x46,0x36,0x2d,0x2a,0x7,0x10,0x3c,0x8b,0x31,0x34,0x32,0x4,0x31,0xc,0xe,0x21,0xf,0x3c,0x0,0x0,0x0,0x0,0x2,0x1,0x43,0x1,0xdb, + 0x4,0x1a,0x5,0x36,0x0,0x1f,0x0,0x22,0x0,0x8,0xb5,0x22,0x20,0xf,0x0,0x2,0x30,0x2b,0x0,0x16,0x7,0x3,0x33,0x32,0x16,0x15,0x14,0x7,0x6,0x23, + 0x23,0x7,0x6,0x6,0x23,0x22,0x35,0x34,0x37,0x37,0x21,0x22,0x35,0x34,0x36,0x37,0x1,0x36,0x36,0x33,0x1,0x21,0x13,0x3,0xa6,0x31,0x4,0x4f,0x6d,0x16, + 0x13,0x2,0x9,0x33,0x6e,0x20,0x5,0x23,0x24,0x3d,0x1,0x21,0xfe,0x88,0x2c,0xe,0xf,0x1,0xcb,0x17,0x28,0x1a,0xfe,0x75,0x1,0x3,0x3f,0x5,0x36,0x20, + 0x17,0xfe,0x41,0x15,0x14,0x6,0x10,0x3e,0xb8,0x18,0x18,0x25,0x7,0x4,0xb8,0x38,0x1e,0x1d,0x10,0x1,0xc5,0x16,0x15,0xfe,0xa,0x1,0x3f,0x0,0x0,0x2, + 0x2,0xcd,0xff,0xf6,0x4,0x6d,0x3,0xa2,0x0,0x11,0x0,0x23,0x0,0x8,0xb5,0x19,0x12,0x7,0x0,0x2,0x30,0x2b,0x0,0x16,0x15,0x14,0x7,0x7,0x6,0x6, + 0x23,0x22,0x26,0x35,0x34,0x37,0x37,0x36,0x36,0x33,0x2,0x16,0x15,0x14,0x7,0x7,0x6,0x6,0x23,0x22,0x26,0x35,0x34,0x37,0x37,0x36,0x36,0x33,0x4,0x2b, + 0x42,0x1,0x21,0x6,0x3e,0x46,0x43,0x42,0x1,0x21,0x6,0x3e,0x46,0x2c,0x42,0x1,0x21,0x6,0x3e,0x46,0x43,0x42,0x1,0x21,0x6,0x3e,0x46,0x3,0xa2,0x19, + 0x1c,0x7,0x3,0xb8,0x22,0x1d,0x19,0x1c,0x7,0x3,0xb8,0x22,0x1d,0xfd,0x8a,0x19,0x1c,0x7,0x3,0xb8,0x22,0x1d,0x19,0x1c,0x7,0x3,0xb8,0x22,0x1d,0x0, + 0x0,0x0,0xff,0xff,0x2,0x35,0xfe,0xda,0x4,0x4,0x1,0x26,0x0,0x3,0x1,0x27,0x1,0x2c,0x0,0x0,0x0,0x0,0xff,0xff,0x2,0xcd,0xff,0xf6,0x3,0xfe, + 0x1,0x2c,0x0,0x3,0x1,0x2c,0x1,0x2c,0x0,0x0,0x0,0x0,0x0,0x2,0x2,0x5d,0xfe,0xda,0x4,0x6d,0x3,0xa2,0x0,0x11,0x0,0x22,0x0,0x8,0xb5,0x18, + 0x12,0x7,0x0,0x2,0x30,0x2b,0x0,0x16,0x15,0x14,0x7,0x7,0x6,0x6,0x23,0x22,0x26,0x35,0x34,0x37,0x37,0x36,0x36,0x33,0x12,0x16,0x15,0x14,0x7,0x1, + 0x6,0x23,0x22,0x35,0x34,0x37,0x13,0x36,0x36,0x33,0x33,0x4,0x2b,0x42,0x1,0x21,0x6,0x3e,0x46,0x43,0x42,0x1,0x21,0x6,0x3e,0x46,0x32,0x12,0xb,0xfe, + 0xdc,0x19,0x48,0x3f,0x5,0xad,0x7,0x1f,0x15,0xc0,0x3,0xa2,0x19,0x1c,0x7,0x3,0xb8,0x22,0x1d,0x19,0x1c,0x7,0x3,0xb8,0x22,0x1d,0xfd,0x84,0x10,0xe, + 0xe,0x13,0xfe,0x1c,0x29,0x28,0x9,0x11,0x1,0xe2,0x13,0x15,0xff,0xff,0xfe,0x1d,0xff,0xf6,0x6,0x56,0x1,0x2c,0x0,0x23,0x1,0x2c,0xfc,0x7c,0x0,0x0, + 0x0,0x22,0x1,0x2c,0x0,0x0,0x0,0x3,0x1,0x2c,0x3,0x84,0x0,0x0,0xff,0xff,0x2,0xe9,0xff,0xf6,0xb,0x22,0x1,0x2c,0x0,0x23,0x1,0x2c,0x1,0x48, + 0x0,0x0,0x0,0x23,0x1,0x2c,0x4,0xcc,0x0,0x0,0x0,0x3,0x1,0x2c,0x8,0x50,0x0,0x0,0x0,0x0,0x0,0x3,0x0,0xf3,0xff,0xf3,0x6,0x1f,0x0,0xe7, + 0x0,0xf,0x0,0x1f,0x0,0x2f,0x0,0xa,0xb7,0x25,0x20,0x15,0x10,0x5,0x0,0x3,0x30,0x2b,0x24,0x16,0x7,0x7,0x6,0x6,0x23,0x22,0x26,0x35,0x34,0x37, + 0x37,0x36,0x36,0x33,0x20,0x16,0x7,0x7,0x6,0x6,0x23,0x22,0x26,0x35,0x34,0x37,0x37,0x36,0x36,0x33,0x20,0x16,0x7,0x7,0x6,0x6,0x23,0x22,0x26,0x35, + 0x34,0x37,0x37,0x36,0x36,0x33,0x1,0xb4,0x33,0x4,0x1a,0x5,0x30,0x38,0x35,0x34,0x1,0x1a,0x5,0x30,0x38,0x2,0x55,0x33,0x4,0x1a,0x5,0x30,0x38,0x35, + 0x34,0x1,0x1a,0x5,0x30,0x38,0x2,0x55,0x33,0x4,0x1a,0x5,0x30,0x38,0x35,0x34,0x1,0x1a,0x5,0x30,0x38,0xe7,0x17,0x1a,0x91,0x1b,0x17,0x14,0x16,0x5, + 0x3,0x91,0x1b,0x16,0x17,0x1a,0x91,0x1b,0x17,0x14,0x16,0x5,0x3,0x91,0x1b,0x16,0x17,0x1a,0x91,0x1b,0x17,0x14,0x16,0x5,0x3,0x91,0x1b,0x16,0x0,0x0, + 0x0,0x3,0xfd,0xfb,0xff,0xf3,0x4,0x3f,0x0,0xe7,0x0,0xf,0x0,0x1f,0x0,0x2f,0x0,0xa,0xb7,0x25,0x20,0x15,0x10,0x5,0x0,0x3,0x30,0x2b,0x24,0x16, + 0x7,0x7,0x6,0x6,0x23,0x22,0x26,0x35,0x34,0x37,0x37,0x36,0x36,0x33,0x20,0x16,0x7,0x7,0x6,0x6,0x23,0x22,0x26,0x35,0x34,0x37,0x37,0x36,0x36,0x33, + 0x20,0x16,0x7,0x7,0x6,0x6,0x23,0x22,0x26,0x35,0x34,0x37,0x37,0x36,0x36,0x33,0xfe,0xbc,0x33,0x4,0x1a,0x5,0x30,0x38,0x35,0x34,0x1,0x1a,0x5,0x30, + 0x38,0x2,0xe1,0x33,0x4,0x1a,0x5,0x30,0x38,0x35,0x34,0x1,0x1a,0x5,0x30,0x38,0x2,0xe1,0x33,0x4,0x1a,0x5,0x30,0x38,0x35,0x34,0x1,0x1a,0x5,0x30, + 0x38,0xe7,0x17,0x1a,0x91,0x1b,0x17,0x14,0x16,0x5,0x3,0x91,0x1b,0x16,0x17,0x1a,0x91,0x1b,0x17,0x14,0x16,0x5,0x3,0x91,0x1b,0x16,0x17,0x1a,0x91,0x1b, + 0x17,0x14,0x16,0x5,0x3,0x91,0x1b,0x16,0x0,0x0,0xff,0xff,0x2,0xe9,0xff,0xf6,0xc,0x4e,0x1,0x2c,0x0,0x23,0x1,0x2c,0x1,0x48,0x0,0x0,0x0,0x23, + 0x1,0x2c,0x5,0x62,0x0,0x0,0x0,0x3,0x1,0x2c,0x9,0x7c,0x0,0x0,0x0,0x0,0x0,0x1,0x0,0xa7,0x0,0x96,0x4,0x97,0x4,0x67,0x0,0x32,0x0,0x60, + 0x4b,0xb0,0x31,0x50,0x58,0x40,0xe,0x29,0x1,0x2,0x3,0x2c,0x22,0x18,0xe,0x3,0x5,0x0,0x2,0x2,0x4a,0x1b,0x40,0xe,0x29,0x1,0x2,0x3,0x2c,0x22, + 0x18,0xe,0x3,0x5,0x0,0x4,0x2,0x4a,0x59,0x4b,0xb0,0x31,0x50,0x58,0x40,0x10,0x0,0x3,0x2,0x3,0x72,0x4,0x1,0x2,0x0,0x2,0x72,0x1,0x1,0x0, + 0x0,0x69,0x1b,0x40,0x14,0x0,0x3,0x2,0x3,0x72,0x0,0x2,0x4,0x2,0x72,0x0,0x4,0x0,0x4,0x72,0x1,0x1,0x0,0x0,0x69,0x59,0xb7,0x27,0x25,0x2c, + 0x25,0x29,0x5,0x7,0x19,0x2b,0x0,0x6,0x7,0x5,0x13,0x16,0x15,0x14,0x7,0x6,0x23,0x22,0x26,0x27,0x3,0x3,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37, + 0x1,0x25,0x26,0x35,0x34,0x37,0x36,0x33,0x32,0x17,0x5,0x13,0x36,0x36,0x33,0x32,0x16,0x15,0x14,0x7,0x3,0x25,0x36,0x33,0x32,0x16,0x15,0x4,0x97,0x29, + 0x25,0xfe,0x87,0xc7,0x12,0x30,0x1b,0x19,0x15,0x21,0xa,0x87,0xed,0x20,0x2c,0x1c,0x1b,0x22,0x21,0x1,0x2c,0xfe,0xa4,0x36,0xd,0x1c,0x2e,0x14,0x14,0x1, + 0x40,0x17,0x2,0x36,0x2e,0x25,0x29,0x5,0x69,0x1,0x65,0x15,0x13,0x20,0x27,0x2,0xba,0x2e,0x8,0x48,0xfe,0xdf,0x19,0x19,0x26,0x1c,0x11,0x1c,0x18,0x1, + 0x3d,0xfe,0xc3,0x2b,0x15,0x1d,0x23,0x22,0x1e,0x1,0x11,0x50,0xc,0x2e,0x18,0x1b,0x3d,0xa,0x99,0x1,0x71,0x2a,0x30,0x20,0x1d,0xc,0x11,0xfe,0x89,0x96, + 0x9,0x2d,0x2a,0x0,0x0,0x0,0x0,0x1,0x1,0x72,0xfe,0x9d,0x3,0x84,0x5,0x72,0x0,0x11,0x0,0x36,0xb6,0xc,0x3,0x2,0x0,0x1,0x1,0x4a,0x4b,0xb0, + 0x15,0x50,0x58,0x40,0xc,0x2,0x1,0x1,0x0,0x1,0x72,0x0,0x0,0x0,0x13,0x0,0x4c,0x1b,0x40,0xa,0x2,0x1,0x1,0x0,0x1,0x72,0x0,0x0,0x0,0x69, + 0x59,0x40,0xa,0x0,0x0,0x0,0x11,0x0,0x10,0x27,0x3,0x7,0x15,0x2b,0x0,0x16,0x17,0x1,0x16,0x15,0x14,0x6,0x23,0x22,0x26,0x27,0x1,0x26,0x35,0x34, + 0x36,0x33,0x1,0xdc,0x1f,0x4,0x1,0x84,0x1,0x36,0x20,0x15,0x1e,0x4,0xfe,0x7c,0x1,0x35,0x20,0x5,0x72,0xf,0xf,0xf9,0x89,0x3,0x6,0x18,0x1f,0xf, + 0xf,0x6,0x77,0x3,0x6,0x18,0x1f,0x0,0x0,0x0,0xff,0xff,0x1,0xee,0x1,0xa9,0x3,0x1f,0x2,0xdf,0x1,0x7,0x1,0x2c,0x0,0x4d,0x1,0xb3,0x0,0x9, + 0xb1,0x0,0x1,0xb8,0x1,0xb3,0xb0,0x33,0x2b,0x0,0x0,0x0,0x0,0x1,0x1,0x87,0x1,0x63,0x3,0x8e,0x3,0x55,0x0,0x11,0x0,0x26,0x40,0x23,0xb,0x2, + 0x2,0x0,0x1,0x1,0x4a,0x2,0x1,0x1,0x0,0x0,0x1,0x57,0x2,0x1,0x1,0x1,0x0,0x5b,0x0,0x0,0x1,0x0,0x4f,0x0,0x0,0x0,0x11,0x0,0x10,0x27, + 0x3,0x7,0x15,0x2b,0x0,0x16,0x15,0x14,0x7,0x3,0x6,0x6,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x36,0x36,0x33,0x3,0x20,0x6e,0x1,0x35,0x9,0x6d,0x79, + 0x74,0x6e,0x1,0x35,0x9,0x6d,0x79,0x3,0x55,0x28,0x2c,0x9,0x5,0xfe,0xd2,0x35,0x2d,0x28,0x2c,0x9,0x5,0x1,0x2e,0x35,0x2d,0x0,0x0,0x2,0x1,0xa1, + 0xff,0xf6,0x3,0x41,0x3,0xa2,0x0,0x11,0x0,0x23,0x0,0x38,0x40,0x35,0xb,0x2,0x2,0x0,0x1,0x1d,0x14,0x2,0x2,0x3,0x2,0x4a,0x0,0x0,0x0,0x1, + 0x5b,0x4,0x1,0x1,0x1,0x11,0x4b,0x5,0x1,0x3,0x3,0x2,0x5b,0x0,0x2,0x2,0xf,0x2,0x4c,0x12,0x12,0x0,0x0,0x12,0x23,0x12,0x22,0x1b,0x19,0x0, + 0x11,0x0,0x10,0x27,0x6,0x7,0x15,0x2b,0x0,0x16,0x15,0x14,0x7,0x7,0x6,0x6,0x23,0x22,0x26,0x35,0x34,0x37,0x37,0x36,0x36,0x33,0x2,0x16,0x15,0x14, + 0x7,0x7,0x6,0x6,0x23,0x22,0x26,0x35,0x34,0x37,0x37,0x36,0x36,0x33,0x2,0xff,0x42,0x1,0x21,0x6,0x3e,0x46,0x43,0x42,0x1,0x21,0x6,0x3e,0x46,0x2c, + 0x42,0x1,0x21,0x6,0x3e,0x46,0x43,0x42,0x1,0x21,0x6,0x3e,0x46,0x3,0xa2,0x19,0x1c,0x7,0x3,0xb8,0x22,0x1d,0x19,0x1c,0x7,0x3,0xb8,0x22,0x1d,0xfd, + 0x8a,0x19,0x1c,0x7,0x3,0xb8,0x22,0x1d,0x19,0x1c,0x7,0x3,0xb8,0x22,0x1d,0x0,0x0,0x0,0x0,0x1,0x1,0x9,0xfe,0xda,0x2,0xd8,0x1,0x26,0x0,0x10, + 0x0,0x17,0x40,0x14,0x9,0x1,0x0,0x1,0x1,0x4a,0x0,0x1,0x0,0x1,0x72,0x0,0x0,0x0,0x69,0x26,0x26,0x2,0x7,0x16,0x2b,0x0,0x16,0x15,0x14,0x7, + 0x1,0x6,0x23,0x22,0x35,0x34,0x37,0x13,0x36,0x36,0x33,0x33,0x2,0xc6,0x12,0xb,0xfe,0xdc,0x19,0x48,0x3f,0x5,0xad,0x7,0x1f,0x15,0xc0,0x1,0x26,0x10, + 0xe,0xe,0x13,0xfe,0x1c,0x29,0x28,0x9,0x11,0x1,0xe2,0x13,0x15,0x0,0x0,0x0,0x0,0x3,0x0,0x2e,0xff,0xf6,0x4,0x39,0x0,0xe6,0x0,0x11,0x0,0x23, + 0x0,0x35,0x0,0x3a,0x40,0x37,0x2f,0x26,0x1d,0x14,0xb,0x2,0x6,0x0,0x1,0x1,0x4a,0x8,0x5,0x7,0x3,0x6,0x5,0x1,0x1,0x0,0x5b,0x4,0x2,0x2, + 0x0,0x0,0xf,0x0,0x4c,0x24,0x24,0x12,0x12,0x0,0x0,0x24,0x35,0x24,0x34,0x2d,0x2b,0x12,0x23,0x12,0x22,0x1b,0x19,0x0,0x11,0x0,0x10,0x27,0x9,0x7, + 0x15,0x2b,0x36,0x16,0x15,0x14,0x7,0x7,0x6,0x6,0x23,0x22,0x26,0x35,0x34,0x37,0x37,0x36,0x36,0x33,0x20,0x16,0x15,0x14,0x7,0x7,0x6,0x6,0x23,0x22, + 0x26,0x35,0x34,0x37,0x37,0x36,0x36,0x33,0x20,0x16,0x15,0x14,0x7,0x7,0x6,0x6,0x23,0x22,0x26,0x35,0x34,0x37,0x37,0x36,0x36,0x33,0xe7,0x32,0x1,0x17, + 0x5,0x31,0x37,0x34,0x32,0x1,0x17,0x5,0x31,0x37,0x1,0xc4,0x32,0x1,0x17,0x5,0x31,0x37,0x34,0x32,0x1,0x17,0x5,0x31,0x37,0x1,0xc4,0x32,0x1,0x17, + 0x5,0x31,0x37,0x34,0x32,0x1,0x17,0x5,0x31,0x37,0xe6,0x15,0x17,0x6,0x3,0x86,0x1d,0x18,0x15,0x17,0x6,0x3,0x86,0x1d,0x18,0x15,0x17,0x6,0x3,0x86, + 0x1d,0x18,0x15,0x17,0x6,0x3,0x86,0x1d,0x18,0x15,0x17,0x6,0x3,0x86,0x1d,0x18,0x15,0x17,0x6,0x3,0x86,0x1d,0x18,0x0,0x0,0x0,0x0,0x2,0x1,0xb1, + 0xff,0xf6,0x3,0x5e,0x4,0xf5,0x0,0xe,0x0,0x20,0x0,0x37,0x40,0x34,0x1,0x1,0x0,0x1,0x1a,0x11,0x2,0x2,0x3,0x2,0x4a,0x4,0x1,0x1,0x0,0x1, + 0x72,0x0,0x0,0x3,0x0,0x72,0x5,0x1,0x3,0x3,0x2,0x5b,0x0,0x2,0x2,0xf,0x2,0x4c,0xf,0xf,0x0,0x0,0xf,0x20,0xf,0x1f,0x18,0x16,0x0,0xe, + 0x0,0xc,0x26,0x6,0x7,0x15,0x2b,0x0,0x15,0x14,0x7,0x3,0x6,0x6,0x23,0x22,0x26,0x37,0x13,0x36,0x33,0x33,0x2,0x16,0x15,0x14,0x7,0x7,0x6,0x6, + 0x23,0x22,0x26,0x35,0x34,0x37,0x37,0x36,0x36,0x33,0x3,0x5e,0x3,0xae,0x5,0x22,0x1a,0x1a,0x1a,0x2,0x4d,0x4,0x2e,0x82,0xaf,0x3c,0x1,0x1b,0x6,0x39, + 0x41,0x3e,0x3d,0x1,0x1b,0x6,0x3a,0x41,0x4,0xf5,0x1d,0x9,0xc,0xfd,0x3d,0x17,0x19,0x19,0x17,0x2,0xc3,0x32,0xfc,0x19,0x19,0x1b,0x7,0x4,0x9a,0x22, + 0x1d,0x19,0x1c,0x6,0x4,0x9a,0x22,0x1d,0x0,0x0,0x0,0x2,0x1,0x6e,0xfe,0xa7,0x3,0x1b,0x3,0xa6,0x0,0x11,0x0,0x20,0x0,0x39,0x40,0x36,0xb,0x2, + 0x2,0x1,0x0,0x13,0x1,0x3,0x2,0x2,0x4a,0x0,0x2,0x1,0x3,0x1,0x2,0x3,0x70,0x5,0x1,0x3,0x3,0x71,0x4,0x1,0x1,0x1,0x0,0x5b,0x0,0x0, + 0x0,0x11,0x1,0x4c,0x12,0x12,0x0,0x0,0x12,0x20,0x12,0x1e,0x1a,0x18,0x0,0x11,0x0,0x10,0x27,0x6,0x7,0x15,0x2b,0x0,0x26,0x35,0x34,0x37,0x37,0x36, + 0x36,0x33,0x32,0x16,0x15,0x14,0x7,0x7,0x6,0x6,0x23,0x0,0x35,0x34,0x37,0x13,0x36,0x36,0x33,0x32,0x16,0x7,0x3,0x6,0x23,0x23,0x2,0x40,0x3c,0x1, + 0x1b,0x6,0x39,0x41,0x3e,0x3d,0x1,0x1b,0x6,0x3a,0x41,0xfe,0xf0,0x3,0xae,0x5,0x22,0x1a,0x1a,0x1a,0x2,0x4d,0x4,0x2e,0x82,0x2,0x8e,0x19,0x1b,0x7, + 0x4,0x9a,0x22,0x1d,0x19,0x1c,0x6,0x4,0x9a,0x22,0x1d,0xfc,0x19,0x1d,0x9,0xc,0x2,0xc3,0x17,0x19,0x19,0x17,0xfd,0x3d,0x32,0x0,0x0,0x0,0x0,0x2, + 0x0,0x3b,0xff,0xea,0x4,0xd9,0x4,0xb9,0x0,0x4f,0x0,0x53,0x0,0x5d,0x40,0x5a,0x4b,0x41,0x2,0x9,0xa,0x35,0x2,0x2,0x0,0x9,0x2a,0xd,0x2,0x2, + 0x1,0x23,0x19,0x2,0x3,0x2,0x4,0x4a,0x10,0xd,0xb,0x3,0x9,0xe,0x8,0x2,0x0,0x1,0x9,0x0,0x62,0xf,0x7,0x2,0x1,0x6,0x4,0x2,0x2,0x3, + 0x1,0x2,0x61,0xc,0x1,0xa,0xa,0x16,0x4b,0x5,0x1,0x3,0x3,0x17,0x3,0x4c,0x0,0x0,0x53,0x52,0x51,0x50,0x0,0x4f,0x0,0x4e,0x49,0x47,0x45,0x44, + 0x3f,0x3d,0x3b,0x39,0x21,0x26,0x25,0x22,0x15,0x22,0x26,0x21,0x26,0x11,0x7,0x1d,0x2b,0x0,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x23,0x7,0x33,0x32,0x16, + 0x15,0x14,0x7,0x6,0x6,0x23,0x23,0x3,0x6,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x23,0x3,0x6,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x23,0x22,0x26,0x35, + 0x34,0x37,0x36,0x36,0x33,0x33,0x37,0x23,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x33,0x13,0x36,0x33,0x32,0x16,0x15,0x14,0x7,0x3,0x33,0x13,0x36,0x33, + 0x32,0x16,0x15,0x14,0x7,0x3,0x33,0x5,0x23,0x7,0x33,0x4,0xbf,0x1a,0x3,0x7,0x23,0x22,0xc0,0x69,0xb8,0x1b,0x1a,0x3,0x7,0x22,0x22,0xdc,0x8a,0xe, + 0x2e,0x1d,0x29,0x4,0x7b,0xfd,0x8a,0xe,0x2e,0x1d,0x29,0x4,0x7b,0x9d,0x1c,0x19,0x2,0x7,0x23,0x22,0xc2,0x69,0xba,0x1b,0x1a,0x3,0x7,0x23,0x22,0xde, + 0x85,0xe,0x2e,0x1d,0x29,0x4,0x77,0xfe,0x85,0xe,0x2e,0x1d,0x29,0x4,0x77,0x9c,0xfe,0x9a,0xfe,0x69,0xfe,0x3,0x61,0x16,0x18,0xb,0xf,0x27,0x21,0xf5, + 0x16,0x18,0xb,0xf,0x27,0x21,0xfe,0xbf,0x21,0x1a,0x16,0xa,0x9,0x1,0x1f,0xfe,0xbf,0x21,0x1a,0x16,0xa,0x9,0x1,0x1f,0x16,0x19,0xf,0xa,0x27,0x21, + 0xf5,0x16,0x18,0xb,0xf,0x27,0x21,0x1,0x37,0x21,0x1a,0x16,0xa,0x9,0xfe,0xeb,0x1,0x37,0x21,0x1a,0x16,0xa,0x9,0xfe,0xeb,0x90,0xf5,0x0,0x0,0x0, + 0x0,0x1,0x1,0xa1,0xff,0xf6,0x2,0xd2,0x1,0x2c,0x0,0x11,0x0,0x20,0x40,0x1d,0xb,0x2,0x2,0x0,0x1,0x1,0x4a,0x2,0x1,0x1,0x1,0x0,0x5b,0x0, + 0x0,0x0,0xf,0x0,0x4c,0x0,0x0,0x0,0x11,0x0,0x10,0x27,0x3,0x7,0x15,0x2b,0x0,0x16,0x15,0x14,0x7,0x7,0x6,0x6,0x23,0x22,0x26,0x35,0x34,0x37, + 0x37,0x36,0x36,0x33,0x2,0x90,0x42,0x1,0x21,0x6,0x3e,0x46,0x43,0x42,0x1,0x21,0x6,0x3e,0x46,0x1,0x2c,0x19,0x1c,0x7,0x3,0xb8,0x22,0x1d,0x19,0x1c, + 0x7,0x3,0xb8,0x22,0x1d,0x0,0x0,0x2,0x1,0x82,0xff,0xf6,0x4,0x30,0x4,0xf5,0x0,0x24,0x0,0x36,0x0,0x42,0x40,0x3f,0x30,0x27,0x2,0x4,0x5,0x1, + 0x4a,0x0,0x2,0x1,0x0,0x1,0x2,0x0,0x70,0x0,0x0,0x5,0x1,0x0,0x5,0x6e,0x6,0x1,0x3,0x0,0x1,0x2,0x3,0x1,0x63,0x7,0x1,0x5,0x5,0x4, + 0x5b,0x0,0x4,0x4,0xf,0x4,0x4c,0x25,0x25,0x0,0x0,0x25,0x36,0x25,0x35,0x2e,0x2c,0x0,0x24,0x0,0x23,0x22,0x2c,0x29,0x8,0x7,0x17,0x2b,0x0,0x16, + 0x16,0x15,0x14,0x6,0x7,0x7,0x6,0x6,0x23,0x22,0x26,0x37,0x37,0x36,0x37,0x37,0x3e,0x2,0x35,0x34,0x26,0x23,0x22,0x7,0x6,0x23,0x22,0x27,0x26,0x35, + 0x34,0x37,0x36,0x33,0x2,0x16,0x15,0x14,0x7,0x7,0x6,0x6,0x23,0x22,0x26,0x35,0x34,0x37,0x37,0x36,0x36,0x33,0x3,0x5c,0x8b,0x49,0xbd,0xc1,0x26,0x5, + 0x2a,0x1f,0x1f,0x20,0x3,0x1c,0x4,0x23,0x4a,0x5c,0x6e,0x38,0x55,0x51,0x7d,0x94,0x12,0xd,0x1d,0x12,0xa,0x20,0xa5,0xb3,0x9d,0x36,0x1,0x14,0x6,0x36, + 0x3c,0x38,0x37,0x1,0x14,0x6,0x37,0x3c,0x4,0xf5,0x43,0x7b,0x52,0x8b,0xb4,0x48,0xa6,0x17,0x1c,0x1c,0x17,0xe1,0x26,0xd,0x1c,0x23,0x41,0x56,0x3c,0x3d, + 0x46,0x41,0x8,0x2b,0x18,0x12,0x21,0x11,0x56,0xfb,0xf1,0x18,0x1b,0x8,0x4,0x72,0x22,0x1d,0x19,0x1b,0x7,0x4,0x72,0x22,0x1d,0x0,0x0,0x0,0x0,0x2, + 0x0,0x9c,0xfe,0xa7,0x3,0x4a,0x3,0xa6,0x0,0x11,0x0,0x36,0x0,0x43,0x40,0x40,0xb,0x2,0x2,0x1,0x0,0x1,0x4a,0x0,0x2,0x1,0x4,0x1,0x2,0x4, + 0x70,0x0,0x4,0x3,0x1,0x4,0x3,0x6e,0x0,0x3,0x7,0x1,0x5,0x3,0x5,0x60,0x6,0x1,0x1,0x1,0x0,0x5b,0x0,0x0,0x0,0x11,0x1,0x4c,0x12,0x12, + 0x0,0x0,0x12,0x36,0x12,0x35,0x2f,0x2d,0x2b,0x29,0x1d,0x1b,0x0,0x11,0x0,0x10,0x27,0x8,0x7,0x15,0x2b,0x0,0x26,0x35,0x34,0x37,0x37,0x36,0x36,0x33, + 0x32,0x16,0x15,0x14,0x7,0x7,0x6,0x6,0x23,0x0,0x26,0x26,0x35,0x34,0x36,0x37,0x37,0x36,0x36,0x33,0x32,0x16,0x7,0x7,0x6,0x7,0x7,0xe,0x2,0x15, + 0x14,0x16,0x33,0x32,0x37,0x36,0x33,0x32,0x17,0x16,0x15,0x14,0x7,0x6,0x23,0x2,0x6f,0x36,0x1,0x14,0x6,0x36,0x3c,0x38,0x37,0x1,0x14,0x6,0x37,0x3c, + 0xfe,0xc9,0x8b,0x49,0xbd,0xc1,0x26,0x5,0x2a,0x1f,0x1f,0x20,0x3,0x1c,0x4,0x23,0x4a,0x5c,0x6e,0x38,0x55,0x51,0x7d,0x94,0x12,0xd,0x1d,0x12,0xa,0x20, + 0xa5,0xb3,0x2,0xb6,0x18,0x1b,0x8,0x4,0x72,0x22,0x1d,0x19,0x1b,0x7,0x4,0x72,0x22,0x1d,0xfb,0xf1,0x43,0x7b,0x52,0x8b,0xb4,0x48,0xa6,0x17,0x1c,0x1c, + 0x17,0xe1,0x26,0xd,0x1c,0x23,0x41,0x56,0x3c,0x3d,0x46,0x41,0x8,0x2b,0x18,0x12,0x21,0x11,0x56,0x0,0xff,0xff,0x1,0x7c,0x2,0xcb,0x4,0x5b,0x4,0xf5, + 0x0,0x23,0x1,0x30,0xff,0x1a,0x0,0x0,0x0,0x3,0x1,0x30,0x0,0xe6,0x0,0x0,0x0,0x0,0x0,0x1,0x2,0x62,0x2,0xcb,0x3,0x75,0x4,0xf5,0x0,0xe, + 0x0,0x26,0x40,0x23,0xa,0x1,0x2,0x0,0x1,0x1,0x4a,0x2,0x1,0x1,0x0,0x0,0x1,0x55,0x2,0x1,0x1,0x1,0x0,0x5b,0x0,0x0,0x1,0x0,0x4f,0x0, + 0x0,0x0,0xe,0x0,0xc,0x26,0x3,0x7,0x15,0x2b,0x0,0x15,0x14,0x7,0x3,0x6,0x6,0x23,0x22,0x26,0x37,0x13,0x36,0x33,0x33,0x3,0x75,0x3,0x86,0x8, + 0x28,0x1e,0x1d,0x1f,0x2,0x18,0x3,0x2f,0xa4,0x4,0xf5,0x1d,0x9,0xc,0xfe,0x3e,0x1a,0x1c,0x1c,0x1a,0x1,0xc2,0x32,0x0,0x0,0x2,0x1,0x31,0xfe,0xda, + 0x3,0x41,0x3,0xa2,0x0,0x11,0x0,0x22,0x0,0x34,0x40,0x31,0xb,0x2,0x2,0x0,0x1,0x1b,0x1,0x2,0x3,0x2,0x4a,0x0,0x3,0x0,0x2,0x0,0x3,0x2, + 0x70,0x0,0x2,0x2,0x71,0x0,0x0,0x0,0x1,0x5b,0x4,0x1,0x1,0x1,0x11,0x0,0x4c,0x0,0x0,0x22,0x20,0x1a,0x18,0x0,0x11,0x0,0x10,0x27,0x5,0x7, + 0x15,0x2b,0x0,0x16,0x15,0x14,0x7,0x7,0x6,0x6,0x23,0x22,0x26,0x35,0x34,0x37,0x37,0x36,0x36,0x33,0x12,0x16,0x15,0x14,0x7,0x1,0x6,0x23,0x22,0x35, + 0x34,0x37,0x13,0x36,0x36,0x33,0x33,0x2,0xff,0x42,0x1,0x21,0x6,0x3e,0x46,0x43,0x42,0x1,0x21,0x6,0x3e,0x46,0x32,0x12,0xb,0xfe,0xdc,0x19,0x48,0x3f, + 0x5,0xad,0x7,0x1f,0x15,0xc0,0x3,0xa2,0x19,0x1c,0x7,0x3,0xb8,0x22,0x1d,0x19,0x1c,0x7,0x3,0xb8,0x22,0x1d,0xfd,0x84,0x10,0xe,0xe,0x13,0xfe,0x1c, + 0x29,0x28,0x9,0x11,0x1,0xe2,0x13,0x15,0x0,0x1,0x0,0x52,0xfe,0x9d,0x4,0xa5,0x5,0x72,0x0,0xf,0x0,0x36,0xb6,0xa,0x2,0x2,0x0,0x1,0x1,0x4a, + 0x4b,0xb0,0x15,0x50,0x58,0x40,0xc,0x2,0x1,0x1,0x0,0x1,0x72,0x0,0x0,0x0,0x13,0x0,0x4c,0x1b,0x40,0xa,0x2,0x1,0x1,0x0,0x1,0x72,0x0,0x0, + 0x0,0x69,0x59,0x40,0xa,0x0,0x0,0x0,0xf,0x0,0xe,0x26,0x3,0x7,0x15,0x2b,0x0,0x16,0x15,0x14,0x7,0x1,0x6,0x23,0x22,0x26,0x35,0x34,0x37,0x1, + 0x36,0x33,0x4,0x7a,0x2b,0x7,0xfc,0x47,0x12,0x32,0x24,0x2b,0x7,0x3,0xb9,0x12,0x32,0x5,0x72,0x1f,0x17,0xc,0xd,0xf9,0x9a,0x20,0x1f,0x17,0xc,0xd, + 0x6,0x66,0x20,0x0,0x0,0x1,0x0,0x1,0xfe,0x77,0x4,0x46,0xff,0x7,0x0,0x11,0x0,0x2e,0xb1,0x6,0x64,0x44,0x40,0x23,0xb,0x2,0x2,0x0,0x1,0x1, + 0x4a,0x2,0x1,0x1,0x0,0x0,0x1,0x55,0x2,0x1,0x1,0x1,0x0,0x59,0x0,0x0,0x1,0x0,0x4d,0x0,0x0,0x0,0x11,0x0,0xf,0x36,0x3,0x7,0x15,0x2b, + 0xb1,0x6,0x0,0x44,0x4,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x21,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x21,0x4,0x2c,0x1a,0x3,0x7,0x22,0x22,0xfc, + 0x3e,0x1c,0x19,0x2,0x8,0x22,0x22,0x3,0xc2,0xf9,0x15,0x18,0xc,0xf,0x27,0x21,0x16,0x19,0xf,0xa,0x27,0x21,0x0,0x1,0x0,0xc8,0xfe,0xb3,0x4,0x75, + 0x5,0x5c,0x0,0x3b,0x0,0xe5,0x40,0xf,0x2,0x1,0x0,0x5,0x30,0xf,0x2,0x3,0x4,0x1d,0x1,0x2,0x1,0x3,0x4a,0x4b,0xb0,0xc,0x50,0x58,0x40,0x21, + 0x6,0x1,0x5,0x0,0x0,0x4,0x5,0x0,0x63,0x0,0x4,0x0,0x3,0x1,0x4,0x3,0x63,0x0,0x1,0x2,0x2,0x1,0x57,0x0,0x1,0x1,0x2,0x5b,0x0,0x2, + 0x1,0x2,0x4f,0x1b,0x4b,0xb0,0xe,0x50,0x58,0x40,0x1b,0x0,0x4,0x0,0x3,0x1,0x4,0x3,0x63,0x0,0x1,0x0,0x2,0x1,0x2,0x5f,0x0,0x0,0x0,0x5, + 0x5b,0x6,0x1,0x5,0x5,0x10,0x0,0x4c,0x1b,0x4b,0xb0,0x10,0x50,0x58,0x40,0x21,0x6,0x1,0x5,0x0,0x0,0x4,0x5,0x0,0x63,0x0,0x4,0x0,0x3,0x1, + 0x4,0x3,0x63,0x0,0x1,0x2,0x2,0x1,0x57,0x0,0x1,0x1,0x2,0x5b,0x0,0x2,0x1,0x2,0x4f,0x1b,0x4b,0xb0,0x15,0x50,0x58,0x40,0x1b,0x0,0x4,0x0, + 0x3,0x1,0x4,0x3,0x63,0x0,0x1,0x0,0x2,0x1,0x2,0x5f,0x0,0x0,0x0,0x5,0x5b,0x6,0x1,0x5,0x5,0x10,0x0,0x4c,0x1b,0x40,0x21,0x6,0x1,0x5, + 0x0,0x0,0x4,0x5,0x0,0x63,0x0,0x4,0x0,0x3,0x1,0x4,0x3,0x63,0x0,0x1,0x2,0x2,0x1,0x57,0x0,0x1,0x1,0x2,0x5b,0x0,0x2,0x1,0x2,0x4f, + 0x59,0x59,0x59,0x59,0x40,0x12,0x0,0x0,0x0,0x3b,0x0,0x3a,0x36,0x34,0x2e,0x2c,0x23,0x21,0x1b,0x19,0x26,0x7,0x7,0x15,0x2b,0x0,0x16,0x15,0x14,0x7, + 0x6,0x6,0x23,0x22,0x6,0x6,0x7,0x7,0x6,0x6,0x7,0x7,0x16,0x15,0x14,0x7,0x7,0x6,0x15,0x14,0x16,0x33,0x32,0x16,0x15,0x14,0x7,0x6,0x6,0x23, + 0x22,0x26,0x35,0x34,0x37,0x37,0x36,0x35,0x34,0x26,0x23,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x20,0x13,0x37,0x36,0x36,0x33,0x4,0x5a,0x1b,0x2,0x5, + 0x22,0x22,0x66,0x80,0x46,0xe,0xa,0x12,0x8a,0x87,0x1,0xbd,0xa,0x18,0xc,0x76,0x7b,0x1c,0x18,0x2,0x5,0x23,0x22,0xb0,0xbf,0x10,0x15,0xc,0x70,0x7d, + 0x1d,0x1a,0x2,0x5,0x23,0x22,0x1,0x1e,0x22,0xb,0x19,0xef,0xd6,0x5,0x5c,0x18,0x1b,0x5,0x10,0x27,0x21,0x3b,0x7a,0x64,0x48,0x82,0xa0,0x27,0xa,0x46, + 0xa9,0x24,0x30,0x70,0x37,0x2c,0x61,0x5e,0x16,0x1a,0x6,0x12,0x27,0x21,0x9f,0x98,0x3e,0x46,0x5d,0x35,0x29,0x57,0x55,0x17,0x1b,0x6,0x10,0x27,0x21,0x1, + 0x10,0x5a,0xce,0xbf,0x0,0x0,0x0,0x1,0x0,0x6c,0xfe,0xb3,0x4,0x20,0x5,0x5c,0x0,0x3c,0x0,0xe5,0x40,0xf,0x1d,0x1,0x1,0x2,0x30,0xf,0x2,0x4, + 0x3,0x2,0x1,0x5,0x0,0x3,0x4a,0x4b,0xb0,0xc,0x50,0x58,0x40,0x21,0x0,0x2,0x0,0x1,0x3,0x2,0x1,0x63,0x0,0x3,0x0,0x4,0x0,0x3,0x4,0x63, + 0x0,0x0,0x5,0x5,0x0,0x57,0x0,0x0,0x0,0x5,0x5b,0x6,0x1,0x5,0x0,0x5,0x4f,0x1b,0x4b,0xb0,0xe,0x50,0x58,0x40,0x1b,0x0,0x3,0x0,0x4,0x0, + 0x3,0x4,0x63,0x0,0x0,0x6,0x1,0x5,0x0,0x5,0x5f,0x0,0x1,0x1,0x2,0x5b,0x0,0x2,0x2,0x10,0x1,0x4c,0x1b,0x4b,0xb0,0x10,0x50,0x58,0x40,0x21, + 0x0,0x2,0x0,0x1,0x3,0x2,0x1,0x63,0x0,0x3,0x0,0x4,0x0,0x3,0x4,0x63,0x0,0x0,0x5,0x5,0x0,0x57,0x0,0x0,0x0,0x5,0x5b,0x6,0x1,0x5, + 0x0,0x5,0x4f,0x1b,0x4b,0xb0,0x15,0x50,0x58,0x40,0x1b,0x0,0x3,0x0,0x4,0x0,0x3,0x4,0x63,0x0,0x0,0x6,0x1,0x5,0x0,0x5,0x5f,0x0,0x1,0x1, + 0x2,0x5b,0x0,0x2,0x2,0x10,0x1,0x4c,0x1b,0x40,0x21,0x0,0x2,0x0,0x1,0x3,0x2,0x1,0x63,0x0,0x3,0x0,0x4,0x0,0x3,0x4,0x63,0x0,0x0,0x5, + 0x5,0x0,0x57,0x0,0x0,0x0,0x5,0x5b,0x6,0x1,0x5,0x0,0x5,0x4f,0x59,0x59,0x59,0x59,0x40,0x12,0x0,0x0,0x0,0x3c,0x0,0x3b,0x36,0x34,0x2e,0x2c, + 0x23,0x21,0x1b,0x19,0x26,0x7,0x7,0x15,0x2b,0x12,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x32,0x36,0x36,0x37,0x37,0x36,0x36,0x37,0x37,0x26,0x35,0x34,0x37, + 0x37,0x36,0x35,0x34,0x26,0x23,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x32,0x16,0x15,0x14,0x7,0x7,0x6,0x15,0x14,0x16,0x33,0x32,0x16,0x15,0x14,0x7, + 0x6,0x6,0x23,0x22,0x6,0x7,0x7,0x6,0x6,0x23,0x87,0x1b,0x2,0x5,0x22,0x22,0x66,0x80,0x47,0xd,0x10,0x12,0x8c,0x86,0x1,0xb5,0x8,0xb,0xa,0x73, + 0x7c,0x1c,0x18,0x2,0x5,0x23,0x22,0xb2,0xba,0xd,0xa,0xa,0x6c,0x7b,0x1d,0x1a,0x2,0x5,0x23,0x22,0x93,0x9f,0x10,0x10,0x19,0xf0,0xd5,0xfe,0xb3,0x18, + 0x1b,0x5,0x10,0x27,0x21,0x3b,0x7b,0x63,0x76,0x80,0xa3,0x27,0xa,0x46,0xb2,0x22,0x2d,0x3d,0x3a,0x25,0x64,0x5f,0x16,0x1a,0x6,0x12,0x27,0x21,0xa1,0x9d, + 0x3c,0x41,0x34,0x37,0x24,0x58,0x55,0x17,0x1b,0x6,0x10,0x27,0x21,0x91,0x82,0x82,0xcd,0xc0,0x0,0x0,0x0,0x1,0x0,0xd3,0xfe,0xb3,0x4,0x8,0x5,0x5c, + 0x0,0x1d,0x0,0xb3,0x40,0xb,0x2,0x1,0x0,0x3,0x16,0xd,0x2,0x2,0x1,0x2,0x4a,0x4b,0xb0,0xc,0x50,0x58,0x40,0x19,0x4,0x1,0x3,0x0,0x0,0x1, + 0x3,0x0,0x61,0x0,0x1,0x2,0x2,0x1,0x55,0x0,0x1,0x1,0x2,0x59,0x0,0x2,0x1,0x2,0x4d,0x1b,0x4b,0xb0,0xe,0x50,0x58,0x40,0x13,0x0,0x1,0x0, + 0x2,0x1,0x2,0x5d,0x0,0x0,0x0,0x3,0x59,0x4,0x1,0x3,0x3,0x10,0x0,0x4c,0x1b,0x4b,0xb0,0x10,0x50,0x58,0x40,0x19,0x4,0x1,0x3,0x0,0x0,0x1, + 0x3,0x0,0x61,0x0,0x1,0x2,0x2,0x1,0x55,0x0,0x1,0x1,0x2,0x59,0x0,0x2,0x1,0x2,0x4d,0x1b,0x4b,0xb0,0x15,0x50,0x58,0x40,0x13,0x0,0x1,0x0, + 0x2,0x1,0x2,0x5d,0x0,0x0,0x0,0x3,0x59,0x4,0x1,0x3,0x3,0x10,0x0,0x4c,0x1b,0x40,0x19,0x4,0x1,0x3,0x0,0x0,0x1,0x3,0x0,0x61,0x0,0x1, + 0x2,0x2,0x1,0x55,0x0,0x1,0x1,0x2,0x59,0x0,0x2,0x1,0x2,0x4d,0x59,0x59,0x59,0x59,0x40,0xc,0x0,0x0,0x0,0x1d,0x0,0x1b,0x36,0x21,0x26,0x5, + 0x7,0x17,0x2b,0x0,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x21,0x3,0x21,0x32,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x21,0x22,0x26,0x35,0x34,0x37,0x1,0x36, + 0x36,0x33,0x21,0x3,0xee,0x1a,0x3,0x7,0x23,0x22,0xfe,0xb9,0xf9,0x1,0x47,0x1b,0x1a,0x3,0x7,0x23,0x22,0xfe,0x62,0x1c,0x19,0x2,0x1,0x15,0x7,0x22, + 0x22,0x1,0x9e,0x5,0x5c,0x16,0x18,0xb,0xf,0x27,0x21,0xfa,0x77,0x16,0x18,0xb,0xf,0x27,0x21,0x14,0x18,0xf,0x9,0x6,0x21,0x25,0x1f,0x0,0x0,0x0, + 0x0,0x1,0x0,0xef,0xfe,0xb3,0x4,0x24,0x5,0x5c,0x0,0x1d,0x0,0xb3,0x40,0xb,0x17,0x2,0x2,0x2,0x3,0xc,0x1,0x0,0x1,0x2,0x4a,0x4b,0xb0,0xc, + 0x50,0x58,0x40,0x19,0x4,0x1,0x3,0x0,0x2,0x1,0x3,0x2,0x61,0x0,0x1,0x0,0x0,0x1,0x55,0x0,0x1,0x1,0x0,0x59,0x0,0x0,0x1,0x0,0x4d,0x1b, + 0x4b,0xb0,0xe,0x50,0x58,0x40,0x13,0x0,0x1,0x0,0x0,0x1,0x0,0x5d,0x0,0x2,0x2,0x3,0x59,0x4,0x1,0x3,0x3,0x10,0x2,0x4c,0x1b,0x4b,0xb0,0x10, + 0x50,0x58,0x40,0x19,0x4,0x1,0x3,0x0,0x2,0x1,0x3,0x2,0x61,0x0,0x1,0x0,0x0,0x1,0x55,0x0,0x1,0x1,0x0,0x59,0x0,0x0,0x1,0x0,0x4d,0x1b, + 0x4b,0xb0,0x15,0x50,0x58,0x40,0x13,0x0,0x1,0x0,0x0,0x1,0x0,0x5d,0x0,0x2,0x2,0x3,0x59,0x4,0x1,0x3,0x3,0x10,0x2,0x4c,0x1b,0x40,0x19,0x4, + 0x1,0x3,0x0,0x2,0x1,0x3,0x2,0x61,0x0,0x1,0x0,0x0,0x1,0x55,0x0,0x1,0x1,0x0,0x59,0x0,0x0,0x1,0x0,0x4d,0x59,0x59,0x59,0x59,0x40,0xc, + 0x0,0x0,0x0,0x1d,0x0,0x1b,0x21,0x26,0x37,0x5,0x7,0x17,0x2b,0x0,0x16,0x15,0x14,0x7,0x1,0x6,0x6,0x23,0x21,0x22,0x26,0x35,0x34,0x37,0x36,0x36, + 0x33,0x21,0x13,0x21,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x21,0x4,0xb,0x19,0x2,0xfe,0xeb,0x7,0x22,0x22,0xfe,0x62,0x1b,0x1a,0x3,0x7,0x23,0x22, + 0x1,0x47,0xf9,0xfe,0xb9,0x1b,0x1a,0x3,0x7,0x23,0x22,0x1,0x9e,0x5,0x5c,0x14,0x18,0xf,0x9,0xf9,0xdf,0x25,0x1f,0x16,0x18,0xb,0xf,0x27,0x21,0x5, + 0x89,0x16,0x18,0xb,0xf,0x27,0x21,0x0,0x0,0x0,0x0,0x1,0x1,0x30,0xfe,0xa8,0x3,0xfd,0x5,0x64,0x0,0x21,0x0,0x11,0x40,0xe,0x11,0x1,0x2,0x0, + 0x48,0x0,0x0,0x0,0x69,0x18,0x16,0x1,0x7,0x14,0x2b,0x0,0x33,0x32,0x16,0x17,0x16,0x15,0x14,0x7,0x6,0x2,0x2,0x15,0x14,0x12,0x17,0x16,0x15,0x14, + 0x6,0x7,0x6,0x6,0x23,0x22,0x27,0x26,0x26,0x2,0x35,0x34,0x12,0x0,0x37,0x3,0xb8,0x7,0x10,0x1c,0x8,0xa,0x14,0xa9,0xf8,0x85,0xa1,0x7d,0x9,0xf, + 0xd,0xc,0x1c,0xe,0xd,0xa,0x5a,0x99,0x5e,0xa2,0x1,0x21,0xb9,0x5,0x64,0x1a,0x14,0x15,0x1a,0x22,0xa,0x58,0xfe,0xf4,0xfe,0xaf,0xbc,0xc8,0xfe,0xd0, + 0x5d,0x8,0xd,0xd,0x20,0xe,0xd,0x10,0x7,0x3d,0xcc,0x1,0x13,0x9f,0xd2,0x1,0x8d,0x1,0x3c,0x5a,0x0,0x0,0x0,0x1,0x0,0xf9,0xfe,0xa9,0x3,0xc6, + 0x5,0x65,0x0,0x21,0x0,0x11,0x40,0xe,0x19,0x9,0x2,0x0,0x47,0x0,0x0,0x0,0x69,0x20,0x1e,0x1,0x7,0x14,0x2b,0x0,0x16,0x12,0x15,0x14,0x2,0x0, + 0x7,0x6,0x23,0x22,0x26,0x27,0x26,0x35,0x34,0x37,0x36,0x12,0x12,0x35,0x34,0x2,0x27,0x26,0x35,0x34,0x36,0x37,0x36,0x36,0x33,0x32,0x17,0x2,0xcf,0x99, + 0x5e,0xa2,0xfe,0xdf,0xb9,0xc,0x7,0x10,0x1c,0x8,0xa,0x14,0xa9,0xf8,0x85,0xa1,0x7d,0x9,0xf,0xd,0xc,0x1c,0xe,0xd,0xa,0x5,0x21,0xcc,0xfe,0xed, + 0x9f,0xd2,0xfe,0x73,0xfe,0xc4,0x5a,0x5,0x1a,0x14,0x15,0x1a,0x22,0xa,0x58,0x1,0xc,0x1,0x51,0xbc,0xc8,0x1,0x30,0x5d,0x8,0xd,0xd,0x20,0xe,0xd, + 0x10,0x7,0x0,0x0,0x0,0x1,0x1,0x2b,0x1,0xf8,0x3,0xe1,0x2,0x94,0x0,0x11,0x0,0x6,0xb3,0x6,0x0,0x1,0x30,0x2b,0x0,0x16,0x15,0x14,0x7,0x6, + 0x6,0x23,0x21,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x21,0x3,0xc9,0x18,0x3,0x7,0x23,0x23,0xfd,0xce,0x1c,0x18,0x3,0x8,0x23,0x22,0x2,0x32,0x2, + 0x94,0x14,0x1a,0xe,0x12,0x2c,0x22,0x15,0x1a,0xd,0x12,0x2c,0x22,0x0,0x0,0x0,0x0,0x1,0xfe,0x70,0x1,0xfe,0x6,0x9d,0x2,0x8e,0x0,0x11,0x0,0x6, + 0xb3,0x6,0x0,0x1,0x30,0x2b,0x0,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x21,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x21,0x6,0x83,0x1a,0x3,0x7,0x23, + 0x22,0xf8,0x57,0x1b,0x1a,0x3,0x7,0x23,0x22,0x7,0xa9,0x2,0x8e,0x16,0x18,0xb,0xf,0x27,0x21,0x16,0x18,0xb,0xf,0x27,0x21,0x0,0x0,0x0,0x0,0x1, + 0x0,0xd,0x1,0xfe,0x9,0xcb,0x2,0x8e,0x0,0x11,0x0,0x6,0xb3,0x6,0x0,0x1,0x30,0x2b,0x0,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x21,0x22,0x26,0x35, + 0x34,0x37,0x36,0x36,0x33,0x21,0x9,0xb1,0x1a,0x3,0x7,0x23,0x22,0xf6,0xc6,0x1b,0x1a,0x3,0x7,0x23,0x22,0x9,0x3a,0x2,0x8e,0x16,0x18,0xb,0xf,0x27, + 0x21,0x16,0x18,0xb,0xf,0x27,0x21,0x0,0x0,0x0,0x0,0x1,0x1,0x39,0x1,0xfe,0x8,0x9f,0x2,0x8e,0x0,0x11,0x0,0x6,0xb3,0x6,0x0,0x1,0x30,0x2b, + 0x0,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x21,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x21,0x8,0x85,0x1a,0x3,0x7,0x23,0x22,0xf9,0x1e,0x1b,0x1a,0x3, + 0x7,0x23,0x22,0x6,0xe2,0x2,0x8e,0x16,0x18,0xb,0xf,0x27,0x21,0x16,0x18,0xb,0xf,0x27,0x21,0x0,0x0,0x0,0x0,0x1,0xff,0x38,0x1,0xfe,0x5,0xd5, + 0x2,0x8e,0x0,0x11,0x0,0x6,0xb3,0x6,0x0,0x1,0x30,0x2b,0x0,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x21,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x21, + 0x5,0xbb,0x1a,0x3,0x7,0x23,0x22,0xf9,0xe7,0x1b,0x1a,0x3,0x7,0x23,0x22,0x6,0x19,0x2,0x8e,0x16,0x18,0xb,0xf,0x27,0x21,0x16,0x18,0xb,0xf,0x27, + 0x21,0x0,0x0,0x0,0x0,0x1,0xff,0xff,0x1,0xfe,0x5,0xd,0x2,0x8e,0x0,0x11,0x0,0x26,0x40,0x23,0xb,0x2,0x2,0x0,0x1,0x1,0x4a,0x2,0x1,0x1, + 0x0,0x0,0x1,0x55,0x2,0x1,0x1,0x1,0x0,0x59,0x0,0x0,0x1,0x0,0x4d,0x0,0x0,0x0,0x11,0x0,0xf,0x36,0x3,0x7,0x15,0x2b,0x0,0x16,0x15,0x14, + 0x7,0x6,0x6,0x23,0x21,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x21,0x4,0xf3,0x1a,0x3,0x7,0x23,0x22,0xfb,0x76,0x1b,0x1a,0x3,0x7,0x23,0x22,0x4, + 0x8a,0x2,0x8e,0x16,0x18,0xb,0xf,0x27,0x21,0x16,0x18,0xb,0xf,0x27,0x21,0x0,0x0,0x0,0x0,0x1,0x0,0xc7,0x1,0xfe,0x4,0x45,0x2,0x8e,0x0,0x11, + 0x0,0x26,0x40,0x23,0xb,0x2,0x2,0x0,0x1,0x1,0x4a,0x2,0x1,0x1,0x0,0x0,0x1,0x55,0x2,0x1,0x1,0x1,0x0,0x59,0x0,0x0,0x1,0x0,0x4d,0x0, + 0x0,0x0,0x11,0x0,0xf,0x36,0x3,0x7,0x15,0x2b,0x0,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x21,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x21,0x4,0x2b, + 0x1a,0x3,0x7,0x23,0x22,0xfd,0x6,0x1b,0x1a,0x3,0x7,0x23,0x22,0x2,0xfa,0x2,0x8e,0x16,0x18,0xb,0xf,0x27,0x21,0x16,0x18,0xb,0xf,0x27,0x21,0x0, + 0x0,0x0,0x0,0x1,0x0,0xc7,0x1,0xfe,0x4,0x45,0x2,0x8e,0x0,0x11,0x0,0x26,0x40,0x23,0xb,0x2,0x2,0x0,0x1,0x1,0x4a,0x2,0x1,0x1,0x0,0x0, + 0x1,0x55,0x2,0x1,0x1,0x1,0x0,0x59,0x0,0x0,0x1,0x0,0x4d,0x0,0x0,0x0,0x11,0x0,0xf,0x36,0x3,0x7,0x15,0x2b,0x0,0x16,0x15,0x14,0x7,0x6, + 0x6,0x23,0x21,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x21,0x4,0x2b,0x1a,0x3,0x7,0x23,0x22,0xfd,0x6,0x1b,0x1a,0x3,0x7,0x23,0x22,0x2,0xfa,0x2, + 0x8e,0x16,0x18,0xb,0xf,0x27,0x21,0x16,0x18,0xb,0xf,0x27,0x21,0x0,0x0,0x0,0xff,0xff,0x0,0xc7,0x1,0xfe,0x4,0x45,0x2,0x8e,0x0,0x2,0x1,0x41, + 0x0,0x0,0x0,0x2,0x0,0xac,0x0,0xb6,0x4,0x7a,0x3,0xb2,0x0,0x17,0x0,0x2f,0x0,0x22,0x40,0x1f,0x28,0x1c,0x19,0x10,0x4,0x1,0x6,0x0,0x1,0x1, + 0x4a,0x2,0x1,0x0,0x1,0x0,0x73,0x3,0x1,0x1,0x1,0x19,0x1,0x4c,0x28,0x2c,0x28,0x2a,0x4,0x7,0x18,0x2b,0x0,0x15,0x14,0x7,0x5,0x13,0x16,0x15, + 0x14,0x7,0x6,0x23,0x22,0x27,0x1,0x26,0x35,0x34,0x37,0x1,0x36,0x33,0x32,0x17,0x4,0x15,0x14,0x7,0x1,0x13,0x16,0x15,0x14,0x7,0x6,0x23,0x22,0x27, + 0x3,0x26,0x35,0x34,0x37,0x1,0x36,0x33,0x32,0x17,0x2,0x86,0x12,0xfe,0xe9,0xda,0xa,0x24,0x20,0x19,0x16,0xe,0xfe,0xf3,0x7,0xc,0x1,0x5a,0xc,0x11, + 0x1b,0x22,0x2,0xe,0x12,0xfe,0xd5,0xc6,0xa,0x24,0x20,0x19,0x16,0xe,0xf9,0x7,0xc,0x1,0x6e,0xc,0x11,0x1b,0x22,0x3,0x74,0x16,0x13,0x10,0xf9,0xfe, + 0xdb,0xd,0x10,0x1b,0x19,0x16,0x13,0x1,0x73,0xb,0xa,0x11,0xa,0x1,0x3a,0xc,0x23,0x1b,0x16,0x13,0x10,0xfe,0xf3,0xfe,0xef,0xd,0x10,0x1b,0x19,0x16, + 0x13,0x1,0x5f,0xb,0xa,0x11,0xa,0x1,0x4e,0xc,0x23,0x0,0x0,0x0,0x0,0x2,0x0,0x8c,0x0,0xb6,0x4,0x5a,0x3,0xb2,0x0,0x17,0x0,0x2f,0x0,0x22, + 0x40,0x1f,0x2a,0x27,0x1e,0x12,0xf,0x6,0x6,0x1,0x0,0x1,0x4a,0x3,0x1,0x1,0x0,0x1,0x73,0x2,0x1,0x0,0x0,0x19,0x0,0x4c,0x28,0x2c,0x28,0x20, + 0x4,0x7,0x18,0x2b,0x0,0x33,0x32,0x17,0x13,0x16,0x15,0x14,0x7,0x1,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x1,0x3,0x26,0x35,0x34,0x37,0x24,0x33, + 0x32,0x17,0x1,0x16,0x15,0x14,0x7,0x1,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x25,0x3,0x26,0x35,0x34,0x37,0x1,0x3d,0x19,0x17,0xd,0xf9,0x7,0xc, + 0xfe,0x92,0xc,0x11,0x1b,0x22,0x1a,0x12,0x1,0x2b,0xc6,0xa,0x24,0x1,0xec,0x19,0x17,0xd,0x1,0xd,0x7,0xc,0xfe,0xa6,0xc,0x11,0x1b,0x22,0x1a,0x12, + 0x1,0x17,0xda,0xb,0x25,0x3,0xb2,0x13,0xfe,0xa1,0xa,0xb,0x10,0xb,0xfe,0xb2,0xc,0x23,0x1b,0x16,0x13,0x10,0x1,0xd,0x1,0x11,0xd,0x10,0x1b,0x19, + 0x16,0x13,0xfe,0x8d,0xa,0xb,0x10,0xb,0xfe,0xc6,0xc,0x23,0x1b,0x16,0x13,0x10,0xf9,0x1,0x25,0xe,0xf,0x1a,0x1a,0x0,0x0,0x0,0x0,0x1,0x1,0xa6, + 0x0,0xb6,0x3,0x8a,0x3,0xb2,0x0,0x17,0x0,0x1b,0x40,0x18,0x10,0x4,0x1,0x3,0x0,0x1,0x1,0x4a,0x0,0x0,0x1,0x0,0x73,0x0,0x1,0x1,0x19,0x1, + 0x4c,0x28,0x2a,0x2,0x7,0x16,0x2b,0x0,0x15,0x14,0x7,0x1,0x13,0x16,0x15,0x14,0x7,0x6,0x23,0x22,0x27,0x1,0x26,0x35,0x34,0x37,0x1,0x36,0x33,0x32, + 0x17,0x3,0x8a,0x12,0xfe,0xdf,0xd0,0xb,0x25,0x20,0x19,0x16,0xe,0xfe,0xfd,0x7,0xc,0x1,0x64,0xc,0x11,0x1b,0x22,0x3,0x74,0x16,0x13,0x10,0xfe,0xfd, + 0xfe,0xe5,0xe,0xf,0x1a,0x1a,0x16,0x13,0x1,0x69,0xb,0xa,0x11,0xa,0x1,0x44,0xc,0x23,0x0,0x1,0x1,0x7c,0x0,0xb6,0x3,0x60,0x3,0xb2,0x0,0x17, + 0x0,0x1b,0x40,0x18,0x12,0xf,0x6,0x3,0x1,0x0,0x1,0x4a,0x0,0x1,0x0,0x1,0x73,0x0,0x0,0x0,0x19,0x0,0x4c,0x28,0x20,0x2,0x7,0x16,0x2b,0x0, + 0x33,0x32,0x17,0x1,0x16,0x15,0x14,0x7,0x1,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x1,0x3,0x26,0x35,0x34,0x37,0x2,0x19,0x19,0x17,0xd,0x1,0x3, + 0x7,0xc,0xfe,0x9c,0xc,0x11,0x1b,0x22,0x1a,0x12,0x1,0x21,0xd0,0xb,0x25,0x3,0xb2,0x13,0xfe,0x97,0xa,0xb,0x10,0xb,0xfe,0xbc,0xc,0x23,0x1b,0x16, + 0x13,0x10,0x1,0x3,0x1,0x1b,0xe,0xf,0x1a,0x1a,0xff,0xff,0x0,0x52,0xfe,0xfb,0x4,0x7,0x1,0x26,0x0,0x23,0x1,0x4d,0x0,0xf5,0x0,0x0,0x0,0x3, + 0x1,0x4d,0xff,0xb,0x0,0x0,0x0,0x0,0xff,0xff,0x0,0xee,0x2,0xf0,0x4,0xa3,0x5,0x1b,0x0,0x23,0x1,0x4b,0xff,0xb,0x0,0x0,0x0,0x3,0x1,0x4b, + 0x0,0xf5,0x0,0x0,0x0,0x0,0xff,0xff,0x0,0xfe,0x2,0xca,0x4,0xb3,0x4,0xf5,0x0,0x23,0x1,0x4c,0xff,0xb,0x0,0x0,0x0,0x3,0x1,0x4c,0x0,0xf5, + 0x0,0x0,0x0,0x0,0x0,0x1,0x1,0xe3,0x2,0xf0,0x3,0xae,0x5,0x1b,0x0,0x11,0x0,0x1f,0x40,0x1c,0x2,0x1,0x0,0x1,0x1,0x4a,0x0,0x0,0x1,0x0, + 0x73,0x2,0x1,0x1,0x1,0x10,0x1,0x4c,0x0,0x0,0x0,0x11,0x0,0x10,0x27,0x3,0x7,0x15,0x2b,0x0,0x16,0x15,0x14,0x7,0x3,0x6,0x6,0x23,0x23,0x22, + 0x26,0x35,0x34,0x37,0x1,0x36,0x33,0x3,0x90,0x1e,0x6,0xae,0x7,0x1f,0x15,0xbd,0xf,0x10,0xe,0x1,0x30,0x1d,0x37,0x5,0x1b,0x16,0x14,0xc,0xf,0xfe, + 0x42,0x13,0x15,0xe,0xc,0xf,0x16,0x1,0xc1,0x2b,0x0,0x1,0x1,0xf3,0x2,0xca,0x3,0xbe,0x4,0xf5,0x0,0x11,0x0,0x17,0x40,0x14,0xa,0x1,0x0,0x1, + 0x1,0x4a,0x0,0x1,0x0,0x1,0x72,0x0,0x0,0x0,0x69,0x27,0x26,0x2,0x7,0x16,0x2b,0x0,0x16,0x15,0x14,0x7,0x1,0x6,0x23,0x22,0x26,0x35,0x34,0x37, + 0x13,0x36,0x36,0x33,0x33,0x3,0xae,0x10,0xe,0xfe,0xd0,0x1d,0x37,0x1b,0x1e,0x6,0xae,0x7,0x1f,0x15,0xbd,0x4,0xf5,0xe,0xc,0xf,0x16,0xfe,0x3f,0x2b, + 0x16,0x14,0xc,0xf,0x1,0xbe,0x13,0x15,0x0,0x1,0x1,0x47,0xfe,0xfb,0x3,0x12,0x1,0x26,0x0,0x11,0x0,0x17,0x40,0x14,0xa,0x1,0x0,0x1,0x1,0x4a, + 0x0,0x1,0x0,0x1,0x72,0x0,0x0,0x0,0x69,0x27,0x26,0x2,0x7,0x16,0x2b,0x0,0x16,0x15,0x14,0x7,0x1,0x6,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x36, + 0x36,0x33,0x33,0x3,0x2,0x10,0xe,0xfe,0xd0,0x1d,0x37,0x1b,0x1e,0x6,0xae,0x7,0x1f,0x15,0xbd,0x1,0x26,0xe,0xc,0xf,0x16,0xfe,0x3f,0x2b,0x16,0x14, + 0xc,0xf,0x1,0xbe,0x13,0x15,0x0,0x2,0x0,0xb3,0xff,0x83,0x4,0x73,0x5,0x20,0x0,0x37,0x0,0x3f,0x0,0x34,0x40,0x31,0x3b,0x34,0x31,0x9,0x4,0x0, + 0x3,0x3a,0x22,0x1f,0xa,0x4,0x2,0x1,0x2,0x4a,0x0,0x0,0x3,0x1,0x3,0x0,0x1,0x70,0x0,0x1,0x2,0x3,0x1,0x2,0x6e,0x0,0x2,0x2,0x71,0x0, + 0x3,0x3,0x10,0x3,0x4c,0x2f,0x2d,0x2b,0x29,0x14,0x4,0x7,0x17,0x2b,0x0,0x15,0x14,0x7,0x6,0x23,0x22,0x27,0x26,0x27,0x3,0x36,0x36,0x37,0x36,0x33, + 0x32,0x17,0x16,0x15,0x14,0x7,0x6,0x6,0x7,0x7,0x6,0x6,0x23,0x22,0x26,0x35,0x34,0x37,0x37,0x2e,0x2,0x35,0x34,0x36,0x36,0x37,0x37,0x36,0x36,0x33, + 0x32,0x16,0x15,0x14,0x7,0x7,0x16,0x16,0x17,0x0,0x16,0x17,0x13,0xe,0x2,0x15,0x4,0x73,0xd,0x17,0x21,0x12,0x13,0x65,0x7c,0x75,0x43,0x83,0x38,0x13, + 0x11,0x23,0x15,0xc,0x29,0x47,0xb4,0x5c,0x20,0x6,0x27,0x25,0x1f,0x1d,0x2,0x20,0x6b,0x9c,0x53,0x81,0xeb,0x97,0x1d,0x6,0x27,0x25,0x1f,0x1d,0x2,0x1d, + 0x49,0x89,0x39,0xfd,0x1,0x73,0x66,0x75,0x60,0x98,0x56,0x3,0xb4,0x23,0x15,0x17,0x28,0xd,0x48,0x13,0xfd,0x6b,0x8,0x2c,0x20,0xb,0x2c,0x18,0x13,0x25, + 0x16,0x27,0x35,0x8,0xb5,0x22,0x1f,0x16,0x17,0x6,0xe,0xb6,0xd,0x6d,0xab,0x69,0x8c,0xf9,0xa2,0xd,0xa3,0x22,0x1f,0x16,0x17,0x6,0xe,0xa7,0xb,0x35, + 0x29,0xfd,0xdb,0x8c,0x10,0x2,0x97,0x10,0x75,0xac,0x61,0x0,0x0,0x0,0x0,0x2,0x0,0x62,0x0,0x6a,0x4,0xb2,0x4,0x4a,0x0,0x3b,0x0,0x49,0x0,0x85, + 0x40,0x13,0x36,0x31,0x2,0x7,0x4,0x27,0x22,0x9,0x4,0x4,0x6,0x7,0x18,0x13,0x2,0x1,0x6,0x3,0x4a,0x4b,0xb0,0x19,0x50,0x58,0x40,0x24,0x5,0x1, + 0x3,0x4,0x3,0x72,0x2,0x1,0x0,0x1,0x0,0x73,0x0,0x4,0x0,0x7,0x6,0x4,0x7,0x63,0x0,0x6,0x1,0x1,0x6,0x57,0x0,0x6,0x6,0x1,0x5b,0x0, + 0x1,0x6,0x1,0x4f,0x1b,0x40,0x2e,0x0,0x3,0x5,0x3,0x72,0x0,0x5,0x4,0x5,0x72,0x0,0x2,0x1,0x0,0x1,0x2,0x0,0x70,0x0,0x0,0x0,0x71,0x0, + 0x4,0x0,0x7,0x6,0x4,0x7,0x63,0x0,0x6,0x1,0x1,0x6,0x57,0x0,0x6,0x6,0x1,0x5b,0x0,0x1,0x6,0x1,0x4f,0x59,0x40,0x10,0x46,0x44,0x3f,0x3d, + 0x3a,0x38,0x34,0x32,0x2f,0x2d,0x24,0x23,0x2f,0x8,0x7,0x17,0x2b,0x0,0x15,0x14,0x7,0x7,0x16,0x15,0x14,0x6,0x7,0x17,0x16,0x15,0x14,0x7,0x6,0x23, + 0x22,0x27,0x27,0x6,0x23,0x22,0x26,0x27,0x7,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x37,0x26,0x35,0x34,0x36,0x37,0x27,0x26,0x35,0x34,0x37,0x36,0x33, + 0x32,0x17,0x17,0x36,0x33,0x32,0x16,0x17,0x37,0x36,0x33,0x32,0x17,0x0,0x16,0x33,0x32,0x36,0x36,0x35,0x34,0x26,0x23,0x22,0x6,0x6,0x15,0x4,0xb2,0x1b, + 0x9c,0x22,0x37,0x33,0x71,0x13,0x21,0x1c,0x17,0x16,0x17,0x6f,0x6b,0x80,0x46,0x76,0x2e,0x99,0x16,0x14,0x18,0x1d,0x18,0x1b,0x9c,0x22,0x37,0x33,0x71,0x13, + 0x21,0x1c,0x17,0x17,0x16,0x6f,0x6b,0x80,0x46,0x76,0x2e,0x99,0x16,0x14,0x19,0x1c,0xfc,0xf0,0x75,0x67,0x57,0x85,0x48,0x75,0x67,0x57,0x85,0x48,0x3,0xf9, + 0x17,0x16,0x17,0x83,0x47,0x58,0x53,0x9b,0x3d,0x87,0x16,0x14,0x18,0x1d,0x18,0x1b,0x84,0x43,0x27,0x24,0x80,0x13,0x21,0x1d,0x16,0x17,0x16,0x83,0x47,0x58, + 0x53,0x9b,0x3d,0x87,0x16,0x14,0x19,0x1c,0x18,0x1b,0x84,0x43,0x27,0x24,0x80,0x13,0x21,0xfd,0xb3,0x74,0x59,0x90,0x4f,0x60,0x74,0x59,0x90,0x4f,0x0,0x1, + 0x0,0x98,0xff,0x1f,0x4,0x55,0x5,0x84,0x0,0x53,0x0,0x57,0x40,0x54,0x4e,0x1,0x6,0x7,0x51,0x1,0x1,0x6,0x27,0x1,0x2,0x5,0x24,0x1,0x3,0x2, + 0x4,0x4a,0x0,0x7,0x6,0x7,0x72,0x0,0x0,0x1,0x4,0x1,0x0,0x4,0x70,0x0,0x4,0x5,0x1,0x4,0x5,0x6e,0x0,0x3,0x2,0x3,0x73,0x0,0x6,0x0, + 0x1,0x0,0x6,0x1,0x63,0x0,0x5,0x2,0x2,0x5,0x57,0x0,0x5,0x5,0x2,0x5c,0x0,0x2,0x5,0x2,0x50,0x4c,0x4a,0x47,0x46,0x35,0x33,0x2f,0x2e,0x22, + 0x20,0x1d,0x1c,0x24,0x14,0x8,0x7,0x16,0x2b,0x0,0x15,0x14,0x7,0x6,0x23,0x22,0x27,0x26,0x26,0x23,0x22,0x6,0x6,0x15,0x14,0x16,0x17,0x16,0x17,0x16, + 0x16,0x17,0x16,0x16,0x15,0x14,0x6,0x6,0x7,0x7,0x6,0x6,0x23,0x22,0x26,0x35,0x34,0x37,0x37,0x26,0x27,0x26,0x35,0x34,0x37,0x36,0x33,0x32,0x17,0x16, + 0x16,0x33,0x32,0x36,0x36,0x35,0x34,0x27,0x26,0x26,0x27,0x26,0x26,0x27,0x26,0x26,0x35,0x34,0x36,0x36,0x37,0x37,0x36,0x36,0x33,0x32,0x16,0x15,0x14,0x7, + 0x7,0x16,0x17,0x4,0x55,0xe,0x19,0x23,0xf,0xe,0x49,0xb0,0x5d,0x51,0x72,0x38,0x36,0x34,0x30,0x68,0x47,0x6c,0x31,0x4a,0x54,0x53,0xc3,0x9e,0x29,0x6, + 0x27,0x25,0x1f,0x1d,0x2,0x2a,0xae,0x7b,0x21,0xf,0x1a,0x22,0xe,0x11,0x4a,0xb8,0x64,0x63,0x7e,0x38,0x67,0x23,0x5b,0x2c,0x50,0x5b,0x2d,0x49,0x52,0x5c, + 0xba,0x86,0x29,0x6,0x26,0x25,0x20,0x1d,0x2,0x2a,0x98,0x77,0x3,0xf2,0x20,0x16,0x1b,0x30,0x8,0x29,0x2c,0x2f,0x4c,0x2b,0x28,0x2e,0xd,0xb,0xe,0x8, + 0x14,0x11,0x1a,0x6e,0x54,0x4f,0x9b,0x6c,0x4,0xe9,0x22,0x1f,0x16,0x17,0x6,0xe,0xf0,0x14,0x4e,0x14,0x20,0x15,0x1c,0x30,0xa,0x2f,0x33,0x37,0x53,0x2c, + 0x51,0x1d,0xa,0xe,0x6,0xb,0x10,0x10,0x19,0x68,0x4f,0x54,0x95,0x5d,0x2,0xe3,0x22,0x1f,0x16,0x18,0x5,0xe,0xeb,0x12,0x41,0x0,0x0,0x1,0x0,0x5f, + 0xff,0xea,0x4,0xc9,0x4,0xb9,0x0,0x51,0x0,0x5e,0x40,0x5b,0x46,0x11,0x2,0x3,0x2,0x3a,0x1d,0x2,0x5,0x4,0x2,0x4a,0x0,0x0,0x1,0x2,0x1,0x0, + 0x2,0x70,0x0,0x7,0x5,0x6,0x5,0x7,0x6,0x70,0xc,0x1,0x2,0xb,0x1,0x3,0x4,0x2,0x3,0x63,0xa,0x1,0x4,0x9,0x1,0x5,0x7,0x4,0x5,0x63, + 0x0,0x1,0x1,0xd,0x5b,0x0,0xd,0xd,0x16,0x4b,0x0,0x6,0x6,0x8,0x5b,0x0,0x8,0x8,0x17,0x8,0x4c,0x50,0x4e,0x4c,0x4a,0x44,0x42,0x40,0x3e,0x38, + 0x36,0x26,0x23,0x22,0x26,0x22,0x26,0x22,0x24,0x14,0xe,0x7,0x1d,0x2b,0x0,0x15,0x14,0x7,0x6,0x23,0x22,0x27,0x26,0x26,0x23,0x22,0x6,0x7,0x21,0x32, + 0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x21,0x6,0x7,0x21,0x32,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x21,0x16,0x16,0x33,0x32,0x36,0x37,0x36,0x33,0x32,0x17, + 0x16,0x15,0x14,0x7,0x6,0x23,0x22,0x26,0x27,0x23,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x33,0x36,0x37,0x23,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33, + 0x33,0x36,0x24,0x33,0x32,0x17,0x4,0xc9,0xe,0x17,0x23,0x11,0xe,0x3a,0x8f,0x48,0x73,0xb7,0x3b,0x1,0x5c,0x1b,0x1a,0x3,0x7,0x23,0x22,0xfe,0x84,0x18, + 0x4,0x1,0x7b,0x1b,0x1a,0x3,0x7,0x22,0x22,0xfe,0xa3,0x11,0x84,0x6d,0x52,0xa3,0x46,0xe,0x12,0x22,0x17,0xe,0x24,0xba,0xcf,0xae,0xd1,0x15,0x6a,0x1c, + 0x19,0x2,0x8,0x22,0x22,0x4f,0x4,0x15,0x4b,0x1b,0x1a,0x3,0x7,0x23,0x22,0x61,0x4c,0x1,0x15,0xb6,0xaf,0x9a,0x4,0x47,0x20,0x14,0x1c,0x2d,0xa,0x28, + 0x29,0x80,0x6d,0x16,0x18,0xb,0xf,0x27,0x21,0x55,0x51,0x15,0x18,0xc,0xf,0x27,0x21,0x75,0x7f,0x32,0x2e,0xa,0x2d,0x1c,0x14,0x20,0x15,0x6c,0xd1,0xb7, + 0x16,0x19,0xf,0xa,0x27,0x21,0x50,0x56,0x16,0x18,0xb,0xf,0x27,0x21,0xaf,0xd2,0x5d,0x0,0x0,0x0,0x0,0x1,0xff,0xc7,0xfe,0xe9,0x5,0x5,0x5,0x25, + 0x0,0x38,0x0,0x48,0x40,0x45,0x2d,0x11,0x2,0x3,0x2,0x1e,0x1,0x4,0x6,0x2,0x4a,0x0,0x0,0x1,0x2,0x1,0x0,0x2,0x70,0x0,0x5,0x3,0x6,0x3, + 0x5,0x6,0x70,0x8,0x1,0x2,0x7,0x1,0x3,0x5,0x2,0x3,0x61,0x0,0x6,0x0,0x4,0x6,0x4,0x5f,0x0,0x1,0x1,0x9,0x5b,0x0,0x9,0x9,0x10,0x1, + 0x4c,0x37,0x35,0x26,0x22,0x23,0x16,0x22,0x26,0x22,0x23,0x15,0xa,0x7,0x1d,0x2b,0x0,0x16,0x15,0x14,0x7,0x6,0x23,0x22,0x27,0x26,0x23,0x22,0x7,0x7, + 0x33,0x32,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x23,0x3,0x2,0x21,0x22,0x27,0x26,0x35,0x34,0x37,0x36,0x33,0x32,0x17,0x16,0x33,0x32,0x37,0x13,0x23,0x22, + 0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x33,0x37,0x12,0x21,0x32,0x17,0x4,0xf2,0x13,0x8,0x15,0x25,0x7,0xe,0x57,0x5a,0x99,0x33,0x26,0xea,0x1b,0x1a,0x3, + 0x7,0x23,0x22,0xf8,0xba,0x56,0xfe,0xe6,0x71,0x5c,0x25,0x7,0x14,0x26,0x7,0xe,0x57,0x5a,0x99,0x33,0xb4,0xc5,0x1b,0x1a,0x3,0x7,0x23,0x22,0xd3,0x2c, + 0x56,0x1,0x1a,0x71,0x5c,0x5,0x0,0x17,0x13,0x13,0x15,0x3c,0x4,0x1d,0xbb,0x88,0x16,0x18,0xb,0xf,0x27,0x21,0xfd,0x5f,0xfe,0xca,0x1f,0xd,0x24,0x12, + 0x15,0x3c,0x4,0x1d,0xbb,0x2,0x8a,0x16,0x18,0xb,0xf,0x27,0x21,0x9f,0x1,0x36,0x1f,0x0,0x0,0x0,0x0,0x1,0x0,0x1e,0xff,0xf6,0x4,0x91,0x4,0xa3, + 0x0,0x38,0x0,0x4a,0x40,0x47,0x2,0x1,0x0,0x8,0xd,0x1,0x2,0x1,0x2d,0x18,0x2,0x4,0x3,0x26,0x1,0x5,0x4,0x4,0x4a,0x0,0x1,0x0,0x2,0x3, + 0x1,0x2,0x61,0x7,0x1,0x3,0x6,0x1,0x4,0x5,0x3,0x4,0x63,0x0,0x0,0x0,0x8,0x59,0x9,0x1,0x8,0x8,0xe,0x4b,0x0,0x5,0x5,0xf,0x5,0x4c, + 0x0,0x0,0x0,0x38,0x0,0x36,0x26,0x25,0x33,0x26,0x21,0x26,0x21,0x26,0xa,0x7,0x1c,0x2b,0x0,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x21,0x3,0x21,0x32, + 0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x21,0x7,0x21,0x32,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x21,0x7,0x6,0x6,0x23,0x23,0x22,0x26,0x35,0x34,0x37,0x37, + 0x23,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x33,0x13,0x36,0x36,0x33,0x21,0x4,0x77,0x1a,0x3,0x7,0x23,0x22,0xfd,0xba,0x3c,0x1,0xd4,0x1b,0x1a,0x3, + 0x7,0x22,0x22,0xfe,0x2c,0x21,0x1,0x4b,0x1b,0x1a,0x3,0x7,0x23,0x22,0xfe,0xb6,0x1f,0x6,0x27,0x28,0x2,0x23,0x1e,0x2,0x1f,0x84,0x1b,0x1a,0x3,0x7, + 0x23,0x22,0x83,0x84,0x7,0x20,0x22,0x2,0x9f,0x4,0xa3,0x16,0x18,0xb,0xf,0x27,0x21,0xfe,0xad,0x15,0x18,0xc,0xf,0x27,0x21,0xba,0x16,0x18,0xb,0xf, + 0x27,0x21,0xaf,0x23,0x1e,0x15,0x18,0x4,0x10,0xaf,0x16,0x18,0xb,0xf,0x27,0x21,0x2,0xe9,0x26,0x1e,0x0,0x1,0x0,0xc7,0x0,0x0,0x4,0x32,0x4,0xb9, + 0x0,0x45,0x0,0x41,0x40,0x3e,0x35,0x16,0x2,0x3,0x2,0x23,0x1,0x5,0x4,0x2,0x4a,0x0,0x0,0x1,0x2,0x1,0x0,0x2,0x70,0x7,0x1,0x2,0x6,0x1, + 0x3,0x4,0x2,0x3,0x63,0x0,0x1,0x1,0x8,0x5b,0x0,0x8,0x8,0x16,0x4b,0x0,0x4,0x4,0x5,0x59,0x0,0x5,0x5,0xf,0x5,0x4c,0x26,0x26,0x27,0x36, + 0x23,0x26,0x28,0x23,0x14,0x9,0x7,0x1d,0x2b,0x0,0x15,0x14,0x7,0x6,0x23,0x22,0x27,0x26,0x23,0x22,0x6,0x6,0x15,0x14,0x16,0x17,0x16,0x17,0x21,0x32, + 0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x23,0x6,0x6,0x7,0x21,0x32,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x21,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x35,0x23, + 0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x33,0x26,0x26,0x35,0x34,0x36,0x36,0x33,0x32,0x16,0x17,0x4,0x32,0x16,0x1c,0x1b,0xd,0x11,0x65,0x7a,0x43,0x65, + 0x35,0xf,0x11,0xc,0x5,0x1,0x1f,0x1b,0x1a,0x3,0x7,0x23,0x22,0xef,0x2,0x5e,0x55,0x1,0xc9,0x1b,0x1a,0x3,0x7,0x22,0x22,0xfd,0xa7,0x16,0x17,0x1c, + 0x6b,0x6d,0x8e,0x1b,0x1a,0x3,0x7,0x23,0x22,0x56,0x17,0x14,0x5c,0xb2,0x79,0x51,0x94,0x3b,0x4,0x54,0x18,0x16,0x22,0x2a,0xa,0x41,0x3f,0x67,0x3a,0x20, + 0x38,0x34,0x20,0x11,0x16,0x18,0xb,0xf,0x27,0x21,0x66,0xb5,0x4d,0x16,0x18,0xb,0xf,0x27,0x21,0x34,0x23,0x1c,0x17,0x5b,0xaf,0x64,0x16,0x18,0xb,0xf, + 0x27,0x21,0x46,0x4b,0x2e,0x5f,0xaa,0x69,0x2d,0x28,0x0,0x3,0x0,0x13,0xff,0xea,0x5,0x21,0x4,0xae,0x0,0x3f,0x0,0x42,0x0,0x45,0x0,0x7a,0x40,0xd, + 0x1b,0xf,0x2,0x3,0x0,0x4,0x45,0x42,0x2,0x1,0x0,0x2,0x4a,0x4b,0xb0,0x19,0x50,0x58,0x40,0x21,0xd,0xa,0x8,0x6,0x4,0x4,0xc,0xb,0x3,0x3, + 0x0,0x1,0x4,0x0,0x64,0x9,0x1,0x5,0x5,0xe,0x4b,0x0,0x7,0x7,0x11,0x4b,0x2,0x1,0x1,0x1,0x17,0x1,0x4c,0x1b,0x40,0x24,0x0,0x7,0x5,0x4, + 0x5,0x7,0x4,0x70,0xd,0xa,0x8,0x6,0x4,0x4,0xc,0xb,0x3,0x3,0x0,0x1,0x4,0x0,0x64,0x9,0x1,0x5,0x5,0xe,0x4b,0x2,0x1,0x1,0x1,0x17, + 0x1,0x4c,0x59,0x40,0x18,0x0,0x0,0x44,0x43,0x41,0x40,0x0,0x3f,0x0,0x3e,0x36,0x35,0x12,0x23,0x13,0x23,0x26,0x23,0x25,0x23,0x26,0xe,0x7,0x1d,0x2b, + 0x0,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x23,0x3,0x6,0x6,0x23,0x22,0x27,0x3,0x1,0x6,0x6,0x23,0x22,0x26,0x37,0x13,0x23,0x22,0x26,0x35,0x34,0x37, + 0x36,0x36,0x33,0x33,0x13,0x36,0x36,0x33,0x32,0x16,0x7,0x3,0x33,0x37,0x36,0x36,0x33,0x32,0x17,0x17,0x33,0x13,0x36,0x36,0x33,0x32,0x17,0x16,0x16,0x15, + 0x14,0x7,0x3,0x33,0x5,0x23,0x3,0x1,0x23,0x13,0x5,0x7,0x1a,0x3,0x7,0x23,0x22,0x74,0xb2,0xb,0x2c,0x27,0x50,0xc,0x68,0xfe,0xca,0x12,0x34,0x26, + 0x28,0x27,0x1,0x16,0x68,0x1b,0x1a,0x3,0x7,0x23,0x22,0x53,0xf,0x2,0x1d,0x2b,0x2c,0x1f,0x2,0x13,0xee,0x30,0xb,0x24,0x20,0x3f,0x6,0x10,0xef,0x75, + 0x9,0x1e,0x1b,0xe,0x10,0x1e,0x1b,0x8,0x70,0x61,0xfc,0xfa,0xa9,0x11,0x2,0x9d,0xa9,0x3e,0x2,0xf2,0x16,0x18,0xb,0xf,0x27,0x21,0xfd,0xc9,0x24,0x1d, + 0x41,0x2,0x4a,0xfd,0xb6,0x22,0x1f,0x1e,0x23,0x2,0x37,0x16,0x18,0xb,0xf,0x27,0x21,0x1,0x7e,0x22,0x1a,0x1b,0x24,0xfe,0x85,0x5b,0x14,0x14,0x28,0x5b, + 0x1,0x83,0x1e,0x1b,0x3,0x5,0x16,0x13,0xf,0x18,0xfe,0x9c,0x90,0xfe,0xa0,0x1,0x60,0xfe,0xa0,0x0,0x0,0x1,0x0,0xa1,0xff,0xf6,0x4,0xc7,0x4,0xb2, + 0x0,0x4c,0x0,0x48,0x40,0x45,0x47,0x35,0x8,0x3,0x1,0x0,0x30,0x1,0x2,0x1,0x29,0x14,0x2,0x3,0x2,0x22,0x1,0x4,0x3,0x4,0x4a,0x8,0x1,0x0, + 0x7,0x1,0x1,0x2,0x0,0x1,0x62,0x6,0x1,0x2,0x5,0x1,0x3,0x4,0x2,0x3,0x61,0xa,0x1,0x9,0x9,0xe,0x4b,0x0,0x4,0x4,0xf,0x4,0x4c,0x4b, + 0x49,0x43,0x42,0x26,0x22,0x26,0x25,0x33,0x26,0x22,0x26,0x24,0xb,0x7,0x1d,0x2b,0x0,0x15,0x14,0x7,0x1,0x33,0x32,0x16,0x15,0x14,0x7,0x6,0x6,0x23, + 0x21,0x7,0x7,0x21,0x32,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x21,0x7,0x6,0x6,0x23,0x23,0x22,0x26,0x35,0x34,0x37,0x37,0x21,0x22,0x26,0x35,0x34,0x37, + 0x36,0x36,0x33,0x21,0x37,0x27,0x21,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x33,0x3,0x26,0x35,0x34,0x36,0x37,0x36,0x33,0x32,0x16,0x17,0x13,0x1,0x36, + 0x33,0x32,0x17,0x4,0xc7,0x15,0xfe,0xe0,0xa2,0x1b,0x1a,0x3,0x7,0x23,0x22,0xff,0x1,0x5f,0x9,0x1,0x36,0x1b,0x1a,0x3,0x7,0x22,0x22,0xfe,0xc9,0x29, + 0x6,0x27,0x28,0x2,0x23,0x1e,0x2,0x29,0xfe,0xd3,0x1c,0x19,0x2,0x7,0x23,0x22,0x1,0x2e,0xb,0x30,0xff,0x0,0x1b,0x1a,0x3,0x7,0x23,0x22,0xa2,0xa9, + 0xa,0x19,0x1c,0x1a,0x14,0x10,0x14,0x8,0xf5,0x1,0xb2,0x18,0x20,0x17,0x19,0x4,0x8b,0x1b,0x14,0x1a,0xfe,0xa6,0x16,0x18,0xb,0xf,0x27,0x21,0x72,0x34, + 0x16,0x18,0xb,0xf,0x27,0x21,0xeb,0x23,0x1e,0x15,0x18,0x4,0x10,0xeb,0x16,0x19,0xf,0xa,0x27,0x21,0x41,0x65,0x16,0x18,0xb,0xf,0x27,0x21,0x1,0x65, + 0x16,0xc,0x10,0x19,0xb,0xb,0x10,0x11,0xfd,0xf5,0x2,0x12,0x1e,0xf,0x0,0x0,0xff,0xff,0x0,0x90,0x1,0x6,0x4,0x7a,0x3,0x84,0x0,0x27,0x1,0x59, + 0x0,0x23,0x0,0xc8,0x1,0x7,0x1,0x59,0xff,0xdd,0xff,0x38,0x0,0x11,0xb1,0x0,0x1,0xb0,0xc8,0xb0,0x33,0x2b,0xb1,0x1,0x1,0xb8,0xff,0x38,0xb0,0x33, + 0x2b,0x0,0x0,0x0,0x0,0x1,0x0,0xb3,0x1,0xce,0x4,0x57,0x2,0xbc,0x0,0x27,0x0,0x34,0xb1,0x6,0x64,0x44,0x40,0x29,0x20,0x1,0x3,0x2,0xc,0x1, + 0x0,0x1,0x2,0x4a,0x0,0x3,0x1,0x0,0x3,0x57,0x0,0x2,0x0,0x1,0x0,0x2,0x1,0x63,0x0,0x3,0x3,0x0,0x5b,0x0,0x0,0x3,0x0,0x4f,0x24,0x2c, + 0x24,0x21,0x4,0x7,0x18,0x2b,0xb1,0x6,0x0,0x44,0x0,0x6,0x23,0x22,0x26,0x27,0x26,0x26,0x23,0x22,0x7,0x6,0x23,0x22,0x26,0x27,0x26,0x35,0x34,0x37, + 0x36,0x36,0x33,0x32,0x16,0x17,0x16,0x16,0x33,0x32,0x37,0x36,0x33,0x32,0x16,0x17,0x16,0x15,0x14,0x7,0x3,0xfd,0x80,0x3d,0x31,0x5b,0x40,0x41,0x51,0x2c, + 0x59,0x5a,0xa,0xa,0xe,0x19,0x9,0xc,0x15,0x46,0x80,0x3e,0x31,0x5b,0x40,0x41,0x51,0x2c,0x59,0x5a,0xb,0x9,0xe,0x19,0x9,0xa,0x14,0x1,0xfc,0x2e, + 0x16,0x16,0x16,0x14,0x3a,0x6,0x12,0xf,0x15,0x14,0x1c,0xf,0x35,0x2e,0x16,0x16,0x16,0x14,0x3a,0x6,0x13,0x10,0x14,0x13,0x1b,0x10,0x0,0x3,0x0,0xc7, + 0x0,0x82,0x4,0x45,0x4,0x8,0x0,0xd,0x0,0x1f,0x0,0x2d,0x0,0x48,0x40,0x45,0x19,0x10,0x2,0x2,0x3,0x1,0x4a,0x6,0x1,0x1,0x0,0x0,0x3,0x1, + 0x0,0x63,0x7,0x1,0x3,0x0,0x2,0x5,0x3,0x2,0x61,0x8,0x1,0x5,0x4,0x4,0x5,0x57,0x8,0x1,0x5,0x5,0x4,0x5b,0x0,0x4,0x5,0x4,0x4f,0x20, + 0x20,0xe,0xe,0x0,0x0,0x20,0x2d,0x20,0x2c,0x27,0x25,0xe,0x1f,0xe,0x1d,0x17,0x14,0x0,0xd,0x0,0xc,0x25,0x9,0x7,0x15,0x2b,0x0,0x16,0x7,0x7, + 0x6,0x6,0x23,0x22,0x26,0x37,0x37,0x36,0x36,0x33,0x0,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x21,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x21,0x0,0x16, + 0x7,0x7,0x6,0x6,0x23,0x22,0x26,0x37,0x37,0x36,0x36,0x33,0x2,0xf9,0x2d,0x4,0x17,0x4,0x2b,0x31,0x32,0x2e,0x4,0x17,0x4,0x2b,0x31,0x1,0x65,0x1a, + 0x3,0x7,0x23,0x22,0xfd,0x6,0x1b,0x1a,0x3,0x7,0x23,0x22,0x2,0xfa,0xfe,0x85,0x2d,0x4,0x17,0x4,0x2b,0x31,0x32,0x2e,0x4,0x17,0x4,0x2b,0x31,0x4, + 0x8,0x15,0x17,0x80,0x18,0x14,0x15,0x17,0x80,0x18,0x14,0xfe,0x86,0x16,0x18,0xb,0xf,0x27,0x21,0x16,0x18,0xb,0xf,0x27,0x21,0xfe,0xcc,0x15,0x17,0x80, + 0x18,0x14,0x15,0x17,0x80,0x18,0x14,0x0,0x0,0x0,0xff,0xff,0x0,0xa4,0x1,0x36,0x4,0x68,0x3,0x56,0x0,0x27,0x1,0x63,0x0,0x23,0x0,0xc8,0x1,0x7, + 0x1,0x63,0xff,0xdd,0xff,0x38,0x0,0x11,0xb1,0x0,0x1,0xb0,0xc8,0xb0,0x33,0x2b,0xb1,0x1,0x1,0xb8,0xff,0x38,0xb0,0x33,0x2b,0x0,0x0,0x0,0x0,0x1, + 0x0,0x89,0x0,0x79,0x4,0x71,0x4,0x16,0x0,0x1b,0x0,0x18,0x40,0x15,0x1a,0xa,0x2,0x1,0x0,0x1,0x4a,0x0,0x0,0x1,0x0,0x72,0x0,0x1,0x1,0x69, + 0x2c,0x14,0x2,0x7,0x16,0x2b,0x0,0x35,0x34,0x37,0x36,0x33,0x32,0x17,0x1,0x16,0x15,0x14,0x7,0x6,0x6,0x7,0x1,0x6,0x23,0x22,0x26,0x27,0x26,0x35, + 0x34,0x37,0x1,0x1,0x1,0x10,0x14,0x1f,0x24,0x10,0x11,0x2,0xc6,0x23,0x2,0x5,0x1b,0x19,0xfc,0xb3,0x13,0xd,0x15,0x1d,0x8,0x6,0x2f,0x2,0xd2,0xfd, + 0xa6,0x3,0x9a,0x1b,0x16,0x1d,0x2e,0x9,0xfe,0x83,0x13,0x24,0x6,0xc,0x1a,0x23,0xc,0xfe,0x83,0x8,0x1b,0x19,0x14,0x10,0x2b,0x14,0x1,0x3e,0x1,0x3b, + 0x0,0x0,0x0,0x2,0x0,0x81,0x0,0x6e,0x4,0x63,0x4,0x6b,0x0,0x1c,0x0,0x2e,0x0,0x8,0xb5,0x23,0x1d,0x19,0x9,0x2,0x30,0x2b,0x1,0x16,0x15,0x14, + 0x7,0x6,0x6,0x7,0x1,0x6,0x23,0x22,0x26,0x27,0x26,0x35,0x34,0x37,0x25,0x25,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x32,0x17,0x0,0x16,0x15,0x14,0x7, + 0x6,0x6,0x23,0x21,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x21,0x4,0x3a,0x29,0x2,0x5,0x21,0x1c,0xfc,0xf1,0x12,0x10,0x12,0x18,0x9,0x7,0x35,0x2, + 0x86,0xfd,0xda,0x25,0xd,0xb,0x19,0x10,0x9,0x13,0x2,0x64,0x1a,0x3,0x7,0x23,0x22,0xfd,0x6,0x1b,0x1a,0x3,0x7,0x23,0x22,0x2,0xfa,0x3,0x3e,0x11, + 0x28,0x4,0xe,0x1d,0x24,0xa,0xfe,0xe0,0x7,0x17,0x1a,0x12,0x12,0x2e,0x13,0xe4,0xdf,0xf,0x1e,0x10,0x20,0x1b,0x19,0x7,0xfc,0x9a,0x16,0x18,0xb,0xf, + 0x27,0x21,0x16,0x18,0xb,0xf,0x27,0x21,0x0,0x3,0x0,0x3b,0x1,0x25,0x4,0xd1,0x3,0x67,0x0,0x1f,0x0,0x2b,0x0,0x37,0x0,0xa,0xb7,0x33,0x2d,0x24, + 0x20,0x6,0x0,0x3,0x30,0x2b,0x0,0x16,0x16,0x15,0x14,0x6,0x6,0x23,0x22,0x26,0x26,0x27,0x6,0x6,0x23,0x22,0x26,0x26,0x35,0x34,0x37,0x3e,0x2,0x33, + 0x32,0x16,0x16,0x17,0x36,0x36,0x33,0x0,0x36,0x37,0x26,0x26,0x23,0x22,0x6,0x15,0x14,0x16,0x33,0x24,0x26,0x23,0x22,0x6,0x7,0x16,0x16,0x33,0x32,0x36, + 0x35,0x4,0x1d,0x74,0x40,0x4f,0x8a,0x54,0x47,0x6b,0x4b,0x26,0x4a,0x95,0x68,0x4b,0x74,0x40,0x1,0x7,0x52,0x84,0x50,0x47,0x6b,0x4b,0x26,0x4a,0x95,0x68, + 0xfd,0xbf,0x72,0x3e,0x2f,0x64,0x48,0x48,0x5a,0x45,0x3d,0x3,0x0,0x45,0x3d,0x4b,0x72,0x3e,0x2f,0x64,0x48,0x48,0x5a,0x3,0x67,0x43,0x77,0x4a,0x5b,0x91, + 0x52,0x33,0x52,0x3b,0x5b,0x65,0x43,0x77,0x4a,0x13,0xa,0x54,0x83,0x4a,0x33,0x52,0x3b,0x5b,0x65,0xfe,0x44,0x52,0x4d,0x49,0x4e,0x63,0x4c,0x3b,0x4c,0xea, + 0x4c,0x52,0x4d,0x49,0x4e,0x63,0x4c,0x0,0x0,0x1,0xff,0x8d,0xfe,0x66,0x5,0x75,0x5,0xed,0x0,0x27,0x0,0x6,0xb3,0x12,0x0,0x1,0x30,0x2b,0x0,0x16, + 0x17,0x16,0x15,0x14,0x7,0x6,0x23,0x22,0x27,0x26,0x23,0x22,0x6,0x7,0x3,0x6,0x6,0x23,0x22,0x26,0x27,0x26,0x35,0x34,0x37,0x36,0x33,0x32,0x17,0x16, + 0x33,0x32,0x36,0x37,0x13,0x36,0x36,0x33,0x4,0x8e,0x8a,0x3a,0x23,0xa,0x16,0x20,0x9,0xd,0x7a,0x6e,0x69,0x8b,0x17,0xc4,0x21,0xe0,0xaa,0x49,0x8a,0x3a, + 0x23,0xa,0x16,0x20,0x9,0xd,0x7a,0x6e,0x69,0x8b,0x17,0xc4,0x21,0xe0,0xaa,0x5,0xed,0x1f,0x1b,0x10,0x21,0x11,0x17,0x34,0x5,0x30,0x8f,0x83,0xfb,0xab, + 0xbc,0xd2,0x1f,0x1b,0x10,0x21,0x11,0x17,0x34,0x5,0x30,0x8f,0x83,0x4,0x55,0xbc,0xd2,0x0,0x0,0x0,0x0,0x1,0x0,0x9b,0x0,0x78,0x4,0x83,0x4,0x15, + 0x0,0x1b,0x0,0x18,0x40,0x15,0x1a,0xa,0x2,0x0,0x1,0x1,0x4a,0x0,0x1,0x0,0x1,0x72,0x0,0x0,0x0,0x69,0x2c,0x14,0x2,0x7,0x16,0x2b,0x24,0x15, + 0x14,0x7,0x6,0x23,0x22,0x27,0x1,0x26,0x35,0x34,0x37,0x36,0x36,0x37,0x1,0x36,0x33,0x32,0x16,0x17,0x16,0x15,0x14,0x7,0x1,0x1,0x3,0xfc,0x14,0x1f, + 0x24,0x10,0x11,0xfd,0x3a,0x23,0x2,0x5,0x1b,0x19,0x3,0x4d,0x13,0xd,0x15,0x1d,0x8,0x6,0x2f,0xfd,0x2e,0x2,0x5a,0xf4,0x1b,0x16,0x1d,0x2e,0x9,0x1, + 0x7d,0x13,0x24,0x6,0xc,0x1a,0x23,0xc,0x1,0x7d,0x8,0x1b,0x19,0x13,0x11,0x2b,0x14,0xfe,0xc2,0xfe,0xc5,0x0,0x0,0x0,0x0,0x2,0x0,0x81,0x0,0x6e, + 0x4,0x96,0x4,0x65,0x0,0x1c,0x0,0x2e,0x0,0x8,0xb5,0x23,0x1d,0x10,0x0,0x2,0x30,0x2b,0x0,0x33,0x32,0x16,0x17,0x16,0x15,0x14,0x7,0x5,0x5,0x16, + 0x15,0x14,0x7,0x6,0x6,0x23,0x22,0x27,0x1,0x26,0x35,0x34,0x37,0x36,0x36,0x37,0x1,0x2,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x21,0x22,0x26,0x35,0x34, + 0x37,0x36,0x36,0x33,0x21,0x4,0x4c,0x10,0x12,0x18,0x9,0x7,0x35,0xfd,0x7a,0x2,0x26,0x25,0xd,0xb,0x19,0x10,0x9,0x13,0xfd,0x47,0x29,0x2,0x5,0x21, + 0x1c,0x3,0xf,0x55,0x1a,0x3,0x7,0x23,0x22,0xfd,0x6,0x1b,0x1a,0x3,0x7,0x23,0x22,0x2,0xfa,0x4,0x65,0x17,0x1a,0x12,0x12,0x2e,0x13,0xe4,0xdf,0xf, + 0x1e,0xf,0x21,0x1b,0x19,0x7,0x1,0x26,0x11,0x28,0x4,0xe,0x1d,0x24,0xa,0x1,0x20,0xfc,0xa0,0x16,0x18,0xb,0xf,0x27,0x21,0x16,0x18,0xb,0xf,0x27, + 0x21,0x0,0x0,0x0,0x0,0x1,0x0,0xcc,0x1,0x3e,0x4,0x7b,0x3,0x51,0x0,0x18,0x0,0x30,0x40,0x2d,0x12,0x2,0x2,0x1,0x2,0xb,0x1,0x0,0x1,0x2, + 0x4a,0x0,0x0,0x1,0x0,0x73,0x3,0x1,0x2,0x1,0x1,0x2,0x55,0x3,0x1,0x2,0x2,0x1,0x59,0x0,0x1,0x2,0x1,0x4d,0x0,0x0,0x0,0x18,0x0,0x16, + 0x25,0x27,0x4,0x7,0x16,0x2b,0x0,0x16,0x15,0x14,0x7,0x3,0x6,0x6,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x21,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33, + 0x21,0x4,0x62,0x19,0x2,0x46,0x6,0x27,0x29,0x23,0x1f,0x2,0x39,0xfd,0x2b,0x1b,0x1a,0x3,0x7,0x22,0x22,0x3,0x2c,0x3,0x51,0x14,0x18,0xf,0x9,0xfe, + 0x72,0x23,0x1e,0x15,0x18,0x6,0xe,0x1,0x42,0x15,0x18,0xc,0xf,0x27,0x21,0x0,0x0,0x1,0x0,0xc7,0x1,0xfe,0x4,0x45,0x2,0x8e,0x0,0x11,0x0,0x6, + 0xb3,0x6,0x0,0x1,0x30,0x2b,0x0,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x21,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x21,0x4,0x2b,0x1a,0x3,0x7,0x23, + 0x22,0xfd,0x6,0x1b,0x1a,0x3,0x7,0x23,0x22,0x2,0xfa,0x2,0x8e,0x16,0x18,0xb,0xf,0x27,0x21,0x16,0x18,0xb,0xf,0x27,0x21,0x0,0x0,0x0,0x0,0x1, + 0x0,0xe4,0x0,0xc8,0x4,0x27,0x3,0xc4,0x0,0x29,0x0,0x55,0x4b,0xb0,0x24,0x50,0x58,0x40,0x9,0x24,0x19,0xf,0x4,0x4,0x0,0x2,0x1,0x4a,0x1b,0x40, + 0x9,0x24,0x19,0xf,0x4,0x4,0x1,0x3,0x1,0x4a,0x59,0x4b,0xb0,0x24,0x50,0x58,0x40,0xd,0x1,0x1,0x0,0x2,0x0,0x73,0x3,0x1,0x2,0x2,0x19,0x2, + 0x4c,0x1b,0x40,0x17,0x0,0x1,0x3,0x0,0x3,0x1,0x0,0x70,0x0,0x0,0x0,0x71,0x0,0x2,0x2,0x19,0x4b,0x0,0x3,0x3,0x19,0x3,0x4c,0x59,0xb6,0x24, + 0x2d,0x24,0x2b,0x4,0x7,0x18,0x2b,0x0,0x15,0x14,0x7,0x5,0x13,0x16,0x15,0x14,0x6,0x7,0x6,0x23,0x22,0x27,0x27,0x5,0x6,0x23,0x22,0x27,0x26,0x35, + 0x34,0x37,0x25,0x3,0x26,0x35,0x34,0x36,0x37,0x36,0x33,0x32,0x17,0x17,0x25,0x36,0x33,0x32,0x17,0x4,0x27,0x19,0xfe,0xdc,0xda,0x12,0x11,0x11,0x1c,0x16, + 0x19,0x17,0xd5,0xfe,0xe2,0x17,0x14,0x19,0x1d,0x1a,0x19,0x1,0x24,0xd9,0x12,0x11,0x11,0x1c,0x16,0x1a,0x16,0xd4,0x1,0x1e,0x17,0x14,0x19,0x1d,0x3,0x7b, + 0x16,0x16,0x15,0xfb,0xfe,0xfc,0x15,0x12,0xd,0x19,0xf,0x17,0x1b,0xfe,0xf7,0x14,0x1f,0x1c,0x16,0x16,0x15,0xfc,0x1,0x3,0x15,0x12,0xd,0x19,0xf,0x17, + 0x1b,0xfd,0xf6,0x14,0x1f,0x0,0x0,0x0,0x0,0x1,0x0,0xa4,0x0,0x45,0x4,0x68,0x4,0x47,0x0,0x3d,0x0,0x6,0xb3,0x34,0x15,0x1,0x30,0x2b,0x0,0x16, + 0x15,0x14,0x7,0x6,0x6,0x23,0x23,0x3,0x21,0x32,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x21,0x7,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x37,0x23,0x22, + 0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x33,0x13,0x21,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x21,0x37,0x36,0x33,0x32,0x17,0x16,0x15,0x14,0x7,0x7,0x33, + 0x4,0x4e,0x1a,0x3,0x7,0x23,0x22,0xde,0xc4,0x1,0x76,0x1b,0x1a,0x3,0x7,0x23,0x22,0xfe,0x36,0x9d,0x1d,0x1e,0x15,0x13,0x20,0x12,0x68,0x8a,0x1b,0x1a, + 0x3,0x7,0x23,0x22,0xde,0xc4,0xfe,0x8a,0x1b,0x1a,0x3,0x7,0x23,0x22,0x1,0xcb,0x9c,0x1d,0x1f,0x15,0x13,0x1f,0x13,0x66,0x89,0x3,0x56,0x16,0x18,0xb, + 0xf,0x27,0x21,0xff,0x0,0x16,0x18,0xb,0xf,0x27,0x21,0xcc,0x25,0x10,0x18,0x18,0x12,0x18,0x87,0x16,0x18,0xb,0xf,0x27,0x21,0x1,0x0,0x16,0x18,0xb, + 0xf,0x27,0x21,0xcc,0x25,0x10,0x19,0x17,0x13,0x19,0x85,0x0,0x0,0x2,0x0,0xa7,0xff,0xe9,0x4,0x4a,0x4,0xb8,0x0,0x27,0x0,0x36,0x0,0x8,0xb5,0x2c, + 0x28,0x9,0x0,0x2,0x30,0x2b,0x0,0x33,0x32,0x16,0x16,0x15,0x14,0x7,0x6,0x4,0x23,0x22,0x26,0x26,0x35,0x34,0x37,0x3e,0x2,0x33,0x32,0x16,0x17,0x36, + 0x35,0x34,0x26,0x23,0x22,0x7,0x6,0x23,0x22,0x26,0x27,0x26,0x35,0x34,0x37,0x12,0x36,0x37,0x26,0x26,0x23,0x22,0x6,0x6,0x7,0x6,0x15,0x14,0x16,0x33, + 0x2,0x7b,0x5b,0x69,0xa8,0x63,0x49,0x59,0xfe,0xfc,0xb7,0x64,0x93,0x4f,0x2,0xa,0x77,0xbd,0x70,0x6e,0x9f,0x23,0x2f,0x7d,0x7b,0x3a,0x4d,0x4,0x9,0x10, + 0x17,0x8,0x4,0x27,0x47,0xa2,0x3c,0xb,0x79,0x64,0x4e,0x7a,0x4a,0x8,0x2,0x62,0x56,0x4,0xb8,0x55,0xb0,0x83,0x9c,0xca,0xf5,0xec,0x4e,0x8f,0x5e,0xe, + 0x1c,0x75,0xb5,0x63,0x69,0x5e,0x96,0x6f,0x7e,0x88,0x11,0x1,0x19,0x20,0xd,0xf,0x2a,0xe,0xfb,0xe4,0x72,0x78,0x62,0x7d,0x3d,0x72,0x4b,0x16,0x9,0x53, + 0x5d,0x0,0x0,0x0,0x0,0x5,0x0,0x42,0xff,0xea,0x4,0xce,0x4,0xb9,0x0,0xf,0x0,0x21,0x0,0x2f,0x0,0x3f,0x0,0x4d,0x0,0x62,0x40,0x5f,0x0,0x2, + 0x1,0x5,0x1,0x2,0x5,0x70,0x0,0x3,0x8,0x6,0x8,0x3,0x6,0x70,0x0,0x4,0x0,0x0,0x7,0x4,0x0,0x63,0xc,0x1,0x7,0xd,0x1,0x9,0x8,0x7, + 0x9,0x63,0xb,0x1,0x5,0x5,0x1,0x5b,0xa,0x1,0x1,0x1,0x16,0x4b,0x0,0x8,0x8,0x6,0x5b,0x0,0x6,0x6,0x17,0x6,0x4c,0x40,0x40,0x30,0x30,0x22, + 0x22,0x0,0x0,0x40,0x4d,0x40,0x4c,0x47,0x45,0x30,0x3f,0x30,0x3e,0x38,0x36,0x22,0x2f,0x22,0x2e,0x29,0x27,0x1b,0x19,0x12,0x10,0x0,0xf,0x0,0xe,0x26, + 0xe,0x7,0x15,0x2b,0x0,0x16,0x16,0x15,0x14,0x6,0x6,0x23,0x22,0x26,0x26,0x35,0x34,0x36,0x36,0x33,0x4,0x33,0x32,0x17,0x16,0x15,0x14,0x7,0x1,0x6, + 0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x1,0x24,0x6,0x6,0x15,0x14,0x16,0x33,0x32,0x36,0x36,0x35,0x34,0x26,0x23,0x0,0x16,0x16,0x15,0x14,0x6,0x6,0x23, + 0x22,0x26,0x26,0x35,0x34,0x36,0x36,0x33,0xe,0x2,0x15,0x14,0x16,0x33,0x32,0x36,0x36,0x35,0x34,0x26,0x23,0x2,0xe,0x6b,0x3d,0x5a,0x93,0x51,0x42,0x6b, + 0x3d,0x5a,0x93,0x51,0x2,0xad,0x17,0x18,0x15,0x11,0x20,0xfc,0x3,0x1a,0x17,0x1a,0x13,0x11,0x21,0x3,0xfc,0xfd,0x31,0x4f,0x31,0x44,0x35,0x2b,0x4f,0x31, + 0x45,0x34,0x2,0x1f,0x6b,0x3d,0x5a,0x93,0x51,0x42,0x6b,0x3d,0x5a,0x93,0x51,0x3c,0x4f,0x31,0x44,0x35,0x2b,0x4f,0x31,0x45,0x34,0x4,0xb9,0x40,0x6d,0x40, + 0x55,0x8f,0x53,0x3f,0x6d,0x41,0x55,0x8f,0x53,0x80,0x1b,0x16,0x14,0x1b,0x1a,0xfc,0xc2,0x16,0x1b,0x16,0x13,0x1b,0x1b,0x3,0x3e,0x14,0x2f,0x4e,0x2c,0x33, + 0x44,0x2f,0x4e,0x2c,0x33,0x44,0xfd,0xd7,0x40,0x6d,0x40,0x55,0x8f,0x53,0x3f,0x6d,0x41,0x55,0x8f,0x53,0x82,0x2f,0x4e,0x2c,0x33,0x44,0x2f,0x4e,0x2c,0x33, + 0x44,0x0,0x0,0x6,0x0,0x42,0xff,0xea,0x6,0x26,0x4,0xb9,0x0,0xf,0x0,0x21,0x0,0x2f,0x0,0x4b,0x0,0x59,0x0,0x67,0x0,0x7d,0x40,0x7a,0x48,0x1, + 0xb,0x8,0x3a,0x1,0x6,0x3,0x2,0x4a,0x0,0x2,0x1,0x5,0x1,0x2,0x5,0x70,0x0,0x3,0xa,0x6,0xa,0x3,0x6,0x70,0x0,0x4,0x0,0x0,0x8,0x4, + 0x0,0x63,0x10,0x9,0x2,0x8,0x12,0xd,0x11,0x3,0xb,0xa,0x8,0xb,0x63,0xf,0x1,0x5,0x5,0x1,0x5b,0xe,0x1,0x1,0x1,0x16,0x4b,0xc,0x1,0xa, + 0xa,0x6,0x5b,0x7,0x1,0x6,0x6,0x17,0x6,0x4c,0x5a,0x5a,0x4c,0x4c,0x30,0x30,0x22,0x22,0x0,0x0,0x5a,0x67,0x5a,0x66,0x61,0x5f,0x4c,0x59,0x4c,0x58, + 0x53,0x51,0x30,0x4b,0x30,0x4a,0x46,0x44,0x3e,0x3c,0x38,0x36,0x22,0x2f,0x22,0x2e,0x29,0x27,0x1b,0x19,0x12,0x10,0x0,0xf,0x0,0xe,0x26,0x13,0x7,0x15, + 0x2b,0x0,0x16,0x16,0x15,0x14,0x6,0x6,0x23,0x22,0x26,0x26,0x35,0x34,0x36,0x36,0x33,0x4,0x33,0x32,0x17,0x16,0x15,0x14,0x7,0x1,0x6,0x23,0x22,0x27, + 0x26,0x35,0x34,0x37,0x1,0x24,0x6,0x6,0x15,0x14,0x16,0x33,0x32,0x36,0x36,0x35,0x34,0x26,0x23,0x0,0x16,0x16,0x15,0x14,0x6,0x6,0x23,0x22,0x26,0x27, + 0x6,0x6,0x23,0x22,0x26,0x26,0x35,0x34,0x36,0x36,0x33,0x32,0x16,0x17,0x36,0x36,0x33,0x4,0x6,0x6,0x15,0x14,0x16,0x33,0x32,0x36,0x36,0x35,0x34,0x26, + 0x23,0x20,0x6,0x6,0x15,0x14,0x16,0x33,0x32,0x36,0x36,0x35,0x34,0x26,0x23,0x2,0xe,0x6b,0x3d,0x5a,0x93,0x51,0x42,0x6b,0x3d,0x5a,0x93,0x51,0x2,0xad, + 0x17,0x18,0x15,0x11,0x20,0xfc,0x3,0x1a,0x17,0x1a,0x13,0x11,0x21,0x3,0xfc,0xfd,0x31,0x4f,0x31,0x44,0x35,0x2b,0x4f,0x31,0x45,0x34,0x3,0xc3,0x6b,0x3d, + 0x5a,0x93,0x51,0x3e,0x63,0x1d,0x2b,0x79,0x42,0x42,0x6b,0x3d,0x5a,0x93,0x51,0x3e,0x63,0x1d,0x2c,0x78,0x42,0xfe,0x20,0x4f,0x31,0x44,0x35,0x2b,0x4f,0x31, + 0x45,0x34,0x1,0x79,0x4f,0x31,0x44,0x35,0x2b,0x4f,0x31,0x45,0x34,0x4,0xb9,0x40,0x6d,0x40,0x55,0x8f,0x53,0x3f,0x6d,0x41,0x55,0x8f,0x53,0x80,0x1b,0x16, + 0x14,0x1b,0x1a,0xfc,0xc2,0x16,0x1b,0x16,0x13,0x1b,0x1b,0x3,0x3e,0x14,0x2f,0x4e,0x2c,0x33,0x44,0x2f,0x4e,0x2c,0x33,0x44,0xfd,0xd7,0x40,0x6d,0x40,0x55, + 0x8f,0x53,0x39,0x31,0x31,0x39,0x3f,0x6d,0x41,0x55,0x8f,0x53,0x39,0x31,0x32,0x38,0x82,0x2f,0x4e,0x2c,0x33,0x44,0x2f,0x4e,0x2c,0x33,0x44,0x2f,0x4e,0x2c, + 0x33,0x44,0x2f,0x4e,0x2c,0x33,0x44,0x0,0x0,0x0,0x0,0x1,0x0,0xc7,0x0,0x8a,0x4,0x45,0x4,0x2,0x0,0x27,0x0,0x3e,0x40,0x3b,0x2,0x1,0x0,0x5, + 0x1d,0x9,0x2,0x1,0x0,0x16,0x1,0x2,0x1,0x3,0x4a,0x6,0x1,0x5,0x0,0x5,0x72,0x0,0x2,0x1,0x2,0x73,0x4,0x1,0x0,0x1,0x1,0x0,0x55,0x4, + 0x1,0x0,0x0,0x1,0x5a,0x3,0x1,0x1,0x0,0x1,0x4e,0x0,0x0,0x0,0x27,0x0,0x26,0x26,0x25,0x23,0x26,0x25,0x7,0x7,0x19,0x2b,0x0,0x16,0x15,0x14, + 0x7,0x3,0x21,0x32,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x21,0x3,0x6,0x6,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x21,0x22,0x26,0x35,0x34,0x37,0x36,0x36, + 0x33,0x21,0x13,0x36,0x36,0x33,0x2,0xf3,0x20,0x2,0x36,0x1,0x35,0x1b,0x1a,0x3,0x7,0x23,0x22,0xfe,0xcb,0x36,0x6,0x24,0x27,0x21,0x20,0x2,0x36,0xfe, + 0xcb,0x1b,0x1a,0x3,0x7,0x23,0x22,0x1,0x35,0x36,0x6,0x24,0x27,0x4,0x2,0x15,0x18,0x4,0xe,0xfe,0xcb,0x16,0x18,0xb,0xf,0x27,0x21,0xfe,0xcb,0x22, + 0x1d,0x15,0x18,0x4,0xe,0x1,0x35,0x16,0x18,0xb,0xf,0x27,0x21,0x1,0x35,0x22,0x1d,0x0,0x0,0x2,0x0,0x81,0x0,0x6e,0x4,0x61,0x4,0x66,0x0,0x27, + 0x0,0x39,0x0,0x57,0x40,0x54,0x2,0x1,0x0,0x5,0x1d,0x9,0x2,0x1,0x0,0x16,0x1,0x2,0x1,0x33,0x2a,0x2,0x6,0x7,0x4,0x4a,0x8,0x1,0x5,0x0, + 0x5,0x72,0x0,0x2,0x1,0x7,0x1,0x2,0x7,0x70,0x4,0x1,0x0,0x3,0x1,0x1,0x2,0x0,0x1,0x62,0x9,0x1,0x7,0x6,0x6,0x7,0x55,0x9,0x1,0x7, + 0x7,0x6,0x59,0x0,0x6,0x7,0x6,0x4d,0x28,0x28,0x0,0x0,0x28,0x39,0x28,0x37,0x31,0x2e,0x0,0x27,0x0,0x26,0x26,0x25,0x23,0x26,0x25,0xa,0x7,0x19, + 0x2b,0x0,0x16,0x15,0x14,0x7,0x7,0x21,0x32,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x21,0x7,0x6,0x6,0x23,0x22,0x26,0x35,0x34,0x37,0x37,0x21,0x22,0x26, + 0x35,0x34,0x37,0x36,0x36,0x33,0x21,0x37,0x36,0x36,0x33,0x0,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x21,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x21,0x3, + 0x5,0x20,0x2,0x2c,0x1,0x35,0x1b,0x1a,0x3,0x7,0x23,0x22,0xfe,0xcc,0x2c,0x6,0x24,0x27,0x21,0x20,0x2,0x2c,0xfe,0xca,0x1b,0x1a,0x3,0x7,0x23,0x22, + 0x1,0x35,0x2c,0x6,0x24,0x27,0x1,0x1,0x1a,0x3,0x7,0x23,0x22,0xfd,0x6,0x1b,0x1a,0x3,0x7,0x23,0x22,0x2,0xfa,0x4,0x66,0x15,0x18,0x4,0xe,0xf9, + 0x16,0x18,0xb,0xf,0x27,0x21,0xf9,0x22,0x1d,0x15,0x18,0x4,0xe,0xf9,0x16,0x18,0xb,0xf,0x27,0x21,0xf9,0x22,0x1d,0xfc,0x98,0x16,0x18,0xb,0xf,0x27, + 0x21,0x16,0x18,0xb,0xf,0x27,0x21,0x0,0x0,0x1,0x0,0x84,0xff,0xf6,0x4,0x8a,0x4,0xa3,0x0,0x1f,0x0,0x6,0xb3,0x7,0x0,0x1,0x30,0x2b,0x0,0x16, + 0x15,0x14,0x7,0x3,0x6,0x6,0x23,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x21,0x3,0x6,0x6,0x23,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x36,0x36,0x33,0x21, + 0x4,0x71,0x19,0x2,0xbc,0x6,0x27,0x28,0x2,0x23,0x1e,0x2,0xaf,0xfd,0xe6,0xaf,0x6,0x27,0x28,0x2,0x23,0x1e,0x2,0xbc,0x7,0x22,0x22,0x2,0xc8,0x4, + 0xa3,0x14,0x18,0xf,0x9,0xfb,0xd8,0x23,0x1e,0x15,0x18,0x4,0x10,0x3,0xdc,0xfc,0x24,0x23,0x1e,0x15,0x18,0x4,0x10,0x4,0x28,0x25,0x1f,0x0,0x0,0x0, + 0x0,0x1,0x0,0x95,0xff,0xea,0x4,0xd1,0x4,0xad,0x0,0x1f,0x0,0x6,0xb3,0x1c,0x7,0x1,0x30,0x2b,0x0,0x16,0x15,0x14,0x7,0x1,0x6,0x6,0x23,0x22, + 0x26,0x27,0x3,0x23,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x33,0x32,0x16,0x17,0x13,0x1,0x36,0x33,0x32,0x17,0x4,0xbb,0x16,0xc,0xfd,0xce,0x13,0x33, + 0x2b,0x2b,0x2e,0x7,0x6f,0x89,0x1b,0x1a,0x3,0x7,0x22,0x22,0xbc,0x17,0x19,0x4,0x68,0x2,0x1,0x16,0x23,0x14,0x1a,0x4,0x96,0x17,0x10,0x11,0x17,0xfb, + 0xe3,0x23,0x1d,0x1f,0x22,0x1,0xd3,0x15,0x18,0xc,0xf,0x27,0x21,0xf,0x11,0xfe,0x48,0x3,0xcd,0x2a,0xc,0x0,0x0,0x3,0x0,0x69,0xfc,0xba,0x4,0x7d, + 0x7,0x5,0x0,0x3,0x0,0x2d,0x0,0x31,0x0,0xa,0xb7,0x30,0x2e,0x1a,0x4,0x2,0x0,0x3,0x30,0x2b,0x1,0x23,0x35,0x33,0x0,0x16,0x15,0x14,0x7,0x6, + 0x6,0x23,0x21,0x1,0x16,0x15,0x14,0x7,0x1,0x21,0x32,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x21,0x22,0x26,0x35,0x34,0x37,0x36,0x37,0x1,0x1,0x26,0x35, + 0x34,0x37,0x36,0x36,0x33,0x21,0x1,0x23,0x35,0x33,0x3,0x5d,0x1,0x1,0x1,0x7,0x19,0x2,0x8,0x22,0x22,0xfd,0xc9,0x1,0x8,0x8,0xd,0xfe,0x62,0x2, + 0x47,0x1c,0x19,0x2,0x7,0x23,0x22,0xfd,0x15,0x1c,0x1b,0x2,0x6,0x13,0x1,0xdb,0xfe,0xdd,0x6,0x2,0x7,0x24,0x22,0x2,0xc3,0xfd,0x44,0x1,0x1,0x7, + 0x4,0x1,0xfd,0x9e,0x16,0x19,0xf,0xa,0x27,0x21,0xfe,0x5f,0xe,0xe,0x13,0xe,0xfe,0x5b,0x16,0x19,0xf,0xa,0x27,0x21,0x15,0x18,0xe,0x9,0x20,0x15, + 0x1,0xe6,0x1,0xcf,0x8,0x11,0x6,0x12,0x25,0x1f,0xf8,0x17,0x1,0x0,0x0,0x2,0x0,0xf0,0xff,0xea,0x4,0x20,0x4,0xb9,0x0,0x17,0x0,0x1b,0x0,0x8, + 0xb5,0x1b,0x19,0xa,0x0,0x2,0x30,0x2b,0x0,0x16,0x17,0x13,0x16,0x15,0x14,0x7,0x1,0x6,0x6,0x23,0x22,0x26,0x27,0x3,0x26,0x35,0x34,0x37,0x1,0x36, + 0x36,0x33,0x1,0x13,0x1,0x3,0x3,0x16,0x29,0xd,0xd0,0x4,0xf,0xfe,0x89,0x1a,0x33,0x29,0x29,0x27,0xe,0xd2,0x4,0xf,0x1,0x79,0x19,0x33,0x29,0xfe, + 0xa6,0xa9,0x1,0x43,0xaa,0x4,0xb9,0x1e,0x23,0xfd,0xe9,0xe,0x6,0x14,0x14,0xfe,0x6,0x23,0x1e,0x1e,0x23,0x2,0x16,0xe,0x6,0x15,0x13,0x1,0xfb,0x23, + 0x1e,0xfd,0x98,0xfe,0x4d,0x1,0xb3,0x1,0xb4,0x0,0x0,0x1,0x1,0x95,0xfe,0x61,0x3,0x4c,0x5,0x36,0x0,0x11,0x0,0x20,0x40,0x1d,0xb,0x2,0x2,0x0, + 0x1,0x1,0x4a,0x2,0x1,0x1,0x1,0x10,0x4b,0x0,0x0,0x0,0x13,0x0,0x4c,0x0,0x0,0x0,0x11,0x0,0x10,0x27,0x3,0x7,0x15,0x2b,0x0,0x16,0x15,0x14, + 0x7,0x1,0x6,0x6,0x23,0x22,0x26,0x35,0x34,0x37,0x1,0x36,0x36,0x33,0x3,0x2e,0x1e,0x2,0xfe,0xe3,0x6,0x27,0x2a,0x23,0x1e,0x2,0x1,0x1d,0x6,0x27, + 0x2a,0x5,0x36,0x15,0x18,0x4,0x10,0xf9,0xad,0x23,0x1e,0x15,0x18,0x4,0x10,0x6,0x53,0x23,0x1e,0x0,0x0,0x2,0x1,0x95,0xfe,0x61,0x3,0x4c,0x5,0x36, + 0x0,0x11,0x0,0x23,0x0,0x3d,0x40,0x3a,0xb,0x2,0x2,0x0,0x1,0x1d,0x14,0x2,0x2,0x3,0x2,0x4a,0x0,0x0,0x1,0x3,0x1,0x0,0x3,0x70,0x5,0x1, + 0x3,0x2,0x1,0x3,0x2,0x6e,0x4,0x1,0x1,0x1,0x10,0x4b,0x0,0x2,0x2,0x13,0x2,0x4c,0x12,0x12,0x0,0x0,0x12,0x23,0x12,0x22,0x1b,0x19,0x0,0x11, + 0x0,0x10,0x27,0x6,0x7,0x15,0x2b,0x0,0x16,0x15,0x14,0x7,0x3,0x6,0x6,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x36,0x36,0x33,0x2,0x16,0x15,0x14,0x7, + 0x3,0x6,0x6,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x36,0x36,0x33,0x3,0x2e,0x1e,0x2,0x6d,0x6,0x27,0x29,0x23,0x1f,0x2,0x6d,0x6,0x27,0x2a,0x8e,0x1f, + 0x2,0x6d,0x6,0x27,0x2a,0x23,0x1e,0x2,0x6d,0x6,0x27,0x29,0x5,0x36,0x15,0x18,0x4,0x10,0xfd,0x95,0x23,0x1e,0x15,0x18,0x6,0xe,0x2,0x6b,0x23,0x1e, + 0xfc,0x18,0x15,0x18,0x6,0xe,0xfd,0x95,0x23,0x1e,0x15,0x18,0x4,0x10,0x2,0x6b,0x23,0x1e,0x0,0x0,0x0,0x2,0x0,0x26,0xff,0x67,0x4,0xb2,0x4,0x36, + 0x0,0x49,0x0,0x57,0x0,0xc2,0x4b,0xb0,0x15,0x50,0x58,0x40,0xf,0x1d,0x16,0x2,0xb,0x2,0xa,0x1,0x4,0xb,0x3c,0x1,0x8,0x6,0x3,0x4a,0x1b,0x40, + 0xf,0x1d,0x16,0x2,0xb,0x3,0xa,0x1,0x4,0xb,0x3c,0x1,0x8,0x6,0x3,0x4a,0x59,0x4b,0xb0,0x15,0x50,0x58,0x40,0x35,0x0,0x7,0x0,0x6,0x0,0x7, + 0x6,0x70,0xc,0x1,0x9,0x0,0x5,0x2,0x9,0x5,0x63,0x3,0x1,0x2,0xd,0x1,0xb,0x4,0x2,0xb,0x63,0xa,0x1,0x4,0x1,0x1,0x0,0x7,0x4,0x0, + 0x64,0x0,0x6,0x8,0x8,0x6,0x57,0x0,0x6,0x6,0x8,0x5b,0x0,0x8,0x6,0x8,0x4f,0x1b,0x40,0x3c,0x0,0x3,0x2,0xb,0x2,0x3,0xb,0x70,0x0,0x7, + 0x0,0x6,0x0,0x7,0x6,0x70,0xc,0x1,0x9,0x0,0x5,0x2,0x9,0x5,0x63,0x0,0x2,0xd,0x1,0xb,0x4,0x2,0xb,0x63,0xa,0x1,0x4,0x1,0x1,0x0, + 0x7,0x4,0x0,0x64,0x0,0x6,0x8,0x8,0x6,0x57,0x0,0x6,0x6,0x8,0x5b,0x0,0x8,0x6,0x8,0x4f,0x59,0x40,0x1a,0x4a,0x4a,0x0,0x0,0x4a,0x57,0x4a, + 0x56,0x51,0x4f,0x0,0x49,0x0,0x48,0x27,0x32,0x25,0x25,0x2a,0x23,0x25,0x24,0x26,0xe,0x7,0x1d,0x2b,0x0,0x16,0x16,0x15,0x14,0x6,0x6,0x23,0x22,0x26, + 0x27,0x6,0x6,0x23,0x22,0x26,0x35,0x34,0x36,0x36,0x33,0x32,0x17,0x37,0x36,0x33,0x32,0x17,0x16,0x15,0x14,0x7,0x3,0x6,0x15,0x14,0x16,0x33,0x32,0x36, + 0x36,0x35,0x34,0x26,0x23,0x22,0x4,0x2,0x15,0x14,0x16,0x33,0x32,0x36,0x37,0x36,0x33,0x32,0x17,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x22,0x26,0x26,0x35, + 0x34,0x12,0x24,0x33,0x2,0x6,0x6,0x15,0x14,0x16,0x33,0x32,0x36,0x36,0x35,0x34,0x26,0x23,0x3,0x87,0xc3,0x68,0x58,0xa1,0x6a,0x40,0x4e,0xe,0x3b,0x6d, + 0x3d,0x42,0x56,0x78,0xba,0x5c,0x56,0x18,0x2a,0x9,0x12,0xe,0x13,0x21,0x2,0x82,0x12,0x20,0x1d,0x45,0x63,0x32,0xad,0x9c,0xa5,0xfe,0xf0,0x9b,0xa6,0x9d, + 0x45,0x89,0x38,0x8,0x4,0x19,0x9,0x3,0x19,0x3a,0x9c,0x51,0x89,0xc5,0x67,0xc0,0x1,0x50,0xcc,0x83,0x70,0x4f,0x1c,0x18,0x3b,0x70,0x48,0x20,0x1d,0x4, + 0x36,0x6e,0xc6,0x81,0x80,0xe8,0x90,0x43,0x3f,0x43,0x44,0x5b,0x50,0x5e,0xe5,0x9f,0x64,0x3f,0xc,0x8,0xd,0x16,0x7,0x4,0xfe,0xd2,0x2c,0x21,0x1e,0x22, + 0x75,0xad,0x51,0x97,0xaa,0xcb,0xfe,0xc7,0x9d,0x94,0xa4,0x14,0x11,0x2,0x2c,0xe,0xa,0x20,0xa,0x18,0x1c,0x66,0xbf,0x82,0xc8,0x1,0x76,0xea,0xfe,0x5c, + 0x6f,0x9d,0x3f,0x1f,0x1e,0x75,0x9d,0x32,0x21,0x23,0x0,0x2,0x0,0x8c,0xff,0xea,0x4,0x43,0x4,0xf5,0x0,0x3b,0x0,0x43,0x0,0x41,0x40,0x3e,0x3e,0x3d, + 0x34,0x19,0x10,0x6,0x6,0x6,0x5,0x1,0x4a,0x0,0x3,0x4,0x5,0x4,0x3,0x5,0x70,0x0,0x5,0x6,0x4,0x5,0x6,0x6e,0x0,0x2,0x0,0x4,0x3,0x2, + 0x4,0x63,0x7,0x1,0x6,0x6,0x0,0x5b,0x1,0x1,0x0,0x0,0x17,0x0,0x4c,0x3c,0x3c,0x3c,0x43,0x3c,0x42,0x1a,0x24,0x17,0x2b,0x23,0x2c,0x8,0x7,0x1a, + 0x2b,0x0,0x16,0x15,0x14,0x7,0x6,0x7,0x17,0x16,0x15,0x14,0x7,0x6,0x23,0x22,0x27,0x27,0x6,0x23,0x22,0x26,0x26,0x35,0x34,0x36,0x37,0x26,0x35,0x34, + 0x36,0x36,0x33,0x32,0x16,0x17,0x16,0x15,0x14,0x7,0x6,0x23,0x22,0x27,0x26,0x26,0x23,0x22,0x6,0x6,0x15,0x14,0x17,0x13,0x36,0x37,0x36,0x36,0x33,0x32, + 0x17,0x0,0x37,0x3,0x6,0x15,0x14,0x16,0x33,0x4,0x29,0x1a,0xa,0x55,0x65,0x76,0xe,0x2f,0x20,0x17,0x19,0x12,0x59,0xa0,0xbf,0x5f,0x89,0x46,0x8e,0x7d, + 0x3d,0x5e,0xb3,0x7a,0x55,0xa1,0x3e,0x19,0x16,0x1c,0x1b,0xd,0x11,0x34,0x77,0x3e,0x42,0x6a,0x3c,0x48,0xe7,0x47,0x3d,0xb,0x1d,0x16,0xd,0x17,0xfe,0x3d, + 0x7b,0xde,0xbd,0x54,0x4a,0x2,0x9a,0x15,0x11,0xe,0x17,0xcb,0x88,0xa0,0x13,0xf,0x1c,0x19,0x12,0x18,0x7a,0x9b,0x4e,0x85,0x52,0x7c,0xbf,0x4c,0x6a,0x73, + 0x65,0xb1,0x6c,0x31,0x28,0x10,0x18,0x18,0x20,0x2a,0xa,0x20,0x25,0x3d,0x6e,0x46,0x60,0x61,0xfe,0xc7,0x6a,0x96,0x1a,0x17,0x5,0xfd,0xdc,0x7b,0x1,0x2e, + 0x70,0x9d,0x49,0x53,0x0,0x1,0x0,0xcd,0xfe,0x66,0x4,0xde,0x4,0xa3,0x0,0x26,0x0,0x37,0x40,0x34,0x2,0x1,0x0,0x5,0x1a,0xf,0x2,0x1,0x4,0x2, + 0x4a,0x0,0x4,0x0,0x1,0x0,0x4,0x1,0x70,0x2,0x1,0x0,0x0,0x5,0x59,0x6,0x1,0x5,0x5,0xe,0x4b,0x3,0x1,0x1,0x1,0x13,0x1,0x4c,0x0,0x0, + 0x0,0x26,0x0,0x24,0x15,0x23,0x15,0x23,0x26,0x7,0x7,0x19,0x2b,0x0,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x23,0x3,0x6,0x6,0x23,0x22,0x26,0x35,0x34, + 0x37,0x13,0x23,0x3,0x6,0x6,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x2e,0x2,0x35,0x34,0x36,0x36,0x33,0x21,0x4,0xc4,0x1a,0x3,0x7,0x23,0x22,0x44,0xf4, + 0x6,0x27,0x2a,0x23,0x1e,0x2,0xf4,0xa0,0xf4,0x6,0x27,0x2a,0x23,0x1e,0x2,0x95,0x67,0x99,0x53,0x6b,0xd5,0x98,0x2,0x4,0x4,0xa3,0x16,0x18,0xb,0xf, + 0x27,0x21,0xfa,0x94,0x23,0x1e,0x15,0x18,0x4,0x10,0x5,0x6c,0xfa,0x94,0x23,0x1e,0x15,0x18,0x4,0x10,0x3,0x4f,0x3,0x47,0x80,0x55,0x6b,0xb6,0x6d,0x0, + 0x0,0x3,0x0,0x3d,0xff,0xea,0x4,0xd3,0x4,0xb9,0x0,0xf,0x0,0x1f,0x0,0x50,0x0,0x6d,0xb1,0x6,0x64,0x44,0x40,0x62,0x24,0x1,0x5,0x9,0x1,0x4a, + 0x0,0x4,0x5,0x7,0x5,0x4,0x7,0x70,0x0,0x7,0x6,0x5,0x7,0x6,0x6e,0xa,0x1,0x1,0xb,0x1,0x3,0x9,0x1,0x3,0x63,0xc,0x1,0x9,0x0,0x5, + 0x4,0x9,0x5,0x63,0x0,0x6,0x0,0x8,0x2,0x6,0x8,0x63,0x0,0x2,0x0,0x0,0x2,0x57,0x0,0x2,0x2,0x0,0x5b,0x0,0x0,0x2,0x0,0x4f,0x20,0x20, + 0x10,0x10,0x0,0x0,0x20,0x50,0x20,0x4f,0x47,0x45,0x3c,0x3a,0x37,0x35,0x2e,0x2c,0x28,0x27,0x10,0x1f,0x10,0x1e,0x18,0x16,0x0,0xf,0x0,0xe,0x26,0xd, + 0x7,0x15,0x2b,0xb1,0x6,0x0,0x44,0x0,0x16,0x16,0x15,0x14,0x2,0x4,0x23,0x22,0x26,0x26,0x35,0x34,0x12,0x24,0x33,0x6,0x4,0x2,0x15,0x14,0x16,0x16, + 0x33,0x32,0x36,0x12,0x35,0x34,0x26,0x26,0x23,0x16,0x16,0x17,0x16,0x15,0x14,0x7,0x6,0x23,0x22,0x27,0x26,0x26,0x23,0x22,0x6,0x7,0x6,0x6,0x15,0x14, + 0x16,0x33,0x32,0x36,0x37,0x36,0x33,0x32,0x16,0x17,0x16,0x15,0x14,0x6,0x7,0x6,0x6,0x23,0x22,0x26,0x26,0x35,0x34,0x36,0x37,0x36,0x36,0x33,0x3,0x7a, + 0xdf,0x7a,0xb9,0xfe,0xc6,0xb7,0x92,0xe0,0x7a,0xba,0x1,0x3a,0xb7,0x9b,0xff,0x1,0x98,0x5c,0xab,0x74,0x94,0xfe,0x97,0x5b,0xab,0x74,0x17,0x59,0x27,0x24, + 0xa,0x13,0x1e,0xe,0xe,0x22,0x3e,0x26,0x2e,0x51,0x1f,0x20,0x24,0x52,0x43,0x26,0x43,0x28,0xf,0xe,0xe,0x13,0x7,0x5,0x15,0x18,0x2d,0x61,0x2d,0x4f, + 0x7a,0x43,0x39,0x37,0x31,0x84,0x4d,0x4,0xb9,0x84,0xe5,0x8f,0xc4,0xfe,0xb2,0xc5,0x84,0xe7,0x8f,0xc4,0x1,0x4d,0xc4,0x78,0xa0,0xfe,0xec,0xa6,0x73,0xb0, + 0x62,0xa1,0x1,0x15,0xa6,0x72,0xb0,0x61,0x98,0x15,0x13,0x13,0x1d,0x11,0x14,0x28,0x7,0x11,0xf,0x26,0x26,0x27,0x6b,0x36,0x4d,0x53,0xf,0x11,0x7,0x14, + 0x16,0x12,0xc,0x12,0x19,0xa,0x13,0x15,0x43,0x7e,0x54,0x51,0xa4,0x3a,0x35,0x37,0x0,0x0,0x0,0x4,0x0,0x3d,0xff,0xea,0x4,0xd3,0x4,0xb9,0x0,0xf, + 0x0,0x1f,0x0,0x40,0x0,0x49,0x0,0x70,0xb1,0x6,0x64,0x44,0x40,0x65,0x26,0x1,0x5,0x8,0x39,0x1,0x4,0x5,0x2,0x4a,0x6,0x1,0x4,0x5,0x2,0x5, + 0x4,0x2,0x70,0xa,0x1,0x1,0xb,0x1,0x3,0x7,0x1,0x3,0x63,0xc,0x1,0x7,0xd,0x1,0x9,0x8,0x7,0x9,0x63,0x0,0x8,0x0,0x5,0x4,0x8,0x5, + 0x61,0x0,0x2,0x0,0x0,0x2,0x57,0x0,0x2,0x2,0x0,0x5b,0x0,0x0,0x2,0x0,0x4f,0x41,0x41,0x20,0x20,0x10,0x10,0x0,0x0,0x41,0x49,0x41,0x48,0x44, + 0x42,0x20,0x40,0x20,0x3e,0x37,0x35,0x32,0x31,0x2e,0x2c,0x10,0x1f,0x10,0x1e,0x18,0x16,0x0,0xf,0x0,0xe,0x26,0xe,0x7,0x15,0x2b,0xb1,0x6,0x0,0x44, + 0x0,0x16,0x16,0x15,0x14,0x2,0x4,0x23,0x22,0x26,0x26,0x35,0x34,0x12,0x24,0x33,0x6,0x4,0x2,0x15,0x14,0x16,0x16,0x33,0x32,0x36,0x12,0x35,0x34,0x26, + 0x26,0x23,0x1e,0x2,0x15,0x14,0x6,0x7,0x17,0x16,0x15,0x14,0x7,0x6,0x23,0x22,0x26,0x27,0x27,0x23,0x7,0x6,0x6,0x23,0x22,0x26,0x35,0x34,0x37,0x13, + 0x36,0x36,0x33,0x33,0x7,0x7,0x33,0x32,0x36,0x35,0x34,0x26,0x23,0x3,0x7a,0xdf,0x7a,0xb9,0xfe,0xc6,0xb7,0x92,0xe0,0x7a,0xba,0x1,0x3a,0xb7,0x9b,0xff, + 0x1,0x98,0x5c,0xab,0x74,0x94,0xfe,0x97,0x5b,0xab,0x74,0x2f,0x61,0x35,0x5f,0x57,0x5b,0x10,0x21,0x1b,0x10,0xb,0x10,0x9,0x7c,0x65,0x17,0x6,0x23,0x22, + 0x1c,0x17,0x2,0x5b,0x6,0x24,0x22,0xb1,0x8a,0x26,0x6a,0x4c,0x4f,0x36,0x36,0x4,0xb9,0x84,0xe5,0x8f,0xc4,0xfe,0xb2,0xc5,0x84,0xe7,0x8f,0xc4,0x1,0x4d, + 0xc4,0x78,0xa0,0xfe,0xec,0xa6,0x73,0xb0,0x62,0xa1,0x1,0x15,0xa6,0x72,0xb0,0x61,0xac,0x31,0x57,0x37,0x54,0x7b,0x1c,0x7a,0x16,0x10,0x17,0x17,0x12,0xc, + 0xc,0xb3,0x87,0x23,0x1e,0x13,0x16,0xf,0x9,0x2,0x7,0x22,0x1d,0x75,0xd5,0x4b,0x38,0x27,0x2b,0x0,0x0,0x2,0x0,0xa9,0xff,0x1a,0x4,0x3f,0x4,0xa3, + 0x0,0x37,0x0,0x47,0x0,0x33,0x40,0x30,0x2,0x1,0x0,0x3,0x42,0x3a,0x2,0x2,0x0,0x1e,0x1,0x1,0x2,0x3,0x4a,0x0,0x2,0x0,0x1,0x2,0x1,0x5d, + 0x0,0x0,0x0,0x3,0x59,0x4,0x1,0x3,0x3,0xe,0x0,0x4c,0x0,0x0,0x0,0x37,0x0,0x35,0x25,0x22,0x1c,0x19,0x36,0x5,0x7,0x15,0x2b,0x0,0x16,0x15, + 0x14,0x7,0x6,0x6,0x23,0x21,0x22,0x6,0x15,0x14,0x16,0x17,0x5,0x16,0x16,0x15,0x14,0x6,0x7,0x16,0x15,0x14,0x6,0x23,0x21,0x22,0x26,0x35,0x34,0x37, + 0x36,0x36,0x33,0x21,0x32,0x36,0x35,0x34,0x26,0x27,0x25,0x26,0x26,0x35,0x34,0x36,0x37,0x26,0x35,0x34,0x36,0x33,0x21,0x1,0x26,0x27,0x6,0x6,0x15,0x14, + 0x17,0x5,0x16,0x17,0x36,0x36,0x35,0x34,0x27,0x4,0x25,0x1a,0x3,0x7,0x22,0x22,0xfe,0xce,0x51,0x51,0x20,0x23,0x1,0x1f,0x3f,0x42,0x52,0x51,0x7,0xa2, + 0x94,0xfe,0xb0,0x1b,0x1a,0x3,0x7,0x22,0x22,0x1,0x32,0x51,0x51,0x20,0x23,0xfe,0xe1,0x3f,0x42,0x52,0x51,0x7,0xa2,0x94,0x1,0x50,0xfd,0xec,0x1a,0x10, + 0x25,0x27,0x49,0x1,0x29,0x1a,0x10,0x25,0x27,0x49,0x4,0xa3,0x15,0x18,0xc,0xf,0x27,0x21,0x43,0x3f,0x21,0x34,0x18,0xc8,0x2c,0x75,0x46,0x59,0x7a,0x2e, + 0x23,0x1c,0x83,0x98,0x15,0x18,0xc,0xf,0x27,0x21,0x43,0x3e,0x21,0x34,0x19,0xc8,0x2c,0x75,0x46,0x59,0x7a,0x2e,0x1b,0x23,0x84,0x98,0xfe,0x10,0x13,0x10, + 0x18,0x3f,0x2d,0x48,0x32,0xce,0x13,0x10,0x18,0x3e,0x2d,0x49,0x32,0x0,0x0,0x2,0x0,0xd0,0x2,0xec,0x5,0x71,0x5,0x2a,0x0,0x28,0x0,0x45,0x0,0x8, + 0xb5,0x34,0x29,0x6,0x0,0x2,0x30,0x2b,0x0,0x16,0x15,0x7,0x3,0x6,0x6,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x7,0x6,0x23,0x22,0x26,0x27,0x27,0x3, + 0x6,0x6,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x36,0x36,0x33,0x32,0x16,0x17,0x13,0x13,0x36,0x33,0x4,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x23,0x3,0x6, + 0x6,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x23,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x21,0x5,0x52,0x1f,0x1,0x30,0x4,0x1f,0x22,0x1d,0x17,0x1,0x1c, + 0x7f,0x21,0x2e,0x17,0x23,0x7,0x3c,0x3f,0x8,0x21,0x22,0x1a,0x17,0x4,0x6c,0x8,0x23,0x22,0x19,0x1b,0x4,0x59,0xc3,0x10,0x34,0xfd,0x83,0x1a,0x2,0x6, + 0x21,0x22,0x81,0x43,0x6,0x22,0x22,0x1c,0x18,0x2,0x43,0x81,0x1d,0x1a,0x2,0x6,0x22,0x22,0x1,0x7e,0x5,0x2a,0x19,0x1b,0xd,0xfe,0x44,0x23,0x1e,0x16, + 0x18,0xc,0x7,0x1,0x3,0xc0,0x30,0x19,0x17,0xc0,0xfe,0xfd,0x23,0x1e,0x11,0x13,0xe,0xf,0x1,0xbc,0x23,0x1e,0xc,0xe,0xfe,0xd5,0x1,0x2b,0x1a,0xa, + 0x12,0x16,0x4,0x10,0x21,0x1a,0xfe,0x84,0x23,0x1e,0x14,0x16,0xe,0x9,0x1,0x7c,0x12,0x15,0x6,0xe,0x21,0x1b,0x0,0x0,0x0,0x2,0x1,0x51,0x2,0x89, + 0x3,0x85,0x4,0xb9,0x0,0xf,0x0,0x1d,0x0,0x37,0xb1,0x6,0x64,0x44,0x40,0x2c,0x4,0x1,0x1,0x5,0x1,0x3,0x2,0x1,0x3,0x63,0x0,0x2,0x0,0x0, + 0x2,0x57,0x0,0x2,0x2,0x0,0x5b,0x0,0x0,0x2,0x0,0x4f,0x10,0x10,0x0,0x0,0x10,0x1d,0x10,0x1c,0x17,0x15,0x0,0xf,0x0,0xe,0x26,0x6,0x7,0x15, + 0x2b,0xb1,0x6,0x0,0x44,0x0,0x16,0x16,0x15,0x14,0x6,0x6,0x23,0x22,0x26,0x26,0x35,0x34,0x36,0x36,0x33,0xe,0x2,0x15,0x14,0x16,0x33,0x32,0x36,0x36, + 0x35,0x34,0x26,0x23,0x2,0xd2,0x71,0x42,0x50,0x90,0x5b,0x45,0x72,0x42,0x50,0x91,0x5b,0x3c,0x4d,0x2c,0x43,0x3b,0x30,0x4c,0x2c,0x43,0x3a,0x4,0xb9,0x3e, + 0x6f,0x47,0x54,0x91,0x57,0x3f,0x6f,0x47,0x52,0x91,0x58,0x87,0x30,0x50,0x2c,0x34,0x42,0x30,0x4f,0x2d,0x34,0x42,0x0,0x1,0x1,0x4,0x1,0xf6,0x4,0x1, + 0x4,0xb9,0x0,0x1c,0x0,0x27,0xb1,0x6,0x64,0x44,0x40,0x1c,0xd,0x1,0x0,0x2,0x1,0x4a,0x3,0x1,0x2,0x0,0x2,0x72,0x1,0x1,0x0,0x0,0x69,0x0, + 0x0,0x0,0x1c,0x0,0x1b,0x15,0x29,0x4,0x7,0x16,0x2b,0xb1,0x6,0x0,0x44,0x0,0x16,0x17,0x13,0x16,0x15,0x14,0x6,0x7,0x6,0x23,0x22,0x27,0x3,0x1, + 0x6,0x6,0x23,0x22,0x27,0x26,0x26,0x35,0x34,0x37,0x1,0x36,0x36,0x33,0x3,0x11,0x1a,0x7,0xcc,0x3,0x1f,0x17,0x14,0x16,0x27,0x8,0x9a,0xfe,0xc0,0x8, + 0x21,0x14,0x12,0x12,0x16,0x1d,0xa,0x1,0x9c,0xe,0x21,0x1c,0x4,0xb9,0x12,0x13,0xfd,0xb1,0x7,0x8,0x12,0x1e,0x8,0x7,0x18,0x1,0xe0,0xfe,0x20,0xd, + 0xc,0x5,0x6,0x19,0x10,0xd,0xe,0x2,0x4f,0x13,0x12,0x0,0x0,0x1,0x1,0x6f,0x1,0x19,0x3,0xfc,0x4,0xb9,0x0,0x27,0x0,0x38,0x40,0x35,0x2,0x1, + 0x0,0x5,0x1d,0x9,0x2,0x1,0x0,0x16,0x1,0x2,0x1,0x3,0x4a,0x0,0x2,0x1,0x2,0x73,0x6,0x1,0x5,0x5,0x16,0x4b,0x3,0x1,0x1,0x1,0x0,0x59, + 0x4,0x1,0x0,0x0,0x11,0x1,0x4c,0x0,0x0,0x0,0x27,0x0,0x26,0x26,0x25,0x23,0x26,0x25,0x7,0x7,0x19,0x2b,0x0,0x16,0x15,0x14,0x7,0x7,0x33,0x32, + 0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x23,0x3,0x6,0x6,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x23,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x33,0x37,0x36, + 0x36,0x33,0x3,0x14,0x20,0x2,0x28,0xbd,0x1c,0x19,0x2,0x7,0x23,0x22,0xbd,0x4d,0x6,0x24,0x27,0x21,0x20,0x2,0x4d,0xbd,0x1b,0x1a,0x3,0x7,0x22,0x22, + 0xbd,0x28,0x6,0x24,0x27,0x4,0xb9,0x15,0x18,0x4,0xe,0xde,0x16,0x19,0xf,0xa,0x27,0x21,0xfe,0x4c,0x22,0x1d,0x15,0x18,0x4,0xe,0x1,0xb4,0x16,0x18, + 0xb,0xf,0x27,0x21,0xde,0x22,0x1d,0x0,0x0,0x1,0x1,0x36,0x0,0xdd,0x4,0x5,0x4,0xb9,0x0,0x3d,0x0,0xf6,0x40,0x14,0x2,0x1,0x0,0x9,0x33,0x9, + 0x2,0x1,0x0,0x28,0x14,0x2,0x3,0x2,0x21,0x1,0x4,0x3,0x4,0x4a,0x4b,0xb0,0xc,0x50,0x58,0x40,0x21,0x0,0x4,0x3,0x3,0x4,0x67,0x8,0x1,0x0, + 0x7,0x1,0x1,0x2,0x0,0x1,0x62,0x6,0x1,0x2,0x5,0x1,0x3,0x4,0x2,0x3,0x61,0xa,0x1,0x9,0x9,0x16,0x9,0x4c,0x1b,0x4b,0xb0,0xe,0x50,0x58, + 0x40,0x22,0x0,0x4,0x3,0x4,0x73,0x6,0x1,0x2,0x5,0x1,0x3,0x4,0x2,0x3,0x61,0xa,0x1,0x9,0x9,0x16,0x4b,0x7,0x1,0x1,0x1,0x0,0x59,0x8, + 0x1,0x0,0x0,0x11,0x1,0x4c,0x1b,0x4b,0xb0,0x10,0x50,0x58,0x40,0x20,0x0,0x4,0x3,0x4,0x73,0x8,0x1,0x0,0x7,0x1,0x1,0x2,0x0,0x1,0x62,0x6, + 0x1,0x2,0x5,0x1,0x3,0x4,0x2,0x3,0x61,0xa,0x1,0x9,0x9,0x16,0x9,0x4c,0x1b,0x4b,0xb0,0x15,0x50,0x58,0x40,0x22,0x0,0x4,0x3,0x4,0x73,0x6, + 0x1,0x2,0x5,0x1,0x3,0x4,0x2,0x3,0x61,0xa,0x1,0x9,0x9,0x16,0x4b,0x7,0x1,0x1,0x1,0x0,0x59,0x8,0x1,0x0,0x0,0x11,0x1,0x4c,0x1b,0x40, + 0x20,0x0,0x4,0x3,0x4,0x73,0x8,0x1,0x0,0x7,0x1,0x1,0x2,0x0,0x1,0x62,0x6,0x1,0x2,0x5,0x1,0x3,0x4,0x2,0x3,0x61,0xa,0x1,0x9,0x9, + 0x16,0x9,0x4c,0x59,0x59,0x59,0x59,0x40,0x12,0x0,0x0,0x0,0x3d,0x0,0x3c,0x26,0x21,0x26,0x25,0x23,0x26,0x21,0x26,0x25,0xb,0x7,0x1d,0x2b,0x0,0x16, + 0x15,0x14,0x7,0x7,0x33,0x32,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x23,0x7,0x33,0x32,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x23,0x7,0x6,0x6,0x23,0x22, + 0x26,0x35,0x34,0x37,0x37,0x23,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x33,0x37,0x23,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x33,0x37,0x36,0x36,0x33, + 0x3,0x14,0x20,0x2,0x1f,0xbd,0x1c,0x19,0x2,0x8,0x22,0x22,0xbd,0x29,0xbd,0x1c,0x19,0x2,0x8,0x22,0x22,0xbd,0x1e,0x6,0x25,0x27,0x21,0x1f,0x2,0x1e, + 0xbd,0x1b,0x1a,0x3,0x7,0x22,0x22,0xbd,0x29,0xbd,0x1b,0x1a,0x3,0x7,0x22,0x22,0xbd,0x1f,0x6,0x24,0x27,0x4,0xb9,0x15,0x18,0x4,0xe,0xac,0x16,0x19, + 0xf,0xa,0x27,0x21,0xe6,0x16,0x19,0xf,0xa,0x27,0x21,0xac,0x22,0x1d,0x15,0x18,0x4,0xe,0xac,0x15,0x18,0xc,0xf,0x27,0x21,0xe6,0x15,0x18,0xc,0xf, + 0x27,0x21,0xac,0x22,0x1d,0x0,0x0,0x0,0x0,0x1,0xfc,0x66,0xfd,0xf3,0xfd,0xae,0xff,0x6e,0x0,0x11,0x0,0x1f,0xb1,0x6,0x64,0x44,0x40,0x14,0xe,0x1, + 0x0,0x1,0x1,0x4a,0x0,0x1,0x0,0x1,0x72,0x0,0x0,0x0,0x69,0x37,0x21,0x2,0x7,0x16,0x2b,0xb1,0x6,0x0,0x44,0x0,0x6,0x23,0x22,0x26,0x35,0x34, + 0x37,0x13,0x36,0x36,0x33,0x33,0x32,0x15,0x14,0x7,0x3,0xfc,0xd2,0x28,0x17,0x16,0x17,0x4,0x6e,0x6,0x19,0x11,0x8e,0x18,0xb,0xc6,0xfe,0x7,0x14,0x12, + 0x11,0xa,0xb,0x1,0x23,0x10,0x10,0x15,0xf,0xf,0xfe,0xdd,0x0,0x0,0xff,0xff,0xfc,0x66,0xfd,0xf3,0xfd,0xae,0xff,0x6e,0x0,0x2,0x1,0x7e,0x0,0x0, + 0x0,0x1,0x2,0x13,0x4,0xd,0x4,0x48,0x5,0x5e,0x0,0x13,0x0,0x17,0xb1,0x6,0x64,0x44,0x40,0xc,0x10,0x1,0x0,0x47,0x0,0x0,0x0,0x69,0x20,0x1, + 0x7,0x15,0x2b,0xb1,0x6,0x0,0x44,0x0,0x33,0x32,0x16,0x17,0x16,0x15,0x14,0x7,0x5,0x6,0x23,0x22,0x26,0x27,0x26,0x35,0x34,0x37,0x25,0x3,0xdd,0xe, + 0x10,0x21,0x13,0x19,0x19,0xfe,0x1f,0x8,0x4,0xe,0x18,0x5,0x4,0x17,0x1,0xa0,0x5,0x5e,0x1e,0x23,0x2c,0x1c,0x1b,0x8,0xa3,0x2,0x13,0xf,0x9,0xd, + 0x1b,0xd,0xe6,0x0,0x0,0x0,0x0,0x1,0x1,0x53,0x3,0x5e,0x2,0xbf,0x5,0x20,0x0,0x11,0x0,0x13,0x40,0x10,0x0,0x0,0x1,0x0,0x73,0x0,0x1,0x1, + 0x10,0x1,0x4c,0x27,0x26,0x2,0x7,0x16,0x2b,0x0,0x16,0x15,0x14,0x7,0x3,0x6,0x23,0x22,0x26,0x35,0x34,0x37,0x13,0x36,0x36,0x33,0x33,0x2,0xaf,0x10, + 0x8,0xf4,0x14,0x25,0x19,0x1e,0x4,0x93,0x6,0x12,0x12,0x8e,0x5,0x20,0x11,0xd,0xc,0xe,0xfe,0x94,0x1e,0x18,0x13,0x6,0xc,0x1,0x6a,0xf,0xc,0x0, + 0x0,0x1,0x1,0x8f,0x4,0x2f,0x4,0x85,0x5,0x4e,0x0,0x1c,0x0,0x2e,0xb1,0x6,0x64,0x44,0x40,0x23,0xa,0x1,0x2,0x1,0x1,0x4a,0x3,0x1,0x1,0x2, + 0x1,0x72,0x0,0x2,0x0,0x0,0x2,0x57,0x0,0x2,0x2,0x0,0x5b,0x0,0x0,0x2,0x0,0x4f,0x13,0x24,0x26,0x26,0x4,0x7,0x18,0x2b,0xb1,0x6,0x0,0x44, + 0x0,0x16,0x15,0x14,0x7,0x6,0x6,0x23,0x22,0x26,0x27,0x26,0x36,0x37,0x36,0x33,0x32,0x16,0x17,0x16,0x16,0x33,0x32,0x36,0x37,0x36,0x33,0x32,0x17,0x4, + 0x71,0x14,0x3,0x2c,0xd5,0x98,0x99,0xb5,0xb,0x1,0x19,0x13,0xe,0xd,0xc,0x13,0x4,0x1a,0x79,0x68,0x69,0x89,0x2d,0xe,0x1f,0x12,0xf,0x5,0x42,0x16, + 0xd,0x5,0x8,0x6a,0x79,0x7a,0x69,0x12,0x1d,0x7,0x6,0xa,0xa,0x42,0x39,0x39,0x42,0x14,0x6,0x0,0x0,0x1,0x1,0xab,0x4,0xd,0x4,0x62,0x5,0x64, + 0x0,0x19,0x0,0x20,0xb1,0x6,0x64,0x44,0x40,0x15,0x18,0x1,0x2,0x0,0x1,0x1,0x4a,0x0,0x1,0x0,0x1,0x72,0x0,0x0,0x0,0x69,0x28,0x2a,0x2,0x7, + 0x16,0x2b,0xb1,0x6,0x0,0x44,0x0,0x33,0x32,0x16,0x17,0x16,0x15,0x14,0x7,0x5,0x6,0x23,0x22,0x27,0x27,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x32,0x17, + 0x17,0x25,0x4,0x2b,0x8,0xd,0x16,0x6,0x6,0x14,0xfe,0xcc,0x19,0x21,0x20,0x14,0xf6,0xb,0xb,0x8,0x16,0xb,0x9,0x9,0xfc,0x1,0x32,0x5,0x63,0xf, + 0xc,0xc,0xe,0x17,0x10,0xe8,0x12,0x12,0xe8,0xb,0x12,0x14,0x11,0xc,0xf,0x6,0x9f,0x9f,0x0,0x0,0xff,0xff,0x2,0x3b,0x5,0x1b,0x4,0x70,0x6,0x6c, + 0x1,0x7,0x1,0x80,0x0,0x28,0x1,0xe,0x0,0x9,0xb1,0x0,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0x0,0x0,0xff,0xff,0x1,0xb7,0x5,0x3d,0x4,0xad, + 0x6,0x5c,0x1,0x7,0x1,0x82,0x0,0x28,0x1,0xe,0x0,0x9,0xb1,0x0,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0x0,0x0,0xff,0xff,0x1,0xd3,0x5,0x1b, + 0x4,0x8a,0x6,0x72,0x1,0x7,0x1,0x83,0x0,0x28,0x1,0xe,0x0,0x9,0xb1,0x0,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0x0,0x0,0xff,0xff,0x0,0xde, + 0xfe,0x28,0x2,0xef,0x0,0x1c,0x0,0x2,0x1,0x91,0x0,0x0,0xff,0xff,0x1,0xae,0x5,0x17,0x4,0x65,0x6,0x6e,0x1,0x7,0x1,0x92,0x0,0x28,0x1,0xe, + 0x0,0x9,0xb1,0x0,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0x0,0x0,0xff,0xff,0x1,0xc0,0x5,0x3c,0x4,0x74,0x6,0x36,0x1,0x7,0x1,0x93,0x0,0x28, + 0x1,0xe,0x0,0x9,0xb1,0x0,0x2,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0x0,0x0,0xff,0xff,0x2,0x8f,0x5,0x45,0x3,0xad,0x6,0x5f,0x1,0x7,0x1,0x94, + 0x0,0x28,0x1,0xe,0x0,0x9,0xb1,0x0,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0x0,0x0,0xff,0xff,0x1,0xb8,0x5,0x1b,0x3,0xed,0x6,0x6c,0x1,0x7, + 0x1,0x95,0x0,0x28,0x1,0xe,0x0,0x9,0xb1,0x0,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0x0,0x0,0xff,0xff,0x1,0xe1,0x5,0x2b,0x5,0x4,0x6,0x77, + 0x1,0x7,0x1,0x96,0x0,0x28,0x1,0xe,0x0,0x9,0xb1,0x0,0x2,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0x0,0x0,0xff,0xff,0x1,0xbd,0x5,0x60,0x4,0x72, + 0x5,0xf0,0x1,0x7,0x1,0x97,0x0,0x28,0x1,0xe,0x0,0x9,0xb1,0x0,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0x0,0x0,0xff,0xff,0x1,0x25,0xfe,0x28, + 0x2,0xbd,0x0,0x1c,0x0,0x2,0x1,0x98,0x0,0x0,0xff,0xff,0x2,0x3a,0x5,0x37,0x4,0x29,0x7,0x23,0x1,0x7,0x1,0x99,0x0,0x28,0x1,0xe,0x0,0x9, + 0xb1,0x0,0x2,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0x0,0x0,0xff,0xff,0x1,0xbe,0x5,0x4c,0x4,0x74,0x6,0x1f,0x1,0x7,0x1,0x9a,0x0,0x28,0x1,0xe, + 0x0,0x9,0xb1,0x0,0x1,0xb8,0x1,0xe,0xb0,0x33,0x2b,0x0,0x0,0x0,0x0,0x1,0x0,0xde,0xfe,0x28,0x2,0xef,0x0,0x1c,0x0,0x23,0x0,0x75,0xb1,0x6, + 0x64,0x44,0xb5,0x1f,0x1,0x4,0x0,0x1,0x4a,0x4b,0xb0,0x13,0x50,0x58,0x40,0x27,0x0,0x0,0x5,0x4,0x5,0x0,0x68,0x0,0x2,0x4,0x3,0x4,0x2,0x3, + 0x70,0x0,0x5,0x0,0x4,0x2,0x5,0x4,0x63,0x0,0x3,0x1,0x1,0x3,0x57,0x0,0x3,0x3,0x1,0x5b,0x0,0x1,0x3,0x1,0x4f,0x1b,0x40,0x28,0x0,0x0, + 0x5,0x4,0x5,0x0,0x4,0x70,0x0,0x2,0x4,0x3,0x4,0x2,0x3,0x70,0x0,0x5,0x0,0x4,0x2,0x5,0x4,0x63,0x0,0x3,0x1,0x1,0x3,0x57,0x0,0x3, + 0x3,0x1,0x5b,0x0,0x1,0x3,0x1,0x4f,0x59,0x40,0x9,0x14,0x33,0x23,0x28,0x26,0x10,0x6,0x7,0x1a,0x2b,0xb1,0x6,0x0,0x44,0x5,0x36,0x16,0x15,0x14, + 0x6,0x7,0x6,0x23,0x22,0x26,0x27,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x32,0x17,0x16,0x16,0x33,0x32,0x36,0x35,0x34,0x23,0x7,0x22,0x35,0x34,0x37,0x37, + 0x33,0x2,0x3d,0x58,0x5a,0x1c,0x1a,0x45,0x97,0x44,0x7c,0x2e,0x11,0xa,0x9,0x19,0xe,0x7,0x4,0x26,0x64,0x31,0x3a,0x3a,0x67,0x23,0x1d,0x1,0x23,0x87, + 0x72,0x2,0x52,0x40,0x22,0x4f,0x1b,0x4a,0x18,0x18,0x8,0x18,0x15,0x11,0x11,0x15,0x2,0xf,0x13,0x2d,0x24,0x37,0x1,0x1c,0x9,0x5,0xcb,0x0,0x0,0x1, + 0x1,0x86,0x4,0x9,0x4,0x3d,0x5,0x60,0x0,0x19,0x0,0x20,0xb1,0x6,0x64,0x44,0x40,0x15,0x18,0x1,0x2,0x1,0x0,0x1,0x4a,0x0,0x0,0x1,0x0,0x72, + 0x0,0x1,0x1,0x69,0x28,0x2a,0x2,0x7,0x16,0x2b,0xb1,0x6,0x0,0x44,0x0,0x23,0x22,0x26,0x27,0x26,0x35,0x34,0x37,0x25,0x36,0x33,0x32,0x17,0x17,0x16, + 0x15,0x14,0x7,0x6,0x6,0x23,0x22,0x27,0x27,0x5,0x1,0xbd,0x8,0xd,0x16,0x6,0x6,0x14,0x1,0x34,0x1a,0x20,0x21,0x13,0xf6,0xb,0xb,0x8,0x16,0xb, + 0x9,0x9,0xfc,0xfe,0xce,0x4,0xa,0xf,0xc,0xc,0xe,0x17,0x10,0xe8,0x12,0x12,0xe8,0xb,0x12,0x14,0x11,0xc,0xf,0x6,0x9f,0x9f,0x0,0x0,0x0,0x2, + 0x1,0x98,0x4,0x2e,0x4,0x4c,0x5,0x28,0x0,0xd,0x0,0x1b,0x0,0x32,0xb1,0x6,0x64,0x44,0x40,0x27,0x2,0x1,0x0,0x1,0x1,0x0,0x57,0x2,0x1,0x0, + 0x0,0x1,0x5b,0x5,0x3,0x4,0x3,0x1,0x0,0x1,0x4f,0xe,0xe,0x0,0x0,0xe,0x1b,0xe,0x1a,0x15,0x13,0x0,0xd,0x0,0xc,0x25,0x6,0x7,0x15,0x2b, + 0xb1,0x6,0x0,0x44,0x0,0x26,0x35,0x34,0x36,0x36,0x33,0x32,0x16,0x15,0x14,0x6,0x6,0x23,0x20,0x26,0x35,0x34,0x36,0x36,0x33,0x32,0x16,0x15,0x14,0x6, + 0x6,0x23,0x1,0xd5,0x3d,0x29,0x43,0x25,0x2f,0x3c,0x29,0x42,0x25,0x1,0x89,0x3d,0x29,0x43,0x25,0x2f,0x3c,0x29,0x42,0x25,0x4,0x2e,0x3e,0x2e,0x25,0x42, + 0x27,0x3e,0x2d,0x27,0x42,0x26,0x3e,0x2e,0x25,0x42,0x27,0x3e,0x2d,0x27,0x42,0x26,0x0,0x0,0x0,0x1,0x2,0x67,0x4,0x37,0x3,0x85,0x5,0x51,0x0,0xd, + 0x0,0x26,0xb1,0x6,0x64,0x44,0x40,0x1b,0x0,0x0,0x1,0x1,0x0,0x57,0x0,0x0,0x0,0x1,0x5b,0x2,0x1,0x1,0x0,0x1,0x4f,0x0,0x0,0x0,0xd,0x0, + 0xc,0x25,0x3,0x7,0x15,0x2b,0xb1,0x6,0x0,0x44,0x0,0x26,0x35,0x34,0x36,0x36,0x33,0x32,0x16,0x15,0x14,0x6,0x6,0x23,0x2,0xac,0x45,0x2f,0x4c,0x2a, + 0x34,0x45,0x2e,0x4c,0x2a,0x4,0x37,0x46,0x33,0x2b,0x4a,0x2c,0x46,0x32,0x2c,0x4a,0x2c,0x0,0x0,0x0,0x0,0x1,0x1,0x90,0x4,0xd,0x3,0xc5,0x5,0x5e, + 0x0,0x11,0x0,0x19,0xb1,0x6,0x64,0x44,0x40,0xe,0x0,0x1,0x0,0x1,0x72,0x0,0x0,0x0,0x69,0x29,0x13,0x2,0x7,0x16,0x2b,0xb1,0x6,0x0,0x44,0x0, + 0x15,0x14,0x6,0x23,0x22,0x27,0x25,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x32,0x17,0x5,0x3,0xc5,0x1b,0x14,0x4,0x8,0xfe,0x1f,0x19,0x19,0x13,0x21,0x10, + 0xe,0x13,0x1,0xa0,0x4,0x60,0x1b,0x16,0x22,0x2,0xa3,0x8,0x1b,0x1c,0x2c,0x23,0x1e,0xb,0xe6,0x0,0x0,0x0,0x0,0x2,0x1,0xb9,0x4,0x1d,0x4,0xdc, + 0x5,0x69,0x0,0x11,0x0,0x23,0x0,0x26,0xb1,0x6,0x64,0x44,0x40,0x1b,0x1c,0x13,0xa,0x1,0x4,0x0,0x1,0x1,0x4a,0x3,0x1,0x1,0x0,0x1,0x72,0x2, + 0x1,0x0,0x0,0x69,0x27,0x27,0x27,0x25,0x4,0x7,0x18,0x2b,0xb1,0x6,0x0,0x44,0x0,0x15,0x14,0x7,0x5,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x37, + 0x36,0x33,0x32,0x17,0x4,0x15,0x14,0x7,0x5,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x37,0x36,0x33,0x32,0x17,0x3,0x60,0x12,0xfe,0xd3,0x14,0x14,0x16, + 0x14,0x16,0xe,0xf1,0x18,0x1e,0x1d,0x22,0x1,0xaf,0x12,0xfe,0xd3,0x13,0x15,0x16,0x14,0x16,0xe,0xf1,0x18,0x1e,0x1d,0x22,0x5,0x32,0x1a,0x10,0xc,0xd2, + 0xd,0xd,0xd,0x16,0xf,0xe,0xe9,0x16,0x16,0x21,0x1a,0x10,0xc,0xd2,0xd,0xd,0xd,0x16,0xf,0xe,0xe9,0x16,0x16,0x0,0x0,0x1,0x1,0x95,0x4,0x52, + 0x4,0x4a,0x4,0xe2,0x0,0x11,0x0,0x2e,0xb1,0x6,0x64,0x44,0x40,0x23,0xb,0x2,0x2,0x0,0x1,0x1,0x4a,0x2,0x1,0x1,0x0,0x0,0x1,0x55,0x2,0x1, + 0x1,0x1,0x0,0x59,0x0,0x0,0x1,0x0,0x4d,0x0,0x0,0x0,0x11,0x0,0xf,0x36,0x3,0x7,0x15,0x2b,0xb1,0x6,0x0,0x44,0x0,0x16,0x15,0x14,0x7,0x6, + 0x6,0x23,0x21,0x22,0x26,0x35,0x34,0x37,0x36,0x36,0x33,0x21,0x4,0x30,0x1a,0x3,0x7,0x22,0x22,0xfd,0xce,0x1c,0x19,0x2,0x7,0x23,0x22,0x2,0x32,0x4, + 0xe2,0x16,0x18,0xb,0xf,0x27,0x21,0x16,0x19,0xf,0xa,0x27,0x21,0x0,0x0,0x0,0x0,0x1,0x1,0x25,0xfe,0x28,0x2,0xbd,0x0,0x1c,0x0,0x17,0x0,0x4d, + 0xb1,0x6,0x64,0x44,0x4b,0xb0,0x31,0x50,0x58,0x40,0x17,0x0,0x3,0x0,0x3,0x72,0x1,0x1,0x0,0x2,0x2,0x0,0x57,0x1,0x1,0x0,0x0,0x2,0x5b,0x0, + 0x2,0x0,0x2,0x4f,0x1b,0x40,0x1a,0x0,0x3,0x1,0x3,0x72,0x0,0x1,0x0,0x1,0x72,0x0,0x0,0x2,0x2,0x0,0x57,0x0,0x0,0x0,0x2,0x5b,0x0,0x2, + 0x0,0x2,0x4f,0x59,0xb6,0x15,0x26,0x21,0x24,0x4,0x7,0x18,0x2b,0xb1,0x6,0x0,0x44,0x4,0x6,0x15,0x14,0x16,0x33,0x32,0x37,0x37,0x32,0x15,0x14,0x6, + 0x7,0x6,0x6,0x23,0x22,0x26,0x35,0x34,0x36,0x37,0x33,0x2,0x3b,0x7a,0x22,0x1f,0x33,0x3c,0x8,0x28,0x12,0x13,0x26,0x5e,0x2e,0x4f,0x56,0x78,0x76,0xaa, + 0x47,0x94,0x3a,0x1d,0x1e,0xa,0x1,0x42,0x14,0x19,0x7,0xd,0x10,0x58,0x4a,0x57,0xa5,0x56,0x0,0x2,0x2,0x12,0x4,0x29,0x4,0x1,0x6,0x15,0x0,0xf, + 0x0,0x1d,0x0,0x38,0xb1,0x6,0x64,0x44,0x40,0x2d,0x0,0x0,0x0,0x2,0x3,0x0,0x2,0x63,0x5,0x1,0x3,0x1,0x1,0x3,0x57,0x5,0x1,0x3,0x3,0x1, + 0x5b,0x4,0x1,0x1,0x3,0x1,0x4f,0x10,0x10,0x0,0x0,0x10,0x1d,0x10,0x1c,0x17,0x15,0x0,0xf,0x0,0xe,0x26,0x6,0x7,0x15,0x2b,0xb1,0x6,0x0,0x44, + 0x0,0x26,0x26,0x35,0x34,0x36,0x36,0x33,0x32,0x16,0x16,0x15,0x14,0x6,0x6,0x23,0x3e,0x2,0x35,0x34,0x26,0x23,0x22,0x6,0x6,0x15,0x14,0x16,0x33,0x2, + 0xac,0x62,0x38,0x48,0x80,0x51,0x3c,0x62,0x38,0x48,0x80,0x51,0x31,0x46,0x2b,0x3c,0x2e,0x26,0x46,0x2a,0x3b,0x2e,0x4,0x29,0x3a,0x63,0x3a,0x43,0x81,0x51, + 0x3a,0x63,0x3a,0x45,0x80,0x50,0x78,0x29,0x44,0x27,0x2d,0x3b,0x29,0x45,0x27,0x2d,0x3a,0x0,0x0,0x0,0x0,0x1,0x1,0x96,0x4,0x3e,0x4,0x4c,0x5,0x11, + 0x0,0x23,0x0,0x68,0xb1,0x6,0x64,0x44,0x4b,0xb0,0x22,0x50,0x58,0x40,0x1b,0x0,0x0,0x3,0x2,0x0,0x57,0x6,0x5,0x2,0x1,0x0,0x3,0x2,0x1,0x3, + 0x63,0x0,0x0,0x0,0x2,0x5b,0x4,0x1,0x2,0x0,0x2,0x4f,0x1b,0x40,0x29,0x0,0x1,0x5,0x0,0x5,0x1,0x0,0x70,0x0,0x4,0x3,0x2,0x3,0x4,0x2, + 0x70,0x0,0x0,0x3,0x2,0x0,0x57,0x6,0x1,0x5,0x0,0x3,0x4,0x5,0x3,0x63,0x0,0x0,0x0,0x2,0x5b,0x0,0x2,0x0,0x2,0x4f,0x59,0x40,0xe,0x0, + 0x0,0x0,0x23,0x0,0x22,0x13,0x24,0x26,0x13,0x24,0x7,0x7,0x19,0x2b,0xb1,0x6,0x0,0x44,0x0,0x16,0x17,0x16,0x16,0x33,0x32,0x37,0x36,0x33,0x32,0x17, + 0x16,0x15,0x14,0x7,0x6,0x23,0x22,0x26,0x27,0x26,0x26,0x23,0x22,0x7,0x6,0x23,0x22,0x27,0x26,0x35,0x34,0x37,0x36,0x33,0x2,0x91,0x3b,0x30,0x2d,0x36, + 0x1d,0x41,0x46,0xb,0x9,0x17,0x13,0xb,0x11,0x6d,0x5b,0x22,0x3b,0x30,0x2d,0x36,0x1d,0x40,0x47,0xa,0xa,0x17,0x13,0xb,0x11,0x6d,0x5b,0x5,0x11,0x10, + 0x11,0x10,0xe,0x2a,0x6,0x1f,0x12,0x13,0x18,0xd,0x5b,0x10,0x11,0x10,0xe,0x2a,0x6,0x1f,0x12,0x13,0x18,0xd,0x5b,0x0,0x0,0x0,0x0,0x1,0x0,0x0, + 0x0,0x3,0x4,0x9b,0xed,0xcb,0xa1,0x17,0x5f,0xf,0x3c,0xf5,0x0,0x1,0x8,0x0,0x0,0x0,0x0,0x0,0xd2,0x42,0x4a,0x67,0x0,0x0,0x0,0x0,0xd2,0x42, + 0x4a,0x76,0xfc,0x66,0xfc,0xba,0xc,0x4e,0x7,0x23,0x0,0x2,0x0,0x7,0x0,0x2,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0x0,0x0,0x5,0x8d,0xfd,0x7d, + 0x0,0x0,0xe,0x64,0xfc,0x66,0xfe,0x2f,0x9,0xcb,0x3,0xe8,0x0,0xb0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0x81,0x4,0x6c, + 0x0,0xd3,0x4,0xcc,0x0,0x0,0x4,0xcc,0x0,0x29,0x4,0xcc,0x0,0x29,0x4,0xcc,0x0,0x29,0x4,0xcc,0x0,0x29,0x4,0xcc,0x0,0x29,0x4,0xcc,0x0,0x29, + 0x4,0xcc,0x0,0x29,0x4,0xcc,0x0,0x29,0x4,0xcc,0x0,0x29,0x4,0xcc,0x0,0x29,0x4,0xcc,0xff,0xd8,0x4,0xcc,0x0,0x90,0x4,0xcc,0x0,0xaa,0x4,0xcc, + 0x0,0xaa,0x4,0xcc,0x0,0xaa,0x4,0xcc,0x0,0xaa,0x4,0xcc,0x0,0xaa,0x4,0xcc,0x0,0xaa,0x4,0xcc,0x0,0x72,0x4,0xcc,0x0,0x54,0x4,0xcc,0x0,0x72, + 0x4,0xcc,0x0,0x72,0x4,0xcc,0x0,0xa4,0x4,0xcc,0x0,0xa4,0x4,0xcc,0x0,0xa4,0x4,0xcc,0x0,0xa4,0x4,0xcc,0x0,0xa4,0x4,0xcc,0x0,0xa4,0x4,0xcc, + 0x0,0xa4,0x4,0xcc,0x0,0xa4,0x4,0xcc,0x0,0xa4,0x4,0xcc,0x0,0xa4,0x4,0xcc,0x0,0xb6,0x4,0xcc,0x0,0x83,0x4,0xcc,0x0,0x83,0x4,0xcc,0x0,0x83, + 0x4,0xcc,0x0,0x83,0x4,0xcc,0x0,0x83,0x4,0xcc,0x0,0x70,0x4,0xcc,0x0,0x70,0x4,0xcc,0x0,0x70,0x4,0xcc,0x0,0x96,0x4,0xcc,0x0,0x74,0x4,0xcc, + 0x0,0x96,0x4,0xcc,0x0,0x96,0x4,0xcc,0x0,0x96,0x4,0xcc,0x0,0x96,0x4,0xcc,0x0,0x96,0x4,0xcc,0x0,0x96,0x4,0xcc,0x0,0x96,0x4,0xcc,0x0,0x96, + 0x4,0xcc,0x0,0x96,0x4,0xcc,0x0,0x90,0x4,0xcc,0x0,0x90,0x4,0xcc,0x0,0x98,0x4,0xcc,0x0,0x98,0x4,0xcc,0x0,0xb8,0x4,0xcc,0x0,0xb8,0x4,0xcc, + 0x0,0xb8,0x4,0xcc,0x0,0xb8,0x4,0xcc,0x0,0xb8,0x4,0xcc,0x0,0x4e,0x4,0xcc,0x0,0x52,0x4,0xcc,0x0,0x70,0x4,0xcc,0x0,0x70,0x4,0xcc,0x0,0x70, + 0x4,0xcc,0x0,0x70,0x4,0xcc,0x0,0x70,0x4,0xcc,0x0,0x70,0x4,0xcc,0x0,0x72,0x4,0xcc,0x0,0x72,0x4,0xcc,0x0,0x72,0x4,0xcc,0x0,0x72,0x4,0xcc, + 0x0,0x72,0x4,0xcc,0x0,0x72,0x4,0xcc,0x0,0x72,0x4,0xcc,0x0,0x72,0x4,0xcc,0x0,0x3e,0x4,0xcc,0x0,0x72,0x4,0xcc,0x0,0x6b,0x4,0xcc,0x0,0xac, + 0x4,0xcc,0x0,0xac,0x4,0xcc,0x0,0x72,0x4,0xcc,0x0,0xac,0x4,0xcc,0x0,0xac,0x4,0xcc,0x0,0xac,0x4,0xcc,0x0,0xac,0x4,0xcc,0x0,0x7f,0x4,0xcc, + 0x0,0x7f,0x4,0xcc,0x0,0x7f,0x4,0xcc,0x0,0x7f,0x4,0xcc,0x0,0x7f,0x4,0xcc,0x0,0x7f,0x4,0xcc,0x0,0xeb,0x4,0xcc,0x0,0xeb,0x4,0xcc,0x0,0xeb, + 0x4,0xcc,0x0,0xeb,0x4,0xcc,0x0,0xeb,0x4,0xcc,0x0,0xa8,0x4,0xcc,0x0,0xa8,0x4,0xcc,0x0,0xa8,0x4,0xcc,0x0,0xa8,0x4,0xcc,0x0,0xa8,0x4,0xcc, + 0x0,0xa8,0x4,0xcc,0x0,0xa8,0x4,0xcc,0x0,0xa8,0x4,0xcc,0x0,0xa8,0x4,0xcc,0x0,0xa8,0x4,0xcc,0x0,0xa8,0x4,0xcc,0x0,0xdf,0x4,0xcc,0x0,0x99, + 0x4,0xcc,0x0,0x9a,0x4,0xcc,0x0,0x9a,0x4,0xcc,0x0,0x9a,0x4,0xcc,0x0,0x9a,0x4,0xcc,0x0,0x5d,0x4,0xcc,0x1,0x5,0x4,0xcc,0x1,0x5,0x4,0xcc, + 0x1,0x5,0x4,0xcc,0x1,0x5,0x4,0xcc,0x1,0x5,0x4,0xcc,0x0,0x74,0x4,0xcc,0x0,0x74,0x4,0xcc,0x0,0x74,0x4,0xcc,0x0,0x74,0x4,0xcc,0x0,0x8f, + 0x4,0xcc,0x0,0x8f,0x4,0xcc,0x0,0x8f,0x4,0xcc,0x0,0x8f,0x4,0xcc,0x0,0x8f,0x4,0xcc,0x0,0x8f,0x4,0xcc,0x0,0x8f,0x4,0xcc,0x0,0x8f,0x4,0xcc, + 0x0,0x8f,0x4,0xcc,0x0,0x8f,0x4,0xcc,0x0,0x2c,0x4,0xcc,0x0,0x81,0x4,0xcc,0x0,0x9a,0x4,0xcc,0x0,0x9a,0x4,0xcc,0x0,0x9a,0x4,0xcc,0x0,0x9a, + 0x4,0xcc,0x0,0x9a,0x4,0xcc,0x0,0x9a,0x4,0xcc,0x0,0x8f,0x4,0xcc,0x0,0x8e,0x4,0xcc,0x0,0x85,0x4,0xcc,0x0,0x9f,0x4,0xcc,0x0,0x9a,0x4,0xcc, + 0x0,0x9a,0x4,0xcc,0x0,0x9a,0x4,0xcc,0x0,0x9a,0x4,0xcc,0x0,0x9a,0x4,0xcc,0x0,0x9a,0x4,0xcc,0x0,0x9a,0x4,0xcc,0x0,0x9a,0x4,0xcc,0x0,0x9a, + 0x4,0xcc,0x0,0x9a,0x4,0xcc,0x0,0xfb,0x4,0xcc,0x0,0x4c,0x4,0xcc,0x0,0x4c,0x4,0xcc,0x0,0x4c,0x4,0xcc,0x0,0x4c,0x4,0xcc,0x0,0x4c,0x4,0xcc, + 0x0,0x8b,0x4,0xcc,0x0,0x88,0x4,0xcc,0x0,0x8b,0x4,0xcc,0x0,0xf4,0x4,0xcc,0x0,0xf4,0x4,0xcc,0x0,0xf4,0x4,0xcc,0x0,0xf4,0x4,0xcc,0x0,0xf4, + 0x4,0xcc,0x0,0xf4,0x4,0xcc,0x0,0xf4,0x4,0xcc,0x0,0xf4,0x4,0xcc,0x0,0xb3,0x4,0xcc,0x0,0xf4,0x4,0xcc,0x0,0xf4,0x4,0xcc,0x0,0xf4,0x4,0xcc, + 0x0,0x53,0x4,0xcc,0x0,0x53,0x4,0xcc,0x0,0x53,0x4,0xcc,0x0,0xa9,0x4,0xcc,0x0,0xa9,0x4,0xcc,0x1,0x10,0x4,0xcc,0x1,0x10,0x4,0xcc,0x0,0xf2, + 0x4,0xcc,0x1,0x10,0x4,0xcc,0x0,0xf2,0x4,0xcc,0x0,0xf3,0x4,0xcc,0x0,0x3a,0x4,0xcc,0x0,0x8b,0x4,0xcc,0x0,0x8b,0x4,0xcc,0x0,0x8b,0x4,0xcc, + 0x0,0x8b,0x4,0xcc,0x0,0x8b,0x4,0xcc,0x0,0x8b,0x4,0xcc,0x0,0x8c,0x4,0xcc,0x0,0x8c,0x4,0xcc,0x0,0x8c,0x4,0xcc,0x0,0x8c,0x4,0xcc,0x0,0x8c, + 0x4,0xcc,0x0,0x8c,0x4,0xcc,0x0,0x8c,0x4,0xcc,0x0,0x8c,0x4,0xcc,0x0,0x1a,0x4,0xcc,0x0,0x8c,0x4,0xcc,0x0,0x42,0x4,0xcc,0x0,0x3d,0x4,0xcc, + 0x0,0x3d,0x4,0xcc,0x0,0x8f,0x4,0xcc,0x0,0xf4,0x4,0xcc,0x0,0xf4,0x4,0xcc,0x0,0xf4,0x4,0xcc,0x0,0xf4,0x4,0xcc,0x0,0x88,0x4,0xcc,0x0,0x88, + 0x4,0xcc,0x0,0x88,0x4,0xcc,0x0,0x88,0x4,0xcc,0x0,0x88,0x4,0xcc,0x0,0x88,0x4,0xcc,0x0,0xac,0x4,0xcc,0x0,0xb4,0x4,0xcc,0x0,0xb4,0x4,0xcc, + 0x0,0xb4,0x4,0xcc,0x0,0xb4,0x4,0xcc,0x0,0xb4,0x4,0xcc,0x0,0xa2,0x4,0xcc,0x0,0xa2,0x4,0xcc,0x0,0xa2,0x4,0xcc,0x0,0xa2,0x4,0xcc,0x0,0xa2, + 0x4,0xcc,0x0,0xa2,0x4,0xcc,0x0,0xa2,0x4,0xcc,0x0,0xa2,0x4,0xcc,0x0,0xa2,0x4,0xcc,0x0,0xa2,0x4,0xcc,0x0,0xa2,0x4,0xcc,0x0,0xcb,0x4,0xcc, + 0x0,0x8a,0x4,0xcc,0x0,0x8c,0x4,0xcc,0x0,0x8c,0x4,0xcc,0x0,0x8c,0x4,0xcc,0x0,0x8c,0x4,0xcc,0x0,0x6b,0x4,0xcc,0x0,0xc9,0x4,0xcc,0x0,0xc9, + 0x4,0xcc,0x0,0xc9,0x4,0xcc,0x0,0xc9,0x4,0xcc,0x0,0xc9,0x4,0xcc,0x0,0x7f,0x4,0xcc,0x0,0x7f,0x4,0xcc,0x0,0x7f,0x4,0xcc,0x0,0x7f,0x9,0x98, + 0x0,0xfb,0x9,0x98,0x0,0xfb,0x4,0xcc,0x1,0x35,0x4,0xcc,0x1,0x3e,0x4,0xcc,0x0,0x5a,0x4,0xcc,0x0,0x19,0x4,0xcc,0x0,0x57,0x4,0xcc,0x0,0xaf, + 0x4,0xcc,0x0,0xd4,0x4,0xcc,0x0,0xc5,0x4,0xcc,0x0,0x85,0x4,0xcc,0x0,0x93,0x4,0xcc,0x0,0x8b,0x4,0xcc,0x0,0x9d,0x4,0xcc,0x0,0xd7,0x4,0xcc, + 0x1,0x42,0x4,0xcc,0x0,0xc2,0x4,0xcc,0x0,0xba,0x4,0xcc,0x0,0x54,0x4,0xcc,0x0,0x3a,0x4,0xcc,0x0,0x3a,0x4,0xcc,0x0,0x54,0x4,0xcc,0x1,0x53, + 0x4,0xcc,0x1,0x38,0x4,0xcc,0x1,0x4c,0x4,0xcc,0x1,0x43,0x4,0xcc,0x2,0xcd,0x4,0xcc,0x2,0x35,0x4,0xcc,0x2,0xcd,0x4,0xcc,0x2,0x5d,0x4,0xcc, + 0xfe,0x1d,0xe,0x64,0x2,0xe9,0x4,0xcc,0x0,0xf3,0x4,0xcc,0xfd,0xfb,0xe,0x64,0x2,0xe9,0x4,0xcc,0x0,0xa7,0x4,0xcc,0x1,0x72,0x4,0xcc,0x1,0xee, + 0x4,0xcc,0x1,0x87,0x4,0xcc,0x1,0xa1,0x4,0xcc,0x1,0x9,0x4,0xcc,0x0,0x2e,0x4,0xcc,0x1,0xb1,0x4,0xcc,0x1,0x6e,0x4,0xcc,0x0,0x3b,0x4,0xcc, + 0x1,0xa1,0x4,0xcc,0x1,0x82,0x4,0xcc,0x0,0x9c,0x4,0xcc,0x1,0x7e,0x4,0xcc,0x2,0x62,0x4,0xcc,0x1,0x31,0x4,0xcc,0x0,0x52,0x4,0xcc,0x0,0x1, + 0x4,0xcc,0x0,0xc8,0x4,0xcc,0x0,0x6c,0x4,0xcc,0x0,0xd3,0x4,0xcc,0x0,0xef,0x4,0xcc,0x1,0x30,0x4,0xcc,0x0,0xf9,0x4,0xcc,0x1,0x2b,0x4,0xcc, + 0xfe,0x70,0x9,0x98,0x0,0xd,0x9,0x98,0x1,0x39,0x4,0xcc,0xff,0x38,0x4,0xcc,0xff,0xff,0x4,0xcc,0x0,0xc7,0x4,0xcc,0x0,0xc7,0x4,0xcc,0x0,0xc7, + 0x0,0x0,0x0,0x0,0x4,0xcc,0x0,0xac,0x4,0xcc,0x0,0x8c,0x4,0xcc,0x1,0xa6,0x4,0xcc,0x1,0x7c,0x4,0xcc,0x0,0x52,0x4,0xcc,0x0,0xee,0x4,0xcc, + 0x0,0xfe,0x4,0xcc,0x1,0xe3,0x4,0xcc,0x1,0xf3,0x4,0xcc,0x1,0x47,0x4,0xcc,0x0,0x0,0x4,0xcc,0x0,0xb3,0x4,0xcc,0x0,0x62,0x4,0xcc,0x0,0x98, + 0x4,0xcc,0x0,0x5f,0x4,0xcc,0xff,0xc7,0x4,0xcc,0x0,0x1e,0x4,0xcc,0x0,0xc7,0x4,0xcc,0x0,0x13,0x4,0xcc,0x0,0xa1,0x4,0xcc,0x0,0x90,0x4,0xcc, + 0x0,0xb3,0x4,0xcc,0x0,0xc7,0x4,0xcc,0x0,0xa4,0x4,0xcc,0x0,0x89,0x4,0xcc,0x0,0x81,0x4,0xcc,0x0,0x3b,0x4,0xcc,0xff,0x8d,0x4,0xcc,0x0,0x9b, + 0x4,0xcc,0x0,0x81,0x4,0xcc,0x0,0xcc,0x4,0xcc,0x0,0xc7,0x4,0xcc,0x0,0xe4,0x4,0xcc,0x0,0xa4,0x4,0xcc,0x0,0xa7,0x4,0xcc,0x0,0x42,0x4,0xcc, + 0x0,0x42,0x4,0xcc,0x0,0xc7,0x4,0xcc,0x0,0x81,0x4,0xcc,0x0,0x84,0x4,0xcc,0x0,0x95,0x4,0xcc,0x0,0x69,0x4,0xcc,0x0,0xf0,0x4,0xcc,0x1,0x95, + 0x4,0xcc,0x1,0x95,0x4,0xcc,0x0,0x26,0x4,0xcc,0x0,0x8c,0x4,0xcc,0x0,0xcd,0x4,0xcc,0x0,0x3d,0x4,0xcc,0x0,0x3d,0x4,0xcc,0x0,0xa9,0x4,0xcc, + 0x0,0xd0,0x4,0xcc,0x1,0x51,0x4,0xcc,0x1,0x4,0x4,0xcc,0x1,0x6f,0x4,0xcc,0x1,0x36,0x2,0x58,0x0,0x0,0x2,0x58,0x0,0x0,0x0,0x0,0xfc,0x66, + 0x0,0x0,0xfc,0x66,0x4,0xcc,0x2,0x13,0x1,0x53,0x1,0x8f,0x1,0xab,0x2,0x3b,0x1,0xb8,0x1,0xd3,0x0,0xde,0x1,0xae,0x1,0xc0,0x2,0x8f,0x1,0xb8, + 0x1,0xe1,0x1,0xbd,0x1,0x25,0x2,0x3a,0x1,0xbe,0x0,0xde,0x1,0x86,0x1,0x98,0x2,0x67,0x1,0x90,0x1,0xb9,0x1,0x95,0x1,0x25,0x2,0x12,0x1,0x96, + 0x0,0x0,0x0,0x78,0x0,0x78,0x0,0xc8,0x0,0xda,0x0,0xec,0x0,0xfe,0x1,0x10,0x1,0x22,0x1,0x34,0x1,0xb8,0x1,0xca,0x1,0xdc,0x2,0x78,0x2,0xde, + 0x3,0x38,0x3,0x4a,0x3,0x5c,0x3,0xf2,0x4,0x4,0x4,0x16,0x4,0x66,0x4,0xda,0x4,0xec,0x4,0xfe,0x5,0x5e,0x5,0x70,0x5,0x82,0x5,0x94,0x5,0xa6, + 0x5,0xb8,0x5,0xca,0x5,0xdc,0x5,0xee,0x6,0x90,0x6,0xe4,0x7,0x54,0x7,0x66,0x7,0x78,0x7,0x84,0x7,0x96,0x7,0xf4,0x8,0x98,0x8,0xaa,0x9,0x2, + 0x9,0x86,0x9,0x98,0x9,0xaa,0x9,0xbc,0x9,0xce,0x9,0xe0,0x9,0xf2,0xa,0x4,0xa,0x9c,0xa,0xae,0xb,0x2,0xb,0x14,0xb,0x78,0xb,0x84,0xb,0xc4, + 0xb,0xd6,0xb,0xe8,0xb,0xf4,0xc,0x6,0xc,0x70,0xc,0xd6,0xd,0x40,0xd,0x52,0xd,0x64,0xd,0x70,0xd,0xdc,0xd,0xee,0xe,0x44,0xe,0x56,0xe,0x68, + 0xe,0x7a,0xe,0x8c,0xe,0x9e,0xe,0xb0,0xe,0xc2,0xf,0x5c,0xf,0x6e,0xf,0xe0,0x10,0x34,0x10,0xaa,0x11,0x2c,0x11,0x8c,0x11,0x9e,0x11,0xb0,0x11,0xbc, + 0x12,0x38,0x12,0x4a,0x12,0x5c,0x13,0x18,0x13,0x2a,0x13,0x36,0x13,0x7c,0x13,0xe8,0x13,0xfa,0x14,0x76,0x14,0x82,0x14,0xd2,0x14,0xe4,0x14,0xf6,0x15,0x8, + 0x15,0x1a,0x15,0x2c,0x15,0x3e,0x15,0x50,0x15,0xda,0x15,0xec,0x15,0xfe,0x16,0x3e,0x16,0xaa,0x16,0xbc,0x16,0xce,0x16,0xe0,0x16,0xf2,0x17,0x4c,0x17,0x92, + 0x17,0xa4,0x17,0xb6,0x17,0xc8,0x17,0xda,0x18,0x2c,0x18,0x3e,0x18,0x50,0x18,0x62,0x19,0x34,0x19,0x40,0x19,0x4c,0x19,0x58,0x19,0x64,0x19,0x70,0x19,0x7c, + 0x1a,0x7c,0x1a,0x8e,0x1a,0x9a,0x1b,0x4a,0x1b,0xdc,0x1c,0x3a,0x1c,0x46,0x1c,0x56,0x1c,0xf0,0x1c,0xfc,0x1d,0x8,0x1d,0x8a,0x1e,0x42,0x1f,0x12,0x1f,0xd6, + 0x20,0x38,0x20,0x44,0x20,0x50,0x20,0x60,0x20,0x6c,0x20,0x78,0x20,0x84,0x20,0x90,0x20,0x9c,0x21,0x40,0x21,0xa8,0x22,0x5e,0x22,0x6a,0x22,0x76,0x23,0x56, + 0x23,0x62,0x23,0xc6,0x24,0x64,0x24,0x76,0x25,0x10,0x25,0x68,0x25,0x74,0x25,0x80,0x25,0x8c,0x25,0x98,0x25,0xa4,0x25,0xb0,0x26,0x82,0x26,0x8e,0x27,0x6c, + 0x27,0x78,0x28,0xe,0x28,0x62,0x28,0x6e,0x28,0xc6,0x28,0xd2,0x29,0x2c,0x29,0x3e,0x29,0x4a,0x29,0x56,0x29,0x68,0x29,0xe4,0x2a,0x7e,0x2a,0xf2,0x2a,0xfe, + 0x2b,0xe,0x2b,0x1a,0x2b,0xae,0x2b,0xba,0x2c,0x4,0x2c,0x10,0x2c,0x1c,0x2c,0x28,0x2c,0x34,0x2c,0x40,0x2c,0x4c,0x2c,0x58,0x2c,0xe2,0x2c,0xee,0x2d,0x82, + 0x2e,0x4,0x2e,0x72,0x2e,0xf6,0x2f,0x7c,0x2f,0x88,0x2f,0x98,0x2f,0xa4,0x30,0x1c,0x30,0x28,0x30,0x38,0x30,0xee,0x30,0xfa,0x31,0x6,0x31,0x9c,0x32,0xc, + 0x32,0xa6,0x32,0xb8,0x33,0x6a,0x33,0x76,0x33,0xea,0x33,0xf6,0x34,0x2,0x34,0xe,0x34,0x1a,0x34,0x26,0x34,0x32,0x34,0x3e,0x34,0xd0,0x34,0xdc,0x34,0xe8, + 0x35,0x28,0x35,0x82,0x35,0x8e,0x35,0x9a,0x35,0xa6,0x35,0xb2,0x36,0x8,0x36,0x50,0x36,0x5c,0x36,0x68,0x36,0x74,0x36,0x80,0x36,0xd2,0x36,0xde,0x36,0xea, + 0x36,0xf6,0x37,0x2,0x37,0xe,0x37,0x64,0x37,0x98,0x37,0xca,0x38,0x2a,0x38,0x82,0x38,0xc6,0x39,0x1e,0x39,0x74,0x39,0xd8,0x3a,0x58,0x3a,0xb6,0x3b,0x2a, + 0x3b,0x8a,0x3b,0xce,0x3c,0x46,0x3c,0xa4,0x3c,0xce,0x3d,0x8c,0x3e,0x68,0x3f,0x9a,0x3f,0xd6,0x40,0x1a,0x40,0x6e,0x40,0xac,0x40,0xea,0x40,0xf4,0x40,0xfe, + 0x41,0x3a,0x41,0x4a,0x41,0x5c,0x41,0xac,0x41,0xfc,0x42,0xe,0x42,0x90,0x42,0xce,0x42,0xde,0x43,0x12,0x43,0x68,0x43,0x94,0x44,0x2,0x44,0x54,0x44,0xa8, + 0x45,0x4e,0x45,0x7e,0x45,0xf2,0x46,0x66,0x46,0x74,0x46,0xa4,0x46,0xf6,0x47,0x30,0x47,0x66,0x48,0x30,0x48,0xfa,0x49,0x86,0x4a,0x12,0x4a,0x54,0x4a,0x96, + 0x4a,0xba,0x4a,0xde,0x4b,0x2,0x4b,0x26,0x4b,0x4a,0x4b,0x7e,0x4b,0xb2,0x4b,0xe6,0x4b,0xee,0x4b,0xee,0x4c,0x4e,0x4c,0xae,0x4c,0xe6,0x4d,0x1e,0x4d,0x2c, + 0x4d,0x3a,0x4d,0x48,0x4d,0x78,0x4d,0xa4,0x4d,0xd0,0x4d,0xd0,0x4e,0x4c,0x4e,0xf8,0x4f,0x9c,0x50,0x3e,0x50,0xb6,0x51,0x2e,0x51,0xb0,0x52,0x5a,0x52,0xee, + 0x53,0x6,0x53,0x5c,0x53,0xcc,0x53,0xe4,0x54,0x22,0x54,0x70,0x54,0xca,0x55,0xc,0x55,0x4a,0x55,0x9a,0x55,0xdc,0x56,0x0,0x56,0x6e,0x56,0xca,0x57,0x20, + 0x57,0xc4,0x58,0x9a,0x58,0xf8,0x59,0x7a,0x59,0xb2,0x59,0xea,0x5a,0x40,0x5a,0x7a,0x5a,0xac,0x5b,0x6,0x5b,0xe2,0x5c,0x66,0x5c,0xbe,0x5d,0x6c,0x5e,0x12, + 0x5e,0x96,0x5f,0x4,0x5f,0x4e,0x5f,0x94,0x5f,0xec,0x60,0xbe,0x60,0xbe,0x60,0xbe,0x60,0xee,0x60,0xf6,0x61,0x26,0x61,0x50,0x61,0x96,0x61,0xd2,0x61,0xe2, + 0x61,0xf2,0x62,0x2,0x62,0xa,0x62,0x1a,0x62,0x2a,0x62,0x3a,0x62,0x4a,0x62,0x5a,0x62,0x6a,0x62,0x72,0x62,0x82,0x62,0x92,0x63,0x2,0x63,0x3e,0x63,0x84, + 0x63,0xb2,0x63,0xe0,0x64,0x2c,0x64,0x64,0x64,0xb0,0x64,0xfc,0x65,0x67,0x0,0x1,0x0,0x0,0x1,0x9b,0x0,0x6f,0x0,0xa,0x0,0x6c,0x0,0x4,0x0,0x2, + 0x0,0x26,0x0,0x36,0x0,0x77,0x0,0x0,0x0,0x94,0xb,0xe2,0x0,0x3,0x0,0x1,0x0,0x0,0x0,0x1a,0x1,0x3e,0x0,0x1,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x26,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x0,0x0,0x1,0x0,0x12,0x0,0x26,0x0,0x1,0x0,0x0,0x0,0x0,0x0,0x2,0x0,0x6,0x0,0x38,0x0,0x1, + 0x0,0x0,0x0,0x0,0x0,0x3,0x0,0x22,0x0,0x3e,0x0,0x1,0x0,0x0,0x0,0x0,0x0,0x4,0x0,0x19,0x0,0x60,0x0,0x1,0x0,0x0,0x0,0x0,0x0,0x5, + 0x0,0xe,0x0,0x79,0x0,0x1,0x0,0x0,0x0,0x0,0x0,0x6,0x0,0x17,0x0,0x87,0x0,0x1,0x0,0x0,0x0,0x0,0x0,0x8,0x0,0x12,0x0,0x9e,0x0,0x1, + 0x0,0x0,0x0,0x0,0x0,0x9,0x0,0x11,0x0,0xb0,0x0,0x1,0x0,0x0,0x0,0x0,0x0,0xb,0x0,0x1b,0x0,0xc1,0x0,0x1,0x0,0x0,0x0,0x0,0x0,0xc, + 0x0,0x16,0x0,0xdc,0x0,0x1,0x0,0x0,0x0,0x0,0x0,0xd,0x11,0x3,0x0,0xf2,0x0,0x1,0x0,0x0,0x0,0x0,0x0,0xe,0x0,0x1a,0x11,0xf5,0x0,0x3, + 0x0,0x1,0x4,0x9,0x0,0x0,0x0,0x4c,0x12,0xf,0x0,0x3,0x0,0x1,0x4,0x9,0x0,0x1,0x0,0x24,0x12,0x5b,0x0,0x3,0x0,0x1,0x4,0x9,0x0,0x2, + 0x0,0xc,0x12,0x7f,0x0,0x3,0x0,0x1,0x4,0x9,0x0,0x3,0x0,0x44,0x12,0x8b,0x0,0x3,0x0,0x1,0x4,0x9,0x0,0x4,0x0,0x32,0x12,0xcf,0x0,0x3, + 0x0,0x1,0x4,0x9,0x0,0x5,0x0,0x1c,0x13,0x1,0x0,0x3,0x0,0x1,0x4,0x9,0x0,0x6,0x0,0x2e,0x13,0x1d,0x0,0x3,0x0,0x1,0x4,0x9,0x0,0x8, + 0x0,0x24,0x13,0x4b,0x0,0x3,0x0,0x1,0x4,0x9,0x0,0x9,0x0,0x22,0x13,0x6f,0x0,0x3,0x0,0x1,0x4,0x9,0x0,0xb,0x0,0x36,0x13,0x91,0x0,0x3, + 0x0,0x1,0x4,0x9,0x0,0xc,0x0,0x2c,0x13,0xc7,0x0,0x3,0x0,0x1,0x4,0x9,0x0,0xd,0x22,0x6,0x13,0xf3,0x0,0x3,0x0,0x1,0x4,0x9,0x0,0xe, + 0x0,0x34,0x35,0xf9,0x43,0x6f,0x70,0x79,0x72,0x69,0x67,0x68,0x74,0x20,0x28,0x63,0x29,0x20,0x32,0x30,0x31,0x35,0x20,0x51,0x75,0x6f,0x74,0x65,0x2d,0x55, + 0x6e,0x71,0x75,0x6f,0x74,0x65,0x20,0x41,0x70,0x70,0x73,0x2e,0x43,0x6f,0x75,0x72,0x69,0x65,0x72,0x20,0x50,0x72,0x69,0x6d,0x65,0x20,0x43,0x6f,0x64,0x65, + 0x49,0x74,0x61,0x6c,0x69,0x63,0x33,0x2e,0x30,0x31,0x38,0x3b,0x51,0x55,0x51,0x41,0x3b,0x43,0x6f,0x75,0x72,0x69,0x65,0x72,0x50,0x72,0x69,0x6d,0x65,0x43, + 0x6f,0x64,0x65,0x2d,0x49,0x74,0x61,0x6c,0x69,0x63,0x43,0x6f,0x75,0x72,0x69,0x65,0x72,0x20,0x50,0x72,0x69,0x6d,0x65,0x20,0x43,0x6f,0x64,0x65,0x20,0x49, + 0x74,0x61,0x6c,0x69,0x63,0x56,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x33,0x2e,0x30,0x33,0x31,0x38,0x43,0x6f,0x75,0x72,0x69,0x65,0x72,0x50,0x72,0x69,0x6d, + 0x65,0x43,0x6f,0x64,0x65,0x2d,0x49,0x74,0x61,0x6c,0x69,0x63,0x51,0x75,0x6f,0x74,0x65,0x2d,0x55,0x6e,0x71,0x75,0x6f,0x74,0x65,0x20,0x41,0x70,0x70,0x73, + 0x41,0x6c,0x61,0x6e,0x20,0x44,0x61,0x67,0x75,0x65,0x2d,0x47,0x72,0x65,0x65,0x6e,0x65,0x68,0x74,0x74,0x70,0x3a,0x2f,0x2f,0x71,0x75,0x6f,0x74,0x65,0x75, + 0x6e,0x71,0x75,0x6f,0x74,0x65,0x61,0x70,0x70,0x73,0x2e,0x63,0x6f,0x6d,0x68,0x74,0x74,0x70,0x3a,0x2f,0x2f,0x62,0x61,0x73,0x69,0x63,0x72,0x65,0x63,0x69, + 0x70,0x65,0x2e,0x63,0x6f,0x6d,0x43,0x6f,0x70,0x79,0x72,0x69,0x67,0x68,0x74,0x20,0x28,0x63,0x29,0x20,0x32,0x30,0x31,0x35,0xa,0x51,0x75,0x6f,0x74,0x65, + 0x2d,0x55,0x6e,0x71,0x75,0x6f,0x74,0x65,0x20,0x41,0x70,0x70,0x73,0x20,0x28,0x68,0x74,0x74,0x70,0x3a,0x2f,0x2f,0x71,0x75,0x6f,0x74,0x65,0x75,0x6e,0x71, + 0x75,0x6f,0x74,0x65,0x61,0x70,0x70,0x73,0x2e,0x63,0x6f,0x6d,0x29,0xa,0x77,0x69,0x74,0x68,0x20,0x52,0x65,0x73,0x65,0x72,0x76,0x65,0x64,0x20,0x46,0x6f, + 0x6e,0x74,0x20,0x4e,0x61,0x6d,0x65,0x20,0x43,0x6f,0x75,0x72,0x69,0x65,0x72,0x20,0x50,0x72,0x69,0x6d,0x65,0x20,0x43,0x6f,0x64,0x65,0x2e,0xa,0xa,0x54, + 0x68,0x69,0x73,0x20,0x46,0x6f,0x6e,0x74,0x20,0x53,0x6f,0x66,0x74,0x77,0x61,0x72,0x65,0x20,0x69,0x73,0x20,0x6c,0x69,0x63,0x65,0x6e,0x73,0x65,0x64,0x20, + 0x75,0x6e,0x64,0x65,0x72,0x20,0x74,0x68,0x65,0x20,0x53,0x49,0x4c,0x20,0x4f,0x70,0x65,0x6e,0x20,0x46,0x6f,0x6e,0x74,0x20,0x4c,0x69,0x63,0x65,0x6e,0x73, + 0x65,0xa,0x56,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x31,0x2e,0x31,0x2e,0x20,0x54,0x68,0x69,0x73,0x20,0x6c,0x69,0x63,0x65,0x6e,0x73,0x65,0x20,0x69,0x73, + 0x20,0x63,0x6f,0x70,0x69,0x65,0x64,0x20,0x62,0x65,0x6c,0x6f,0x77,0xa,0x61,0x6e,0x64,0x20,0x69,0x73,0x20,0x61,0x6c,0x73,0x6f,0x20,0x61,0x76,0x61,0x69, + 0x6c,0x61,0x62,0x6c,0x65,0x20,0x77,0x69,0x74,0x68,0x20,0x61,0x20,0x46,0x41,0x51,0x20,0x61,0x74,0x3a,0x20,0x68,0x74,0x74,0x70,0x3a,0x2f,0x2f,0x73,0x63, + 0x72,0x69,0x70,0x74,0x73,0x2e,0x73,0x69,0x6c,0x2e,0x6f,0x72,0x67,0x2f,0x4f,0x46,0x4c,0xa,0xa,0xa,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d, + 0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d, + 0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0xa,0x53,0x49,0x4c,0x20,0x4f,0x50,0x45,0x4e,0x20,0x46, + 0x4f,0x4e,0x54,0x20,0x4c,0x49,0x43,0x45,0x4e,0x53,0x45,0x20,0x56,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x31,0x2e,0x31,0x20,0x2d,0x20,0x32,0x36,0x20,0x46, + 0x65,0x62,0x72,0x75,0x61,0x72,0x79,0x20,0x32,0x30,0x30,0x37,0xa,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d, + 0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d, + 0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0xa,0xa,0x50,0x52,0x45,0x41,0x4d,0x42,0x4c,0x45,0xa,0x54,0x68,0x65,0x20,0x67,0x6f,0x61, + 0x6c,0x73,0x20,0x6f,0x66,0x20,0x74,0x68,0x65,0x20,0x4f,0x70,0x65,0x6e,0x20,0x46,0x6f,0x6e,0x74,0x20,0x4c,0x69,0x63,0x65,0x6e,0x73,0x65,0x20,0x28,0x4f, + 0x46,0x4c,0x29,0x20,0x61,0x72,0x65,0x20,0x74,0x6f,0x20,0x73,0x74,0x69,0x6d,0x75,0x6c,0x61,0x74,0x65,0x20,0x77,0x6f,0x72,0x6c,0x64,0x77,0x69,0x64,0x65, + 0x20,0x64,0x65,0x76,0x65,0x6c,0x6f,0x70,0x6d,0x65,0x6e,0x74,0x20,0x6f,0x66,0x20,0x63,0x6f,0x6c,0x6c,0x61,0x62,0x6f,0x72,0x61,0x74,0x69,0x76,0x65,0x20, + 0x66,0x6f,0x6e,0x74,0x20,0x70,0x72,0x6f,0x6a,0x65,0x63,0x74,0x73,0xa,0x74,0x6f,0x20,0x73,0x75,0x70,0x70,0x6f,0x72,0x74,0x20,0x74,0x68,0x65,0x20,0x66, + 0x6f,0x6e,0x74,0x20,0x63,0x72,0x65,0x61,0x74,0x69,0x6f,0x6e,0x20,0x65,0x66,0x66,0x6f,0x72,0x74,0x73,0x20,0x6f,0x66,0x20,0x61,0x63,0x61,0x64,0x65,0x6d, + 0x69,0x63,0x20,0x61,0x6e,0x64,0x20,0x6c,0x69,0x6e,0x67,0x75,0x69,0x73,0x74,0x69,0x63,0x20,0x63,0x6f,0x6d,0x6d,0x75,0x6e,0x69,0x74,0x69,0x65,0x73,0xa, + 0x61,0x6e,0x64,0x20,0x74,0x6f,0x20,0x70,0x72,0x6f,0x76,0x69,0x64,0x65,0x20,0x61,0x20,0x66,0x72,0x65,0x65,0x20,0x61,0x6e,0x64,0x20,0x6f,0x70,0x65,0x6e, + 0x20,0x66,0x72,0x61,0x6d,0x65,0x77,0x6f,0x72,0x6b,0x20,0x69,0x6e,0x20,0x77,0x68,0x69,0x63,0x68,0x20,0x66,0x6f,0x6e,0x74,0x73,0x20,0x6d,0x61,0x79,0x20, + 0x62,0x65,0x20,0x73,0x68,0x61,0x72,0x65,0x64,0x20,0x61,0x6e,0x64,0x20,0x69,0x6d,0x70,0x72,0x6f,0x76,0x65,0x64,0x20,0x69,0x6e,0x20,0x70,0x61,0x72,0x74, + 0x6e,0x65,0x72,0x73,0x68,0x69,0x70,0x20,0x77,0x69,0x74,0x68,0x20,0x6f,0x74,0x68,0x65,0x72,0x73,0x2e,0xa,0xa,0x54,0x68,0x65,0x20,0x4f,0x46,0x4c,0x20, + 0x61,0x6c,0x6c,0x6f,0x77,0x73,0x20,0x74,0x68,0x65,0x20,0x6c,0x69,0x63,0x65,0x6e,0x73,0x65,0x64,0x20,0x66,0x6f,0x6e,0x74,0x73,0x20,0x74,0x6f,0x20,0x62, + 0x65,0x20,0x75,0x73,0x65,0x64,0xa,0x73,0x74,0x75,0x64,0x69,0x65,0x64,0xa,0x6d,0x6f,0x64,0x69,0x66,0x69,0x65,0x64,0x20,0x61,0x6e,0x64,0x20,0x72,0x65, + 0x64,0x69,0x73,0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x64,0x20,0x66,0x72,0x65,0x65,0x6c,0x79,0x20,0x61,0x73,0x20,0x6c,0x6f,0x6e,0x67,0x20,0x61,0x73,0x20, + 0x74,0x68,0x65,0x79,0x20,0x61,0x72,0x65,0x20,0x6e,0x6f,0x74,0x20,0x73,0x6f,0x6c,0x64,0x20,0x62,0x79,0x20,0x74,0x68,0x65,0x6d,0x73,0x65,0x6c,0x76,0x65, + 0x73,0x2e,0x20,0x54,0x68,0x65,0x20,0x66,0x6f,0x6e,0x74,0x73,0xa,0x69,0x6e,0x63,0x6c,0x75,0x64,0x69,0x6e,0x67,0x20,0x61,0x6e,0x79,0x20,0x64,0x65,0x72, + 0x69,0x76,0x61,0x74,0x69,0x76,0x65,0x20,0x77,0x6f,0x72,0x6b,0x73,0xa,0x63,0x61,0x6e,0x20,0x62,0x65,0x20,0x62,0x75,0x6e,0x64,0x6c,0x65,0x64,0xa,0x65, + 0x6d,0x62,0x65,0x64,0x64,0x65,0x64,0xa,0x72,0x65,0x64,0x69,0x73,0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x64,0x20,0x61,0x6e,0x64,0x2f,0x6f,0x72,0x20,0x73, + 0x6f,0x6c,0x64,0x20,0x77,0x69,0x74,0x68,0x20,0x61,0x6e,0x79,0x20,0x73,0x6f,0x66,0x74,0x77,0x61,0x72,0x65,0x20,0x70,0x72,0x6f,0x76,0x69,0x64,0x65,0x64, + 0x20,0x74,0x68,0x61,0x74,0x20,0x61,0x6e,0x79,0x20,0x72,0x65,0x73,0x65,0x72,0x76,0x65,0x64,0x20,0x6e,0x61,0x6d,0x65,0x73,0x20,0x61,0x72,0x65,0x20,0x6e, + 0x6f,0x74,0x20,0x75,0x73,0x65,0x64,0x20,0x62,0x79,0x20,0x64,0x65,0x72,0x69,0x76,0x61,0x74,0x69,0x76,0x65,0x20,0x77,0x6f,0x72,0x6b,0x73,0x2e,0x20,0x54, + 0x68,0x65,0x20,0x66,0x6f,0x6e,0x74,0x73,0x20,0x61,0x6e,0x64,0x20,0x64,0x65,0x72,0x69,0x76,0x61,0x74,0x69,0x76,0x65,0x73,0xa,0x68,0x6f,0x77,0x65,0x76, + 0x65,0x72,0xa,0x63,0x61,0x6e,0x6e,0x6f,0x74,0x20,0x62,0x65,0x20,0x72,0x65,0x6c,0x65,0x61,0x73,0x65,0x64,0x20,0x75,0x6e,0x64,0x65,0x72,0x20,0x61,0x6e, + 0x79,0x20,0x6f,0x74,0x68,0x65,0x72,0x20,0x74,0x79,0x70,0x65,0x20,0x6f,0x66,0x20,0x6c,0x69,0x63,0x65,0x6e,0x73,0x65,0x2e,0x20,0x54,0x68,0x65,0x20,0x72, + 0x65,0x71,0x75,0x69,0x72,0x65,0x6d,0x65,0x6e,0x74,0x20,0x66,0x6f,0x72,0x20,0x66,0x6f,0x6e,0x74,0x73,0x20,0x74,0x6f,0x20,0x72,0x65,0x6d,0x61,0x69,0x6e, + 0x20,0x75,0x6e,0x64,0x65,0x72,0x20,0x74,0x68,0x69,0x73,0x20,0x6c,0x69,0x63,0x65,0x6e,0x73,0x65,0x20,0x64,0x6f,0x65,0x73,0x20,0x6e,0x6f,0x74,0x20,0x61, + 0x70,0x70,0x6c,0x79,0x20,0x74,0x6f,0x20,0x61,0x6e,0x79,0x20,0x64,0x6f,0x63,0x75,0x6d,0x65,0x6e,0x74,0x20,0x63,0x72,0x65,0x61,0x74,0x65,0x64,0x20,0x75, + 0x73,0x69,0x6e,0x67,0x20,0x74,0x68,0x65,0x20,0x66,0x6f,0x6e,0x74,0x73,0x20,0x6f,0x72,0x20,0x74,0x68,0x65,0x69,0x72,0x20,0x64,0x65,0x72,0x69,0x76,0x61, + 0x74,0x69,0x76,0x65,0x73,0x2e,0xa,0xa,0x44,0x45,0x46,0x49,0x4e,0x49,0x54,0x49,0x4f,0x4e,0x53,0xa,0x22,0x46,0x6f,0x6e,0x74,0x20,0x53,0x6f,0x66,0x74, + 0x77,0x61,0x72,0x65,0x22,0x20,0x72,0x65,0x66,0x65,0x72,0x73,0x20,0x74,0x6f,0x20,0x74,0x68,0x65,0x20,0x73,0x65,0x74,0x20,0x6f,0x66,0x20,0x66,0x69,0x6c, + 0x65,0x73,0x20,0x72,0x65,0x6c,0x65,0x61,0x73,0x65,0x64,0x20,0x62,0x79,0x20,0x74,0x68,0x65,0x20,0x43,0x6f,0x70,0x79,0x72,0x69,0x67,0x68,0x74,0x20,0x48, + 0x6f,0x6c,0x64,0x65,0x72,0x28,0x73,0x29,0x20,0x75,0x6e,0x64,0x65,0x72,0x20,0x74,0x68,0x69,0x73,0x20,0x6c,0x69,0x63,0x65,0x6e,0x73,0x65,0x20,0x61,0x6e, + 0x64,0x20,0x63,0x6c,0x65,0x61,0x72,0x6c,0x79,0x20,0x6d,0x61,0x72,0x6b,0x65,0x64,0x20,0x61,0x73,0x20,0x73,0x75,0x63,0x68,0x2e,0x20,0x54,0x68,0x69,0x73, + 0x20,0x6d,0x61,0x79,0x20,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65,0x20,0x73,0x6f,0x75,0x72,0x63,0x65,0x20,0x66,0x69,0x6c,0x65,0x73,0xa,0x62,0x75,0x69,0x6c, + 0x64,0x20,0x73,0x63,0x72,0x69,0x70,0x74,0x73,0x20,0x61,0x6e,0x64,0x20,0x64,0x6f,0x63,0x75,0x6d,0x65,0x6e,0x74,0x61,0x74,0x69,0x6f,0x6e,0x2e,0xa,0xa, + 0x22,0x52,0x65,0x73,0x65,0x72,0x76,0x65,0x64,0x20,0x46,0x6f,0x6e,0x74,0x20,0x4e,0x61,0x6d,0x65,0x22,0x20,0x72,0x65,0x66,0x65,0x72,0x73,0x20,0x74,0x6f, + 0x20,0x61,0x6e,0x79,0x20,0x6e,0x61,0x6d,0x65,0x73,0x20,0x73,0x70,0x65,0x63,0x69,0x66,0x69,0x65,0x64,0x20,0x61,0x73,0x20,0x73,0x75,0x63,0x68,0x20,0x61, + 0x66,0x74,0x65,0x72,0x20,0x74,0x68,0x65,0x20,0x63,0x6f,0x70,0x79,0x72,0x69,0x67,0x68,0x74,0x20,0x73,0x74,0x61,0x74,0x65,0x6d,0x65,0x6e,0x74,0x28,0x73, + 0x29,0x2e,0xa,0xa,0x22,0x4f,0x72,0x69,0x67,0x69,0x6e,0x61,0x6c,0x20,0x56,0x65,0x72,0x73,0x69,0x6f,0x6e,0x22,0x20,0x72,0x65,0x66,0x65,0x72,0x73,0x20, + 0x74,0x6f,0x20,0x74,0x68,0x65,0x20,0x63,0x6f,0x6c,0x6c,0x65,0x63,0x74,0x69,0x6f,0x6e,0x20,0x6f,0x66,0x20,0x46,0x6f,0x6e,0x74,0x20,0x53,0x6f,0x66,0x74, + 0x77,0x61,0x72,0x65,0x20,0x63,0x6f,0x6d,0x70,0x6f,0x6e,0x65,0x6e,0x74,0x73,0x20,0x61,0x73,0x20,0x64,0x69,0x73,0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x64, + 0x20,0x62,0x79,0x20,0x74,0x68,0x65,0x20,0x43,0x6f,0x70,0x79,0x72,0x69,0x67,0x68,0x74,0x20,0x48,0x6f,0x6c,0x64,0x65,0x72,0x28,0x73,0x29,0x2e,0xa,0xa, + 0x22,0x4d,0x6f,0x64,0x69,0x66,0x69,0x65,0x64,0x20,0x56,0x65,0x72,0x73,0x69,0x6f,0x6e,0x22,0x20,0x72,0x65,0x66,0x65,0x72,0x73,0x20,0x74,0x6f,0x20,0x61, + 0x6e,0x79,0x20,0x64,0x65,0x72,0x69,0x76,0x61,0x74,0x69,0x76,0x65,0x20,0x6d,0x61,0x64,0x65,0x20,0x62,0x79,0x20,0x61,0x64,0x64,0x69,0x6e,0x67,0x20,0x74, + 0x6f,0xa,0x64,0x65,0x6c,0x65,0x74,0x69,0x6e,0x67,0xa,0x6f,0x72,0x20,0x73,0x75,0x62,0x73,0x74,0x69,0x74,0x75,0x74,0x69,0x6e,0x67,0x20,0x2d,0x2d,0x20, + 0x69,0x6e,0x20,0x70,0x61,0x72,0x74,0x20,0x6f,0x72,0x20,0x69,0x6e,0x20,0x77,0x68,0x6f,0x6c,0x65,0x20,0x2d,0x2d,0x20,0x61,0x6e,0x79,0x20,0x6f,0x66,0x20, + 0x74,0x68,0x65,0x20,0x63,0x6f,0x6d,0x70,0x6f,0x6e,0x65,0x6e,0x74,0x73,0x20,0x6f,0x66,0x20,0x74,0x68,0x65,0x20,0x4f,0x72,0x69,0x67,0x69,0x6e,0x61,0x6c, + 0x20,0x56,0x65,0x72,0x73,0x69,0x6f,0x6e,0xa,0x62,0x79,0x20,0x63,0x68,0x61,0x6e,0x67,0x69,0x6e,0x67,0x20,0x66,0x6f,0x72,0x6d,0x61,0x74,0x73,0x20,0x6f, + 0x72,0x20,0x62,0x79,0x20,0x70,0x6f,0x72,0x74,0x69,0x6e,0x67,0x20,0x74,0x68,0x65,0x20,0x46,0x6f,0x6e,0x74,0x20,0x53,0x6f,0x66,0x74,0x77,0x61,0x72,0x65, + 0x20,0x74,0x6f,0x20,0x61,0x20,0x6e,0x65,0x77,0x20,0x65,0x6e,0x76,0x69,0x72,0x6f,0x6e,0x6d,0x65,0x6e,0x74,0x2e,0xa,0xa,0x22,0x41,0x75,0x74,0x68,0x6f, + 0x72,0x22,0x20,0x72,0x65,0x66,0x65,0x72,0x73,0x20,0x74,0x6f,0x20,0x61,0x6e,0x79,0x20,0x64,0x65,0x73,0x69,0x67,0x6e,0x65,0x72,0xa,0x65,0x6e,0x67,0x69, + 0x6e,0x65,0x65,0x72,0xa,0x70,0x72,0x6f,0x67,0x72,0x61,0x6d,0x6d,0x65,0x72,0xa,0x74,0x65,0x63,0x68,0x6e,0x69,0x63,0x61,0x6c,0x20,0x77,0x72,0x69,0x74, + 0x65,0x72,0x20,0x6f,0x72,0x20,0x6f,0x74,0x68,0x65,0x72,0x20,0x70,0x65,0x72,0x73,0x6f,0x6e,0x20,0x77,0x68,0x6f,0x20,0x63,0x6f,0x6e,0x74,0x72,0x69,0x62, + 0x75,0x74,0x65,0x64,0x20,0x74,0x6f,0x20,0x74,0x68,0x65,0x20,0x46,0x6f,0x6e,0x74,0x20,0x53,0x6f,0x66,0x74,0x77,0x61,0x72,0x65,0x2e,0xa,0xa,0x50,0x45, + 0x52,0x4d,0x49,0x53,0x53,0x49,0x4f,0x4e,0x20,0x26,0x20,0x43,0x4f,0x4e,0x44,0x49,0x54,0x49,0x4f,0x4e,0x53,0xa,0x50,0x65,0x72,0x6d,0x69,0x73,0x73,0x69, + 0x6f,0x6e,0x20,0x69,0x73,0x20,0x68,0x65,0x72,0x65,0x62,0x79,0x20,0x67,0x72,0x61,0x6e,0x74,0x65,0x64,0xa,0x66,0x72,0x65,0x65,0x20,0x6f,0x66,0x20,0x63, + 0x68,0x61,0x72,0x67,0x65,0xa,0x74,0x6f,0x20,0x61,0x6e,0x79,0x20,0x70,0x65,0x72,0x73,0x6f,0x6e,0x20,0x6f,0x62,0x74,0x61,0x69,0x6e,0x69,0x6e,0x67,0x20, + 0x61,0x20,0x63,0x6f,0x70,0x79,0x20,0x6f,0x66,0x20,0x74,0x68,0x65,0x20,0x46,0x6f,0x6e,0x74,0x20,0x53,0x6f,0x66,0x74,0x77,0x61,0x72,0x65,0xa,0x74,0x6f, + 0x20,0x75,0x73,0x65,0xa,0x73,0x74,0x75,0x64,0x79,0xa,0x63,0x6f,0x70,0x79,0xa,0x6d,0x65,0x72,0x67,0x65,0xa,0x65,0x6d,0x62,0x65,0x64,0xa,0x6d,0x6f, + 0x64,0x69,0x66,0x79,0xa,0x72,0x65,0x64,0x69,0x73,0x74,0x72,0x69,0x62,0x75,0x74,0x65,0xa,0x61,0x6e,0x64,0x20,0x73,0x65,0x6c,0x6c,0x20,0x6d,0x6f,0x64, + 0x69,0x66,0x69,0x65,0x64,0x20,0x61,0x6e,0x64,0x20,0x75,0x6e,0x6d,0x6f,0x64,0x69,0x66,0x69,0x65,0x64,0x20,0x63,0x6f,0x70,0x69,0x65,0x73,0x20,0x6f,0x66, + 0x20,0x74,0x68,0x65,0x20,0x46,0x6f,0x6e,0x74,0x20,0x53,0x6f,0x66,0x74,0x77,0x61,0x72,0x65,0xa,0x73,0x75,0x62,0x6a,0x65,0x63,0x74,0x20,0x74,0x6f,0x20, + 0x74,0x68,0x65,0x20,0x66,0x6f,0x6c,0x6c,0x6f,0x77,0x69,0x6e,0x67,0x20,0x63,0x6f,0x6e,0x64,0x69,0x74,0x69,0x6f,0x6e,0x73,0x3a,0xa,0xa,0x31,0x29,0x20, + 0x4e,0x65,0x69,0x74,0x68,0x65,0x72,0x20,0x74,0x68,0x65,0x20,0x46,0x6f,0x6e,0x74,0x20,0x53,0x6f,0x66,0x74,0x77,0x61,0x72,0x65,0x20,0x6e,0x6f,0x72,0x20, + 0x61,0x6e,0x79,0x20,0x6f,0x66,0x20,0x69,0x74,0x73,0x20,0x69,0x6e,0x64,0x69,0x76,0x69,0x64,0x75,0x61,0x6c,0x20,0x63,0x6f,0x6d,0x70,0x6f,0x6e,0x65,0x6e, + 0x74,0x73,0xa,0x69,0x6e,0x20,0x4f,0x72,0x69,0x67,0x69,0x6e,0x61,0x6c,0x20,0x6f,0x72,0x20,0x4d,0x6f,0x64,0x69,0x66,0x69,0x65,0x64,0x20,0x56,0x65,0x72, + 0x73,0x69,0x6f,0x6e,0x73,0xa,0x6d,0x61,0x79,0x20,0x62,0x65,0x20,0x73,0x6f,0x6c,0x64,0x20,0x62,0x79,0x20,0x69,0x74,0x73,0x65,0x6c,0x66,0x2e,0xa,0xa, + 0x32,0x29,0x20,0x4f,0x72,0x69,0x67,0x69,0x6e,0x61,0x6c,0x20,0x6f,0x72,0x20,0x4d,0x6f,0x64,0x69,0x66,0x69,0x65,0x64,0x20,0x56,0x65,0x72,0x73,0x69,0x6f, + 0x6e,0x73,0x20,0x6f,0x66,0x20,0x74,0x68,0x65,0x20,0x46,0x6f,0x6e,0x74,0x20,0x53,0x6f,0x66,0x74,0x77,0x61,0x72,0x65,0x20,0x6d,0x61,0x79,0x20,0x62,0x65, + 0x20,0x62,0x75,0x6e,0x64,0x6c,0x65,0x64,0xa,0x72,0x65,0x64,0x69,0x73,0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x64,0x20,0x61,0x6e,0x64,0x2f,0x6f,0x72,0x20, + 0x73,0x6f,0x6c,0x64,0x20,0x77,0x69,0x74,0x68,0x20,0x61,0x6e,0x79,0x20,0x73,0x6f,0x66,0x74,0x77,0x61,0x72,0x65,0xa,0x70,0x72,0x6f,0x76,0x69,0x64,0x65, + 0x64,0x20,0x74,0x68,0x61,0x74,0x20,0x65,0x61,0x63,0x68,0x20,0x63,0x6f,0x70,0x79,0x20,0x63,0x6f,0x6e,0x74,0x61,0x69,0x6e,0x73,0x20,0x74,0x68,0x65,0x20, + 0x61,0x62,0x6f,0x76,0x65,0x20,0x63,0x6f,0x70,0x79,0x72,0x69,0x67,0x68,0x74,0x20,0x6e,0x6f,0x74,0x69,0x63,0x65,0x20,0x61,0x6e,0x64,0x20,0x74,0x68,0x69, + 0x73,0x20,0x6c,0x69,0x63,0x65,0x6e,0x73,0x65,0x2e,0x20,0x54,0x68,0x65,0x73,0x65,0x20,0x63,0x61,0x6e,0x20,0x62,0x65,0x20,0x69,0x6e,0x63,0x6c,0x75,0x64, + 0x65,0x64,0x20,0x65,0x69,0x74,0x68,0x65,0x72,0x20,0x61,0x73,0x20,0x73,0x74,0x61,0x6e,0x64,0x2d,0x61,0x6c,0x6f,0x6e,0x65,0x20,0x74,0x65,0x78,0x74,0x20, + 0x66,0x69,0x6c,0x65,0x73,0xa,0x68,0x75,0x6d,0x61,0x6e,0x2d,0x72,0x65,0x61,0x64,0x61,0x62,0x6c,0x65,0x20,0x68,0x65,0x61,0x64,0x65,0x72,0x73,0x20,0x6f, + 0x72,0x20,0x69,0x6e,0x20,0x74,0x68,0x65,0x20,0x61,0x70,0x70,0x72,0x6f,0x70,0x72,0x69,0x61,0x74,0x65,0x20,0x6d,0x61,0x63,0x68,0x69,0x6e,0x65,0x2d,0x72, + 0x65,0x61,0x64,0x61,0x62,0x6c,0x65,0x20,0x6d,0x65,0x74,0x61,0x64,0x61,0x74,0x61,0x20,0x66,0x69,0x65,0x6c,0x64,0x73,0x20,0x77,0x69,0x74,0x68,0x69,0x6e, + 0x20,0x74,0x65,0x78,0x74,0x20,0x6f,0x72,0x20,0x62,0x69,0x6e,0x61,0x72,0x79,0x20,0x66,0x69,0x6c,0x65,0x73,0x20,0x61,0x73,0x20,0x6c,0x6f,0x6e,0x67,0x20, + 0x61,0x73,0x20,0x74,0x68,0x6f,0x73,0x65,0x20,0x66,0x69,0x65,0x6c,0x64,0x73,0x20,0x63,0x61,0x6e,0x20,0x62,0x65,0x20,0x65,0x61,0x73,0x69,0x6c,0x79,0x20, + 0x76,0x69,0x65,0x77,0x65,0x64,0x20,0x62,0x79,0x20,0x74,0x68,0x65,0x20,0x75,0x73,0x65,0x72,0x2e,0xa,0xa,0x33,0x29,0x20,0x4e,0x6f,0x20,0x4d,0x6f,0x64, + 0x69,0x66,0x69,0x65,0x64,0x20,0x56,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x6f,0x66,0x20,0x74,0x68,0x65,0x20,0x46,0x6f,0x6e,0x74,0x20,0x53,0x6f,0x66,0x74, + 0x77,0x61,0x72,0x65,0x20,0x6d,0x61,0x79,0x20,0x75,0x73,0x65,0x20,0x74,0x68,0x65,0x20,0x52,0x65,0x73,0x65,0x72,0x76,0x65,0x64,0x20,0x46,0x6f,0x6e,0x74, + 0x20,0x4e,0x61,0x6d,0x65,0x28,0x73,0x29,0x20,0x75,0x6e,0x6c,0x65,0x73,0x73,0x20,0x65,0x78,0x70,0x6c,0x69,0x63,0x69,0x74,0x20,0x77,0x72,0x69,0x74,0x74, + 0x65,0x6e,0x20,0x70,0x65,0x72,0x6d,0x69,0x73,0x73,0x69,0x6f,0x6e,0x20,0x69,0x73,0x20,0x67,0x72,0x61,0x6e,0x74,0x65,0x64,0x20,0x62,0x79,0x20,0x74,0x68, + 0x65,0x20,0x63,0x6f,0x72,0x72,0x65,0x73,0x70,0x6f,0x6e,0x64,0x69,0x6e,0x67,0x20,0x43,0x6f,0x70,0x79,0x72,0x69,0x67,0x68,0x74,0x20,0x48,0x6f,0x6c,0x64, + 0x65,0x72,0x2e,0x20,0x54,0x68,0x69,0x73,0x20,0x72,0x65,0x73,0x74,0x72,0x69,0x63,0x74,0x69,0x6f,0x6e,0x20,0x6f,0x6e,0x6c,0x79,0x20,0x61,0x70,0x70,0x6c, + 0x69,0x65,0x73,0x20,0x74,0x6f,0x20,0x74,0x68,0x65,0x20,0x70,0x72,0x69,0x6d,0x61,0x72,0x79,0x20,0x66,0x6f,0x6e,0x74,0x20,0x6e,0x61,0x6d,0x65,0x20,0x61, + 0x73,0x20,0x70,0x72,0x65,0x73,0x65,0x6e,0x74,0x65,0x64,0x20,0x74,0x6f,0x20,0x74,0x68,0x65,0x20,0x75,0x73,0x65,0x72,0x73,0x2e,0xa,0xa,0x34,0x29,0x20, + 0x54,0x68,0x65,0x20,0x6e,0x61,0x6d,0x65,0x28,0x73,0x29,0x20,0x6f,0x66,0x20,0x74,0x68,0x65,0x20,0x43,0x6f,0x70,0x79,0x72,0x69,0x67,0x68,0x74,0x20,0x48, + 0x6f,0x6c,0x64,0x65,0x72,0x28,0x73,0x29,0x20,0x6f,0x72,0x20,0x74,0x68,0x65,0x20,0x41,0x75,0x74,0x68,0x6f,0x72,0x28,0x73,0x29,0x20,0x6f,0x66,0x20,0x74, + 0x68,0x65,0x20,0x46,0x6f,0x6e,0x74,0x20,0x53,0x6f,0x66,0x74,0x77,0x61,0x72,0x65,0x20,0x73,0x68,0x61,0x6c,0x6c,0x20,0x6e,0x6f,0x74,0x20,0x62,0x65,0x20, + 0x75,0x73,0x65,0x64,0x20,0x74,0x6f,0x20,0x70,0x72,0x6f,0x6d,0x6f,0x74,0x65,0xa,0x65,0x6e,0x64,0x6f,0x72,0x73,0x65,0x20,0x6f,0x72,0x20,0x61,0x64,0x76, + 0x65,0x72,0x74,0x69,0x73,0x65,0x20,0x61,0x6e,0x79,0x20,0x4d,0x6f,0x64,0x69,0x66,0x69,0x65,0x64,0x20,0x56,0x65,0x72,0x73,0x69,0x6f,0x6e,0xa,0x65,0x78, + 0x63,0x65,0x70,0x74,0x20,0x74,0x6f,0x20,0x61,0x63,0x6b,0x6e,0x6f,0x77,0x6c,0x65,0x64,0x67,0x65,0x20,0x74,0x68,0x65,0x20,0x63,0x6f,0x6e,0x74,0x72,0x69, + 0x62,0x75,0x74,0x69,0x6f,0x6e,0x28,0x73,0x29,0x20,0x6f,0x66,0x20,0x74,0x68,0x65,0x20,0x43,0x6f,0x70,0x79,0x72,0x69,0x67,0x68,0x74,0x20,0x48,0x6f,0x6c, + 0x64,0x65,0x72,0x28,0x73,0x29,0x20,0x61,0x6e,0x64,0x20,0x74,0x68,0x65,0x20,0x41,0x75,0x74,0x68,0x6f,0x72,0x28,0x73,0x29,0x20,0x6f,0x72,0x20,0x77,0x69, + 0x74,0x68,0x20,0x74,0x68,0x65,0x69,0x72,0x20,0x65,0x78,0x70,0x6c,0x69,0x63,0x69,0x74,0x20,0x77,0x72,0x69,0x74,0x74,0x65,0x6e,0x20,0x70,0x65,0x72,0x6d, + 0x69,0x73,0x73,0x69,0x6f,0x6e,0x2e,0xa,0xa,0x35,0x29,0x20,0x54,0x68,0x65,0x20,0x46,0x6f,0x6e,0x74,0x20,0x53,0x6f,0x66,0x74,0x77,0x61,0x72,0x65,0xa, + 0x6d,0x6f,0x64,0x69,0x66,0x69,0x65,0x64,0x20,0x6f,0x72,0x20,0x75,0x6e,0x6d,0x6f,0x64,0x69,0x66,0x69,0x65,0x64,0xa,0x69,0x6e,0x20,0x70,0x61,0x72,0x74, + 0x20,0x6f,0x72,0x20,0x69,0x6e,0x20,0x77,0x68,0x6f,0x6c,0x65,0xa,0x6d,0x75,0x73,0x74,0x20,0x62,0x65,0x20,0x64,0x69,0x73,0x74,0x72,0x69,0x62,0x75,0x74, + 0x65,0x64,0x20,0x65,0x6e,0x74,0x69,0x72,0x65,0x6c,0x79,0x20,0x75,0x6e,0x64,0x65,0x72,0x20,0x74,0x68,0x69,0x73,0x20,0x6c,0x69,0x63,0x65,0x6e,0x73,0x65, + 0xa,0x61,0x6e,0x64,0x20,0x6d,0x75,0x73,0x74,0x20,0x6e,0x6f,0x74,0x20,0x62,0x65,0x20,0x64,0x69,0x73,0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x64,0x20,0x75, + 0x6e,0x64,0x65,0x72,0x20,0x61,0x6e,0x79,0x20,0x6f,0x74,0x68,0x65,0x72,0x20,0x6c,0x69,0x63,0x65,0x6e,0x73,0x65,0x2e,0x20,0x54,0x68,0x65,0x20,0x72,0x65, + 0x71,0x75,0x69,0x72,0x65,0x6d,0x65,0x6e,0x74,0x20,0x66,0x6f,0x72,0x20,0x66,0x6f,0x6e,0x74,0x73,0x20,0x74,0x6f,0x20,0x72,0x65,0x6d,0x61,0x69,0x6e,0x20, + 0x75,0x6e,0x64,0x65,0x72,0x20,0x74,0x68,0x69,0x73,0x20,0x6c,0x69,0x63,0x65,0x6e,0x73,0x65,0x20,0x64,0x6f,0x65,0x73,0x20,0x6e,0x6f,0x74,0x20,0x61,0x70, + 0x70,0x6c,0x79,0x20,0x74,0x6f,0x20,0x61,0x6e,0x79,0x20,0x64,0x6f,0x63,0x75,0x6d,0x65,0x6e,0x74,0x20,0x63,0x72,0x65,0x61,0x74,0x65,0x64,0x20,0x75,0x73, + 0x69,0x6e,0x67,0x20,0x74,0x68,0x65,0x20,0x46,0x6f,0x6e,0x74,0x20,0x53,0x6f,0x66,0x74,0x77,0x61,0x72,0x65,0x2e,0xa,0xa,0x54,0x45,0x52,0x4d,0x49,0x4e, + 0x41,0x54,0x49,0x4f,0x4e,0xa,0x54,0x68,0x69,0x73,0x20,0x6c,0x69,0x63,0x65,0x6e,0x73,0x65,0x20,0x62,0x65,0x63,0x6f,0x6d,0x65,0x73,0x20,0x6e,0x75,0x6c, + 0x6c,0x20,0x61,0x6e,0x64,0x20,0x76,0x6f,0x69,0x64,0x20,0x69,0x66,0x20,0x61,0x6e,0x79,0x20,0x6f,0x66,0x20,0x74,0x68,0x65,0x20,0x61,0x62,0x6f,0x76,0x65, + 0x20,0x63,0x6f,0x6e,0x64,0x69,0x74,0x69,0x6f,0x6e,0x73,0x20,0x61,0x72,0x65,0x20,0x6e,0x6f,0x74,0x20,0x6d,0x65,0x74,0x2e,0xa,0xa,0x44,0x49,0x53,0x43, + 0x4c,0x41,0x49,0x4d,0x45,0x52,0xa,0x54,0x48,0x45,0x20,0x46,0x4f,0x4e,0x54,0x20,0x53,0x4f,0x46,0x54,0x57,0x41,0x52,0x45,0x20,0x49,0x53,0x20,0x50,0x52, + 0x4f,0x56,0x49,0x44,0x45,0x44,0x20,0x22,0x41,0x53,0x20,0x49,0x53,0x22,0xa,0x57,0x49,0x54,0x48,0x4f,0x55,0x54,0x20,0x57,0x41,0x52,0x52,0x41,0x4e,0x54, + 0x59,0x20,0x4f,0x46,0x20,0x41,0x4e,0x59,0x20,0x4b,0x49,0x4e,0x44,0xa,0x45,0x58,0x50,0x52,0x45,0x53,0x53,0x20,0x4f,0x52,0x20,0x49,0x4d,0x50,0x4c,0x49, + 0x45,0x44,0xa,0x49,0x4e,0x43,0x4c,0x55,0x44,0x49,0x4e,0x47,0x20,0x42,0x55,0x54,0x20,0x4e,0x4f,0x54,0x20,0x4c,0x49,0x4d,0x49,0x54,0x45,0x44,0x20,0x54, + 0x4f,0x20,0x41,0x4e,0x59,0x20,0x57,0x41,0x52,0x52,0x41,0x4e,0x54,0x49,0x45,0x53,0x20,0x4f,0x46,0x20,0x4d,0x45,0x52,0x43,0x48,0x41,0x4e,0x54,0x41,0x42, + 0x49,0x4c,0x49,0x54,0x59,0xa,0x46,0x49,0x54,0x4e,0x45,0x53,0x53,0x20,0x46,0x4f,0x52,0x20,0x41,0x20,0x50,0x41,0x52,0x54,0x49,0x43,0x55,0x4c,0x41,0x52, + 0x20,0x50,0x55,0x52,0x50,0x4f,0x53,0x45,0x20,0x41,0x4e,0x44,0x20,0x4e,0x4f,0x4e,0x49,0x4e,0x46,0x52,0x49,0x4e,0x47,0x45,0x4d,0x45,0x4e,0x54,0x20,0x4f, + 0x46,0x20,0x43,0x4f,0x50,0x59,0x52,0x49,0x47,0x48,0x54,0xa,0x50,0x41,0x54,0x45,0x4e,0x54,0xa,0x54,0x52,0x41,0x44,0x45,0x4d,0x41,0x52,0x4b,0xa,0x4f, + 0x52,0x20,0x4f,0x54,0x48,0x45,0x52,0x20,0x52,0x49,0x47,0x48,0x54,0x2e,0x20,0x49,0x4e,0x20,0x4e,0x4f,0x20,0x45,0x56,0x45,0x4e,0x54,0x20,0x53,0x48,0x41, + 0x4c,0x4c,0x20,0x54,0x48,0x45,0x20,0x43,0x4f,0x50,0x59,0x52,0x49,0x47,0x48,0x54,0x20,0x48,0x4f,0x4c,0x44,0x45,0x52,0x20,0x42,0x45,0x20,0x4c,0x49,0x41, + 0x42,0x4c,0x45,0x20,0x46,0x4f,0x52,0x20,0x41,0x4e,0x59,0x20,0x43,0x4c,0x41,0x49,0x4d,0xa,0x44,0x41,0x4d,0x41,0x47,0x45,0x53,0x20,0x4f,0x52,0x20,0x4f, + 0x54,0x48,0x45,0x52,0x20,0x4c,0x49,0x41,0x42,0x49,0x4c,0x49,0x54,0x59,0xa,0x49,0x4e,0x43,0x4c,0x55,0x44,0x49,0x4e,0x47,0x20,0x41,0x4e,0x59,0x20,0x47, + 0x45,0x4e,0x45,0x52,0x41,0x4c,0xa,0x53,0x50,0x45,0x43,0x49,0x41,0x4c,0xa,0x49,0x4e,0x44,0x49,0x52,0x45,0x43,0x54,0xa,0x49,0x4e,0x43,0x49,0x44,0x45, + 0x4e,0x54,0x41,0x4c,0xa,0x4f,0x52,0x20,0x43,0x4f,0x4e,0x53,0x45,0x51,0x55,0x45,0x4e,0x54,0x49,0x41,0x4c,0x20,0x44,0x41,0x4d,0x41,0x47,0x45,0x53,0xa, + 0x57,0x48,0x45,0x54,0x48,0x45,0x52,0x20,0x49,0x4e,0x20,0x41,0x4e,0x20,0x41,0x43,0x54,0x49,0x4f,0x4e,0x20,0x4f,0x46,0x20,0x43,0x4f,0x4e,0x54,0x52,0x41, + 0x43,0x54,0xa,0x54,0x4f,0x52,0x54,0x20,0x4f,0x52,0x20,0x4f,0x54,0x48,0x45,0x52,0x57,0x49,0x53,0x45,0xa,0x41,0x52,0x49,0x53,0x49,0x4e,0x47,0x20,0x46, + 0x52,0x4f,0x4d,0xa,0x4f,0x55,0x54,0x20,0x4f,0x46,0x20,0x54,0x48,0x45,0x20,0x55,0x53,0x45,0x20,0x4f,0x52,0x20,0x49,0x4e,0x41,0x42,0x49,0x4c,0x49,0x54, + 0x59,0x20,0x54,0x4f,0x20,0x55,0x53,0x45,0x20,0x54,0x48,0x45,0x20,0x46,0x4f,0x4e,0x54,0x20,0x53,0x4f,0x46,0x54,0x57,0x41,0x52,0x45,0x20,0x4f,0x52,0x20, + 0x46,0x52,0x4f,0x4d,0x20,0x4f,0x54,0x48,0x45,0x52,0x20,0x44,0x45,0x41,0x4c,0x49,0x4e,0x47,0x53,0x20,0x49,0x4e,0x20,0x54,0x48,0x45,0x20,0x46,0x4f,0x4e, + 0x54,0x20,0x53,0x4f,0x46,0x54,0x57,0x41,0x52,0x45,0x2e,0x68,0x74,0x74,0x70,0x3a,0x2f,0x2f,0x73,0x63,0x72,0x69,0x70,0x74,0x73,0x2e,0x73,0x69,0x6c,0x2e, + 0x6f,0x72,0x67,0x2f,0x4f,0x46,0x4c,0x0,0x43,0x0,0x6f,0x0,0x70,0x0,0x79,0x0,0x72,0x0,0x69,0x0,0x67,0x0,0x68,0x0,0x74,0x0,0x20,0x0,0x28,0x0, + 0x63,0x0,0x29,0x0,0x20,0x0,0x32,0x0,0x30,0x0,0x31,0x0,0x35,0x0,0x20,0x0,0x51,0x0,0x75,0x0,0x6f,0x0,0x74,0x0,0x65,0x0,0x2d,0x0,0x55,0x0, + 0x6e,0x0,0x71,0x0,0x75,0x0,0x6f,0x0,0x74,0x0,0x65,0x0,0x20,0x0,0x41,0x0,0x70,0x0,0x70,0x0,0x73,0x0,0x2e,0x0,0x43,0x0,0x6f,0x0,0x75,0x0, + 0x72,0x0,0x69,0x0,0x65,0x0,0x72,0x0,0x20,0x0,0x50,0x0,0x72,0x0,0x69,0x0,0x6d,0x0,0x65,0x0,0x20,0x0,0x43,0x0,0x6f,0x0,0x64,0x0,0x65,0x0, + 0x49,0x0,0x74,0x0,0x61,0x0,0x6c,0x0,0x69,0x0,0x63,0x0,0x33,0x0,0x2e,0x0,0x30,0x0,0x31,0x0,0x38,0x0,0x3b,0x0,0x51,0x0,0x55,0x0,0x51,0x0, + 0x41,0x0,0x3b,0x0,0x43,0x0,0x6f,0x0,0x75,0x0,0x72,0x0,0x69,0x0,0x65,0x0,0x72,0x0,0x50,0x0,0x72,0x0,0x69,0x0,0x6d,0x0,0x65,0x0,0x43,0x0, + 0x6f,0x0,0x64,0x0,0x65,0x0,0x2d,0x0,0x49,0x0,0x74,0x0,0x61,0x0,0x6c,0x0,0x69,0x0,0x63,0x0,0x43,0x0,0x6f,0x0,0x75,0x0,0x72,0x0,0x69,0x0, + 0x65,0x0,0x72,0x0,0x20,0x0,0x50,0x0,0x72,0x0,0x69,0x0,0x6d,0x0,0x65,0x0,0x20,0x0,0x43,0x0,0x6f,0x0,0x64,0x0,0x65,0x0,0x20,0x0,0x49,0x0, + 0x74,0x0,0x61,0x0,0x6c,0x0,0x69,0x0,0x63,0x0,0x56,0x0,0x65,0x0,0x72,0x0,0x73,0x0,0x69,0x0,0x6f,0x0,0x6e,0x0,0x20,0x0,0x33,0x0,0x2e,0x0, + 0x30,0x0,0x33,0x0,0x31,0x0,0x38,0x0,0x43,0x0,0x6f,0x0,0x75,0x0,0x72,0x0,0x69,0x0,0x65,0x0,0x72,0x0,0x50,0x0,0x72,0x0,0x69,0x0,0x6d,0x0, + 0x65,0x0,0x43,0x0,0x6f,0x0,0x64,0x0,0x65,0x0,0x2d,0x0,0x49,0x0,0x74,0x0,0x61,0x0,0x6c,0x0,0x69,0x0,0x63,0x0,0x51,0x0,0x75,0x0,0x6f,0x0, + 0x74,0x0,0x65,0x0,0x2d,0x0,0x55,0x0,0x6e,0x0,0x71,0x0,0x75,0x0,0x6f,0x0,0x74,0x0,0x65,0x0,0x20,0x0,0x41,0x0,0x70,0x0,0x70,0x0,0x73,0x0, + 0x41,0x0,0x6c,0x0,0x61,0x0,0x6e,0x0,0x20,0x0,0x44,0x0,0x61,0x0,0x67,0x0,0x75,0x0,0x65,0x0,0x2d,0x0,0x47,0x0,0x72,0x0,0x65,0x0,0x65,0x0, + 0x6e,0x0,0x65,0x0,0x68,0x0,0x74,0x0,0x74,0x0,0x70,0x0,0x3a,0x0,0x2f,0x0,0x2f,0x0,0x71,0x0,0x75,0x0,0x6f,0x0,0x74,0x0,0x65,0x0,0x75,0x0, + 0x6e,0x0,0x71,0x0,0x75,0x0,0x6f,0x0,0x74,0x0,0x65,0x0,0x61,0x0,0x70,0x0,0x70,0x0,0x73,0x0,0x2e,0x0,0x63,0x0,0x6f,0x0,0x6d,0x0,0x68,0x0, + 0x74,0x0,0x74,0x0,0x70,0x0,0x3a,0x0,0x2f,0x0,0x2f,0x0,0x62,0x0,0x61,0x0,0x73,0x0,0x69,0x0,0x63,0x0,0x72,0x0,0x65,0x0,0x63,0x0,0x69,0x0, + 0x70,0x0,0x65,0x0,0x2e,0x0,0x63,0x0,0x6f,0x0,0x6d,0x0,0x43,0x0,0x6f,0x0,0x70,0x0,0x79,0x0,0x72,0x0,0x69,0x0,0x67,0x0,0x68,0x0,0x74,0x0, + 0x20,0x0,0x28,0x0,0x63,0x0,0x29,0x0,0x20,0x0,0x32,0x0,0x30,0x0,0x31,0x0,0x35,0x0,0xa,0x0,0x51,0x0,0x75,0x0,0x6f,0x0,0x74,0x0,0x65,0x0, + 0x2d,0x0,0x55,0x0,0x6e,0x0,0x71,0x0,0x75,0x0,0x6f,0x0,0x74,0x0,0x65,0x0,0x20,0x0,0x41,0x0,0x70,0x0,0x70,0x0,0x73,0x0,0x20,0x0,0x28,0x0, + 0x68,0x0,0x74,0x0,0x74,0x0,0x70,0x0,0x3a,0x0,0x2f,0x0,0x2f,0x0,0x71,0x0,0x75,0x0,0x6f,0x0,0x74,0x0,0x65,0x0,0x75,0x0,0x6e,0x0,0x71,0x0, + 0x75,0x0,0x6f,0x0,0x74,0x0,0x65,0x0,0x61,0x0,0x70,0x0,0x70,0x0,0x73,0x0,0x2e,0x0,0x63,0x0,0x6f,0x0,0x6d,0x0,0x29,0x0,0xa,0x0,0x77,0x0, + 0x69,0x0,0x74,0x0,0x68,0x0,0x20,0x0,0x52,0x0,0x65,0x0,0x73,0x0,0x65,0x0,0x72,0x0,0x76,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x46,0x0,0x6f,0x0, + 0x6e,0x0,0x74,0x0,0x20,0x0,0x4e,0x0,0x61,0x0,0x6d,0x0,0x65,0x0,0x20,0x0,0x43,0x0,0x6f,0x0,0x75,0x0,0x72,0x0,0x69,0x0,0x65,0x0,0x72,0x0, + 0x20,0x0,0x50,0x0,0x72,0x0,0x69,0x0,0x6d,0x0,0x65,0x0,0x20,0x0,0x43,0x0,0x6f,0x0,0x64,0x0,0x65,0x0,0x2e,0x0,0xa,0x0,0xa,0x0,0x54,0x0, + 0x68,0x0,0x69,0x0,0x73,0x0,0x20,0x0,0x46,0x0,0x6f,0x0,0x6e,0x0,0x74,0x0,0x20,0x0,0x53,0x0,0x6f,0x0,0x66,0x0,0x74,0x0,0x77,0x0,0x61,0x0, + 0x72,0x0,0x65,0x0,0x20,0x0,0x69,0x0,0x73,0x0,0x20,0x0,0x6c,0x0,0x69,0x0,0x63,0x0,0x65,0x0,0x6e,0x0,0x73,0x0,0x65,0x0,0x64,0x0,0x20,0x0, + 0x75,0x0,0x6e,0x0,0x64,0x0,0x65,0x0,0x72,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x53,0x0,0x49,0x0,0x4c,0x0,0x20,0x0,0x4f,0x0, + 0x70,0x0,0x65,0x0,0x6e,0x0,0x20,0x0,0x46,0x0,0x6f,0x0,0x6e,0x0,0x74,0x0,0x20,0x0,0x4c,0x0,0x69,0x0,0x63,0x0,0x65,0x0,0x6e,0x0,0x73,0x0, + 0x65,0x0,0xa,0x0,0x56,0x0,0x65,0x0,0x72,0x0,0x73,0x0,0x69,0x0,0x6f,0x0,0x6e,0x0,0x20,0x0,0x31,0x0,0x2e,0x0,0x31,0x0,0x2e,0x0,0x20,0x0, + 0x54,0x0,0x68,0x0,0x69,0x0,0x73,0x0,0x20,0x0,0x6c,0x0,0x69,0x0,0x63,0x0,0x65,0x0,0x6e,0x0,0x73,0x0,0x65,0x0,0x20,0x0,0x69,0x0,0x73,0x0, + 0x20,0x0,0x63,0x0,0x6f,0x0,0x70,0x0,0x69,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x62,0x0,0x65,0x0,0x6c,0x0,0x6f,0x0,0x77,0x0,0xa,0x0,0x61,0x0, + 0x6e,0x0,0x64,0x0,0x20,0x0,0x69,0x0,0x73,0x0,0x20,0x0,0x61,0x0,0x6c,0x0,0x73,0x0,0x6f,0x0,0x20,0x0,0x61,0x0,0x76,0x0,0x61,0x0,0x69,0x0, + 0x6c,0x0,0x61,0x0,0x62,0x0,0x6c,0x0,0x65,0x0,0x20,0x0,0x77,0x0,0x69,0x0,0x74,0x0,0x68,0x0,0x20,0x0,0x61,0x0,0x20,0x0,0x46,0x0,0x41,0x0, + 0x51,0x0,0x20,0x0,0x61,0x0,0x74,0x0,0x3a,0x0,0x20,0x0,0x68,0x0,0x74,0x0,0x74,0x0,0x70,0x0,0x3a,0x0,0x2f,0x0,0x2f,0x0,0x73,0x0,0x63,0x0, + 0x72,0x0,0x69,0x0,0x70,0x0,0x74,0x0,0x73,0x0,0x2e,0x0,0x73,0x0,0x69,0x0,0x6c,0x0,0x2e,0x0,0x6f,0x0,0x72,0x0,0x67,0x0,0x2f,0x0,0x4f,0x0, + 0x46,0x0,0x4c,0x0,0xa,0x0,0xa,0x0,0xa,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0, + 0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0, + 0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0, + 0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0, + 0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0xa,0x0,0x53,0x0,0x49,0x0,0x4c,0x0,0x20,0x0,0x4f,0x0,0x50,0x0,0x45,0x0,0x4e,0x0,0x20,0x0,0x46,0x0, + 0x4f,0x0,0x4e,0x0,0x54,0x0,0x20,0x0,0x4c,0x0,0x49,0x0,0x43,0x0,0x45,0x0,0x4e,0x0,0x53,0x0,0x45,0x0,0x20,0x0,0x56,0x0,0x65,0x0,0x72,0x0, + 0x73,0x0,0x69,0x0,0x6f,0x0,0x6e,0x0,0x20,0x0,0x31,0x0,0x2e,0x0,0x31,0x0,0x20,0x0,0x2d,0x0,0x20,0x0,0x32,0x0,0x36,0x0,0x20,0x0,0x46,0x0, + 0x65,0x0,0x62,0x0,0x72,0x0,0x75,0x0,0x61,0x0,0x72,0x0,0x79,0x0,0x20,0x0,0x32,0x0,0x30,0x0,0x30,0x0,0x37,0x0,0xa,0x0,0x2d,0x0,0x2d,0x0, + 0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0, + 0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0, + 0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0, + 0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0x2d,0x0,0xa,0x0,0xa,0x0,0x50,0x0, + 0x52,0x0,0x45,0x0,0x41,0x0,0x4d,0x0,0x42,0x0,0x4c,0x0,0x45,0x0,0xa,0x0,0x54,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x67,0x0,0x6f,0x0,0x61,0x0, + 0x6c,0x0,0x73,0x0,0x20,0x0,0x6f,0x0,0x66,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x4f,0x0,0x70,0x0,0x65,0x0,0x6e,0x0,0x20,0x0, + 0x46,0x0,0x6f,0x0,0x6e,0x0,0x74,0x0,0x20,0x0,0x4c,0x0,0x69,0x0,0x63,0x0,0x65,0x0,0x6e,0x0,0x73,0x0,0x65,0x0,0x20,0x0,0x28,0x0,0x4f,0x0, + 0x46,0x0,0x4c,0x0,0x29,0x0,0x20,0x0,0x61,0x0,0x72,0x0,0x65,0x0,0x20,0x0,0x74,0x0,0x6f,0x0,0x20,0x0,0x73,0x0,0x74,0x0,0x69,0x0,0x6d,0x0, + 0x75,0x0,0x6c,0x0,0x61,0x0,0x74,0x0,0x65,0x0,0x20,0x0,0x77,0x0,0x6f,0x0,0x72,0x0,0x6c,0x0,0x64,0x0,0x77,0x0,0x69,0x0,0x64,0x0,0x65,0x0, + 0x20,0x0,0x64,0x0,0x65,0x0,0x76,0x0,0x65,0x0,0x6c,0x0,0x6f,0x0,0x70,0x0,0x6d,0x0,0x65,0x0,0x6e,0x0,0x74,0x0,0x20,0x0,0x6f,0x0,0x66,0x0, + 0x20,0x0,0x63,0x0,0x6f,0x0,0x6c,0x0,0x6c,0x0,0x61,0x0,0x62,0x0,0x6f,0x0,0x72,0x0,0x61,0x0,0x74,0x0,0x69,0x0,0x76,0x0,0x65,0x0,0x20,0x0, + 0x66,0x0,0x6f,0x0,0x6e,0x0,0x74,0x0,0x20,0x0,0x70,0x0,0x72,0x0,0x6f,0x0,0x6a,0x0,0x65,0x0,0x63,0x0,0x74,0x0,0x73,0x0,0xa,0x0,0x74,0x0, + 0x6f,0x0,0x20,0x0,0x73,0x0,0x75,0x0,0x70,0x0,0x70,0x0,0x6f,0x0,0x72,0x0,0x74,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x66,0x0, + 0x6f,0x0,0x6e,0x0,0x74,0x0,0x20,0x0,0x63,0x0,0x72,0x0,0x65,0x0,0x61,0x0,0x74,0x0,0x69,0x0,0x6f,0x0,0x6e,0x0,0x20,0x0,0x65,0x0,0x66,0x0, + 0x66,0x0,0x6f,0x0,0x72,0x0,0x74,0x0,0x73,0x0,0x20,0x0,0x6f,0x0,0x66,0x0,0x20,0x0,0x61,0x0,0x63,0x0,0x61,0x0,0x64,0x0,0x65,0x0,0x6d,0x0, + 0x69,0x0,0x63,0x0,0x20,0x0,0x61,0x0,0x6e,0x0,0x64,0x0,0x20,0x0,0x6c,0x0,0x69,0x0,0x6e,0x0,0x67,0x0,0x75,0x0,0x69,0x0,0x73,0x0,0x74,0x0, + 0x69,0x0,0x63,0x0,0x20,0x0,0x63,0x0,0x6f,0x0,0x6d,0x0,0x6d,0x0,0x75,0x0,0x6e,0x0,0x69,0x0,0x74,0x0,0x69,0x0,0x65,0x0,0x73,0x0,0xa,0x0, + 0x61,0x0,0x6e,0x0,0x64,0x0,0x20,0x0,0x74,0x0,0x6f,0x0,0x20,0x0,0x70,0x0,0x72,0x0,0x6f,0x0,0x76,0x0,0x69,0x0,0x64,0x0,0x65,0x0,0x20,0x0, + 0x61,0x0,0x20,0x0,0x66,0x0,0x72,0x0,0x65,0x0,0x65,0x0,0x20,0x0,0x61,0x0,0x6e,0x0,0x64,0x0,0x20,0x0,0x6f,0x0,0x70,0x0,0x65,0x0,0x6e,0x0, + 0x20,0x0,0x66,0x0,0x72,0x0,0x61,0x0,0x6d,0x0,0x65,0x0,0x77,0x0,0x6f,0x0,0x72,0x0,0x6b,0x0,0x20,0x0,0x69,0x0,0x6e,0x0,0x20,0x0,0x77,0x0, + 0x68,0x0,0x69,0x0,0x63,0x0,0x68,0x0,0x20,0x0,0x66,0x0,0x6f,0x0,0x6e,0x0,0x74,0x0,0x73,0x0,0x20,0x0,0x6d,0x0,0x61,0x0,0x79,0x0,0x20,0x0, + 0x62,0x0,0x65,0x0,0x20,0x0,0x73,0x0,0x68,0x0,0x61,0x0,0x72,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x61,0x0,0x6e,0x0,0x64,0x0,0x20,0x0,0x69,0x0, + 0x6d,0x0,0x70,0x0,0x72,0x0,0x6f,0x0,0x76,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x69,0x0,0x6e,0x0,0x20,0x0,0x70,0x0,0x61,0x0,0x72,0x0,0x74,0x0, + 0x6e,0x0,0x65,0x0,0x72,0x0,0x73,0x0,0x68,0x0,0x69,0x0,0x70,0x0,0x20,0x0,0x77,0x0,0x69,0x0,0x74,0x0,0x68,0x0,0x20,0x0,0x6f,0x0,0x74,0x0, + 0x68,0x0,0x65,0x0,0x72,0x0,0x73,0x0,0x2e,0x0,0xa,0x0,0xa,0x0,0x54,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x4f,0x0,0x46,0x0,0x4c,0x0,0x20,0x0, + 0x61,0x0,0x6c,0x0,0x6c,0x0,0x6f,0x0,0x77,0x0,0x73,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x6c,0x0,0x69,0x0,0x63,0x0,0x65,0x0, + 0x6e,0x0,0x73,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x66,0x0,0x6f,0x0,0x6e,0x0,0x74,0x0,0x73,0x0,0x20,0x0,0x74,0x0,0x6f,0x0,0x20,0x0,0x62,0x0, + 0x65,0x0,0x20,0x0,0x75,0x0,0x73,0x0,0x65,0x0,0x64,0x0,0xa,0x0,0x73,0x0,0x74,0x0,0x75,0x0,0x64,0x0,0x69,0x0,0x65,0x0,0x64,0x0,0xa,0x0, + 0x6d,0x0,0x6f,0x0,0x64,0x0,0x69,0x0,0x66,0x0,0x69,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x61,0x0,0x6e,0x0,0x64,0x0,0x20,0x0,0x72,0x0,0x65,0x0, + 0x64,0x0,0x69,0x0,0x73,0x0,0x74,0x0,0x72,0x0,0x69,0x0,0x62,0x0,0x75,0x0,0x74,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x66,0x0,0x72,0x0,0x65,0x0, + 0x65,0x0,0x6c,0x0,0x79,0x0,0x20,0x0,0x61,0x0,0x73,0x0,0x20,0x0,0x6c,0x0,0x6f,0x0,0x6e,0x0,0x67,0x0,0x20,0x0,0x61,0x0,0x73,0x0,0x20,0x0, + 0x74,0x0,0x68,0x0,0x65,0x0,0x79,0x0,0x20,0x0,0x61,0x0,0x72,0x0,0x65,0x0,0x20,0x0,0x6e,0x0,0x6f,0x0,0x74,0x0,0x20,0x0,0x73,0x0,0x6f,0x0, + 0x6c,0x0,0x64,0x0,0x20,0x0,0x62,0x0,0x79,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x6d,0x0,0x73,0x0,0x65,0x0,0x6c,0x0,0x76,0x0,0x65,0x0, + 0x73,0x0,0x2e,0x0,0x20,0x0,0x54,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x66,0x0,0x6f,0x0,0x6e,0x0,0x74,0x0,0x73,0x0,0xa,0x0,0x69,0x0,0x6e,0x0, + 0x63,0x0,0x6c,0x0,0x75,0x0,0x64,0x0,0x69,0x0,0x6e,0x0,0x67,0x0,0x20,0x0,0x61,0x0,0x6e,0x0,0x79,0x0,0x20,0x0,0x64,0x0,0x65,0x0,0x72,0x0, + 0x69,0x0,0x76,0x0,0x61,0x0,0x74,0x0,0x69,0x0,0x76,0x0,0x65,0x0,0x20,0x0,0x77,0x0,0x6f,0x0,0x72,0x0,0x6b,0x0,0x73,0x0,0xa,0x0,0x63,0x0, + 0x61,0x0,0x6e,0x0,0x20,0x0,0x62,0x0,0x65,0x0,0x20,0x0,0x62,0x0,0x75,0x0,0x6e,0x0,0x64,0x0,0x6c,0x0,0x65,0x0,0x64,0x0,0xa,0x0,0x65,0x0, + 0x6d,0x0,0x62,0x0,0x65,0x0,0x64,0x0,0x64,0x0,0x65,0x0,0x64,0x0,0xa,0x0,0x72,0x0,0x65,0x0,0x64,0x0,0x69,0x0,0x73,0x0,0x74,0x0,0x72,0x0, + 0x69,0x0,0x62,0x0,0x75,0x0,0x74,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x61,0x0,0x6e,0x0,0x64,0x0,0x2f,0x0,0x6f,0x0,0x72,0x0,0x20,0x0,0x73,0x0, + 0x6f,0x0,0x6c,0x0,0x64,0x0,0x20,0x0,0x77,0x0,0x69,0x0,0x74,0x0,0x68,0x0,0x20,0x0,0x61,0x0,0x6e,0x0,0x79,0x0,0x20,0x0,0x73,0x0,0x6f,0x0, + 0x66,0x0,0x74,0x0,0x77,0x0,0x61,0x0,0x72,0x0,0x65,0x0,0x20,0x0,0x70,0x0,0x72,0x0,0x6f,0x0,0x76,0x0,0x69,0x0,0x64,0x0,0x65,0x0,0x64,0x0, + 0x20,0x0,0x74,0x0,0x68,0x0,0x61,0x0,0x74,0x0,0x20,0x0,0x61,0x0,0x6e,0x0,0x79,0x0,0x20,0x0,0x72,0x0,0x65,0x0,0x73,0x0,0x65,0x0,0x72,0x0, + 0x76,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x6e,0x0,0x61,0x0,0x6d,0x0,0x65,0x0,0x73,0x0,0x20,0x0,0x61,0x0,0x72,0x0,0x65,0x0,0x20,0x0,0x6e,0x0, + 0x6f,0x0,0x74,0x0,0x20,0x0,0x75,0x0,0x73,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x62,0x0,0x79,0x0,0x20,0x0,0x64,0x0,0x65,0x0,0x72,0x0,0x69,0x0, + 0x76,0x0,0x61,0x0,0x74,0x0,0x69,0x0,0x76,0x0,0x65,0x0,0x20,0x0,0x77,0x0,0x6f,0x0,0x72,0x0,0x6b,0x0,0x73,0x0,0x2e,0x0,0x20,0x0,0x54,0x0, + 0x68,0x0,0x65,0x0,0x20,0x0,0x66,0x0,0x6f,0x0,0x6e,0x0,0x74,0x0,0x73,0x0,0x20,0x0,0x61,0x0,0x6e,0x0,0x64,0x0,0x20,0x0,0x64,0x0,0x65,0x0, + 0x72,0x0,0x69,0x0,0x76,0x0,0x61,0x0,0x74,0x0,0x69,0x0,0x76,0x0,0x65,0x0,0x73,0x0,0xa,0x0,0x68,0x0,0x6f,0x0,0x77,0x0,0x65,0x0,0x76,0x0, + 0x65,0x0,0x72,0x0,0xa,0x0,0x63,0x0,0x61,0x0,0x6e,0x0,0x6e,0x0,0x6f,0x0,0x74,0x0,0x20,0x0,0x62,0x0,0x65,0x0,0x20,0x0,0x72,0x0,0x65,0x0, + 0x6c,0x0,0x65,0x0,0x61,0x0,0x73,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x75,0x0,0x6e,0x0,0x64,0x0,0x65,0x0,0x72,0x0,0x20,0x0,0x61,0x0,0x6e,0x0, + 0x79,0x0,0x20,0x0,0x6f,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x72,0x0,0x20,0x0,0x74,0x0,0x79,0x0,0x70,0x0,0x65,0x0,0x20,0x0,0x6f,0x0,0x66,0x0, + 0x20,0x0,0x6c,0x0,0x69,0x0,0x63,0x0,0x65,0x0,0x6e,0x0,0x73,0x0,0x65,0x0,0x2e,0x0,0x20,0x0,0x54,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x72,0x0, + 0x65,0x0,0x71,0x0,0x75,0x0,0x69,0x0,0x72,0x0,0x65,0x0,0x6d,0x0,0x65,0x0,0x6e,0x0,0x74,0x0,0x20,0x0,0x66,0x0,0x6f,0x0,0x72,0x0,0x20,0x0, + 0x66,0x0,0x6f,0x0,0x6e,0x0,0x74,0x0,0x73,0x0,0x20,0x0,0x74,0x0,0x6f,0x0,0x20,0x0,0x72,0x0,0x65,0x0,0x6d,0x0,0x61,0x0,0x69,0x0,0x6e,0x0, + 0x20,0x0,0x75,0x0,0x6e,0x0,0x64,0x0,0x65,0x0,0x72,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x69,0x0,0x73,0x0,0x20,0x0,0x6c,0x0,0x69,0x0,0x63,0x0, + 0x65,0x0,0x6e,0x0,0x73,0x0,0x65,0x0,0x20,0x0,0x64,0x0,0x6f,0x0,0x65,0x0,0x73,0x0,0x20,0x0,0x6e,0x0,0x6f,0x0,0x74,0x0,0x20,0x0,0x61,0x0, + 0x70,0x0,0x70,0x0,0x6c,0x0,0x79,0x0,0x20,0x0,0x74,0x0,0x6f,0x0,0x20,0x0,0x61,0x0,0x6e,0x0,0x79,0x0,0x20,0x0,0x64,0x0,0x6f,0x0,0x63,0x0, + 0x75,0x0,0x6d,0x0,0x65,0x0,0x6e,0x0,0x74,0x0,0x20,0x0,0x63,0x0,0x72,0x0,0x65,0x0,0x61,0x0,0x74,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x75,0x0, + 0x73,0x0,0x69,0x0,0x6e,0x0,0x67,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x66,0x0,0x6f,0x0,0x6e,0x0,0x74,0x0,0x73,0x0,0x20,0x0, + 0x6f,0x0,0x72,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x69,0x0,0x72,0x0,0x20,0x0,0x64,0x0,0x65,0x0,0x72,0x0,0x69,0x0,0x76,0x0,0x61,0x0, + 0x74,0x0,0x69,0x0,0x76,0x0,0x65,0x0,0x73,0x0,0x2e,0x0,0xa,0x0,0xa,0x0,0x44,0x0,0x45,0x0,0x46,0x0,0x49,0x0,0x4e,0x0,0x49,0x0,0x54,0x0, + 0x49,0x0,0x4f,0x0,0x4e,0x0,0x53,0x0,0xa,0x0,0x22,0x0,0x46,0x0,0x6f,0x0,0x6e,0x0,0x74,0x0,0x20,0x0,0x53,0x0,0x6f,0x0,0x66,0x0,0x74,0x0, + 0x77,0x0,0x61,0x0,0x72,0x0,0x65,0x0,0x22,0x0,0x20,0x0,0x72,0x0,0x65,0x0,0x66,0x0,0x65,0x0,0x72,0x0,0x73,0x0,0x20,0x0,0x74,0x0,0x6f,0x0, + 0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x73,0x0,0x65,0x0,0x74,0x0,0x20,0x0,0x6f,0x0,0x66,0x0,0x20,0x0,0x66,0x0,0x69,0x0,0x6c,0x0, + 0x65,0x0,0x73,0x0,0x20,0x0,0x72,0x0,0x65,0x0,0x6c,0x0,0x65,0x0,0x61,0x0,0x73,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x62,0x0,0x79,0x0,0x20,0x0, + 0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x43,0x0,0x6f,0x0,0x70,0x0,0x79,0x0,0x72,0x0,0x69,0x0,0x67,0x0,0x68,0x0,0x74,0x0,0x20,0x0,0x48,0x0, + 0x6f,0x0,0x6c,0x0,0x64,0x0,0x65,0x0,0x72,0x0,0x28,0x0,0x73,0x0,0x29,0x0,0x20,0x0,0x75,0x0,0x6e,0x0,0x64,0x0,0x65,0x0,0x72,0x0,0x20,0x0, + 0x74,0x0,0x68,0x0,0x69,0x0,0x73,0x0,0x20,0x0,0x6c,0x0,0x69,0x0,0x63,0x0,0x65,0x0,0x6e,0x0,0x73,0x0,0x65,0x0,0x20,0x0,0x61,0x0,0x6e,0x0, + 0x64,0x0,0x20,0x0,0x63,0x0,0x6c,0x0,0x65,0x0,0x61,0x0,0x72,0x0,0x6c,0x0,0x79,0x0,0x20,0x0,0x6d,0x0,0x61,0x0,0x72,0x0,0x6b,0x0,0x65,0x0, + 0x64,0x0,0x20,0x0,0x61,0x0,0x73,0x0,0x20,0x0,0x73,0x0,0x75,0x0,0x63,0x0,0x68,0x0,0x2e,0x0,0x20,0x0,0x54,0x0,0x68,0x0,0x69,0x0,0x73,0x0, + 0x20,0x0,0x6d,0x0,0x61,0x0,0x79,0x0,0x20,0x0,0x69,0x0,0x6e,0x0,0x63,0x0,0x6c,0x0,0x75,0x0,0x64,0x0,0x65,0x0,0x20,0x0,0x73,0x0,0x6f,0x0, + 0x75,0x0,0x72,0x0,0x63,0x0,0x65,0x0,0x20,0x0,0x66,0x0,0x69,0x0,0x6c,0x0,0x65,0x0,0x73,0x0,0xa,0x0,0x62,0x0,0x75,0x0,0x69,0x0,0x6c,0x0, + 0x64,0x0,0x20,0x0,0x73,0x0,0x63,0x0,0x72,0x0,0x69,0x0,0x70,0x0,0x74,0x0,0x73,0x0,0x20,0x0,0x61,0x0,0x6e,0x0,0x64,0x0,0x20,0x0,0x64,0x0, + 0x6f,0x0,0x63,0x0,0x75,0x0,0x6d,0x0,0x65,0x0,0x6e,0x0,0x74,0x0,0x61,0x0,0x74,0x0,0x69,0x0,0x6f,0x0,0x6e,0x0,0x2e,0x0,0xa,0x0,0xa,0x0, + 0x22,0x0,0x52,0x0,0x65,0x0,0x73,0x0,0x65,0x0,0x72,0x0,0x76,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x46,0x0,0x6f,0x0,0x6e,0x0,0x74,0x0,0x20,0x0, + 0x4e,0x0,0x61,0x0,0x6d,0x0,0x65,0x0,0x22,0x0,0x20,0x0,0x72,0x0,0x65,0x0,0x66,0x0,0x65,0x0,0x72,0x0,0x73,0x0,0x20,0x0,0x74,0x0,0x6f,0x0, + 0x20,0x0,0x61,0x0,0x6e,0x0,0x79,0x0,0x20,0x0,0x6e,0x0,0x61,0x0,0x6d,0x0,0x65,0x0,0x73,0x0,0x20,0x0,0x73,0x0,0x70,0x0,0x65,0x0,0x63,0x0, + 0x69,0x0,0x66,0x0,0x69,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x61,0x0,0x73,0x0,0x20,0x0,0x73,0x0,0x75,0x0,0x63,0x0,0x68,0x0,0x20,0x0,0x61,0x0, + 0x66,0x0,0x74,0x0,0x65,0x0,0x72,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x63,0x0,0x6f,0x0,0x70,0x0,0x79,0x0,0x72,0x0,0x69,0x0, + 0x67,0x0,0x68,0x0,0x74,0x0,0x20,0x0,0x73,0x0,0x74,0x0,0x61,0x0,0x74,0x0,0x65,0x0,0x6d,0x0,0x65,0x0,0x6e,0x0,0x74,0x0,0x28,0x0,0x73,0x0, + 0x29,0x0,0x2e,0x0,0xa,0x0,0xa,0x0,0x22,0x0,0x4f,0x0,0x72,0x0,0x69,0x0,0x67,0x0,0x69,0x0,0x6e,0x0,0x61,0x0,0x6c,0x0,0x20,0x0,0x56,0x0, + 0x65,0x0,0x72,0x0,0x73,0x0,0x69,0x0,0x6f,0x0,0x6e,0x0,0x22,0x0,0x20,0x0,0x72,0x0,0x65,0x0,0x66,0x0,0x65,0x0,0x72,0x0,0x73,0x0,0x20,0x0, + 0x74,0x0,0x6f,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x63,0x0,0x6f,0x0,0x6c,0x0,0x6c,0x0,0x65,0x0,0x63,0x0,0x74,0x0,0x69,0x0, + 0x6f,0x0,0x6e,0x0,0x20,0x0,0x6f,0x0,0x66,0x0,0x20,0x0,0x46,0x0,0x6f,0x0,0x6e,0x0,0x74,0x0,0x20,0x0,0x53,0x0,0x6f,0x0,0x66,0x0,0x74,0x0, + 0x77,0x0,0x61,0x0,0x72,0x0,0x65,0x0,0x20,0x0,0x63,0x0,0x6f,0x0,0x6d,0x0,0x70,0x0,0x6f,0x0,0x6e,0x0,0x65,0x0,0x6e,0x0,0x74,0x0,0x73,0x0, + 0x20,0x0,0x61,0x0,0x73,0x0,0x20,0x0,0x64,0x0,0x69,0x0,0x73,0x0,0x74,0x0,0x72,0x0,0x69,0x0,0x62,0x0,0x75,0x0,0x74,0x0,0x65,0x0,0x64,0x0, + 0x20,0x0,0x62,0x0,0x79,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x43,0x0,0x6f,0x0,0x70,0x0,0x79,0x0,0x72,0x0,0x69,0x0,0x67,0x0, + 0x68,0x0,0x74,0x0,0x20,0x0,0x48,0x0,0x6f,0x0,0x6c,0x0,0x64,0x0,0x65,0x0,0x72,0x0,0x28,0x0,0x73,0x0,0x29,0x0,0x2e,0x0,0xa,0x0,0xa,0x0, + 0x22,0x0,0x4d,0x0,0x6f,0x0,0x64,0x0,0x69,0x0,0x66,0x0,0x69,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x56,0x0,0x65,0x0,0x72,0x0,0x73,0x0,0x69,0x0, + 0x6f,0x0,0x6e,0x0,0x22,0x0,0x20,0x0,0x72,0x0,0x65,0x0,0x66,0x0,0x65,0x0,0x72,0x0,0x73,0x0,0x20,0x0,0x74,0x0,0x6f,0x0,0x20,0x0,0x61,0x0, + 0x6e,0x0,0x79,0x0,0x20,0x0,0x64,0x0,0x65,0x0,0x72,0x0,0x69,0x0,0x76,0x0,0x61,0x0,0x74,0x0,0x69,0x0,0x76,0x0,0x65,0x0,0x20,0x0,0x6d,0x0, + 0x61,0x0,0x64,0x0,0x65,0x0,0x20,0x0,0x62,0x0,0x79,0x0,0x20,0x0,0x61,0x0,0x64,0x0,0x64,0x0,0x69,0x0,0x6e,0x0,0x67,0x0,0x20,0x0,0x74,0x0, + 0x6f,0x0,0xa,0x0,0x64,0x0,0x65,0x0,0x6c,0x0,0x65,0x0,0x74,0x0,0x69,0x0,0x6e,0x0,0x67,0x0,0xa,0x0,0x6f,0x0,0x72,0x0,0x20,0x0,0x73,0x0, + 0x75,0x0,0x62,0x0,0x73,0x0,0x74,0x0,0x69,0x0,0x74,0x0,0x75,0x0,0x74,0x0,0x69,0x0,0x6e,0x0,0x67,0x0,0x20,0x0,0x2d,0x0,0x2d,0x0,0x20,0x0, + 0x69,0x0,0x6e,0x0,0x20,0x0,0x70,0x0,0x61,0x0,0x72,0x0,0x74,0x0,0x20,0x0,0x6f,0x0,0x72,0x0,0x20,0x0,0x69,0x0,0x6e,0x0,0x20,0x0,0x77,0x0, + 0x68,0x0,0x6f,0x0,0x6c,0x0,0x65,0x0,0x20,0x0,0x2d,0x0,0x2d,0x0,0x20,0x0,0x61,0x0,0x6e,0x0,0x79,0x0,0x20,0x0,0x6f,0x0,0x66,0x0,0x20,0x0, + 0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x63,0x0,0x6f,0x0,0x6d,0x0,0x70,0x0,0x6f,0x0,0x6e,0x0,0x65,0x0,0x6e,0x0,0x74,0x0,0x73,0x0,0x20,0x0, + 0x6f,0x0,0x66,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x4f,0x0,0x72,0x0,0x69,0x0,0x67,0x0,0x69,0x0,0x6e,0x0,0x61,0x0,0x6c,0x0, + 0x20,0x0,0x56,0x0,0x65,0x0,0x72,0x0,0x73,0x0,0x69,0x0,0x6f,0x0,0x6e,0x0,0xa,0x0,0x62,0x0,0x79,0x0,0x20,0x0,0x63,0x0,0x68,0x0,0x61,0x0, + 0x6e,0x0,0x67,0x0,0x69,0x0,0x6e,0x0,0x67,0x0,0x20,0x0,0x66,0x0,0x6f,0x0,0x72,0x0,0x6d,0x0,0x61,0x0,0x74,0x0,0x73,0x0,0x20,0x0,0x6f,0x0, + 0x72,0x0,0x20,0x0,0x62,0x0,0x79,0x0,0x20,0x0,0x70,0x0,0x6f,0x0,0x72,0x0,0x74,0x0,0x69,0x0,0x6e,0x0,0x67,0x0,0x20,0x0,0x74,0x0,0x68,0x0, + 0x65,0x0,0x20,0x0,0x46,0x0,0x6f,0x0,0x6e,0x0,0x74,0x0,0x20,0x0,0x53,0x0,0x6f,0x0,0x66,0x0,0x74,0x0,0x77,0x0,0x61,0x0,0x72,0x0,0x65,0x0, + 0x20,0x0,0x74,0x0,0x6f,0x0,0x20,0x0,0x61,0x0,0x20,0x0,0x6e,0x0,0x65,0x0,0x77,0x0,0x20,0x0,0x65,0x0,0x6e,0x0,0x76,0x0,0x69,0x0,0x72,0x0, + 0x6f,0x0,0x6e,0x0,0x6d,0x0,0x65,0x0,0x6e,0x0,0x74,0x0,0x2e,0x0,0xa,0x0,0xa,0x0,0x22,0x0,0x41,0x0,0x75,0x0,0x74,0x0,0x68,0x0,0x6f,0x0, + 0x72,0x0,0x22,0x0,0x20,0x0,0x72,0x0,0x65,0x0,0x66,0x0,0x65,0x0,0x72,0x0,0x73,0x0,0x20,0x0,0x74,0x0,0x6f,0x0,0x20,0x0,0x61,0x0,0x6e,0x0, + 0x79,0x0,0x20,0x0,0x64,0x0,0x65,0x0,0x73,0x0,0x69,0x0,0x67,0x0,0x6e,0x0,0x65,0x0,0x72,0x0,0xa,0x0,0x65,0x0,0x6e,0x0,0x67,0x0,0x69,0x0, + 0x6e,0x0,0x65,0x0,0x65,0x0,0x72,0x0,0xa,0x0,0x70,0x0,0x72,0x0,0x6f,0x0,0x67,0x0,0x72,0x0,0x61,0x0,0x6d,0x0,0x6d,0x0,0x65,0x0,0x72,0x0, + 0xa,0x0,0x74,0x0,0x65,0x0,0x63,0x0,0x68,0x0,0x6e,0x0,0x69,0x0,0x63,0x0,0x61,0x0,0x6c,0x0,0x20,0x0,0x77,0x0,0x72,0x0,0x69,0x0,0x74,0x0, + 0x65,0x0,0x72,0x0,0x20,0x0,0x6f,0x0,0x72,0x0,0x20,0x0,0x6f,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x72,0x0,0x20,0x0,0x70,0x0,0x65,0x0,0x72,0x0, + 0x73,0x0,0x6f,0x0,0x6e,0x0,0x20,0x0,0x77,0x0,0x68,0x0,0x6f,0x0,0x20,0x0,0x63,0x0,0x6f,0x0,0x6e,0x0,0x74,0x0,0x72,0x0,0x69,0x0,0x62,0x0, + 0x75,0x0,0x74,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x74,0x0,0x6f,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x46,0x0,0x6f,0x0,0x6e,0x0, + 0x74,0x0,0x20,0x0,0x53,0x0,0x6f,0x0,0x66,0x0,0x74,0x0,0x77,0x0,0x61,0x0,0x72,0x0,0x65,0x0,0x2e,0x0,0xa,0x0,0xa,0x0,0x50,0x0,0x45,0x0, + 0x52,0x0,0x4d,0x0,0x49,0x0,0x53,0x0,0x53,0x0,0x49,0x0,0x4f,0x0,0x4e,0x0,0x20,0x0,0x26,0x0,0x20,0x0,0x43,0x0,0x4f,0x0,0x4e,0x0,0x44,0x0, + 0x49,0x0,0x54,0x0,0x49,0x0,0x4f,0x0,0x4e,0x0,0x53,0x0,0xa,0x0,0x50,0x0,0x65,0x0,0x72,0x0,0x6d,0x0,0x69,0x0,0x73,0x0,0x73,0x0,0x69,0x0, + 0x6f,0x0,0x6e,0x0,0x20,0x0,0x69,0x0,0x73,0x0,0x20,0x0,0x68,0x0,0x65,0x0,0x72,0x0,0x65,0x0,0x62,0x0,0x79,0x0,0x20,0x0,0x67,0x0,0x72,0x0, + 0x61,0x0,0x6e,0x0,0x74,0x0,0x65,0x0,0x64,0x0,0xa,0x0,0x66,0x0,0x72,0x0,0x65,0x0,0x65,0x0,0x20,0x0,0x6f,0x0,0x66,0x0,0x20,0x0,0x63,0x0, + 0x68,0x0,0x61,0x0,0x72,0x0,0x67,0x0,0x65,0x0,0xa,0x0,0x74,0x0,0x6f,0x0,0x20,0x0,0x61,0x0,0x6e,0x0,0x79,0x0,0x20,0x0,0x70,0x0,0x65,0x0, + 0x72,0x0,0x73,0x0,0x6f,0x0,0x6e,0x0,0x20,0x0,0x6f,0x0,0x62,0x0,0x74,0x0,0x61,0x0,0x69,0x0,0x6e,0x0,0x69,0x0,0x6e,0x0,0x67,0x0,0x20,0x0, + 0x61,0x0,0x20,0x0,0x63,0x0,0x6f,0x0,0x70,0x0,0x79,0x0,0x20,0x0,0x6f,0x0,0x66,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x46,0x0, + 0x6f,0x0,0x6e,0x0,0x74,0x0,0x20,0x0,0x53,0x0,0x6f,0x0,0x66,0x0,0x74,0x0,0x77,0x0,0x61,0x0,0x72,0x0,0x65,0x0,0xa,0x0,0x74,0x0,0x6f,0x0, + 0x20,0x0,0x75,0x0,0x73,0x0,0x65,0x0,0xa,0x0,0x73,0x0,0x74,0x0,0x75,0x0,0x64,0x0,0x79,0x0,0xa,0x0,0x63,0x0,0x6f,0x0,0x70,0x0,0x79,0x0, + 0xa,0x0,0x6d,0x0,0x65,0x0,0x72,0x0,0x67,0x0,0x65,0x0,0xa,0x0,0x65,0x0,0x6d,0x0,0x62,0x0,0x65,0x0,0x64,0x0,0xa,0x0,0x6d,0x0,0x6f,0x0, + 0x64,0x0,0x69,0x0,0x66,0x0,0x79,0x0,0xa,0x0,0x72,0x0,0x65,0x0,0x64,0x0,0x69,0x0,0x73,0x0,0x74,0x0,0x72,0x0,0x69,0x0,0x62,0x0,0x75,0x0, + 0x74,0x0,0x65,0x0,0xa,0x0,0x61,0x0,0x6e,0x0,0x64,0x0,0x20,0x0,0x73,0x0,0x65,0x0,0x6c,0x0,0x6c,0x0,0x20,0x0,0x6d,0x0,0x6f,0x0,0x64,0x0, + 0x69,0x0,0x66,0x0,0x69,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x61,0x0,0x6e,0x0,0x64,0x0,0x20,0x0,0x75,0x0,0x6e,0x0,0x6d,0x0,0x6f,0x0,0x64,0x0, + 0x69,0x0,0x66,0x0,0x69,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x63,0x0,0x6f,0x0,0x70,0x0,0x69,0x0,0x65,0x0,0x73,0x0,0x20,0x0,0x6f,0x0,0x66,0x0, + 0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x46,0x0,0x6f,0x0,0x6e,0x0,0x74,0x0,0x20,0x0,0x53,0x0,0x6f,0x0,0x66,0x0,0x74,0x0,0x77,0x0, + 0x61,0x0,0x72,0x0,0x65,0x0,0xa,0x0,0x73,0x0,0x75,0x0,0x62,0x0,0x6a,0x0,0x65,0x0,0x63,0x0,0x74,0x0,0x20,0x0,0x74,0x0,0x6f,0x0,0x20,0x0, + 0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x66,0x0,0x6f,0x0,0x6c,0x0,0x6c,0x0,0x6f,0x0,0x77,0x0,0x69,0x0,0x6e,0x0,0x67,0x0,0x20,0x0,0x63,0x0, + 0x6f,0x0,0x6e,0x0,0x64,0x0,0x69,0x0,0x74,0x0,0x69,0x0,0x6f,0x0,0x6e,0x0,0x73,0x0,0x3a,0x0,0xa,0x0,0xa,0x0,0x31,0x0,0x29,0x0,0x20,0x0, + 0x4e,0x0,0x65,0x0,0x69,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x72,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x46,0x0,0x6f,0x0,0x6e,0x0, + 0x74,0x0,0x20,0x0,0x53,0x0,0x6f,0x0,0x66,0x0,0x74,0x0,0x77,0x0,0x61,0x0,0x72,0x0,0x65,0x0,0x20,0x0,0x6e,0x0,0x6f,0x0,0x72,0x0,0x20,0x0, + 0x61,0x0,0x6e,0x0,0x79,0x0,0x20,0x0,0x6f,0x0,0x66,0x0,0x20,0x0,0x69,0x0,0x74,0x0,0x73,0x0,0x20,0x0,0x69,0x0,0x6e,0x0,0x64,0x0,0x69,0x0, + 0x76,0x0,0x69,0x0,0x64,0x0,0x75,0x0,0x61,0x0,0x6c,0x0,0x20,0x0,0x63,0x0,0x6f,0x0,0x6d,0x0,0x70,0x0,0x6f,0x0,0x6e,0x0,0x65,0x0,0x6e,0x0, + 0x74,0x0,0x73,0x0,0xa,0x0,0x69,0x0,0x6e,0x0,0x20,0x0,0x4f,0x0,0x72,0x0,0x69,0x0,0x67,0x0,0x69,0x0,0x6e,0x0,0x61,0x0,0x6c,0x0,0x20,0x0, + 0x6f,0x0,0x72,0x0,0x20,0x0,0x4d,0x0,0x6f,0x0,0x64,0x0,0x69,0x0,0x66,0x0,0x69,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x56,0x0,0x65,0x0,0x72,0x0, + 0x73,0x0,0x69,0x0,0x6f,0x0,0x6e,0x0,0x73,0x0,0xa,0x0,0x6d,0x0,0x61,0x0,0x79,0x0,0x20,0x0,0x62,0x0,0x65,0x0,0x20,0x0,0x73,0x0,0x6f,0x0, + 0x6c,0x0,0x64,0x0,0x20,0x0,0x62,0x0,0x79,0x0,0x20,0x0,0x69,0x0,0x74,0x0,0x73,0x0,0x65,0x0,0x6c,0x0,0x66,0x0,0x2e,0x0,0xa,0x0,0xa,0x0, + 0x32,0x0,0x29,0x0,0x20,0x0,0x4f,0x0,0x72,0x0,0x69,0x0,0x67,0x0,0x69,0x0,0x6e,0x0,0x61,0x0,0x6c,0x0,0x20,0x0,0x6f,0x0,0x72,0x0,0x20,0x0, + 0x4d,0x0,0x6f,0x0,0x64,0x0,0x69,0x0,0x66,0x0,0x69,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x56,0x0,0x65,0x0,0x72,0x0,0x73,0x0,0x69,0x0,0x6f,0x0, + 0x6e,0x0,0x73,0x0,0x20,0x0,0x6f,0x0,0x66,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x46,0x0,0x6f,0x0,0x6e,0x0,0x74,0x0,0x20,0x0, + 0x53,0x0,0x6f,0x0,0x66,0x0,0x74,0x0,0x77,0x0,0x61,0x0,0x72,0x0,0x65,0x0,0x20,0x0,0x6d,0x0,0x61,0x0,0x79,0x0,0x20,0x0,0x62,0x0,0x65,0x0, + 0x20,0x0,0x62,0x0,0x75,0x0,0x6e,0x0,0x64,0x0,0x6c,0x0,0x65,0x0,0x64,0x0,0xa,0x0,0x72,0x0,0x65,0x0,0x64,0x0,0x69,0x0,0x73,0x0,0x74,0x0, + 0x72,0x0,0x69,0x0,0x62,0x0,0x75,0x0,0x74,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x61,0x0,0x6e,0x0,0x64,0x0,0x2f,0x0,0x6f,0x0,0x72,0x0,0x20,0x0, + 0x73,0x0,0x6f,0x0,0x6c,0x0,0x64,0x0,0x20,0x0,0x77,0x0,0x69,0x0,0x74,0x0,0x68,0x0,0x20,0x0,0x61,0x0,0x6e,0x0,0x79,0x0,0x20,0x0,0x73,0x0, + 0x6f,0x0,0x66,0x0,0x74,0x0,0x77,0x0,0x61,0x0,0x72,0x0,0x65,0x0,0xa,0x0,0x70,0x0,0x72,0x0,0x6f,0x0,0x76,0x0,0x69,0x0,0x64,0x0,0x65,0x0, + 0x64,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x61,0x0,0x74,0x0,0x20,0x0,0x65,0x0,0x61,0x0,0x63,0x0,0x68,0x0,0x20,0x0,0x63,0x0,0x6f,0x0,0x70,0x0, + 0x79,0x0,0x20,0x0,0x63,0x0,0x6f,0x0,0x6e,0x0,0x74,0x0,0x61,0x0,0x69,0x0,0x6e,0x0,0x73,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0, + 0x61,0x0,0x62,0x0,0x6f,0x0,0x76,0x0,0x65,0x0,0x20,0x0,0x63,0x0,0x6f,0x0,0x70,0x0,0x79,0x0,0x72,0x0,0x69,0x0,0x67,0x0,0x68,0x0,0x74,0x0, + 0x20,0x0,0x6e,0x0,0x6f,0x0,0x74,0x0,0x69,0x0,0x63,0x0,0x65,0x0,0x20,0x0,0x61,0x0,0x6e,0x0,0x64,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x69,0x0, + 0x73,0x0,0x20,0x0,0x6c,0x0,0x69,0x0,0x63,0x0,0x65,0x0,0x6e,0x0,0x73,0x0,0x65,0x0,0x2e,0x0,0x20,0x0,0x54,0x0,0x68,0x0,0x65,0x0,0x73,0x0, + 0x65,0x0,0x20,0x0,0x63,0x0,0x61,0x0,0x6e,0x0,0x20,0x0,0x62,0x0,0x65,0x0,0x20,0x0,0x69,0x0,0x6e,0x0,0x63,0x0,0x6c,0x0,0x75,0x0,0x64,0x0, + 0x65,0x0,0x64,0x0,0x20,0x0,0x65,0x0,0x69,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x72,0x0,0x20,0x0,0x61,0x0,0x73,0x0,0x20,0x0,0x73,0x0,0x74,0x0, + 0x61,0x0,0x6e,0x0,0x64,0x0,0x2d,0x0,0x61,0x0,0x6c,0x0,0x6f,0x0,0x6e,0x0,0x65,0x0,0x20,0x0,0x74,0x0,0x65,0x0,0x78,0x0,0x74,0x0,0x20,0x0, + 0x66,0x0,0x69,0x0,0x6c,0x0,0x65,0x0,0x73,0x0,0xa,0x0,0x68,0x0,0x75,0x0,0x6d,0x0,0x61,0x0,0x6e,0x0,0x2d,0x0,0x72,0x0,0x65,0x0,0x61,0x0, + 0x64,0x0,0x61,0x0,0x62,0x0,0x6c,0x0,0x65,0x0,0x20,0x0,0x68,0x0,0x65,0x0,0x61,0x0,0x64,0x0,0x65,0x0,0x72,0x0,0x73,0x0,0x20,0x0,0x6f,0x0, + 0x72,0x0,0x20,0x0,0x69,0x0,0x6e,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x61,0x0,0x70,0x0,0x70,0x0,0x72,0x0,0x6f,0x0,0x70,0x0, + 0x72,0x0,0x69,0x0,0x61,0x0,0x74,0x0,0x65,0x0,0x20,0x0,0x6d,0x0,0x61,0x0,0x63,0x0,0x68,0x0,0x69,0x0,0x6e,0x0,0x65,0x0,0x2d,0x0,0x72,0x0, + 0x65,0x0,0x61,0x0,0x64,0x0,0x61,0x0,0x62,0x0,0x6c,0x0,0x65,0x0,0x20,0x0,0x6d,0x0,0x65,0x0,0x74,0x0,0x61,0x0,0x64,0x0,0x61,0x0,0x74,0x0, + 0x61,0x0,0x20,0x0,0x66,0x0,0x69,0x0,0x65,0x0,0x6c,0x0,0x64,0x0,0x73,0x0,0x20,0x0,0x77,0x0,0x69,0x0,0x74,0x0,0x68,0x0,0x69,0x0,0x6e,0x0, + 0x20,0x0,0x74,0x0,0x65,0x0,0x78,0x0,0x74,0x0,0x20,0x0,0x6f,0x0,0x72,0x0,0x20,0x0,0x62,0x0,0x69,0x0,0x6e,0x0,0x61,0x0,0x72,0x0,0x79,0x0, + 0x20,0x0,0x66,0x0,0x69,0x0,0x6c,0x0,0x65,0x0,0x73,0x0,0x20,0x0,0x61,0x0,0x73,0x0,0x20,0x0,0x6c,0x0,0x6f,0x0,0x6e,0x0,0x67,0x0,0x20,0x0, + 0x61,0x0,0x73,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x6f,0x0,0x73,0x0,0x65,0x0,0x20,0x0,0x66,0x0,0x69,0x0,0x65,0x0,0x6c,0x0,0x64,0x0,0x73,0x0, + 0x20,0x0,0x63,0x0,0x61,0x0,0x6e,0x0,0x20,0x0,0x62,0x0,0x65,0x0,0x20,0x0,0x65,0x0,0x61,0x0,0x73,0x0,0x69,0x0,0x6c,0x0,0x79,0x0,0x20,0x0, + 0x76,0x0,0x69,0x0,0x65,0x0,0x77,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x62,0x0,0x79,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x75,0x0, + 0x73,0x0,0x65,0x0,0x72,0x0,0x2e,0x0,0xa,0x0,0xa,0x0,0x33,0x0,0x29,0x0,0x20,0x0,0x4e,0x0,0x6f,0x0,0x20,0x0,0x4d,0x0,0x6f,0x0,0x64,0x0, + 0x69,0x0,0x66,0x0,0x69,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x56,0x0,0x65,0x0,0x72,0x0,0x73,0x0,0x69,0x0,0x6f,0x0,0x6e,0x0,0x20,0x0,0x6f,0x0, + 0x66,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x46,0x0,0x6f,0x0,0x6e,0x0,0x74,0x0,0x20,0x0,0x53,0x0,0x6f,0x0,0x66,0x0,0x74,0x0, + 0x77,0x0,0x61,0x0,0x72,0x0,0x65,0x0,0x20,0x0,0x6d,0x0,0x61,0x0,0x79,0x0,0x20,0x0,0x75,0x0,0x73,0x0,0x65,0x0,0x20,0x0,0x74,0x0,0x68,0x0, + 0x65,0x0,0x20,0x0,0x52,0x0,0x65,0x0,0x73,0x0,0x65,0x0,0x72,0x0,0x76,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x46,0x0,0x6f,0x0,0x6e,0x0,0x74,0x0, + 0x20,0x0,0x4e,0x0,0x61,0x0,0x6d,0x0,0x65,0x0,0x28,0x0,0x73,0x0,0x29,0x0,0x20,0x0,0x75,0x0,0x6e,0x0,0x6c,0x0,0x65,0x0,0x73,0x0,0x73,0x0, + 0x20,0x0,0x65,0x0,0x78,0x0,0x70,0x0,0x6c,0x0,0x69,0x0,0x63,0x0,0x69,0x0,0x74,0x0,0x20,0x0,0x77,0x0,0x72,0x0,0x69,0x0,0x74,0x0,0x74,0x0, + 0x65,0x0,0x6e,0x0,0x20,0x0,0x70,0x0,0x65,0x0,0x72,0x0,0x6d,0x0,0x69,0x0,0x73,0x0,0x73,0x0,0x69,0x0,0x6f,0x0,0x6e,0x0,0x20,0x0,0x69,0x0, + 0x73,0x0,0x20,0x0,0x67,0x0,0x72,0x0,0x61,0x0,0x6e,0x0,0x74,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x62,0x0,0x79,0x0,0x20,0x0,0x74,0x0,0x68,0x0, + 0x65,0x0,0x20,0x0,0x63,0x0,0x6f,0x0,0x72,0x0,0x72,0x0,0x65,0x0,0x73,0x0,0x70,0x0,0x6f,0x0,0x6e,0x0,0x64,0x0,0x69,0x0,0x6e,0x0,0x67,0x0, + 0x20,0x0,0x43,0x0,0x6f,0x0,0x70,0x0,0x79,0x0,0x72,0x0,0x69,0x0,0x67,0x0,0x68,0x0,0x74,0x0,0x20,0x0,0x48,0x0,0x6f,0x0,0x6c,0x0,0x64,0x0, + 0x65,0x0,0x72,0x0,0x2e,0x0,0x20,0x0,0x54,0x0,0x68,0x0,0x69,0x0,0x73,0x0,0x20,0x0,0x72,0x0,0x65,0x0,0x73,0x0,0x74,0x0,0x72,0x0,0x69,0x0, + 0x63,0x0,0x74,0x0,0x69,0x0,0x6f,0x0,0x6e,0x0,0x20,0x0,0x6f,0x0,0x6e,0x0,0x6c,0x0,0x79,0x0,0x20,0x0,0x61,0x0,0x70,0x0,0x70,0x0,0x6c,0x0, + 0x69,0x0,0x65,0x0,0x73,0x0,0x20,0x0,0x74,0x0,0x6f,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x70,0x0,0x72,0x0,0x69,0x0,0x6d,0x0, + 0x61,0x0,0x72,0x0,0x79,0x0,0x20,0x0,0x66,0x0,0x6f,0x0,0x6e,0x0,0x74,0x0,0x20,0x0,0x6e,0x0,0x61,0x0,0x6d,0x0,0x65,0x0,0x20,0x0,0x61,0x0, + 0x73,0x0,0x20,0x0,0x70,0x0,0x72,0x0,0x65,0x0,0x73,0x0,0x65,0x0,0x6e,0x0,0x74,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x74,0x0,0x6f,0x0,0x20,0x0, + 0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x75,0x0,0x73,0x0,0x65,0x0,0x72,0x0,0x73,0x0,0x2e,0x0,0xa,0x0,0xa,0x0,0x34,0x0,0x29,0x0,0x20,0x0, + 0x54,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x6e,0x0,0x61,0x0,0x6d,0x0,0x65,0x0,0x28,0x0,0x73,0x0,0x29,0x0,0x20,0x0,0x6f,0x0,0x66,0x0,0x20,0x0, + 0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x43,0x0,0x6f,0x0,0x70,0x0,0x79,0x0,0x72,0x0,0x69,0x0,0x67,0x0,0x68,0x0,0x74,0x0,0x20,0x0,0x48,0x0, + 0x6f,0x0,0x6c,0x0,0x64,0x0,0x65,0x0,0x72,0x0,0x28,0x0,0x73,0x0,0x29,0x0,0x20,0x0,0x6f,0x0,0x72,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0, + 0x20,0x0,0x41,0x0,0x75,0x0,0x74,0x0,0x68,0x0,0x6f,0x0,0x72,0x0,0x28,0x0,0x73,0x0,0x29,0x0,0x20,0x0,0x6f,0x0,0x66,0x0,0x20,0x0,0x74,0x0, + 0x68,0x0,0x65,0x0,0x20,0x0,0x46,0x0,0x6f,0x0,0x6e,0x0,0x74,0x0,0x20,0x0,0x53,0x0,0x6f,0x0,0x66,0x0,0x74,0x0,0x77,0x0,0x61,0x0,0x72,0x0, + 0x65,0x0,0x20,0x0,0x73,0x0,0x68,0x0,0x61,0x0,0x6c,0x0,0x6c,0x0,0x20,0x0,0x6e,0x0,0x6f,0x0,0x74,0x0,0x20,0x0,0x62,0x0,0x65,0x0,0x20,0x0, + 0x75,0x0,0x73,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x74,0x0,0x6f,0x0,0x20,0x0,0x70,0x0,0x72,0x0,0x6f,0x0,0x6d,0x0,0x6f,0x0,0x74,0x0,0x65,0x0, + 0xa,0x0,0x65,0x0,0x6e,0x0,0x64,0x0,0x6f,0x0,0x72,0x0,0x73,0x0,0x65,0x0,0x20,0x0,0x6f,0x0,0x72,0x0,0x20,0x0,0x61,0x0,0x64,0x0,0x76,0x0, + 0x65,0x0,0x72,0x0,0x74,0x0,0x69,0x0,0x73,0x0,0x65,0x0,0x20,0x0,0x61,0x0,0x6e,0x0,0x79,0x0,0x20,0x0,0x4d,0x0,0x6f,0x0,0x64,0x0,0x69,0x0, + 0x66,0x0,0x69,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x56,0x0,0x65,0x0,0x72,0x0,0x73,0x0,0x69,0x0,0x6f,0x0,0x6e,0x0,0xa,0x0,0x65,0x0,0x78,0x0, + 0x63,0x0,0x65,0x0,0x70,0x0,0x74,0x0,0x20,0x0,0x74,0x0,0x6f,0x0,0x20,0x0,0x61,0x0,0x63,0x0,0x6b,0x0,0x6e,0x0,0x6f,0x0,0x77,0x0,0x6c,0x0, + 0x65,0x0,0x64,0x0,0x67,0x0,0x65,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x63,0x0,0x6f,0x0,0x6e,0x0,0x74,0x0,0x72,0x0,0x69,0x0, + 0x62,0x0,0x75,0x0,0x74,0x0,0x69,0x0,0x6f,0x0,0x6e,0x0,0x28,0x0,0x73,0x0,0x29,0x0,0x20,0x0,0x6f,0x0,0x66,0x0,0x20,0x0,0x74,0x0,0x68,0x0, + 0x65,0x0,0x20,0x0,0x43,0x0,0x6f,0x0,0x70,0x0,0x79,0x0,0x72,0x0,0x69,0x0,0x67,0x0,0x68,0x0,0x74,0x0,0x20,0x0,0x48,0x0,0x6f,0x0,0x6c,0x0, + 0x64,0x0,0x65,0x0,0x72,0x0,0x28,0x0,0x73,0x0,0x29,0x0,0x20,0x0,0x61,0x0,0x6e,0x0,0x64,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0, + 0x41,0x0,0x75,0x0,0x74,0x0,0x68,0x0,0x6f,0x0,0x72,0x0,0x28,0x0,0x73,0x0,0x29,0x0,0x20,0x0,0x6f,0x0,0x72,0x0,0x20,0x0,0x77,0x0,0x69,0x0, + 0x74,0x0,0x68,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x69,0x0,0x72,0x0,0x20,0x0,0x65,0x0,0x78,0x0,0x70,0x0,0x6c,0x0,0x69,0x0,0x63,0x0, + 0x69,0x0,0x74,0x0,0x20,0x0,0x77,0x0,0x72,0x0,0x69,0x0,0x74,0x0,0x74,0x0,0x65,0x0,0x6e,0x0,0x20,0x0,0x70,0x0,0x65,0x0,0x72,0x0,0x6d,0x0, + 0x69,0x0,0x73,0x0,0x73,0x0,0x69,0x0,0x6f,0x0,0x6e,0x0,0x2e,0x0,0xa,0x0,0xa,0x0,0x35,0x0,0x29,0x0,0x20,0x0,0x54,0x0,0x68,0x0,0x65,0x0, + 0x20,0x0,0x46,0x0,0x6f,0x0,0x6e,0x0,0x74,0x0,0x20,0x0,0x53,0x0,0x6f,0x0,0x66,0x0,0x74,0x0,0x77,0x0,0x61,0x0,0x72,0x0,0x65,0x0,0xa,0x0, + 0x6d,0x0,0x6f,0x0,0x64,0x0,0x69,0x0,0x66,0x0,0x69,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x6f,0x0,0x72,0x0,0x20,0x0,0x75,0x0,0x6e,0x0,0x6d,0x0, + 0x6f,0x0,0x64,0x0,0x69,0x0,0x66,0x0,0x69,0x0,0x65,0x0,0x64,0x0,0xa,0x0,0x69,0x0,0x6e,0x0,0x20,0x0,0x70,0x0,0x61,0x0,0x72,0x0,0x74,0x0, + 0x20,0x0,0x6f,0x0,0x72,0x0,0x20,0x0,0x69,0x0,0x6e,0x0,0x20,0x0,0x77,0x0,0x68,0x0,0x6f,0x0,0x6c,0x0,0x65,0x0,0xa,0x0,0x6d,0x0,0x75,0x0, + 0x73,0x0,0x74,0x0,0x20,0x0,0x62,0x0,0x65,0x0,0x20,0x0,0x64,0x0,0x69,0x0,0x73,0x0,0x74,0x0,0x72,0x0,0x69,0x0,0x62,0x0,0x75,0x0,0x74,0x0, + 0x65,0x0,0x64,0x0,0x20,0x0,0x65,0x0,0x6e,0x0,0x74,0x0,0x69,0x0,0x72,0x0,0x65,0x0,0x6c,0x0,0x79,0x0,0x20,0x0,0x75,0x0,0x6e,0x0,0x64,0x0, + 0x65,0x0,0x72,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x69,0x0,0x73,0x0,0x20,0x0,0x6c,0x0,0x69,0x0,0x63,0x0,0x65,0x0,0x6e,0x0,0x73,0x0,0x65,0x0, + 0xa,0x0,0x61,0x0,0x6e,0x0,0x64,0x0,0x20,0x0,0x6d,0x0,0x75,0x0,0x73,0x0,0x74,0x0,0x20,0x0,0x6e,0x0,0x6f,0x0,0x74,0x0,0x20,0x0,0x62,0x0, + 0x65,0x0,0x20,0x0,0x64,0x0,0x69,0x0,0x73,0x0,0x74,0x0,0x72,0x0,0x69,0x0,0x62,0x0,0x75,0x0,0x74,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x75,0x0, + 0x6e,0x0,0x64,0x0,0x65,0x0,0x72,0x0,0x20,0x0,0x61,0x0,0x6e,0x0,0x79,0x0,0x20,0x0,0x6f,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x72,0x0,0x20,0x0, + 0x6c,0x0,0x69,0x0,0x63,0x0,0x65,0x0,0x6e,0x0,0x73,0x0,0x65,0x0,0x2e,0x0,0x20,0x0,0x54,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x72,0x0,0x65,0x0, + 0x71,0x0,0x75,0x0,0x69,0x0,0x72,0x0,0x65,0x0,0x6d,0x0,0x65,0x0,0x6e,0x0,0x74,0x0,0x20,0x0,0x66,0x0,0x6f,0x0,0x72,0x0,0x20,0x0,0x66,0x0, + 0x6f,0x0,0x6e,0x0,0x74,0x0,0x73,0x0,0x20,0x0,0x74,0x0,0x6f,0x0,0x20,0x0,0x72,0x0,0x65,0x0,0x6d,0x0,0x61,0x0,0x69,0x0,0x6e,0x0,0x20,0x0, + 0x75,0x0,0x6e,0x0,0x64,0x0,0x65,0x0,0x72,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x69,0x0,0x73,0x0,0x20,0x0,0x6c,0x0,0x69,0x0,0x63,0x0,0x65,0x0, + 0x6e,0x0,0x73,0x0,0x65,0x0,0x20,0x0,0x64,0x0,0x6f,0x0,0x65,0x0,0x73,0x0,0x20,0x0,0x6e,0x0,0x6f,0x0,0x74,0x0,0x20,0x0,0x61,0x0,0x70,0x0, + 0x70,0x0,0x6c,0x0,0x79,0x0,0x20,0x0,0x74,0x0,0x6f,0x0,0x20,0x0,0x61,0x0,0x6e,0x0,0x79,0x0,0x20,0x0,0x64,0x0,0x6f,0x0,0x63,0x0,0x75,0x0, + 0x6d,0x0,0x65,0x0,0x6e,0x0,0x74,0x0,0x20,0x0,0x63,0x0,0x72,0x0,0x65,0x0,0x61,0x0,0x74,0x0,0x65,0x0,0x64,0x0,0x20,0x0,0x75,0x0,0x73,0x0, + 0x69,0x0,0x6e,0x0,0x67,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x46,0x0,0x6f,0x0,0x6e,0x0,0x74,0x0,0x20,0x0,0x53,0x0,0x6f,0x0, + 0x66,0x0,0x74,0x0,0x77,0x0,0x61,0x0,0x72,0x0,0x65,0x0,0x2e,0x0,0xa,0x0,0xa,0x0,0x54,0x0,0x45,0x0,0x52,0x0,0x4d,0x0,0x49,0x0,0x4e,0x0, + 0x41,0x0,0x54,0x0,0x49,0x0,0x4f,0x0,0x4e,0x0,0xa,0x0,0x54,0x0,0x68,0x0,0x69,0x0,0x73,0x0,0x20,0x0,0x6c,0x0,0x69,0x0,0x63,0x0,0x65,0x0, + 0x6e,0x0,0x73,0x0,0x65,0x0,0x20,0x0,0x62,0x0,0x65,0x0,0x63,0x0,0x6f,0x0,0x6d,0x0,0x65,0x0,0x73,0x0,0x20,0x0,0x6e,0x0,0x75,0x0,0x6c,0x0, + 0x6c,0x0,0x20,0x0,0x61,0x0,0x6e,0x0,0x64,0x0,0x20,0x0,0x76,0x0,0x6f,0x0,0x69,0x0,0x64,0x0,0x20,0x0,0x69,0x0,0x66,0x0,0x20,0x0,0x61,0x0, + 0x6e,0x0,0x79,0x0,0x20,0x0,0x6f,0x0,0x66,0x0,0x20,0x0,0x74,0x0,0x68,0x0,0x65,0x0,0x20,0x0,0x61,0x0,0x62,0x0,0x6f,0x0,0x76,0x0,0x65,0x0, + 0x20,0x0,0x63,0x0,0x6f,0x0,0x6e,0x0,0x64,0x0,0x69,0x0,0x74,0x0,0x69,0x0,0x6f,0x0,0x6e,0x0,0x73,0x0,0x20,0x0,0x61,0x0,0x72,0x0,0x65,0x0, + 0x20,0x0,0x6e,0x0,0x6f,0x0,0x74,0x0,0x20,0x0,0x6d,0x0,0x65,0x0,0x74,0x0,0x2e,0x0,0xa,0x0,0xa,0x0,0x44,0x0,0x49,0x0,0x53,0x0,0x43,0x0, + 0x4c,0x0,0x41,0x0,0x49,0x0,0x4d,0x0,0x45,0x0,0x52,0x0,0xa,0x0,0x54,0x0,0x48,0x0,0x45,0x0,0x20,0x0,0x46,0x0,0x4f,0x0,0x4e,0x0,0x54,0x0, + 0x20,0x0,0x53,0x0,0x4f,0x0,0x46,0x0,0x54,0x0,0x57,0x0,0x41,0x0,0x52,0x0,0x45,0x0,0x20,0x0,0x49,0x0,0x53,0x0,0x20,0x0,0x50,0x0,0x52,0x0, + 0x4f,0x0,0x56,0x0,0x49,0x0,0x44,0x0,0x45,0x0,0x44,0x0,0x20,0x0,0x22,0x0,0x41,0x0,0x53,0x0,0x20,0x0,0x49,0x0,0x53,0x0,0x22,0x0,0xa,0x0, + 0x57,0x0,0x49,0x0,0x54,0x0,0x48,0x0,0x4f,0x0,0x55,0x0,0x54,0x0,0x20,0x0,0x57,0x0,0x41,0x0,0x52,0x0,0x52,0x0,0x41,0x0,0x4e,0x0,0x54,0x0, + 0x59,0x0,0x20,0x0,0x4f,0x0,0x46,0x0,0x20,0x0,0x41,0x0,0x4e,0x0,0x59,0x0,0x20,0x0,0x4b,0x0,0x49,0x0,0x4e,0x0,0x44,0x0,0xa,0x0,0x45,0x0, + 0x58,0x0,0x50,0x0,0x52,0x0,0x45,0x0,0x53,0x0,0x53,0x0,0x20,0x0,0x4f,0x0,0x52,0x0,0x20,0x0,0x49,0x0,0x4d,0x0,0x50,0x0,0x4c,0x0,0x49,0x0, + 0x45,0x0,0x44,0x0,0xa,0x0,0x49,0x0,0x4e,0x0,0x43,0x0,0x4c,0x0,0x55,0x0,0x44,0x0,0x49,0x0,0x4e,0x0,0x47,0x0,0x20,0x0,0x42,0x0,0x55,0x0, + 0x54,0x0,0x20,0x0,0x4e,0x0,0x4f,0x0,0x54,0x0,0x20,0x0,0x4c,0x0,0x49,0x0,0x4d,0x0,0x49,0x0,0x54,0x0,0x45,0x0,0x44,0x0,0x20,0x0,0x54,0x0, + 0x4f,0x0,0x20,0x0,0x41,0x0,0x4e,0x0,0x59,0x0,0x20,0x0,0x57,0x0,0x41,0x0,0x52,0x0,0x52,0x0,0x41,0x0,0x4e,0x0,0x54,0x0,0x49,0x0,0x45,0x0, + 0x53,0x0,0x20,0x0,0x4f,0x0,0x46,0x0,0x20,0x0,0x4d,0x0,0x45,0x0,0x52,0x0,0x43,0x0,0x48,0x0,0x41,0x0,0x4e,0x0,0x54,0x0,0x41,0x0,0x42,0x0, + 0x49,0x0,0x4c,0x0,0x49,0x0,0x54,0x0,0x59,0x0,0xa,0x0,0x46,0x0,0x49,0x0,0x54,0x0,0x4e,0x0,0x45,0x0,0x53,0x0,0x53,0x0,0x20,0x0,0x46,0x0, + 0x4f,0x0,0x52,0x0,0x20,0x0,0x41,0x0,0x20,0x0,0x50,0x0,0x41,0x0,0x52,0x0,0x54,0x0,0x49,0x0,0x43,0x0,0x55,0x0,0x4c,0x0,0x41,0x0,0x52,0x0, + 0x20,0x0,0x50,0x0,0x55,0x0,0x52,0x0,0x50,0x0,0x4f,0x0,0x53,0x0,0x45,0x0,0x20,0x0,0x41,0x0,0x4e,0x0,0x44,0x0,0x20,0x0,0x4e,0x0,0x4f,0x0, + 0x4e,0x0,0x49,0x0,0x4e,0x0,0x46,0x0,0x52,0x0,0x49,0x0,0x4e,0x0,0x47,0x0,0x45,0x0,0x4d,0x0,0x45,0x0,0x4e,0x0,0x54,0x0,0x20,0x0,0x4f,0x0, + 0x46,0x0,0x20,0x0,0x43,0x0,0x4f,0x0,0x50,0x0,0x59,0x0,0x52,0x0,0x49,0x0,0x47,0x0,0x48,0x0,0x54,0x0,0xa,0x0,0x50,0x0,0x41,0x0,0x54,0x0, + 0x45,0x0,0x4e,0x0,0x54,0x0,0xa,0x0,0x54,0x0,0x52,0x0,0x41,0x0,0x44,0x0,0x45,0x0,0x4d,0x0,0x41,0x0,0x52,0x0,0x4b,0x0,0xa,0x0,0x4f,0x0, + 0x52,0x0,0x20,0x0,0x4f,0x0,0x54,0x0,0x48,0x0,0x45,0x0,0x52,0x0,0x20,0x0,0x52,0x0,0x49,0x0,0x47,0x0,0x48,0x0,0x54,0x0,0x2e,0x0,0x20,0x0, + 0x49,0x0,0x4e,0x0,0x20,0x0,0x4e,0x0,0x4f,0x0,0x20,0x0,0x45,0x0,0x56,0x0,0x45,0x0,0x4e,0x0,0x54,0x0,0x20,0x0,0x53,0x0,0x48,0x0,0x41,0x0, + 0x4c,0x0,0x4c,0x0,0x20,0x0,0x54,0x0,0x48,0x0,0x45,0x0,0x20,0x0,0x43,0x0,0x4f,0x0,0x50,0x0,0x59,0x0,0x52,0x0,0x49,0x0,0x47,0x0,0x48,0x0, + 0x54,0x0,0x20,0x0,0x48,0x0,0x4f,0x0,0x4c,0x0,0x44,0x0,0x45,0x0,0x52,0x0,0x20,0x0,0x42,0x0,0x45,0x0,0x20,0x0,0x4c,0x0,0x49,0x0,0x41,0x0, + 0x42,0x0,0x4c,0x0,0x45,0x0,0x20,0x0,0x46,0x0,0x4f,0x0,0x52,0x0,0x20,0x0,0x41,0x0,0x4e,0x0,0x59,0x0,0x20,0x0,0x43,0x0,0x4c,0x0,0x41,0x0, + 0x49,0x0,0x4d,0x0,0xa,0x0,0x44,0x0,0x41,0x0,0x4d,0x0,0x41,0x0,0x47,0x0,0x45,0x0,0x53,0x0,0x20,0x0,0x4f,0x0,0x52,0x0,0x20,0x0,0x4f,0x0, + 0x54,0x0,0x48,0x0,0x45,0x0,0x52,0x0,0x20,0x0,0x4c,0x0,0x49,0x0,0x41,0x0,0x42,0x0,0x49,0x0,0x4c,0x0,0x49,0x0,0x54,0x0,0x59,0x0,0xa,0x0, + 0x49,0x0,0x4e,0x0,0x43,0x0,0x4c,0x0,0x55,0x0,0x44,0x0,0x49,0x0,0x4e,0x0,0x47,0x0,0x20,0x0,0x41,0x0,0x4e,0x0,0x59,0x0,0x20,0x0,0x47,0x0, + 0x45,0x0,0x4e,0x0,0x45,0x0,0x52,0x0,0x41,0x0,0x4c,0x0,0xa,0x0,0x53,0x0,0x50,0x0,0x45,0x0,0x43,0x0,0x49,0x0,0x41,0x0,0x4c,0x0,0xa,0x0, + 0x49,0x0,0x4e,0x0,0x44,0x0,0x49,0x0,0x52,0x0,0x45,0x0,0x43,0x0,0x54,0x0,0xa,0x0,0x49,0x0,0x4e,0x0,0x43,0x0,0x49,0x0,0x44,0x0,0x45,0x0, + 0x4e,0x0,0x54,0x0,0x41,0x0,0x4c,0x0,0xa,0x0,0x4f,0x0,0x52,0x0,0x20,0x0,0x43,0x0,0x4f,0x0,0x4e,0x0,0x53,0x0,0x45,0x0,0x51,0x0,0x55,0x0, + 0x45,0x0,0x4e,0x0,0x54,0x0,0x49,0x0,0x41,0x0,0x4c,0x0,0x20,0x0,0x44,0x0,0x41,0x0,0x4d,0x0,0x41,0x0,0x47,0x0,0x45,0x0,0x53,0x0,0xa,0x0, + 0x57,0x0,0x48,0x0,0x45,0x0,0x54,0x0,0x48,0x0,0x45,0x0,0x52,0x0,0x20,0x0,0x49,0x0,0x4e,0x0,0x20,0x0,0x41,0x0,0x4e,0x0,0x20,0x0,0x41,0x0, + 0x43,0x0,0x54,0x0,0x49,0x0,0x4f,0x0,0x4e,0x0,0x20,0x0,0x4f,0x0,0x46,0x0,0x20,0x0,0x43,0x0,0x4f,0x0,0x4e,0x0,0x54,0x0,0x52,0x0,0x41,0x0, + 0x43,0x0,0x54,0x0,0xa,0x0,0x54,0x0,0x4f,0x0,0x52,0x0,0x54,0x0,0x20,0x0,0x4f,0x0,0x52,0x0,0x20,0x0,0x4f,0x0,0x54,0x0,0x48,0x0,0x45,0x0, + 0x52,0x0,0x57,0x0,0x49,0x0,0x53,0x0,0x45,0x0,0xa,0x0,0x41,0x0,0x52,0x0,0x49,0x0,0x53,0x0,0x49,0x0,0x4e,0x0,0x47,0x0,0x20,0x0,0x46,0x0, + 0x52,0x0,0x4f,0x0,0x4d,0x0,0xa,0x0,0x4f,0x0,0x55,0x0,0x54,0x0,0x20,0x0,0x4f,0x0,0x46,0x0,0x20,0x0,0x54,0x0,0x48,0x0,0x45,0x0,0x20,0x0, + 0x55,0x0,0x53,0x0,0x45,0x0,0x20,0x0,0x4f,0x0,0x52,0x0,0x20,0x0,0x49,0x0,0x4e,0x0,0x41,0x0,0x42,0x0,0x49,0x0,0x4c,0x0,0x49,0x0,0x54,0x0, + 0x59,0x0,0x20,0x0,0x54,0x0,0x4f,0x0,0x20,0x0,0x55,0x0,0x53,0x0,0x45,0x0,0x20,0x0,0x54,0x0,0x48,0x0,0x45,0x0,0x20,0x0,0x46,0x0,0x4f,0x0, + 0x4e,0x0,0x54,0x0,0x20,0x0,0x53,0x0,0x4f,0x0,0x46,0x0,0x54,0x0,0x57,0x0,0x41,0x0,0x52,0x0,0x45,0x0,0x20,0x0,0x4f,0x0,0x52,0x0,0x20,0x0, + 0x46,0x0,0x52,0x0,0x4f,0x0,0x4d,0x0,0x20,0x0,0x4f,0x0,0x54,0x0,0x48,0x0,0x45,0x0,0x52,0x0,0x20,0x0,0x44,0x0,0x45,0x0,0x41,0x0,0x4c,0x0, + 0x49,0x0,0x4e,0x0,0x47,0x0,0x53,0x0,0x20,0x0,0x49,0x0,0x4e,0x0,0x20,0x0,0x54,0x0,0x48,0x0,0x45,0x0,0x20,0x0,0x46,0x0,0x4f,0x0,0x4e,0x0, + 0x54,0x0,0x20,0x0,0x53,0x0,0x4f,0x0,0x46,0x0,0x54,0x0,0x57,0x0,0x41,0x0,0x52,0x0,0x45,0x0,0x2e,0x0,0x68,0x0,0x74,0x0,0x74,0x0,0x70,0x0, + 0x3a,0x0,0x2f,0x0,0x2f,0x0,0x73,0x0,0x63,0x0,0x72,0x0,0x69,0x0,0x70,0x0,0x74,0x0,0x73,0x0,0x2e,0x0,0x73,0x0,0x69,0x0,0x6c,0x0,0x2e,0x0, + 0x6f,0x0,0x72,0x0,0x67,0x0,0x2f,0x0,0x4f,0x0,0x46,0x0,0x4c,0x0,0x0,0x2,0x0,0x0,0xff,0xf6,0x0,0x0,0xff,0x47,0x0,0x82,0x0,0x0,0x0,0x1, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0x9b,0x0,0x0,0x0,0x3,0x0,0x24,0x0,0xc9,0x1,0x2,0x0,0xc7, + 0x0,0x62,0x0,0xad,0x1,0x3,0x1,0x4,0x0,0x63,0x0,0xae,0x0,0x90,0x0,0x25,0x0,0x26,0x0,0xfd,0x0,0xff,0x0,0x64,0x1,0x5,0x1,0x6,0x0,0x27, + 0x0,0xe9,0x1,0x7,0x1,0x8,0x0,0x28,0x0,0x65,0x1,0x9,0x1,0xa,0x0,0xc8,0x0,0xca,0x1,0xb,0x0,0xcb,0x1,0xc,0x1,0xd,0x0,0x29,0x0,0x2a, + 0x0,0xf8,0x1,0xe,0x1,0xf,0x1,0x10,0x0,0x2b,0x1,0x11,0x1,0x12,0x0,0x2c,0x1,0x13,0x0,0xcc,0x1,0x14,0x0,0xcd,0x0,0xce,0x0,0xfa,0x0,0xcf, + 0x1,0x15,0x1,0x16,0x1,0x17,0x0,0x2d,0x1,0x18,0x0,0x2e,0x1,0x19,0x0,0x2f,0x1,0x1a,0x1,0x1b,0x1,0x1c,0x1,0x1d,0x0,0xe2,0x0,0x30,0x0,0x31, + 0x1,0x1e,0x1,0x1f,0x1,0x20,0x1,0x21,0x0,0x66,0x0,0x32,0x0,0xd0,0x1,0x22,0x0,0xd1,0x0,0x67,0x0,0xd3,0x1,0x23,0x1,0x24,0x0,0x91,0x0,0xaf, + 0x0,0xb0,0x0,0x33,0x0,0xed,0x0,0x34,0x0,0x35,0x1,0x25,0x1,0x26,0x1,0x27,0x0,0x36,0x1,0x28,0x0,0xe4,0x0,0xfb,0x1,0x29,0x1,0x2a,0x0,0x37, + 0x1,0x2b,0x1,0x2c,0x1,0x2d,0x1,0x2e,0x0,0x38,0x0,0xd4,0x1,0x2f,0x0,0xd5,0x0,0x68,0x0,0xd6,0x1,0x30,0x1,0x31,0x1,0x32,0x1,0x33,0x1,0x34, + 0x0,0x39,0x0,0x3a,0x1,0x35,0x1,0x36,0x1,0x37,0x1,0x38,0x0,0x3b,0x0,0x3c,0x0,0xeb,0x1,0x39,0x0,0xbb,0x1,0x3a,0x0,0x3d,0x1,0x3b,0x0,0xe6, + 0x1,0x3c,0x0,0x44,0x0,0x69,0x1,0x3d,0x0,0x6b,0x0,0x6c,0x0,0x6a,0x1,0x3e,0x1,0x3f,0x0,0x6e,0x0,0x6d,0x0,0xa0,0x0,0x45,0x0,0x46,0x0,0xfe, + 0x1,0x0,0x0,0x6f,0x1,0x40,0x1,0x41,0x0,0x47,0x0,0xea,0x1,0x42,0x1,0x1,0x0,0x48,0x0,0x70,0x1,0x43,0x1,0x44,0x0,0x72,0x0,0x73,0x1,0x45, + 0x0,0x71,0x1,0x46,0x1,0x47,0x0,0x49,0x0,0x4a,0x0,0xf9,0x1,0x48,0x1,0x49,0x1,0x4a,0x0,0x4b,0x1,0x4b,0x1,0x4c,0x0,0x4c,0x0,0xd7,0x0,0x74, + 0x1,0x4d,0x0,0x76,0x0,0x77,0x1,0x4e,0x0,0x75,0x1,0x4f,0x1,0x50,0x1,0x51,0x1,0x52,0x0,0x4d,0x1,0x53,0x1,0x54,0x0,0x4e,0x1,0x55,0x0,0x4f, + 0x1,0x56,0x1,0x57,0x1,0x58,0x1,0x59,0x0,0xe3,0x0,0x50,0x0,0x51,0x1,0x5a,0x1,0x5b,0x1,0x5c,0x1,0x5d,0x0,0x78,0x0,0x52,0x0,0x79,0x1,0x5e, + 0x0,0x7b,0x0,0x7c,0x0,0x7a,0x1,0x5f,0x1,0x60,0x0,0xa1,0x0,0x7d,0x0,0xb1,0x0,0x53,0x0,0xee,0x0,0x54,0x0,0x55,0x1,0x61,0x1,0x62,0x1,0x63, + 0x0,0x56,0x1,0x64,0x0,0xe5,0x0,0xfc,0x1,0x65,0x1,0x66,0x0,0x89,0x0,0x57,0x1,0x67,0x1,0x68,0x1,0x69,0x1,0x6a,0x0,0x58,0x0,0x7e,0x1,0x6b, + 0x0,0x80,0x0,0x81,0x0,0x7f,0x1,0x6c,0x1,0x6d,0x1,0x6e,0x1,0x6f,0x1,0x70,0x0,0x59,0x0,0x5a,0x1,0x71,0x1,0x72,0x1,0x73,0x1,0x74,0x0,0x5b, + 0x0,0x5c,0x0,0xec,0x1,0x75,0x0,0xba,0x1,0x76,0x0,0x5d,0x1,0x77,0x0,0xe7,0x1,0x78,0x0,0xc0,0x0,0xc1,0x0,0x9d,0x0,0x9e,0x1,0x79,0x1,0x7a, + 0x1,0x7b,0x0,0x9b,0x0,0x13,0x0,0x14,0x0,0x15,0x0,0x16,0x0,0x17,0x0,0x18,0x0,0x19,0x0,0x1a,0x0,0x1b,0x0,0x1c,0x0,0xbc,0x0,0xf4,0x0,0xf5, + 0x0,0xf6,0x1,0x7c,0x1,0x7d,0x1,0x7e,0x1,0x7f,0x1,0x80,0x1,0x81,0x1,0x82,0x1,0x83,0x1,0x84,0x1,0x85,0x1,0x86,0x1,0x87,0x1,0x88,0x0,0xd, + 0x0,0x3f,0x0,0xc3,0x0,0x87,0x0,0x1d,0x0,0xf,0x0,0xab,0x0,0x4,0x0,0xa3,0x0,0x6,0x0,0x11,0x0,0x22,0x0,0xa2,0x0,0x5,0x0,0xa,0x0,0x1e, + 0x0,0x12,0x0,0x42,0x0,0x5e,0x0,0x60,0x0,0x3e,0x0,0x40,0x0,0xb,0x0,0xc,0x1,0x89,0x1,0x8a,0x1,0x8b,0x1,0x8c,0x1,0x8d,0x0,0xb3,0x0,0xb2, + 0x0,0x10,0x1,0x8e,0x1,0x8f,0x0,0xa9,0x0,0xaa,0x0,0xbe,0x0,0xbf,0x0,0xc5,0x0,0xb4,0x0,0xb5,0x0,0xb6,0x0,0xb7,0x0,0xc4,0x1,0x90,0x0,0x84, + 0x0,0xbd,0x0,0x7,0x1,0x91,0x0,0xa6,0x0,0xf7,0x0,0x85,0x1,0x92,0x0,0x96,0x0,0xa7,0x0,0x61,0x0,0xb8,0x0,0x20,0x0,0x21,0x0,0x95,0x0,0x92, + 0x0,0x9c,0x0,0x1f,0x0,0x94,0x0,0xa4,0x0,0xef,0x0,0xf0,0x0,0x8f,0x0,0x98,0x0,0x8,0x0,0xc6,0x0,0xe,0x0,0x93,0x0,0x9a,0x0,0xa5,0x0,0x99, + 0x0,0xb9,0x0,0x5f,0x0,0xe8,0x0,0x23,0x0,0x9,0x0,0x88,0x0,0x8b,0x0,0x8a,0x0,0x86,0x0,0x8c,0x0,0x83,0x0,0x41,0x0,0x82,0x0,0xc2,0x1,0x93, + 0x1,0x94,0x1,0x95,0x1,0x96,0x0,0x8d,0x1,0x97,0x0,0xdb,0x0,0xe1,0x1,0x98,0x1,0x99,0x1,0x9a,0x1,0x9b,0x1,0x9c,0x1,0x9d,0x1,0x9e,0x1,0x9f, + 0x1,0xa0,0x1,0xa1,0x1,0xa2,0x1,0xa3,0x1,0xa4,0x0,0xde,0x0,0xd8,0x0,0x8e,0x0,0xdc,0x0,0x43,0x0,0xdf,0x0,0xda,0x0,0xe0,0x0,0xdd,0x0,0xd9, + 0x6,0x41,0x62,0x72,0x65,0x76,0x65,0x7,0x41,0x6d,0x61,0x63,0x72,0x6f,0x6e,0x7,0x41,0x6f,0x67,0x6f,0x6e,0x65,0x6b,0xb,0x43,0x63,0x69,0x72,0x63,0x75, + 0x6d,0x66,0x6c,0x65,0x78,0xa,0x43,0x64,0x6f,0x74,0x61,0x63,0x63,0x65,0x6e,0x74,0x6,0x44,0x63,0x61,0x72,0x6f,0x6e,0x6,0x44,0x63,0x72,0x6f,0x61,0x74, + 0x6,0x45,0x62,0x72,0x65,0x76,0x65,0x6,0x45,0x63,0x61,0x72,0x6f,0x6e,0xa,0x45,0x64,0x6f,0x74,0x61,0x63,0x63,0x65,0x6e,0x74,0x7,0x45,0x6d,0x61,0x63, + 0x72,0x6f,0x6e,0x7,0x45,0x6f,0x67,0x6f,0x6e,0x65,0x6b,0xb,0x47,0x63,0x69,0x72,0x63,0x75,0x6d,0x66,0x6c,0x65,0x78,0xc,0x47,0x63,0x6f,0x6d,0x6d,0x61, + 0x61,0x63,0x63,0x65,0x6e,0x74,0xa,0x47,0x64,0x6f,0x74,0x61,0x63,0x63,0x65,0x6e,0x74,0x4,0x48,0x62,0x61,0x72,0xb,0x48,0x63,0x69,0x72,0x63,0x75,0x6d, + 0x66,0x6c,0x65,0x78,0x2,0x49,0x4a,0x6,0x49,0x62,0x72,0x65,0x76,0x65,0x7,0x49,0x6d,0x61,0x63,0x72,0x6f,0x6e,0x7,0x49,0x6f,0x67,0x6f,0x6e,0x65,0x6b, + 0x6,0x49,0x74,0x69,0x6c,0x64,0x65,0xb,0x4a,0x63,0x69,0x72,0x63,0x75,0x6d,0x66,0x6c,0x65,0x78,0xc,0x4b,0x63,0x6f,0x6d,0x6d,0x61,0x61,0x63,0x63,0x65, + 0x6e,0x74,0x6,0x4c,0x61,0x63,0x75,0x74,0x65,0x6,0x4c,0x63,0x61,0x72,0x6f,0x6e,0xc,0x4c,0x63,0x6f,0x6d,0x6d,0x61,0x61,0x63,0x63,0x65,0x6e,0x74,0x4, + 0x4c,0x64,0x6f,0x74,0x6,0x4e,0x61,0x63,0x75,0x74,0x65,0x6,0x4e,0x63,0x61,0x72,0x6f,0x6e,0xc,0x4e,0x63,0x6f,0x6d,0x6d,0x61,0x61,0x63,0x63,0x65,0x6e, + 0x74,0x3,0x45,0x6e,0x67,0x6,0x4f,0x62,0x72,0x65,0x76,0x65,0xd,0x4f,0x68,0x75,0x6e,0x67,0x61,0x72,0x75,0x6d,0x6c,0x61,0x75,0x74,0x7,0x4f,0x6d,0x61, + 0x63,0x72,0x6f,0x6e,0x6,0x52,0x61,0x63,0x75,0x74,0x65,0x6,0x52,0x63,0x61,0x72,0x6f,0x6e,0xc,0x52,0x63,0x6f,0x6d,0x6d,0x61,0x61,0x63,0x63,0x65,0x6e, + 0x74,0x6,0x53,0x61,0x63,0x75,0x74,0x65,0xb,0x53,0x63,0x69,0x72,0x63,0x75,0x6d,0x66,0x6c,0x65,0x78,0xc,0x53,0x63,0x6f,0x6d,0x6d,0x61,0x61,0x63,0x63, + 0x65,0x6e,0x74,0x4,0x54,0x62,0x61,0x72,0x6,0x54,0x63,0x61,0x72,0x6f,0x6e,0x7,0x75,0x6e,0x69,0x30,0x31,0x36,0x32,0x7,0x75,0x6e,0x69,0x30,0x32,0x31, + 0x41,0x6,0x55,0x62,0x72,0x65,0x76,0x65,0xd,0x55,0x68,0x75,0x6e,0x67,0x61,0x72,0x75,0x6d,0x6c,0x61,0x75,0x74,0x7,0x55,0x6d,0x61,0x63,0x72,0x6f,0x6e, + 0x7,0x55,0x6f,0x67,0x6f,0x6e,0x65,0x6b,0x5,0x55,0x72,0x69,0x6e,0x67,0x6,0x55,0x74,0x69,0x6c,0x64,0x65,0x6,0x57,0x61,0x63,0x75,0x74,0x65,0xb,0x57, + 0x63,0x69,0x72,0x63,0x75,0x6d,0x66,0x6c,0x65,0x78,0x9,0x57,0x64,0x69,0x65,0x72,0x65,0x73,0x69,0x73,0x6,0x57,0x67,0x72,0x61,0x76,0x65,0xb,0x59,0x63, + 0x69,0x72,0x63,0x75,0x6d,0x66,0x6c,0x65,0x78,0x6,0x59,0x67,0x72,0x61,0x76,0x65,0x6,0x5a,0x61,0x63,0x75,0x74,0x65,0xa,0x5a,0x64,0x6f,0x74,0x61,0x63, + 0x63,0x65,0x6e,0x74,0x6,0x61,0x62,0x72,0x65,0x76,0x65,0x7,0x61,0x6d,0x61,0x63,0x72,0x6f,0x6e,0x7,0x61,0x6f,0x67,0x6f,0x6e,0x65,0x6b,0xb,0x63,0x63, + 0x69,0x72,0x63,0x75,0x6d,0x66,0x6c,0x65,0x78,0xa,0x63,0x64,0x6f,0x74,0x61,0x63,0x63,0x65,0x6e,0x74,0x6,0x64,0x63,0x61,0x72,0x6f,0x6e,0x6,0x65,0x62, + 0x72,0x65,0x76,0x65,0x6,0x65,0x63,0x61,0x72,0x6f,0x6e,0xa,0x65,0x64,0x6f,0x74,0x61,0x63,0x63,0x65,0x6e,0x74,0x7,0x65,0x6d,0x61,0x63,0x72,0x6f,0x6e, + 0x7,0x65,0x6f,0x67,0x6f,0x6e,0x65,0x6b,0xb,0x67,0x63,0x69,0x72,0x63,0x75,0x6d,0x66,0x6c,0x65,0x78,0xc,0x67,0x63,0x6f,0x6d,0x6d,0x61,0x61,0x63,0x63, + 0x65,0x6e,0x74,0xa,0x67,0x64,0x6f,0x74,0x61,0x63,0x63,0x65,0x6e,0x74,0x4,0x68,0x62,0x61,0x72,0xb,0x68,0x63,0x69,0x72,0x63,0x75,0x6d,0x66,0x6c,0x65, + 0x78,0x6,0x69,0x62,0x72,0x65,0x76,0x65,0x9,0x69,0x2e,0x6c,0x6f,0x63,0x6c,0x54,0x52,0x4b,0x2,0x69,0x6a,0x7,0x69,0x6d,0x61,0x63,0x72,0x6f,0x6e,0x7, + 0x69,0x6f,0x67,0x6f,0x6e,0x65,0x6b,0x6,0x69,0x74,0x69,0x6c,0x64,0x65,0x7,0x75,0x6e,0x69,0x30,0x32,0x33,0x37,0xb,0x6a,0x63,0x69,0x72,0x63,0x75,0x6d, + 0x66,0x6c,0x65,0x78,0xc,0x6b,0x63,0x6f,0x6d,0x6d,0x61,0x61,0x63,0x63,0x65,0x6e,0x74,0x6,0x6c,0x61,0x63,0x75,0x74,0x65,0x6,0x6c,0x63,0x61,0x72,0x6f, + 0x6e,0xc,0x6c,0x63,0x6f,0x6d,0x6d,0x61,0x61,0x63,0x63,0x65,0x6e,0x74,0x4,0x6c,0x64,0x6f,0x74,0x6,0x6e,0x61,0x63,0x75,0x74,0x65,0x6,0x6e,0x63,0x61, + 0x72,0x6f,0x6e,0xc,0x6e,0x63,0x6f,0x6d,0x6d,0x61,0x61,0x63,0x63,0x65,0x6e,0x74,0x3,0x65,0x6e,0x67,0x6,0x6f,0x62,0x72,0x65,0x76,0x65,0xd,0x6f,0x68, + 0x75,0x6e,0x67,0x61,0x72,0x75,0x6d,0x6c,0x61,0x75,0x74,0x7,0x6f,0x6d,0x61,0x63,0x72,0x6f,0x6e,0x6,0x72,0x61,0x63,0x75,0x74,0x65,0x6,0x72,0x63,0x61, + 0x72,0x6f,0x6e,0xc,0x72,0x63,0x6f,0x6d,0x6d,0x61,0x61,0x63,0x63,0x65,0x6e,0x74,0x6,0x73,0x61,0x63,0x75,0x74,0x65,0xb,0x73,0x63,0x69,0x72,0x63,0x75, + 0x6d,0x66,0x6c,0x65,0x78,0xc,0x73,0x63,0x6f,0x6d,0x6d,0x61,0x61,0x63,0x63,0x65,0x6e,0x74,0x4,0x74,0x62,0x61,0x72,0x6,0x74,0x63,0x61,0x72,0x6f,0x6e, + 0x7,0x75,0x6e,0x69,0x30,0x31,0x36,0x33,0x7,0x75,0x6e,0x69,0x30,0x32,0x31,0x42,0x6,0x75,0x62,0x72,0x65,0x76,0x65,0xd,0x75,0x68,0x75,0x6e,0x67,0x61, + 0x72,0x75,0x6d,0x6c,0x61,0x75,0x74,0x7,0x75,0x6d,0x61,0x63,0x72,0x6f,0x6e,0x7,0x75,0x6f,0x67,0x6f,0x6e,0x65,0x6b,0x5,0x75,0x72,0x69,0x6e,0x67,0x6, + 0x75,0x74,0x69,0x6c,0x64,0x65,0x6,0x77,0x61,0x63,0x75,0x74,0x65,0xb,0x77,0x63,0x69,0x72,0x63,0x75,0x6d,0x66,0x6c,0x65,0x78,0x9,0x77,0x64,0x69,0x65, + 0x72,0x65,0x73,0x69,0x73,0x6,0x77,0x67,0x72,0x61,0x76,0x65,0xb,0x79,0x63,0x69,0x72,0x63,0x75,0x6d,0x66,0x6c,0x65,0x78,0x6,0x79,0x67,0x72,0x61,0x76, + 0x65,0x6,0x7a,0x61,0x63,0x75,0x74,0x65,0xa,0x7a,0x64,0x6f,0x74,0x61,0x63,0x63,0x65,0x6e,0x74,0x7,0x75,0x6e,0x69,0x30,0x33,0x39,0x34,0x7,0x75,0x6e, + 0x69,0x30,0x33,0x41,0x39,0x7,0x75,0x6e,0x69,0x30,0x33,0x42,0x43,0x7,0x75,0x6e,0x69,0x30,0x30,0x42,0x39,0x7,0x75,0x6e,0x69,0x30,0x30,0x42,0x32,0x7, + 0x75,0x6e,0x69,0x30,0x30,0x42,0x33,0x7,0x75,0x6e,0x69,0x32,0x30,0x37,0x34,0x9,0x63,0x6f,0x6c,0x6f,0x6e,0x2e,0x61,0x6c,0x74,0x9,0x63,0x6f,0x6d,0x6d, + 0x61,0x2e,0x61,0x6c,0x74,0xa,0x70,0x65,0x72,0x69,0x6f,0x64,0x2e,0x61,0x6c,0x74,0xd,0x73,0x65,0x6d,0x69,0x63,0x6f,0x6c,0x6f,0x6e,0x2e,0x61,0x6c,0x74, + 0xd,0x65,0x6c,0x6c,0x69,0x70,0x73,0x69,0x73,0x2e,0x61,0x6c,0x74,0x31,0xd,0x65,0x6c,0x6c,0x69,0x70,0x73,0x69,0x73,0x2e,0x61,0x6c,0x74,0x32,0xd,0x65, + 0x6c,0x6c,0x69,0x70,0x73,0x69,0x73,0x2e,0x61,0x6c,0x74,0x33,0xd,0x65,0x6c,0x6c,0x69,0x70,0x73,0x69,0x73,0x2e,0x61,0x6c,0x74,0x34,0xd,0x65,0x6c,0x6c, + 0x69,0x70,0x73,0x69,0x73,0x2e,0x61,0x6c,0x74,0x35,0xa,0x68,0x79,0x70,0x68,0x65,0x6e,0x2e,0x61,0x6c,0x74,0xb,0x65,0x6d,0x64,0x61,0x73,0x68,0x2e,0x61, + 0x6c,0x74,0x31,0xb,0x65,0x6d,0x64,0x61,0x73,0x68,0x2e,0x61,0x6c,0x74,0x32,0xb,0x65,0x6d,0x64,0x61,0x73,0x68,0x2e,0x61,0x6c,0x74,0x33,0xb,0x65,0x6d, + 0x64,0x61,0x73,0x68,0x2e,0x61,0x6c,0x74,0x34,0x7,0x75,0x6e,0x69,0x32,0x30,0x31,0x31,0x7,0x75,0x6e,0x69,0x30,0x30,0x41,0x44,0x7,0x75,0x6e,0x69,0x30, + 0x30,0x41,0x30,0x4,0x45,0x75,0x72,0x6f,0x7,0x75,0x6e,0x69,0x32,0x30,0x41,0x39,0x6,0x6d,0x69,0x6e,0x75,0x74,0x65,0x6,0x73,0x65,0x63,0x6f,0x6e,0x64, + 0x7,0x75,0x6e,0x69,0x30,0x33,0x32,0x36,0xc,0x75,0x6e,0x69,0x30,0x33,0x32,0x36,0x2e,0x63,0x61,0x73,0x65,0x9,0x63,0x61,0x72,0x6f,0x6e,0x2e,0x61,0x6c, + 0x74,0xa,0x61,0x63,0x75,0x74,0x65,0x2e,0x63,0x61,0x73,0x65,0xa,0x62,0x72,0x65,0x76,0x65,0x2e,0x63,0x61,0x73,0x65,0xa,0x63,0x61,0x72,0x6f,0x6e,0x2e, + 0x63,0x61,0x73,0x65,0xc,0x63,0x65,0x64,0x69,0x6c,0x6c,0x61,0x2e,0x63,0x61,0x73,0x65,0xf,0x63,0x69,0x72,0x63,0x75,0x6d,0x66,0x6c,0x65,0x78,0x2e,0x63, + 0x61,0x73,0x65,0xd,0x64,0x69,0x65,0x72,0x65,0x73,0x69,0x73,0x2e,0x63,0x61,0x73,0x65,0xe,0x64,0x6f,0x74,0x61,0x63,0x63,0x65,0x6e,0x74,0x2e,0x63,0x61, + 0x73,0x65,0xa,0x67,0x72,0x61,0x76,0x65,0x2e,0x63,0x61,0x73,0x65,0x11,0x68,0x75,0x6e,0x67,0x61,0x72,0x75,0x6d,0x6c,0x61,0x75,0x74,0x2e,0x63,0x61,0x73, + 0x65,0xb,0x6d,0x61,0x63,0x72,0x6f,0x6e,0x2e,0x63,0x61,0x73,0x65,0xb,0x6f,0x67,0x6f,0x6e,0x65,0x6b,0x2e,0x63,0x61,0x73,0x65,0x9,0x72,0x69,0x6e,0x67, + 0x2e,0x63,0x61,0x73,0x65,0xa,0x74,0x69,0x6c,0x64,0x65,0x2e,0x63,0x61,0x73,0x65,0x0,0x1,0x0,0x1,0xff,0xff,0x0,0xf,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x99,0x0,0x99,0x0,0x94,0x0,0x94,0x4,0xa3,0x0,0x0,0x5,0x2a,0x3,0x9c, + 0x0,0x0,0xfe,0x6d,0x7,0x5,0xfc,0xb9,0x4,0xb9,0xff,0xea,0x5,0x2a,0x3,0xb2,0xff,0xea,0xfe,0x6d,0x7,0x5,0xfc,0xb9,0xb0,0x0,0x2c,0x20,0xb0,0x0, + 0x55,0x58,0x45,0x59,0x20,0x20,0x4b,0xb8,0x0,0xe,0x51,0x4b,0xb0,0x6,0x53,0x5a,0x58,0xb0,0x34,0x1b,0xb0,0x28,0x59,0x60,0x66,0x20,0x8a,0x55,0x58,0xb0, + 0x2,0x25,0x61,0xb9,0x8,0x0,0x8,0x0,0x63,0x63,0x23,0x62,0x1b,0x21,0x21,0xb0,0x0,0x59,0xb0,0x0,0x43,0x23,0x44,0xb2,0x0,0x1,0x0,0x43,0x60,0x42, + 0x2d,0xb0,0x1,0x2c,0xb0,0x20,0x60,0x66,0x2d,0xb0,0x2,0x2c,0x20,0x64,0x20,0xb0,0xc0,0x50,0xb0,0x4,0x26,0x5a,0xb2,0x28,0x1,0xa,0x43,0x45,0x63,0x45, + 0xb0,0x6,0x45,0x58,0x21,0xb0,0x3,0x25,0x59,0x52,0x5b,0x58,0x21,0x23,0x21,0x1b,0x8a,0x58,0x20,0xb0,0x50,0x50,0x58,0x21,0xb0,0x40,0x59,0x1b,0x20,0xb0, + 0x38,0x50,0x58,0x21,0xb0,0x38,0x59,0x59,0x20,0xb1,0x1,0xa,0x43,0x45,0x63,0x45,0x61,0x64,0xb0,0x28,0x50,0x58,0x21,0xb1,0x1,0xa,0x43,0x45,0x63,0x45, + 0x20,0xb0,0x30,0x50,0x58,0x21,0xb0,0x30,0x59,0x1b,0x20,0xb0,0xc0,0x50,0x58,0x20,0x66,0x20,0x8a,0x8a,0x61,0x20,0xb0,0xa,0x50,0x58,0x60,0x1b,0x20,0xb0, + 0x20,0x50,0x58,0x21,0xb0,0xa,0x60,0x1b,0x20,0xb0,0x36,0x50,0x58,0x21,0xb0,0x36,0x60,0x1b,0x60,0x59,0x59,0x59,0x1b,0xb0,0x1,0x2b,0x59,0x59,0x23,0xb0, + 0x0,0x50,0x58,0x65,0x59,0x59,0x2d,0xb0,0x3,0x2c,0x20,0x45,0x20,0xb0,0x4,0x25,0x61,0x64,0x20,0xb0,0x5,0x43,0x50,0x58,0xb0,0x5,0x23,0x42,0xb0,0x6, + 0x23,0x42,0x1b,0x21,0x21,0x59,0xb0,0x1,0x60,0x2d,0xb0,0x4,0x2c,0x23,0x21,0x23,0x21,0x20,0x64,0xb1,0x5,0x62,0x42,0x20,0xb0,0x6,0x23,0x42,0xb0,0x6, + 0x45,0x58,0x1b,0xb1,0x1,0xa,0x43,0x45,0x63,0xb1,0x1,0xa,0x43,0xb0,0x1,0x60,0x45,0x63,0xb0,0x3,0x2a,0x21,0x20,0xb0,0x6,0x43,0x20,0x8a,0x20,0x8a, + 0xb0,0x1,0x2b,0xb1,0x30,0x5,0x25,0xb0,0x4,0x26,0x51,0x58,0x60,0x50,0x1b,0x61,0x52,0x59,0x58,0x23,0x59,0x21,0x59,0x20,0xb0,0x40,0x53,0x58,0xb0,0x1, + 0x2b,0x1b,0x21,0xb0,0x40,0x59,0x23,0xb0,0x0,0x50,0x58,0x65,0x59,0x2d,0xb0,0x5,0x2c,0xb0,0x7,0x43,0x2b,0xb2,0x0,0x2,0x0,0x43,0x60,0x42,0x2d,0xb0, + 0x6,0x2c,0xb0,0x7,0x23,0x42,0x23,0x20,0xb0,0x0,0x23,0x42,0x61,0xb0,0x2,0x62,0x66,0xb0,0x1,0x63,0xb0,0x1,0x60,0xb0,0x5,0x2a,0x2d,0xb0,0x7,0x2c, + 0x20,0x20,0x45,0x20,0xb0,0xb,0x43,0x63,0xb8,0x4,0x0,0x62,0x20,0xb0,0x0,0x50,0x58,0xb0,0x40,0x60,0x59,0x66,0xb0,0x1,0x63,0x60,0x44,0xb0,0x1,0x60, + 0x2d,0xb0,0x8,0x2c,0xb2,0x7,0xb,0x0,0x43,0x45,0x42,0x2a,0x21,0xb2,0x0,0x1,0x0,0x43,0x60,0x42,0x2d,0xb0,0x9,0x2c,0xb0,0x0,0x43,0x23,0x44,0xb2, + 0x0,0x1,0x0,0x43,0x60,0x42,0x2d,0xb0,0xa,0x2c,0x20,0x20,0x45,0x20,0xb0,0x1,0x2b,0x23,0xb0,0x0,0x43,0xb0,0x4,0x25,0x60,0x20,0x45,0x8a,0x23,0x61, + 0x20,0x64,0x20,0xb0,0x20,0x50,0x58,0x21,0xb0,0x0,0x1b,0xb0,0x30,0x50,0x58,0xb0,0x20,0x1b,0xb0,0x40,0x59,0x59,0x23,0xb0,0x0,0x50,0x58,0x65,0x59,0xb0, + 0x3,0x25,0x23,0x61,0x44,0x44,0xb0,0x1,0x60,0x2d,0xb0,0xb,0x2c,0x20,0x20,0x45,0x20,0xb0,0x1,0x2b,0x23,0xb0,0x0,0x43,0xb0,0x4,0x25,0x60,0x20,0x45, + 0x8a,0x23,0x61,0x20,0x64,0xb0,0x24,0x50,0x58,0xb0,0x0,0x1b,0xb0,0x40,0x59,0x23,0xb0,0x0,0x50,0x58,0x65,0x59,0xb0,0x3,0x25,0x23,0x61,0x44,0x44,0xb0, + 0x1,0x60,0x2d,0xb0,0xc,0x2c,0x20,0xb0,0x0,0x23,0x42,0xb2,0xb,0xa,0x3,0x45,0x58,0x21,0x1b,0x23,0x21,0x59,0x2a,0x21,0x2d,0xb0,0xd,0x2c,0xb1,0x2, + 0x2,0x45,0xb0,0x64,0x61,0x44,0x2d,0xb0,0xe,0x2c,0xb0,0x1,0x60,0x20,0x20,0xb0,0xc,0x43,0x4a,0xb0,0x0,0x50,0x58,0x20,0xb0,0xc,0x23,0x42,0x59,0xb0, + 0xd,0x43,0x4a,0xb0,0x0,0x52,0x58,0x20,0xb0,0xd,0x23,0x42,0x59,0x2d,0xb0,0xf,0x2c,0x20,0xb0,0x10,0x62,0x66,0xb0,0x1,0x63,0x20,0xb8,0x4,0x0,0x63, + 0x8a,0x23,0x61,0xb0,0xe,0x43,0x60,0x20,0x8a,0x60,0x20,0xb0,0xe,0x23,0x42,0x23,0x2d,0xb0,0x10,0x2c,0x4b,0x54,0x58,0xb1,0x4,0x64,0x44,0x59,0x24,0xb0, + 0xd,0x65,0x23,0x78,0x2d,0xb0,0x11,0x2c,0x4b,0x51,0x58,0x4b,0x53,0x58,0xb1,0x4,0x64,0x44,0x59,0x1b,0x21,0x59,0x24,0xb0,0x13,0x65,0x23,0x78,0x2d,0xb0, + 0x12,0x2c,0xb1,0x0,0xf,0x43,0x55,0x58,0xb1,0xf,0xf,0x43,0xb0,0x1,0x61,0x42,0xb0,0xf,0x2b,0x59,0xb0,0x0,0x43,0xb0,0x2,0x25,0x42,0xb1,0xc,0x2, + 0x25,0x42,0xb1,0xd,0x2,0x25,0x42,0xb0,0x1,0x16,0x23,0x20,0xb0,0x3,0x25,0x50,0x58,0xb1,0x1,0x0,0x43,0x60,0xb0,0x4,0x25,0x42,0x8a,0x8a,0x20,0x8a, + 0x23,0x61,0xb0,0xe,0x2a,0x21,0x23,0xb0,0x1,0x61,0x20,0x8a,0x23,0x61,0xb0,0xe,0x2a,0x21,0x1b,0xb1,0x1,0x0,0x43,0x60,0xb0,0x2,0x25,0x42,0xb0,0x2, + 0x25,0x61,0xb0,0xe,0x2a,0x21,0x59,0xb0,0xc,0x43,0x47,0xb0,0xd,0x43,0x47,0x60,0xb0,0x2,0x62,0x20,0xb0,0x0,0x50,0x58,0xb0,0x40,0x60,0x59,0x66,0xb0, + 0x1,0x63,0x20,0xb0,0xb,0x43,0x63,0xb8,0x4,0x0,0x62,0x20,0xb0,0x0,0x50,0x58,0xb0,0x40,0x60,0x59,0x66,0xb0,0x1,0x63,0x60,0xb1,0x0,0x0,0x13,0x23, + 0x44,0xb0,0x1,0x43,0xb0,0x0,0x3e,0xb2,0x1,0x1,0x1,0x43,0x60,0x42,0x2d,0xb0,0x13,0x2c,0x0,0xb1,0x0,0x2,0x45,0x54,0x58,0xb0,0xf,0x23,0x42,0x20, + 0x45,0xb0,0xb,0x23,0x42,0xb0,0xa,0x23,0xb0,0x1,0x60,0x42,0x20,0x60,0xb0,0x1,0x61,0xb5,0x10,0x10,0x1,0x0,0xe,0x0,0x42,0x42,0x8a,0x60,0xb1,0x12, + 0x6,0x2b,0xb0,0x75,0x2b,0x1b,0x22,0x59,0x2d,0xb0,0x14,0x2c,0xb1,0x0,0x13,0x2b,0x2d,0xb0,0x15,0x2c,0xb1,0x1,0x13,0x2b,0x2d,0xb0,0x16,0x2c,0xb1,0x2, + 0x13,0x2b,0x2d,0xb0,0x17,0x2c,0xb1,0x3,0x13,0x2b,0x2d,0xb0,0x18,0x2c,0xb1,0x4,0x13,0x2b,0x2d,0xb0,0x19,0x2c,0xb1,0x5,0x13,0x2b,0x2d,0xb0,0x1a,0x2c, + 0xb1,0x6,0x13,0x2b,0x2d,0xb0,0x1b,0x2c,0xb1,0x7,0x13,0x2b,0x2d,0xb0,0x1c,0x2c,0xb1,0x8,0x13,0x2b,0x2d,0xb0,0x1d,0x2c,0xb1,0x9,0x13,0x2b,0x2d,0xb0, + 0x29,0x2c,0x23,0x20,0xb0,0x10,0x62,0x66,0xb0,0x1,0x63,0xb0,0x6,0x60,0x4b,0x54,0x58,0x23,0x20,0x2e,0xb0,0x1,0x5d,0x1b,0x21,0x21,0x59,0x2d,0xb0,0x2a, + 0x2c,0x23,0x20,0xb0,0x10,0x62,0x66,0xb0,0x1,0x63,0xb0,0x16,0x60,0x4b,0x54,0x58,0x23,0x20,0x2e,0xb0,0x1,0x71,0x1b,0x21,0x21,0x59,0x2d,0xb0,0x2b,0x2c, + 0x23,0x20,0xb0,0x10,0x62,0x66,0xb0,0x1,0x63,0xb0,0x26,0x60,0x4b,0x54,0x58,0x23,0x20,0x2e,0xb0,0x1,0x72,0x1b,0x21,0x21,0x59,0x2d,0xb0,0x1e,0x2c,0x0, + 0xb0,0xd,0x2b,0xb1,0x0,0x2,0x45,0x54,0x58,0xb0,0xf,0x23,0x42,0x20,0x45,0xb0,0xb,0x23,0x42,0xb0,0xa,0x23,0xb0,0x1,0x60,0x42,0x20,0x60,0xb0,0x1, + 0x61,0xb5,0x10,0x10,0x1,0x0,0xe,0x0,0x42,0x42,0x8a,0x60,0xb1,0x12,0x6,0x2b,0xb0,0x75,0x2b,0x1b,0x22,0x59,0x2d,0xb0,0x1f,0x2c,0xb1,0x0,0x1e,0x2b, + 0x2d,0xb0,0x20,0x2c,0xb1,0x1,0x1e,0x2b,0x2d,0xb0,0x21,0x2c,0xb1,0x2,0x1e,0x2b,0x2d,0xb0,0x22,0x2c,0xb1,0x3,0x1e,0x2b,0x2d,0xb0,0x23,0x2c,0xb1,0x4, + 0x1e,0x2b,0x2d,0xb0,0x24,0x2c,0xb1,0x5,0x1e,0x2b,0x2d,0xb0,0x25,0x2c,0xb1,0x6,0x1e,0x2b,0x2d,0xb0,0x26,0x2c,0xb1,0x7,0x1e,0x2b,0x2d,0xb0,0x27,0x2c, + 0xb1,0x8,0x1e,0x2b,0x2d,0xb0,0x28,0x2c,0xb1,0x9,0x1e,0x2b,0x2d,0xb0,0x2c,0x2c,0x20,0x3c,0xb0,0x1,0x60,0x2d,0xb0,0x2d,0x2c,0x20,0x60,0xb0,0x10,0x60, + 0x20,0x43,0x23,0xb0,0x1,0x60,0x43,0xb0,0x2,0x25,0x61,0xb0,0x1,0x60,0xb0,0x2c,0x2a,0x21,0x2d,0xb0,0x2e,0x2c,0xb0,0x2d,0x2b,0xb0,0x2d,0x2a,0x2d,0xb0, + 0x2f,0x2c,0x20,0x20,0x47,0x20,0x20,0xb0,0xb,0x43,0x63,0xb8,0x4,0x0,0x62,0x20,0xb0,0x0,0x50,0x58,0xb0,0x40,0x60,0x59,0x66,0xb0,0x1,0x63,0x60,0x23, + 0x61,0x38,0x23,0x20,0x8a,0x55,0x58,0x20,0x47,0x20,0x20,0xb0,0xb,0x43,0x63,0xb8,0x4,0x0,0x62,0x20,0xb0,0x0,0x50,0x58,0xb0,0x40,0x60,0x59,0x66,0xb0, + 0x1,0x63,0x60,0x23,0x61,0x38,0x1b,0x21,0x59,0x2d,0xb0,0x30,0x2c,0x0,0xb1,0x0,0x2,0x45,0x54,0x58,0xb0,0x1,0x16,0xb0,0x2f,0x2a,0xb1,0x5,0x1,0x15, + 0x45,0x58,0x30,0x59,0x1b,0x22,0x59,0x2d,0xb0,0x31,0x2c,0x0,0xb0,0xd,0x2b,0xb1,0x0,0x2,0x45,0x54,0x58,0xb0,0x1,0x16,0xb0,0x2f,0x2a,0xb1,0x5,0x1, + 0x15,0x45,0x58,0x30,0x59,0x1b,0x22,0x59,0x2d,0xb0,0x32,0x2c,0x20,0x35,0xb0,0x1,0x60,0x2d,0xb0,0x33,0x2c,0x0,0xb0,0x1,0x45,0x63,0xb8,0x4,0x0,0x62, + 0x20,0xb0,0x0,0x50,0x58,0xb0,0x40,0x60,0x59,0x66,0xb0,0x1,0x63,0xb0,0x1,0x2b,0xb0,0xb,0x43,0x63,0xb8,0x4,0x0,0x62,0x20,0xb0,0x0,0x50,0x58,0xb0, + 0x40,0x60,0x59,0x66,0xb0,0x1,0x63,0xb0,0x1,0x2b,0xb0,0x0,0x16,0xb4,0x0,0x0,0x0,0x0,0x0,0x44,0x3e,0x23,0x38,0xb1,0x32,0x1,0x15,0x2a,0x2d,0xb0, + 0x34,0x2c,0x20,0x3c,0x20,0x47,0x20,0xb0,0xb,0x43,0x63,0xb8,0x4,0x0,0x62,0x20,0xb0,0x0,0x50,0x58,0xb0,0x40,0x60,0x59,0x66,0xb0,0x1,0x63,0x60,0xb0, + 0x0,0x43,0x61,0x38,0x2d,0xb0,0x35,0x2c,0x2e,0x17,0x3c,0x2d,0xb0,0x36,0x2c,0x20,0x3c,0x20,0x47,0x20,0xb0,0xb,0x43,0x63,0xb8,0x4,0x0,0x62,0x20,0xb0, + 0x0,0x50,0x58,0xb0,0x40,0x60,0x59,0x66,0xb0,0x1,0x63,0x60,0xb0,0x0,0x43,0x61,0xb0,0x1,0x43,0x63,0x38,0x2d,0xb0,0x37,0x2c,0xb1,0x2,0x0,0x16,0x25, + 0x20,0x2e,0x20,0x47,0xb0,0x0,0x23,0x42,0xb0,0x2,0x25,0x49,0x8a,0x8a,0x47,0x23,0x47,0x23,0x61,0x20,0x58,0x62,0x1b,0x21,0x59,0xb0,0x1,0x23,0x42,0xb2, + 0x36,0x1,0x1,0x15,0x14,0x2a,0x2d,0xb0,0x38,0x2c,0xb0,0x0,0x16,0xb0,0x4,0x25,0xb0,0x4,0x25,0x47,0x23,0x47,0x23,0x61,0xb0,0x9,0x43,0x2b,0x65,0x8a, + 0x2e,0x23,0x20,0x20,0x3c,0x8a,0x38,0x2d,0xb0,0x39,0x2c,0xb0,0x0,0x16,0xb0,0x4,0x25,0xb0,0x4,0x25,0x20,0x2e,0x47,0x23,0x47,0x23,0x61,0x20,0xb0,0x4, + 0x23,0x42,0xb0,0x9,0x43,0x2b,0x20,0xb0,0x60,0x50,0x58,0x20,0xb0,0x40,0x51,0x58,0xb3,0x2,0x20,0x3,0x20,0x1b,0xb3,0x2,0x26,0x3,0x1a,0x59,0x42,0x42, + 0x23,0x20,0xb0,0x8,0x43,0x20,0x8a,0x23,0x47,0x23,0x47,0x23,0x61,0x23,0x46,0x60,0xb0,0x4,0x43,0xb0,0x2,0x62,0x20,0xb0,0x0,0x50,0x58,0xb0,0x40,0x60, + 0x59,0x66,0xb0,0x1,0x63,0x60,0x20,0xb0,0x1,0x2b,0x20,0x8a,0x8a,0x61,0x20,0xb0,0x2,0x43,0x60,0x64,0x23,0xb0,0x3,0x43,0x61,0x64,0x50,0x58,0xb0,0x2, + 0x43,0x61,0x1b,0xb0,0x3,0x43,0x60,0x59,0xb0,0x3,0x25,0xb0,0x2,0x62,0x20,0xb0,0x0,0x50,0x58,0xb0,0x40,0x60,0x59,0x66,0xb0,0x1,0x63,0x61,0x23,0x20, + 0x20,0xb0,0x4,0x26,0x23,0x46,0x61,0x38,0x1b,0x23,0xb0,0x8,0x43,0x46,0xb0,0x2,0x25,0xb0,0x8,0x43,0x47,0x23,0x47,0x23,0x61,0x60,0x20,0xb0,0x4,0x43, + 0xb0,0x2,0x62,0x20,0xb0,0x0,0x50,0x58,0xb0,0x40,0x60,0x59,0x66,0xb0,0x1,0x63,0x60,0x23,0x20,0xb0,0x1,0x2b,0x23,0xb0,0x4,0x43,0x60,0xb0,0x1,0x2b, + 0xb0,0x5,0x25,0x61,0xb0,0x5,0x25,0xb0,0x2,0x62,0x20,0xb0,0x0,0x50,0x58,0xb0,0x40,0x60,0x59,0x66,0xb0,0x1,0x63,0xb0,0x4,0x26,0x61,0x20,0xb0,0x4, + 0x25,0x60,0x64,0x23,0xb0,0x3,0x25,0x60,0x64,0x50,0x58,0x21,0x1b,0x23,0x21,0x59,0x23,0x20,0x20,0xb0,0x4,0x26,0x23,0x46,0x61,0x38,0x59,0x2d,0xb0,0x3a, + 0x2c,0xb0,0x0,0x16,0x20,0x20,0x20,0xb0,0x5,0x26,0x20,0x2e,0x47,0x23,0x47,0x23,0x61,0x23,0x3c,0x38,0x2d,0xb0,0x3b,0x2c,0xb0,0x0,0x16,0x20,0xb0,0x8, + 0x23,0x42,0x20,0x20,0x20,0x46,0x23,0x47,0xb0,0x1,0x2b,0x23,0x61,0x38,0x2d,0xb0,0x3c,0x2c,0xb0,0x0,0x16,0xb0,0x3,0x25,0xb0,0x2,0x25,0x47,0x23,0x47, + 0x23,0x61,0xb0,0x0,0x54,0x58,0x2e,0x20,0x3c,0x23,0x21,0x1b,0xb0,0x2,0x25,0xb0,0x2,0x25,0x47,0x23,0x47,0x23,0x61,0x20,0xb0,0x5,0x25,0xb0,0x4,0x25, + 0x47,0x23,0x47,0x23,0x61,0xb0,0x6,0x25,0xb0,0x5,0x25,0x49,0xb0,0x2,0x25,0x61,0xb9,0x8,0x0,0x8,0x0,0x63,0x63,0x23,0x20,0x58,0x62,0x1b,0x21,0x59, + 0x63,0xb8,0x4,0x0,0x62,0x20,0xb0,0x0,0x50,0x58,0xb0,0x40,0x60,0x59,0x66,0xb0,0x1,0x63,0x60,0x23,0x2e,0x23,0x20,0x20,0x3c,0x8a,0x38,0x23,0x21,0x59, + 0x2d,0xb0,0x3d,0x2c,0xb0,0x0,0x16,0x20,0xb0,0x8,0x43,0x20,0x2e,0x47,0x23,0x47,0x23,0x61,0x20,0x60,0xb0,0x20,0x60,0x66,0xb0,0x2,0x62,0x20,0xb0,0x0, + 0x50,0x58,0xb0,0x40,0x60,0x59,0x66,0xb0,0x1,0x63,0x23,0x20,0x20,0x3c,0x8a,0x38,0x2d,0xb0,0x3e,0x2c,0x23,0x20,0x2e,0x46,0xb0,0x2,0x25,0x46,0x52,0x58, + 0x20,0x3c,0x59,0x2e,0xb1,0x2e,0x1,0x14,0x2b,0x2d,0xb0,0x3f,0x2c,0x23,0x20,0x2e,0x46,0xb0,0x2,0x25,0x46,0x50,0x58,0x20,0x3c,0x59,0x2e,0xb1,0x2e,0x1, + 0x14,0x2b,0x2d,0xb0,0x40,0x2c,0x23,0x20,0x2e,0x46,0xb0,0x2,0x25,0x46,0x52,0x58,0x20,0x3c,0x59,0x23,0x20,0x2e,0x46,0xb0,0x2,0x25,0x46,0x50,0x58,0x20, + 0x3c,0x59,0x2e,0xb1,0x2e,0x1,0x14,0x2b,0x2d,0xb0,0x41,0x2c,0xb0,0x38,0x2b,0x23,0x20,0x2e,0x46,0xb0,0x2,0x25,0x46,0x52,0x58,0x20,0x3c,0x59,0x2e,0xb1, + 0x2e,0x1,0x14,0x2b,0x2d,0xb0,0x42,0x2c,0xb0,0x39,0x2b,0x8a,0x20,0x20,0x3c,0xb0,0x4,0x23,0x42,0x8a,0x38,0x23,0x20,0x2e,0x46,0xb0,0x2,0x25,0x46,0x52, + 0x58,0x20,0x3c,0x59,0x2e,0xb1,0x2e,0x1,0x14,0x2b,0xb0,0x4,0x43,0x2e,0xb0,0x2e,0x2b,0x2d,0xb0,0x43,0x2c,0xb0,0x0,0x16,0xb0,0x4,0x25,0xb0,0x4,0x26, + 0x20,0x2e,0x47,0x23,0x47,0x23,0x61,0xb0,0x9,0x43,0x2b,0x23,0x20,0x3c,0x20,0x2e,0x23,0x38,0xb1,0x2e,0x1,0x14,0x2b,0x2d,0xb0,0x44,0x2c,0xb1,0x8,0x4, + 0x25,0x42,0xb0,0x0,0x16,0xb0,0x4,0x25,0xb0,0x4,0x25,0x20,0x2e,0x47,0x23,0x47,0x23,0x61,0x20,0xb0,0x4,0x23,0x42,0xb0,0x9,0x43,0x2b,0x20,0xb0,0x60, + 0x50,0x58,0x20,0xb0,0x40,0x51,0x58,0xb3,0x2,0x20,0x3,0x20,0x1b,0xb3,0x2,0x26,0x3,0x1a,0x59,0x42,0x42,0x23,0x20,0x47,0xb0,0x4,0x43,0xb0,0x2,0x62, + 0x20,0xb0,0x0,0x50,0x58,0xb0,0x40,0x60,0x59,0x66,0xb0,0x1,0x63,0x60,0x20,0xb0,0x1,0x2b,0x20,0x8a,0x8a,0x61,0x20,0xb0,0x2,0x43,0x60,0x64,0x23,0xb0, + 0x3,0x43,0x61,0x64,0x50,0x58,0xb0,0x2,0x43,0x61,0x1b,0xb0,0x3,0x43,0x60,0x59,0xb0,0x3,0x25,0xb0,0x2,0x62,0x20,0xb0,0x0,0x50,0x58,0xb0,0x40,0x60, + 0x59,0x66,0xb0,0x1,0x63,0x61,0xb0,0x2,0x25,0x46,0x61,0x38,0x23,0x20,0x3c,0x23,0x38,0x1b,0x21,0x20,0x20,0x46,0x23,0x47,0xb0,0x1,0x2b,0x23,0x61,0x38, + 0x21,0x59,0xb1,0x2e,0x1,0x14,0x2b,0x2d,0xb0,0x45,0x2c,0xb0,0x38,0x2b,0x2e,0xb1,0x2e,0x1,0x14,0x2b,0x2d,0xb0,0x46,0x2c,0xb0,0x39,0x2b,0x21,0x23,0x20, + 0x20,0x3c,0xb0,0x4,0x23,0x42,0x23,0x38,0xb1,0x2e,0x1,0x14,0x2b,0xb0,0x4,0x43,0x2e,0xb0,0x2e,0x2b,0x2d,0xb0,0x47,0x2c,0xb0,0x0,0x15,0x20,0x47,0xb0, + 0x0,0x23,0x42,0xb2,0x0,0x1,0x1,0x15,0x14,0x13,0x2e,0xb0,0x34,0x2a,0x2d,0xb0,0x48,0x2c,0xb0,0x0,0x15,0x20,0x47,0xb0,0x0,0x23,0x42,0xb2,0x0,0x1, + 0x1,0x15,0x14,0x13,0x2e,0xb0,0x34,0x2a,0x2d,0xb0,0x49,0x2c,0xb1,0x0,0x1,0x14,0x13,0xb0,0x35,0x2a,0x2d,0xb0,0x4a,0x2c,0xb0,0x37,0x2a,0x2d,0xb0,0x4b, + 0x2c,0xb0,0x0,0x16,0x45,0x23,0x20,0x2e,0x20,0x46,0x8a,0x23,0x61,0x38,0xb1,0x2e,0x1,0x14,0x2b,0x2d,0xb0,0x4c,0x2c,0xb0,0x8,0x23,0x42,0xb0,0x4b,0x2b, + 0x2d,0xb0,0x4d,0x2c,0xb2,0x0,0x0,0x44,0x2b,0x2d,0xb0,0x4e,0x2c,0xb2,0x0,0x1,0x44,0x2b,0x2d,0xb0,0x4f,0x2c,0xb2,0x1,0x0,0x44,0x2b,0x2d,0xb0,0x50, + 0x2c,0xb2,0x1,0x1,0x44,0x2b,0x2d,0xb0,0x51,0x2c,0xb2,0x0,0x0,0x45,0x2b,0x2d,0xb0,0x52,0x2c,0xb2,0x0,0x1,0x45,0x2b,0x2d,0xb0,0x53,0x2c,0xb2,0x1, + 0x0,0x45,0x2b,0x2d,0xb0,0x54,0x2c,0xb2,0x1,0x1,0x45,0x2b,0x2d,0xb0,0x55,0x2c,0xb2,0x0,0x0,0x41,0x2b,0x2d,0xb0,0x56,0x2c,0xb2,0x0,0x1,0x41,0x2b, + 0x2d,0xb0,0x57,0x2c,0xb2,0x1,0x0,0x41,0x2b,0x2d,0xb0,0x58,0x2c,0xb2,0x1,0x1,0x41,0x2b,0x2d,0xb0,0x59,0x2c,0xb2,0x0,0x0,0x43,0x2b,0x2d,0xb0,0x5a, + 0x2c,0xb2,0x0,0x1,0x43,0x2b,0x2d,0xb0,0x5b,0x2c,0xb2,0x1,0x0,0x43,0x2b,0x2d,0xb0,0x5c,0x2c,0xb2,0x1,0x1,0x43,0x2b,0x2d,0xb0,0x5d,0x2c,0xb2,0x0, + 0x0,0x46,0x2b,0x2d,0xb0,0x5e,0x2c,0xb2,0x0,0x1,0x46,0x2b,0x2d,0xb0,0x5f,0x2c,0xb2,0x1,0x0,0x46,0x2b,0x2d,0xb0,0x60,0x2c,0xb2,0x1,0x1,0x46,0x2b, + 0x2d,0xb0,0x61,0x2c,0xb2,0x0,0x0,0x42,0x2b,0x2d,0xb0,0x62,0x2c,0xb2,0x0,0x1,0x42,0x2b,0x2d,0xb0,0x63,0x2c,0xb2,0x1,0x0,0x42,0x2b,0x2d,0xb0,0x64, + 0x2c,0xb2,0x1,0x1,0x42,0x2b,0x2d,0xb0,0x65,0x2c,0xb0,0x3a,0x2b,0x2e,0xb1,0x2e,0x1,0x14,0x2b,0x2d,0xb0,0x66,0x2c,0xb0,0x3a,0x2b,0xb0,0x3e,0x2b,0x2d, + 0xb0,0x67,0x2c,0xb0,0x3a,0x2b,0xb0,0x3f,0x2b,0x2d,0xb0,0x68,0x2c,0xb0,0x0,0x16,0xb0,0x3a,0x2b,0xb0,0x40,0x2b,0x2d,0xb0,0x69,0x2c,0xb0,0x3b,0x2b,0x2e, + 0xb1,0x2e,0x1,0x14,0x2b,0x2d,0xb0,0x6a,0x2c,0xb0,0x3b,0x2b,0xb0,0x3e,0x2b,0x2d,0xb0,0x6b,0x2c,0xb0,0x3b,0x2b,0xb0,0x3f,0x2b,0x2d,0xb0,0x6c,0x2c,0xb0, + 0x3b,0x2b,0xb0,0x40,0x2b,0x2d,0xb0,0x6d,0x2c,0xb0,0x3c,0x2b,0x2e,0xb1,0x2e,0x1,0x14,0x2b,0x2d,0xb0,0x6e,0x2c,0xb0,0x3c,0x2b,0xb0,0x3e,0x2b,0x2d,0xb0, + 0x6f,0x2c,0xb0,0x3c,0x2b,0xb0,0x3f,0x2b,0x2d,0xb0,0x70,0x2c,0xb0,0x3c,0x2b,0xb0,0x40,0x2b,0x2d,0xb0,0x71,0x2c,0xb0,0x3d,0x2b,0x2e,0xb1,0x2e,0x1,0x14, + 0x2b,0x2d,0xb0,0x72,0x2c,0xb0,0x3d,0x2b,0xb0,0x3e,0x2b,0x2d,0xb0,0x73,0x2c,0xb0,0x3d,0x2b,0xb0,0x3f,0x2b,0x2d,0xb0,0x74,0x2c,0xb0,0x3d,0x2b,0xb0,0x40, + 0x2b,0x2d,0xb0,0x75,0x2c,0xb3,0x9,0x4,0x2,0x3,0x45,0x58,0x21,0x1b,0x23,0x21,0x59,0x42,0x2b,0xb0,0x8,0x65,0xb0,0x3,0x24,0x50,0x78,0xb1,0x5,0x1, + 0x15,0x45,0x58,0x30,0x59,0x2d,0x0,0x0,0x0,0x4b,0xb8,0x0,0xc8,0x52,0x58,0xb1,0x1,0x1,0x8e,0x59,0xb0,0x1,0xb9,0x8,0x0,0x8,0x0,0x63,0x70,0xb1, + 0x0,0x7,0x42,0xb2,0x19,0x1,0x0,0x2a,0xb1,0x0,0x7,0x42,0xb3,0xc,0x8,0x1,0x8,0x2a,0xb1,0x0,0x7,0x42,0xb3,0x16,0x6,0x1,0x8,0x2a,0xb1,0x0, + 0x8,0x42,0xba,0x3,0x40,0x0,0x1,0x0,0x9,0x2a,0xb1,0x0,0x9,0x42,0xba,0x0,0x40,0x0,0x1,0x0,0x9,0x2a,0xb1,0x3,0x0,0x44,0xb1,0x24,0x1,0x88, + 0x51,0x58,0xb0,0x40,0x88,0x58,0xb1,0x3,0x64,0x44,0xb1,0x26,0x1,0x88,0x51,0x58,0xba,0x8,0x80,0x0,0x1,0x4,0x40,0x88,0x63,0x54,0x58,0xb1,0x3,0x0, + 0x44,0x59,0x59,0x59,0x59,0xb3,0xe,0x8,0x1,0xc,0x2a,0xb8,0x1,0xff,0x85,0xb0,0x4,0x8d,0xb1,0x2,0x0,0x44,0xb3,0x5,0x64,0x6,0x0,0x44,0x44,0x0, + 0x0,0x0,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x0 +}; +// clang-format on diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/Geometry.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/Geometry.cpp new file mode 100644 index 00000000..643da598 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/Geometry.cpp @@ -0,0 +1,85 @@ +#include "Geometry.h" +#include "../../Include/RmlUi/Core/Context.h" +#include "../../Include/RmlUi/Core/Core.h" +#include "../../Include/RmlUi/Core/Geometry.h" +#include "../../Include/RmlUi/Core/MeshUtilities.h" +#include "../../Include/RmlUi/Core/RenderManager.h" + +namespace Rml { +namespace Debugger { + +static Context* g_context = nullptr; +static RenderManager* g_render_manager = nullptr; + +Geometry::Geometry() {} + +void Geometry::SetContext(Context* context) +{ + g_context = context; + g_render_manager = context ? &context->GetRenderManager() : nullptr; +} + +void Geometry::RenderOutline(const Vector2f origin, const Vector2f dimensions, const Colourb colour, float width) +{ + if (!g_context || !g_render_manager) + return; + + Mesh mesh; + mesh.vertices.reserve(4 * 4); + mesh.indices.reserve(6 * 4); + + ColourbPremultiplied colour_pre = colour.ToPremultiplied(); + + MeshUtilities::GenerateQuad(mesh, Vector2f(0, 0), Vector2f(dimensions.x, width), colour_pre); + MeshUtilities::GenerateQuad(mesh, Vector2f(0, dimensions.y - width), Vector2f(dimensions.x, width), colour_pre); + MeshUtilities::GenerateQuad(mesh, Vector2f(0, 0), Vector2f(width, dimensions.y), colour_pre); + MeshUtilities::GenerateQuad(mesh, Vector2f(dimensions.x - width, 0), Vector2f(width, dimensions.y), colour_pre); + + g_render_manager->MakeGeometry(std::move(mesh)).Render(origin); +} + +void Geometry::RenderBox(const Vector2f origin, const Vector2f dimensions, const Colourb colour) +{ + if (!g_context || !g_render_manager) + return; + + Mesh mesh; + MeshUtilities::GenerateQuad(mesh, Vector2f(0, 0), Vector2f(dimensions.x, dimensions.y), colour.ToPremultiplied()); + + g_render_manager->MakeGeometry(std::move(mesh)).Render(origin); +} + +void Geometry::RenderBox(const Vector2f origin, const Vector2f dimensions, const Vector2f hole_origin, const Vector2f hole_dimensions, + const Colourb colour) +{ + // Top box. + float top_y_dimensions = hole_origin.y - origin.y; + if (top_y_dimensions > 0) + { + RenderBox(origin, Vector2f(dimensions.x, top_y_dimensions), colour); + } + + // Bottom box. + float bottom_y_dimensions = (origin.y + dimensions.y) - (hole_origin.y + hole_dimensions.y); + if (bottom_y_dimensions > 0) + { + RenderBox(Vector2f(origin.x, hole_origin.y + hole_dimensions.y), Vector2f(dimensions.x, bottom_y_dimensions), colour); + } + + // Left box. + float left_x_dimensions = hole_origin.x - origin.x; + if (left_x_dimensions > 0) + { + RenderBox(Vector2f(origin.x, hole_origin.y), Vector2f(left_x_dimensions, hole_dimensions.y), colour); + } + + // Right box. + float right_x_dimensions = (origin.x + dimensions.x) - (hole_origin.x + hole_dimensions.x); + if (right_x_dimensions > 0) + { + RenderBox(Vector2f(hole_origin.x + hole_dimensions.x, hole_origin.y), Vector2f(right_x_dimensions, hole_dimensions.y), colour); + } +} + +} // namespace Debugger +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/Geometry.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/Geometry.h new file mode 100644 index 00000000..014be494 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/Geometry.h @@ -0,0 +1,32 @@ +#pragma once + +#include "../../Include/RmlUi/Core/Types.h" + +namespace Rml { + +class Context; + +namespace Debugger { + +/** + Helper class for generating geometry for the debugger. + */ + +class Geometry { +public: + // Set the context to render through. + static void SetContext(Context* context); + + // Renders a one-pixel rectangular outline. + static void RenderOutline(Vector2f origin, Vector2f dimensions, Colourb colour, float width); + // Renders a box. + static void RenderBox(Vector2f origin, Vector2f dimensions, Colourb colour); + // Renders a box with a hole in the middle. + static void RenderBox(Vector2f origin, Vector2f dimensions, Vector2f hole_origin, Vector2f hole_dimensions, Colourb colour); + +private: + Geometry(); +}; + +} // namespace Debugger +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/InfoSource.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/InfoSource.h new file mode 100644 index 00000000..8fb081a4 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/InfoSource.h @@ -0,0 +1,146 @@ +static const char* info_rcss = R"RCSS( +body +{ + width: 320dp; + min-width: 320dp; + margin-top: 42dp; + margin-right: 20dp; + margin-left: auto; +} +div#content +{ + height: auto; + min-height: 200dp; + max-height: 650dp; +} +div#content div h2 +{ + padding-left: 5dp; +} +div#content div div +{ + font-size: 12dp; + padding-left: 10dp; +} +div#content .name +{ + color: #610; +} +div#position p:hover, +div#ancestors p:hover, +div#children p:hover +{ + background-color: #ddd; +} +scrollbarvertical +{ + scrollbar-margin: 0px; +} +h3.strong +{ + margin-top: 1.0em; + padding: 2dp; + color: #900; + background-color: #eee; +} +#pseudo pseudo +{ + padding: 0 8dp 0 3dp; + background-color: #ddd; + border: 2px #aaa; + display: inline-block; +} +#pseudo pseudo.active +{ + border-color: #8af; + background-color: #eef; +} +div.header_button +{ + font-size: 0.9em; + margin-left: 3dp; + z-index: 1; + float: right; + width: 18dp; + color: #999; + background-color: #666; + border-width: 1px; + border-color: #666; + text-align: center; +} +div.header_button.active +{ + border-color: #ccc; + color: #fff; +} +div.header_button:hover +{ + background-color: #555; +} +div.header_button:active +{ + background-color: #444; +} +div#title-content { + width: 220dp; +} +div#title-content em { + font-size: 14dp; +} +p.non_dom { + font-style: italic; +} +.break-all { + word-break: break-all; +} +)RCSS"; + +static const char* info_rml = R"RML( +

    + +
    X
    +
    U
    +
    D
    +
    *
    +
    Element Information
    +

    +
    +
    + :hover + :active + :focus + :checked + +
    +
    +

    Attributes

    +
    +
    +
    +
    +

    Properties

    +
    +
    +
    +
    +

    Events

    +
    +
    +
    +
    +

    Position

    +
    +
    +
    +
    +

    Ancestors

    +
    +
    +
    +
    +

    Children

    +
    +
    +
    +
    +)RML"; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/LICENSE.txt b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/LICENSE.txt new file mode 100644 index 00000000..6997c88b --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/LICENSE.txt @@ -0,0 +1,99 @@ +RmlUi Debugger includes thirdparty font assets. They are listed below along +with their full license. + +--- + +'Courier Prime Code' and 'Courier Prime Code Italic'. + +Copyright (c) 2013, Quote-Unquote Apps (http://quoteunquoteapps.com), +with Reserved Font Name Courier Prime. + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/LogSource.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/LogSource.h new file mode 100644 index 00000000..0b0edb3c --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/LogSource.h @@ -0,0 +1,82 @@ +static const char* log_rcss = R"RCSS(body +{ + width: 400dp; + height: 300dp; + min-width: 250dp; + min-height: 150dp; + top: 42dp; + left: 20dp; +} +div#tools +{ + float: right; + width: 200dp; +} +div.log-entry +{ + margin: 3dp 2dp; +} +div.log-entry div.icon +{ + float: left; + display: block; + width: 18dp; + height: 18dp; + text-align: center; + border-width: 1px; + margin-right: 5dp; +} +div.button +{ + display: inline-block; + width: 32dp; + font-size: 13dp; + line-height: 20dp; + text-align: center; + border-width: 1px; + margin-right: 3dp; +} +div.button.clear +{ + border-color: #666; + background-color: #aaa; + color: #111; +} +div.button:hover +{ + border-color: #ddd; +} +div.button:active +{ + border-color: #fff; +} +div.button.last +{ + margin-right: 0px; +} +div.log-entry p.message +{ + display: block; + white-space: pre-wrap; + margin-left: 20dp; +} +)RCSS"; + +static const char* log_rml = R"RML( +

    + +
    X
    +
    +
    Clear
    +
    On
    +
    On
    +
    Off
    +
    On
    +
    +
    Event Log
    +

    +
    + No messages in log. +
    + +)RML"; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/MenuSource.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/MenuSource.h new file mode 100644 index 00000000..ff4efee9 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Debugger/MenuSource.h @@ -0,0 +1,66 @@ +static const char* menu_rcss = R"RCSS( +body +{ + width: 100%; + height: 32dp; + position: absolute; + z-index: 1000000; + background: #888; + font-family: rmlui-debugger-font; + font-size: 14dp; + color: black; +} +div +{ + display: block; +} +div#button-group +{ + margin-top: 3dp; +} +button +{ + border-width: 1px; + border-color: #666; + background: #ddd; + margin-left: 6dp; + display: inline-block; + width: 130dp; + line-height: 24dp; + text-align: center; +} +button.open { + border-color: #6cf; + color: #22a; + background: #cee; +} +button:hover { background: #eee; } +button:active { background: #fff; } +button.open:hover { background: #dff; } +button.open:active { background: #eff; } +div#version-info +{ + padding: 0px; + margin-top: 0px; + font-size: 20dp; + float: right; + margin-right: 20dp; + width: 200dp; + text-align: right; + color: white; +} +span#version-number +{ + font-size: 15dp; +} +)RCSS"; + +static const char* menu_rml = R"RML( +
    RmlUi
    +
    + + + + +
    +)RML"; diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lottie/CMakeLists.txt b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lottie/CMakeLists.txt new file mode 100644 index 00000000..f673644b --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lottie/CMakeLists.txt @@ -0,0 +1,10 @@ +# This plugin targets rmlui_core directly and is incorporated into the core library when enabled. + +target_sources(rmlui_core PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/ElementLottie.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/LottiePlugin.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/LottiePlugin.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Lottie/ElementLottie.h" +) + +target_compile_definitions(rmlui_core PRIVATE "RMLUI_LOTTIE_PLUGIN") diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lottie/ElementLottie.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lottie/ElementLottie.cpp new file mode 100644 index 00000000..5a2e0602 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lottie/ElementLottie.cpp @@ -0,0 +1,234 @@ +#include "../../Include/RmlUi/Lottie/ElementLottie.h" +#include "../../Include/RmlUi/Core/ComputedValues.h" +#include "../../Include/RmlUi/Core/Context.h" +#include "../../Include/RmlUi/Core/Core.h" +#include "../../Include/RmlUi/Core/ElementDocument.h" +#include "../../Include/RmlUi/Core/ElementUtilities.h" +#include "../../Include/RmlUi/Core/FileInterface.h" +#include "../../Include/RmlUi/Core/MeshUtilities.h" +#include "../../Include/RmlUi/Core/PropertyIdSet.h" +#include "../../Include/RmlUi/Core/RenderManager.h" +#include "../../Include/RmlUi/Core/SystemInterface.h" +#include +#include + +namespace Rml { + +ElementLottie::ElementLottie(const String& tag) : Element(tag) {} + +ElementLottie::~ElementLottie() {} + +bool ElementLottie::GetIntrinsicDimensions(Vector2f& dimensions, float& ratio) +{ + EnsureSourceLoaded(); + + dimensions = intrinsic_dimensions; + if (dimensions.y > 0) + ratio = dimensions.x / dimensions.y; + + dimensions *= ElementUtilities::GetDensityIndependentPixelRatio(this); + + return true; +} + +void ElementLottie::EnsureSourceLoaded() +{ + if (animation_dirty) + LoadAnimation(); +} + +void ElementLottie::OnUpdate() +{ + EnsureSourceLoaded(); + + if (!animation) + return; + + const auto t = GetSystemInterface()->GetElapsedTime(); + + if (time_animation_start < 0.0) + time_animation_start = t; + + double _unused; + const double frame_duration = 1.0 / animation->frameRate(); + const double delay = std::modf((t - time_animation_start) / frame_duration, &_unused) * frame_duration; + if (IsVisible(true)) + { + if (Context* ctx = GetContext()) + ctx->RequestNextUpdate(delay); + } +} + +void ElementLottie::OnRender() +{ + if (animation) + { + if (geometry_dirty) + GenerateGeometry(); + + UpdateTexture(); + geometry.Render(GetAbsoluteOffset(BoxArea::Content).Round(), texture); + } +} + +void ElementLottie::OnResize() +{ + geometry_dirty = true; + texture_size_dirty = true; +} + +void ElementLottie::OnAttributeChange(const ElementAttributes& changed_attributes) +{ + Element::OnAttributeChange(changed_attributes); + + if (changed_attributes.count("src")) + { + animation_dirty = true; + DirtyLayout(); + } +} + +void ElementLottie::OnPropertyChange(const PropertyIdSet& changed_properties) +{ + Element::OnPropertyChange(changed_properties); + + if (changed_properties.Contains(PropertyId::ImageColor) || changed_properties.Contains(PropertyId::Opacity)) + { + geometry_dirty = true; + } +} + +void ElementLottie::GenerateGeometry() +{ + const ComputedValues& computed = GetComputedValues(); + ColourbPremultiplied quad_colour = computed.image_color().ToPremultiplied(computed.opacity()); + + const Vector2f render_dimensions_f = GetBox().GetSize(BoxArea::Content).Round(); + render_dimensions = Vector2i(render_dimensions_f); + + Mesh mesh = geometry.Release(Geometry::ReleaseMode::ClearMesh); + MeshUtilities::GenerateQuad(mesh, Vector2f(0), render_dimensions_f, quad_colour, Vector2f(0), Vector2f(1)); + geometry = GetRenderManager()->MakeGeometry(std::move(mesh)); + + geometry_dirty = false; +} + +bool ElementLottie::LoadAnimation() +{ + animation_dirty = false; + intrinsic_dimensions = Vector2f{}; + texture = {}; + animation.reset(); + prev_animation_frame = size_t(-1); + time_animation_start = -1; + + const String attribute_src = GetAttribute("src", ""); + + if (attribute_src.empty()) + return false; + + String path = attribute_src; + String directory; + + if (ElementDocument* document = GetOwnerDocument()) + { + const String document_source_url = StringUtilities::Replace(document->GetSourceURL(), '|', ':'); + GetSystemInterface()->JoinPath(path, document_source_url, attribute_src); + GetSystemInterface()->JoinPath(directory, document_source_url, ""); + } + + String json_data; + + if (path.empty() || !GetFileInterface()->LoadFile(path, json_data)) + { + Log::Message(Rml::Log::Type::LT_WARNING, "Could not load lottie file %s", path.c_str()); + return false; + } + + animation = rlottie::Animation::loadFromData(std::move(json_data), path, directory); + + if (!animation) + { + Log::Message(Rml::Log::Type::LT_WARNING, "Could not construct the lottie animation %s", path.c_str()); + return false; + } + + size_t width = 0, height = 0; + animation->size(width, height); + intrinsic_dimensions.x = float(width); + intrinsic_dimensions.y = float(height); + + return true; +} + +void ElementLottie::UpdateTexture() +{ + if (!animation) + return; + + RenderManager* render_manager = GetRenderManager(); + if (!render_manager) + return; + + const double t = GetSystemInterface()->GetElapsedTime(); + + // Find the next animation frame to display. + // Here it is possible to add more logic to control playback speed, pause/resume, and more. + double _unused; + // Find the normalized animation progress [0, 1]. + const double pos = std::modf((t - time_animation_start) / animation->duration(), &_unused); + + const size_t next_frame = animation->frameAtPos(pos); + if (!texture_size_dirty && next_frame == prev_animation_frame) + { + // No need to update the texture if we are drawing the same frame at the same size. + return; + } + + // Resize the texture buffer if necessary. + const size_t new_texture_data_size = 4 * render_dimensions.x * render_dimensions.y; + if (new_texture_data_size > texture_data_size) + { + texture_data.reset(new byte[new_texture_data_size]); + texture_data_size = new_texture_data_size; + } + + // Callback for generating texture. + auto texture_callback = [this, next_frame](const CallbackTextureInterface& texture_interface) -> bool { + RMLUI_ASSERT(animation); + + const size_t bytes_per_line = 4 * render_dimensions.x; + const size_t total_bytes = bytes_per_line * render_dimensions.y; + byte* p_data = texture_data.get(); + + rlottie::Surface surface(reinterpret_cast(p_data), render_dimensions.x, render_dimensions.y, bytes_per_line); + animation->renderSync(next_frame, surface); + + // Swizzle the channel order from rlottie's BGRA to RmlUi's RGBA. + for (size_t i = 0; i < total_bytes; i += 4) + { + // Swap the RB order for correct color channels. + std::swap(p_data[i], p_data[i + 2]); + +#ifdef RMLUI_DEBUG + const byte alpha = p_data[i + 3]; + for (int c = 0; c < 3; c++) + RMLUI_ASSERTMSG(p_data[i + c] <= alpha, "Glyph data is assumed to be encoded in premultiplied alpha, but that is not the case."); +#endif + } + + if (!texture_interface.GenerateTexture({p_data, total_bytes}, render_dimensions)) + { + Log::Message(Rml::Log::Type::LT_WARNING, "Could not generate texture for lottie animation: %s", GetAttribute("src", "").c_str()); + return false; + } + return true; + }; + + texture = render_manager->MakeCallbackTexture(std::move(texture_callback)); + + prev_animation_frame = next_frame; + texture_size_dirty = false; +} + +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lottie/LottiePlugin.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lottie/LottiePlugin.cpp new file mode 100644 index 00000000..eeda064a --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lottie/LottiePlugin.cpp @@ -0,0 +1,36 @@ +#include "../../Include/RmlUi/Core/Core.h" +#include "../../Include/RmlUi/Core/ElementInstancer.h" +#include "../../Include/RmlUi/Core/Factory.h" +#include "../../Include/RmlUi/Core/Log.h" +#include "../../Include/RmlUi/Core/Plugin.h" +#include "../../Include/RmlUi/Lottie/ElementLottie.h" + +namespace Rml { +namespace Lottie { + + class LottiePlugin : public Plugin { + public: + void OnInitialise() override + { + instancer = MakeUnique>(); + + Factory::RegisterElementInstancer("lottie", instancer.get()); + + Log::Message(Log::LT_INFO, "Lottie plugin initialised."); + } + + void OnShutdown() override { delete this; } + + int GetEventClasses() override { return Plugin::EVT_BASIC; } + + private: + UniquePtr> instancer; + }; + + void Initialise() + { + RegisterPlugin(new LottiePlugin); + } + +} // namespace Lottie +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lottie/LottiePlugin.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lottie/LottiePlugin.h new file mode 100644 index 00000000..ffbb012a --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lottie/LottiePlugin.h @@ -0,0 +1,9 @@ +#pragma once + +namespace Rml { +namespace Lottie { + + void Initialise(); + +} +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/.clang-format b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/.clang-format new file mode 100644 index 00000000..cbb10f54 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/.clang-format @@ -0,0 +1,2 @@ +BasedOnStyle: InheritParentConfig +NamespaceIndentation: None diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/CMakeLists.txt b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/CMakeLists.txt new file mode 100644 index 00000000..ed6ded8f --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/CMakeLists.txt @@ -0,0 +1,118 @@ +add_library(rmlui_lua + Colourb.cpp + Colourb.h + Colourf.cpp + Colourf.h + Context.cpp + Context.h + ContextDocumentsProxy.cpp + ContextDocumentsProxy.h + Document.cpp + Document.h + Element.cpp + Element.h + ElementAttributesProxy.cpp + ElementAttributesProxy.h + ElementChildNodesProxy.cpp + ElementChildNodesProxy.h + ElementInstancer.cpp + ElementInstancer.h + ElementStyleProxy.cpp + ElementStyleProxy.h + ElementText.cpp + ElementText.h + Event.cpp + Event.h + EventParametersProxy.cpp + EventParametersProxy.h + GlobalLuaFunctions.cpp + GlobalLuaFunctions.h + Interpreter.cpp + Log.cpp + Log.h + Lua.cpp + LuaDataModel.cpp + LuaDataModel.h + LuaDocument.cpp + LuaDocument.h + LuaDocumentElementInstancer.cpp + LuaDocumentElementInstancer.h + LuaElementInstancer.cpp + LuaElementInstancer.h + LuaEventListener.cpp + LuaEventListener.h + LuaEventListenerInstancer.cpp + LuaEventListenerInstancer.h + LuaPlugin.cpp + LuaPlugin.h + LuaType.cpp + Pairs.h + RmlUi.cpp + RmlUi.h + RmlUiContextsProxy.cpp + RmlUiContextsProxy.h + Utilities.cpp + Vector2f.cpp + Vector2f.h + Vector2i.cpp + Vector2i.h +) + +target_sources(rmlui_lua PRIVATE + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Lua/Header.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Lua/IncludeLua.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Lua/Interpreter.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Lua/Lua.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Lua/LuaType.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Lua/LuaType.inl" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Lua/Utilities.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Lua.h" +) + +set_common_target_options(rmlui_lua) + +target_include_directories(rmlui_lua PRIVATE "${PROJECT_SOURCE_DIR}/Include") +target_include_directories(rmlui_lua INTERFACE + "$" + "$" +) + +add_library(RmlUi::Lua ALIAS rmlui_lua) + +target_link_libraries(rmlui_lua PUBLIC rmlui_core) + +# Use public linking, as some of our public headers require Lua. However, in principle the Lua bindings can be used +# without the Lua header files, in particular `RmlUi/Lua/Lua.h` does not require Lua. +target_link_libraries(rmlui_lua PUBLIC RmlUi::External::Lua) + +# Set up the necessary compile definition if Lua is compiled as C++. +if(RMLUI_LUA_BINDINGS_LIBRARY STREQUAL "lua_as_cxx") + target_compile_definitions(rmlui_lua PUBLIC "RMLUI_LUA_AS_CXX") +endif() + +# Set up definitions to export functions and classes as appropriate +get_target_property(rmlui_lua_TYPE rmlui_lua "TYPE") +if(rmlui_lua_TYPE STREQUAL "SHARED_LIBRARY") + # If RmlUi lua is being compiled as a shared library, notify code to export functions using __dllspec() + # For applications consuming the library, the headers will automatically use dllimport + # Platform and compiler handling is already done by code via pre-processor macros + target_compile_definitions(rmlui_lua PRIVATE "RMLUI_LUA_EXPORTS") +endif() + +# Set additional target properties +set_target_properties(rmlui_lua PROPERTIES + # Add export name so that it can be exported with a namespaced name instead + # of using the name we actually used to declare the target + EXPORT_NAME "Lua" +) + +add_subdirectory("Elements") + +install(TARGETS rmlui_lua + EXPORT RmlUiTargets + ${RMLUI_RUNTIME_DEPENDENCY_SET_ARG} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} +) +install_target_pdb(rmlui_lua) diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Colourb.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Colourb.cpp new file mode 100644 index 00000000..83040ad9 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Colourb.cpp @@ -0,0 +1,201 @@ +#include "Colourb.h" + +namespace Rml { +namespace Lua { + +template <> +void ExtraInit(lua_State* L, int metatable_index) +{ + lua_pushcfunction(L, Colourbnew); + lua_setfield(L, metatable_index - 1, "new"); + + lua_pushcfunction(L, Colourb__eq); + lua_setfield(L, metatable_index, "__eq"); + + lua_pushcfunction(L, Colourb__add); + lua_setfield(L, metatable_index, "__add"); + + lua_pushcfunction(L, Colourb__mul); + lua_setfield(L, metatable_index, "__mul"); + + return; +} +int Colourbnew(lua_State* L) +{ + byte red = (byte)luaL_checkinteger(L, 1); + byte green = (byte)luaL_checkinteger(L, 2); + byte blue = (byte)luaL_checkinteger(L, 3); + byte alpha = (byte)luaL_checkinteger(L, 4); + + Colourb* col = new Colourb(red, green, blue, alpha); + + LuaType::push(L, col, true); + return 1; +} + +int Colourb__eq(lua_State* L) +{ + Colourb* lhs = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(lhs); + Colourb* rhs = LuaType::check(L, 2); + RMLUI_CHECK_OBJ(rhs); + + lua_pushboolean(L, (*lhs) == (*rhs) ? 1 : 0); + return 1; +} + +int Colourb__add(lua_State* L) +{ + Colourb* lhs = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(lhs); + Colourb* rhs = LuaType::check(L, 2); + RMLUI_CHECK_OBJ(rhs); + + Colourb* res = new Colourb((*lhs) + (*rhs)); + + LuaType::push(L, res, true); + return 1; +} + +int Colourb__mul(lua_State* L) +{ + Colourb* lhs = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(lhs); + float rhs = (float)luaL_checknumber(L, 2); + + Colourb* res = new Colourb((*lhs) * rhs); + + LuaType::push(L, res, true); + return 1; +} + +// getters +int ColourbGetAttrred(lua_State* L) +{ + Colourb* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + lua_pushinteger(L, obj->red); + return 1; +} + +int ColourbGetAttrgreen(lua_State* L) +{ + Colourb* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + lua_pushinteger(L, obj->green); + return 1; +} + +int ColourbGetAttrblue(lua_State* L) +{ + Colourb* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + lua_pushinteger(L, obj->blue); + return 1; +} + +int ColourbGetAttralpha(lua_State* L) +{ + Colourb* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + lua_pushinteger(L, obj->alpha); + return 1; +} + +int ColourbGetAttrrgba(lua_State* L) +{ + Colourb* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + lua_pushinteger(L, obj->red); + lua_pushinteger(L, obj->green); + lua_pushinteger(L, obj->blue); + lua_pushinteger(L, obj->alpha); + return 4; +} + +// setters +int ColourbSetAttrred(lua_State* L) +{ + Colourb* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + byte red = (byte)luaL_checkinteger(L, 2); + obj->red = red; + return 0; +} + +int ColourbSetAttrgreen(lua_State* L) +{ + Colourb* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + byte green = (byte)luaL_checkinteger(L, 2); + obj->green = green; + return 0; +} + +int ColourbSetAttrblue(lua_State* L) +{ + Colourb* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + byte blue = (byte)luaL_checkinteger(L, 2); + obj->blue = blue; + return 0; +} + +int ColourbSetAttralpha(lua_State* L) +{ + Colourb* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + byte alpha = (byte)luaL_checkinteger(L, 2); + obj->alpha = alpha; + return 0; +} + +int ColourbSetAttrrgba(lua_State* L) +{ + Colourb* obj = nullptr; + int top = lua_gettop(L); + // each of the items are optional. + if (top > 0) + { + obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + if (top > 1) + { + if (top > 2) + { + if (top > 3) + obj->alpha = (byte)luaL_checkinteger(L, 4); + obj->blue = (byte)luaL_checkinteger(L, 3); + } + obj->green = (byte)luaL_checkinteger(L, 2); + } + obj->red = (byte)luaL_checkinteger(L, 1); + } + return 0; +} + +RegType ColourbMethods[] = { + {nullptr, nullptr}, +}; + +luaL_Reg ColourbGetters[] = { + RMLUI_LUAGETTER(Colourb, red), + RMLUI_LUAGETTER(Colourb, green), + RMLUI_LUAGETTER(Colourb, blue), + RMLUI_LUAGETTER(Colourb, alpha), + RMLUI_LUAGETTER(Colourb, rgba), + {nullptr, nullptr}, +}; + +luaL_Reg ColourbSetters[] = { + RMLUI_LUASETTER(Colourb, red), + RMLUI_LUASETTER(Colourb, green), + RMLUI_LUASETTER(Colourb, blue), + RMLUI_LUASETTER(Colourb, alpha), + RMLUI_LUASETTER(Colourb, rgba), + {nullptr, nullptr}, +}; + +RMLUI_LUATYPE_DEFINE(Colourb) +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Colourb.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Colourb.h new file mode 100644 index 00000000..2ba92bf0 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Colourb.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include + +namespace Rml { +namespace Lua { +template <> +void ExtraInit(lua_State* L, int metatable_index); +int Colourbnew(lua_State* L); +int Colourb__eq(lua_State* L); +int Colourb__add(lua_State* L); +int Colourb__mul(lua_State* L); + +// getters +int ColourbGetAttrred(lua_State* L); +int ColourbGetAttrgreen(lua_State* L); +int ColourbGetAttrblue(lua_State* L); +int ColourbGetAttralpha(lua_State* L); +int ColourbGetAttrrgba(lua_State* L); + +// setters +int ColourbSetAttrred(lua_State* L); +int ColourbSetAttrgreen(lua_State* L); +int ColourbSetAttrblue(lua_State* L); +int ColourbSetAttralpha(lua_State* L); +int ColourbSetAttrrgba(lua_State* L); + +extern RegType ColourbMethods[]; +extern luaL_Reg ColourbGetters[]; +extern luaL_Reg ColourbSetters[]; + +RMLUI_LUATYPE_DECLARE(Colourb) +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Colourf.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Colourf.cpp new file mode 100644 index 00000000..8f7e41dd --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Colourf.cpp @@ -0,0 +1,173 @@ +#include "Colourf.h" + +namespace Rml { +namespace Lua { + +template <> +void ExtraInit(lua_State* L, int metatable_index) +{ + lua_pushcfunction(L, Colourfnew); + lua_setfield(L, metatable_index - 1, "new"); + + lua_pushcfunction(L, Colourf__eq); + lua_setfield(L, metatable_index, "__eq"); + + return; +} + +// metamethods +int Colourfnew(lua_State* L) +{ + float red = (float)luaL_checknumber(L, 1); + float green = (float)luaL_checknumber(L, 2); + float blue = (float)luaL_checknumber(L, 3); + float alpha = (float)luaL_checknumber(L, 4); + + Colourf* col = new Colourf(red, green, blue, alpha); + + LuaType::push(L, col, true); + return 1; +} + +int Colourf__eq(lua_State* L) +{ + Colourf* lhs = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(lhs); + Colourf* rhs = LuaType::check(L, 2); + RMLUI_CHECK_OBJ(rhs); + + lua_pushboolean(L, (*lhs) == (*rhs) ? 1 : 0); + return 1; +} + +// getters +int ColourfGetAttrred(lua_State* L) +{ + Colourf* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + lua_pushnumber(L, obj->red); + return 1; +} + +int ColourfGetAttrgreen(lua_State* L) +{ + Colourf* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + lua_pushnumber(L, obj->green); + return 1; +} + +int ColourfGetAttrblue(lua_State* L) +{ + Colourf* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + lua_pushnumber(L, obj->blue); + return 1; +} + +int ColourfGetAttralpha(lua_State* L) +{ + Colourf* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + lua_pushnumber(L, obj->alpha); + return 1; +} + +int ColourfGetAttrrgba(lua_State* L) +{ + Colourf* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + lua_pushnumber(L, obj->red); + lua_pushnumber(L, obj->green); + lua_pushnumber(L, obj->blue); + lua_pushnumber(L, obj->alpha); + return 4; +} + +// setters +int ColourfSetAttrred(lua_State* L) +{ + Colourf* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + float red = (float)luaL_checknumber(L, 2); + obj->red = red; + return 0; +} + +int ColourfSetAttrgreen(lua_State* L) +{ + Colourf* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + float green = (float)luaL_checknumber(L, 2); + obj->green = green; + return 0; +} + +int ColourfSetAttrblue(lua_State* L) +{ + Colourf* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + float blue = (float)luaL_checknumber(L, 2); + obj->blue = blue; + return 0; +} + +int ColourfSetAttralpha(lua_State* L) +{ + Colourf* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + float alpha = (float)luaL_checknumber(L, 2); + obj->alpha = alpha; + return 0; +} + +int ColourfSetAttrrgba(lua_State* L) +{ + Colourf* obj = nullptr; + int top = lua_gettop(L); + // each of the items are optional. + if (top > 0) + { + obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + if (top > 1) + { + if (top > 2) + { + if (top > 3) + obj->alpha = (float)luaL_checknumber(L, 4); + obj->blue = (float)luaL_checknumber(L, 3); + } + obj->green = (float)luaL_checknumber(L, 2); + } + obj->red = (float)luaL_checknumber(L, 1); + } + return 0; +} + +RegType ColourfMethods[] = { + {nullptr, nullptr}, +}; + +luaL_Reg ColourfGetters[] = { + RMLUI_LUAGETTER(Colourf, red), + RMLUI_LUAGETTER(Colourf, green), + RMLUI_LUAGETTER(Colourf, blue), + RMLUI_LUAGETTER(Colourf, alpha), + RMLUI_LUAGETTER(Colourf, rgba), + {nullptr, nullptr}, +}; + +luaL_Reg ColourfSetters[] = { + RMLUI_LUASETTER(Colourf, red), + RMLUI_LUASETTER(Colourf, green), + RMLUI_LUASETTER(Colourf, blue), + RMLUI_LUASETTER(Colourf, alpha), + RMLUI_LUASETTER(Colourf, rgba), + {nullptr, nullptr}, +}; + +RMLUI_LUATYPE_DEFINE(Colourf) + +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Colourf.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Colourf.h new file mode 100644 index 00000000..8e0c5100 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Colourf.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include +#include + +namespace Rml { +namespace Lua { +template <> +void ExtraInit(lua_State* L, int metatable_index); +// metamethods +int Colourfnew(lua_State* L); +int Colourf__eq(lua_State* L); + +// getters +int ColourfGetAttrred(lua_State* L); +int ColourfGetAttrgreen(lua_State* L); +int ColourfGetAttrblue(lua_State* L); +int ColourfGetAttralpha(lua_State* L); +int ColourfGetAttrrgba(lua_State* L); + +// setters +int ColourfSetAttrred(lua_State* L); +int ColourfSetAttrgreen(lua_State* L); +int ColourfSetAttrblue(lua_State* L); +int ColourfSetAttralpha(lua_State* L); +int ColourfSetAttrrgba(lua_State* L); + +extern RegType ColourfMethods[]; +extern luaL_Reg ColourfGetters[]; +extern luaL_Reg ColourfSetters[]; + +RMLUI_LUATYPE_DECLARE(Colourf) +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Context.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Context.cpp new file mode 100644 index 00000000..9f0b694c --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Context.cpp @@ -0,0 +1,319 @@ +#include "Context.h" +#include "ContextDocumentsProxy.h" +#include "LuaDataModel.h" +#include "LuaEventListener.h" +#include +#include +#include + +namespace Rml { +namespace Lua { +typedef ElementDocument Document; +template <> +void ExtraInit(lua_State* /*L*/, int /*metatable_index*/) +{ + return; +} + +// methods +int ContextAddEventListener(lua_State* L, Context* obj) +{ + // need to make an EventListener for Lua before I can do anything else + const char* evt = luaL_checkstring(L, 1); // event + Element* element = nullptr; + bool capturephase = false; + // get the rest of the stuff needed to construct the listener + if (lua_gettop(L) > 2) + { + if (!lua_isnoneornil(L, 3)) + element = LuaType::check(L, 3); + if (!lua_isnoneornil(L, 4)) + capturephase = RMLUI_CHECK_BOOL(L, 4); + } + int type = lua_type(L, 2); + if (type == LUA_TFUNCTION) + { + if (element) + element->AddEventListener(evt, new LuaEventListener(L, 2, element), capturephase); + else + obj->AddEventListener(evt, new LuaEventListener(L, 2, nullptr), capturephase); + } + else if (type == LUA_TSTRING) + { + if (element) + element->AddEventListener(evt, new LuaEventListener(luaL_checkstring(L, 2), element), capturephase); + else + obj->AddEventListener(evt, new LuaEventListener(luaL_checkstring(L, 2), nullptr), capturephase); + } + else + { + Log::Message(Log::LT_WARNING, "Lua Context:AddEventLisener's 2nd argument can only be a Lua function or a string, you passed in a %s", + lua_typename(L, type)); + } + return 0; +} + +int ContextCreateDocument(lua_State* L, Context* obj) +{ + const char* tag; + if (lua_gettop(L) < 1) + tag = "body"; + else + tag = luaL_checkstring(L, 1); + Document* doc = obj->CreateDocument(tag); + LuaType::push(L, doc, false); + return 1; +} + +int ContextLoadDocument(lua_State* L, Context* obj) +{ + const char* path = luaL_checkstring(L, 1); + Document* doc = obj->LoadDocument(path); + LuaType::push(L, doc, false); + return 1; +} + +int ContextRender(lua_State* L, Context* obj) +{ + lua_pushboolean(L, obj->Render()); + return 1; +} + +int ContextUnloadAllDocuments(lua_State* /*L*/, Context* obj) +{ + obj->UnloadAllDocuments(); + return 0; +} + +int ContextUnloadDocument(lua_State* L, Context* obj) +{ + Document* doc = LuaType::check(L, 1); + obj->UnloadDocument(doc); + return 0; +} + +int ContextUpdate(lua_State* L, Context* obj) +{ + lua_pushboolean(L, obj->Update()); + return 1; +} + +int ContextOpenDataModel(lua_State* L, Context* obj) +{ + if (!OpenLuaDataModel(L, obj, 1, 2)) + { + // Open fails + lua_pushboolean(L, false); + } + return 1; +} + +int ContextCloseDataModel(lua_State* L, Context* obj) +{ + CloseLuaDataModel(L, obj, 1); + return 0; +} + +// input +int ContextProcessMouseMove(lua_State* L, Context* obj) +{ + int x = (int)luaL_checkinteger(L, 1); + int y = (int)luaL_checkinteger(L, 2); + int flags = (int)luaL_checkinteger(L, 3); + lua_pushboolean(L, obj->ProcessMouseMove(x, y, flags)); + return 1; +} + +int ContextProcessMouseButtonDown(lua_State* L, Context* obj) +{ + int button_index = (int)luaL_checkinteger(L, 1); + int key_modifier_state = (int)luaL_checkinteger(L, 2); + lua_pushboolean(L, obj->ProcessMouseButtonDown(button_index, key_modifier_state)); + return 1; +} + +int ContextProcessMouseButtonUp(lua_State* L, Context* obj) +{ + int button_index = (int)luaL_checkinteger(L, 1); + int key_modifier_state = (int)luaL_checkinteger(L, 2); + lua_pushboolean(L, obj->ProcessMouseButtonUp(button_index, key_modifier_state)); + return 1; +} + +int ContextProcessMouseWheel(lua_State* L, Context* obj) +{ + float wheel_delta = (float)luaL_checknumber(L, 1); + int key_modifier_state = (int)luaL_checkinteger(L, 2); + lua_pushboolean(L, obj->ProcessMouseWheel(wheel_delta, key_modifier_state)); + return 1; +} + +int ContextProcessMouseLeave(lua_State* L, Context* obj) +{ + lua_pushboolean(L, obj->ProcessMouseLeave()); + return 1; +} + +int ContextIsMouseInteracting(lua_State* L, Context* obj) +{ + lua_pushboolean(L, obj->IsMouseInteracting()); + return 1; +} + +int ContextProcessKeyDown(lua_State* L, Context* obj) +{ + Rml::Input::KeyIdentifier key_identifier = (Rml::Input::KeyIdentifier)luaL_checkinteger(L, 1); + int key_modifier_state = (int)luaL_checkinteger(L, 2); + lua_pushboolean(L, obj->ProcessKeyDown(key_identifier, key_modifier_state)); + return 1; +} + +int ContextProcessKeyUp(lua_State* L, Context* obj) +{ + Rml::Input::KeyIdentifier key_identifier = (Rml::Input::KeyIdentifier)luaL_checkinteger(L, 1); + int key_modifier_state = (int)luaL_checkinteger(L, 2); + lua_pushboolean(L, obj->ProcessKeyUp(key_identifier, key_modifier_state)); + return 1; +} + +int ContextProcessTextInput(lua_State* L, Context* obj) +{ + const char* text = NULL; + int character = -1; + + if (lua_isstring(L, 1)) + text = lua_tostring(L, 1); + else + character = (int)luaL_checkinteger(L, 1); + + if (character > 0) + lua_pushboolean(L, obj->ProcessTextInput((char)character)); + else if (text != NULL) + lua_pushboolean(L, obj->ProcessTextInput(text)); + else + Log::Message(Log::LT_WARNING, "Could not process text input on context '%s'.", obj->GetName().c_str()); + + return 1; +} + +// getters +int ContextGetAttrdimensions(lua_State* L) +{ + Context* cont = LuaType::check(L, 1); + Vector2i* dim = new Vector2i(cont->GetDimensions()); + LuaType::push(L, dim, true); + return 1; +} + +// returns a table of everything +int ContextGetAttrdocuments(lua_State* L) +{ + Context* cont = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(cont); + ContextDocumentsProxy* cdp = new ContextDocumentsProxy(); + cdp->owner = cont; + LuaType::push(L, cdp, true); // does get garbage collected (deleted) + return 1; +} + +int ContextGetAttrdp_ratio(lua_State* L) +{ + Context* cont = LuaType::check(L, 1); + float dp_ratio = cont->GetDensityIndependentPixelRatio(); + lua_pushnumber(L, dp_ratio); + return 1; +} + +int ContextGetAttrfocus_element(lua_State* L) +{ + Context* cont = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(cont); + LuaType::push(L, cont->GetFocusElement()); + return 1; +} + +int ContextGetAttrhover_element(lua_State* L) +{ + Context* cont = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(cont); + LuaType::push(L, cont->GetHoverElement()); + return 1; +} + +int ContextGetAttrname(lua_State* L) +{ + Context* cont = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(cont); + lua_pushstring(L, cont->GetName().c_str()); + return 1; +} + +int ContextGetAttrroot_element(lua_State* L) +{ + Context* cont = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(cont); + LuaType::push(L, cont->GetRootElement()); + return 1; +} + +// setters +int ContextSetAttrdimensions(lua_State* L) +{ + Context* cont = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(cont); + Vector2i* dim = LuaType::check(L, 2); + cont->SetDimensions(*dim); + return 0; +} + +int ContextSetAttrdp_ratio(lua_State* L) +{ + Context* cont = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(cont); + lua_Number dp_ratio = luaL_checknumber(L, 2); + cont->SetDensityIndependentPixelRatio((float)dp_ratio); + return 0; +} + +RegType ContextMethods[] = { + RMLUI_LUAMETHOD(Context, AddEventListener), + RMLUI_LUAMETHOD(Context, CreateDocument), + RMLUI_LUAMETHOD(Context, LoadDocument), + RMLUI_LUAMETHOD(Context, Render), + RMLUI_LUAMETHOD(Context, UnloadAllDocuments), + RMLUI_LUAMETHOD(Context, UnloadDocument), + RMLUI_LUAMETHOD(Context, Update), + RMLUI_LUAMETHOD(Context, OpenDataModel), + RMLUI_LUAMETHOD(Context, CloseDataModel), + RMLUI_LUAMETHOD(Context, ProcessMouseMove), + RMLUI_LUAMETHOD(Context, ProcessMouseButtonDown), + RMLUI_LUAMETHOD(Context, ProcessMouseButtonUp), + RMLUI_LUAMETHOD(Context, ProcessMouseWheel), + RMLUI_LUAMETHOD(Context, ProcessMouseLeave), + RMLUI_LUAMETHOD(Context, IsMouseInteracting), + RMLUI_LUAMETHOD(Context, ProcessKeyDown), + RMLUI_LUAMETHOD(Context, ProcessKeyUp), + RMLUI_LUAMETHOD(Context, ProcessTextInput), + {nullptr, nullptr}, +}; + +luaL_Reg ContextGetters[] = { + RMLUI_LUAGETTER(Context, dimensions), + RMLUI_LUAGETTER(Context, documents), + RMLUI_LUAGETTER(Context, dp_ratio), + RMLUI_LUAGETTER(Context, focus_element), + RMLUI_LUAGETTER(Context, hover_element), + RMLUI_LUAGETTER(Context, name), + RMLUI_LUAGETTER(Context, root_element), + {nullptr, nullptr}, +}; + +luaL_Reg ContextSetters[] = { + RMLUI_LUASETTER(Context, dimensions), + RMLUI_LUASETTER(Context, dp_ratio), + {nullptr, nullptr}, +}; + +RMLUI_LUATYPE_DEFINE(Context) +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Context.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Context.h new file mode 100644 index 00000000..8f2025d9 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Context.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include + +namespace Rml { +namespace Lua { +template <> +void ExtraInit(lua_State* L, int metatable_index); + +// methods +int ContextAddEventListener(lua_State* L, Context* obj); +int ContextCreateDocument(lua_State* L, Context* obj); +int ContextLoadDocument(lua_State* L, Context* obj); +int ContextRender(lua_State* L, Context* obj); +int ContextUnloadAllDocuments(lua_State* L, Context* obj); +int ContextUnloadDocument(lua_State* L, Context* obj); +int ContextUpdate(lua_State* L, Context* obj); + +// getters +int ContextGetAttrdimensions(lua_State* L); +int ContextGetAttrdocuments(lua_State* L); +int ContextGetAttrdp_ratio(lua_State* L); +int ContextGetAttrfocus_element(lua_State* L); +int ContextGetAttrhover_element(lua_State* L); +int ContextGetAttrname(lua_State* L); +int ContextGetAttrroot_element(lua_State* L); + +// setters +int ContextSetAttrdimensions(lua_State* L); +int ContextSetAttrdp_ratio(lua_State* L); + +extern RegType ContextMethods[]; +extern luaL_Reg ContextGetters[]; +extern luaL_Reg ContextSetters[]; + +RMLUI_LUATYPE_DECLARE(Context) +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/ContextDocumentsProxy.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/ContextDocumentsProxy.cpp new file mode 100644 index 00000000..eb688a96 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/ContextDocumentsProxy.cpp @@ -0,0 +1,105 @@ +#include "ContextDocumentsProxy.h" +#include + +namespace Rml { +namespace Lua { +typedef ElementDocument Document; +template <> +void ExtraInit(lua_State* L, int metatable_index) +{ + lua_pushcfunction(L, ContextDocumentsProxy__index); + lua_setfield(L, metatable_index, "__index"); + lua_pushcfunction(L, ContextDocumentsProxy__pairs); + lua_setfield(L, metatable_index, "__pairs"); +} + +int ContextDocumentsProxy__index(lua_State* L) +{ + /*the table obj and the missing key are currently on the stack(index 1 & 2) as defined by the Lua language*/ + int type = lua_type(L, 2); + if (type == LUA_TNUMBER || type == LUA_TSTRING) // only valid key types + { + ContextDocumentsProxy* proxy = LuaType::check(L, 1); + Document* ret = nullptr; + if (type == LUA_TSTRING) + ret = proxy->owner->GetDocument(luaL_checkstring(L, 2)); + else + ret = proxy->owner->GetDocument((int)luaL_checkinteger(L, 2) - 1); + LuaType::push(L, ret, false); + return 1; + } + else + return LuaType::index(L); +} + +struct ContextDocumentsProxyPairs { + static int next(lua_State* L) + { + ContextDocumentsProxy* obj = LuaType::check(L, 1); + ContextDocumentsProxyPairs* self = static_cast(lua_touserdata(L, lua_upvalueindex(1))); + Document* doc = nullptr; + int num_docs = obj->owner->GetNumDocuments(); + // because there can be missing indexes, make sure to continue until there + // is actually a document at the index + while (self->m_cur < num_docs) + { + doc = obj->owner->GetDocument(self->m_cur++); + if (doc != nullptr) + break; + } + if (doc == nullptr) + { + return 0; + } + lua_pushstring(L, doc->GetId().c_str()); + LuaType::push(L, doc); + return 2; + } + static int destroy(lua_State* L) + { + static_cast(lua_touserdata(L, 1))->~ContextDocumentsProxyPairs(); + return 0; + } + static int constructor(lua_State* L) + { + void* storage = lua_newuserdata(L, sizeof(ContextDocumentsProxyPairs)); + if (luaL_newmetatable(L, "RmlUi::Lua::ContextDocumentsProxyPairs")) + { + static luaL_Reg mt[] = { + {"__gc", destroy}, + {NULL, NULL}, + }; + luaL_setfuncs(L, mt, 0); + } + lua_setmetatable(L, -2); + lua_pushcclosure(L, next, 1); + new (storage) ContextDocumentsProxyPairs(); + return 1; + } + ContextDocumentsProxyPairs() : m_cur(0) {} + int m_cur; +}; + +int ContextDocumentsProxy__pairs(lua_State* L) +{ + ContextDocumentsProxyPairs::constructor(L); + lua_pushvalue(L, 1); + return 2; +} + +RegType ContextDocumentsProxyMethods[] = { + {nullptr, nullptr}, +}; + +luaL_Reg ContextDocumentsProxyGetters[] = { + {nullptr, nullptr}, +}; + +luaL_Reg ContextDocumentsProxySetters[] = { + {nullptr, nullptr}, +}; + +RMLUI_LUATYPE_DEFINE(ContextDocumentsProxy) + +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/ContextDocumentsProxy.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/ContextDocumentsProxy.h new file mode 100644 index 00000000..17a7b147 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/ContextDocumentsProxy.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include +#include + +namespace Rml { +namespace Lua { +// where owner is the context that we should look information from +struct ContextDocumentsProxy { + Context* owner; +}; + +template <> +void ExtraInit(lua_State* L, int metatable_index); +int ContextDocumentsProxy__index(lua_State* L); +int ContextDocumentsProxy__pairs(lua_State* L); + +extern RegType ContextDocumentsProxyMethods[]; +extern luaL_Reg ContextDocumentsProxyGetters[]; +extern luaL_Reg ContextDocumentsProxySetters[]; + +RMLUI_LUATYPE_DECLARE(ContextDocumentsProxy) +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Document.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Document.cpp new file mode 100644 index 00000000..7715be0c --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Document.cpp @@ -0,0 +1,163 @@ +#include "Document.h" +#include "Element.h" +#include +#include +#include + +namespace Rml { +namespace Lua { + +template <> +void ExtraInit(lua_State* L, int metatable_index) +{ + // we will inherit from Element + ExtraInit(L, metatable_index); + LuaType::_regfunctions(L, metatable_index, metatable_index - 1); + AddTypeToElementAsTable(L); + + // create the DocumentFocus table + lua_getglobal(L, "DocumentModal"); + if (lua_isnoneornil(L, -1)) + { + lua_pop(L, 1); // pop unsucessful getglobal + lua_newtable(L); // create a table for holding the enum + lua_pushinteger(L, (int)ModalFlag::None); + lua_setfield(L, -2, "None"); + lua_pushinteger(L, (int)ModalFlag::Modal); + lua_setfield(L, -2, "Modal"); + lua_pushinteger(L, (int)ModalFlag::Keep); + lua_setfield(L, -2, "Keep"); + lua_setglobal(L, "DocumentModal"); + } + + // create the DocumentFocus table + lua_getglobal(L, "DocumentFocus"); + if (lua_isnoneornil(L, -1)) + { + lua_pop(L, 1); // pop unsucessful getglobal + lua_newtable(L); // create a table for holding the enum + lua_pushinteger(L, (int)FocusFlag::None); + lua_setfield(L, -2, "None"); + lua_pushinteger(L, (int)FocusFlag::Document); + lua_setfield(L, -2, "Document"); + lua_pushinteger(L, (int)FocusFlag::Keep); + lua_setfield(L, -2, "Keep"); + lua_pushinteger(L, (int)FocusFlag::Auto); + lua_setfield(L, -2, "Auto"); + lua_setglobal(L, "DocumentFocus"); + } +} + +// methods +int DocumentPullToFront(lua_State* /*L*/, Document* obj) +{ + obj->PullToFront(); + return 0; +} + +int DocumentPushToBack(lua_State* /*L*/, Document* obj) +{ + obj->PushToBack(); + return 0; +} + +int DocumentShow(lua_State* L, Document* obj) +{ + int top = lua_gettop(L); + if (top == 0) + obj->Show(); + else if (top == 1) + { + ModalFlag modal = (ModalFlag)luaL_checkinteger(L, 1); + obj->Show(modal); + } + else + { + ModalFlag modal = (ModalFlag)luaL_checkinteger(L, 1); + FocusFlag focus = (FocusFlag)luaL_checkinteger(L, 2); + obj->Show(modal, focus); + } + return 0; +} + +int DocumentHide(lua_State* /*L*/, Document* obj) +{ + obj->Hide(); + return 0; +} + +int DocumentClose(lua_State* /*L*/, Document* obj) +{ + obj->Close(); + return 0; +} + +int DocumentCreateElement(lua_State* L, Document* obj) +{ + const char* tag = luaL_checkstring(L, 1); + ElementPtr* ele = new ElementPtr(obj->CreateElement(tag)); + LuaType::push(L, ele, true); + return 1; +} + +int DocumentCreateTextNode(lua_State* L, Document* obj) +{ + // need ElementText object first + const char* text = luaL_checkstring(L, 1); + ElementPtr* et = new ElementPtr(obj->CreateTextNode(text)); + LuaType::push(L, et, true); + return 1; +} + +// getters +int DocumentGetAttrtitle(lua_State* L) +{ + Document* doc = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(doc); + lua_pushstring(L, doc->GetTitle().c_str()); + return 1; +} + +int DocumentGetAttrcontext(lua_State* L) +{ + Document* doc = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(doc); + LuaType::push(L, doc->GetContext(), false); + return 1; +} + +// setters +int DocumentSetAttrtitle(lua_State* L) +{ + Document* doc = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(doc); + const char* title = luaL_checkstring(L, 2); + doc->SetTitle(title); + return 0; +} + +RegType DocumentMethods[] = { + RMLUI_LUAMETHOD(Document, PullToFront), + RMLUI_LUAMETHOD(Document, PushToBack), + RMLUI_LUAMETHOD(Document, Show), + RMLUI_LUAMETHOD(Document, Hide), + RMLUI_LUAMETHOD(Document, Close), + RMLUI_LUAMETHOD(Document, CreateElement), + RMLUI_LUAMETHOD(Document, CreateTextNode), + {nullptr, nullptr}, +}; + +luaL_Reg DocumentGetters[] = { + RMLUI_LUAGETTER(Document, title), + RMLUI_LUAGETTER(Document, context), + {nullptr, nullptr}, +}; + +luaL_Reg DocumentSetters[] = { + RMLUI_LUASETTER(Document, title), + {nullptr, nullptr}, +}; + +RMLUI_LUATYPE_DEFINE(Document) +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Document.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Document.h new file mode 100644 index 00000000..69ed6fa7 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Document.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include + +namespace Rml { +namespace Lua { +typedef ElementDocument Document; + +template <> +void ExtraInit(lua_State* L, int metatable_index); + +// methods +int DocumentPullToFront(lua_State* L, Document* obj); +int DocumentPushToBack(lua_State* L, Document* obj); +int DocumentShow(lua_State* L, Document* obj); +int DocumentHide(lua_State* L, Document* obj); +int DocumentClose(lua_State* L, Document* obj); +int DocumentCreateElement(lua_State* L, Document* obj); +int DocumentCreateTextNode(lua_State* L, Document* obj); + +// getters +int DocumentGetAttrtitle(lua_State* L); +int DocumentGetAttrcontext(lua_State* L); + +// setters +int DocumentSetAttrtitle(lua_State* L); + +extern RegType DocumentMethods[]; +extern luaL_Reg DocumentGetters[]; +extern luaL_Reg DocumentSetters[]; + +RMLUI_LUATYPE_DECLARE(Document) +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Element.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Element.cpp new file mode 100644 index 00000000..c17be17f --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Element.cpp @@ -0,0 +1,652 @@ +#include "Element.h" +#include "ElementAttributesProxy.h" +#include "ElementChildNodesProxy.h" +#include "ElementStyleProxy.h" +#include "LuaEventListener.h" +#include + +namespace Rml { +namespace Lua { +typedef ElementDocument Document; + +template <> +void ExtraInit(lua_State* L, int metatable_index) +{ + int top = lua_gettop(L); + // guarantee the "Element.As" table exists + lua_getfield(L, metatable_index - 1, "As"); + if (lua_isnoneornil(L, -1)) // if it doesn't exist, create it + { + lua_newtable(L); + lua_setfield(L, metatable_index - 1, "As"); + } + lua_pop(L, 1); // pop the result of lua_getfield + lua_pushcfunction(L, Elementnew); + lua_setfield(L, metatable_index - 1, "new"); + lua_settop(L, top); +} + +int Elementnew(lua_State* L) +{ + const char* tag = luaL_checkstring(L, 1); + Element* ele = new Element(tag); + LuaType::push(L, ele, true); + return 1; +} + +// methods +int ElementAddEventListener(lua_State* L, Element* obj) +{ + int top = lua_gettop(L); + bool capture = false; + // default false if they didn't pass it in + if (top > 2) + capture = RMLUI_CHECK_BOOL(L, 3); + + const char* event = luaL_checkstring(L, 1); + + LuaEventListener* listener = nullptr; + int type = lua_type(L, 2); + if (type == LUA_TFUNCTION) + { + listener = new LuaEventListener(L, 2, obj); + } + else if (type == LUA_TSTRING) + { + const char* code = luaL_checkstring(L, 2); + listener = new LuaEventListener(code, obj); + } + else + { + Log::Message(Log::LT_WARNING, "Lua Context:AddEventLisener's 2nd argument can only be a Lua function or a string, you passed in a %s", + lua_typename(L, type)); + } + + if (listener != nullptr) + { + obj->AddEventListener(event, listener, capture); + } + return 0; +} + +int ElementAppendChild(lua_State* L, Element* obj) +{ + ElementPtr* element = LuaType::check(L, 1); + if (*element) + { + Element* child = obj->AppendChild(std::move(*element)); + LuaType::push(L, child, false); + } + else + { + Log::Message(Log::LT_WARNING, "Could not append child to element '%s', as the child was null. Was it already moved from?", + obj->GetAddress().c_str()); + lua_pushnil(L); + } + return 1; +} + +int ElementBlur(lua_State* /*L*/, Element* obj) +{ + obj->Blur(); + return 0; +} + +int ElementClick(lua_State* /*L*/, Element* obj) +{ + obj->Click(); + return 0; +} + +int ElementDispatchEvent(lua_State* L, Element* obj) +{ + const char* event = luaL_checkstring(L, 1); + Dictionary params; + lua_pushnil(L); // becauase lua_next pops a key from the stack first, we don't want to pop the table + while (lua_next(L, 2) != 0) + { + //[-1] is value, [-2] is key + int type = lua_type(L, -1); + const char* key = luaL_checkstring(L, -2); // key HAS to be a string, or things will go bad + switch (type) + { + case LUA_TNUMBER: params[key] = (float)lua_tonumber(L, -1); break; + case LUA_TBOOLEAN: params[key] = RMLUI_CHECK_BOOL(L, -1); break; + case LUA_TSTRING: params[key] = luaL_checkstring(L, -1); break; + case LUA_TUSERDATA: + case LUA_TLIGHTUSERDATA: params[key] = lua_touserdata(L, -1); break; + default: break; + } + lua_pop(L, 1); // pops value, leaves key for next iteration + } + obj->DispatchEvent(event, params); + return 0; +} + +int ElementFocus(lua_State* /*L*/, Element* obj) +{ + obj->Focus(); + return 0; +} + +int ElementGetAttribute(lua_State* L, Element* obj) +{ + const char* name = luaL_checkstring(L, 1); + Variant* var = obj->GetAttribute(name); + PushVariant(L, var); + return 1; +} + +int ElementGetElementById(lua_State* L, Element* obj) +{ + const char* id = luaL_checkstring(L, 1); + Element* ele = obj->GetElementById(id); + LuaType::push(L, ele, false); + return 1; +} + +int ElementGetElementsByTagName(lua_State* L, Element* obj) +{ + const char* tag = luaL_checkstring(L, 1); + ElementList list; + obj->GetElementsByTagName(list, tag); + lua_newtable(L); + for (unsigned int i = 0; i < list.size(); i++) + { + PushIndex(L, i); + LuaType::push(L, list[i], false); + lua_settable(L, -3); //-3 is the table + } + return 1; +} + +int ElementQuerySelector(lua_State* L, Element* obj) +{ + const char* sel = luaL_checkstring(L, 1); + Element* ele = obj->QuerySelector(sel); + LuaType::push(L, ele, false); + return 1; +} + +int ElementQuerySelectorAll(lua_State* L, Element* obj) +{ + const char* tag = luaL_checkstring(L, 1); + ElementList list; + obj->QuerySelectorAll(list, tag); + lua_newtable(L); + for (unsigned int i = 0; i < list.size(); i++) + { + PushIndex(L, i); + LuaType::push(L, list[i], false); + lua_settable(L, -3); //-3 is the table + } + return 1; +} + +int ElementMatches(lua_State* L, Element* obj) +{ + const char* tag = luaL_checkstring(L, 1); + lua_pushboolean(L, obj->Matches(tag)); + return 1; +} + +int ElementHasAttribute(lua_State* L, Element* obj) +{ + const char* name = luaL_checkstring(L, 1); + lua_pushboolean(L, obj->HasAttribute(name)); + return 1; +} + +int ElementHasChildNodes(lua_State* L, Element* obj) +{ + lua_pushboolean(L, obj->HasChildNodes()); + return 1; +} + +int ElementInsertBefore(lua_State* L, Element* obj) +{ + ElementPtr* element = LuaType::check(L, 1); + Element* adjacent = LuaType::check(L, 2); + if (*element) + { + Element* inserted = obj->InsertBefore(std::move(*element), adjacent); + LuaType::push(L, inserted, false); + } + else + { + Log::Message(Log::LT_WARNING, "Could not insert child to element '%s', as the child was null. Was it already moved from?", + obj->GetAddress().c_str()); + lua_pushnil(L); + } + return 1; +} + +int ElementIsClassSet(lua_State* L, Element* obj) +{ + const char* name = luaL_checkstring(L, 1); + lua_pushboolean(L, obj->IsClassSet(name)); + return 1; +} + +int ElementRemoveAttribute(lua_State* L, Element* obj) +{ + const char* name = luaL_checkstring(L, 1); + obj->RemoveAttribute(name); + return 0; +} + +int ElementRemoveChild(lua_State* L, Element* obj) +{ + Element* element = LuaType::check(L, 1); + lua_pushboolean(L, static_cast(obj->RemoveChild(element))); + return 1; +} + +int ElementReplaceChild(lua_State* L, Element* obj) +{ + ElementPtr* inserted = LuaType::check(L, 1); + Element* replaced = LuaType::check(L, 2); + if (*inserted) + lua_pushboolean(L, static_cast(obj->ReplaceChild(std::move(*inserted), replaced))); + else + Log::Message(Log::LT_WARNING, "Could not replace child in element '%s', as the child was null. Was it already moved from?", + obj->GetAddress().c_str()); + return 1; +} + +int ElementScrollIntoView(lua_State* L, Element* obj) +{ + bool align = RMLUI_CHECK_BOOL(L, 1); + obj->ScrollIntoView(align); + return 0; +} + +int ElementSetAttribute(lua_State* L, Element* obj) +{ + const char* name = luaL_checkstring(L, 1); + const char* value = luaL_checkstring(L, 2); + obj->SetAttribute(name, String(value)); + return 0; +} + +int ElementSetClass(lua_State* L, Element* obj) +{ + const char* name = luaL_checkstring(L, 1); + bool value = RMLUI_CHECK_BOOL(L, 2); + obj->SetClass(name, value); + return 0; +} + +// getters +int ElementGetAttrattributes(lua_State* L) +{ + Element* ele = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(ele); + ElementAttributesProxy* proxy = new ElementAttributesProxy(); + proxy->owner = ele; + LuaType::push(L, proxy, true); + return 1; +} + +int ElementGetAttrchild_nodes(lua_State* L) +{ + Element* ele = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(ele); + ElementChildNodesProxy* ecnp = new ElementChildNodesProxy(); + ecnp->owner = ele; + LuaType::push(L, ecnp, true); + return 1; +} + +int ElementGetAttrclass_name(lua_State* L) +{ + Element* ele = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(ele); + String classnames = ele->GetClassNames(); + lua_pushstring(L, classnames.c_str()); + return 1; +} + +int ElementGetAttrclient_left(lua_State* L) +{ + Element* ele = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(ele); + lua_pushnumber(L, ele->GetClientLeft()); + return 1; +} + +int ElementGetAttrclient_height(lua_State* L) +{ + Element* ele = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(ele); + lua_pushnumber(L, ele->GetClientHeight()); + return 1; +} + +int ElementGetAttrclient_top(lua_State* L) +{ + Element* ele = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(ele); + lua_pushnumber(L, ele->GetClientTop()); + return 1; +} + +int ElementGetAttrclient_width(lua_State* L) +{ + Element* ele = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(ele); + lua_pushnumber(L, ele->GetClientWidth()); + return 1; +} + +int ElementGetAttrfirst_child(lua_State* L) +{ + Element* ele = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(ele); + Element* child = ele->GetFirstChild(); + if (child == nullptr) + lua_pushnil(L); + else + LuaType::push(L, child, false); + return 1; +} + +int ElementGetAttrid(lua_State* L) +{ + Element* ele = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(ele); + lua_pushstring(L, ele->GetId().c_str()); + return 1; +} + +int ElementGetAttrinner_rml(lua_State* L) +{ + Element* ele = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(ele); + lua_pushstring(L, ele->GetInnerRML().c_str()); + return 1; +} + +int ElementGetAttrlast_child(lua_State* L) +{ + Element* ele = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(ele); + Element* child = ele->GetLastChild(); + if (child == nullptr) + lua_pushnil(L); + else + LuaType::push(L, child, false); + return 1; +} + +int ElementGetAttrnext_sibling(lua_State* L) +{ + Element* ele = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(ele); + Element* sibling = ele->GetNextSibling(); + if (sibling == nullptr) + lua_pushnil(L); + else + LuaType::push(L, sibling, false); + return 1; +} + +int ElementGetAttroffset_height(lua_State* L) +{ + Element* ele = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(ele); + lua_pushnumber(L, ele->GetOffsetHeight()); + return 1; +} + +int ElementGetAttroffset_left(lua_State* L) +{ + Element* ele = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(ele); + lua_pushnumber(L, ele->GetOffsetLeft()); + return 1; +} + +int ElementGetAttroffset_parent(lua_State* L) +{ + Element* ele = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(ele); + Element* parent = ele->GetOffsetParent(); + LuaType::push(L, parent, false); + return 1; +} + +int ElementGetAttroffset_top(lua_State* L) +{ + Element* ele = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(ele); + lua_pushnumber(L, ele->GetOffsetTop()); + return 1; +} + +int ElementGetAttroffset_width(lua_State* L) +{ + Element* ele = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(ele); + lua_pushnumber(L, ele->GetOffsetWidth()); + return 1; +} + +int ElementGetAttrowner_document(lua_State* L) +{ + Element* ele = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(ele); + Document* doc = ele->GetOwnerDocument(); + LuaType::push(L, doc, false); + return 1; +} + +int ElementGetAttrparent_node(lua_State* L) +{ + Element* ele = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(ele); + Element* parent = ele->GetParentNode(); + if (parent == nullptr) + lua_pushnil(L); + else + LuaType::push(L, parent, false); + return 1; +} + +int ElementGetAttrprevious_sibling(lua_State* L) +{ + Element* ele = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(ele); + Element* sibling = ele->GetPreviousSibling(); + if (sibling == nullptr) + lua_pushnil(L); + else + LuaType::push(L, sibling, false); + return 1; +} + +int ElementGetAttrscroll_height(lua_State* L) +{ + Element* ele = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(ele); + lua_pushnumber(L, ele->GetScrollHeight()); + return 1; +} + +int ElementGetAttrscroll_left(lua_State* L) +{ + Element* ele = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(ele); + lua_pushnumber(L, ele->GetScrollLeft()); + return 1; +} + +int ElementGetAttrscroll_top(lua_State* L) +{ + Element* ele = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(ele); + lua_pushnumber(L, ele->GetScrollTop()); + return 1; +} + +int ElementGetAttrscroll_width(lua_State* L) +{ + Element* ele = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(ele); + lua_pushnumber(L, ele->GetScrollWidth()); + return 1; +} + +int ElementGetAttrstyle(lua_State* L) +{ + Element* ele = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(ele); + ElementStyleProxy* prox = new ElementStyleProxy(); + prox->owner = ele; + LuaType::push(L, prox, true); + return 1; +} + +int ElementGetAttrtag_name(lua_State* L) +{ + Element* ele = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(ele); + lua_pushstring(L, ele->GetTagName().c_str()); + return 1; +} + +// setters +int ElementSetAttrclass_name(lua_State* L) +{ + Element* ele = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(ele); + const char* name = luaL_checkstring(L, 2); + ele->SetClassNames(name); + return 0; +} + +int ElementSetAttrid(lua_State* L) +{ + Element* ele = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(ele); + const char* id = luaL_checkstring(L, 2); + ele->SetId(id); + return 0; +} + +int ElementSetAttrinner_rml(lua_State* L) +{ + Element* ele = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(ele); + const char* rml = luaL_checkstring(L, 2); + ele->SetInnerRML(rml); + return 0; +} + +int ElementSetAttrscroll_left(lua_State* L) +{ + Element* ele = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(ele); + float scroll = (float)luaL_checknumber(L, 2); + ele->SetScrollLeft(scroll); + return 0; +} + +int ElementSetAttrscroll_top(lua_State* L) +{ + Element* ele = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(ele); + float scroll = (float)luaL_checknumber(L, 2); + ele->SetScrollTop(scroll); + return 0; +} + +RegType ElementMethods[] = { + RMLUI_LUAMETHOD(Element, AddEventListener), + RMLUI_LUAMETHOD(Element, AppendChild), + RMLUI_LUAMETHOD(Element, Blur), + RMLUI_LUAMETHOD(Element, Click), + RMLUI_LUAMETHOD(Element, DispatchEvent), + RMLUI_LUAMETHOD(Element, Focus), + RMLUI_LUAMETHOD(Element, GetAttribute), + RMLUI_LUAMETHOD(Element, GetElementById), + RMLUI_LUAMETHOD(Element, GetElementsByTagName), + RMLUI_LUAMETHOD(Element, QuerySelector), + RMLUI_LUAMETHOD(Element, QuerySelectorAll), + RMLUI_LUAMETHOD(Element, Matches), + RMLUI_LUAMETHOD(Element, HasAttribute), + RMLUI_LUAMETHOD(Element, HasChildNodes), + RMLUI_LUAMETHOD(Element, InsertBefore), + RMLUI_LUAMETHOD(Element, IsClassSet), + RMLUI_LUAMETHOD(Element, RemoveAttribute), + RMLUI_LUAMETHOD(Element, RemoveChild), + RMLUI_LUAMETHOD(Element, ReplaceChild), + RMLUI_LUAMETHOD(Element, ScrollIntoView), + RMLUI_LUAMETHOD(Element, SetAttribute), + RMLUI_LUAMETHOD(Element, SetClass), + {nullptr, nullptr}, +}; + +luaL_Reg ElementGetters[] = { + RMLUI_LUAGETTER(Element, attributes), + RMLUI_LUAGETTER(Element, child_nodes), + RMLUI_LUAGETTER(Element, class_name), + RMLUI_LUAGETTER(Element, client_left), + RMLUI_LUAGETTER(Element, client_height), + RMLUI_LUAGETTER(Element, client_top), + RMLUI_LUAGETTER(Element, client_width), + RMLUI_LUAGETTER(Element, first_child), + RMLUI_LUAGETTER(Element, id), + RMLUI_LUAGETTER(Element, inner_rml), + RMLUI_LUAGETTER(Element, last_child), + RMLUI_LUAGETTER(Element, next_sibling), + RMLUI_LUAGETTER(Element, offset_height), + RMLUI_LUAGETTER(Element, offset_left), + RMLUI_LUAGETTER(Element, offset_parent), + RMLUI_LUAGETTER(Element, offset_top), + RMLUI_LUAGETTER(Element, offset_width), + RMLUI_LUAGETTER(Element, owner_document), + RMLUI_LUAGETTER(Element, parent_node), + RMLUI_LUAGETTER(Element, previous_sibling), + RMLUI_LUAGETTER(Element, scroll_height), + RMLUI_LUAGETTER(Element, scroll_left), + RMLUI_LUAGETTER(Element, scroll_top), + RMLUI_LUAGETTER(Element, scroll_width), + RMLUI_LUAGETTER(Element, style), + RMLUI_LUAGETTER(Element, tag_name), + {nullptr, nullptr}, +}; + +luaL_Reg ElementSetters[] = { + RMLUI_LUASETTER(Element, class_name), + RMLUI_LUASETTER(Element, id), + RMLUI_LUASETTER(Element, inner_rml), + RMLUI_LUASETTER(Element, scroll_left), + RMLUI_LUASETTER(Element, scroll_top), + {nullptr, nullptr}, +}; + +RMLUI_LUATYPE_DEFINE(Element) + +template <> +void ExtraInit(lua_State* /*L*/, int /*metatable_index*/) +{ + return; +} + +RegType ElementPtrMethods[] = { + {nullptr, nullptr}, +}; + +luaL_Reg ElementPtrGetters[] = { + {nullptr, nullptr}, +}; + +luaL_Reg ElementPtrSetters[] = { + {nullptr, nullptr}, +}; + +RMLUI_LUATYPE_DEFINE(ElementPtr) + +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Element.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Element.h new file mode 100644 index 00000000..021f99c1 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Element.h @@ -0,0 +1,89 @@ +#pragma once + +#include +#include +#include + +namespace Rml { +namespace Lua { +template <> +RMLUILUA_API void ExtraInit(lua_State* L, int metatable_index); + +int Elementnew(lua_State* L); +// methods +int ElementAddEventListener(lua_State* L, Element* obj); +int ElementAppendChild(lua_State* L, Element* obj); +int ElementBlur(lua_State* L, Element* obj); +int ElementClick(lua_State* L, Element* obj); +int ElementDispatchEvent(lua_State* L, Element* obj); +int ElementFocus(lua_State* L, Element* obj); +int ElementGetAttribute(lua_State* L, Element* obj); +int ElementGetElementById(lua_State* L, Element* obj); +int ElementGetElementsByTagName(lua_State* L, Element* obj); +int ElementQuerySelector(lua_State* L, Element* obj); +int ElementQuerySelectorAll(lua_State* L, Element* obj); +int ElementMatches(lua_State* L, Element* obj); +int ElementHasAttribute(lua_State* L, Element* obj); +int ElementHasChildNodes(lua_State* L, Element* obj); +int ElementInsertBefore(lua_State* L, Element* obj); +int ElementIsClassSet(lua_State* L, Element* obj); +int ElementRemoveAttribute(lua_State* L, Element* obj); +int ElementRemoveChild(lua_State* L, Element* obj); +int ElementReplaceChild(lua_State* L, Element* obj); +int ElementScrollIntoView(lua_State* L, Element* obj); +int ElementSetAttribute(lua_State* L, Element* obj); +int ElementSetClass(lua_State* L, Element* obj); + +// getters +int ElementGetAttrattributes(lua_State* L); +int ElementGetAttrchild_nodes(lua_State* L); +int ElementGetAttrclass_name(lua_State* L); +int ElementGetAttrclient_left(lua_State* L); +int ElementGetAttrclient_height(lua_State* L); +int ElementGetAttrclient_top(lua_State* L); +int ElementGetAttrclient_width(lua_State* L); +int ElementGetAttrfirst_child(lua_State* L); +int ElementGetAttrid(lua_State* L); +int ElementGetAttrinner_rml(lua_State* L); +int ElementGetAttrlast_child(lua_State* L); +int ElementGetAttrnext_sibling(lua_State* L); +int ElementGetAttroffset_height(lua_State* L); +int ElementGetAttroffset_left(lua_State* L); +int ElementGetAttroffset_parent(lua_State* L); +int ElementGetAttroffset_top(lua_State* L); +int ElementGetAttroffset_width(lua_State* L); +int ElementGetAttrowner_document(lua_State* L); +int ElementGetAttrparent_node(lua_State* L); +int ElementGetAttrprevious_sibling(lua_State* L); +int ElementGetAttrscroll_height(lua_State* L); +int ElementGetAttrscroll_left(lua_State* L); +int ElementGetAttrscroll_top(lua_State* L); +int ElementGetAttrscroll_width(lua_State* L); +int ElementGetAttrstyle(lua_State* L); +int ElementGetAttrtag_name(lua_State* L); + +// setters +int ElementSetAttrclass_name(lua_State* L); +int ElementSetAttrid(lua_State* L); +int ElementSetAttrinner_rml(lua_State* L); +int ElementSetAttrscroll_left(lua_State* L); +int ElementSetAttrscroll_top(lua_State* L); + +extern RegType ElementMethods[]; +extern luaL_Reg ElementGetters[]; +extern luaL_Reg ElementSetters[]; + +RMLUI_LUATYPE_DECLARE(Element) + +template <> +void ExtraInit(lua_State* L, int metatable_index); +int LogMessage(lua_State* L); + +extern RegType ElementPtrMethods[]; +extern luaL_Reg ElementPtrGetters[]; +extern luaL_Reg ElementPtrSetters[]; + +RMLUI_LUATYPE_DECLARE(ElementPtr) + +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/ElementAttributesProxy.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/ElementAttributesProxy.cpp new file mode 100644 index 00000000..03a814a9 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/ElementAttributesProxy.cpp @@ -0,0 +1,54 @@ +#include "ElementAttributesProxy.h" +#include "Pairs.h" +#include +#include + +namespace Rml { +namespace Lua { +template <> +void ExtraInit(lua_State* L, int metatable_index) +{ + lua_pushcfunction(L, ElementAttributesProxy__index); + lua_setfield(L, metatable_index, "__index"); + lua_pushcfunction(L, ElementAttributesProxy__pairs); + lua_setfield(L, metatable_index, "__pairs"); +} + +int ElementAttributesProxy__index(lua_State* L) +{ + /*the table obj and the missing key are currently on the stack(index 1 & 2) as defined by the Lua language*/ + int keytype = lua_type(L, 2); + if (keytype == LUA_TSTRING) // only valid key types + { + ElementAttributesProxy* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + const char* key = lua_tostring(L, 2); + Variant* attr = obj->owner->GetAttribute(key); + PushVariant(L, attr); // Utilities.h + return 1; + } + else + return LuaType::index(L); +} + +int ElementAttributesProxy__pairs(lua_State* L) +{ + ElementAttributesProxy* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + return MakePairs(L, obj->owner->GetAttributes()); +} + +RegType ElementAttributesProxyMethods[] = { + {nullptr, nullptr}, +}; + +luaL_Reg ElementAttributesProxyGetters[] = { + {nullptr, nullptr}, +}; +luaL_Reg ElementAttributesProxySetters[] = { + {nullptr, nullptr}, +}; + +RMLUI_LUATYPE_DEFINE(ElementAttributesProxy) +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/ElementAttributesProxy.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/ElementAttributesProxy.h new file mode 100644 index 00000000..a9bc4da9 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/ElementAttributesProxy.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include +#include + +namespace Rml { +namespace Lua { +// where owner is the Element that we should look up information from +struct ElementAttributesProxy { + Element* owner; +}; + +template <> +void ExtraInit(lua_State* L, int metatable_index); +int ElementAttributesProxy__index(lua_State* L); +int ElementAttributesProxy__pairs(lua_State* L); + +extern RegType ElementAttributesProxyMethods[]; +extern luaL_Reg ElementAttributesProxyGetters[]; +extern luaL_Reg ElementAttributesProxySetters[]; + +RMLUI_LUATYPE_DECLARE(ElementAttributesProxy) +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/ElementChildNodesProxy.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/ElementChildNodesProxy.cpp new file mode 100644 index 00000000..f74e51c9 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/ElementChildNodesProxy.cpp @@ -0,0 +1,62 @@ +#include "ElementChildNodesProxy.h" +#include "Element.h" +#include "Pairs.h" + +namespace Rml { +namespace Lua { + +template <> +void ExtraInit(lua_State* L, int metatable_index) +{ + lua_pushcfunction(L, ElementChildNodesProxy__index); + lua_setfield(L, metatable_index, "__index"); + lua_pushcfunction(L, ElementChildNodesProxy__pairs); + lua_setfield(L, metatable_index, "__pairs"); + lua_pushcfunction(L, ElementChildNodesProxy__len); + lua_setfield(L, metatable_index, "__len"); +} + +int ElementChildNodesProxy__index(lua_State* L) +{ + /*the table obj and the missing key are currently on the stack(index 1 & 2) as defined by the Lua language*/ + int keytype = lua_type(L, 2); + if (keytype == LUA_TNUMBER) // only valid key types + { + ElementChildNodesProxy* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + int key = (int)luaL_checkinteger(L, 2); + Element* child = obj->owner->GetChild(key - 1); + LuaType::push(L, child, false); + return 1; + } + else + return LuaType::index(L); +} + +int ElementChildNodesProxy__pairs(lua_State* L) +{ + return MakeIntPairs(L); +} + +int ElementChildNodesProxy__len(lua_State* L) +{ + ElementChildNodesProxy* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + lua_pushinteger(L, obj->owner->GetNumChildren()); + ; + return 1; +} + +RegType ElementChildNodesProxyMethods[] = { + {nullptr, nullptr}, +}; +luaL_Reg ElementChildNodesProxyGetters[] = { + {nullptr, nullptr}, +}; +luaL_Reg ElementChildNodesProxySetters[] = { + {nullptr, nullptr}, +}; + +RMLUI_LUATYPE_DEFINE(ElementChildNodesProxy) +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/ElementChildNodesProxy.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/ElementChildNodesProxy.h new file mode 100644 index 00000000..5d2cd523 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/ElementChildNodesProxy.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include +#include + +namespace Rml { +namespace Lua { +// where owner is the Element that we should look up information from +struct ElementChildNodesProxy { + Element* owner; +}; + +template <> +void ExtraInit(lua_State* L, int metatable_index); +int ElementChildNodesProxy__index(lua_State* L); +int ElementChildNodesProxy__pairs(lua_State* L); +int ElementChildNodesProxy__len(lua_State* L); + +extern RegType ElementChildNodesProxyMethods[]; +extern luaL_Reg ElementChildNodesProxyGetters[]; +extern luaL_Reg ElementChildNodesProxySetters[]; + +RMLUI_LUATYPE_DECLARE(ElementChildNodesProxy) +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/ElementInstancer.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/ElementInstancer.cpp new file mode 100644 index 00000000..18d566c2 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/ElementInstancer.cpp @@ -0,0 +1,53 @@ +#include "ElementInstancer.h" + +namespace Rml { +namespace Lua { +template <> +void ExtraInit(lua_State* L, int metatable_index) +{ + lua_pushcfunction(L, ElementInstancernew); + lua_setfield(L, metatable_index - 1, "new"); +} + +// method +int ElementInstancernew(lua_State* L) +{ + LuaElementInstancer* lei = new LuaElementInstancer(L); + LuaType::push(L, lei, true); + return 1; +} + +// setter +int ElementInstancerSetAttrInstanceElement(lua_State* L) +{ + LuaElementInstancer* lei = (LuaElementInstancer*)LuaType::check(L, 1); + RMLUI_CHECK_OBJ(lei); + + if (lua_type(L, 2) != LUA_TFUNCTION) + { + Log::Message(Log::LT_ERROR, "The argument to ElementInstancer.InstanceElement must be a function. You passed in a %s.", luaL_typename(L, 2)); + return 0; + } + lei->PushFunctionsTable(L); // top of the stack is now ELEMENTINSTANCERFUNCTIONS table + lua_pushvalue(L, 2); // copy of the function + lei->ref_InstanceElement = luaL_ref(L, -2); + lua_pop(L, 1); // pop the ELEMENTINSTANCERFUNCTIONS table + return 0; +} + +RegType ElementInstancerMethods[] = { + {nullptr, nullptr}, +}; + +luaL_Reg ElementInstancerGetters[] = { + {nullptr, nullptr}, +}; + +luaL_Reg ElementInstancerSetters[] = { + RMLUI_LUASETTER(ElementInstancer, InstanceElement), + {nullptr, nullptr}, +}; + +RMLUI_LUATYPE_DEFINE(ElementInstancer) +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/ElementInstancer.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/ElementInstancer.h new file mode 100644 index 00000000..b2aa9733 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/ElementInstancer.h @@ -0,0 +1,22 @@ +#pragma once + +#include "LuaElementInstancer.h" +#include +#include + +namespace Rml { +namespace Lua { +template <> +void ExtraInit(lua_State* L, int metatable_index); +// method +int ElementInstancernew(lua_State* L); +// setter +int ElementInstancerSetAttrInstanceElement(lua_State* L); + +extern RegType ElementInstancerMethods[]; +extern luaL_Reg ElementInstancerGetters[]; +extern luaL_Reg ElementInstancerSetters[]; + +RMLUI_LUATYPE_DECLARE(ElementInstancer) +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/ElementStyleProxy.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/ElementStyleProxy.cpp new file mode 100644 index 00000000..0141d16c --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/ElementStyleProxy.cpp @@ -0,0 +1,137 @@ +#include "ElementStyleProxy.h" +#include +#include +#include +#include +#include +#include + +namespace Rml { +namespace Lua { +template <> +void ExtraInit(lua_State* L, int metatable_index) +{ + lua_pushcfunction(L, ElementStyleProxy__index); + lua_setfield(L, metatable_index, "__index"); + + lua_pushcfunction(L, ElementStyleProxy__newindex); + lua_setfield(L, metatable_index, "__newindex"); + + lua_pushcfunction(L, ElementStyleProxy__pairs); + lua_setfield(L, metatable_index, "__pairs"); +} + +int ElementStyleProxy__index(lua_State* L) +{ + /*the table obj and the missing key are currently on the stack(index 1 & 2) as defined by the Lua language*/ + int keytype = lua_type(L, 2); + if (keytype == LUA_TSTRING) // if we are trying to access a string, then we will assume that it is a property + { + ElementStyleProxy* es = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(es); + const Property* prop = es->owner->GetProperty(lua_tostring(L, 2)); + RMLUI_CHECK_OBJ(prop) + lua_pushstring(L, prop->ToString().c_str()); + return 1; + } + else // if it wasn't trying to get a string + { + lua_settop(L, 2); + return LuaType::index(L); + } +} + +int ElementStyleProxy__newindex(lua_State* L) +{ + //[1] = obj, [2] = key, [3] = value + ElementStyleProxy* es = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(es); + int keytype = lua_type(L, 2); + int valuetype = lua_type(L, 3); + if (keytype == LUA_TSTRING) + { + const char* key = lua_tostring(L, 2); + if (valuetype == LUA_TSTRING) + { + const char* value = lua_tostring(L, 3); + lua_pushboolean(L, es->owner->SetProperty(key, value)); + return 1; + } + else if (valuetype == LUA_TNIL) + { + es->owner->RemoveProperty(key); + return 0; + } + } + // everything else returns when it needs to, so we are safe to pass it + // on if needed + + lua_settop(L, 3); + return LuaType::newindex(L); +} + +struct ElementStyleProxyPairs { + static int next(lua_State* L) + { + ElementStyleProxyPairs* self = static_cast(lua_touserdata(L, lua_upvalueindex(1))); + if (self->m_view.AtEnd()) + { + return 0; + } + const String& key = self->m_view.GetName(); + const Property& property = self->m_view.GetProperty(); + String val; + property.definition->GetValue(val, property); + lua_pushlstring(L, key.c_str(), key.size()); + lua_pushlstring(L, val.c_str(), val.size()); + ++self->m_view; + return 2; + } + static int destroy(lua_State* L) + { + static_cast(lua_touserdata(L, 1))->~ElementStyleProxyPairs(); + return 0; + } + static int constructor(lua_State* L, ElementStyleProxy* obj) + { + void* storage = lua_newuserdata(L, sizeof(ElementStyleProxyPairs)); + if (luaL_newmetatable(L, "RmlUi::Lua::ElementStyleProxyPairs")) + { + static luaL_Reg mt[] = { + {"__gc", destroy}, + {NULL, NULL}, + }; + luaL_setfuncs(L, mt, 0); + } + lua_setmetatable(L, -2); + lua_pushcclosure(L, next, 1); + new (storage) ElementStyleProxyPairs(obj); + return 1; + } + ElementStyleProxyPairs(ElementStyleProxy* obj) : m_view(obj->owner->IterateLocalProperties()) {} + PropertiesIteratorView m_view; +}; + +int ElementStyleProxy__pairs(lua_State* L) +{ + ElementStyleProxy* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + ElementStyleProxyPairs::constructor(L, obj); + return 1; +} + +RegType ElementStyleProxyMethods[] = { + {nullptr, nullptr}, +}; + +luaL_Reg ElementStyleProxyGetters[] = { + {nullptr, nullptr}, +}; + +luaL_Reg ElementStyleProxySetters[] = { + {nullptr, nullptr}, +}; + +RMLUI_LUATYPE_DEFINE(ElementStyleProxy) +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/ElementStyleProxy.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/ElementStyleProxy.h new file mode 100644 index 00000000..e5305c66 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/ElementStyleProxy.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include + +namespace Rml { +class Element; + +namespace Lua { +struct ElementStyleProxy { + Element* owner; +}; + +template <> +void ExtraInit(lua_State* L, int metatable_index); +int ElementStyleProxy__index(lua_State* L); +int ElementStyleProxy__newindex(lua_State* L); +int ElementStyleProxy__pairs(lua_State* L); + +extern RegType ElementStyleProxyMethods[]; +extern luaL_Reg ElementStyleProxyGetters[]; +extern luaL_Reg ElementStyleProxySetters[]; + +RMLUI_LUATYPE_DECLARE(ElementStyleProxy) +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/ElementText.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/ElementText.cpp new file mode 100644 index 00000000..66483292 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/ElementText.cpp @@ -0,0 +1,49 @@ +#include "ElementText.h" +#include "Element.h" +#include + +namespace Rml { +namespace Lua { +template <> +void ExtraInit(lua_State* L, int metatable_index) +{ + // inherit from Element + ExtraInit(L, metatable_index); + LuaType::_regfunctions(L, metatable_index, metatable_index - 1); + AddTypeToElementAsTable(L); +} + +int ElementTextGetAttrtext(lua_State* L) +{ + ElementText* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + lua_pushstring(L, obj->GetText().c_str()); + return 1; +} + +int ElementTextSetAttrtext(lua_State* L) +{ + ElementText* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + const char* text = luaL_checkstring(L, 2); + obj->SetText(text); + return 0; +} + +RegType ElementTextMethods[] = { + {nullptr, nullptr}, +}; + +luaL_Reg ElementTextGetters[] = { + RMLUI_LUAGETTER(ElementText, text), + {nullptr, nullptr}, +}; + +luaL_Reg ElementTextSetters[] = { + RMLUI_LUASETTER(ElementText, text), + {nullptr, nullptr}, +}; + +RMLUI_LUATYPE_DEFINE(ElementText) +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/ElementText.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/ElementText.h new file mode 100644 index 00000000..ebe24f86 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/ElementText.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include +#include + +namespace Rml { +namespace Lua { +// will inherit from Element +template <> +void ExtraInit(lua_State* L, int metatable_index); + +int ElementTextGetAttrtext(lua_State* L); +int ElementTextSetAttrtext(lua_State* L); + +extern RegType ElementTextMethods[]; +extern luaL_Reg ElementTextGetters[]; +extern luaL_Reg ElementTextSetters[]; + +RMLUI_LUATYPE_DECLARE(ElementText) +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/As.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/As.h new file mode 100644 index 00000000..8a091706 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/As.h @@ -0,0 +1,40 @@ +#pragma once +/* + These are helper functions to fill up the Element.As table with types that are able to be casted +*/ + +#include +#include +#include + +namespace Rml { +namespace Lua { + +// Helper function for the controls, so that the types don't have to define individual functions themselves +// to fill the Elements.As table +template +int CastFromElementTo(lua_State* L) +{ + Element* ele = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(ele); + LuaType::push(L, (ToType*)ele, false); + return 1; +} + +// Adds to the Element.As table the name of the type, and the function to use to cast +template +void AddCastFunctionToElementAsTable(lua_State* L) +{ + int top = lua_gettop(L); + lua_getglobal(L, "Element"); + lua_getfield(L, -1, "As"); + if (!lua_isnoneornil(L, -1)) + { + lua_pushcfunction(L, CastFromElementTo); + lua_setfield(L, -2, GetTClassName()); + } + lua_settop(L, top); // pop "As" and "Element" +} + +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/CMakeLists.txt b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/CMakeLists.txt new file mode 100644 index 00000000..88972be4 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/CMakeLists.txt @@ -0,0 +1,20 @@ +# RMLUI_CMAKE_MINIMUM_VERSION_RAISE_NOTICE: +# Using absolute paths to prevent improper interpretation of relative paths. Relative paths can be used once the minimum +# CMake version is greater or equal than CMake 3.13 +target_sources(rmlui_lua PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/As.h" + "${CMAKE_CURRENT_SOURCE_DIR}/ElementForm.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/ElementForm.h" + "${CMAKE_CURRENT_SOURCE_DIR}/ElementFormControl.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/ElementFormControl.h" + "${CMAKE_CURRENT_SOURCE_DIR}/ElementFormControlInput.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/ElementFormControlInput.h" + "${CMAKE_CURRENT_SOURCE_DIR}/ElementFormControlSelect.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/ElementFormControlSelect.h" + "${CMAKE_CURRENT_SOURCE_DIR}/ElementFormControlTextArea.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/ElementFormControlTextArea.h" + "${CMAKE_CURRENT_SOURCE_DIR}/ElementTabSet.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/ElementTabSet.h" + "${CMAKE_CURRENT_SOURCE_DIR}/SelectOptionsProxy.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/SelectOptionsProxy.h" +) diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/ElementForm.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/ElementForm.cpp new file mode 100644 index 00000000..26784335 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/ElementForm.cpp @@ -0,0 +1,48 @@ +#include "ElementForm.h" +#include +#include +#include + +namespace Rml { +namespace Lua { + +// method +int ElementFormSubmit(lua_State* L, ElementForm* obj) +{ + int top = lua_gettop(L); + const char* name = ""; + const char* value = ""; + if (top > 0) + { + name = luaL_checkstring(L, 1); + if (top > 1) + value = luaL_checkstring(L, 2); + } + obj->Submit(name, value); + return 0; +} + +RegType ElementFormMethods[] = { + RMLUI_LUAMETHOD(ElementForm, Submit), + {nullptr, nullptr}, +}; + +luaL_Reg ElementFormGetters[] = { + {nullptr, nullptr}, +}; + +luaL_Reg ElementFormSetters[] = { + {nullptr, nullptr}, +}; + +template <> +void ExtraInit(lua_State* L, int metatable_index) +{ + // inherit from Element + ExtraInit(L, metatable_index); + LuaType::_regfunctions(L, metatable_index, metatable_index - 1); + AddTypeToElementAsTable(L); +} +RMLUI_LUATYPE_DEFINE(ElementForm) +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/ElementForm.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/ElementForm.h new file mode 100644 index 00000000..f298d97c --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/ElementForm.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include +#include + +namespace Rml { +namespace Lua { + +// method +int ElementFormSubmit(lua_State* L, ElementForm* obj); + +extern RegType ElementFormMethods[]; +extern luaL_Reg ElementFormGetters[]; +extern luaL_Reg ElementFormSetters[]; + +// this will be used to "inherit" from Element +template <> +void ExtraInit(lua_State* L, int metatable_index); +RMLUI_LUATYPE_DECLARE(ElementForm) +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/ElementFormControl.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/ElementFormControl.cpp new file mode 100644 index 00000000..c7b408a0 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/ElementFormControl.cpp @@ -0,0 +1,89 @@ +#include "ElementFormControl.h" +#include "../Element.h" +#include +#include +#include + +namespace Rml { +namespace Lua { + +// getters +int ElementFormControlGetAttrdisabled(lua_State* L) +{ + ElementFormControl* efc = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(efc); + lua_pushboolean(L, efc->IsDisabled()); + return 1; +} + +int ElementFormControlGetAttrname(lua_State* L) +{ + ElementFormControl* efc = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(efc); + lua_pushstring(L, efc->GetName().c_str()); + return 1; +} + +int ElementFormControlGetAttrvalue(lua_State* L) +{ + ElementFormControl* efc = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(efc); + lua_pushstring(L, efc->GetValue().c_str()); + return 1; +} + +// setters +int ElementFormControlSetAttrdisabled(lua_State* L) +{ + ElementFormControl* efc = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(efc); + efc->SetDisabled(RMLUI_CHECK_BOOL(L, 2)); + return 0; +} + +int ElementFormControlSetAttrname(lua_State* L) +{ + ElementFormControl* efc = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(efc); + const char* name = luaL_checkstring(L, 2); + efc->SetName(name); + return 0; +} + +int ElementFormControlSetAttrvalue(lua_State* L) +{ + ElementFormControl* efc = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(efc); + const char* value = luaL_checkstring(L, 2); + efc->SetValue(value); + return 0; +} + +RegType ElementFormControlMethods[] = { + {nullptr, nullptr}, +}; + +luaL_Reg ElementFormControlGetters[] = { + RMLUI_LUAGETTER(ElementFormControl, disabled), + RMLUI_LUAGETTER(ElementFormControl, name), + RMLUI_LUAGETTER(ElementFormControl, value), + {nullptr, nullptr}, +}; + +luaL_Reg ElementFormControlSetters[] = { + RMLUI_LUASETTER(ElementFormControl, disabled), + RMLUI_LUASETTER(ElementFormControl, name), + RMLUI_LUASETTER(ElementFormControl, value), + {nullptr, nullptr}, +}; + +template <> +void ExtraInit(lua_State* L, int metatable_index) +{ + ExtraInit(L, metatable_index); + LuaType::_regfunctions(L, metatable_index, metatable_index - 1); + AddTypeToElementAsTable(L); +} +RMLUI_LUATYPE_DEFINE(ElementFormControl) +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/ElementFormControl.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/ElementFormControl.h new file mode 100644 index 00000000..e0776d67 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/ElementFormControl.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include + +namespace Rml { +namespace Lua { + +// getters +int ElementFormControlGetAttrdisabled(lua_State* L); +int ElementFormControlGetAttrname(lua_State* L); +int ElementFormControlGetAttrvalue(lua_State* L); + +// setters +int ElementFormControlSetAttrdisabled(lua_State* L); +int ElementFormControlSetAttrname(lua_State* L); +int ElementFormControlSetAttrvalue(lua_State* L); + +extern RegType ElementFormControlMethods[]; +extern luaL_Reg ElementFormControlGetters[]; +extern luaL_Reg ElementFormControlSetters[]; + +template <> +void ExtraInit(lua_State* L, int metatable_index); +RMLUI_LUATYPE_DECLARE(ElementFormControl) +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/ElementFormControlInput.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/ElementFormControlInput.cpp new file mode 100644 index 00000000..a54f95a3 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/ElementFormControlInput.cpp @@ -0,0 +1,178 @@ +#include "ElementFormControlInput.h" +#include "ElementFormControl.h" +#include +#include + +namespace Rml { +namespace Lua { + +// methods +int ElementFormControlInputSelect(lua_State* /*L*/, ElementFormControlInput* obj) +{ + obj->Select(); + return 0; +} + +int ElementFormControlInputSetSelection(lua_State* L, ElementFormControlInput* obj) +{ + int start = (int)GetIndex(L, 1); + int end = (int)GetIndex(L, 2); + obj->SetSelectionRange(start, end); + return 0; +} + +int ElementFormControlInputGetSelection(lua_State* L, ElementFormControlInput* obj) +{ + int selection_start = 0, selection_end = 0; + String selected_text; + obj->GetSelection(&selection_start, &selection_end, &selected_text); + PushIndex(L, selection_start); + PushIndex(L, selection_end); + lua_pushstring(L, selected_text.c_str()); + return 3; +} + +// getters +int ElementFormControlInputGetAttrchecked(lua_State* L) +{ + ElementFormControlInput* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + lua_pushboolean(L, obj->HasAttribute("checked")); + return 1; +} + +int ElementFormControlInputGetAttrmaxlength(lua_State* L) +{ + ElementFormControlInput* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + lua_pushinteger(L, obj->GetAttribute("maxlength", -1)); + return 1; +} + +int ElementFormControlInputGetAttrsize(lua_State* L) +{ + ElementFormControlInput* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + lua_pushinteger(L, obj->GetAttribute("size", 20)); + return 1; +} + +int ElementFormControlInputGetAttrmax(lua_State* L) +{ + ElementFormControlInput* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + lua_pushinteger(L, obj->GetAttribute("max", 100)); + return 1; +} + +int ElementFormControlInputGetAttrmin(lua_State* L) +{ + ElementFormControlInput* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + lua_pushinteger(L, obj->GetAttribute("min", 0)); + return 1; +} + +int ElementFormControlInputGetAttrstep(lua_State* L) +{ + ElementFormControlInput* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + lua_pushinteger(L, obj->GetAttribute("step", 1)); + return 1; +} + +// setters +int ElementFormControlInputSetAttrchecked(lua_State* L) +{ + ElementFormControlInput* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + bool checked = RMLUI_CHECK_BOOL(L, 2); + if (checked) + obj->SetAttribute("checked", true); + else + obj->RemoveAttribute("checked"); + return 0; +} + +int ElementFormControlInputSetAttrmaxlength(lua_State* L) +{ + ElementFormControlInput* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + int maxlength = (int)luaL_checkinteger(L, 2); + obj->SetAttribute("maxlength", maxlength); + return 0; +} + +int ElementFormControlInputSetAttrsize(lua_State* L) +{ + ElementFormControlInput* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + int size = (int)luaL_checkinteger(L, 2); + obj->SetAttribute("size", size); + return 0; +} + +int ElementFormControlInputSetAttrmax(lua_State* L) +{ + ElementFormControlInput* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + int max = (int)luaL_checkinteger(L, 2); + obj->SetAttribute("max", max); + return 0; +} + +int ElementFormControlInputSetAttrmin(lua_State* L) +{ + ElementFormControlInput* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + int min = (int)luaL_checkinteger(L, 2); + obj->SetAttribute("min", min); + return 0; +} + +int ElementFormControlInputSetAttrstep(lua_State* L) +{ + ElementFormControlInput* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + int step = (int)luaL_checkinteger(L, 2); + obj->SetAttribute("step", step); + return 0; +} + +RegType ElementFormControlInputMethods[] = { + RMLUI_LUAMETHOD(ElementFormControlInput, Select), + RMLUI_LUAMETHOD(ElementFormControlInput, SetSelection), + RMLUI_LUAMETHOD(ElementFormControlInput, GetSelection), + {nullptr, nullptr}, +}; + +luaL_Reg ElementFormControlInputGetters[] = { + RMLUI_LUAGETTER(ElementFormControlInput, checked), + RMLUI_LUAGETTER(ElementFormControlInput, maxlength), + RMLUI_LUAGETTER(ElementFormControlInput, size), + RMLUI_LUAGETTER(ElementFormControlInput, max), + RMLUI_LUAGETTER(ElementFormControlInput, min), + RMLUI_LUAGETTER(ElementFormControlInput, step), + {nullptr, nullptr}, +}; + +luaL_Reg ElementFormControlInputSetters[] = { + RMLUI_LUASETTER(ElementFormControlInput, checked), + RMLUI_LUASETTER(ElementFormControlInput, maxlength), + RMLUI_LUASETTER(ElementFormControlInput, size), + RMLUI_LUASETTER(ElementFormControlInput, max), + RMLUI_LUASETTER(ElementFormControlInput, min), + RMLUI_LUASETTER(ElementFormControlInput, step), + {nullptr, nullptr}, +}; + +template <> +void ExtraInit(lua_State* L, int metatable_index) +{ + ExtraInit(L, metatable_index); + LuaType::_regfunctions(L, metatable_index, metatable_index - 1); + AddTypeToElementAsTable(L); +} +RMLUI_LUATYPE_DEFINE(ElementFormControlInput) +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/ElementFormControlInput.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/ElementFormControlInput.h new file mode 100644 index 00000000..ae2a7dd5 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/ElementFormControlInput.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include + +namespace Rml { +namespace Lua { + +// methods +int ElementFormControlInputSelect(lua_State* L, ElementFormControlInput* obj); +int ElementFormControlInputSetSelection(lua_State* L, ElementFormControlInput* obj); +int ElementFormControlInputGetSelection(lua_State* L, ElementFormControlInput* obj); + +// getters +int ElementFormControlInputGetAttrchecked(lua_State* L); +int ElementFormControlInputGetAttrmaxlength(lua_State* L); +int ElementFormControlInputGetAttrsize(lua_State* L); +int ElementFormControlInputGetAttrmax(lua_State* L); +int ElementFormControlInputGetAttrmin(lua_State* L); +int ElementFormControlInputGetAttrstep(lua_State* L); + +// setters +int ElementFormControlInputSetAttrchecked(lua_State* L); +int ElementFormControlInputSetAttrmaxlength(lua_State* L); +int ElementFormControlInputSetAttrsize(lua_State* L); +int ElementFormControlInputSetAttrmax(lua_State* L); +int ElementFormControlInputSetAttrmin(lua_State* L); +int ElementFormControlInputSetAttrstep(lua_State* L); + +extern RegType ElementFormControlInputMethods[]; +extern luaL_Reg ElementFormControlInputGetters[]; +extern luaL_Reg ElementFormControlInputSetters[]; + +// inherits from ElementFormControl which inherits from Element +template <> +void ExtraInit(lua_State* L, int metatable_index); +RMLUI_LUATYPE_DECLARE(ElementFormControlInput) +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/ElementFormControlSelect.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/ElementFormControlSelect.cpp new file mode 100644 index 00000000..bad2dbc4 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/ElementFormControlSelect.cpp @@ -0,0 +1,99 @@ +#include "ElementFormControlSelect.h" +#include "ElementFormControl.h" +#include "SelectOptionsProxy.h" +#include +#include +#include +#include + +namespace Rml { +namespace Lua { + +// methods +int ElementFormControlSelectAdd(lua_State* L, ElementFormControlSelect* obj) +{ + const char* rml = luaL_checkstring(L, 1); + const char* value = luaL_checkstring(L, 2); + int before = -1; // default + if (lua_gettop(L) >= 3) + before = GetIndex(L, 3); + + int index = obj->Add(rml, value, before); + lua_pushinteger(L, index); + return 1; +} + +int ElementFormControlSelectRemove(lua_State* L, ElementFormControlSelect* obj) +{ + int index = GetIndex(L, 1); + obj->Remove(index); + return 0; +} + +int ElementFormControlSelectRemoveAll(lua_State* /*L*/, ElementFormControlSelect* obj) +{ + obj->RemoveAll(); + return 0; +} + +// getters +int ElementFormControlSelectGetAttroptions(lua_State* L) +{ + ElementFormControlSelect* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + SelectOptionsProxy* proxy = new SelectOptionsProxy(); + proxy->owner = obj; + LuaType::push(L, proxy, true); + return 1; +} + +int ElementFormControlSelectGetAttrselection(lua_State* L) +{ + ElementFormControlSelect* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + int selection = obj->GetSelection(); + PushIndex(L, selection); + return 1; +} + +// setter +int ElementFormControlSelectSetAttrselection(lua_State* L) +{ + ElementFormControlSelect* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + int selection = GetIndex(L, 2); + obj->SetSelection(selection); + return 0; +} + +RegType ElementFormControlSelectMethods[] = { + RMLUI_LUAMETHOD(ElementFormControlSelect, Add), + RMLUI_LUAMETHOD(ElementFormControlSelect, Remove), + RMLUI_LUAMETHOD(ElementFormControlSelect, RemoveAll), + {nullptr, nullptr}, +}; + +luaL_Reg ElementFormControlSelectGetters[] = { + RMLUI_LUAGETTER(ElementFormControlSelect, options), + RMLUI_LUAGETTER(ElementFormControlSelect, selection), + {nullptr, nullptr}, +}; + +luaL_Reg ElementFormControlSelectSetters[] = { + RMLUI_LUASETTER(ElementFormControlSelect, selection), + {nullptr, nullptr}, +}; + +// inherits from ElementFormControl which inherits from Element +template <> +void ExtraInit(lua_State* L, int metatable_index) +{ + // init whatever elementformcontrol did extra, like inheritance + ExtraInit(L, metatable_index); + // then inherit from elementformcontrol + LuaType::_regfunctions(L, metatable_index, metatable_index - 1); + AddTypeToElementAsTable(L); +} +RMLUI_LUATYPE_DEFINE(ElementFormControlSelect) +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/ElementFormControlSelect.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/ElementFormControlSelect.h new file mode 100644 index 00000000..1869d53c --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/ElementFormControlSelect.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include + +namespace Rml { +namespace Lua { + +// methods +int ElementFormControlSelectAdd(lua_State* L, ElementFormControlSelect* obj); +int ElementFormControlSelectRemove(lua_State* L, ElementFormControlSelect* obj); + +// getters +int ElementFormControlSelectGetAttroptions(lua_State* L); +int ElementFormControlSelectGetAttrselection(lua_State* L); + +// setter +int ElementFormControlSelectSetAttrselection(lua_State* L); + +extern RegType ElementFormControlSelectMethods[]; +extern luaL_Reg ElementFormControlSelectGetters[]; +extern luaL_Reg ElementFormControlSelectSetters[]; + +// inherits from ElementFormControl which inherits from Element +template <> +void ExtraInit(lua_State* L, int metatable_index); +RMLUI_LUATYPE_DECLARE(ElementFormControlSelect) +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/ElementFormControlTextArea.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/ElementFormControlTextArea.cpp new file mode 100644 index 00000000..58437bfa --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/ElementFormControlTextArea.cpp @@ -0,0 +1,138 @@ +#include "ElementFormControlTextArea.h" +#include "ElementFormControl.h" +#include +#include + +namespace Rml { +namespace Lua { + +// methods +int ElementFormControlTextAreaSelect(lua_State* /*L*/, ElementFormControlTextArea* obj) +{ + obj->Select(); + return 0; +} + +int ElementFormControlTextAreaSetSelection(lua_State* L, ElementFormControlTextArea* obj) +{ + int start = (int)GetIndex(L, 1); + int end = (int)GetIndex(L, 2); + obj->SetSelectionRange(start, end); + return 0; +} + +int ElementFormControlTextAreaGetSelection(lua_State* L, ElementFormControlTextArea* obj) +{ + int selection_start = 0, selection_end = 0; + String selected_text; + obj->GetSelection(&selection_start, &selection_end, &selected_text); + PushIndex(L, selection_start); + PushIndex(L, selection_end); + lua_pushstring(L, selected_text.c_str()); + return 3; +} + +// getters +int ElementFormControlTextAreaGetAttrcols(lua_State* L) +{ + ElementFormControlTextArea* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + lua_pushinteger(L, obj->GetNumColumns()); + return 1; +} + +int ElementFormControlTextAreaGetAttrmaxlength(lua_State* L) +{ + ElementFormControlTextArea* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + lua_pushinteger(L, obj->GetMaxLength()); + return 1; +} + +int ElementFormControlTextAreaGetAttrrows(lua_State* L) +{ + ElementFormControlTextArea* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + lua_pushinteger(L, obj->GetNumRows()); + return 1; +} + +int ElementFormControlTextAreaGetAttrwordwrap(lua_State* L) +{ + ElementFormControlTextArea* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + lua_pushboolean(L, obj->GetWordWrap()); + return 1; +} + +// setters +int ElementFormControlTextAreaSetAttrcols(lua_State* L) +{ + ElementFormControlTextArea* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + int cols = (int)luaL_checkinteger(L, 2); + obj->SetNumColumns(cols); + return 0; +} + +int ElementFormControlTextAreaSetAttrmaxlength(lua_State* L) +{ + ElementFormControlTextArea* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + int ml = (int)luaL_checkinteger(L, 2); + obj->SetMaxLength(ml); + return 0; +} + +int ElementFormControlTextAreaSetAttrrows(lua_State* L) +{ + ElementFormControlTextArea* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + int rows = (int)luaL_checkinteger(L, 2); + obj->SetNumRows(rows); + return 0; +} + +int ElementFormControlTextAreaSetAttrwordwrap(lua_State* L) +{ + ElementFormControlTextArea* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + bool ww = RMLUI_CHECK_BOOL(L, 2); + obj->SetWordWrap(ww); + return 0; +} + +RegType ElementFormControlTextAreaMethods[] = { + RMLUI_LUAMETHOD(ElementFormControlTextArea, Select), + RMLUI_LUAMETHOD(ElementFormControlTextArea, SetSelection), + RMLUI_LUAMETHOD(ElementFormControlTextArea, GetSelection), + {nullptr, nullptr}, +}; + +luaL_Reg ElementFormControlTextAreaGetters[] = { + RMLUI_LUAGETTER(ElementFormControlTextArea, cols), + RMLUI_LUAGETTER(ElementFormControlTextArea, maxlength), + RMLUI_LUAGETTER(ElementFormControlTextArea, rows), + RMLUI_LUAGETTER(ElementFormControlTextArea, wordwrap), + {nullptr, nullptr}, +}; + +luaL_Reg ElementFormControlTextAreaSetters[] = { + RMLUI_LUASETTER(ElementFormControlTextArea, cols), + RMLUI_LUASETTER(ElementFormControlTextArea, maxlength), + RMLUI_LUASETTER(ElementFormControlTextArea, rows), + RMLUI_LUASETTER(ElementFormControlTextArea, wordwrap), + {nullptr, nullptr}, +}; + +template <> +void ExtraInit(lua_State* L, int metatable_index) +{ + ExtraInit(L, metatable_index); + LuaType::_regfunctions(L, metatable_index, metatable_index - 1); + AddTypeToElementAsTable(L); +} + +RMLUI_LUATYPE_DEFINE(ElementFormControlTextArea) +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/ElementFormControlTextArea.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/ElementFormControlTextArea.h new file mode 100644 index 00000000..d6ba134b --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/ElementFormControlTextArea.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include + +namespace Rml { +namespace Lua { + +// methods +int ElementFormControlTextAreaSelect(lua_State* L, ElementFormControlTextArea* obj); +int ElementFormControlTextAreaSetSelection(lua_State* L, ElementFormControlTextArea* obj); +int ElementFormControlTextAreaGetSelection(lua_State* L, ElementFormControlTextArea* obj); + +// getters +int ElementFormControlTextAreaGetAttrcols(lua_State* L); +int ElementFormControlTextAreaGetAttrmaxlength(lua_State* L); +int ElementFormControlTextAreaGetAttrrows(lua_State* L); +int ElementFormControlTextAreaGetAttrwordwrap(lua_State* L); + +// setters +int ElementFormControlTextAreaSetAttrcols(lua_State* L); +int ElementFormControlTextAreaSetAttrmaxlength(lua_State* L); +int ElementFormControlTextAreaSetAttrrows(lua_State* L); +int ElementFormControlTextAreaSetAttrwordwrap(lua_State* L); + +extern RegType ElementFormControlTextAreaMethods[]; +extern luaL_Reg ElementFormControlTextAreaGetters[]; +extern luaL_Reg ElementFormControlTextAreaSetters[]; + +// inherits from ElementFormControl which inherits from Element +template <> +void ExtraInit(lua_State* L, int metatable_index); +RMLUI_LUATYPE_DECLARE(ElementFormControlTextArea) +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/ElementTabSet.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/ElementTabSet.cpp new file mode 100644 index 00000000..12f56113 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/ElementTabSet.cpp @@ -0,0 +1,86 @@ +#include "ElementTabSet.h" +#include +#include + +namespace Rml { +namespace Lua { + +// methods +int ElementTabSetSetPanel(lua_State* L, ElementTabSet* obj) +{ + RMLUI_CHECK_OBJ(obj); + int index = GetIndex(L, 1); + const char* rml = luaL_checkstring(L, 2); + + obj->SetPanel(index, rml); + return 0; +} + +int ElementTabSetSetTab(lua_State* L, ElementTabSet* obj) +{ + RMLUI_CHECK_OBJ(obj); + int index = GetIndex(L, 1); + const char* rml = luaL_checkstring(L, 2); + + obj->SetTab(index, rml); + return 0; +} + +// getters +int ElementTabSetGetAttractive_tab(lua_State* L) +{ + ElementTabSet* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + int tab = obj->GetActiveTab(); + PushIndex(L, tab); + return 1; +} + +int ElementTabSetGetAttrnum_tabs(lua_State* L) +{ + ElementTabSet* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + int num = obj->GetNumTabs(); + lua_pushinteger(L, num); + return 1; +} + +// setter +int ElementTabSetSetAttractive_tab(lua_State* L) +{ + ElementTabSet* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + int tab = GetIndex(L, 2); + obj->SetActiveTab(tab); + return 0; +} + +RegType ElementTabSetMethods[] = { + RMLUI_LUAMETHOD(ElementTabSet, SetPanel), + RMLUI_LUAMETHOD(ElementTabSet, SetTab), + {nullptr, nullptr}, +}; + +luaL_Reg ElementTabSetGetters[] = { + RMLUI_LUAGETTER(ElementTabSet, active_tab), + RMLUI_LUAGETTER(ElementTabSet, num_tabs), + {nullptr, nullptr}, +}; + +luaL_Reg ElementTabSetSetters[] = { + RMLUI_LUASETTER(ElementTabSet, active_tab), + {nullptr, nullptr}, +}; + +// this will be used to "inherit" from Element +template <> +void ExtraInit(lua_State* L, int metatable_index) +{ + ExtraInit(L, metatable_index); + LuaType::_regfunctions(L, metatable_index, metatable_index - 1); + AddTypeToElementAsTable(L); +} + +RMLUI_LUATYPE_DEFINE(ElementTabSet) +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/ElementTabSet.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/ElementTabSet.h new file mode 100644 index 00000000..bdd7ae14 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/ElementTabSet.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include + +namespace Rml { +namespace Lua { + +// methods +int ElementTabSetSetPanel(lua_State* L, ElementTabSet* obj); +int ElementTabSetSetTab(lua_State* L, ElementTabSet* obj); + +// getters +int ElementTabSetGetAttractive_tab(lua_State* L); +int ElementTabSetGetAttrnum_tabs(lua_State* L); + +// setter +int ElementTabSetSetAttractive_tab(lua_State* L); + +extern RegType ElementTabSetMethods[]; +extern luaL_Reg ElementTabSetGetters[]; +extern luaL_Reg ElementTabSetSetters[]; + +// this will be used to "inherit" from Element +template <> +void ExtraInit(lua_State* L, int metatable_index); +RMLUI_LUATYPE_DECLARE(ElementTabSet) +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/SelectOptionsProxy.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/SelectOptionsProxy.cpp new file mode 100644 index 00000000..c48b66be --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/SelectOptionsProxy.cpp @@ -0,0 +1,58 @@ +#include "SelectOptionsProxy.h" +#include "../Pairs.h" +#include + +namespace Rml { +namespace Lua { + +int SelectOptionsProxy__index(lua_State* L) +{ + /*the table obj and the missing key are currently on the stack(index 1 & 2) as defined by the Lua language*/ + int keytype = lua_type(L, 2); + if (keytype == LUA_TNUMBER) // only valid key types + { + SelectOptionsProxy* proxy = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(proxy); + int index = (int)luaL_checkinteger(L, 2); + Element* opt = proxy->owner->GetOption(index - 1); + RMLUI_CHECK_OBJ(opt); + lua_newtable(L); + LuaType::push(L, opt, false); + lua_setfield(L, -2, "element"); + lua_pushstring(L, opt->GetAttribute("value", String()).c_str()); + lua_setfield(L, -2, "value"); + return 1; + } + else + return LuaType::index(L); +} + +int SelectOptionsProxy__pairs(lua_State* L) +{ + return MakeIntPairs(L); +} + +RegType SelectOptionsProxyMethods[] = { + {nullptr, nullptr}, +}; + +luaL_Reg SelectOptionsProxyGetters[] = { + {nullptr, nullptr}, +}; + +luaL_Reg SelectOptionsProxySetters[] = { + {nullptr, nullptr}, +}; + +template <> +void ExtraInit(lua_State* L, int metatable_index) +{ + lua_pushcfunction(L, SelectOptionsProxy__index); + lua_setfield(L, metatable_index, "__index"); + lua_pushcfunction(L, SelectOptionsProxy__pairs); + lua_setfield(L, metatable_index, "__pairs"); +} + +RMLUI_LUATYPE_DEFINE(SelectOptionsProxy) +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/SelectOptionsProxy.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/SelectOptionsProxy.h new file mode 100644 index 00000000..b4acc8ad --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Elements/SelectOptionsProxy.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include +#include + +namespace Rml { +namespace Lua { +// where owner is the ElementFormControlSelect that we should look up information from +struct SelectOptionsProxy { + ElementFormControlSelect* owner; +}; + +int SelectOptionsProxy__index(lua_State* L); +int SelectOptionsProxy__pairs(lua_State* L); + +extern RegType SelectOptionsProxyMethods[]; +extern luaL_Reg SelectOptionsProxyGetters[]; +extern luaL_Reg SelectOptionsProxySetters[]; + +template <> +void ExtraInit(lua_State* L, int metatable_index); +RMLUI_LUATYPE_DECLARE(SelectOptionsProxy) +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Event.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Event.cpp new file mode 100644 index 00000000..655993fb --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Event.cpp @@ -0,0 +1,87 @@ +#include "EventParametersProxy.h" +#include +#include +#include +#include +#include + +namespace Rml { +namespace Lua { +template <> +void ExtraInit(lua_State* /*L*/, int /*metatable_index*/) +{ + return; +} + +// methods +int EventStopPropagation(lua_State* /*L*/, Event* obj) +{ + obj->StopPropagation(); + return 0; +} + +int EventStopImmediatePropagation(lua_State* /*L*/, Event* obj) +{ + obj->StopImmediatePropagation(); + return 0; +} + +// getters +int EventGetAttrcurrent_element(lua_State* L) +{ + Event* evt = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(evt); + Element* ele = evt->GetCurrentElement(); + LuaType::push(L, ele, false); + return 1; +} + +int EventGetAttrtype(lua_State* L) +{ + Event* evt = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(evt); + String type = evt->GetType(); + lua_pushstring(L, type.c_str()); + return 1; +} + +int EventGetAttrtarget_element(lua_State* L) +{ + Event* evt = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(evt); + Element* target = evt->GetTargetElement(); + LuaType::push(L, target, false); + return 1; +} + +int EventGetAttrparameters(lua_State* L) +{ + Event* evt = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(evt); + EventParametersProxy* proxy = new EventParametersProxy(); + proxy->owner = evt; + LuaType::push(L, proxy, true); + return 1; +} + +RegType EventMethods[] = { + RMLUI_LUAMETHOD(Event, StopPropagation), + RMLUI_LUAMETHOD(Event, StopImmediatePropagation), + {nullptr, nullptr}, +}; + +luaL_Reg EventGetters[] = { + RMLUI_LUAGETTER(Event, current_element), + RMLUI_LUAGETTER(Event, type), + RMLUI_LUAGETTER(Event, target_element), + RMLUI_LUAGETTER(Event, parameters), + {nullptr, nullptr}, +}; + +luaL_Reg EventSetters[] = { + {nullptr, nullptr}, +}; + +RMLUI_LUATYPE_DEFINE(Event) +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Event.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Event.h new file mode 100644 index 00000000..d5189789 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Event.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include + +namespace Rml { +namespace Lua { +template <> +void ExtraInit(lua_State* L, int metatable_index); +// methods +int EventStopPropagation(lua_State* L, Event* obj); +int EventStopImmediatePropagation(lua_State* L, Event* obj); + +// getters +int EventGetAttrcurrent_element(lua_State* L); +int EventGetAttrtype(lua_State* L); +int EventGetAttrtarget_element(lua_State* L); +int EventGetAttrparameters(lua_State* L); + +extern RegType EventMethods[]; +extern luaL_Reg EventGetters[]; +extern luaL_Reg EventSetters[]; + +RMLUI_LUATYPE_DECLARE(Event) +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/EventParametersProxy.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/EventParametersProxy.cpp new file mode 100644 index 00000000..42d310b8 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/EventParametersProxy.cpp @@ -0,0 +1,62 @@ +#include "EventParametersProxy.h" +#include "Pairs.h" +#include +#include +#include +#include + +namespace Rml { +namespace Lua { + +template <> +void ExtraInit(lua_State* L, int metatable_index) +{ + lua_pushcfunction(L, EventParametersProxy__index); + lua_setfield(L, metatable_index, "__index"); + lua_pushcfunction(L, EventParametersProxy__pairs); + lua_setfield(L, metatable_index, "__pairs"); +} + +int EventParametersProxy__index(lua_State* L) +{ + /*the table obj and the missing key are currently on the stack(index 1 & 2) as defined by the Lua language*/ + int keytype = lua_type(L, 2); + if (keytype == LUA_TSTRING) // only valid key types + { + EventParametersProxy* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + const char* key = lua_tostring(L, 2); + auto it = obj->owner->GetParameters().find(key); + const Variant* param = (it == obj->owner->GetParameters().end() ? nullptr : &it->second); + if (obj->owner->GetId() == EventId::Tabchange && std::strcmp(key, "tab_index") == 0 && param && param->GetType() == Variant::Type::INT) + { + PushIndex(L, param->Get()); + } + else + PushVariant(L, param); + return 1; + } + else + return LuaType::index(L); +} + +int EventParametersProxy__pairs(lua_State* L) +{ + EventParametersProxy* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + return MakePairs(L, obj->owner->GetParameters()); +} + +RegType EventParametersProxyMethods[] = { + {nullptr, nullptr}, +}; +luaL_Reg EventParametersProxyGetters[] = { + {nullptr, nullptr}, +}; +luaL_Reg EventParametersProxySetters[] = { + {nullptr, nullptr}, +}; + +RMLUI_LUATYPE_DEFINE(EventParametersProxy) +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/EventParametersProxy.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/EventParametersProxy.h new file mode 100644 index 00000000..71846e50 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/EventParametersProxy.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include +#include + +namespace Rml { +namespace Lua { +// where owner is the Element that we should look up information from +struct EventParametersProxy { + Event* owner; +}; + +template <> +void ExtraInit(lua_State* L, int metatable_index); +int EventParametersProxy__index(lua_State* L); +int EventParametersProxy__pairs(lua_State* L); + +extern RegType EventParametersProxyMethods[]; +extern luaL_Reg EventParametersProxyGetters[]; +extern luaL_Reg EventParametersProxySetters[]; + +RMLUI_LUATYPE_DECLARE(EventParametersProxy) +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/GlobalLuaFunctions.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/GlobalLuaFunctions.cpp new file mode 100644 index 00000000..f2f81164 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/GlobalLuaFunctions.cpp @@ -0,0 +1,52 @@ +#include "GlobalLuaFunctions.h" +#include +#include +#include +#include +#include + +namespace Rml { +namespace Lua { +/* +Below here are global functions and their helper functions that help overwrite the Lua global functions +*/ + +// Based off of the luaB_print function from Lua's lbaselib.c +int LuaPrint(lua_State* L) +{ + int n = lua_gettop(L); /* number of arguments */ + int i; + lua_getglobal(L, "tostring"); + StringList string_list = StringList(); + String output = ""; + for (i = 1; i <= n; i++) + { + const char* s; + lua_pushvalue(L, -1); /* function to be called */ + lua_pushvalue(L, i); /* value to print */ + lua_call(L, 1, 1); + s = lua_tostring(L, -1); /* get result */ + if (s == nullptr) + return luaL_error(L, "'tostring' must return a string to 'print'"); + if (i > 1) + output += "\t"; + output += String(s); + lua_pop(L, 1); /* pop result */ + } + output += "\n"; + Log::Message(Log::LT_INFO, "%s", output.c_str()); + return 0; +} + +void OverrideLuaGlobalFunctions(lua_State* L) +{ + lua_getglobal(L, "_G"); + + lua_pushcfunction(L, LuaPrint); + lua_setfield(L, -2, "print"); + + lua_pop(L, 1); // pop _G +} + +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/GlobalLuaFunctions.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/GlobalLuaFunctions.h new file mode 100644 index 00000000..337348e6 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/GlobalLuaFunctions.h @@ -0,0 +1,11 @@ +#pragma once + +typedef struct lua_State lua_State; + +namespace Rml { +namespace Lua { +void OverrideLuaGlobalFunctions(lua_State* L); +// overrides pairs and ipairs to respect __pairs and __ipairs metamethods +// overrdes print to print to the console +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Interpreter.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Interpreter.cpp new file mode 100644 index 00000000..ff44a587 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Interpreter.cpp @@ -0,0 +1,121 @@ +#include "LuaDocumentElementInstancer.h" +#include "LuaEventListenerInstancer.h" +#include "LuaPlugin.h" +#include +#include +#include +#include +#include + +namespace Rml { +namespace Lua { + +static int ErrorHandler(lua_State* L) +{ + const char* msg = lua_tostring(L, 1); + if (msg == NULL) + { + if (luaL_callmeta(L, 1, "__tostring") && lua_type(L, -1) == LUA_TSTRING) + return 1; + else + msg = lua_pushfstring(L, "(error object is a %s value)", luaL_typename(L, 1)); + } + luaL_traceback(L, L, msg, 1); + return 1; +} + +static bool LuaCall(lua_State* L, int nargs, int nresults) +{ + int errfunc = -2 - nargs; + lua_pushcfunction(L, ErrorHandler); + lua_insert(L, errfunc); + if (lua_pcall(L, nargs, nresults, errfunc) != LUA_OK) + { + Log::Message(Log::LT_WARNING, "%s", lua_tostring(L, -1)); + lua_pop(L, 2); + return false; + } + lua_remove(L, -1 - nresults); + return true; +} + +lua_State* Interpreter::GetLuaState() +{ + return LuaPlugin::GetLuaState(); +} + +bool Interpreter::LoadFile(const String& file) +{ + lua_State* L = GetLuaState(); + + // use the file interface to get the contents of the script + FileInterface* file_interface = GetFileInterface(); + FileHandle handle = file_interface->Open(file); + if (handle == 0) + { + Log::Message(Log::LT_WARNING, "LoadFile: Unable to open file: %s", file.c_str()); + return false; + } + + size_t size = file_interface->Length(handle); + if (size == 0) + { + Log::Message(Log::LT_WARNING, "LoadFile: File is 0 bytes in size: %s", file.c_str()); + return false; + } + UniquePtr file_contents(new char[size]); + file_interface->Read(file_contents.get(), size, handle); + file_interface->Close(handle); + + if (luaL_loadbuffer(L, file_contents.get(), size, ("@" + file).c_str()) != 0) + { + Log::Message(Log::LT_WARNING, "%s", lua_tostring(L, -1)); + lua_pop(L, 1); + return false; + } + return LuaCall(L, 0, 0); + ; +} + +bool Interpreter::DoString(const String& code, const String& name) +{ + lua_State* L = GetLuaState(); + return LoadString(code, name) && LuaCall(L, 0, 0); +} + +bool Interpreter::LoadString(const String& code, const String& name) +{ + lua_State* L = GetLuaState(); + + if (luaL_loadbuffer(L, code.c_str(), code.length(), name.c_str()) != 0) + { + Log::Message(Log::LT_WARNING, "%s", lua_tostring(L, -1)); + lua_pop(L, 1); + return false; + } + return true; +} + +void Interpreter::BeginCall(int funRef) +{ + lua_State* L = GetLuaState(); + + lua_settop(L, 0); // empty stack + // lua_getref(g_L,funRef); + lua_rawgeti(L, LUA_REGISTRYINDEX, (int)funRef); +} + +bool Interpreter::ExecuteCall(int params, int res) +{ + lua_State* L = GetLuaState(); + return LuaCall(L, params, res); +} + +void Interpreter::EndCall(int res) +{ + lua_State* L = GetLuaState(); + lua_pop(L, res); +} + +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Log.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Log.cpp new file mode 100644 index 00000000..4d93bf3a --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Log.cpp @@ -0,0 +1,66 @@ +#include "Log.h" +#include +#include + +namespace Rml { +namespace Lua { + +template <> +void ExtraInit(lua_State* L, int metatable_index) +{ + // due to they way that LuaType::Register is made, we know that the method table is at the index + // directly below the metatable + int method_index = metatable_index - 1; + + lua_pushcfunction(L, LogMessage); + lua_setfield(L, method_index, "Message"); + + // construct the "logtype" table, so that we can use the Log::Type enum like Log.logtype.always in Lua for Log::LT_ALWAYS + lua_newtable(L); + int logtype = lua_gettop(L); + lua_pushvalue(L, -1); // copy of the new table, so that the logtype index will stay valid + lua_setfield(L, method_index, "logtype"); + + lua_pushinteger(L, (int)Log::LT_ALWAYS); + lua_setfield(L, logtype, "always"); + + lua_pushinteger(L, (int)Log::LT_ERROR); + lua_setfield(L, logtype, "error"); + + lua_pushinteger(L, (int)Log::LT_WARNING); + lua_setfield(L, logtype, "warning"); + + lua_pushinteger(L, (int)Log::LT_INFO); + lua_setfield(L, logtype, "info"); + + lua_pushinteger(L, (int)Log::LT_DEBUG); + lua_setfield(L, logtype, "debug"); + + lua_pop(L, 1); // pop the logtype table + return; +} + +int LogMessage(lua_State* L) +{ + Log::Type type = Log::Type((int)luaL_checkinteger(L, 1)); + const char* str = luaL_checkstring(L, 2); + + Log::Message(type, "%s", str); + return 0; +} + +RegType LogMethods[] = { + {nullptr, nullptr}, +}; + +luaL_Reg LogGetters[] = { + {nullptr, nullptr}, +}; + +luaL_Reg LogSetters[] = { + {nullptr, nullptr}, +}; + +RMLUI_LUATYPE_DEFINE(Log) +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Log.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Log.h new file mode 100644 index 00000000..8c695414 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Log.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include +#include + +namespace Rml { +namespace Lua { + +template <> +void ExtraInit(lua_State* L, int metatable_index); +int LogMessage(lua_State* L); + +extern RegType LogMethods[]; +extern luaL_Reg LogGetters[]; +extern luaL_Reg LogSetters[]; + +RMLUI_LUATYPE_DECLARE(Log) +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Lua.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Lua.cpp new file mode 100644 index 00000000..d549af11 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Lua.cpp @@ -0,0 +1,19 @@ +#include "LuaPlugin.h" +#include +#include + +namespace Rml { +namespace Lua { + +void Initialise() +{ + ::Rml::Lua::Initialise(nullptr); +} + +void Initialise(lua_State* lua_state) +{ + ::Rml::RegisterPlugin(new LuaPlugin(lua_state)); +} + +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaDataModel.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaDataModel.cpp new file mode 100644 index 00000000..bf6e60ee --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaDataModel.cpp @@ -0,0 +1,812 @@ +#include "LuaDataModel.h" +#include +#include +#include +#include + +#define RMLDATAMODEL "RMLDATAMODEL" +#define RMLTABLEPROXY "RMLTABLEPROXY" + +namespace Rml { +namespace Lua { + +namespace luabind { +#if LUA_VERSION_NUM < 503 +static void lua_reverse(lua_State* L, int a, int b) +{ + for (; a < b; ++a, --b) + { + lua_pushvalue(L, a); + lua_pushvalue(L, b); + lua_replace(L, a); + lua_replace(L, b); + } +} +void lua_rotate(lua_State* L, int idx, int n) +{ + int n_elems = 0; + idx = lua_absindex(L, idx); + n_elems = lua_gettop(L) - idx + 1; + if (n < 0) + { + n += n_elems; + } + if (n > 0 && n < n_elems) + { + luaL_checkstack(L, 2, "not enough stack slots available"); + n = n_elems - n; + lua_reverse(L, idx, idx + n - 1); + lua_reverse(L, idx + n, idx + n_elems - 1); + lua_reverse(L, idx, idx + n_elems - 1); + } +} +#endif +using call_t = Rml::Function; +inline int errhandler(lua_State* L) +{ + const char* msg = lua_tostring(L, 1); + if (msg == NULL) + { + if (luaL_callmeta(L, 1, "__tostring") && lua_type(L, -1) == LUA_TSTRING) + return 1; + else + msg = lua_pushfstring(L, "(error object is a %s value)", luaL_typename(L, 1)); + } + luaL_traceback(L, L, msg, 1); + return 1; +} +inline void errfunc(const char* msg) +{ + Log::Message(Log::LT_WARNING, "%s", msg); +} +inline int function_call(lua_State* L) +{ + call_t& f = *(call_t*)lua_touserdata(L, 1); + f(); + return 0; +} +inline bool invoke(lua_State* L, call_t f, int argn = 0) +{ + if (!lua_checkstack(L, 3)) + { + errfunc("stack overflow"); + lua_pop(L, argn); + return false; + } + lua_pushcfunction(L, errhandler); + lua_pushcfunction(L, function_call); + lua_pushlightuserdata(L, &f); + lua_rotate(L, -argn - 3, 3); + if (lua_pcall(L, 1 + argn, 0, lua_gettop(L) - argn - 2) != LUA_OK) + { + errfunc(lua_tostring(L, -1)); + lua_pop(L, 2); + return false; + } + lua_pop(L, 1); + return true; +} +} // namespace luabind + +class LuaObjectDef; + +struct LuaDataModel { + DataModelConstructor constructor; + DataModelHandle handle; + lua_State* L; + LuaObjectDef* objectDef; + int model_ref = LUA_NOREF; + int valuestore_ref = LUA_NOREF; + int tablestore_ref = LUA_NOREF; + int keystore_ref = LUA_NOREF; +}; + +struct TableProxy { + LuaDataModel* model; + int table_ref = LUA_NOREF; + int key_ref = LUA_NOREF; +}; + +static struct TableProxy* NewTableProxy(struct LuaDataModel* model, int key_ref); +static struct TableProxy* ToTableProxy(lua_State* L, int index) +{ + return (struct TableProxy*)luaL_testudata(L, index, RMLTABLEPROXY); +} + +class LuaObjectDef : public VariableDefinition { +public: + LuaObjectDef(struct LuaDataModel* model); + bool Get(void* ptr, Variant& variant) override; + bool Set(void* ptr, const Variant& variant) override; + int Size(void* ptr) override; + DataVariable Child(void* ptr, const DataAddressEntry& address) override; + +protected: + struct LuaDataModel* model; +}; + +void PushValue(struct LuaDataModel* model, int ref) +{ + lua_State* L = model->L; + lua_rawgeti(L, LUA_REGISTRYINDEX, model->valuestore_ref); + lua_rawgeti(L, -1, ref); + lua_replace(L, -2); +} + +void SetValue(struct LuaDataModel* model, int ref) +{ + // -1 = value + lua_State* L = model->L; + lua_rawgeti(L, LUA_REGISTRYINDEX, model->valuestore_ref); + lua_pushvalue(L, -2); + lua_rawseti(L, -2, ref); + lua_pop(L, 2); +} + +int ChildRef(struct LuaDataModel* model, int ref) +{ + // -1 = key + lua_State* L = model->L; + lua_rawgeti(L, LUA_REGISTRYINDEX, model->tablestore_ref); + // -1 = tablestore, -2 = key + lua_rawgeti(L, -1, ref); + // -1 = tablestore entry / nil, -2 = tablestore, -3 = key + lua_replace(L, -2); + // -1 = tablestore entry / nil, -2 = key + + if (lua_type(L, -1) != LUA_TTABLE) + { + lua_pop(L, 1); + // -1 = key; + return LUA_REFNIL; + } + + lua_pushvalue(L, -2); + // -1 = key, -2 = tablestore entry, -3 = key + lua_rawget(L, -2); + // -1 = child ref / nil, -2 = tablestore entry, -3 = key + + if (lua_isnil(L, -1)) + { + lua_pop(L, 2); + // -1 = key; + return LUA_REFNIL; + } + + int child_ref = (int)lua_tonumber(L, -1); + lua_pop(L, 2); + // -1 = key; + return child_ref; +} + +LuaObjectDef::LuaObjectDef(struct LuaDataModel* model) : VariableDefinition(DataVariableType::Scalar), model(model) {} + +bool LuaObjectDef::Get(void* ptr, Variant& variant) +{ + lua_State* L = model->L; + if (!L) + return false; + int ref = (int)(intptr_t)ptr; + PushValue(model, ref); + GetVariant(L, -1, &variant); + lua_pop(L, 1); + return true; +} + +bool LuaObjectDef::Set(void* ptr, const Variant& variant) +{ + int ref = (int)(intptr_t)ptr; + lua_State* L = model->L; + if (!L || ref == LUA_REFNIL) + return false; + PushVariant(L, &variant); + SetValue(model, ref); + return true; +} + +int LuaObjectDef::Size(void* ptr) +{ + lua_State* L = model->L; + if (!L) + return 0; + int ref = (int)(intptr_t)ptr; + PushValue(model, ref); + + struct TableProxy* proxy = ToTableProxy(L, -1); + lua_pop(L, 1); + + if (proxy == nullptr || proxy->model != model) + return 1; + + PushValue(model, proxy->table_ref); + int size = (int)luaL_len(L, -1); + lua_pop(L, 1); + return size; +} + +DataVariable LuaObjectDef::Child(void* ptr, const DataAddressEntry& address) +{ + lua_State* L = model->L; + if (!L) + return DataVariable{}; + int ref = (int)(intptr_t)ptr; + + PushValue(model, ref); + // -1 = this table + + struct TableProxy* proxy = ToTableProxy(L, -1); + + if (proxy == nullptr || proxy->model != model) + { + lua_pop(L, 1); + return DataVariable{}; + } + + if (address.index == -1) + lua_pushlstring(L, address.name.data(), address.name.size()); + else + lua_pushinteger(L, (lua_Integer)address.index + 1); + // -1 = key, -2 = this table + + int child_ref = ChildRef(model, proxy->table_ref); + + if (child_ref != LUA_REFNIL) + { + lua_pop(L, 2); + return DataVariable(model->objectDef, (void*)(intptr_t)child_ref); + } + + lua_pushvalue(L, -1); + // -1 = key, -2 = key, -3 = this table + lua_gettable(L, -3); + // -1 = value, -2 = key, -3 = this table + lua_remove(L, -3); + // -1 = value, -2 = key + + if (lua_type(L, -1) == LUA_TTABLE) + NewTableProxy(model, proxy->key_ref); + else if (lua_isnil(L, -1)) + { + lua_pop(L, 2); + return DataVariable{}; + } + + lua_rawgeti(L, LUA_REGISTRYINDEX, model->valuestore_ref); + // -1 = valuestore, -2 = value, -3 = key + lua_pushvalue(L, -2); + // -1 = value, -2 = valuestore, -3 = value, -4 = key + child_ref = luaL_ref(L, -2); + // -1 = valuestore, -2 = value, -3 = key + lua_pop(L, 2); + // -1 = key + + lua_rawgeti(L, LUA_REGISTRYINDEX, model->tablestore_ref); + // -1 = tablestore, -2 = key + lua_rawgeti(L, -1, proxy->table_ref); + // -1 = tablestore entry / nil, -2 = tablestore, -3 = key + + if (lua_type(L, -1) != LUA_TTABLE) + { + lua_pop(L, 1); + // -1 = tablestore, -2 = key + lua_newtable(L); + // -1 = tablestore entry, -2 = tablestore, -3 = key + lua_pushvalue(L, -1); + // -1 = tablestore entry, -2 = tablestore entry, -3 = tablestore, -4 = key + lua_rawseti(L, -3, proxy->table_ref); + // -1 = tablestore entry, -2 = tablestore, -3 = key + } + + lua_replace(L, -2); + // -1 = tablestore entry, -2 = key + + lua_pushvalue(L, -2); + // -1 = key, -2 = tablestore entry, -3 = key + lua_pushnumber(L, child_ref); + // -1 = child ref, -2 = key, -3 = tablestore entry, -4 = key + lua_rawset(L, -3); + // -1 = tablestore entry, -2 = key + lua_pop(L, 2); + + return DataVariable(model->objectDef, (void*)(intptr_t)child_ref); +} + +static int TableProxyGet(lua_State* L) +{ + // 1 = table proxy, 2 = key + struct TableProxy* proxy = (struct TableProxy*)lua_touserdata(L, 1); + + lua_rawgeti(L, LUA_REGISTRYINDEX, proxy->model->tablestore_ref); + // -1 = tablestore + lua_rawgeti(L, -1, proxy->table_ref); + // -1 = tablestore entry, -2 = tablestore + lua_replace(L, -2); + // -1 = tablestore entry + lua_pushvalue(L, 2); + // -1 = key, -2 = tablestore entry + lua_rawget(L, -2); + // -1 = child ref / nil, -2 = tablestore entry + + int child_ref = LUA_REFNIL; + + if (lua_isnil(L, -1)) + { + lua_pop(L, 1); + // -1 = tablestore entry + lua_rawgeti(L, LUA_REGISTRYINDEX, proxy->model->valuestore_ref); + // -1 = valuestore, -2 = tablestore entry + lua_rawgeti(L, -1, proxy->table_ref); + // -1 = table, -2 = valuestore, -3 = tablestore entry + lua_replace(L, -2); + // -1 = table, -2 = tablestore entry + lua_pushvalue(L, 2); + // -1 = key, -2 = table, -3 = tablestore entry + lua_gettable(L, -2); + // -1 = value, -2 = table, -3 = tablestore entry + lua_replace(L, -2); + // -1 = value, -2 = tablestore entry + if (lua_isnil(L, -1)) + return 1; // Just return nil; avoid setting LUA_REFNIL + + lua_rawgeti(L, LUA_REGISTRYINDEX, proxy->model->valuestore_ref); + // -1 = valuestore, -2 = value, -3 = tablestore entry + lua_pushvalue(L, -2); + // -1 = value, -2 = valuestore, -3 = value, -4 = tablestore entry + child_ref = luaL_ref(L, -2); + // -1 = valuestore, -2 = value, -3 = tablestore entry + lua_pushvalue(L, 2); + // -1 = key, -2 = valuestore, -3 = value, -4 = tablestore entry + lua_replace(L, -2); + // -1 = key, -2 = value, -3 = tablestore entry + lua_pushnumber(L, child_ref); + // -1 = child ref, -2 = key, -3 = value, -4 = tablestore entry + lua_rawset(L, -4); + // -1 = value, -2 = tablestore entry + lua_replace(L, -2); + // -1 = value + } + else + { + child_ref = (int)lua_tonumber(L, -1); + // -1 = child ref, -2 = tablestore entry + lua_pop(L, 2); + + lua_rawgeti(L, LUA_REGISTRYINDEX, proxy->model->valuestore_ref); + // -1 = valuestore + lua_rawgeti(L, -1, child_ref); + // -1 = value, -2 = valuestore + lua_replace(L, -2); + // -1 = value + } + + if (lua_type(L, -1) == LUA_TTABLE) + { + NewTableProxy(proxy->model, proxy->key_ref); + // -1 = value + lua_pushvalue(L, -1); + // -1 = value, -2 = value + SetValue(proxy->model, child_ref); + // -1 = value + } + + return 1; +} + +static int TableProxySet(lua_State* L) +{ + // 1 = table proxy, 2 = key, 3 = value + struct TableProxy* proxy = (struct TableProxy*)lua_touserdata(L, 1); + + struct TableProxy* value_proxy = ToTableProxy(L, 3); + if (value_proxy != nullptr) + { + PushValue(proxy->model, value_proxy->table_ref); + lua_replace(L, 3); + } + + lua_rawgeti(L, LUA_REGISTRYINDEX, proxy->model->valuestore_ref); + // -1 = valuestore + lua_rawgeti(L, LUA_REGISTRYINDEX, proxy->model->tablestore_ref); + // -1 = tablestore, -2 = valuestore + lua_rawgeti(L, -1, proxy->table_ref); + // -1 = tablestore entry, -2 = tablestore, -3 = valuestore + lua_replace(L, -2); + // -1 = tablestore entry, -2 = valuestore + lua_pushvalue(L, 2); + // -1 = key, -2 = tablestore entry, -3 = valuestore + lua_rawget(L, -2); + // -1 = child ref, -2 = tablestore entry, -3 = valuestore + + if (lua_isnil(L, -1)) + { + if (lua_isnil(L, 3)) + return 0; + + lua_pop(L, 1); + // -1 = tablestore entry, -2 = valuestore + lua_pushvalue(L, 3); + // -1 = value, -2 = tablestore entry, -3 = valuestore + if (lua_type(L, -1) == LUA_TTABLE) + NewTableProxy(proxy->model, proxy->key_ref); + int child_ref = luaL_ref(L, -3); + // -1 = tablestore entry, -2 = valuestore + lua_pushvalue(L, 2); + // -1 = key, -2 = tablestore entry, -3 = valuestore + lua_pushnumber(L, child_ref); + // -1 = child ref, -2 = key, -3 = tablestore entry, -4 = valuestore + lua_rawset(L, -3); + // -1 = tablestore entry, -2 = valuestore + return 0; + } + + int child_ref = (int)lua_tonumber(L, -1); + lua_pop(L, 3); + + PushValue(proxy->model, child_ref); + // -1 = prev value + + if (lua_rawequal(L, -1, 3)) + return 0; + + lua_pop(L, 1); + + lua_pushvalue(L, 3); + // -1 = value + if (lua_type(L, -1) == LUA_TTABLE) + NewTableProxy(proxy->model, proxy->key_ref); + SetValue(proxy->model, child_ref); + + lua_rawgeti(L, LUA_REGISTRYINDEX, proxy->model->keystore_ref); + // -1 = keystore + lua_rawgeti(L, -1, proxy->key_ref); + // -1 = root key, -2 = keystore + proxy->model->handle.DirtyVariable(lua_tostring(L, -1)); + lua_pop(L, 2); + + return 0; +} + +static int TableProxyGc(lua_State* L) +{ + struct TableProxy* proxy = (struct TableProxy*)lua_touserdata(L, 1); + + if (proxy->model->valuestore_ref != LUA_NOREF) + { + lua_rawgeti(L, LUA_REGISTRYINDEX, proxy->model->valuestore_ref); + lua_pushnil(L); + lua_rawseti(L, -2, proxy->table_ref); + lua_pop(L, 1); + } + + if (proxy->model->tablestore_ref != LUA_NOREF) + { + lua_rawgeti(L, LUA_REGISTRYINDEX, proxy->model->tablestore_ref); + lua_pushnil(L); + lua_rawseti(L, -2, proxy->table_ref); + lua_pop(L, 1); + } + + proxy->table_ref = LUA_NOREF; + + return 0; +} + +static struct TableProxy* NewTableProxy(struct LuaDataModel* model, int key_ref) +{ + // -1 = table + lua_State* L = model->L; + + lua_rawgeti(L, LUA_REGISTRYINDEX, model->valuestore_ref); + lua_pushvalue(L, -2); + int table_ref = luaL_ref(L, -2); + lua_pop(L, 2); + + lua_rawgeti(L, LUA_REGISTRYINDEX, model->tablestore_ref); + lua_newtable(L); + lua_rawseti(L, -2, table_ref); + lua_pop(L, 1); + + struct TableProxy* proxy = (struct TableProxy*)lua_newuserdata(L, sizeof(*proxy)); + // -1 = proxy + proxy->model = model; + proxy->table_ref = table_ref; + proxy->key_ref = key_ref; + + if (luaL_newmetatable(L, RMLTABLEPROXY)) + { + luaL_Reg l[] = { + {"__index", TableProxyGet}, + {"__newindex", TableProxySet}, + {"__gc", TableProxyGc}, + {nullptr, nullptr}, + }; + luaL_setfuncs(L, l, 0); + } + lua_setmetatable(L, -2); + + return proxy; +} + +static void BindVariable(struct LuaDataModel* model) +{ + // -1 = value, -2 = key + lua_State* L = model->L; + + struct TableProxy* value_proxy = ToTableProxy(L, -1); + if (value_proxy != nullptr) + { + lua_pop(L, 1); + PushValue(model, value_proxy->table_ref); + } + + lua_rawgeti(L, LUA_REGISTRYINDEX, model->valuestore_ref); + // -1 = valuestore, -2 = value, -3 = key + lua_pushvalue(L, -2); + // -1 = value, -2 = valuestore, -3 = value, -4 = key + value_proxy = lua_type(L, -1) == LUA_TTABLE ? NewTableProxy(model, LUA_REFNIL) : nullptr; + int ref = luaL_ref(L, -2); + if (value_proxy != nullptr) + value_proxy->key_ref = ref; + // -1 = valuestore, -2 = value, -3 = key + lua_pop(L, 1); + // -1 = value, -2 = key + + lua_rawgeti(L, LUA_REGISTRYINDEX, model->tablestore_ref); + // -1 = tablestore, -2 = value, -3 = key + lua_rawgeti(L, -1, model->model_ref); + // -1 = tablestore entry, -2 = tablestore, -2 = value, -3 = key + lua_replace(L, -2); + // -1 = tablestore entry, -2 = value, -3 = key + lua_pushvalue(L, -3); + // -1 = key, -2 = tablestore entry, -3 = value, -4 = key + lua_pushnumber(L, ref); + // -1 = ref, -2 = key, -3 = tablestore entry, -4 = value, -5 = key + lua_rawset(L, -3); + // -1 = tablestore entry, -2 = value, -3 = key + lua_pop(L, 1); + // -1 = value, -2 = key + + lua_rawgeti(L, LUA_REGISTRYINDEX, model->keystore_ref); + // -1 = keystore, -2 = value, -3 = key + lua_pushvalue(L, -3); + // -1 = key, -2 = keystore, -3 = value, -4 = key + lua_rawseti(L, -2, ref); + // -1 = keystore, -2 = value, -3 = key + lua_pop(L, 1); + // -1 = value, -2 = key + + // Root data model variable names are assumed to be strings. But lua_tostring + // actually converts the value on the stack to a string, which can cause a + // problem for lua_next. So call it on a copy, just to be safe. + lua_pushvalue(L, -2); + const char* key = lua_tostring(L, -1); + lua_pop(L, 1); + + if (lua_type(L, -1) == LUA_TFUNCTION) + { + model->constructor.BindEventCallback(key, [=](DataModelHandle, Event& event, const VariantList& varlist) { + PushValue(model, ref); + luabind::invoke( + L, + [&]() { + LuaType::push(L, &event, false); + for (const auto& variant : varlist) + { + PushVariant(L, &variant); + } + lua_call(L, (int)varlist.size() + 1, 0); + }, + 1); + }); + } + else + { + model->constructor.BindCustomDataVariable(key, DataVariable(model->objectDef, (void*)(intptr_t)ref)); + } +} + +static int lDataModelGet(lua_State* L) +{ + // 1 = data model, 2 = key + struct LuaDataModel* model = (struct LuaDataModel*)lua_touserdata(L, 1); + if (model->model_ref == LUA_NOREF) + { + luaL_error(L, "DataModel closed"); + return 0; + } + + lua_pushvalue(L, 2); + int child_ref = ChildRef(model, model->model_ref); + lua_pop(L, 1); + + if (child_ref == LUA_REFNIL) + { + lua_pushnil(L); + return 1; + } + + lua_rawgeti(L, LUA_REGISTRYINDEX, model->valuestore_ref); + lua_rawgeti(L, -1, child_ref); + + if (lua_type(L, -1) == LUA_TTABLE) + { + NewTableProxy(model, child_ref); + lua_pushvalue(L, -1); + lua_rawseti(L, -3, child_ref); + } + + return 1; +} + +static int lDataModelSet(lua_State* L) +{ + // 1 = data model, 2 = key, 3 = value + struct LuaDataModel* model = (struct LuaDataModel*)lua_touserdata(L, 1); + if (model->model_ref == LUA_NOREF) + { + luaL_error(L, "DataModel closed"); + return 0; + } + + lua_pushvalue(L, 2); + int child_ref = ChildRef(model, model->model_ref); + lua_pop(L, 1); + + if (child_ref == LUA_REFNIL) + { + BindVariable(model); + return 0; + } + + lua_pushvalue(L, 3); + struct TableProxy* value_proxy = ToTableProxy(L, -1); + if (value_proxy != nullptr) + { + lua_pop(L, 1); + PushValue(model, value_proxy->table_ref); + } + + if (lua_type(L, -1) == LUA_TTABLE) + NewTableProxy(model, child_ref); + SetValue(model, child_ref); + + lua_pushvalue(L, 2); + model->handle.DirtyVariable(lua_tostring(L, -1)); + lua_pop(L, 1); + + return 0; +} + +void PushDataModelsTable(lua_State* L) +{ + static constexpr auto datamodels_key = "datamodels"; + + lua_pushstring(L, datamodels_key); + lua_rawget(L, LUA_REGISTRYINDEX); + + if (lua_type(L, -1) == LUA_TTABLE) + return; + + lua_pop(L, 1); + lua_newtable(L); + lua_pushstring(L, datamodels_key); + lua_pushvalue(L, -2); + lua_rawset(L, LUA_REGISTRYINDEX); +} + +bool OpenLuaDataModel(lua_State* L, Context* context, int name_index, int table_index) +{ + String name = luaL_checkstring(L, name_index); + luaL_checktype(L, table_index, LUA_TTABLE); + + DataModelConstructor constructor = context->CreateDataModel(name); + if (!constructor) + { + constructor = context->GetDataModel(name); + if (!constructor) + { + return false; + } + } + + struct LuaDataModel* model = (struct LuaDataModel*)lua_newuserdata(L, sizeof(*model)); + model->L = L; + model->constructor = constructor; + model->handle = constructor.GetModelHandle(); + model->objectDef = new LuaObjectDef(model); + + lua_newtable(L); + lua_pushvalue(L, -2); + model->model_ref = luaL_ref(L, -2); + model->valuestore_ref = luaL_ref(L, LUA_REGISTRYINDEX); + + lua_newtable(L); + lua_newtable(L); + lua_rawseti(L, -2, model->model_ref); + model->tablestore_ref = luaL_ref(L, LUA_REGISTRYINDEX); + + lua_newtable(L); + model->keystore_ref = luaL_ref(L, LUA_REGISTRYINDEX); + + lua_pushnil(L); + while (lua_next(L, table_index) != 0) + { + BindVariable(model); + lua_pop(L, 1); + } + + if (luaL_newmetatable(L, RMLDATAMODEL)) + { + luaL_Reg l[] = { + {"__index", lDataModelGet}, + {"__newindex", lDataModelSet}, + {nullptr, nullptr}, + }; + luaL_setfuncs(L, l, 0); + } + lua_setmetatable(L, -2); + // -1 = data model + + PushDataModelsTable(L); + // -1 = data models table, -2 = data model + lua_pushvalue(L, name_index); + // -1 = name, -2 = data models table, -3 = data model + lua_pushvalue(L, -3); + // -1 = data model, -2 = name, -3 = data models table, -4 = data model + lua_rawset(L, -3); + // -1 = data models table, -2 = data model + lua_pop(L, 1); + // -1 = data model + + return true; +} + +void CloseLuaDataModel(lua_State* L, Context* context, int name_index) +{ + String name = luaL_checkstring(L, name_index); + + PushDataModelsTable(L); + // -1 = data models table + lua_pushvalue(L, name_index); + // -1 = name, -2 = data models table + lua_rawget(L, -2); + // -1 = data model / nil, -2 = data models table + struct LuaDataModel* model = (struct LuaDataModel*)luaL_testudata(L, -1, RMLDATAMODEL); + lua_pop(L, 2); + + if (model == nullptr) + return; + + lua_pushnil(L); + lua_rawseti(L, LUA_REGISTRYINDEX, model->valuestore_ref); + model->valuestore_ref = LUA_NOREF; + + lua_pushnil(L); + lua_rawseti(L, LUA_REGISTRYINDEX, model->tablestore_ref); + model->tablestore_ref = LUA_NOREF; + + lua_pushnil(L); + lua_rawseti(L, LUA_REGISTRYINDEX, model->keystore_ref); + model->keystore_ref = LUA_NOREF; + + model->L = nullptr; + delete model->objectDef; + model->objectDef = nullptr; + + model->model_ref = LUA_NOREF; + + PushDataModelsTable(L); + // -1 = data models table + lua_pushvalue(L, name_index); + // -1 = name, -2 = data models table + lua_pushnil(L); + // -1 = nil, -2 = name, -3 = data models table + lua_rawset(L, -3); + // -1 = data models table + lua_pop(L, 1); + + context->RemoveDataModel(name); +} + +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaDataModel.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaDataModel.h new file mode 100644 index 00000000..7441a01c --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaDataModel.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include +#include + +namespace Rml { +namespace Lua { + +struct LuaDataModel; + +// Create or Get a DataModel in L, return false on fail +bool OpenLuaDataModel(lua_State* L, Rml::Context* context, int name_index, int table_index); +// Close a DataModel +void CloseLuaDataModel(lua_State* L, Rml::Context* context, int name_index); + +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaDocument.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaDocument.cpp new file mode 100644 index 00000000..681b1e78 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaDocument.cpp @@ -0,0 +1,29 @@ +#include "LuaDocument.h" +#include +#include +#include + +namespace Rml { +namespace Lua { + +LuaDocument::LuaDocument(const String& tag) : ElementDocument(tag) {} + +void LuaDocument::LoadInlineScript(const String& context, const String& source_path, int source_line) +{ + String buffer; + buffer += "--"; + buffer += source_path; + buffer += ":"; + buffer += Rml::ToString(source_line); + buffer += "\n"; + buffer += context; + Interpreter::DoString(buffer, buffer); +} + +void LuaDocument::LoadExternalScript(const String& source_path) +{ + Interpreter::LoadFile(source_path); +} + +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaDocument.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaDocument.h new file mode 100644 index 00000000..440b3826 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaDocument.h @@ -0,0 +1,18 @@ +#pragma once +/* + This class is an ElementDocument that overrides the LoadInlineScript and LoadExternalScript function +*/ +#include + +namespace Rml { +namespace Lua { + +class LuaDocument : public ::Rml::ElementDocument { +public: + LuaDocument(const String& tag); + void LoadInlineScript(const String& content, const String& source_path, int source_line) override; + void LoadExternalScript(const String& source_path) override; +}; + +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaDocumentElementInstancer.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaDocumentElementInstancer.cpp new file mode 100644 index 00000000..fa1b08dd --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaDocumentElementInstancer.cpp @@ -0,0 +1,23 @@ +#include "LuaDocumentElementInstancer.h" +#include "LuaDocument.h" + +namespace Rml { +namespace Lua { + +/// Instances an element given the tag name and attributes. +/// @param[in] parent The element the new element is destined to be parented to. +/// @param[in] tag The tag of the element to instance. +/// @param[in] attributes Dictionary of attributes. +ElementPtr LuaDocumentElementInstancer::InstanceElement(Element* /*parent*/, const String& tag, const XMLAttributes& /*attributes*/) +{ + return ElementPtr(new LuaDocument(tag)); +} +/// Releases an element instanced by this instancer. +/// @param[in] element The element to release. +void LuaDocumentElementInstancer::ReleaseElement(Element* element) +{ + delete element; +} + +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaDocumentElementInstancer.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaDocumentElementInstancer.h new file mode 100644 index 00000000..33f8259a --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaDocumentElementInstancer.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +namespace Rml { +namespace Lua { + +class LuaDocumentElementInstancer : public ::Rml::ElementInstancer { + /// Instances an element given the tag name and attributes. + /// @param[in] parent The element the new element is destined to be parented to. + /// @param[in] tag The tag of the element to instance. + /// @param[in] attributes Dictionary of attributes. + ElementPtr InstanceElement(Element* parent, const String& tag, const XMLAttributes& attributes) override; + /// Releases an element instanced by this instancer. + /// @param[in] element The element to release. + void ReleaseElement(Element* element) override; +}; + +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaElementInstancer.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaElementInstancer.cpp new file mode 100644 index 00000000..f9cb0a6d --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaElementInstancer.cpp @@ -0,0 +1,63 @@ +#include "LuaElementInstancer.h" +#include +#include +#include +#include + +namespace Rml { +namespace Lua { +// This will be called from ElementInstancernew +LuaElementInstancer::LuaElementInstancer(lua_State* L) : ElementInstancer(), ref_InstanceElement(LUA_NOREF) +{ + if (lua_type(L, 1) != LUA_TFUNCTION && !lua_isnoneornil(L, 1)) + { + Log::Message(Log::LT_ERROR, "The argument to ElementInstancer.new has to be a function or nil. You passed in a %s.", luaL_typename(L, 1)); + return; + } + PushFunctionsTable(L); // top of the table is now ELEMENTINSTANCERFUNCTIONS table + lua_pushvalue(L, 1); // copy of the function + ref_InstanceElement = luaL_ref(L, -2); + lua_pop(L, 1); // pop the ELEMENTINSTANCERFUNCTIONS table +} + +ElementPtr LuaElementInstancer::InstanceElement(Element* /*parent*/, const String& tag, const XMLAttributes& /*attributes*/) +{ + lua_State* L = Interpreter::GetLuaState(); + int top = lua_gettop(L); + ElementPtr ret = nullptr; + if (ref_InstanceElement != LUA_REFNIL && ref_InstanceElement != LUA_NOREF) + { + PushFunctionsTable(L); + lua_rawgeti(L, -1, ref_InstanceElement); // push the function + lua_pushstring(L, tag.c_str()); // push the tag + Interpreter::ExecuteCall(1, 1); // we pass in a string, and we want to get an Element back + ret = std::move(*LuaType::check(L, -1)); + } + else + { + Log::Message(Log::LT_WARNING, "Attempt to call the function for ElementInstancer.InstanceElement, the function does not exist."); + } + lua_settop(L, top); + return ret; +} + +void LuaElementInstancer::ReleaseElement(Element* element) +{ + delete element; +} + +void LuaElementInstancer::PushFunctionsTable(lua_State* L) +{ + // make sure there is an area to save the function + lua_getglobal(L, "ELEMENTINSTANCERFUNCTIONS"); + if (lua_isnoneornil(L, -1)) + { + lua_newtable(L); + lua_setglobal(L, "ELEMENTINSTANCERFUNCTIONS"); + lua_pop(L, 1); // pop the unsucessful getglobal + lua_getglobal(L, "ELEMENTINSTANCERFUNCTIONS"); + } +} + +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaElementInstancer.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaElementInstancer.h new file mode 100644 index 00000000..b96e9d7d --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaElementInstancer.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include +#include + +namespace Rml { +namespace Lua { + +class LuaElementInstancer : public ::Rml::ElementInstancer { +public: + LuaElementInstancer(lua_State* L); + /// Instances an element given the tag name and attributes. + /// @param[in] parent The element the new element is destined to be parented to. + /// @param[in] tag The tag of the element to instance. + /// @param[in] attributes Dictionary of attributes. + ElementPtr InstanceElement(Element* parent, const String& tag, const XMLAttributes& attributes) override; + /// Releases an element instanced by this instancer. + /// @param[in] element The element to release. + void ReleaseElement(Element* element) override; + + int ref_InstanceElement; + + // Pushes on to the top of the stack the table named EVENTINSTNACERFUNCTIONS + void PushFunctionsTable(lua_State* L); +}; + +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaEventListener.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaEventListener.cpp new file mode 100644 index 00000000..51ac1186 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaEventListener.cpp @@ -0,0 +1,113 @@ +#include "LuaEventListener.h" +#include +#include +#include +#include + +namespace Rml { +namespace Lua { +typedef ElementDocument Document; + +LuaEventListener::LuaEventListener(const String& code, Element* element) : EventListener() +{ + // compose function + String function = "return function (event,element,document) "; + function.append(code); + function.append(" end"); + + // make sure there is an area to save the function + lua_State* L = Interpreter::GetLuaState(); + int top = lua_gettop(L); + lua_getglobal(L, "EVENTLISTENERFUNCTIONS"); + if (lua_isnoneornil(L, -1)) + { + lua_newtable(L); + lua_setglobal(L, "EVENTLISTENERFUNCTIONS"); + lua_pop(L, 1); // pop the unsucessful getglobal + lua_getglobal(L, "EVENTLISTENERFUNCTIONS"); + } + int tbl = lua_gettop(L); + + // compile,execute,and save the function + if (!Interpreter::LoadString(function, code) || !Interpreter::ExecuteCall(0, 1)) + { + return; + } + + luaFuncRef = luaL_ref(L, tbl); // creates a reference to the item at the top of the stack in to the table we just created + lua_pop(L, 1); // pop the EVENTLISTENERFUNCTIONS table + + attached = element; + if (element) + owner_document = element->GetOwnerDocument(); + else + owner_document = nullptr; + strFunc = function; + lua_settop(L, top); +} + +// if it is passed in a Lua function +LuaEventListener::LuaEventListener(lua_State* L, int narg, Element* element) +{ + int top = lua_gettop(L); + lua_getglobal(L, "EVENTLISTENERFUNCTIONS"); + if (lua_isnoneornil(L, -1)) + { + lua_newtable(L); + lua_setglobal(L, "EVENTLISTENERFUNCTIONS"); + lua_pop(L, 1); // pop the unsucessful getglobal + lua_getglobal(L, "EVENTLISTENERFUNCTIONS"); + } + lua_pushvalue(L, narg); + luaFuncRef = luaL_ref(L, -2); // put the funtion as a ref in to that table + lua_pop(L, 1); // pop the EVENTLISTENERFUNCTIONS table + + attached = element; + if (element) + owner_document = element->GetOwnerDocument(); + else + owner_document = nullptr; + lua_settop(L, top); +} + +LuaEventListener::~LuaEventListener() +{ + // Remove the Lua function from its table + lua_State* L = Interpreter::GetLuaState(); + lua_getglobal(L, "EVENTLISTENERFUNCTIONS"); + luaL_unref(L, -1, luaFuncRef); + lua_pop(L, 1); // pop table +} + +void LuaEventListener::OnDetach(Element* /*element*/) +{ + // We consider this listener owned by its element, so we must delete ourselves when + // we detach (probably because element was removed). + delete this; +} + +/// Process the incoming Event +void LuaEventListener::ProcessEvent(Event& event) +{ + // not sure if this is the right place to do this, but if the element we are attached to isn't a document, then + // the 'owner_document' variable will be nullptr, because element->ower_document hasn't been set on the construction. We should + // correct that + if (!owner_document && attached) + owner_document = attached->GetOwnerDocument(); + lua_State* L = Interpreter::GetLuaState(); + int top = lua_gettop(L); + + // push the arguments + lua_getglobal(L, "EVENTLISTENERFUNCTIONS"); + lua_rawgeti(L, -1, luaFuncRef); + LuaType::push(L, &event, false); + LuaType::push(L, attached, false); + LuaType::push(L, owner_document, false); + + Interpreter::ExecuteCall(3, 0); // call the function at the top of the stack with 3 arguments + + lua_settop(L, top); // balanced stack makes Lua happy +} + +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaEventListener.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaEventListener.h new file mode 100644 index 00000000..6b9b9b1e --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaEventListener.h @@ -0,0 +1,42 @@ +#pragma once + +#include +#include + +namespace Rml { +class Element; +class ElementDocument; + +namespace Lua { + +class LuaEventListener : public ::Rml::EventListener { +public: + // The plan is to wrap the code in an anonymous function so that we can have named parameters to use, + // rather than putting them in global variables + LuaEventListener(const String& code, Element* element); + + // This is called from a Lua Element if in element:AddEventListener it passes a function in as the 2nd + // parameter rather than a string. We don't wrap the function in an anonymous function, so the user + // should take care to have the proper order. The order is event,element,document. + // narg is the position on the stack + LuaEventListener(lua_State* L, int narg, Element* element); + + virtual ~LuaEventListener(); + + // Deletes itself, which also unreferences the Lua function. + void OnDetach(Element* element) override; + + // Calls the associated Lua function. + void ProcessEvent(Event& event) override; + +private: + // the lua-side function to call when ProcessEvent is called + int luaFuncRef = -1; + + Element* attached = nullptr; + ElementDocument* owner_document = nullptr; + String strFunc; // for debugging purposes +}; + +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaEventListenerInstancer.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaEventListenerInstancer.cpp new file mode 100644 index 00000000..b6eb1af0 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaEventListenerInstancer.cpp @@ -0,0 +1,16 @@ +#include "LuaEventListenerInstancer.h" +#include "LuaEventListener.h" + +namespace Rml { +namespace Lua { + +/// Instance an event listener object. +/// @param value Value of the event. +/// @param element Element that triggers the events. +EventListener* LuaEventListenerInstancer::InstanceEventListener(const String& value, Element* element) +{ + return new LuaEventListener(value, element); +} + +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaEventListenerInstancer.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaEventListenerInstancer.h new file mode 100644 index 00000000..bdbb055d --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaEventListenerInstancer.h @@ -0,0 +1,16 @@ +#pragma once +#include + +namespace Rml { +namespace Lua { + +class LuaEventListenerInstancer : public ::Rml::EventListenerInstancer { +public: + /// Instance an event listener object. + /// @param value Value of the event. + /// @param element Element that triggers the events. + EventListener* InstanceEventListener(const String& value, Element* element) override; +}; + +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaPlugin.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaPlugin.cpp new file mode 100644 index 00000000..343a577d --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaPlugin.cpp @@ -0,0 +1,144 @@ +#include "LuaPlugin.h" +#include "LuaDocumentElementInstancer.h" +#include "LuaEventListenerInstancer.h" +#include "RmlUi.h" +#include +#include +#include +#include +#include +#include +// the types I made +#include "Colourb.h" +#include "Colourf.h" +#include "Context.h" +#include "ContextDocumentsProxy.h" +#include "Document.h" +#include "Element.h" +#include "ElementAttributesProxy.h" +#include "ElementChildNodesProxy.h" +#include "ElementInstancer.h" +#include "ElementStyleProxy.h" +#include "ElementText.h" +#include "Event.h" +#include "EventParametersProxy.h" +#include "GlobalLuaFunctions.h" +#include "Log.h" +#include "RmlUiContextsProxy.h" +#include "Vector2f.h" +#include "Vector2i.h" +// Control types +#include "Elements/ElementForm.h" +#include "Elements/ElementFormControl.h" +#include "Elements/ElementFormControlInput.h" +#include "Elements/ElementFormControlSelect.h" +#include "Elements/ElementFormControlTextArea.h" +#include "Elements/ElementTabSet.h" +#include "Elements/SelectOptionsProxy.h" + +namespace Rml { +namespace Lua { + +static lua_State* g_L = nullptr; + +/** This will populate the global Lua table with all of the Lua core types by calling LuaType::Register +@remark This is called automatically by LuaPlugin::OnInitialise(). */ +static void RegisterTypes(); + +LuaPlugin::LuaPlugin(lua_State* lua_state) +{ + RMLUI_ASSERT(g_L == nullptr); + g_L = lua_state; +} + +int LuaPlugin::GetEventClasses() +{ + return EVT_BASIC; +} + +void LuaPlugin::OnInitialise() +{ + if (g_L == nullptr) + { + Log::Message(Log::LT_INFO, "Loading Lua plugin using a new Lua state."); + g_L = luaL_newstate(); + luaL_openlibs(g_L); + owns_lua_state = true; + } + else + { + Log::Message(Log::LT_INFO, "Loading Lua plugin using the provided Lua state."); + owns_lua_state = false; + } + RegisterTypes(); + + lua_document_element_instancer = new LuaDocumentElementInstancer(); + lua_event_listener_instancer = new LuaEventListenerInstancer(); + Factory::RegisterElementInstancer("body", lua_document_element_instancer); + Factory::RegisterEventListenerInstancer(lua_event_listener_instancer); +} + +void LuaPlugin::OnShutdown() +{ + delete lua_document_element_instancer; + delete lua_event_listener_instancer; + lua_document_element_instancer = nullptr; + lua_event_listener_instancer = nullptr; + + if (owns_lua_state) + lua_close(g_L); + + g_L = nullptr; + + delete this; +} + +static void RegisterTypes() +{ + RMLUI_ASSERT(g_L); + lua_State* L = g_L; + + LuaType::Register(L); + LuaType::Register(L); + LuaType::Register(L); + LuaType::Register(L); + LuaType::Register(L); + LuaType::Register(L); + LuaType::Register(L); + // things that inherit from Element + LuaType::Register(L); + LuaType::Register(L); + LuaType::Register(L); + LuaType::Register(L); + LuaType::Register(L); + LuaType::Register(L); + LuaType::Register(L); + // Proxy tables + LuaType::Register(L); + LuaType::Register(L); + LuaType::Register(L); + LuaType::Register(L); + LuaType::Register(L); + OverrideLuaGlobalFunctions(L); + // push the global variable "rmlui" to use the "RmlUi" methods + LuaRmlUiPushrmluiGlobal(L); + + // Control types + LuaType::Register(L); + LuaType::Register(L); + // Inherits from ElementFormControl + LuaType::Register(L); + LuaType::Register(L); + LuaType::Register(L); + LuaType::Register(L); + // proxy tables + LuaType::Register(L); +} + +lua_State* LuaPlugin::GetLuaState() +{ + return g_L; +} + +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaPlugin.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaPlugin.h new file mode 100644 index 00000000..66e14ff8 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaPlugin.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include + +typedef struct lua_State lua_State; + +namespace Rml { +namespace Lua { + +class LuaDocumentElementInstancer; +class LuaEventListenerInstancer; + +/** + This initializes the Lua interpreter, and has functions to load the scripts or + call functions that exist in Lua. +*/ +class RMLUILUA_API LuaPlugin : public Plugin { +public: + LuaPlugin(lua_State* lua_state); + + static lua_State* GetLuaState(); + +private: + int GetEventClasses() override; + + void OnInitialise() override; + + void OnShutdown() override; + + LuaDocumentElementInstancer* lua_document_element_instancer = nullptr; + LuaEventListenerInstancer* lua_event_listener_instancer = nullptr; + bool owns_lua_state = false; +}; + +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaType.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaType.cpp new file mode 100644 index 00000000..14928533 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/LuaType.cpp @@ -0,0 +1,90 @@ +#include +#include +#include + +namespace Rml { +namespace Lua { + +int LuaTypeImpl::index(lua_State* L, const char* class_name) +{ + /*the table obj and the missing key are currently on the stack(index 1 & 2) as defined by the Lua language*/ + lua_getglobal(L, class_name); // stack pos [3] (fairly important, just refered to as [3]) + // string form of the key. + const char* key = luaL_checkstring(L, 2); + if (lua_istable(L, -1)) //[-1 = 3] + { + lua_pushvalue(L, 2); //[2] = key, [4] = copy of key + lua_rawget(L, -2); //[-2 = 3] -> pop top and push the return value to top [4] + // If the key were looking for is not in the table, retrieve its' metatables' index value. + if (lua_isnil(L, -1)) //[-1 = 4] is value from rawget above + { + // try __getters + lua_pop(L, 1); // remove top item (nil) from the stack + lua_pushstring(L, "__getters"); + lua_rawget(L, -2); //[-2 = 3], ._getters -> result to [4] + lua_pushvalue(L, 2); //[2 = key] -> copy to [5] + lua_rawget(L, -2); //[-2 = __getters] -> __getters[key], result to [5] + if (lua_type(L, -1) == LUA_TFUNCTION) //[-1 = 5] + { + lua_pushvalue(L, 1); // push the userdata to the stack [6] + lua_call(L, 1, 1); // remove one, result is at [6] + } + else + { + lua_settop(L, 4); // forget everything we did above + lua_getmetatable(L, -2); //[-2 = 3] -> metatable from to top [5] + if (lua_istable(L, -1)) //[-1 = 5] = the result of the above + { + lua_getfield(L, -1, "__index"); //[-1 = 5] = check the __index metamethod for the metatable-> push result to [6] + if (lua_isfunction(L, -1)) //[-1 = 6] = __index metamethod + { + lua_pushvalue(L, 1); //[1] = object -> [7] = object + lua_pushvalue(L, 2); //[2] = key -> [8] = key + lua_call(L, 2, 1); // call function at top of stack (__index) -> pop top 2 as args; [7] = return value + } + else if (lua_istable(L, -1)) + lua_getfield(L, -1, key); // shorthand version of above -> [7] = return value + else + lua_pushnil(L); //[7] = nil + } + else + lua_pushnil(L); //[6] = nil + } + } + else if (lua_istable(L, -1)) //[-1 = 4] is value from rawget [3] + { + lua_pushvalue(L, 2); //[2] = key, [5] = key + lua_rawget(L, -2); //[-2 = 3] = table of -> pop top and push the return value to top [5] + } + } + else + lua_pushnil(L); //[4] = nil + + lua_insert(L, 1); // top element to position 1 -> [1] = top element as calculated in the earlier rest of the function + lua_settop(L, 1); // -> [1 = -1], removes the other elements + return 1; +} + +int LuaTypeImpl::newindex(lua_State* L, const char* class_name) +{ + //[1] = obj, [2] = key, [3] = value + // look for it in __setters + lua_getglobal(L, class_name); //[4] = this table + lua_pushstring(L, "__setters"); //[5] + lua_rawget(L, -2); //[-2 = 4] -> .__setters to [5] + lua_pushvalue(L, 2); //[2 = key] -> [6] = copy of key + lua_rawget(L, -2); //[-2 = __setters] -> __setters[key] to [6] + if (lua_type(L, -1) == LUA_TFUNCTION) + { + lua_pushvalue(L, 1); // userdata at [7] + lua_pushvalue(L, 3); //[8] = copy of [3] + lua_call(L, 2, 0); // call function, pop 2 off push 0 on + } + else + lua_pop(L, 1); // not a setter function. + lua_pop(L, 2); // pop __setters and the table + return 0; +} + +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Pairs.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Pairs.h new file mode 100644 index 00000000..cb355923 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Pairs.h @@ -0,0 +1,110 @@ +#pragma once + +#include +#include +#include + +namespace Rml { +namespace Lua { + +template +int PairsConvertTolua(lua_State* L, const T& v); + +template +int PairsConvertTolua(lua_State* L, const std::pair& v) +{ + int nresult = 0; + nresult += PairsConvertTolua(L, v.first); + nresult += PairsConvertTolua(L, v.second); + return nresult; +} + +template <> +inline int PairsConvertTolua(lua_State* L, const String& s) +{ + lua_pushlstring(L, s.c_str(), s.size()); + return 1; +} + +template <> +inline int PairsConvertTolua(lua_State* L, const Variant& v) +{ + PushVariant(L, &v); + return 1; +} + +template +struct PairsHelper { + static int next(lua_State* L) + { + PairsHelper* self = static_cast(lua_touserdata(L, lua_upvalueindex(1))); + if (self->m_first == self->m_last) + { + return 0; + } + int nreslut = PairsConvertTolua(L, *self->m_first); + ++(self->m_first); + return nreslut; + } + static int destroy(lua_State* L) + { + static_cast(lua_touserdata(L, 1))->~PairsHelper(); + return 0; + } + static int constructor(lua_State* L, const T& first, const T& last) + { + void* storage = lua_newuserdata(L, sizeof(PairsHelper)); + if (luaL_newmetatable(L, "RmlUi::Lua::PairsHelper")) + { + static luaL_Reg mt[] = { + {"__gc", destroy}, + {NULL, NULL}, + }; + luaL_setfuncs(L, mt, 0); + } + lua_setmetatable(L, -2); + lua_pushcclosure(L, next, 1); + new (storage) PairsHelper(first, last); + return 1; + } + PairsHelper(const T& first, const T& last) : m_first(first), m_last(last) {} + T m_first; + T m_last; +}; + +template +int MakePairs(lua_State* L, const Iterator& first, const Iterator& last) +{ + return PairsHelper::constructor(L, first, last); +} + +template +int MakePairs(lua_State* L, const Container& container) +{ + return MakePairs(L, std::begin(container), std::end(container)); +} + +inline int ipairsaux(lua_State* L) +{ + lua_Integer i = luaL_checkinteger(L, 2) + 1; + lua_pushinteger(L, i); +#if LUA_VERSION_NUM >= 503 + return (lua_geti(L, 1, i) == LUA_TNIL) ? 1 : 2; +#else + lua_pushinteger(L, i); + lua_gettable(L, 1); + return (lua_isnil(L, -1)) ? 1 : 2; +#endif +} + +inline int MakeIntPairs(lua_State* L) +{ + luaL_checkany(L, 1); + lua_pushcfunction(L, ipairsaux); + lua_pushvalue(L, 1); + lua_pushinteger(L, 0); + return 3; +} + +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/RmlUi.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/RmlUi.cpp new file mode 100644 index 00000000..973b1c66 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/RmlUi.cpp @@ -0,0 +1,307 @@ +#include "RmlUi.h" +#include "ElementInstancer.h" +#include "LuaElementInstancer.h" +#include "RmlUiContextsProxy.h" +#include +#include +#include + +namespace Rml { +namespace Lua { +#define RMLUILUA_INPUTENUM(keyident, tbl) \ + lua_pushinteger(L, Input::KI_##keyident); \ + lua_setfield(L, (tbl), #keyident); +#define RMLUILUA_INPUTMODIFIERENUM(keymod, tbl) \ + lua_pushinteger(L, Input::KM_##keymod); \ + lua_setfield(L, (tbl), #keymod); + +// c++ representation of the global variable in Lua so that the syntax is consistent +LuaRmlUi lua_global_rmlui; + +void LuaRmlUiPushrmluiGlobal(lua_State* L) +{ + luaL_getmetatable(L, GetTClassName()); + LuaRmlUiEnumkey_identifier(L); + lua_global_rmlui.key_identifier_ref = luaL_ref(L, -2); + LuaRmlUiEnumkey_modifier(L); + lua_global_rmlui.key_modifier_ref = luaL_ref(L, -2); + LuaType::push(L, &lua_global_rmlui, false); + lua_setglobal(L, "rmlui"); +} + +template <> +void ExtraInit(lua_State* /*L*/, int /*metatable_index*/) +{ + return; +} + +int LuaRmlUiCreateContext(lua_State* L, LuaRmlUi* /*obj*/) +{ + const char* name = luaL_checkstring(L, 1); + Vector2i* dimensions = LuaType::check(L, 2); + Context* new_context = CreateContext(name, *dimensions); + if (new_context == nullptr || dimensions == nullptr) + { + lua_pushnil(L); + } + else + { + LuaType::push(L, new_context); + } + return 1; +} + +int LuaRmlUiLoadFontFace(lua_State* L, LuaRmlUi* /*obj*/) +{ + const char* file = luaL_checkstring(L, 1); + bool fallback_face = lua_gettop(L) <= 1 ? false : RMLUI_CHECK_BOOL(L, 2); + int face_index = lua_gettop(L) <= 2 ? 0 : static_cast(luaL_checkinteger(L, 3)); + lua_pushboolean(L, LoadFontFace(file, fallback_face, Style::FontWeight::Auto, face_index)); + return 1; +} + +int LuaRmlUiRegisterTag(lua_State* L, LuaRmlUi* /*obj*/) +{ + const char* tag = luaL_checkstring(L, 1); + LuaElementInstancer* lei = (LuaElementInstancer*)LuaType::check(L, 2); + RMLUI_CHECK_OBJ(lei); + Factory::RegisterElementInstancer(tag, lei); + return 0; +} + +int LuaRmlUiGetAttrcontexts(lua_State* L) +{ + RmlUiContextsProxy* proxy = new RmlUiContextsProxy(); + LuaType::push(L, proxy, true); + return 1; +} + +int LuaRmlUiGetAttrkey_identifier(lua_State* L) +{ + luaL_getmetatable(L, GetTClassName()); + lua_rawgeti(L, -1, lua_global_rmlui.key_identifier_ref); + return 1; +} + +int LuaRmlUiGetAttrkey_modifier(lua_State* L) +{ + luaL_getmetatable(L, GetTClassName()); + lua_rawgeti(L, -1, lua_global_rmlui.key_modifier_ref); + return 1; +} + +void LuaRmlUiEnumkey_identifier(lua_State* L) +{ + lua_newtable(L); + int tbl = lua_gettop(L); + RMLUILUA_INPUTENUM(UNKNOWN, tbl) + RMLUILUA_INPUTENUM(SPACE, tbl) + RMLUILUA_INPUTENUM(0, tbl) + RMLUILUA_INPUTENUM(1, tbl) + RMLUILUA_INPUTENUM(2, tbl) + RMLUILUA_INPUTENUM(3, tbl) + RMLUILUA_INPUTENUM(4, tbl) + RMLUILUA_INPUTENUM(5, tbl) + RMLUILUA_INPUTENUM(6, tbl) + RMLUILUA_INPUTENUM(7, tbl) + RMLUILUA_INPUTENUM(8, tbl) + RMLUILUA_INPUTENUM(9, tbl) + RMLUILUA_INPUTENUM(A, tbl) + RMLUILUA_INPUTENUM(B, tbl) + RMLUILUA_INPUTENUM(C, tbl) + RMLUILUA_INPUTENUM(D, tbl) + RMLUILUA_INPUTENUM(E, tbl) + RMLUILUA_INPUTENUM(F, tbl) + RMLUILUA_INPUTENUM(G, tbl) + RMLUILUA_INPUTENUM(H, tbl) + RMLUILUA_INPUTENUM(I, tbl) + RMLUILUA_INPUTENUM(J, tbl) + RMLUILUA_INPUTENUM(K, tbl) + RMLUILUA_INPUTENUM(L, tbl) + RMLUILUA_INPUTENUM(M, tbl) + RMLUILUA_INPUTENUM(N, tbl) + RMLUILUA_INPUTENUM(O, tbl) + RMLUILUA_INPUTENUM(P, tbl) + RMLUILUA_INPUTENUM(Q, tbl) + RMLUILUA_INPUTENUM(R, tbl) + RMLUILUA_INPUTENUM(S, tbl) + RMLUILUA_INPUTENUM(T, tbl) + RMLUILUA_INPUTENUM(U, tbl) + RMLUILUA_INPUTENUM(V, tbl) + RMLUILUA_INPUTENUM(W, tbl) + RMLUILUA_INPUTENUM(X, tbl) + RMLUILUA_INPUTENUM(Y, tbl) + RMLUILUA_INPUTENUM(Z, tbl) + RMLUILUA_INPUTENUM(OEM_1, tbl) + RMLUILUA_INPUTENUM(OEM_PLUS, tbl) + RMLUILUA_INPUTENUM(OEM_COMMA, tbl) + RMLUILUA_INPUTENUM(OEM_MINUS, tbl) + RMLUILUA_INPUTENUM(OEM_PERIOD, tbl) + RMLUILUA_INPUTENUM(OEM_2, tbl) + RMLUILUA_INPUTENUM(OEM_3, tbl) + RMLUILUA_INPUTENUM(OEM_4, tbl) + RMLUILUA_INPUTENUM(OEM_5, tbl) + RMLUILUA_INPUTENUM(OEM_6, tbl) + RMLUILUA_INPUTENUM(OEM_7, tbl) + RMLUILUA_INPUTENUM(OEM_8, tbl) + RMLUILUA_INPUTENUM(OEM_102, tbl) + RMLUILUA_INPUTENUM(NUMPAD0, tbl) + RMLUILUA_INPUTENUM(NUMPAD1, tbl) + RMLUILUA_INPUTENUM(NUMPAD2, tbl) + RMLUILUA_INPUTENUM(NUMPAD3, tbl) + RMLUILUA_INPUTENUM(NUMPAD4, tbl) + RMLUILUA_INPUTENUM(NUMPAD5, tbl) + RMLUILUA_INPUTENUM(NUMPAD6, tbl) + RMLUILUA_INPUTENUM(NUMPAD7, tbl) + RMLUILUA_INPUTENUM(NUMPAD8, tbl) + RMLUILUA_INPUTENUM(NUMPAD9, tbl) + RMLUILUA_INPUTENUM(NUMPADENTER, tbl) + RMLUILUA_INPUTENUM(MULTIPLY, tbl) + RMLUILUA_INPUTENUM(ADD, tbl) + RMLUILUA_INPUTENUM(SEPARATOR, tbl) + RMLUILUA_INPUTENUM(SUBTRACT, tbl) + RMLUILUA_INPUTENUM(DECIMAL, tbl) + RMLUILUA_INPUTENUM(DIVIDE, tbl) + RMLUILUA_INPUTENUM(OEM_NEC_EQUAL, tbl) + RMLUILUA_INPUTENUM(BACK, tbl) + RMLUILUA_INPUTENUM(TAB, tbl) + RMLUILUA_INPUTENUM(CLEAR, tbl) + RMLUILUA_INPUTENUM(RETURN, tbl) + RMLUILUA_INPUTENUM(PAUSE, tbl) + RMLUILUA_INPUTENUM(CAPITAL, tbl) + RMLUILUA_INPUTENUM(KANA, tbl) + RMLUILUA_INPUTENUM(HANGUL, tbl) + RMLUILUA_INPUTENUM(JUNJA, tbl) + RMLUILUA_INPUTENUM(FINAL, tbl) + RMLUILUA_INPUTENUM(HANJA, tbl) + RMLUILUA_INPUTENUM(KANJI, tbl) + RMLUILUA_INPUTENUM(ESCAPE, tbl) + RMLUILUA_INPUTENUM(CONVERT, tbl) + RMLUILUA_INPUTENUM(NONCONVERT, tbl) + RMLUILUA_INPUTENUM(ACCEPT, tbl) + RMLUILUA_INPUTENUM(MODECHANGE, tbl) + RMLUILUA_INPUTENUM(PRIOR, tbl) + RMLUILUA_INPUTENUM(NEXT, tbl) + RMLUILUA_INPUTENUM(END, tbl) + RMLUILUA_INPUTENUM(HOME, tbl) + RMLUILUA_INPUTENUM(LEFT, tbl) + RMLUILUA_INPUTENUM(UP, tbl) + RMLUILUA_INPUTENUM(RIGHT, tbl) + RMLUILUA_INPUTENUM(DOWN, tbl) + RMLUILUA_INPUTENUM(SELECT, tbl) + RMLUILUA_INPUTENUM(PRINT, tbl) + RMLUILUA_INPUTENUM(EXECUTE, tbl) + RMLUILUA_INPUTENUM(SNAPSHOT, tbl) + RMLUILUA_INPUTENUM(INSERT, tbl) + RMLUILUA_INPUTENUM(DELETE, tbl) + RMLUILUA_INPUTENUM(HELP, tbl) + RMLUILUA_INPUTENUM(LWIN, tbl) + RMLUILUA_INPUTENUM(RWIN, tbl) + RMLUILUA_INPUTENUM(APPS, tbl) + RMLUILUA_INPUTENUM(POWER, tbl) + RMLUILUA_INPUTENUM(SLEEP, tbl) + RMLUILUA_INPUTENUM(WAKE, tbl) + RMLUILUA_INPUTENUM(F1, tbl) + RMLUILUA_INPUTENUM(F2, tbl) + RMLUILUA_INPUTENUM(F3, tbl) + RMLUILUA_INPUTENUM(F4, tbl) + RMLUILUA_INPUTENUM(F5, tbl) + RMLUILUA_INPUTENUM(F6, tbl) + RMLUILUA_INPUTENUM(F7, tbl) + RMLUILUA_INPUTENUM(F8, tbl) + RMLUILUA_INPUTENUM(F9, tbl) + RMLUILUA_INPUTENUM(F10, tbl) + RMLUILUA_INPUTENUM(F11, tbl) + RMLUILUA_INPUTENUM(F12, tbl) + RMLUILUA_INPUTENUM(F13, tbl) + RMLUILUA_INPUTENUM(F14, tbl) + RMLUILUA_INPUTENUM(F15, tbl) + RMLUILUA_INPUTENUM(F16, tbl) + RMLUILUA_INPUTENUM(F17, tbl) + RMLUILUA_INPUTENUM(F18, tbl) + RMLUILUA_INPUTENUM(F19, tbl) + RMLUILUA_INPUTENUM(F20, tbl) + RMLUILUA_INPUTENUM(F21, tbl) + RMLUILUA_INPUTENUM(F22, tbl) + RMLUILUA_INPUTENUM(F23, tbl) + RMLUILUA_INPUTENUM(F24, tbl) + RMLUILUA_INPUTENUM(NUMLOCK, tbl) + RMLUILUA_INPUTENUM(SCROLL, tbl) + RMLUILUA_INPUTENUM(OEM_FJ_JISHO, tbl) + RMLUILUA_INPUTENUM(OEM_FJ_MASSHOU, tbl) + RMLUILUA_INPUTENUM(OEM_FJ_TOUROKU, tbl) + RMLUILUA_INPUTENUM(OEM_FJ_LOYA, tbl) + RMLUILUA_INPUTENUM(OEM_FJ_ROYA, tbl) + RMLUILUA_INPUTENUM(LSHIFT, tbl) + RMLUILUA_INPUTENUM(RSHIFT, tbl) + RMLUILUA_INPUTENUM(LCONTROL, tbl) + RMLUILUA_INPUTENUM(RCONTROL, tbl) + RMLUILUA_INPUTENUM(LMENU, tbl) + RMLUILUA_INPUTENUM(RMENU, tbl) + RMLUILUA_INPUTENUM(BROWSER_BACK, tbl) + RMLUILUA_INPUTENUM(BROWSER_FORWARD, tbl) + RMLUILUA_INPUTENUM(BROWSER_REFRESH, tbl) + RMLUILUA_INPUTENUM(BROWSER_STOP, tbl) + RMLUILUA_INPUTENUM(BROWSER_SEARCH, tbl) + RMLUILUA_INPUTENUM(BROWSER_FAVORITES, tbl) + RMLUILUA_INPUTENUM(BROWSER_HOME, tbl) + RMLUILUA_INPUTENUM(VOLUME_MUTE, tbl) + RMLUILUA_INPUTENUM(VOLUME_DOWN, tbl) + RMLUILUA_INPUTENUM(VOLUME_UP, tbl) + RMLUILUA_INPUTENUM(MEDIA_NEXT_TRACK, tbl) + RMLUILUA_INPUTENUM(MEDIA_PREV_TRACK, tbl) + RMLUILUA_INPUTENUM(MEDIA_STOP, tbl) + RMLUILUA_INPUTENUM(MEDIA_PLAY_PAUSE, tbl) + RMLUILUA_INPUTENUM(LAUNCH_MAIL, tbl) + RMLUILUA_INPUTENUM(LAUNCH_MEDIA_SELECT, tbl) + RMLUILUA_INPUTENUM(LAUNCH_APP1, tbl) + RMLUILUA_INPUTENUM(LAUNCH_APP2, tbl) + RMLUILUA_INPUTENUM(OEM_AX, tbl) + RMLUILUA_INPUTENUM(ICO_HELP, tbl) + RMLUILUA_INPUTENUM(ICO_00, tbl) + RMLUILUA_INPUTENUM(PROCESSKEY, tbl) + RMLUILUA_INPUTENUM(ICO_CLEAR, tbl) + RMLUILUA_INPUTENUM(ATTN, tbl) + RMLUILUA_INPUTENUM(CRSEL, tbl) + RMLUILUA_INPUTENUM(EXSEL, tbl) + RMLUILUA_INPUTENUM(EREOF, tbl) + RMLUILUA_INPUTENUM(PLAY, tbl) + RMLUILUA_INPUTENUM(ZOOM, tbl) + RMLUILUA_INPUTENUM(PA1, tbl) + RMLUILUA_INPUTENUM(OEM_CLEAR, tbl) +} + +void LuaRmlUiEnumkey_modifier(lua_State* L) +{ + lua_newtable(L); + int tbl = lua_gettop(L); + RMLUILUA_INPUTMODIFIERENUM(CTRL, tbl) + RMLUILUA_INPUTMODIFIERENUM(SHIFT, tbl) + RMLUILUA_INPUTMODIFIERENUM(ALT, tbl) + RMLUILUA_INPUTMODIFIERENUM(META, tbl) + RMLUILUA_INPUTMODIFIERENUM(CAPSLOCK, tbl) + RMLUILUA_INPUTMODIFIERENUM(NUMLOCK, tbl) + RMLUILUA_INPUTMODIFIERENUM(SCROLLLOCK, tbl) +} + +RegType LuaRmlUiMethods[] = { + RMLUI_LUAMETHOD(LuaRmlUi, CreateContext), + RMLUI_LUAMETHOD(LuaRmlUi, LoadFontFace), + RMLUI_LUAMETHOD(LuaRmlUi, RegisterTag), + {nullptr, nullptr}, +}; + +luaL_Reg LuaRmlUiGetters[] = { + RMLUI_LUAGETTER(LuaRmlUi, contexts), + RMLUI_LUAGETTER(LuaRmlUi, key_identifier), + RMLUI_LUAGETTER(LuaRmlUi, key_modifier), + {nullptr, nullptr}, +}; + +luaL_Reg LuaRmlUiSetters[] = { + {nullptr, nullptr}, +}; + +RMLUI_LUATYPE_DEFINE(LuaRmlUi) +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/RmlUi.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/RmlUi.h new file mode 100644 index 00000000..b29bdda9 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/RmlUi.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include + +namespace Rml { +namespace Lua { + +class LuaRmlUi { +public: + // reference to the table defined in LuaRmlUiEnumkey_identifier + int key_identifier_ref; + // reference to the table defined in LuaRmlUiEnumkey_modifier + int key_modifier_ref; +}; + +void LuaRmlUiPushrmluiGlobal(lua_State* L); + +template <> +void ExtraInit(lua_State* L, int metatable_index); +int LuaRmlUiCreateContext(lua_State* L, LuaRmlUi* obj); +int LuaRmlUiLoadFontFace(lua_State* L, LuaRmlUi* obj); +int LuaRmlUiRegisterTag(lua_State* L, LuaRmlUi* obj); + +int LuaRmlUiGetAttrcontexts(lua_State* L); +int LuaRmlUiGetAttrkey_identifier(lua_State* L); +int LuaRmlUiGetAttrkey_modifier(lua_State* L); + +void LuaRmlUiEnumkey_identifier(lua_State* L); +void LuaRmlUiEnumkey_modifier(lua_State* L); + +extern RegType LuaRmlUiMethods[]; +extern luaL_Reg LuaRmlUiGetters[]; +extern luaL_Reg LuaRmlUiSetters[]; + +RMLUI_LUATYPE_DECLARE(LuaRmlUi) +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/RmlUiContextsProxy.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/RmlUiContextsProxy.cpp new file mode 100644 index 00000000..11b60c3c --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/RmlUiContextsProxy.cpp @@ -0,0 +1,59 @@ +#include "RmlUiContextsProxy.h" +#include "Pairs.h" +#include +#include + +namespace Rml { +namespace Lua { + +template <> +void ExtraInit(lua_State* L, int metatable_index) +{ + lua_pushcfunction(L, RmlUiContextsProxy__index); + lua_setfield(L, metatable_index, "__index"); + lua_pushcfunction(L, RmlUiContextsProxy__pairs); + lua_setfield(L, metatable_index, "__pairs"); +} + +int RmlUiContextsProxy__index(lua_State* L) +{ + /*the table obj and the missing key are currently on the stack(index 1 & 2) as defined by the Lua language*/ + int keytype = lua_type(L, 2); + if (keytype == LUA_TSTRING || keytype == LUA_TNUMBER) // only valid key types + { + RmlUiContextsProxy* obj = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(obj); + if (keytype == LUA_TSTRING) + { + const char* key = lua_tostring(L, 2); + LuaType::push(L, GetContext(key)); + } + else + { + int key = (int)luaL_checkinteger(L, 2); + LuaType::push(L, GetContext(key - 1)); + } + return 1; + } + else + return LuaType::index(L); +} + +int RmlUiContextsProxy__pairs(lua_State* L) +{ + return MakeIntPairs(L); +} + +RegType RmlUiContextsProxyMethods[] = { + {nullptr, nullptr}, +}; +luaL_Reg RmlUiContextsProxyGetters[] = { + {nullptr, nullptr}, +}; +luaL_Reg RmlUiContextsProxySetters[] = { + {nullptr, nullptr}, +}; + +RMLUI_LUATYPE_DEFINE(RmlUiContextsProxy) +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/RmlUiContextsProxy.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/RmlUiContextsProxy.h new file mode 100644 index 00000000..c60409a9 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/RmlUiContextsProxy.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +namespace Rml { +namespace Lua { +// where owner is the Element that we should look up information from +struct RmlUiContextsProxy { + void* nothing; +}; + +template <> +void ExtraInit(lua_State* L, int metatable_index); +int RmlUiContextsProxy__index(lua_State* L); +int RmlUiContextsProxy__pairs(lua_State* L); + +extern RegType RmlUiContextsProxyMethods[]; +extern luaL_Reg RmlUiContextsProxyGetters[]; +extern luaL_Reg RmlUiContextsProxySetters[]; + +RMLUI_LUATYPE_DECLARE(RmlUiContextsProxy) +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Utilities.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Utilities.cpp new file mode 100644 index 00000000..2248c9a0 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Utilities.cpp @@ -0,0 +1,104 @@ +#include +#include + +namespace Rml { +namespace Lua { + +#if LUA_VERSION_NUM < 502 +void lua_len(lua_State* L, int i) +{ + switch (lua_type(L, i)) + { + case LUA_TSTRING: lua_pushnumber(L, (lua_Number)lua_objlen(L, i)); break; + case LUA_TTABLE: + if (!luaL_callmeta(L, i, "__len")) + lua_pushnumber(L, (lua_Number)lua_objlen(L, i)); + break; + case LUA_TUSERDATA: + if (luaL_callmeta(L, i, "__len")) + break; + /* FALLTHROUGH */ + default: luaL_error(L, "attempt to get length of a %s value", lua_typename(L, lua_type(L, i))); + } +} + +lua_Integer luaL_len(lua_State* L, int i) +{ + lua_Integer res = 0; + int isnum = 0; + luaL_checkstack(L, 1, "not enough stack slots"); + lua_len(L, i); + res = lua_tointegerx(L, -1, &isnum); + lua_pop(L, 1); + if (!isnum) + luaL_error(L, "object length is not an integer"); + return res; +} +#endif + +void PushVariant(lua_State* L, const Variant* var) +{ + if (!var) + { + lua_pushnil(L); + return; + } + + switch (var->GetType()) + { + case Variant::BOOL: lua_pushboolean(L, var->Get()); break; + case Variant::BYTE: + case Variant::CHAR: + case Variant::INT: lua_pushinteger(L, var->Get()); break; + case Variant::INT64: lua_pushinteger(L, var->Get()); break; + case Variant::UINT: lua_pushinteger(L, var->Get()); break; + case Variant::UINT64: lua_pushinteger(L, var->Get()); break; + case Variant::FLOAT: + case Variant::DOUBLE: lua_pushnumber(L, var->Get()); break; + case Variant::COLOURB: LuaType::push(L, new Colourb(var->Get()), true); break; + case Variant::COLOURF: LuaType::push(L, new Colourf(var->Get()), true); break; + case Variant::STRING: + { + const String& s = var->GetReference(); + lua_pushlstring(L, s.c_str(), s.length()); + } + break; + case Variant::VECTOR2: + // according to Variant.inl, it is going to be a Vector2f + LuaType::push(L, new Vector2f(var->Get()), true); + break; + case Variant::VOIDPTR: lua_pushlightuserdata(L, var->Get()); break; + default: lua_pushnil(L); break; + } +} + +void GetVariant(lua_State* L, int index, Variant* variant) +{ + if (!variant) + return; + + switch (lua_type(L, index)) + { + case LUA_TBOOLEAN: *variant = (bool)lua_toboolean(L, index); break; + case LUA_TNUMBER: *variant = lua_tonumber(L, index); break; + case LUA_TSTRING: *variant = Rml::String(lua_tostring(L, index)); break; + case LUA_TLIGHTUSERDATA: *variant = lua_touserdata(L, index); break; + case LUA_TNIL: + default: // todo: support other types + *variant = Variant(); + break; + } +} + +void PushIndex(lua_State* L, int index) +{ + lua_pushinteger(L, index + 1); +} + +int GetIndex(lua_State* L, int index) +{ + return (int)luaL_checkinteger(L, index) - 1; +} + +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Vector2f.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Vector2f.cpp new file mode 100644 index 00000000..977c40c0 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Vector2f.cpp @@ -0,0 +1,209 @@ +#include "Vector2f.h" +#include + +namespace Rml { +namespace Lua { + +template <> +void ExtraInit(lua_State* L, int metatable_index) +{ + lua_pushcfunction(L, Vector2fnew); + lua_setfield(L, metatable_index - 1, "new"); + + lua_pushcfunction(L, Vector2f__mul); + lua_setfield(L, metatable_index, "__mul"); + + lua_pushcfunction(L, Vector2f__div); + lua_setfield(L, metatable_index, "__div"); + + lua_pushcfunction(L, Vector2f__add); + lua_setfield(L, metatable_index, "__add"); + + lua_pushcfunction(L, Vector2f__sub); + lua_setfield(L, metatable_index, "__sub"); + + lua_pushcfunction(L, Vector2f__eq); + lua_setfield(L, metatable_index, "__eq"); + + // stack is in the same state as it was before it entered this function + return; +} + +int Vector2fnew(lua_State* L) +{ + float x = (float)luaL_checknumber(L, 1); + float y = (float)luaL_checknumber(L, 2); + + Vector2f* vect = new Vector2f(x, y); + + LuaType::push(L, vect, true); // true means it will be deleted when it is garbage collected + return 1; +} + +int Vector2f__mul(lua_State* L) +{ + Vector2f* lhs = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(lhs); + float rhs = (float)luaL_checknumber(L, 2); + + Vector2f* res = new Vector2f(0.f, 0.f); + (*res) = (*lhs) * rhs; + + LuaType::push(L, res, true); + return 1; +} + +int Vector2f__div(lua_State* L) +{ + Vector2f* lhs = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(lhs); + float rhs = (float)luaL_checknumber(L, 2); + + Vector2f* res = new Vector2f(0.f, 0.f); + (*res) = (*lhs) / rhs; + + LuaType::push(L, res, true); + return 1; +} + +int Vector2f__add(lua_State* L) +{ + Vector2f* lhs = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(lhs); + Vector2f* rhs = LuaType::check(L, 2); + RMLUI_CHECK_OBJ(rhs); + + Vector2f* res = new Vector2f(0.f, 0.f); + (*res) = (*lhs) + (*rhs); + + LuaType::push(L, res, true); + return 1; +} + +int Vector2f__sub(lua_State* L) +{ + Vector2f* lhs = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(lhs); + Vector2f* rhs = LuaType::check(L, 2); + RMLUI_CHECK_OBJ(rhs); + + Vector2f* res = new Vector2f(0.f, 0.f); + (*res) = (*lhs) - (*rhs); + + LuaType::push(L, res, true); + return 1; +} + +int Vector2f__eq(lua_State* L) +{ + Vector2f* lhs = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(lhs); + Vector2f* rhs = LuaType::check(L, 2); + RMLUI_CHECK_OBJ(rhs); + + lua_pushboolean(L, (*lhs) == (*rhs) ? 1 : 0); + return 1; +} + +int Vector2fDotProduct(lua_State* L, Vector2f* obj) +{ + Vector2f* rhs = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(rhs); + + float res = obj->DotProduct(*rhs); + + lua_pushnumber(L, res); + return 1; +} + +int Vector2fNormalise(lua_State* L, Vector2f* obj) +{ + Vector2f* res = new Vector2f(); + (*res) = obj->Normalise(); + + LuaType::push(L, res, true); + return 1; +} + +int Vector2fRotate(lua_State* L, Vector2f* obj) +{ + float num = (float)luaL_checknumber(L, 1); + + Vector2f* res = new Vector2f(); + (*res) = obj->Rotate(num); + + LuaType::push(L, res, true); + return 1; +} + +int Vector2fGetAttrx(lua_State* L) +{ + Vector2f* self = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(self); + + lua_pushnumber(L, self->x); + return 1; +} + +int Vector2fGetAttry(lua_State* L) +{ + Vector2f* self = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(self); + + lua_pushnumber(L, self->y); + return 1; +} + +int Vector2fGetAttrmagnitude(lua_State* L) +{ + Vector2f* self = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(self); + + lua_pushnumber(L, self->Magnitude()); + return 1; +} + +int Vector2fSetAttrx(lua_State* L) +{ + Vector2f* self = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(self); + float value = (float)luaL_checknumber(L, 2); + + self->x = value; + return 0; +} + +int Vector2fSetAttry(lua_State* L) +{ + Vector2f* self = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(self); + float value = (float)luaL_checknumber(L, 2); + + self->y = value; + return 0; +} + +RegType Vector2fMethods[] = { + RMLUI_LUAMETHOD(Vector2f, DotProduct), + RMLUI_LUAMETHOD(Vector2f, Normalise), + RMLUI_LUAMETHOD(Vector2f, Rotate), + {nullptr, nullptr}, +}; + +luaL_Reg Vector2fGetters[] = { + RMLUI_LUAGETTER(Vector2f, x), + RMLUI_LUAGETTER(Vector2f, y), + RMLUI_LUAGETTER(Vector2f, magnitude), + {nullptr, nullptr}, +}; + +luaL_Reg Vector2fSetters[] = { + RMLUI_LUASETTER(Vector2f, x), + RMLUI_LUASETTER(Vector2f, y), + {nullptr, nullptr}, +}; + +RMLUI_LUATYPE_DEFINE(Vector2f) + +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Vector2f.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Vector2f.h new file mode 100644 index 00000000..ecd47d3a --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Vector2f.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include + +namespace Rml { +namespace Lua { +template <> +void ExtraInit(lua_State* L, int metatable_index); + +int Vector2fnew(lua_State* L); +int Vector2f__mul(lua_State* L); +int Vector2f__div(lua_State* L); +int Vector2f__add(lua_State* L); +int Vector2f__sub(lua_State* L); +int Vector2f__eq(lua_State* L); + +int Vector2fDotProduct(lua_State* L, Vector2f* obj); +int Vector2fNormalise(lua_State* L, Vector2f* obj); +int Vector2fRotate(lua_State* L, Vector2f* obj); + +int Vector2fGetAttrx(lua_State* L); +int Vector2fGetAttry(lua_State* L); +int Vector2fGetAttrmagnitude(lua_State* L); + +int Vector2fSetAttrx(lua_State* L); +int Vector2fSetAttry(lua_State* L); + +extern RegType Vector2fMethods[]; +extern luaL_Reg Vector2fGetters[]; +extern luaL_Reg Vector2fSetters[]; + +RMLUI_LUATYPE_DECLARE(Vector2f) +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Vector2i.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Vector2i.cpp new file mode 100644 index 00000000..aea935d7 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Vector2i.cpp @@ -0,0 +1,173 @@ +#include "Vector2i.h" + +namespace Rml { +namespace Lua { +template <> +void ExtraInit(lua_State* L, int metatable_index) +{ + lua_pushcfunction(L, Vector2inew); + lua_setfield(L, metatable_index - 1, "new"); + + lua_pushcfunction(L, Vector2i__mul); + lua_setfield(L, metatable_index, "__mul"); + + lua_pushcfunction(L, Vector2i__div); + lua_setfield(L, metatable_index, "__div"); + + lua_pushcfunction(L, Vector2i__add); + lua_setfield(L, metatable_index, "__add"); + + lua_pushcfunction(L, Vector2i__sub); + lua_setfield(L, metatable_index, "__sub"); + + lua_pushcfunction(L, Vector2i__eq); + lua_setfield(L, metatable_index, "__eq"); + + // stack is in the same state as it was before it entered this function + return; +} + +int Vector2inew(lua_State* L) +{ + int x = (int)luaL_checkinteger(L, 1); + int y = (int)luaL_checkinteger(L, 2); + + Vector2i* vect = new Vector2i(x, y); + + LuaType::push(L, vect, true); // true means it will be deleted when it is garbage collected + return 1; +} + +int Vector2i__mul(lua_State* L) +{ + Vector2i* lhs = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(lhs); + int rhs = (int)luaL_checkinteger(L, 2); + + Vector2i* res = new Vector2i(0, 0); + (*res) = (*lhs) * rhs; + + LuaType::push(L, res, true); + return 1; +} + +int Vector2i__div(lua_State* L) +{ + Vector2i* lhs = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(lhs); + int rhs = (int)luaL_checkinteger(L, 2); + + Vector2i* res = new Vector2i(0, 0); + (*res) = (*lhs) / rhs; + + LuaType::push(L, res, true); + return 1; +} + +int Vector2i__add(lua_State* L) +{ + Vector2i* lhs = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(lhs); + Vector2i* rhs = LuaType::check(L, 2); + RMLUI_CHECK_OBJ(rhs); + + Vector2i* res = new Vector2i(0, 0); + (*res) = (*lhs) + (*rhs); + + LuaType::push(L, res, true); + return 1; +} + +int Vector2i__sub(lua_State* L) +{ + Vector2i* lhs = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(lhs); + Vector2i* rhs = LuaType::check(L, 2); + RMLUI_CHECK_OBJ(rhs); + + Vector2i* res = new Vector2i(0, 0); + (*res) = (*lhs) - (*rhs); + + LuaType::push(L, res, true); + return 1; +} + +int Vector2i__eq(lua_State* L) +{ + Vector2i* lhs = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(lhs); + Vector2i* rhs = LuaType::check(L, 2); + RMLUI_CHECK_OBJ(rhs); + + lua_pushboolean(L, (*lhs) == (*rhs) ? 1 : 0); + return 1; +} + +int Vector2iGetAttrx(lua_State* L) +{ + Vector2i* self = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(self); + + lua_pushinteger(L, self->x); + return 1; +} + +int Vector2iGetAttry(lua_State* L) +{ + Vector2i* self = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(self); + + lua_pushinteger(L, self->y); + return 1; +} + +int Vector2iGetAttrmagnitude(lua_State* L) +{ + Vector2i* self = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(self); + + lua_pushnumber(L, self->Magnitude()); + return 1; +} + +int Vector2iSetAttrx(lua_State* L) +{ + Vector2i* self = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(self); + int value = (int)luaL_checkinteger(L, 2); + + self->x = value; + return 0; +} + +int Vector2iSetAttry(lua_State* L) +{ + Vector2i* self = LuaType::check(L, 1); + RMLUI_CHECK_OBJ(self); + int value = (int)luaL_checkinteger(L, 2); + + self->y = value; + return 0; +} + +RegType Vector2iMethods[] = { + {nullptr, nullptr}, +}; + +luaL_Reg Vector2iGetters[] = { + RMLUI_LUAGETTER(Vector2i, x), + RMLUI_LUAGETTER(Vector2i, y), + RMLUI_LUAGETTER(Vector2i, magnitude), + {nullptr, nullptr}, +}; + +luaL_Reg Vector2iSetters[] = { + RMLUI_LUASETTER(Vector2i, x), + RMLUI_LUASETTER(Vector2i, y), + {nullptr, nullptr}, +}; + +RMLUI_LUATYPE_DEFINE(Vector2i) + +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Vector2i.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Vector2i.h new file mode 100644 index 00000000..e5d9586d --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/Lua/Vector2i.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include +#include + +namespace Rml { +namespace Lua { +template <> +void ExtraInit(lua_State* L, int metatable_index); +int Vector2inew(lua_State* L); +int Vector2i__mul(lua_State* L); +int Vector2i__div(lua_State* L); +int Vector2i__add(lua_State* L); +int Vector2i__sub(lua_State* L); +int Vector2i__eq(lua_State* L); + +// getters +int Vector2iGetAttrx(lua_State* L); +int Vector2iGetAttry(lua_State* L); +int Vector2iGetAttrmagnitude(lua_State* L); + +// setters +int Vector2iSetAttrx(lua_State* L); +int Vector2iSetAttry(lua_State* L); + +extern RegType Vector2iMethods[]; +extern luaL_Reg Vector2iGetters[]; +extern luaL_Reg Vector2iSetters[]; + +RMLUI_LUATYPE_DECLARE(Vector2i) +} // namespace Lua +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/SVG/CMakeLists.txt b/Minecraft.Client/Windows64/Libs/RmlUi/Source/SVG/CMakeLists.txt new file mode 100644 index 00000000..091652c5 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/SVG/CMakeLists.txt @@ -0,0 +1,16 @@ +# This plugin targets rmlui_core directly and is incorporated into the core library when enabled. + +target_sources(rmlui_core PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/DecoratorSVG.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/DecoratorSVG.h" + "${CMAKE_CURRENT_SOURCE_DIR}/ElementSVG.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/SVGCache.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/SVGCache.h" + "${CMAKE_CURRENT_SOURCE_DIR}/SVGPlugin.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/SVGPlugin.h" + "${CMAKE_CURRENT_SOURCE_DIR}/XMLNodeHandlerSVG.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/XMLNodeHandlerSVG.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/SVG/ElementSVG.h" +) + +target_compile_definitions(rmlui_core PRIVATE "RMLUI_SVG_PLUGIN") diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/SVG/DecoratorSVG.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/SVG/DecoratorSVG.cpp new file mode 100644 index 00000000..69509b3c --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/SVG/DecoratorSVG.cpp @@ -0,0 +1,67 @@ +#include "DecoratorSVG.h" +#include "../../Include/RmlUi/Core/Element.h" +#include "../../Include/RmlUi/Core/ElementDocument.h" +#include "../../Include/RmlUi/Core/Geometry.h" +#include "../../Include/RmlUi/Core/PropertyDefinition.h" +#include "SVGCache.h" + +namespace Rml { +namespace SVG { + + DecoratorSVG::DecoratorSVG(const String& source, const bool crop_to_content) : source_path(source), crop_to_content(crop_to_content) + { + RMLUI_ASSERT(!source_path.empty()); + } + + DecoratorSVG::~DecoratorSVG() {} + + DecoratorDataHandle DecoratorSVG::GenerateElementData(Element* element, BoxArea paint_area) const + { + SharedPtr handle = SVGCache::GetHandle(source_path, source_path, SVGCache::File, element, crop_to_content, paint_area); + if (!handle) + return {}; + + Data* data = new Data{ + std::move(handle), + paint_area, + }; + + return reinterpret_cast(data); + } + + void DecoratorSVG::ReleaseElementData(DecoratorDataHandle element_data) const + { + Data* data = reinterpret_cast(element_data); + delete data; + } + + void DecoratorSVG::RenderElement(Element* element, DecoratorDataHandle element_data) const + { + Data* data = reinterpret_cast(element_data); + RMLUI_ASSERT(data && data->handle); + data->handle->geometry.Render(element->GetAbsoluteOffset(data->paint_area), data->handle->texture); + } + + DecoratorSVGInstancer::DecoratorSVGInstancer() + { + source_id = RegisterProperty("source", "").AddParser("string").GetId(); + crop_id = RegisterProperty("crop", "crop-none").AddParser("keyword", "crop-none, crop-to-content").GetId(); + RegisterShorthand("decorator", "source, crop", ShorthandType::FallThrough); + } + + DecoratorSVGInstancer::~DecoratorSVGInstancer() {} + + SharedPtr DecoratorSVGInstancer::InstanceDecorator(const String&, const PropertyDictionary& properties, + const DecoratorInstancerInterface&) + { + String source = properties.GetProperty(source_id)->Get(); + if (source.empty()) + return nullptr; + + const bool crop_to_content = properties.GetProperty(crop_id)->Get() != 0; + + return MakeShared(source, crop_to_content); + } + +} // namespace SVG +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/SVG/DecoratorSVG.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/SVG/DecoratorSVG.h new file mode 100644 index 00000000..e1f55650 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/SVG/DecoratorSVG.h @@ -0,0 +1,43 @@ +#pragma once + +#include "../../Include/RmlUi/Core/Decorator.h" + +namespace Rml { +namespace SVG { + + struct SVGData; + + class DecoratorSVG : public Decorator { + public: + DecoratorSVG(const String& source, const bool crop_to_content); + virtual ~DecoratorSVG(); + + DecoratorDataHandle GenerateElementData(Element* element, BoxArea paint_area) const override; + void ReleaseElementData(DecoratorDataHandle element_data) const override; + + void RenderElement(Element* element, DecoratorDataHandle element_data) const override; + + private: + struct Data { + SharedPtr handle; + BoxArea paint_area; + }; + + String source_path; + bool crop_to_content; + }; + + class DecoratorSVGInstancer : public DecoratorInstancer { + public: + DecoratorSVGInstancer(); + ~DecoratorSVGInstancer(); + + SharedPtr InstanceDecorator(const String&, const PropertyDictionary& properties, const DecoratorInstancerInterface&) override; + + private: + PropertyId source_id; + PropertyId crop_id; + }; + +} // namespace SVG +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/SVG/ElementSVG.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/SVG/ElementSVG.cpp new file mode 100644 index 00000000..846fa3a4 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/SVG/ElementSVG.cpp @@ -0,0 +1,143 @@ +#include "../../Include/RmlUi/SVG/ElementSVG.h" +#include "../../Include/RmlUi/Core/ElementUtilities.h" +#include "../../Include/RmlUi/Core/Geometry.h" +#include "../../Include/RmlUi/Core/PropertyIdSet.h" +#include "SVGCache.h" + +namespace Rml { + +unsigned long ElementSVG::internal_id_counter = 0; + +ElementSVG::ElementSVG(const String& tag) : Element(tag) {} +ElementSVG::~ElementSVG() +{ + handle.reset(); +} + +bool ElementSVG::GetIntrinsicDimensions(Vector2f& dimensions, float& ratio) +{ + EnsureSourceLoaded(); + + dimensions = handle ? handle->intrinsic_dimensions : Vector2f(0); + + if (HasAttribute("width")) + { + dimensions.x = GetAttribute("width", 0); + } + if (HasAttribute("height")) + { + dimensions.y = GetAttribute("height", 0); + } + + if (dimensions.y > 0) + ratio = dimensions.x / dimensions.y; + + dimensions *= ElementUtilities::GetDensityIndependentPixelRatio(this); + + return true; +} + +void ElementSVG::OnRender() +{ + EnsureSourceLoaded(); + if (handle) + handle->geometry.Render(GetAbsoluteOffset(BoxArea::Content), handle->texture); +} + +void ElementSVG::OnResize() +{ + svg_dirty = true; +} + +void ElementSVG::OnAttributeChange(const ElementAttributes& changed_attributes) +{ + Element::OnAttributeChange(changed_attributes); + + if (changed_attributes.count("src") || changed_attributes.count("crop-to-content")) + { + svg_dirty = true; + DirtyLayout(); + } + + if (changed_attributes.find("width") != changed_attributes.end() || changed_attributes.find("height") != changed_attributes.end()) + { + DirtyLayout(); + } +} + +void ElementSVG::OnPropertyChange(const PropertyIdSet& changed_properties) +{ + Element::OnPropertyChange(changed_properties); + + if (changed_properties.Contains(PropertyId::ImageColor) || changed_properties.Contains(PropertyId::Opacity)) + { + svg_dirty = true; + } +} + +void ElementSVG::GetInnerRML(String& content) const +{ + // If the SVG is from a file source don't add anything to the content string. + const auto source = GetAttribute("src", ""); + if (!source.empty()) + return; + + content += svg_data; +} + +void ElementSVG::SetInnerRML(const String& content) +{ + // If the SVG is from a file source don't set the svg xml data on the element. + const auto source = GetAttribute("src", ""); + if (!source.empty()) + return; + + // We use CreateString instead of std::to_string to avoid having to create an extra std::string and convert it to Rml::String in case clients use + // a non-std string. + if (!HasAttribute("rmlui-svgdata-id")) + SetAttribute("rmlui-svgdata-id", "svgdata:" + CreateString("%lu", internal_id_counter++)); + + svg_data = "" + content; + svg_dirty = true; + EnsureSourceLoaded(); +} + +void ElementSVG::EnsureSourceLoaded() +{ + if (!svg_dirty) + return; + + svg_dirty = false; + + const bool crop_to_content = HasAttribute("crop-to-content"); + const auto source = GetAttribute("src", ""); + if (source.empty()) + { + const auto source_id = GetAttribute("rmlui-svgdata-id", "svgdata:undefined"); + if (handle) + handle.reset(); // The old handle won't be re-used so clear it. + + // Build an svg wrapper tag, copying all but src attribute (expected attributes could be width, height, viewBox, etc.) + String svg_element_source = "())); + svg_element_source.append("\" "); + } + svg_element_source.append(">"); + svg_element_source.append(svg_data); + svg_element_source.append(""); + + handle = SVG::SVGCache::GetHandle(source_id, svg_element_source, SVG::SVGCache::Data, this, crop_to_content, BoxArea::Content); + } + else + { + handle = SVG::SVGCache::GetHandle(source, source, SVG::SVGCache::File, this, crop_to_content, BoxArea::Content); + } +} +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/SVG/SVGCache.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/SVG/SVGCache.cpp new file mode 100644 index 00000000..920e21c1 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/SVG/SVGCache.cpp @@ -0,0 +1,390 @@ +#include "SVGCache.h" +#include "../../Include/RmlUi/Core/CallbackTexture.h" +#include "../../Include/RmlUi/Core/ComputedValues.h" +#include "../../Include/RmlUi/Core/Core.h" +#include "../../Include/RmlUi/Core/Element.h" +#include "../../Include/RmlUi/Core/ElementDocument.h" +#include "../../Include/RmlUi/Core/FileInterface.h" +#include "../../Include/RmlUi/Core/Geometry.h" +#include "../../Include/RmlUi/Core/MeshUtilities.h" +#include "../../Include/RmlUi/Core/RenderManager.h" +#include "../../Include/RmlUi/Core/SystemInterface.h" +#include "../../Include/RmlUi/Core/Texture.h" +#include "../../Include/RmlUi/Core/Utilities.h" +#include "../Core/ControlledLifetimeResource.h" +#include +#include + +#ifdef RMLUI_SVG_DEBUG + #define RMLUI_SVG_DEBUG_LOG(...) Rml::Log::Message(Rml::Log::LT_DEBUG, __VA_ARGS__) +#else + #define RMLUI_SVG_DEBUG_LOG(...) +#endif + +namespace Rml { +namespace SVG { + struct SVGKey { + String source_id; + Vector2i dimensions; + bool crop_to_content; + ColourbPremultiplied colour; + + friend bool operator==(const SVGKey& lhs, const SVGKey& rhs) + { + return lhs.source_id == rhs.source_id && lhs.dimensions == rhs.dimensions && lhs.crop_to_content == rhs.crop_to_content && + lhs.colour == rhs.colour; + } + }; +} // namespace SVG +} // namespace Rml + +namespace std { +template <> +struct hash<::Rml::SVG::SVGKey> { + size_t operator()(const ::Rml::SVG::SVGKey& key) const noexcept + { + size_t hash = 0; + Rml::Utilities::HashCombine(hash, key.source_id); + Rml::Utilities::HashCombine(hash, key.dimensions.x); + Rml::Utilities::HashCombine(hash, key.dimensions.y); + Rml::Utilities::HashCombine(hash, key.crop_to_content); + static_assert(sizeof(uint32_t) == sizeof(key.colour), "Expecting color to be 4 bytes"); + Rml::Utilities::HashCombine(hash, *reinterpret_cast(&key.colour[0])); + return hash; + } +}; +} // namespace std + +namespace Rml { +namespace SVG { + + static SharedPtr GetHandle(RenderManager& render_manager, String source_id, const String& source, SVGCache::SourceType source_type, + Vector2i dimensions, bool crop_to_content, ColourbPremultiplied colour); + static void ReleaseHandle(SVGData* handle); + + struct SVGGeometry { + ColourbPremultiplied colour; + UniquePtr geometry; + }; + + struct SVGTexture { + Vector2i render_dimensions; + bool crop_to_content; + CallbackTexture texture; + // List of geometries using this texture, one entry for each unique color. + Vector geometries; + }; + + struct SVGDocument { + Vector2f intrinsic_dimensions; + UniquePtr svg_document; + // List of textures using this document, one entry for each unique render dimension plus meta-data. + Vector textures; + }; + + struct SVGCacheData { + // A list of SVG documents mapped by their path. Owns all geometry and textures needed for rendering. + UnorderedMap documents; + + // Handles are reference-counted lookup keys and views into the SVG document resources. Handles are responsible + // for cleaning up the resources in the documents, when nothing refers to them any longer. When a handle is + // destroyed, it also removes itself from the handle map. + StableUnorderedMap> handles; + }; + + static ControlledLifetimeResource svg_cache_data; + + SVGData::SVGData(Geometry& geometry, Texture texture, Vector2f intrinsic_dimensions, const SVGKey& cache_key) : + geometry(geometry), texture(texture), intrinsic_dimensions(intrinsic_dimensions), cache_key(cache_key) + {} + + SVGData::~SVGData() + { + ReleaseHandle(this); + } + + static Vector::iterator FindSVGTexture(SVGDocument& doc, Vector2i dimensions, bool crop_to_content) + { + return std::find_if(doc.textures.begin(), doc.textures.end(), + [&](const auto& entry) { return entry.render_dimensions == dimensions && entry.crop_to_content == crop_to_content; }); + } + + static Vector::iterator FindSVGGeometry(SVGTexture& per_size_data, const ColourbPremultiplied colour) + { + return std::find_if(per_size_data.geometries.begin(), per_size_data.geometries.end(), + [&](const SVGGeometry& data) { return data.colour == colour; }); + } + + static const String& GetSourceOr(const lunasvg::Document* svg_document, const String& default_value) + { + const auto& documents = svg_cache_data->documents; + auto it = std::find_if(documents.begin(), documents.end(), + [svg_document](const auto& pair) { return pair.second.svg_document.get() == svg_document; }); + if (it != documents.end()) + return it->first; + return default_value; + } + + static SharedPtr GetHandle(RenderManager& render_manager, String move_from_id, const String& source, SVGCache::SourceType source_type, + const Vector2i dimensions, const bool crop_to_content, const ColourbPremultiplied colour) + { + SVGKey key{std::move(move_from_id), dimensions, crop_to_content, colour}; + const String& source_id = key.source_id; + auto& documents = svg_cache_data->documents; + auto& handles = svg_cache_data->handles; + + const auto it_handle = handles.find(key); + if (it_handle != handles.cend()) + { + RMLUI_SVG_DEBUG_LOG("Found handle, reusing: %s, (%d, %d), %s, %#x", source_id.c_str(), dimensions.x, dimensions.y, + crop_to_content ? "crop_to_content" : "crop_none", *reinterpret_cast(&colour[0])); + SharedPtr result = it_handle->second.lock(); + RMLUI_ASSERTMSG(result, "Failed to lock handle in SVG cache"); + return result; + } + + RMLUI_SVG_DEBUG_LOG("Making new handle: %s, (%d, %d), %s, %#x", source_id.c_str(), dimensions.x, dimensions.y, + crop_to_content ? "crop_to_content" : "crop_none", *reinterpret_cast(&colour[0])); + + // Find or create a document + auto it_svg_document = documents.find(source_id); + if (it_svg_document == documents.cend()) + { + SVGDocument doc; + if (source_type == SVGCache::SourceType::File) + { + RMLUI_SVG_DEBUG_LOG("Loading SVG document from file %s", source.c_str()); + String svg_data; + if (source.empty() || !GetFileInterface()->LoadFile(source, svg_data)) + { + Log::Message(Rml::Log::Type::LT_WARNING, "Could not load SVG file %s", source.c_str()); + return {}; + } + + // We use a reset-release approach here in case clients use a non-std unique_ptr (lunasvg uses std::unique_ptr). We also use + // loadFromData(char*, size_t) instead of loadFromData(std::string) in case clients use a non-std string. + doc.svg_document.reset(lunasvg::Document::loadFromData(svg_data.data(), svg_data.size()).release()); + } + else + { + RMLUI_SVG_DEBUG_LOG("Loading SVG document from element %s contents", source_id.c_str()); + // We use a reset-release approach here in case clients use a non-std unique_ptr (lunasvg uses std::unique_ptr). We also use + // loadFromData(char*, size_t) instead of loadFromData(std::string) in case clients use a non-std string. + doc.svg_document.reset(lunasvg::Document::loadFromData(source.data(), source.size()).release()); + } + + if (!doc.svg_document) + { + Log::Message(Rml::Log::Type::LT_WARNING, "Could not load SVG data for item %s", source_id.c_str()); + return {}; + } + + doc.intrinsic_dimensions.x = Math::Max(float(doc.svg_document->width()), 1.0f); + doc.intrinsic_dimensions.y = Math::Max(float(doc.svg_document->height()), 1.0f); + + const auto it_inserted = documents.insert_or_assign(source_id, std::move(doc)); + RMLUI_ASSERT(it_inserted.second); + + it_svg_document = it_inserted.first; + } + + SVGDocument& doc = it_svg_document->second; + + Vector2f intrinsic_dimensions = doc.intrinsic_dimensions; + if (crop_to_content) + { + const lunasvg::Box smallest_fit = doc.svg_document->boundingBox(); + intrinsic_dimensions.x = static_cast(smallest_fit.w); + intrinsic_dimensions.y = static_cast(smallest_fit.h); + } + + // Find or create texture + auto it_size = FindSVGTexture(doc, dimensions, crop_to_content); + if (it_size == doc.textures.cend()) + { + RMLUI_SVG_DEBUG_LOG("Creating per-size data for (%d, %d), %s", dimensions.x, dimensions.y, + crop_to_content ? "crop_to_content" : "crop_none"); + SVGTexture svg_texture; + svg_texture.render_dimensions = dimensions; + svg_texture.crop_to_content = crop_to_content; + svg_texture.texture = {}; + + // Callback for generating texture. + auto texture_callback = [svg_document = doc.svg_document.get(), dimensions, crop_to_content]( + const CallbackTextureInterface& texture_interface) -> bool { + RMLUI_ASSERT(svg_document); + RMLUI_SVG_DEBUG_LOG("Generating texture: %s, (%d, %d), %s", GetSourceOr(svg_document, "").c_str(), dimensions.x, dimensions.y, + crop_to_content ? "crop_to_content" : "crop_none"); + + if (dimensions.x == 0 || dimensions.y == 0) + return false; + + lunasvg::Bitmap bitmap; + if (crop_to_content) + { + const lunasvg::Box smallest_fit = svg_document->boundingBox(); + + lunasvg::Matrix matrix(dimensions.x / svg_document->width(), 0, 0, dimensions.y / svg_document->height(), 0, 0); + matrix.scale(svg_document->width() / smallest_fit.w, svg_document->height() / smallest_fit.h); + matrix.translate(-smallest_fit.x, -smallest_fit.y); + + bitmap = lunasvg::Bitmap(dimensions.x, dimensions.y); + bitmap.clear(0x00000000); + svg_document->render(bitmap, matrix); + } + else + { + bitmap = svg_document->renderToBitmap(dimensions.x, dimensions.y); + } + + if (!bitmap.valid() || !bitmap.data()) + { + Log::Message(Rml::Log::Type::LT_WARNING, "Could not render SVG to bitmap: %s", GetSourceOr(svg_document, "").c_str()); + return false; + } + + // Swap red and blue channels, assuming LunaSVG v2.3.2 or newer, to convert to RmlUi's expected RGBA-ordering. + const size_t bitmap_byte_size = bitmap.width() * bitmap.height() * 4; + uint8_t* bitmap_data = bitmap.data(); + for (size_t i = 0; i < bitmap_byte_size; i += 4) + std::swap(bitmap_data[i], bitmap_data[i + 2]); + + if (!texture_interface.GenerateTexture({reinterpret_cast(bitmap.data()), bitmap_byte_size}, + Vector2i{bitmap.width(), bitmap.height()})) + { + Log::Message(Rml::Log::Type::LT_WARNING, "Could not generate texture for SVG: %s", GetSourceOr(svg_document, "").c_str()); + return false; + } + + return true; + }; + + svg_texture.texture = render_manager.MakeCallbackTexture(std::move(texture_callback)); + + doc.textures.push_back(std::move(svg_texture)); + it_size = std::prev(doc.textures.end()); + } + + // Construct and insert per-color geometry + SVGTexture& size_data = *it_size; + RMLUI_ASSERTMSG(FindSVGGeometry(size_data, colour) == size_data.geometries.end(), + "We found an existing color entry in the SVG document cache, this should have been found as a cache key map entry instead."); + SVGGeometry colour_data; + colour_data.colour = colour; + Mesh mesh; + MeshUtilities::GenerateQuad(mesh, Vector2f(0), Vector2f(size_data.render_dimensions), colour, Vector2f(0), Vector2f(1)); + colour_data.geometry = MakeUnique(render_manager.MakeGeometry(std::move(mesh))); + size_data.geometries.push_back(std::move(colour_data)); + + // Create and insert the handle + const auto iterator_inserted = handles.emplace(std::move(key), WeakPtr()); + RMLUI_ASSERTMSG(iterator_inserted.second, "Could not insert entry into the SVG cache handle map, duplicate key."); + const SVGKey& inserted_key = iterator_inserted.first->first; + WeakPtr& inserted_weak_data_pointer = iterator_inserted.first->second; + + auto svg_handle = MakeShared(*size_data.geometries.back().geometry.get(), size_data.texture, intrinsic_dimensions, inserted_key); + inserted_weak_data_pointer = svg_handle; + + return svg_handle; + } + + static void ReleaseHandle(SVGData* handle) + { + // There are no longer any users of the cache entry uniquely identified by the handle address. Start from the + // tip (i.e. per-color data) and remove that entry from its parent. Move up the cache ancestry and erase any + // entries that no longer have any children. + auto& documents = svg_cache_data->documents; + auto& handles = svg_cache_data->handles; + const SVGKey& key = handle->cache_key; + + auto it_handle = handles.find(key); + RMLUI_ASSERT(it_handle != handles.cend()); + + const auto it_document = documents.find(key.source_id); + RMLUI_ASSERT(it_document != documents.cend()); + SVGDocument& svg_document = it_document->second; + + RMLUI_SVG_DEBUG_LOG("Releasing handle: %s, (%d, %d), %s, %#x", key.path.c_str(), key.dimensions.x, key.dimensions.y, + key.crop_to_content ? "crop_to_content" : "crop_none", *reinterpret_cast(&key.colour[0])); + + auto it_texture = FindSVGTexture(svg_document, key.dimensions, key.crop_to_content); + RMLUI_ASSERT(it_texture != svg_document.textures.cend()); + SVGTexture& svg_texture = *it_texture; + + auto it_geometry = FindSVGGeometry(svg_texture, key.colour); + RMLUI_ASSERT(it_geometry != svg_texture.geometries.cend()); + + if (svg_texture.geometries.size() > 1) + { + RMLUI_SVG_DEBUG_LOG("Releasing handle from geometries, size: %zu", svg_texture.geometries.size()); + std::iter_swap(it_geometry, std::prev(svg_texture.geometries.end())); + svg_texture.geometries.pop_back(); + } + else if (svg_document.textures.size() > 1) + { + RMLUI_SVG_DEBUG_LOG("Releasing handle from textures, size: %zu", svg_document.textures.size()); + std::iter_swap(it_texture, std::prev(svg_document.textures.end())); + svg_document.textures.pop_back(); + } + else + { + RMLUI_SVG_DEBUG_LOG("Releasing document"); + documents.erase(it_document); + } + + handles.erase(it_handle); + +#ifdef RMLUI_DEBUG + size_t count_unique_entries = 0; + for (auto& document : documents) + { + RMLUI_ASSERT(!document.second.textures.empty()); + for (auto& size_data : document.second.textures) + { + RMLUI_ASSERT(!size_data.geometries.empty()); + count_unique_entries += size_data.geometries.size(); + } + } + RMLUI_ASSERT(count_unique_entries == handles.size()); +#endif + } + + void SVGCache::Initialize() + { + svg_cache_data.Initialize(); + } + + void SVGCache::Shutdown() + { + svg_cache_data.Shutdown(); + } + + SharedPtr SVGCache::GetHandle(const String& source_id, const String& source, SourceType source_type, Element* element, + const bool crop_to_content, const BoxArea area) + { + RenderManager* render_manager = element->GetRenderManager(); + if (!render_manager) + return {}; + + const ComputedValues& computed = element->GetComputedValues(); + const ColourbPremultiplied colour = computed.image_color().ToPremultiplied(computed.opacity()); + Vector2i dimensions(element->GetBox().GetSize(area).Round()); + if (dimensions.x == 0 || dimensions.y == 0) + dimensions = {0, 0}; + + if (source_type == File) + { + String path; + if (ElementDocument* document = element->GetOwnerDocument()) + { + const String document_source_url = StringUtilities::Replace(document->GetSourceURL(), '|', ':'); + GetSystemInterface()->JoinPath(path, document_source_url, source_id); + } + return Rml::SVG::GetHandle(*render_manager, path, path, source_type, dimensions, crop_to_content, colour); + } + + return Rml::SVG::GetHandle(*render_manager, source_id, source, source_type, dimensions, crop_to_content, colour); + } + +} // namespace SVG +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/SVG/SVGCache.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/SVG/SVGCache.h new file mode 100644 index 00000000..d0f5f71d --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/SVG/SVGCache.h @@ -0,0 +1,49 @@ +#pragma once + +#include "../../Include/RmlUi/Core/Texture.h" +#include "../../Include/RmlUi/Core/Types.h" + +namespace Rml { + +class Element; +class Geometry; + +namespace SVG { + + struct SVGKey; + + struct SVGData : NonCopyMoveable { + SVGData(Geometry& geometry, Texture texture, Vector2f intrinsic_dimensions, const SVGKey& cache_key); + ~SVGData(); + + Geometry& geometry; + Texture texture; + Vector2f intrinsic_dimensions; + const SVGKey& cache_key; + }; + + class SVGCache { + public: + enum SourceType { + File = 1, /// The source is a file path. + Data = 2 /// The source is raw SVG data. + }; + static void Initialize(); + static void Shutdown(); + + /// Returns a handle to SVG data matching the parameters - creates new data if none is found. + /// @param[in] source_id Key to be used for caching the SVG data. + /// @param[in] source SVG source data or Path to a file containing the SVG source data (type specified by source_type). + /// @param[in] source_type If source refers to a file or is the SVG source data itself. + /// @param[in] element Element for which to calculate the dimensions and color. + /// @param[in] crop_to_content Crop the rendered SVG to its contents. + /// @param[in] area The area of the element used to determine the SVG dimensions. + /// @return A handle to the SVG data, with automatic reference counting. + /// @note When changing color or dimensions of an SVG without changing the source file, it's best to get a + /// new handle before releasing the old one, to avoid unnecessarily reloading data. + static SharedPtr GetHandle(const String& source_id, const String& source, SourceType source_type, Element* element, + const bool crop_to_content, const BoxArea area); + }; + +} // namespace SVG +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/SVG/SVGPlugin.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/SVG/SVGPlugin.cpp new file mode 100644 index 00000000..e0467e82 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/SVG/SVGPlugin.cpp @@ -0,0 +1,51 @@ +#include "../../Include/RmlUi/Core/Core.h" +#include "../../Include/RmlUi/Core/ElementInstancer.h" +#include "../../Include/RmlUi/Core/Factory.h" +#include "../../Include/RmlUi/Core/Log.h" +#include "../../Include/RmlUi/Core/Plugin.h" +#include "../../Include/RmlUi/SVG/ElementSVG.h" +#include "DecoratorSVG.h" +#include "SVGCache.h" +#include "XMLNodeHandlerSVG.h" + +namespace Rml { +namespace SVG { + + class SVGPlugin : public Plugin { + public: + void OnInitialise() override + { + SVGCache::Initialize(); + + element_instancer = MakeUnique>(); + Factory::RegisterElementInstancer("svg", element_instancer.get()); + + decorator_instancer = MakeUnique(); + Factory::RegisterDecoratorInstancer("svg", decorator_instancer.get()); + + XMLParser::RegisterNodeHandler("svg", MakeShared()); + XMLParser::RegisterPersistentCDATATag("svg"); + + Log::Message(Log::LT_INFO, "SVG plugin initialised."); + } + + void OnShutdown() override + { + delete this; + SVGCache::Shutdown(); + } + + int GetEventClasses() override { return Plugin::EVT_BASIC; } + + private: + UniquePtr> element_instancer; + UniquePtr decorator_instancer; + }; + + void Initialise() + { + RegisterPlugin(new SVGPlugin); + } + +} // namespace SVG +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/SVG/SVGPlugin.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/SVG/SVGPlugin.h new file mode 100644 index 00000000..e9634cea --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/SVG/SVGPlugin.h @@ -0,0 +1,9 @@ +#pragma once + +namespace Rml { +namespace SVG { + + void Initialise(); + +} +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/SVG/XMLNodeHandlerSVG.cpp b/Minecraft.Client/Windows64/Libs/RmlUi/Source/SVG/XMLNodeHandlerSVG.cpp new file mode 100644 index 00000000..c8aa2f05 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/SVG/XMLNodeHandlerSVG.cpp @@ -0,0 +1,14 @@ +#include "XMLNodeHandlerSVG.h" + +namespace Rml { +namespace SVG { + bool XMLNodeHandlerSVG::ElementData(XMLParser* parser, const String& data, XMLDataType /*type*/) + { + auto* element = rmlui_dynamic_cast(parser->GetParseFrame()->element); + RMLUI_ASSERT(element); + if (element) + element->SetInnerRML(data); + return true; + } +} // namespace SVG +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Source/SVG/XMLNodeHandlerSVG.h b/Minecraft.Client/Windows64/Libs/RmlUi/Source/SVG/XMLNodeHandlerSVG.h new file mode 100644 index 00000000..3ab12f40 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Source/SVG/XMLNodeHandlerSVG.h @@ -0,0 +1,19 @@ +#pragma once + +#include "../../Include/RmlUi/Core/XMLParser.h" +#include "../../Include/RmlUi/SVG/ElementSVG.h" +#include "../Core/XMLNodeHandlerDefault.h" + +namespace Rml { +namespace SVG { + /** + Element Node handler that processes the SVG tag + */ + class XMLNodeHandlerSVG : public XMLNodeHandlerDefault { + public: + /// Called for element data + bool ElementData(XMLParser* parser, const String& data, XMLDataType type) override; + }; + +} // namespace SVG +} // namespace Rml diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/CMakeLists.txt b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/CMakeLists.txt new file mode 100644 index 00000000..f733dc5b --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/CMakeLists.txt @@ -0,0 +1,13 @@ +# Enable some extra testing code in the core library. +target_compile_definitions(rmlui_core PUBLIC RMLUI_TESTS_ENABLED) + +include("${PROJECT_SOURCE_DIR}/CMake/DependenciesForTests.cmake") + +if(EMSCRIPTEN) + # Add data for tests to emscripten targets + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -sLLD_REPORT_UNDEFINED") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --preload-file \"${CMAKE_CURRENT_SOURCE_DIR}/Data/@/Tests/Data/\"") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --preload-file \"${PROJECT_SOURCE_DIR}/Samples/basic/demo/data/@/basic/demo/data/\"") +endif() + +add_subdirectory("Source") diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/UnitTests/Specificity_Basic.rcss b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/UnitTests/Specificity_Basic.rcss new file mode 100644 index 00000000..a9ce2b50 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/UnitTests/Specificity_Basic.rcss @@ -0,0 +1,16 @@ +body { + display: block; + width: 10px; + height: 100px; + background: #ccc; +} + +.a { + width: 80px; +} +.b { + width: 90px; +} +.c { + width: 100px; +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/UnitTests/Specificity_MediaQuery.rcss b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/UnitTests/Specificity_MediaQuery.rcss new file mode 100644 index 00000000..1396f231 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/UnitTests/Specificity_MediaQuery.rcss @@ -0,0 +1,6 @@ +body { + display:block; + width: 100px; + height: 100px; + background: #ccc; +} diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/UnitTests/data-title.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/UnitTests/data-title.rml new file mode 100644 index 00000000..51085736 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/UnitTests/data-title.rml @@ -0,0 +1,11 @@ + diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/UnitTests/template_basic.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/UnitTests/template_basic.rml new file mode 100644 index 00000000..507da21d --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/UnitTests/template_basic.rml @@ -0,0 +1,7 @@ + diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/LICENSE.txt b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/LICENSE.txt new file mode 100644 index 00000000..851310aa --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/LICENSE.txt @@ -0,0 +1,42 @@ +This software or document includes material copied and modified from the CSS specifications [1], in particular from +examples there-in. In addition, parts of the CSS test suites [2] have been modified and included with this software. The +material is located in some of the *.rml files in the same directory as this file, and have been tagged with an XML +'link' node with a 'rel=source' attribute pointing to the exact place on the 'w3.org' or 'csswg.org' websites the +material has been copied and modified from. The license [3] of this material is restated below. Copyright © 2020 W3C® +(MIT, ERCIM, Keio, Beihang). + +[1] https://drafts.csswg.org/ +[2] https://www.w3.org/Style/CSS/Test/ +[3] https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + +-------------------------------------------- + +W3C SOFTWARE AND DOCUMENT NOTICE AND LICENSE +Status: This license takes effect 13 May, 2015. + +This work is being provided by the copyright holders under the following license. + +License +By obtaining and/or copying this work, you (the licensee) agree that you have read, understood, and will comply with the +following terms and conditions. + +Permission to copy, modify, and distribute this work, with or without modification, for any purpose and without fee or +royalty is hereby granted, provided that you include the following on ALL copies of the work or portions thereof, +including modifications: + +- The full text of this NOTICE in a location viewable to users of the redistributed or derivative work. +- Any pre-existing intellectual property disclaimers, notices, or terms and conditions. If none exist, the W3C Software + and Document Short Notice should be included. +- Notice of any changes or modifications, through a copyright statement on the new code or document such as "This + software or document includes material copied from or derived from [title and URI of the W3C document]. Copyright © + [YEAR] W3C® (MIT, ERCIM, Keio, Beihang)." + +Disclaimers THIS WORK IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS MAKE NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE +USE OF THE SOFTWARE OR DOCUMENT WILL NOT INFRINGE ANY THIRD PARTY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS. + +COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE +OF THE SOFTWARE OR DOCUMENT. + +The name and trademarks of copyright holders may NOT be used in advertising or publicity pertaining to the work without +specific, written prior permission. Title to copyright in this work will at all times remain with copyright holders. diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/acid1.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/acid1.rml new file mode 100644 index 00000000..1703990f --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/acid1.rml @@ -0,0 +1,175 @@ + + +display/box/float/clear test + + + + + + + + +
    +
    + toggle +
    +
    +
      +
    • + the way +
    • +
    • +

      + the world ends +

      +
      +

      + bang + +

      +

      + whimper + +

      +
      +
    • +
    • + i grow old +
    • +
    • + pluot? +
    • +
    +
    +
    + bar maids, +
    +
    +

    + sing to me, erbarme dich +

    +
    +
    +
    + + diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/border_radius.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/border_radius.rml new file mode 100644 index 00000000..f77f252a --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/border_radius.rml @@ -0,0 +1,55 @@ + + + Border-radius + + + + + + + +

    The following boxes should produce curved borders. There should be no overlapping triangles, and their backgrounds should not be visible outside their borders.

    +
    +
    +
    +

    The following text should wrap down to the
    next line
    and produce borders on each line.

    + + diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/box_sizing.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/box_sizing.rml new file mode 100644 index 00000000..3f17e508 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/box_sizing.rml @@ -0,0 +1,67 @@ + + + Box-sizing property + + + + + + + +

    Setting the 'box-sizing' property on elements using the same width and height.

    +
    +
    box-sizing: content-box
    +
    box-sizing: border-box
    +
    +
    +

    The following boxes should line up side-by-side.

    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    There should be no scrollbars, and no red visible.

    +
    +
    +
    +
    + + diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/clip_mask.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/clip_mask.rml new file mode 100644 index 00000000..91ae86b2 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/clip_mask.rml @@ -0,0 +1,72 @@ + + + Clip mask + + + + + + +
    + Duck aloft + + A + + X +
    +
    + Y + + C + + Some long text + + Z + + W +
    + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/color_interpolation.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/color_interpolation.rml new file mode 100644 index 00000000..38ef0c86 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/color_interpolation.rml @@ -0,0 +1,74 @@ + + + Color interpolation + + + + + + + + +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    + + diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/css1_clear.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/css1_clear.rml new file mode 100644 index 00000000..db236c5c --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/css1_clear.rml @@ -0,0 +1,50 @@ + + +CSS1 Test Suite: 5.5.26 clear + + + + + + + + +[Image] +

    +This text should be flowing past a tall orange rectangle on the left side of the browser window. +

    +
    +[Image] +

    +This paragraph should appear below the tall orange rectangle above and to the left, and not flow past it. +

    +
    +[Image] +

    +This paragraph should appear below the tall orange rectangle above and to the right, and not flow past it. +

    +
    +[Image] +[Image] +

    +This paragraph should appear below the two tall orange rectangles, and not flow between them. +

    +[Image] +[Image] +

    +This paragraph should be between both tall orange rectangles. +

    +
    + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/display_block_inside_inline.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/display_block_inside_inline.rml new file mode 100644 index 00000000..e41e5ebb --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/display_block_inside_inline.rml @@ -0,0 +1,51 @@ + + + CSS Display: Block box within inline + + + + + + + + + +
    Y
    +
    + Z +
    + + + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/document_position.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/document_position.rml new file mode 100644 index 00000000..2dc8f144 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/document_position.rml @@ -0,0 +1,72 @@ + + + Document position + + + + + + + +

    Document

    + +
    +

    Block

    + + + +
    + + + + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/drag.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/drag.rml new file mode 100644 index 00000000..af08df92 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/drag.rml @@ -0,0 +1,57 @@ + + + Drag + + + + + + + + + +
    +
    +
    +
    +
    +
    + + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/element_label.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/element_label.rml new file mode 100644 index 00000000..6ba569d9 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/element_label.rml @@ -0,0 +1,92 @@ + + + Element <label> + + + + + + + +

    + +

    +

    + +

    +

    + +

    +

    + +

    +

    + +

    + +

    +

    + +

    + + + + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/filter_backdrop_filter_box_shadow.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/filter_backdrop_filter_box_shadow.rml new file mode 100644 index 00000000..dd3fcc33 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/filter_backdrop_filter_box_shadow.rml @@ -0,0 +1,44 @@ + + + Filter, backdrop filter, and box shadow combinations + + + + + + +
    +
    + +
    +
    + +
    +
    + +
    +
    + + + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/filter_blur_area.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/filter_blur_area.rml new file mode 100644 index 00000000..6805f15e --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/filter_blur_area.rml @@ -0,0 +1,42 @@ + + + Filter: blur area + + + + + + + + + + + diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/filter_blur_orb.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/filter_blur_orb.rml new file mode 100644 index 00000000..34637826 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/filter_blur_orb.rml @@ -0,0 +1,44 @@ + + + Filter: blur orb + + + + + + + + + + diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/filter_drop_shadow.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/filter_drop_shadow.rml new file mode 100644 index 00000000..004a3366 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/filter_drop_shadow.rml @@ -0,0 +1,29 @@ + + + Filter: drop-shadow + + + + + + +
    + + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/filter_overflow.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/filter_overflow.rml new file mode 100644 index 00000000..40c2bdd8 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/filter_overflow.rml @@ -0,0 +1,35 @@ + + + Filter: overflowing content + + + + + + + + +
    +
    +
    + + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/filter_with_backdrop.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/filter_with_backdrop.rml new file mode 100644 index 00000000..e0422f93 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/filter_with_backdrop.rml @@ -0,0 +1,113 @@ + + + Filter: backdrop + + + + + + + +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    + +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    + +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    + +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    + + + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/flex_01.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/flex_01.rml new file mode 100644 index 00000000..72841dcc --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/flex_01.rml @@ -0,0 +1,71 @@ + + + Flex 01 - Basic flexbox layout + + + + + + + + +
    +

    Header

    +
    +
    +
    +

    First article

    +

    Etiam libero lorem, lacinia non augue lobortis, tincidunt consequat justo. Sed id enim tempor, feugiat tortor id, rhoncus enim. Quisque pretium neque eu felis tincidunt fringilla. Mauris laoreet enim neque, iaculis cursus lorem mollis sed. Nulla pretium euismod nulla sed convallis. Curabitur in tempus sem. Phasellus suscipit vitae nulla nec ultricies.

    +
    +
    +

    Second article

    +

    Ut volutpat, odio et facilisis molestie, lacus elit euismod enim, et tempor lacus sapien finibus ipsum. Aliquam erat volutpat. Nullam risus turpis, hendrerit ac fermentum in, dapibus non risus.

    +
    +
    +

    Third article

    +

    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed aliquet commodo nisi, id cursus enim eleifend vitae. Praesent turpis lorem, commodo id tempus sit amet, faucibus et libero. Aliquam malesuada ultrices leo, ut molestie tortor posuere sit amet. Proin vitae tortor a sem consequat gravida. Maecenas sed egestas dolor.

    +

    In gravida ligula in turpis molestie varius. Ut sed velit id tellus aliquet aliquet. Nulla et leo tellus. Ut a convallis dolor, eu rutrum enim. Nam vitae ultrices dui. Aliquam semper eros ut ultrices rutrum.

    +
    +
    + + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/flex_02.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/flex_02.rml new file mode 100644 index 00000000..7b06c995 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/flex_02.rml @@ -0,0 +1,73 @@ + + + + Flex 02 - Various features + + + + + +
    +
    Abs
    +
    1
    +
    2
    +
    3
    +
    + +
    +

    Abs
    +
    1
    +
    2
    +
    3
    +
    + +
    +
    1
    +
    2
    +
    3
    +
    + +
    +
    1
    +
    2 LONG_OVERFLOWING_WORD
    +
    3
    +
    + + + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/flex_03_scroll.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/flex_03_scroll.rml new file mode 100644 index 00000000..42feabe0 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/flex_03_scroll.rml @@ -0,0 +1,73 @@ + + + Flex 03 - Scrolling container + + + + + + + +overflow: scroll +
    +
    Hello
    +
    big world!
    +
    LOOOOOOOOOOOOOOOOOOOOONG
    +
    +overflow: auto +
    +
    Hello
    +
    big world!
    +
    LOOOOOOOOOOOOOOOOOOOOONG
    +
    +overflow: auto - only vertical overflow +
    +
    Hello
    +
    big world!
    +
    LONG
    +
    +overflow: auto - only horizontal overflow +
    +
    Hello
    +
    big
    +
    LOOOOOOOOOOOOOOOOOOOOONG
    +
    +overflow: visible +
    +
    Hello
    +
    big world!
    +
    LOOOOOOOOOOOOOOOOOOOOONG
    +
    + + + diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/flex_04.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/flex_04.rml new file mode 100644 index 00000000..bdae30be --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/flex_04.rml @@ -0,0 +1,113 @@ + + + Flex 04 - Flex shorthand + + + + + + + + +

    flex: default value (0 1 auto)

    +
    +

    flex-wrap: wrap

    +
    +
    Hello
    +
    big world!
    +
    LOOOOOOOOOOOOOOOOOOOOONG
    +
    +

    flex-wrap: nowrap

    +
    +
    Hello
    +
    big world!
    +
    LOOOOOOOOOOOOOOOOOOOOONG
    +
    +
    +

    flex: auto (1 1 auto)

    +
    +

    flex-wrap: wrap

    +
    +
    Hello
    +
    big world!
    +
    LOOOOOOOOOOOOOOOOOOOOONG
    +
    +

    flex-wrap: nowrap

    +
    +
    Hello
    +
    big world!
    +
    LOOOOOOOOOOOOOOOOOOOOONG
    +
    +
    +

    flex: none (0 0 auto)

    +
    +

    flex-wrap: wrap

    +
    +
    Hello
    +
    big world!
    +
    LOOOOOOOOOOOOOOOOOOOOONG
    +
    +

    flex-wrap: nowrap

    +
    +
    Hello
    +
    big world!
    +
    LOOOOOOOOOOOOOOOOOOOOONG
    +
    +
    +

    flex: 1 (1 1 0)

    +
    +

    flex-wrap: wrap

    +
    +
    Hello
    +
    big world!
    +
    LOOOOOOOOOOOOOOOOOOOOONG
    +
    +

    flex-wrap: nowrap

    +
    +
    Hello
    +
    big world!
    +
    LOOOOOOOOOOOOOOOOOOOOONG
    +
    +
    + + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/flex_05.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/flex_05.rml new file mode 100644 index 00000000..efdba20d --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/flex_05.rml @@ -0,0 +1,97 @@ + + + Flex 05 - Height constraints + + + + + + + + +

    height, rows

    +
    +
    Hello
    world!
    More
    Text
    More Text
    +
    Hello world!
    More Text
    +
    +
    +
    Hello world!
    More Text
    +
    +

    height, columns

    +
    +
    Hello
    world!
    More
    Text
    More Text
    +
    Hello world!
    More Text
    +
    +
    +
    Hello
    world!
    More
    Text
    +
    +

    min-height, rows

    +
    +
    Hello world!
    +
    +
    +
    Hello world!
    +
    +

    min-height, columns

    +
    +
    Hello world!
    +
    +
    +
    Hello world!
    +
    +

    max-height, rows

    +
    +
    Hello
    world!
    More
    Text
    +
    +
    +
    Hello
    world!
    More
    Text
    +
    +

    max-height, columns

    +
    +
    Hello
    world!
    More
    Text
    More
    Text
    +
    Hello
    world!
    More
    Text
    +
    +
    +
    Hello
    world!
    More
    Text
    +
    + + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/flex_06.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/flex_06.rml new file mode 100644 index 00000000..dc92b930 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/flex_06.rml @@ -0,0 +1,217 @@ + + + Flex 06 - Player list + + + + + + + + + +Player List + +
    +
    +
    Server: Europe #56763
    +
    Players: 99
    +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    IDPlayer TagPingScore
    10tagPlayer name55 ms108
    11tagPlayer name55 ms108
    12tagPlayer name55 ms108
    13tagPlayer name55 ms108
    14tagPlayer name55 ms108
    15tagPlayer name55 ms108
    16tagPlayer name55 ms108
    17tagPlayer name55 ms108
    18tagPlayer name55 ms108
    19tagPlayer name55 ms108
    20tagPlayer name55 ms108
    +
    + + +
    + + + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/flex_absolutely_positioned.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/flex_absolutely_positioned.rml new file mode 100644 index 00000000..fa213513 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/flex_absolutely_positioned.rml @@ -0,0 +1,50 @@ + + + Flex - Absolutely positioned + + + + + + +
    +
    1
    +
    2
    +
    3
    +
    +
    +
    +
    1
    +
    2
    +
    3
    +
    +
    +
    +
    1
    +
    2
    +
    3
    +
    + + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/flex_direction.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/flex_direction.rml new file mode 100644 index 00000000..a5a81eab --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/flex_direction.rml @@ -0,0 +1,71 @@ + + + + CSS Test: flex flow direction + + + + + + + + +

    flex-direction:row

    +
    +
    1
    +
    2
    +
    3
    +
    + +

    flex-direction:row-reverse

    +
    +
    1
    +
    2
    +
    3
    +
    + +

    flex-direction:column

    +
    +
    1
    +
    2
    +
    3
    +
    + +

    flex-direction:column-reverse

    +
    +
    1
    +
    2
    +
    3
    +
    + + + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/flex_inline.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/flex_inline.rml new file mode 100644 index 00000000..18144d57 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/flex_inline.rml @@ -0,0 +1,71 @@ + + + Inline flexboxes + + + + + + + +

    +Can we +

    +
    A
    +
    B
    +
    C
    +
    +play +
    +
    A
    +
    B
    +
    C
    +
    +Tetris +
    +
    A
    +
    B
    +
    C
    +
    + +with flexboxes +
    +
    A
    +
    B
    +
    C
    +
    +? + +

    + + + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/flex_nested.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/flex_nested.rml new file mode 100644 index 00000000..46b19a09 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/flex_nested.rml @@ -0,0 +1,46 @@ + + + Flex - Nested flex boxes + + + + + + + + + +
    +
    +
    1
    +
    2
    +
    3
    +
    +
    +
    1
    +
    2
    +
    3
    +
    +
    + + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/flex_shrink_to_fit_01.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/flex_shrink_to_fit_01.rml new file mode 100644 index 00000000..a34e1d8c --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/flex_shrink_to_fit_01.rml @@ -0,0 +1,88 @@ + + + Flex - Shrink-to-fit 01 + + + + + + + + +
    + Before +
    +
    Flex
    +
    + After +
    + +
    + Before +
    +
    Inline-flex
    +
    + After +
    + +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/flex_shrink_to_fit_02.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/flex_shrink_to_fit_02.rml new file mode 100644 index 00000000..6ddf5236 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/flex_shrink_to_fit_02.rml @@ -0,0 +1,72 @@ + + + Flex - Shrink-to-fit 02 + + + + + + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/flex_wrap_column_reverse.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/flex_wrap_column_reverse.rml new file mode 100644 index 00000000..be90fabe --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/flex_wrap_column_reverse.rml @@ -0,0 +1,63 @@ + + + + CSS Test: flex container multiline wrapping in column-reverse direction + + + + + + + + + + +
    +

    1-3

    +

    1-2

    +

    1-1

    +

    2-2

    +

    2-1

    +

    3-1

    +
    + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/float_basic.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/float_basic.rml new file mode 100644 index 00000000..fb2f3c41 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/float_basic.rml @@ -0,0 +1,51 @@ + + + Floats, block formatting contexts + + + + + + + +

    This test makes assumptions about the font properties, that is, it depends on the size of the layed-out text.

    +
    +

    Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod. +

    float: left
    + This is the first word after the float and should flow next to the float.

    +

    This paragraph should flow next to the float.

    + +
    +

    Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod. +

    float: left
    + This is the first word after the float and should flow next to the float.

    +

    This paragraph should be below the float as it clears the float.

    + +
    +

    Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod. +

    float: left
    + This is the first word after the float and should flow next to the float.

    +

    This paragraph establishes a new block formatting context. This paragraph should therefore be placed below the preceding float to avoid overlapping it, but may be placed adjacent to the float if there is sufficient space.

    +
    + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/float_excluded_by_flow_root.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/float_excluded_by_flow_root.rml new file mode 100644 index 00000000..ba3eb347 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/float_excluded_by_flow_root.rml @@ -0,0 +1,58 @@ + + + Floats excluded by new block formatting context + + + + + + + + + +
    +
    Try to resize this outer float
    +

    Normal text

    +
    +
    +
    Try to resize this outer float
    +

    display: flow-root

    +
    + + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/float_overflow.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/float_overflow.rml new file mode 100644 index 00000000..ce74bbff --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/float_overflow.rml @@ -0,0 +1,48 @@ + + + Floats: overflow + + + + + + + +
    +
    float: left
    +

    The float to the left should extend past the background of the containing '.box' element.

    +
    +
    +
    +
    float: left
    +

    Using 'overflow: auto' on the containing '.box' element. This should establish a new block formatting context (thereby resolving all floats) so that the background wraps around the entire float.

    +
    +
    +
    +
    float: left
    +

    Using 'display: flow-root' on the containing '.box' element. This also establishes a new block formatting context, and should be equivalent to the previous box.

    +
    + + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/float_placement.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/float_placement.rml new file mode 100644 index 00000000..261f6449 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/float_placement.rml @@ -0,0 +1,59 @@ + + + Floats: placement + + + + + + + + +
    In consectetur, neque dignissim tincidunt dapibus, leo metus molestie erat, eu pharetra velit nunc in ipsum. Donec aliquet malesuada iaculis. Quisque maximus urna in congue placerat. In hac habitasse platea dictumst. Mauris a fringilla augue. Donec posuere ante at orci efficitur porttitor vitae ac tellus. Nunc nec sapien orci. Sed rhoncus et dui sed sagittis. Nullam venenatis quis elit vel mattis. In mattis erat et blandit aliquam. Aliquam erat volutpat.
    + +
    + +
    Float
    In consectetur, neque dignissim tincidunt dapibus, leo metus molestie erat, eu pharetra velit nunc in ipsum. Donec aliquet malesuada iaculis. Quisque maximus urna in congue placerat. In hac habitasse platea dictumst. Mauris a fringilla augue. Donec posuere ante at orci efficitur porttitor vitae ac tellus. Nunc nec sapien orci. Sed rhoncus et dui sed sagittis. Nullam venenatis quis elit vel mattis. In mattis erat et blandit aliquam. Aliquam erat volutpat.
    + +
    + +
    Float
    In consectetur, neque dignissim tincidunt dapibus, leo metus molestie erat, eu pharetra velit nunc in ipsum. Donec aliquet malesuada iaculis. Quisque maximus urna in congue placerat. In hac habitasse platea dictumst. Mauris a fringilla augue. Donec posuere ante at orci efficitur porttitor vitae ac tellus. Nunc nec sapien orci. Sed rhoncus et dui sed sagittis. Nullam venenatis quis elit vel mattis. In mattis erat et blandit aliquam. Aliquam erat volutpat.
    + +
    + +
    In consectetur, neque
    Float
    dignissim tincidunt dapibus, leo metus molestie erat, eu pharetra velit nunc in ipsum. Donec aliquet malesuada iaculis . Quisque maximus urna in congue placerat. In hac habitasse platea dictumst. Mauris a fringilla augue. Donec posuere ante at orci efficitur porttitor vitae ac tellus. Nunc nec sapien orci. Sed rhoncus et dui sed sagittis. Nullam venenatis quis elit vel mattis. In mattis erat et blandit aliquam. Aliquam erat volutpat.
    + +
    + +
    In consectetur, neque dignissim tincidunt dapibus, leo metus
    Float
    molestie erat, eu pharetra velit nunc in ipsum. Donec aliquet malesuada iaculis. Quisque maximus urna in congue placerat. In hac habitasse platea dictumst. Mauris a fringilla augue. Donec posuere ante at orci efficitur porttitor vitae ac tellus. Nunc nec sapien orci. Sed rhoncus et dui sed sagittis. Nullam venenatis quis elit vel mattis. In mattis erat et blandit aliquam. Aliquam erat volutpat.
    + +
    + +
    In consectetur, neque dignissim tincidunt dapibus, leo metus molestie erat, eu pharetra velit nunc in ipsum. Donec aliquet malesuada iaculis. Quisque maximus urna in congue
    Float
    placerat. In hac habitasse platea dictumst. Mauris a fringilla augue. Donec posuere ante at orci efficitur porttitor vitae ac tellus. Nunc nec sapien orci. Sed rhoncus et dui sed sagittis. Nullam venenatis quis elit vel mattis. In mattis erat et blandit aliquam. Aliquam erat volutpat.
    + + + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/float_text_wrap.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/float_text_wrap.rml new file mode 100644 index 00000000..bc877c74 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/float_text_wrap.rml @@ -0,0 +1,42 @@ + + + Text wrapping around floats + + + + + + + +
    +
    Try to resize this outer float
    +

    AA B CCC

    +
    + + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/fractional-dimensions-01.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/fractional-dimensions-01.rml new file mode 100644 index 00000000..528bd98e --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/fractional-dimensions-01.rml @@ -0,0 +1,65 @@ + + + + Fractional dimensions 01 + + + + + +

    Borders should be placed edge-to-edge, and there should be no red.

    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/fractional-dimensions-02.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/fractional-dimensions-02.rml new file mode 100644 index 00000000..b3be0dcb --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/fractional-dimensions-02.rml @@ -0,0 +1,67 @@ + + + + Fractional dimensions 02 + + + + + + + + + + + + +

    You should see no red below, just a green square with a green border.

    + + + + + + + + + + + + + + + + + + + + + + + + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/fractional-dimensions-03.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/fractional-dimensions-03.rml new file mode 100644 index 00000000..4f90f792 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/fractional-dimensions-03.rml @@ -0,0 +1,69 @@ + + + + Fractional dimensions 03 - Flexbox + + + + +

    Borders should be placed edge-to-edge, and there should be no red.

    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/gap-001.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/gap-001.rml new file mode 100644 index 00000000..e05ee17e --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/gap-001.rml @@ -0,0 +1,35 @@ + + + + CSS Flexbox Test: gap - horizontal + + + + + + + + + + +

    Test passes if there is a green vertical line between boxes.

    +
    +
    +
    +
    +
    + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/gap-002.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/gap-002.rml new file mode 100644 index 00000000..990b03ac --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/gap-002.rml @@ -0,0 +1,35 @@ + + + + CSS Flexbox Test: gap - vertical + + + + + + + + +

    Test passes if there is a green horizontal line between boxes.

    +
    +
    +
    +
    +
    +
    + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/gap-003.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/gap-003.rml new file mode 100644 index 00000000..f3e5aade --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/gap-003.rml @@ -0,0 +1,37 @@ + + + + CSS Flexbox Test: gap - rows and columns + + + + + + + + + + +

    Test passes if there are green lines between boxes.

    +
    +
    +
    +
    +
    +
    + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/gap-004.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/gap-004.rml new file mode 100644 index 00000000..c6fdc644 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/gap-004.rml @@ -0,0 +1,35 @@ + + + + CSS Flexbox Test: gap - intrinsic horizontal + + + + + + + + + + +

    Test passes if there are green lines between boxes.

    +
    +
    Black Panther
    +
    Wonder Woman
    +
    Storm
    +
    Flash
    +
    + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/gap-005.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/gap-005.rml new file mode 100644 index 00000000..8d61448e --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/gap-005.rml @@ -0,0 +1,35 @@ + + + + CSS Flexbox Test: gap - intrinsic vertical + + + + + + + + + + +

    Test passes if there are green lines between boxes.

    +
    +
    Black Panther
    +
    Wonder Woman
    +
    Storm
    +
    Flash
    +
    + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/gap-006.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/gap-006.rml new file mode 100644 index 00000000..1183afea --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/gap-006.rml @@ -0,0 +1,50 @@ + + + + CSS Flexbox Test: gap - wrap horizontal + + + + + + + + + + +

    Test passes if there are green lines between boxes.

    +
    +
    Black Panther
    +
    Wonder Woman
    +
    Storm
    +
    Flash
    +
    + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/gap-007.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/gap-007.rml new file mode 100644 index 00000000..9e08e917 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/gap-007.rml @@ -0,0 +1,38 @@ + + + + CSS Flexbox Test: gap - wrap vertical + + + + + + + + + + +

    Test passes if there are green lines between boxes.

    +
    +
    Black Panther
    +
    Wonder Woman
    +
    Storm
    +
    Flash
    +
    + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/gap-008.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/gap-008.rml new file mode 100644 index 00000000..12de80d1 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/gap-008.rml @@ -0,0 +1,40 @@ + + + + CSS Flexbox Test: gap - row and column gap + + + + + + + + + + +

    + Test passes if there are 40px horizontal green lines and 20px vertical green lines between + boxes. +

    +
    +
    +
    +
    +
    +
    + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/gap-009.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/gap-009.rml new file mode 100644 index 00000000..0d1a1936 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/gap-009.rml @@ -0,0 +1,39 @@ + + + + CSS Flexbox Test: gap - mixed units + + + + + + + + + + +

    + Test passes if there are 50% horizontal green line and 1rem vertical green line between boxes. +

    +
    +
    +
    +
    +
    +
    + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/handle_target_anchor.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/handle_target_anchor.rml new file mode 100644 index 00000000..392c2a8d --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/handle_target_anchor.rml @@ -0,0 +1,91 @@ + + + Handle anchor positioning + + + + + + + + +

    Document

    + +
    +

    Anchored top-left

    +

    Edge margin: -50%

    + + +
    + +
    +

    Anchored all sides

    +

    Edge margin: 50dp

    + + +
    + +
    +

    Anchored bottom-right

    +

    Edge margin: 0px

    + + +
    + + + + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/image_ratio.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/image_ratio.rml new file mode 100644 index 00000000..5d1cbbd6 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/image_ratio.rml @@ -0,0 +1,40 @@ + + + Image Ratio Sizing - Attribute + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    AttributeCSS
    Original size (square 100x100):
    no width/height set
    Set height to 50, width auto, expect half sized square:
    height 50 only, attribute
    Set width to 50, height auto, expect half sized square:
    width 50 only, attribute
    Set width to 50 and height to 100 (via attributes), expect rectangular:
    height 100, width 50, attribute
    + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/ink_overflow_bounding_box.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/ink_overflow_bounding_box.rml new file mode 100644 index 00000000..b892c1df --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/ink_overflow_bounding_box.rml @@ -0,0 +1,47 @@ + + + Ink overflow bounding box + + + + + + + +
    Hello, do you feel the funk?
    +
    Hello, do you feel the funk?
    +
    Hello, do you feel the funk?
    + + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/inline_block.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/inline_block.rml new file mode 100644 index 00000000..d2bae83c --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/inline_block.rml @@ -0,0 +1,46 @@ + + + Inline-block + + + + + + + + +
    The following text
    is an inline-block
    and should flow naturally.
    +
    +
    Left filler text
    meets an inline-block
    with a line-break
    right filler text.
    +
    +
    Left filler text.
    An inline-block.

    A paragraph

    Filler text.
    Right filler text.
    +
    +
    Left filler text.
    An inline-block.
    Float right
    Float left
    Filler text.
    Right filler text.
    +
    +
    Nesting inline-blocks: +
    A
    B
    C
    +
    D
    E
    F
    +
    + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/inline_formatting_01.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/inline_formatting_01.rml new file mode 100644 index 00000000..38fa65dd --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/inline_formatting_01.rml @@ -0,0 +1,55 @@ + + + Inline formatting 01 + + + + + + + + +
    In consectetur, neque dignissim tincidunt dapibus, leo metus molestie erat, eu pharetra velit nunc in ipsum. Donec aliquet malesuada iaculis. Quisque maximus urna in congue placerat. In hac habitasse platea dictumst. Mauris a fringilla augue. Donec posuere ante at orci efficitur porttitor vitae ac tellus.
    Nunc nec sapien orci. Sed rhoncus et dui sed sagittis. Nullam venenatis quis elit vel mattis. In mattis erat et blandit aliquam. Aliquam erat volutpat.
    +
    +
    In consectetur, neque dignissim tincidunt dapibus, leo metus molestie erat, eu pharetra velit nunc in ipsum. Donec aliquet malesuada iaculis. Quisque maximus urna in congue placerat. In hac habitasse platea dictumst. Mauris a fringilla augue. Donec posuere ante at orci efficitur porttitor vitae ac tellus.
    Nunc nec sapien orci. Sed rhoncus et dui sed sagittis. Nullam  venenatis quis elit vel mattis. In mattis erat et blandit aliquam. Aliquam erat volutpat.
    +
    +
    In consectetur, neque dignissim tincidunt dapibus, leo metus molestie erat, eu pharetra velit nunc in ipsum. Donec aliquet malesuada iaculis. Quisque maximus urna in congue placerat. In hac habitasse platea dictumst. Mauris a fringilla augue. Donec posuere ante at orci efficitur porttitor vitae ac tellus.
    Nunc nec sapien orci. Sed rhoncus et dui sed sagittis. Nullam  venenatis quis elit vel mattis. In mattis erat et blandit aliquam. Aliquam erat volutpat.
    +
    +
    In consectetur, neque dignissim tincidunt dapibus, leo metus molestie erat, eu pharetra velit nunc in ipsum. Donec aliquet malesuada iaculis. Quisque maximus urna in congue placerat. In hac habitasse platea dictumst. Mauris a fringilla augue. Donec posuere ante at orci efficitur porttitor vitae ac tellus.
    Nunc nec sapien orci. Sed rhoncus et dui sed sagittis. Nullam  venenatis quis elit vel mattis. In mattis erat et blandit aliquam. Aliquam erat volutpat.
    + + + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/inline_formatting_02.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/inline_formatting_02.rml new file mode 100644 index 00000000..ac41a8d0 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/inline_formatting_02.rml @@ -0,0 +1,27 @@ + + + Inline formatting 02 + + + + + + + + +A B C D + + + + diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/inline_formatting_03.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/inline_formatting_03.rml new file mode 100644 index 00000000..9cef6aeb --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/inline_formatting_03.rml @@ -0,0 +1,31 @@ + + + Inline formatting 03 + + + + + + + + +
    In consecteturA metus Quisque maximus urna in congue placerat. In hac habitasse platea dictumst. Mauris a fringilla augue.
    + + + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/inline_formatting_04.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/inline_formatting_04.rml new file mode 100644 index 00000000..b4b5c228 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/inline_formatting_04.rml @@ -0,0 +1,66 @@ + + + Inline formatting 04 + + + + + + + + +
    + + + Before Inner After + + +
    + + + + +
    Y
    +
    + Z +
    +
    + +
    + + +
    + Z +
    + +
    + + +
    Y
    + Z +
    + + + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/inline_formatting_05.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/inline_formatting_05.rml new file mode 100644 index 00000000..8d5889ba --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/inline_formatting_05.rml @@ -0,0 +1,58 @@ + + + Inline formatting 05 + + + + + + + + +

    vertical-align: baseline

    +
    + A BCdEF G +
    + +

    vertical-align: middle

    +
    + A BCdEF G +
    + +

    vertical-align: text-top

    +
    + A BCdEF G +
    + + + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/inline_formatting_06.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/inline_formatting_06.rml new file mode 100644 index 00000000..e6ee00f7 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/inline_formatting_06.rml @@ -0,0 +1,73 @@ + + + Inline formatting 06 + + + + + + + + +

    Normally, the baseline of an inline-block is set to its last line, nicely aligning with its neighboring text.

    +
    + X
    Start Game

    + X
    High Scores

    + X
    Options
    +
    + +

    However, after making the inline-block a scroll container (such as by setting a non-visible overflow value), it is instead aligned to its bottom edge. Due to the line's text descent, there is additional spacing appearing below the box.

    +
    +

    overflow: auto;

    + X
    Start Game

    + X
    High Scores

    + X
    Options
    +
    + +

    We can manually adjust the vertical alignment to make sure we remove that additional space. However, it won't perfectly line up the baselines anymore.

    +
    +

    overflow: auto;
    vertical-align: -12px;

    + X
    Start Game

    + X
    High Scores

    + X
    Options
    +
    + +

    Instead of manually adjusting the vertical alignment, we can use 'center' for perfect centering. Note that baselines will still not line up.

    +
    +

    overflow: auto;
    vertical-align: center;

    + X
    Start Game

    + X
    High Scores

    + X
    Options
    +
    + + + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/inline_formatting_07.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/inline_formatting_07.rml new file mode 100644 index 00000000..612abd40 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/inline_formatting_07.rml @@ -0,0 +1,55 @@ + + + Inline formatting 07 + + + + + + + +
    Big
    +
    + First line
    + Second line ABC DEF +
    X
    Y
    + GHI JKL MNO PQRS TUV +
    + +
    + +
    Big
    +
    + First line
    + Second line +
    X
    Y
    Z
    + ABC DEF GHI JKL MNO PQRS TUV +
    + +
    + +
    Big
    +
    + First line
    + Second line ABC DEF GHI JKL MNO PQRS TUV
    X
    Y
    Z
    + ABC DEF GHI JKL MNO PQRS TUV ABC DEF GHI JKL MNO PQRS TUV ABC DEF GHI JKL MNO PQRS TUV
    Z
    +
    + + + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/inline_formatting_aligned_subtree.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/inline_formatting_aligned_subtree.rml new file mode 100644 index 00000000..c63cbc63 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/inline_formatting_aligned_subtree.rml @@ -0,0 +1,114 @@ + + + Inline formatting - Aligned subtree + + + + + + + +
    + A + + B + C + D + + + E + F + G + +
    +
    + + A + B + + C + + D + E + + + + F +
    +
    + + A + + B + + C + + D + E + + + + + F +
    +
    + + A + + B + + C + + D + E + + + + + F +
    +
    + The following text should wrap down to the
    next line
    and produce borders on each line. +
    + + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/issue_143_overflow_in_absolutely_positioned_input.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/issue_143_overflow_in_absolutely_positioned_input.rml new file mode 100644 index 00000000..14201d89 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/issue_143_overflow_in_absolutely_positioned_input.rml @@ -0,0 +1,32 @@ + + + Overflow in absolutely positioned input + + + + + + + + +

    +
    hover here
    + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/issue_452_scrollbar_resets.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/issue_452_scrollbar_resets.rml new file mode 100644 index 00000000..f1f65029 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/issue_452_scrollbar_resets.rml @@ -0,0 +1,119 @@ + + + Layout update and scrollbar resets + + + + + + + + + + +
    +
    + + + +
    +
    +

    elem1

    +
    +
    +

    elem2

    +
    +
    +

    elem3

    +
    +
    +

    elem4

    +
    +
    +

    elem5

    +
    +
    +

    elem6

    +
    +
    +
    +
    +
    +
    +
    +
    +

    elem1

    +
    +
    +

    elem2

    +
    +
    +

    elem3

    +
    +
    +

    elem4

    +
    +
    +

    elem5

    +
    +
    +

    elem6

    +
    +
    +
    +
    + + + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/letter_spacing.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/letter_spacing.rml new file mode 100644 index 00000000..63d8e4ff --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/letter_spacing.rml @@ -0,0 +1,44 @@ + + + 'letter-spacing' property + + + + + + + +

    normal

    In consectetur, neque dignissim tincidunt dapibus, leo metus molestie erat.

    +

    1px

    In consectetur, neque dignissim tincidunt dapibus, leo metus molestie erat.

    +

    -1px

    In consectetur, neque dignissim tincidunt dapibus, leo metus molestie erat.

    +

    5x

    In consectetur, neque dignissim tincidunt dapibus, leo metus molestie erat.

    +

    0.4em (5.6px)

    In consectetur, neque dignissim tincidunt dapibus, leo metus molestie erat.

    +

    -5px

    In consectetur, neque dignissim tincidunt dapibus, leo metus molestie erat.

    +

    20px

    In consectetur, neque dignissim tincidunt dapibus, leo metus molestie erat.

    +

    -20px

    In consectetur, neque dignissim tincidunt dapibus, leo metus molestie erat.

    + +
    +

    20px (left-aligned)

    abc

    +

    20px (centered)

    abc

    +

    20px (right-aligned)

    abc

    + +
    +

    letter-spacing on inner element

    abbc

    + + + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/mask_image.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/mask_image.rml new file mode 100644 index 00000000..5871192f --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/mask_image.rml @@ -0,0 +1,68 @@ + + + Mask image + + + + + + + +
    + I RmlUi +
    +
    +
    + I RmlUi +
    +
    + I RmlUi +
    +
    + I RmlUi +
    +
    + I RmlUi +
    + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/media_query_01.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/media_query_01.rml new file mode 100644 index 00000000..b31f302f --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/media_query_01.rml @@ -0,0 +1,100 @@ + + + Media query: Collapsing menu + + + + + + + + + Home + Blog + About + Contact + + +

    Try reducing the width of the window.

    + +
    diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/media_query_02.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/media_query_02.rml new file mode 100644 index 00000000..8eeec0ec --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/media_query_02.rml @@ -0,0 +1,58 @@ + + + Media query: Sprite sheets + + + + + + + + + + diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/navigation_01.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/navigation_01.rml new file mode 100644 index 00000000..cb435b1b --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/navigation_01.rml @@ -0,0 +1,163 @@ + + + Spatial navigation 01 + + + + + + + + + +
    +
    +
    +
    +
    + +
    +
    +
    + +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +

    +

    +

    +

    +
    +
    +
    +

    +

    +

    +
    +
    +
    +

    +

    +

    +
    +
    +
    +

    +

    +

    +
    +
    +
    +
    +

    +

    +

    +

    +
    +
    +

    +

    +

    +

    +
    +
    +
    +
    +

    +

    +

    +

    +
    + + + diff --git a/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/navigation_02.rml b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/navigation_02.rml new file mode 100644 index 00000000..67869817 --- /dev/null +++ b/Minecraft.Client/Windows64/Libs/RmlUi/Tests/Data/VisualTests/navigation_02.rml @@ -0,0 +1,111 @@ + + + Spatial navigation 02 + + + + + + + + + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    +

    +

    +

    +
    +

    +

    +

    +
    +
    +

    +

    +

    +

    +
    +

    +

    +

    +
    +
    +
    +

    + This is a nonsensical document, but syntactically valid HTML 4.0. All 100%-conformant CSS1 agents should be able to render the document elements above this paragraph indistinguishably (to the pixel) from this + reference rendering, + (except font rasterization and form widgets). All discrepancies should be traceable to CSS1 implementation shortcomings. Once you have finished evaluating this test, you can return to the parent page. +

    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 z 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¬ga!@^6rz!4~kHfBT7j{nuZ&&;Rfx`}jBBVITP1 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%0Dtc%w$^rQBU>{|HwV? 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 yo=^Rp^E0cSP5lh?^Ql5VCwW%-nf2>mT + + game + + + + + +

    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 z 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¬ga!@^6rz!4~kHfBT7j{nuZ&&;Rfx`}jBBVITP1 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%0Dtc%w$^rQBU>{|HwV? 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 yo=^Rp^E0cSP5lh?^Ql5VCwW%-nf2>mT + + game + + + + + + +