From 9012986f9dd6284c8dd7e85b2170a0fcfc4facf4 Mon Sep 17 00:00:00 2001 From: Vladislav Khorev Date: Sun, 2 Mar 2025 18:53:27 +0300 Subject: [PATCH] Added loading --- AudioPlayerAsync.h | 5 +- Environment.cpp | 2 + Environment.h | 2 + Game.cpp | 30 ++++- GameObjectManager.cpp | 267 +++++++++++++++++++++++++----------------- GameObjectManager.h | 23 ++-- RenderSystem.cpp | 51 ++++++-- RenderSystem.h | 2 + loading.bmp | Bin 0 -> 786486 bytes 9 files changed, 249 insertions(+), 133 deletions(-) create mode 100644 loading.bmp diff --git a/AudioPlayerAsync.h b/AudioPlayerAsync.h index 9f29cef..a31e9de 100644 --- a/AudioPlayerAsync.h +++ b/AudioPlayerAsync.h @@ -25,6 +25,8 @@ public: stop = true; } + std::thread worker; + private: std::unique_ptr audioPlayer; //std::mutex audioPlayerMutex; @@ -35,9 +37,6 @@ private: std::string latestSoundName; std::string latestMusicName; - - - std::thread worker; std::mutex mtx; std::condition_variable cv; std::queue> taskQueue; diff --git a/Environment.cpp b/Environment.cpp index 2ccd2fd..b7abe4f 100644 --- a/Environment.cpp +++ b/Environment.cpp @@ -40,4 +40,6 @@ bool Environment::showMouse = false; bool Environment::exitGameLoop = false; +bool Environment::gameIsLoading = true; + } // namespace ZL diff --git a/Environment.h b/Environment.h index 1a52c35..728900e 100644 --- a/Environment.h +++ b/Environment.h @@ -44,6 +44,8 @@ public: static bool showMouse; static bool exitGameLoop; + static bool gameIsLoading; + }; diff --git a/Game.cpp b/Game.cpp index 5e21dce..504f90a 100755 --- a/Game.cpp +++ b/Game.cpp @@ -72,6 +72,24 @@ void Game::drawScene() { } void Game::processTickCount() { + + if (Environment::gameIsLoading) + { + if (gameObjects.loadingFunctions.size() != 0) + { + bool result = gameObjects.loadingFunctions.begin()->operator()(); + if (result) + { + gameObjects.loadingFunctions.erase(gameObjects.loadingFunctions.begin()); + } + } + else + { + Environment::gameIsLoading = false; + } + return; + } + if (lastTickCount == 0) { lastTickCount = SDL_GetTicks64(); return; @@ -105,9 +123,19 @@ void Game::update() { SDL_Event event; while (SDL_PollEvent(&event)) { if (event.type == SDL_QUIT) { + if (gameObjects.loadingThread.joinable()) + { + gameObjects.loadingThread.join(); + } + gameObjects.audioPlayerAsync.exit(); Environment::exitGameLoop = true; + + } + + if (!Environment::gameIsLoading) + { + gameObjects.handleEvent(event); } - gameObjects.handleEvent(event); } render(); diff --git a/GameObjectManager.cpp b/GameObjectManager.cpp index 9a79790..314c95d 100644 --- a/GameObjectManager.cpp +++ b/GameObjectManager.cpp @@ -10,122 +10,166 @@ namespace ZL { const float GameObjectManager::INVENTORY_ICON_SIZE = 64.0f; const float GameObjectManager::INVENTORY_MARGIN = 10.0f; -void GameObjectManager::initialize() { +void GameObjectManager::initializeLoadingScreen() +{ + loadingScreenTexturePtr = std::make_shared(CreateTextureDataFromBmp24("./loading.bmp")); - current_room_index = 0; - objects_in_inventory = 0; - - coneTexturePtr = std::make_shared(CreateTextureDataFromBmp24("./conus.bmp")); - - // Load models - colorCubeMesh = CreateCube3D(5.0); - colorCubeMeshMutable.data = CreateCube3D(5.0); - colorCubeMeshMutable.RefreshVBO(); - - testObjMesh = LoadFromObjFile("./chair_01.obj"); - testObjMesh.Scale(10); - testObjMesh.SwapZandY(); - testObjMeshMutable.data = testObjMesh; - testObjMeshMutable.RefreshVBO(); - - //textMesh = ZL::LoadFromTextFile("./textures/mesh_first_room.txt"); - textMesh = ZL::LoadFromTextFile("./oneroom001.txt"); - textMesh.Scale(10); - textMesh.SwapZandY(); - textMesh.RotateByMatrix(QuatToMatrix(QuatFromRotateAroundX(M_PI * 0.5))); - textMesh.Move(Vector3f{0, 93, 0}); - - coneMesh = ZL::LoadFromTextFile("./cone001.txt"); // Add ZL:: namespace - coneMesh.Scale(200); - - - textMeshMutable.AssignFrom(textMesh); - textMeshMutable.RefreshVBO(); - coneMeshMutable.AssignFrom(coneMesh); - coneMeshMutable.RefreshVBO(); - - // Load bone animations - //bx.LoadFromFile("./violetta001.txt"); - violaIdleModel.LoadFromFile("./idleviola001.txt"); - violaWalkModel.LoadFromFile("./walkviolla001.txt"); - // Create active object - - ActiveObject ao1; - ao1.name = "book"; - ao1.activeObjectMesh = ZL::LoadFromTextFile("./book001.txt"); // Add ZL:: namespace - ao1.activeObjectMesh.Scale(4); - ao1.activeObjectMeshMutable.AssignFrom(ao1.activeObjectMesh); - ao1.activeObjectMeshMutable.RefreshVBO(); - ao1.objectPos = Vector3f{50, 0, -300}; - ao1.activeObjectTexturePtr = std::make_shared(CreateTextureDataFromBmp24("./book03.bmp")); - ao1.activeObjectScreenTexturePtr = std::make_shared(CreateTextureDataFromBmp24("./aoscreen01.bmp")); - ao1.activeObjectScreenMesh = CreateRect2D({ 0.f, 0.f }, { 64.f, 64.f }, 0.5); - ao1.activeObjectScreenMeshMutable.AssignFrom(ao1.activeObjectScreenMesh); - ao1.activeObjectScreenMeshMutable.RefreshVBO(); - - /* - ActiveObject ao2; - ao2.name = "superchair001"; - ao2.activeObjectMesh = ZL::LoadFromTextFile("./superchair001.txt"); // Add ZL:: namespace - ao2.activeObjectMesh.Scale(400); - ao2.activeObjectMesh.SwapZandY(); - ao2.activeObjectMeshMutable.AssignFrom(ao2.activeObjectMesh); - ao2.activeObjectMeshMutable.RefreshVBO(); - ao2.objectPos = Vector3f{ 0, 0, 0 }; - ao2.activeObjectTexturePtr = std::make_shared(CreateTextureDataFromBmp24("./chair_01_Base_Color.bmp")); - - ao2.activeObjectScreenTexturePtr = std::make_shared(CreateTextureDataFromBmp24("./aoscreen01.bmp")); - ao2.activeObjectScreenMesh = CreateRect2D({ 0.f, 0.f }, { 64.f, 64.f }, 0.5); - ao2.activeObjectScreenMeshMutable.AssignFrom(ao2.activeObjectScreenMesh); - ao2.activeObjectScreenMeshMutable.RefreshVBO(); - */ - - - - Room room_1; - room_1.roomTexture = std::make_shared(CreateTextureDataFromBmp24("./Material_Base_color_1001.bmp")); - room_1.objects.push_back(ao1); - room_1.sound_name = "Symphony No.6 (1st movement).ogg"; - room_1.roomLogic = createRoom1Logic(); - rooms.push_back(room_1); - aoMgr.addActiveObject(ao1); - - Room room_2; - room_2.roomTexture = std::make_shared(CreateTextureDataFromBmp24("./background.bmp")); - room_2.sound_name = "Symphony No.6 (1st movement).ogg"; - room_2.roomLogic = createRoom2Logic(); - rooms.push_back(room_2); - - activeObjects = rooms[current_room_index].objects; - - // Initialize audio - /* - audioPlayer = std::make_unique(); - if (audioPlayer) { - audioPlayer->playMusic(rooms[current_room_index].sound_name); - }*/ - audioPlayerAsync.resetAsync(); - audioPlayerAsync.playMusicAsync(rooms[current_room_index].sound_name); - - // Initialize inventory - inventoryIconMesh = CreateRect2D( - {0.0f, 40.0f}, - {INVENTORY_ICON_SIZE/2, INVENTORY_ICON_SIZE/2}, + loadingScreenMesh = CreateRect2D( + { Environment::width / 2.f, Environment::height / 2.f }, + { Environment::width / 2.f, Environment::height / 2.f }, 0.5f ); - inventoryIconMeshMutable.AssignFrom(inventoryIconMesh); - inventoryIconMeshMutable.RefreshVBO(); + loadingScreenMeshMutable.AssignFrom(loadingScreenMesh); + loadingScreenMeshMutable.RefreshVBO(); +} - roomTexturePtr = rooms[current_room_index].roomTexture; +void GameObjectManager::initialize() { - AddItemToInventory("book1", std::make_shared(CreateTextureDataFromBmp24("./Kitchen_ceramics.bmp")), objects_in_inventory+1); - objects_in_inventory++; - AddItemToInventory("book2", std::make_shared(CreateTextureDataFromBmp24("./Kitchen_ceramics.bmp")), objects_in_inventory+1); - objects_in_inventory++; + initializeLoadingScreen(); + + std::function loadingFunction1 = [this]() + { + + current_room_index = 0; + objects_in_inventory = 0; + + coneTexturePtr = std::make_shared(CreateTextureDataFromBmp24("./conus.bmp")); + + // Load models + /* + colorCubeMesh = CreateCube3D(5.0); + colorCubeMeshMutable.data = CreateCube3D(5.0); + colorCubeMeshMutable.RefreshVBO(); + */ + return true; + }; - //SDL_ShowCursor(SDL_DISABLE); - SDL_SetRelativeMouseMode(SDL_TRUE); + loadingThread = std::thread([this]() { + + textMesh = ZL::LoadFromTextFile("./oneroom001.txt"); + violaIdleModel.LoadFromFile("./idleviola001.txt"); + violaWalkModel.LoadFromFile("./walkviolla001.txt"); + sideThreadLoadingCompleted = true; + }); + + std::function loadingFunction2 = [this]() + { + return sideThreadLoadingCompleted; + }; + + std::function loadingFunction3 = [this]() + { + + /* + testObjMesh = LoadFromObjFile("./chair_01.obj"); + testObjMesh.Scale(10); + testObjMesh.SwapZandY(); + testObjMeshMutable.data = testObjMesh; + testObjMeshMutable.RefreshVBO();*/ + + + textMesh.Scale(10); + textMesh.SwapZandY(); + textMesh.RotateByMatrix(QuatToMatrix(QuatFromRotateAroundX(M_PI * 0.5))); + textMesh.Move(Vector3f{ 0, 93, 0 }); + + //coneMesh = ZL::LoadFromTextFile("./cone001.txt"); // Add ZL:: namespace + //coneMesh.Scale(200); + + + textMeshMutable.AssignFrom(textMesh); + textMeshMutable.RefreshVBO(); + //coneMeshMutable.AssignFrom(coneMesh); + //coneMeshMutable.RefreshVBO(); + + + // Create active object + + ActiveObject ao1; + ao1.name = "book"; + ao1.activeObjectMesh = ZL::LoadFromTextFile("./book001.txt"); // Add ZL:: namespace + ao1.activeObjectMesh.Scale(4); + ao1.activeObjectMeshMutable.AssignFrom(ao1.activeObjectMesh); + ao1.activeObjectMeshMutable.RefreshVBO(); + ao1.objectPos = Vector3f{ 50, 0, -300 }; + ao1.activeObjectTexturePtr = std::make_shared(CreateTextureDataFromBmp24("./book03.bmp")); + ao1.activeObjectScreenTexturePtr = std::make_shared(CreateTextureDataFromBmp24("./aoscreen01.bmp")); + ao1.activeObjectScreenMesh = CreateRect2D({ 0.f, 0.f }, { 64.f, 64.f }, 0.5); + ao1.activeObjectScreenMeshMutable.AssignFrom(ao1.activeObjectScreenMesh); + ao1.activeObjectScreenMeshMutable.RefreshVBO(); + + /* + ActiveObject ao2; + ao2.name = "superchair001"; + ao2.activeObjectMesh = ZL::LoadFromTextFile("./superchair001.txt"); // Add ZL:: namespace + ao2.activeObjectMesh.Scale(400); + ao2.activeObjectMesh.SwapZandY(); + ao2.activeObjectMeshMutable.AssignFrom(ao2.activeObjectMesh); + ao2.activeObjectMeshMutable.RefreshVBO(); + ao2.objectPos = Vector3f{ 0, 0, 0 }; + ao2.activeObjectTexturePtr = std::make_shared(CreateTextureDataFromBmp24("./chair_01_Base_Color.bmp")); + + ao2.activeObjectScreenTexturePtr = std::make_shared(CreateTextureDataFromBmp24("./aoscreen01.bmp")); + ao2.activeObjectScreenMesh = CreateRect2D({ 0.f, 0.f }, { 64.f, 64.f }, 0.5); + ao2.activeObjectScreenMeshMutable.AssignFrom(ao2.activeObjectScreenMesh); + ao2.activeObjectScreenMeshMutable.RefreshVBO(); + */ + + + + Room room_1; + room_1.roomTexture = std::make_shared(CreateTextureDataFromBmp24("./Material_Base_color_1001.bmp")); + room_1.objects.push_back(ao1); + room_1.sound_name = "Symphony No.6 (1st movement).ogg"; + room_1.roomLogic = createRoom1Logic(); + rooms.push_back(room_1); + aoMgr.addActiveObject(ao1); + + Room room_2; + room_2.roomTexture = std::make_shared(CreateTextureDataFromBmp24("./background.bmp")); + room_2.sound_name = "Symphony No.6 (1st movement).ogg"; + room_2.roomLogic = createRoom2Logic(); + rooms.push_back(room_2); + + activeObjects = rooms[current_room_index].objects; + + // Initialize audio + /* + audioPlayer = std::make_unique(); + if (audioPlayer) { + audioPlayer->playMusic(rooms[current_room_index].sound_name); + }*/ + audioPlayerAsync.resetAsync(); + audioPlayerAsync.playMusicAsync(rooms[current_room_index].sound_name); + + // Initialize inventory + inventoryIconMesh = CreateRect2D( + { 0.0f, 40.0f }, + { INVENTORY_ICON_SIZE / 2, INVENTORY_ICON_SIZE / 2 }, + 0.5f + ); + inventoryIconMeshMutable.AssignFrom(inventoryIconMesh); + inventoryIconMeshMutable.RefreshVBO(); + + roomTexturePtr = rooms[current_room_index].roomTexture; + + AddItemToInventory("book1", std::make_shared(CreateTextureDataFromBmp24("./Kitchen_ceramics.bmp")), objects_in_inventory + 1); + objects_in_inventory++; + AddItemToInventory("book2", std::make_shared(CreateTextureDataFromBmp24("./Kitchen_ceramics.bmp")), objects_in_inventory + 1); + objects_in_inventory++; + + + //SDL_ShowCursor(SDL_DISABLE); + SDL_SetRelativeMouseMode(SDL_TRUE); + + return true; + + }; + + loadingFunctions.push_back(loadingFunction1); + loadingFunctions.push_back(loadingFunction2); + loadingFunctions.push_back(loadingFunction3); } void GameObjectManager::switch_room(int index){ @@ -214,6 +258,11 @@ void GameObjectManager::handleEvent(const SDL_Event& event) { case SDLK_ESCAPE: case SDLK_q: + if (loadingThread.joinable()) + { + loadingThread.join(); + } + audioPlayerAsync.exit(); Environment::exitGameLoop = true; break; case SDLK_LEFT: diff --git a/GameObjectManager.h b/GameObjectManager.h index 1c30493..b4c50c1 100644 --- a/GameObjectManager.h +++ b/GameObjectManager.h @@ -20,6 +20,7 @@ namespace ZL { class GameObjectManager { public: + void initializeLoadingScreen(); void initialize(); void switch_room(int index); @@ -32,11 +33,11 @@ public: std::shared_ptr roomTexturePtr; std::shared_ptr coneTexturePtr; - ZL::VertexDataStruct colorCubeMesh; - ZL::VertexRenderStruct colorCubeMeshMutable; + //ZL::VertexDataStruct colorCubeMesh; + //ZL::VertexRenderStruct colorCubeMeshMutable; - ZL::VertexDataStruct testObjMesh; - ZL::VertexRenderStruct testObjMeshMutable; + //ZL::VertexDataStruct testObjMesh; + //ZL::VertexRenderStruct testObjMeshMutable; ZL::BoneSystem violaIdleModel; ZL::VertexRenderStruct violaIdleModelMutable; @@ -47,8 +48,8 @@ public: ZL::VertexDataStruct textMesh; ZL::VertexRenderStruct textMeshMutable; - ZL::VertexDataStruct coneMesh; - ZL::VertexRenderStruct coneMeshMutable; + //ZL::VertexDataStruct coneMesh; + //ZL::VertexRenderStruct coneMeshMutable; std::vector activeObjects; std::vector rooms; @@ -63,7 +64,15 @@ public: ActiveObjectManager aoMgr; int objects_in_inventory; - + + std::shared_ptr loadingScreenTexturePtr; + + ZL::VertexDataStruct loadingScreenMesh; + ZL::VertexRenderStruct loadingScreenMeshMutable; + + std::list> loadingFunctions; + std::thread loadingThread; + bool sideThreadLoadingCompleted = false; private: //int animationCounter = 0; diff --git a/RenderSystem.cpp b/RenderSystem.cpp index fb5023e..0152d68 100644 --- a/RenderSystem.cpp +++ b/RenderSystem.cpp @@ -26,20 +26,16 @@ void RenderSystem::drawScene(GameObjectManager& gameObjects) { glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); glViewport(0, 0, Environment::width, Environment::height); - /* - renderer.shaderManager.PushShader(defaultShaderName); - renderer.RenderUniform1i(textureUniformName, 0); - renderer.EnableVertexAttribArray(vPositionName); - renderer.EnableVertexAttribArray(vTexCoordName); - */ - drawWorld(gameObjects); - drawUI(gameObjects); - - /*renderer.DisableVertexAttribArray(vPositionName); - renderer.DisableVertexAttribArray(vTexCoordName); - renderer.shaderManager.PopShader();*/ - + if (Environment::gameIsLoading) + { + drawLoadingScreen(gameObjects); + } + else + { + drawWorld(gameObjects); + drawUI(gameObjects); + } CheckGlError(); } @@ -277,6 +273,35 @@ void RenderSystem::drawUI(const GameObjectManager& gameObjects) { renderer.shaderManager.PopShader(); } +void RenderSystem::drawLoadingScreen(const GameObjectManager& gameObjects) +{ + renderer.shaderManager.PushShader("default"); + + // Если шейдер ожидает атрибуты вершин, их нужно включить + static const std::string vPositionName = "vPosition"; + static const std::string vTexCoordName = "vTexCoord"; + renderer.EnableVertexAttribArray(vPositionName); + renderer.EnableVertexAttribArray(vTexCoordName); + + renderer.PushProjectionMatrix(static_cast(Environment::width), + static_cast(Environment::height)); + renderer.PushMatrix(); + renderer.LoadIdentity(); + + glBindTexture(GL_TEXTURE_2D, gameObjects.loadingScreenTexturePtr->getTexID()); + renderer.DrawVertexRenderStruct(gameObjects.loadingScreenMeshMutable); + + renderer.PopMatrix(); + renderer.PopProjectionMatrix(); + + // Выключаем атрибуты, чтобы сохранить баланс + renderer.DisableVertexAttribArray(vPositionName); + renderer.DisableVertexAttribArray(vTexCoordName); + + // Снимаем шейдер, тем самым балансируя стек + renderer.shaderManager.PopShader(); + +} void RenderSystem::worldToScreenCoordinates(Vector3f objectPos, Matrix4f projectionModelView, diff --git a/RenderSystem.h b/RenderSystem.h index 5729320..979cf07 100644 --- a/RenderSystem.h +++ b/RenderSystem.h @@ -24,6 +24,8 @@ private: void drawViola(GameObjectManager& gameObjects); + void drawLoadingScreen(const GameObjectManager& gameObjects); + Renderer renderer; ShaderManager shaderManager; Matrix4f currentProjectionModelView; // Добавлено для хранения матрицы между drawWorld и drawUI diff --git a/loading.bmp b/loading.bmp new file mode 100644 index 0000000000000000000000000000000000000000..cffbe8f1c600a9d68fd283a78b64d42a279117d0 GIT binary patch literal 786486 zcmeI*=h7ufdEjw&KX2^ky@b62UBG_ddx0SEZb5_h3qpV}!U*rZcfvcNMR<=!!U}JM zlk}g-;ECKi=Y6}UdwS^E{hf$8)LSPjEAw~gC$loM>im!Y`Dgd|FZa0Z^=JL}-*4Oa z&+EVcbdUdb+h4c8KKNhnagW>n`{V0>c@ZE$fB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7?mU5qKJ=jv ze(-}I@{osIf0hq>*u%=FR~y%#n;ZxbAVA>82^6Rw|MkQf*{^x)G z=l}cmR~t1eH?G)nCP07yf$I^d+yBgGKJ$Cu``&MV``g~1{p@GA=bAHo-~%7{fe(D( zm%sewFMjchFMjchH7lBcYsPdS0RjYWsK5gr@POC8_O<{1$Jbll@|OGG|Nhr}#{1px zeqa9bm;adAzrX2CZ@Qk`uDKWvBtU=wfh!8sV}H$SUNaHi$9aa2q5_#f(ZNYUmDA!XM#}XhwfWSophxq^Qcfb2Osw`|DN}}=Q`tm(f%=ydCZT0{Nok> zfBy5Iy9?}^za0k>AVA>82^`{otM{)n{#VrU{QB3wzT$sVz<>SMe{Bl*#(jfwCP09| zwF%sa_+Jb1k&k?2rU0&UP0_X0%TWXf5cm@cH0WKww_tdm``qWg_q}h^p#6D=M#4*6 zeD2s@Z5)nmq@~wI6}b4Ee8nqXG4cQEr+_<4=UQUD zpH}(w)R}bDB@XM55|r!!IJx!FI%(h0;qgZu(yyiW=UY?gE=%W%`u?kaK z8w3d4%mV#eE_OZo(T{%p>tFxrPk*{6q5Z=@{KLmS_OUm<@r{j**9sIdu2}w(mAKCH z|GE9u%FJ%9nvht~c-qsR_VlMe{cK=)_`@IGQ{8$!;t`KHn_$jfhWqm$*7#55@9cl` zH-Gc-kAM8LpZ)B6-}~Ob`m4Vx<@vbBJ#OWB zC|f(LV+r~yOMLj$p9Bcpy#)FfT~I2HH3#~KKm1|3!EN+s55_FW6i*7Um(;6IdyvAv z{L8=m+rRzW%At*){NyKZe)F6AsBUVpu_t!6%%&OJ!vgyPW-Ue^_4?fBK6m+vPUC-9 zD@C>T~)S~j&c=aup z;Mc$Yb&tzCTZKJO+XM*Qv;sxBy8T6iiJWIQ{^_6osX%nmKlb9ri(d4i;>PK*wIGF* zd)@0^pZLTl)~Ch2L;NqQ^d#r?X>Cwehq$!sb*@AI|DN}}XD!s}soPVtQUx!`ZROlp zO5N$np)$Ylg)h9MvRx$={U-hCMgh5=_Mru-810;eMpe0r?^RnBFWG2RlIK0|d8;Jt z?^OUlJzaZlz4g|<$1b?mRZQ`@K)vU`LiaS0<$RdtF7pad^D+5<@Pi*b+zQd3AO7%% zCqe(OxV=#SS0i$$kv|C#xM>B7E&JWC^H2M9beaTv@eZo~ zBtYOM7wDgS107cr&DKBpZ+`Qer{l)Ogv;Yn$JRPDEWa|jbL0OfKl#b&QBPdjHwtKt zjhr@H^VO`lS18+$sbocG^ND1CQ5w{waYQI`+X^pxN$U-`;j{!;d5MVc~{xEYE| zY$tOY1PI)00`(8;CDuQmf6-UmXgq%p8pWml`8W8mAK2(ex8;85Lm%prVfIg8xpU+H zWV=pOw4Z6*IdxHSFZ1DB2;sTUeQpCLlfI3MMmA~`s@J59u1<2}B>jk3mr+d|%tvi> zPr)8PuO|LC5;Ye}<D1r&Rf`D6*(LKU-!D#?ZrD>eZ{OH> z{rt0EK_PvA{?E#=UUZ$|)1GaKXQrU4Rrj*3roF0lLpOUG*#M%H&($ z`qnB#gIcXVFkAe->G|b3&1`~Q~B#WA41uD7e&!l)JzrD zMzObcaH_c{Uj6@A`m_p8Q`knQs72UA=zh6$i2t>u9X>g?QOOIZa}hDikUFrBdN9!x zvH`d1xQ^}9s!)CRujw@YSK4YcIka&Yp6V10G0pTu2~O{WI5a~g?vM`LYhjxJfxCr3 z@uyWi_1^oJzQWg`*!|Nkrp?lw6+IeqFAl7fZM4{a?eD#jmV)q}Nj=Qgiq9oRX>LUP z|NFoH`zcaquP(J;d%*0Zhk{KZLuj;TRiUsyMLqjuxuR&D?S3Y5#pKCkHag4RnluYM z=k1W%(;xllN9$b0{|Y$=wy{F=%w;zvz1o+)g=S`FhNAZ6FzQmy9lQ6;Pvd`?v?8<> zx$Ep@(nR*50Daf{^zPnxt#bBlHfy|c=)FEehwN3bO@P4NMWDFRfJwu`b=nW1V5-pm z`tbWme*O94SjU#8s5|2!1%&1<_aTLqNqbuJIYHx2kN-u~BHN+-Mcw@ppnd|H>4$qLJRKC|5+U29BNyck|&Ugzz7U;oo&?xDBCbe5X(nGHFO|0~VQUiPwz)DTm5_I1dfL)&T8veX3Bbk>yb1AQyy zI&aU)`1ZHI-7L>Ox^>9Pp9Bcp`~v-po&a@GX^W-%s_#bHr~lbUGcH}vxf@GQqe^?a zTWVIG-|6wcj{4sDU$rP^w(Mu-g6r;{9nY_9jfzaTt}UDS&6>I5;UUakL^S(r8oIZ) z@VnZ({b|x=b$R@+?I@^DL)rawt9M;pnpv1hmR8`LZJ64b?K=Ey{I8G|uPXO`QNO(^ zmrw7JUR3v$V7*VpZ4e;v|D-?>V_zY&kBt}q8{$|orwCuNV#@)cU{dUzRi>x?>JFJX zf;+nYzh3m&A?RXP=ba&-z07;@igMjSFd?@~p=s$(hgtRmFWN3vw+Me?bqil-#q5gz zbA8bzRf#8`KCL3NmNl-KS}j>)HIrwfE~o8X`Se}P+M$c$f5|#guQ~78jq6WvHd{Lh z5V+e36i8cCv)>ui=t14TDpDYt^^o=d&)zm$`09G$2F3sPzyJNky|bk4--qHG%{~pA z?U_4jYrh)M=$QtqIq0HbpXok(iqoBkSlxh9X;wJxeINfn#Qz%E=Hh4ep&^=SN%OBQ zs6AQ*>k8#;@4dP-XtUB>68}%jY!3kf1TGR-fooq2I={MDE3o&U&&Jq7VnZ<#zV6)d zpO$W(M)g&Z%C=7dv_${VQmlH-rAHI2^}u^@+zeHn9a*|aY3)CR@$Q%_jPLP(7T6r( ze`Ra6|6GYQ_&=pB{x=0P7Y=KY)yi`(OzCH)Ui{x>Y!e`G%>sp*V)(2}FGfu)tt0yK zm%n_XR#S(k(fEeO|5ieto^jQ*vNhc~W!sl6tppXUv6`7PEmqfLbfq&ZFwa)!a>j!2 zK8IUlH(`7=H)rF25x&XRRs^lmb`{W!NlnQ**TKK$sk<}g(L%llEQ@|FHI^*k17_LE~EO>A<}( zdm8b7m$6NNz%>i3I5Ugni$eR__`>XdY0#8wqdmo<0%_gajxE8B8~?c^{vX<%p2l$K zGgru&yr@!dc*7fNx!T#?1S`_koK^DjIZIdV%5c592C406l}?kh6Q#S3n1xd15XCG>HQAMT%B z@qg0PxJ1!vsLC*t<4Z5?^54(6j@=)!+I3Ac*WGK-(xC1R%enM3-!1;{dbSA=xMqPm z@IBU`kg~#Bp>lsVLZPX_iv8-KWDTkm@C$2oYkRdZ!z_1Y{4XlbZL`f^SCK(j#hz%~ zjj=20)<(37X-a$1`l34Cb+)5uS!ObG)9xbbz2@?8gY6;yPs)9xs9u9o=2OnusA1~X z<0;rWs#7hx=`73%XHj0%k*4>*`=#H55@-;hO?3@Sf_Gc%}(XtJ&dQI2;aK@qd=_oE}vq z?@H=$kus<5@IIQcsnf&vJ>vhk*&smRngt3wh4%)}C%l|}mO`Pah&P?>Nqo9-SH}PS zu{nqJmQ`wh(tax^*E*u=TN5ygLK~%zuAK+K>buW+?A7eC`CTsU*Ba~CGTHC{-49vY zjTbfha2Wa7&)Y{0rqu)-t^!W8690EM+XM()n?Rk|M!6g4m>_fdkhhg{5w+RUMnxL> z*wfE}YvZnr|Be4oPri`8$K)dI+$~kX_8E;Df(p4x+eOV`7-v;BmVK&suVam1V>qYX z_$$W$qWXHQ=q2COnpN*%r#%H|mQ?L$U)$pUC5ApCK;UwLf>oiWNZ26B43ZaH3hZYe z^0RWQgIZv$73eP7LRoD=oz-G#YcFRV=3N&5EC0UksDQc0|Kf8C{#UfDY&|?}KCNkJ z{(6Gy*#Y?yH|<+p>a_QCyE5O0zj{_%QzSKrJMxPEGyYSWT9BT`wom@Xd-z$#J%`y| zjZ5O4mJR8rck(&ED!)XvFiWtR<;QcxI~};(o_1od$d5bUad>qR~xS2Uo7qS!X}DdlHvNO7}cu?xsSu# zW%0jqb{Fl0@%{d<>f590=8|N8N^>Qzz{TXIxmT@QpIMAOTiI2?-V49wSu>H_I;!`> z53H1IAOCMYx)QDfJGL@hbl}-j?>kp9YQB3i^U6g0-+gQoAaIF5@$I6~>FM0k?-Q)ci4;ze{ZhRZvnLAah$t!qH6$+<4v~#9_Pw&Q^l!uMN z`2Ks!XV?GF_|HBcT~WIQY?`W`0^OcN=2!f)N?pzKKd+dN1Oy2De8PeqXR@+El0zfX*(caGJW~Ee;ET>uVBFX3^y_1L=-!Vi9(I%LRAhgm zS|810t4q*hpASVU!90q;HKP64ZnfAmzi7L<%ym-@M{VEKrH#G+zsLVR>NQW;Ypkph zvyQ#!ENA!UP#d@ETmRpWvz%43lK_E(Kv8Rg&jifbXym-*{YA)P)ZxIZ`LoRTUU8l6 zqU(O(^~C>z?L1(A&$I2$vj6Vbex84Iv zfI4A3y^G@iD(&>%JL5(79Uo4&|5^O+ZiQ9m+N`t7miF|v2@oKVKm*a2+|ATTc%A2N z@|{q2K)bE;dU4ZGkG<*CX-$f9haUU&#Q(D01N#)L@)T_6vZ8f0HDPP!qFU_p5l#L! z-+Cy2WiIAVnfGglImOwH8jk6NcgeHgD7T8V&;Rb%6rFK@l4?Igb7&oeG%ssGFLcWit~4>stnOHU!l)^xC7@PFk0EzkRr+ zm3MCZFQT^m=Fm3PrqrviZ@R|}DeaIw!D;`$U|x~-*QDsgr7Fm>!D@xQ~1?K2D0*ik3ikL`@z zaomp!q&NGg05wT_dsjYv)EOs%4FUuRTqIDeDE_QhL98%aoN1h79~bJ**6Br0J_XMf zx)ey8?rfa@5KD_o^?_&ce@iY;eC}o;&efui3dj{~9NMUCE#sMcpS!=HG!ul|*vJ37 z+N!4e;T3Ggj+z1}tI5BV6<}S?)cUNiX+O3`b{_s&rJB4xG_reQ+XM&@xS~Mur@pldOG3j)H9vBg7AFW;m>;3vsTUrjUwtSr!SN4 z_U+WAoEryf8GB@OJJ&0RTW+~!e<)Df6=t%%V*D?M`Ssz>xKnrD${9}kYZ_|5y4t#p z`1V%^c2zZlU8OmDr8Xt8L4W{(s}U#+HV{-uS+62zozFtq99!_25%1Czow^Vxau$M` zHf`kOkB8p&zi0cU1-FU+d*Xe&p_tCtc?-5P{?h=<-0@oCnuD%T8UtT(at_}I!z+A| zb7nW1JE)6(I-JtER#2Tx`lXl}zKYr~%j91^)x9f-u09%5I;^2O%;6s5e@f*xjBWI1 z500D6_uqN%qy1^6m8z?oW@Kt3a&BYV!9MB&q=xB``F65NBgLYi<#b?vz*=N z!lZI`$k}??Nq_)>s~0GU?Xv)f#qfoimYQ5K2p6eZ(^616jZ5W!HSz!PkAHmU+T(u_ zvVhq*`Sh7j<6##d6Gf|Xmotr@PqNd+sKc`AH8bGlvmVRSh|4UqpL|X?x-KZ?bEl(kcndENN^QF~Rq0j^oLbb7|Mr>5=j+=;ymwh0g*aEAm6NZ><`eF%x`@OJLQ=BLNDrH>0SZw=}pFg8ll zPr4t1Vex;~$&~)`n`Dc|O<}Bx)PD6$h85C#?~|E9EP?&J?P)foRx*(N5ZeR@5V&T6qJ16F2{k?9@-+UJ zd10qW-m3MYZYx3y&P^8g)Xg4fU$i`pb$fa07Q&fdR7W_+uGGr z16gY!li+{g@+ScT*CSB0uYW%AzmR^o*=o^g5LJLIC=+TS6!o%>RgRlD9V z%4(IWQ&-b<-pX)E`b)~=BLV~nTq02XpJ9+%fs6Y8m*n6h0t5&UxH}5e$=n~Ld3OB& z?pQBnCP07yfd(adoporap*hxl<$2cvhe^q^2ii%1009Cwvp{|Oo;lEvez(CkD1BPg z60~$@Uv1JV^md-6w1)rz0tD_x0*&pjr`+_kftJ%0*t-y@&%a*HFtz|@U!31E`+1Q5 z-Kd(1N`L?X0tbPK|4rSi{epkj0=?HF%+DaM^*`Nmb){8enNf1`{f(W0yG!UpwLxj zE*Sv=1PI);0*&+ZK>HqUb9v-npLRvieMUEJT@;J}0RjZ>kU&j9kCE@GJC`>BJy)S; z7hKIfvv=t15)&XmfWV(hV9oaS15!QW_@Y*z-;?d3G)8{@RNs;F5+Fd}1_{&zG_ce2 z?R&g%&!z0qZLJ0B@tCdlx%Jjtd(PNJcL&^{^2n9|0RjZ>N`YE|*8exc)3fV)m}Kkx zd!lAf%WRIgkM7EnI2Qo|1PI*q0)_gTfQEXyB4}#hqUS=~^#yZA0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk j1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7E)n<-!{4!N literal 0 HcmV?d00001