From e3e5689fce6f4f19d9a4d356b53b31a9308b21c7 Mon Sep 17 00:00:00 2001 From: 4sval Date: Wed, 12 Oct 2022 02:18:54 +0200 Subject: [PATCH] ctrl s --- FModel/Constants.cs | 2 +- FModel/FModel.csproj | 10 +- FModel/Views/Snooper/BufferObject.cs | 36 +- FModel/Views/Snooper/Cube.cs | 2 +- FModel/Views/Snooper/FWindow.cs | 381 +++++++++++++++++++ FModel/Views/Snooper/FramebufferObject.cs | 67 ++-- FModel/Views/Snooper/Grid.cs | 27 +- FModel/Views/Snooper/Model.cs | 60 ++- FModel/Views/Snooper/Morph.cs | 18 +- FModel/Views/Snooper/RenderbufferObject.cs | 24 +- FModel/Views/Snooper/Section.cs | 35 +- FModel/Views/Snooper/Shader.cs | 79 ++-- FModel/Views/Snooper/Skybox.cs | 29 +- FModel/Views/Snooper/Snooper.cs | 405 +-------------------- FModel/Views/Snooper/Texture.cs | 107 +++--- FModel/Views/Snooper/VertexArrayObject.cs | 50 ++- FModel/glfw3.dll | Bin 216576 -> 0 bytes 17 files changed, 642 insertions(+), 690 deletions(-) create mode 100644 FModel/Views/Snooper/FWindow.cs delete mode 100644 FModel/glfw3.dll diff --git a/FModel/Constants.cs b/FModel/Constants.cs index 0b6d0157..e2667514 100644 --- a/FModel/Constants.cs +++ b/FModel/Constants.cs @@ -10,7 +10,7 @@ public static class Constants public static readonly FGuid ZERO_GUID = new(0U); public const float SCALE_DOWN_RATIO = 0.01F; - public const uint SAMPLES_COUNT = 4; + public const int SAMPLES_COUNT = 4; public const string WHITE = "#DAE5F2"; public const string GRAY = "#BBBBBB"; diff --git a/FModel/FModel.csproj b/FModel/FModel.csproj index 91524b53..1d5c1898 100644 --- a/FModel/FModel.csproj +++ b/FModel/FModel.csproj @@ -103,10 +103,6 @@ - - Always - True - @@ -141,14 +137,10 @@ + - - - - - diff --git a/FModel/Views/Snooper/BufferObject.cs b/FModel/Views/Snooper/BufferObject.cs index 51847103..2d413596 100644 --- a/FModel/Views/Snooper/BufferObject.cs +++ b/FModel/Views/Snooper/BufferObject.cs @@ -1,56 +1,48 @@ using System; -using Silk.NET.OpenGL; +using OpenTK.Graphics.OpenGL4; namespace FModel.Views.Snooper; public class BufferObject : IDisposable where TDataType : unmanaged { - private uint _handle; - private BufferTargetARB _bufferType; - private GL _gl; + private readonly int _handle; + private readonly BufferTarget _bufferTarget; - public BufferObject(GL gl, BufferTargetARB bufferType) + private BufferObject(BufferTarget bufferTarget) { - _gl = gl; - _bufferType = bufferType; + _bufferTarget = bufferTarget; + _handle = GL.GenBuffer(); - _handle = _gl.GenBuffer(); Bind(); } - public unsafe BufferObject(GL gl, Span data, BufferTargetARB bufferType) : this(gl, bufferType) + public unsafe BufferObject(TDataType[] data, BufferTarget bufferTarget) : this(bufferTarget) { - fixed (void* d = data) - { - _gl.BufferData(bufferType, (nuint) (data.Length * sizeof(TDataType)), d, BufferUsageARB.StaticDraw); - } + GL.BufferData(bufferTarget, data.Length * sizeof(TDataType), data, BufferUsageHint.StaticDraw); } public unsafe void Update(int offset, TDataType data) { - _gl.BufferSubData(_bufferType, offset * sizeof(TDataType), (nuint) sizeof(TDataType), data); + GL.BufferSubData(_bufferTarget, (IntPtr) (offset * sizeof(TDataType)), sizeof(TDataType), ref data); } - public unsafe void Update(Span data) + public unsafe void Update(TDataType[] data) { - fixed (void* d = data) - { - _gl.BufferSubData(_bufferType, 0, (nuint) (data.Length * sizeof(TDataType)), d); - } + GL.BufferSubData(_bufferTarget, IntPtr.Zero, data.Length * sizeof(TDataType), data); } public void Bind() { - _gl.BindBuffer(_bufferType, _handle); + GL.BindBuffer(_bufferTarget, _handle); } public void Unbind() { - _gl.BindBuffer(_bufferType, 0); + GL.BindBuffer(_bufferTarget, 0); } public void Dispose() { - _gl.DeleteBuffer(_handle); + GL.DeleteBuffer(_handle); } } diff --git a/FModel/Views/Snooper/Cube.cs b/FModel/Views/Snooper/Cube.cs index 62975102..d2b2632d 100644 --- a/FModel/Views/Snooper/Cube.cs +++ b/FModel/Views/Snooper/Cube.cs @@ -63,7 +63,7 @@ public Cube(UObject owner, string name, string type, UMaterialInterface unrealMa }; Sections = new Section[1]; - Sections[0] = new Section(0, (uint) Indices.Length, 0, unrealMaterial); + Sections[0] = new Section(0, Indices.Length, 0, unrealMaterial); AddInstance(Transform.Identity); } diff --git a/FModel/Views/Snooper/FWindow.cs b/FModel/Views/Snooper/FWindow.cs new file mode 100644 index 00000000..259f9137 --- /dev/null +++ b/FModel/Views/Snooper/FWindow.cs @@ -0,0 +1,381 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using CUE4Parse.UE4.Assets.Exports; +using CUE4Parse.UE4.Assets.Exports.Material; +using CUE4Parse.UE4.Assets.Exports.SkeletalMesh; +using CUE4Parse.UE4.Assets.Exports.StaticMesh; +using CUE4Parse.UE4.Assets.Exports.Texture; +using CUE4Parse.UE4.Objects.Core.Math; +using CUE4Parse.UE4.Objects.Core.Misc; +using CUE4Parse.UE4.Objects.Engine; +using CUE4Parse.UE4.Objects.UObject; +using CUE4Parse_Conversion.Meshes; +using OpenTK.Graphics.OpenGL4; +using OpenTK.Mathematics; +using OpenTK.Windowing.Common; +using OpenTK.Windowing.Desktop; +using OpenTK.Windowing.GraphicsLibraryFramework; + +namespace FModel.Views.Snooper; + +public class FWindow : GameWindow +{ + private SnimGui _imGui; + private Camera _camera; + private IMouse _mouse; + private Image _icon; + private Options _options; + + private readonly FramebufferObject _framebuffer; + private readonly Skybox _skybox; + private readonly Grid _grid; + + private Shader _shader; + private Shader _outline; + private Vector3 _diffuseLight; + private Vector3 _specularLight; + private readonly Dictionary _models; + + private float _previousSpeed; + + public FWindow(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings) : base(gameWindowSettings, nativeWindowSettings) + { + _options = new Options(); + _framebuffer = new FramebufferObject(Size); + _skybox = new Skybox(); + _grid = new Grid(); + _models = new Dictionary(); + } + + public void Run(CancellationToken cancellationToken, UObject export) + { + switch (export) + { + case UStaticMesh st: + { + var guid = st.LightingGuid; + if (!_models.TryGetValue(guid, out _) && st.TryConvert(out var mesh)) + { + _models[guid] = new Model(export, st.Name, st.ExportType, mesh.LODs[0], mesh.LODs[0].Verts); + SetupCamera(mesh.BoundingBox *= Constants.SCALE_DOWN_RATIO); + _options.SelectModel(guid); + } + break; + } + case USkeletalMesh sk: + { + var guid = Guid.NewGuid(); + if (!_models.TryGetValue(guid, out _) && sk.TryConvert(out var mesh)) + { + _models[guid] = new Model(export, sk.Name, sk.ExportType, mesh.LODs[0], mesh.LODs[0].Verts, sk.MorphTargets, mesh.RefSkeleton); + SetupCamera(mesh.BoundingBox *= Constants.SCALE_DOWN_RATIO); + _options.SelectModel(guid); + } + break; + } + case UMaterialInstance mi: + { + var guid = Guid.NewGuid(); + if (!_models.TryGetValue(guid, out _)) + { + _models[guid] = new Cube(export, mi.Name, mi.ExportType, mi); + SetupCamera(new FBox(new FVector(-.65f), new FVector(.65f))); + } + break; + } + case UWorld wd: + { + var persistentLevel = wd.PersistentLevel.Load(); + var length = persistentLevel.Actors.Length; + for (var i = 0; i < length; i++) + { + cancellationToken.ThrowIfCancellationRequested(); + if (persistentLevel.Actors[i].Load() is not { } actor || actor.ExportType == "LODActor" || + !actor.TryGetValue(out FPackageIndex staticMeshComponent, "StaticMeshComponent") || + staticMeshComponent.Load() is not { } staticMeshComp) continue; + + if (!staticMeshComp.TryGetValue(out FPackageIndex staticMesh, "StaticMesh") && actor.Class is UBlueprintGeneratedClass) + foreach (var actorExp in actor.Class.Owner.GetExports()) + if (actorExp.TryGetValue(out staticMesh, "StaticMesh")) + break; + if (staticMesh?.Load() is not UStaticMesh m) + continue; + + Services.ApplicationService.ApplicationView.Status.UpdateStatusLabel($"Actor {i}/{length}"); + + var guid = m.LightingGuid; + var transform = new Transform + { + Position = staticMeshComp.GetOrDefault("RelativeLocation", FVector.ZeroVector) * Constants.SCALE_DOWN_RATIO, + Rotation = staticMeshComp.GetOrDefault("RelativeRotation", FRotator.ZeroRotator), + Scale = staticMeshComp.GetOrDefault("RelativeScale3D", FVector.OneVector) + }; + transform.Rotation.Yaw = -transform.Rotation.Yaw; + + if (_models.TryGetValue(guid, out var model)) + { + model.AddInstance(transform); + } + else if (m.TryConvert(out var mesh)) + { + model = new Model(export, m.Name, m.ExportType, mesh.LODs[0], mesh.LODs[0].Verts, null, null, transform); + + if (actor.TryGetAllValues(out FPackageIndex[] textureData, "TextureData")) + { + for (int j = 0; j < textureData.Length; j++) + { + if (textureData[j].Load() is not { } textureDataIdx) + continue; + + if (textureDataIdx.TryGetValue(out FPackageIndex diffuse, "Diffuse") && + diffuse.Load() is UTexture2D diffuseTexture) + model.Sections[j].Parameters.Diffuse = diffuseTexture; + if (textureDataIdx.TryGetValue(out FPackageIndex normal, "Normal") && + normal.Load() is UTexture2D normalTexture) + model.Sections[j].Parameters.Normal = normalTexture; + if (textureDataIdx.TryGetValue(out FPackageIndex specular, "Specular") && + specular.Load() is UTexture2D specularTexture) + model.Sections[j].Parameters.Specular = specularTexture; + } + } + if (staticMeshComp.TryGetValue(out FPackageIndex[] overrideMaterials, "OverrideMaterials")) + { + var max = model.Sections.Length - 1; + for (var j = 0; j < overrideMaterials.Length; j++) + { + if (j > max) break; + if (overrideMaterials[j].Load() is not UMaterialInterface unrealMaterial) continue; + model.Sections[j].SwapMaterial(unrealMaterial); + } + } + + _models[guid] = model; + } + } + _camera = new Camera(new Vector3(0f, 5f, 5f), Vector3.Zero, 0.01f, 1000f, 5f); + break; + } + default: + throw new ArgumentOutOfRangeException(nameof(export)); + } + + DoLoop(); + } + + public void SwapMaterial(UMaterialInstance mi) + { + if (!_models.TryGetValue(_options.SelectedModel, out var model) || + !_options.TryGetSection(model, out var section)) return; + + section.SwapMaterial(mi); + _options.SwapMaterial(false); + DoLoop(); + } + + private void DoLoop() + { + if (_options.Append) _options.Append = false; + _window.Run(); + // if (_window.IsInitialized) + // { + // if (!_window.GLContext.IsCurrent) + // { + // _window.GLContext.MakeCurrent(); + // } + // + // _append = false; + // _window.IsVisible = true; + // var model = _models.Last(); + // model.Value.Setup(_gl); + // _imGui.Increment(model.Key); + // } + // else _window.Initialize(); + // + // while (!_window.IsClosing && _window.IsVisible) + // { + // _window.DoEvents(); + // if (!_window.IsClosing && _window.IsVisible) + // _window.DoUpdate(); + // if (_window.IsClosing || !_window.IsVisible) + // return; + // _window.DoRender(); + // } + // + // _window.DoEvents(); + // if (_window.IsClosing) _window.Reset(); + } + + private void SetupCamera(FBox box) + { + var far = box.Max.Max(); + var center = box.GetCenter(); + var position = new Vector3(0f, center.Z, box.Max.Y * 3); + var speed = far / 2f; + if (speed > _previousSpeed) + { + _camera = new Camera(position, center, 0.01f, far * 50f, speed); + _previousSpeed = _camera.Speed; + } + } + + protected override void OnLoad() + { + base.OnLoad(); + + _window.SetWindowIcon(ref _icon); + _window.Center(); + + var input = _window.CreateInput(); + _keyboard = input.Keyboards[0]; + _mouse = input.Mice[0]; + + _gl = GL.GetApi(_window); + _gl.Enable(EnableCap.Blend); + _gl.Enable(EnableCap.DepthTest); + _gl.Enable(EnableCap.Multisample); + _gl.StencilOp(StencilOp.Keep, StencilOp.Replace, StencilOp.Replace); + _gl.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha); + + _imGui = new SnimGui(_gl, _window, input); + + _framebuffer.Setup(); + _skybox.Setup(); + _grid.Setup(); + + _shader = new Shader(); + _outline = new Shader("outline"); + _diffuseLight = new Vector3(0.75f); + _specularLight = new Vector3(0.5f); + foreach (var model in _models.Values) + { + model.Setup(); + } + } + + protected override void OnRenderFrame(FrameEventArgs args) + { + base.OnRenderFrame(args); + + _imGui.Update((float) args.Time); + + ClearWhatHasBeenDrawn(); // in main window + + _framebuffer.Bind(); // switch to dedicated window + ClearWhatHasBeenDrawn(); // in dedicated window + + _skybox.Bind(_camera); + _grid.Bind(_camera); + + var viewMatrix = _camera.GetViewMatrix(); + var projMatrix = _camera.GetProjectionMatrix(); + + _outline.Use(); + _outline.SetUniform("uView", viewMatrix); + _outline.SetUniform("uProjection", projMatrix); + _outline.SetUniform("viewPos", _camera.Position); + + _shader.Use(); + _shader.SetUniform("uView", viewMatrix); + _shader.SetUniform("uProjection", projMatrix); + _shader.SetUniform("viewPos", _camera.Position); + + _shader.SetUniform("material.diffuseMap", 0); + _shader.SetUniform("material.normalMap", 1); + _shader.SetUniform("material.specularMap", 2); + _shader.SetUniform("material.emissionMap", 3); + + _shader.SetUniform("light.position", _camera.Position); + _shader.SetUniform("light.diffuse", _diffuseLight); + _shader.SetUniform("light.specular", _specularLight); + + foreach (var model in _models.Values.Where(model => model.Show)) + { + model.Bind(_shader); + } + GL.Enable(EnableCap.StencilTest); // I don't get why this must be here but it works now so... + foreach (var model in _models.Values.Where(model => model.IsSelected && model.Show)) + { + model.Outline(_outline); + } + + _imGui.Construct(ref _options, _size, _framebuffer, _camera, _mouse, _models); + + _framebuffer.BindMsaa(); + _framebuffer.Bind(0); // switch back to main window + _framebuffer.BindStuff(); + + _imGui.Render(); // render ImGui in main window + + SwapBuffers(); + } + + private void ClearWhatHasBeenDrawn() + { + GL.ClearColor(1.0f, 0.102f, 0.129f, 1.0f); + GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit | ClearBufferMask.StencilBufferBit); + GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Fill); + } + + protected override void OnUpdateFrame(FrameEventArgs e) + { + base.OnUpdateFrame(e); + if (!IsFocused || ImGui.GetIO().WantTextInput) + return; + + var multiplier = KeyboardState.IsKeyPressed(Keys.LeftShift) ? 2f : 1f; + var moveSpeed = _camera.Speed * multiplier * (float) e.Time; + if (KeyboardState.IsKeyPressed(Keys.W)) + _camera.Position += moveSpeed * _camera.Direction; + if (KeyboardState.IsKeyPressed(Keys.S)) + _camera.Position -= moveSpeed * _camera.Direction; + if (KeyboardState.IsKeyPressed(Keys.A)) + _camera.Position -= Vector3.Normalize(Vector3.Cross(_camera.Direction, _camera.Up)) * moveSpeed; + if (KeyboardState.IsKeyPressed(Keys.D)) + _camera.Position += Vector3.Normalize(Vector3.Cross(_camera.Direction, _camera.Up)) * moveSpeed; + if (KeyboardState.IsKeyPressed(Keys.E)) + _camera.Position += moveSpeed * _camera.Up; + if (KeyboardState.IsKeyPressed(Keys.Q)) + _camera.Position -= moveSpeed * _camera.Up; + if (KeyboardState.IsKeyPressed(Keys.X)) + _camera.ModifyZoom(-.5f); + if (KeyboardState.IsKeyPressed(Keys.C)) + _camera.ModifyZoom(+.5f); + + if (KeyboardState.IsKeyPressed(Keys.H)) + IsVisible = false; + if (KeyboardState.IsKeyPressed(Keys.Escape)) + Close(); + } + + private void OnClose() + { + _framebuffer.Dispose(); + _grid.Dispose(); + _skybox.Dispose(); + _shader.Dispose(); + _outline.Dispose(); + foreach (var model in _models.Values) + { + model.Dispose(); + } + if (!_options.Append) + { + _models.Clear(); + _options.Reset(); + _previousSpeed = 0f; + } + _imGui.Dispose(); + _window.Dispose(); + _gl.Dispose(); + } + + protected override void OnResize(ResizeEventArgs e) + { + base.OnResize(e); + + GL.Viewport(0, 0, Size.X, Size.Y); + _camera.AspectRatio = Size.X / (float)Size.Y; + } +} diff --git a/FModel/Views/Snooper/FramebufferObject.cs b/FModel/Views/Snooper/FramebufferObject.cs index 2994779c..6b1608b8 100644 --- a/FModel/Views/Snooper/FramebufferObject.cs +++ b/FModel/Views/Snooper/FramebufferObject.cs @@ -1,14 +1,13 @@ using System; -using Silk.NET.Maths; -using Silk.NET.OpenGL; +using OpenTK.Graphics.OpenGL4; +using OpenTK.Mathematics; namespace FModel.Views.Snooper; public class FramebufferObject : IDisposable { - private uint _framebufferHandle; - private uint _postProcessingHandle; - private GL _gl; + private int _framebufferHandle; + private int _postProcessingHandle; private readonly int _width; private readonly int _height; @@ -34,77 +33,75 @@ public class FramebufferObject : IDisposable -1.0f, 1.0f, 0.0f, 1.0f }; - public FramebufferObject(Vector2D size) + public FramebufferObject(Vector2i size) { _width = size.X; _height = size.Y; - _renderbuffer = new RenderbufferObject((uint) _width, (uint) _height); + _renderbuffer = new RenderbufferObject(_width, _height); } - public void Setup(GL gl) + public void Setup() { - _gl = gl; - - _framebufferHandle = _gl.GenFramebuffer(); + _framebufferHandle = GL.GenFramebuffer(); Bind(_framebufferHandle); - _framebufferTexture = new Texture(_gl, (uint) _width, (uint) _height); + _framebufferTexture = new Texture((uint) _width, (uint) _height); - _renderbuffer.Setup(gl); + _renderbuffer.Setup(); - _shader = new Shader(_gl, "framebuffer"); + _shader = new Shader("framebuffer"); _shader.Use(); _shader.SetUniform("screenTexture", 0); - _ebo = new BufferObject(_gl, Indices, BufferTargetARB.ElementArrayBuffer); - _vbo = new BufferObject(_gl, Vertices, BufferTargetARB.ArrayBuffer); - _vao = new VertexArrayObject(_gl, _vbo, _ebo); + _ebo = new BufferObject(Indices, BufferTarget.ElementArrayBuffer); + _vbo = new BufferObject(Vertices, BufferTarget.ArrayBuffer); + _vao = new VertexArrayObject(_vbo, _ebo); _vao.VertexAttributePointer(0, 2, VertexAttribPointerType.Float, 4, 0); // position _vao.VertexAttributePointer(1, 2, VertexAttribPointerType.Float, 4, 2); // uv - var status = _gl.CheckFramebufferStatus(FramebufferTarget.Framebuffer); - if (status != GLEnum.FramebufferComplete) + var status = GL.CheckFramebufferStatus(FramebufferTarget.Framebuffer); + if (status != FramebufferErrorCode.FramebufferComplete) { - throw new Exception($"Framebuffer failed to bind with error: {_gl.GetProgramInfoLog(_framebufferHandle)}"); + throw new Exception($"Framebuffer failed to bind with error: {GL.GetProgramInfoLog(_framebufferHandle)}"); } - _postProcessingHandle = _gl.GenFramebuffer(); + _postProcessingHandle = GL.GenFramebuffer(); Bind(_postProcessingHandle); - _postProcessingTexture = new Texture(_gl, _width, _height); + _postProcessingTexture = new Texture(_width, _height); - status = _gl.CheckFramebufferStatus(FramebufferTarget.Framebuffer); - if (status != GLEnum.FramebufferComplete) + status = GL.CheckFramebufferStatus(FramebufferTarget.Framebuffer); + if (status != FramebufferErrorCode.FramebufferComplete) { - throw new Exception($"Post-Processing framebuffer failed to bind with error: {_gl.GetProgramInfoLog(_postProcessingHandle)}"); + throw new Exception($"Post-Processing framebuffer failed to bind with error: {GL.GetProgramInfoLog(_postProcessingHandle)}"); } } public void Bind() => Bind(_framebufferHandle); - public void Bind(uint handle) + public void Bind(int handle) { - _gl.BindFramebuffer(FramebufferTarget.Framebuffer, handle); + GL.BindFramebuffer(FramebufferTarget.Framebuffer, handle); } public void BindMsaa() { - _gl.BindFramebuffer(FramebufferTarget.ReadFramebuffer, _framebufferHandle); - _gl.BindFramebuffer(FramebufferTarget.DrawFramebuffer, _postProcessingHandle); - _gl.BlitFramebuffer(0, 0, _width, _height, 0, 0, _width, _height, ClearBufferMask.ColorBufferBit, BlitFramebufferFilter.Nearest); + GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, _framebufferHandle); + GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, _postProcessingHandle); + GL.BlitFramebuffer(0, 0, _width, _height, 0, 0, _width, _height, ClearBufferMask.ColorBufferBit, BlitFramebufferFilter.Nearest); } public void BindStuff() { - _gl.Disable(EnableCap.DepthTest); + GL.Disable(EnableCap.DepthTest); _shader.Use(); _vao.Bind(); _postProcessingTexture.Bind(TextureUnit.Texture0); - _gl.DrawArrays(PrimitiveType.Triangles, 0, (uint) Indices.Length); - _gl.Enable(EnableCap.DepthTest); + GL.DrawArrays(PrimitiveType.Triangles, 0, Indices.Length); + GL.Enable(EnableCap.DepthTest); } public IntPtr GetPointer() => _postProcessingTexture.GetPointer(); @@ -116,7 +113,7 @@ public void Dispose() _framebufferTexture.Dispose(); _postProcessingTexture.Dispose(); _renderbuffer.Dispose(); - _gl.DeleteFramebuffer(_framebufferHandle); - _gl.DeleteFramebuffer(_postProcessingHandle); + GL.DeleteFramebuffer(_framebufferHandle); + GL.DeleteFramebuffer(_postProcessingHandle); } } diff --git a/FModel/Views/Snooper/Grid.cs b/FModel/Views/Snooper/Grid.cs index 93ccd331..718d5ded 100644 --- a/FModel/Views/Snooper/Grid.cs +++ b/FModel/Views/Snooper/Grid.cs @@ -1,12 +1,11 @@ using System; -using Silk.NET.OpenGL; +using OpenTK.Graphics.OpenGL4; namespace FModel.Views.Snooper; public class Grid : IDisposable { - private uint _handle; - private GL _gl; + private int _handle; private BufferObject _ebo; private BufferObject _vbo; @@ -26,24 +25,22 @@ public class Grid : IDisposable public Grid() {} - public void Setup(GL gl) + public void Setup() { - _gl = gl; + _handle = GL.CreateProgram(); - _handle = _gl.CreateProgram(); + _ebo = new BufferObject(Indices, BufferTarget.ElementArrayBuffer); + _vbo = new BufferObject(Vertices, BufferTarget.ArrayBuffer); + _vao = new VertexArrayObject(_vbo, _ebo); - _ebo = new BufferObject(_gl, Indices, BufferTargetARB.ElementArrayBuffer); - _vbo = new BufferObject(_gl, Vertices, BufferTargetARB.ArrayBuffer); - _vao = new VertexArrayObject(_gl, _vbo, _ebo); - - _shader = new Shader(_gl, "grid"); + _shader = new Shader("grid"); _vao.VertexAttributePointer(0, 3, VertexAttribPointerType.Float, 3, 0); // position } public void Bind(Camera camera) { - _gl.Disable(EnableCap.DepthTest); + GL.Disable(EnableCap.DepthTest); _vao.Bind(); _shader.Use(); @@ -53,8 +50,8 @@ public void Bind(Camera camera) _shader.SetUniform("uNear", camera.Near); _shader.SetUniform("uFar", camera.Far); - _gl.DrawArrays(PrimitiveType.Triangles, 0, (uint) Indices.Length); - _gl.Enable(EnableCap.DepthTest); + GL.DrawArrays(PrimitiveType.Triangles, 0, Indices.Length); + GL.Enable(EnableCap.DepthTest); } public void Dispose() @@ -63,6 +60,6 @@ public void Dispose() _vbo.Dispose(); _vao.Dispose(); _shader.Dispose(); - _gl.DeleteProgram(_handle); + GL.DeleteProgram(_handle); } } diff --git a/FModel/Views/Snooper/Model.cs b/FModel/Views/Snooper/Model.cs index 8b47a5f0..e0ff27bb 100644 --- a/FModel/Views/Snooper/Model.cs +++ b/FModel/Views/Snooper/Model.cs @@ -4,17 +4,15 @@ using System.Numerics; using CUE4Parse.UE4.Assets.Exports; using CUE4Parse.UE4.Assets.Exports.Animation; -using CUE4Parse.UE4.Objects.Engine; using CUE4Parse.UE4.Objects.UObject; using CUE4Parse_Conversion.Meshes.PSK; -using Silk.NET.OpenGL; +using OpenTK.Graphics.OpenGL4; namespace FModel.Views.Snooper; public class Model : IDisposable { - private uint _handle; - private GL _gl; + private int _handle; private BufferObject _ebo; private BufferObject _vbo; @@ -22,9 +20,9 @@ public class Model : IDisposable private BufferObject _matrixVbo; private VertexArrayObject _vao; - private uint _vertexSize = 9; // VertexIndex + Position + Normal + UV - private const uint _faceSize = 3; // just so we don't have to do .Length + private readonly int _vertexSize = 9; // VertexIndex + Position + Normal + UV private readonly uint[] _facesIndex = { 1, 0, 2 }; + private const int _faceSize = 3; // just so we don't have to do .Length public readonly UObject Owner; public readonly string Name; @@ -43,7 +41,6 @@ public class Model : IDisposable public bool Show; public bool IsSelected; - public bool IsSavable; public bool DisplayVertexColors; public bool DisplayBones; public float MorphTime; @@ -55,7 +52,6 @@ protected Model(UObject owner, string name, string type) Type = type; Transforms = new List(); Show = true; - IsSavable = owner is not UWorld; } public Model(UObject owner, string name, string type, CBaseMeshLod lod, CMeshVertex[] vertices, FPackageIndex[] morphTargets = null, List skeleton = null, Transform transform = null) @@ -76,7 +72,7 @@ public Model(UObject owner, string name, string type, CBaseMeshLod lod, CMeshVer for (var s = 0; s < sections.Length; s++) { var section = sections[s]; - Sections[s] = new Section(section.MaterialName, section.MaterialIndex, (uint) section.NumFaces * _faceSize, section.FirstIndex, section); + Sections[s] = new Section(section.MaterialName, section.MaterialIndex, section.NumFaces * _faceSize, section.FirstIndex, section); for (uint face = 0; face < section.NumFaces; face++) { foreach (var f in _facesIndex) @@ -155,15 +151,13 @@ public void UpdateMorph(int index) _morphVbo.Unbind(); } - public void Setup(GL gl) + public void Setup() { - _gl = gl; + _handle = GL.CreateProgram(); - _handle = _gl.CreateProgram(); - - _ebo = new BufferObject(_gl, Indices, BufferTargetARB.ElementArrayBuffer); - _vbo = new BufferObject(_gl, Vertices, BufferTargetARB.ArrayBuffer); - _vao = new VertexArrayObject(_gl, _vbo, _ebo); + _ebo = new BufferObject(Indices, BufferTarget.ElementArrayBuffer); + _vbo = new BufferObject(Vertices, BufferTarget.ArrayBuffer); + _vao = new VertexArrayObject(_vbo, _ebo); _vao.VertexAttributePointer(0, 1, VertexAttribPointerType.Int, _vertexSize, 0); // vertex index _vao.VertexAttributePointer(1, 3, VertexAttribPointerType.Float, _vertexSize, 1); // position @@ -178,7 +172,7 @@ public void Setup(GL gl) var instanceMatrix = new Matrix4x4[TransformsCount]; for (var i = 0; i < instanceMatrix.Length; i++) instanceMatrix[i] = Transforms[i].Matrix; - _matrixVbo = new BufferObject(_gl, instanceMatrix, BufferTargetARB.ArrayBuffer); + _matrixVbo = new BufferObject(instanceMatrix, BufferTarget.ArrayBuffer); _vao.BindInstancing(); // VertexAttributePointer 7, 8, 9, 10 } @@ -186,9 +180,9 @@ public void Setup(GL gl) { for (uint morph = 0; morph < Morphs.Length; morph++) { - Morphs[morph].Setup(gl); + Morphs[morph].Setup(); if (morph == 0) - _morphVbo = new BufferObject(_gl, Morphs[morph].Vertices, BufferTargetARB.ArrayBuffer); + _morphVbo = new BufferObject(Morphs[morph].Vertices, BufferTarget.ArrayBuffer); } _vao.Bind(); _vao.VertexAttributePointer(11, 3, VertexAttribPointerType.Float, 3, 0); // morph position @@ -197,7 +191,7 @@ public void Setup(GL gl) for (int section = 0; section < Sections.Length; section++) { - Sections[section].Setup(_gl); + Sections[section].Setup(); } } @@ -205,8 +199,8 @@ public void Bind(Shader shader) { if (IsSelected) { - _gl.Enable(EnableCap.StencilTest); - _gl.StencilFunc(StencilFunction.Always, 1, 0xFF); + GL.Enable(EnableCap.StencilTest); + GL.StencilFunc(StencilFunction.Always, 1, 0xFF); } _vao.Bind(); @@ -214,22 +208,22 @@ public void Bind(Shader shader) shader.SetUniform("display_vertex_colors", DisplayVertexColors); for (int section = 0; section < Sections.Length; section++) { - Sections[section].Bind(shader, (uint) TransformsCount); + Sections[section].Bind(shader, TransformsCount); } _vao.Unbind(); if (IsSelected) { - _gl.StencilFunc(StencilFunction.Always, 0, 0xFF); - _gl.Disable(EnableCap.StencilTest); + GL.StencilFunc(StencilFunction.Always, 0, 0xFF); + GL.Disable(EnableCap.StencilTest); } } public void Outline(Shader shader) { - _gl.StencilMask(0x00); - _gl.Disable(EnableCap.DepthTest); - _gl.StencilFunc(StencilFunction.Notequal, 1, 0xFF); + GL.StencilMask(0x00); + GL.Disable(EnableCap.DepthTest); + GL.StencilFunc(StencilFunction.Notequal, 1, 0xFF); _vao.Bind(); shader.Use(); @@ -237,13 +231,13 @@ public void Outline(Shader shader) for (int section = 0; section < Sections.Length; section++) { if (!Sections[section].Show) continue; - _gl.DrawArraysInstanced(PrimitiveType.Triangles, Sections[section].FirstFaceIndex, Sections[section].FacesCount, (uint) TransformsCount); + GL.DrawArraysInstanced(PrimitiveType.Triangles, Sections[section].FirstFaceIndex, Sections[section].FacesCount, TransformsCount); } _vao.Unbind(); - _gl.StencilFunc(StencilFunction.Always, 0, 0xFF); - _gl.Enable(EnableCap.DepthTest); - _gl.StencilMask(0xFF); + GL.StencilFunc(StencilFunction.Always, 0, 0xFF); + GL.Enable(EnableCap.DepthTest); + GL.StencilMask(0xFF); } public void Dispose() @@ -264,6 +258,6 @@ public void Dispose() { Sections[section].Dispose(); } - _gl.DeleteProgram(_handle); + GL.DeleteProgram(_handle); } } diff --git a/FModel/Views/Snooper/Morph.cs b/FModel/Views/Snooper/Morph.cs index 80488c9e..ea4b1aaa 100644 --- a/FModel/Views/Snooper/Morph.cs +++ b/FModel/Views/Snooper/Morph.cs @@ -1,21 +1,20 @@ using System; using CUE4Parse.UE4.Assets.Exports.Animation; using CUE4Parse.UE4.Objects.Core.Math; -using Silk.NET.OpenGL; +using OpenTK.Graphics.OpenGL4; namespace FModel.Views.Snooper; public class Morph : IDisposable { - private uint _handle; - private GL _gl; + private int _handle; - private uint _vertexSize = 3; // Position + private readonly int _vertexSize = 3; // Position public readonly string Name; public readonly float[] Vertices; - public Morph(float[] vertices, uint vertexSize, UMorphTarget morphTarget) + public Morph(float[] vertices, int vertexSize, UMorphTarget morphTarget) { Name = morphTarget.Name; Vertices = new float[vertices.Length / vertexSize * _vertexSize]; @@ -34,7 +33,7 @@ bool TryFindVertex(uint index, out FVector positionDelta) return false; } - for (uint i = 0; i < vertices.Length; i += vertexSize) + for (int i = 0; i < vertices.Length; i += vertexSize) { var count = 0; var baseIndex = i / vertexSize * _vertexSize; @@ -53,14 +52,13 @@ bool TryFindVertex(uint index, out FVector positionDelta) } } - public void Setup(GL gl) + public void Setup() { - _gl = gl; - _handle = _gl.CreateProgram(); + _handle = GL.CreateProgram(); } public void Dispose() { - _gl.DeleteProgram(_handle); + GL.DeleteProgram(_handle); } } diff --git a/FModel/Views/Snooper/RenderbufferObject.cs b/FModel/Views/Snooper/RenderbufferObject.cs index bc789e4e..30fac53b 100644 --- a/FModel/Views/Snooper/RenderbufferObject.cs +++ b/FModel/Views/Snooper/RenderbufferObject.cs @@ -1,34 +1,32 @@ using System; -using Silk.NET.OpenGL; +using OpenTK.Graphics.OpenGL4; namespace FModel.Views.Snooper; public class RenderbufferObject : IDisposable { - private uint _handle; - private GL _gl; + private int _handle; - private readonly uint _width; - private readonly uint _height; + private readonly int _width; + private readonly int _height; - public RenderbufferObject(uint width, uint height) + public RenderbufferObject(int width, int height) { _width = width; _height = height; } - public void Setup(GL gl) + public void Setup() { - _gl = gl; - _handle = _gl.GenRenderbuffer(); + _handle = GL.GenRenderbuffer(); - _gl.BindRenderbuffer(RenderbufferTarget.Renderbuffer, _handle); - _gl.RenderbufferStorageMultisample(RenderbufferTarget.Renderbuffer, Constants.SAMPLES_COUNT, InternalFormat.Depth24Stencil8, _width, _height); - _gl.FramebufferRenderbuffer(FramebufferTarget.Framebuffer, FramebufferAttachment.DepthStencilAttachment, RenderbufferTarget.Renderbuffer, _handle); + GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, _handle); + GL.RenderbufferStorageMultisample(RenderbufferTarget.Renderbuffer, Constants.SAMPLES_COUNT, RenderbufferStorage.Depth24Stencil8, _width, _height); + GL.FramebufferRenderbuffer(FramebufferTarget.Framebuffer, FramebufferAttachment.DepthStencilAttachment, RenderbufferTarget.Renderbuffer, _handle); } public void Dispose() { - _gl.DeleteRenderbuffer(_handle); + GL.DeleteRenderbuffer(_handle); } } diff --git a/FModel/Views/Snooper/Section.cs b/FModel/Views/Snooper/Section.cs index efd39cab..222d42f6 100644 --- a/FModel/Views/Snooper/Section.cs +++ b/FModel/Views/Snooper/Section.cs @@ -6,15 +6,14 @@ using CUE4Parse_Conversion.Textures; using FModel.Services; using FModel.Settings; -using Silk.NET.OpenGL; +using OpenTK.Graphics.OpenGL4; using SkiaSharp; namespace FModel.Views.Snooper; public class Section : IDisposable { - private uint _handle; - private GL _gl; + private int _handle; private Vector3 _ambientLight; @@ -22,7 +21,7 @@ public class Section : IDisposable public string Name; public readonly int Index; - public readonly uint FacesCount; + public readonly int FacesCount; public readonly int FirstFaceIndex; public readonly CMaterialParams Parameters; @@ -35,7 +34,7 @@ public class Section : IDisposable public bool HasSpecularMap; public bool HasDiffuseColor; - private Section(string name, int index, uint facesCount, int firstFaceIndex) + private Section(string name, int index, int facesCount, int firstFaceIndex) { Name = name; Index = index; @@ -52,7 +51,7 @@ private Section(string name, int index, uint facesCount, int firstFaceIndex) _game = ApplicationService.ApplicationView.CUE4Parse.Game; } - public Section(string name, int index, uint facesCount, int firstFaceIndex, CMeshSection section) : this(name, index, facesCount, firstFaceIndex) + public Section(string name, int index, int facesCount, int firstFaceIndex, CMeshSection section) : this(name, index, facesCount, firstFaceIndex) { if (section.Material != null && section.Material.TryLoad(out var material) && material is UMaterialInterface unrealMaterial) { @@ -60,7 +59,7 @@ public Section(string name, int index, uint facesCount, int firstFaceIndex, CMes } } - public Section(int index, uint facesCount, int firstFaceIndex, UMaterialInterface unrealMaterial) : this(string.Empty, index, facesCount, firstFaceIndex) + public Section(int index, int facesCount, int firstFaceIndex, UMaterialInterface unrealMaterial) : this(string.Empty, index, facesCount, firstFaceIndex) { SwapMaterial(unrealMaterial); } @@ -71,11 +70,9 @@ public void SwapMaterial(UMaterialInterface unrealMaterial) unrealMaterial.GetParams(Parameters); } - public void Setup(GL gl) + public void Setup() { - _gl = gl; - - _handle = _gl.CreateProgram(); + _handle = GL.CreateProgram(); if (Parameters.IsNull) { @@ -92,21 +89,21 @@ public void Setup(GL gl) { var mip = diffuse.GetFirstMip(); TextureDecoder.DecodeTexture(mip, diffuse.Format, diffuse.isNormalMap, platform, out var data, out var colorType); - Textures[0] = new Texture(_gl, data, (uint) mip.SizeX, (uint) mip.SizeY, colorType, diffuse); + Textures[0] = new Texture(data, (uint) mip.SizeX, (uint) mip.SizeY, colorType, diffuse); } if (Parameters.Normal is UTexture2D { IsVirtual: false } normal) { var mip = normal.GetFirstMip(); TextureDecoder.DecodeTexture(mip, normal.Format, normal.isNormalMap, platform, out var data, out var colorType); - Textures[1] = new Texture(_gl, data, (uint) mip.SizeX, (uint) mip.SizeY, colorType, normal); + Textures[1] = new Texture(data, (uint) mip.SizeX, (uint) mip.SizeY, colorType, normal); } if (Parameters.Specular is UTexture2D { IsVirtual: false } specular) { var mip = specular.GetFirstMip(); SwapSpecular(specular, mip, platform, out var data, out var colorType); - Textures[2] = new Texture(_gl, data, (uint) mip.SizeX, (uint) mip.SizeY, colorType, specular); + Textures[2] = new Texture(data, (uint) mip.SizeX, (uint) mip.SizeY, colorType, specular); } if (Parameters.HasTopEmissiveTexture && @@ -114,7 +111,7 @@ public void Setup(GL gl) { var mip = emissive.GetFirstMip(); TextureDecoder.DecodeTexture(mip, emissive.Format, emissive.isNormalMap, platform, out var data, out var colorType); - Textures[3] = new Texture(_gl, data, (uint) mip.SizeX, (uint) mip.SizeY, colorType, emissive); + Textures[3] = new Texture(data, (uint) mip.SizeX, (uint) mip.SizeY, colorType, emissive); if (Parameters.EmissiveColor is { A: > 0 } emissiveColor) { EmissionColor = new Vector4(emissiveColor.R, emissiveColor.G, emissiveColor.B, emissiveColor.A); @@ -232,7 +229,7 @@ private void SwapSpecular(UTexture2D specular, FTexture2DMipMap mip, ETexturePla Parameters.RoughnessValue = 0; } - public void Bind(Shader shader, uint instanceCount) + public void Bind(Shader shader, int instanceCount) { for (var i = 0; i < Textures.Length; i++) { @@ -251,8 +248,8 @@ public void Bind(Shader shader, uint instanceCount) shader.SetUniform("light.ambient", _ambientLight); - _gl.PolygonMode(MaterialFace.FrontAndBack, Wireframe ? PolygonMode.Line : PolygonMode.Fill); - if (Show) _gl.DrawArraysInstanced(PrimitiveType.Triangles, FirstFaceIndex, FacesCount, instanceCount); + GL.PolygonMode(MaterialFace.FrontAndBack, Wireframe ? PolygonMode.Line : PolygonMode.Fill); + if (Show) GL.DrawArraysInstanced(PrimitiveType.Triangles, FirstFaceIndex, FacesCount, instanceCount); } public void Dispose() @@ -261,6 +258,6 @@ public void Dispose() { Textures[i]?.Dispose(); } - _gl.DeleteProgram(_handle); + GL.DeleteProgram(_handle); } } diff --git a/FModel/Views/Snooper/Shader.cs b/FModel/Views/Snooper/Shader.cs index e67fb2ef..0aa07df8 100644 --- a/FModel/Views/Snooper/Shader.cs +++ b/FModel/Views/Snooper/Shader.cs @@ -3,114 +3,111 @@ using System.IO; using System.Numerics; using System.Reflection; -using Silk.NET.OpenGL; +using OpenTK.Graphics.OpenGL4; namespace FModel.Views.Snooper; public class Shader : IDisposable { - private readonly uint _handle; - private readonly GL _gl; + private readonly int _handle; private readonly Dictionary _uniformToLocation = new (); private readonly Dictionary _attribLocation = new (); - public Shader(GL gl) : this(gl, "default") {} + public Shader() : this("default") {} - public Shader(GL gl, string name) + public Shader(string name) { - _gl = gl; - - _handle = _gl.CreateProgram(); - - uint v = LoadShader(ShaderType.VertexShader, $"{name}.vert"); - uint f = LoadShader(ShaderType.FragmentShader, $"{name}.frag"); - _gl.AttachShader(_handle, v); - _gl.AttachShader(_handle, f); - _gl.LinkProgram(_handle); - _gl.GetProgram(_handle, GLEnum.LinkStatus, out var status); + _handle = GL.CreateProgram(); + + var v = LoadShader(ShaderType.VertexShader, $"{name}.vert"); + var f = LoadShader(ShaderType.FragmentShader, $"{name}.frag"); + GL.AttachShader(_handle, v); + GL.AttachShader(_handle, f); + GL.LinkProgram(_handle); + GL.GetProgram(_handle, GetProgramParameterName.LinkStatus, out var status); if (status == 0) { - throw new Exception($"Program failed to link with error: {_gl.GetProgramInfoLog(_handle)}"); + throw new Exception($"Program failed to link with error: {GL.GetProgramInfoLog(_handle)}"); } - _gl.DetachShader(_handle, v); - _gl.DetachShader(_handle, f); - _gl.DeleteShader(v); - _gl.DeleteShader(f); + GL.DetachShader(_handle, v); + GL.DetachShader(_handle, f); + GL.DeleteShader(v); + GL.DeleteShader(f); } public void Use() { - _gl.UseProgram(_handle); + GL.UseProgram(_handle); } public void SetUniform(string name, int value) { - int location = _gl.GetUniformLocation(_handle, name); + int location = GL.GetUniformLocation(_handle, name); if (location == -1) { throw new Exception($"{name} uniform not found on shader."); } - _gl.Uniform1(location, value); + GL.Uniform1(location, value); } public unsafe void SetUniform(string name, Matrix4x4 value) { //A new overload has been created for setting a uniform so we can use the transform in our shader. - int location = _gl.GetUniformLocation(_handle, name); + int location = GL.GetUniformLocation(_handle, name); if (location == -1) { throw new Exception($"{name} uniform not found on shader."); } - _gl.UniformMatrix4(location, 1, false, (float*) &value); + GL.UniformMatrix4(location, 1, false, (float*) &value); } public void SetUniform(string name, bool value) => SetUniform(name, Convert.ToUInt32(value)); public void SetUniform(string name, uint value) { - int location = _gl.GetUniformLocation(_handle, name); + int location = GL.GetUniformLocation(_handle, name); if (location == -1) { throw new Exception($"{name} uniform not found on shader."); } - _gl.Uniform1(location, value); + GL.Uniform1(location, value); } public void SetUniform(string name, float value) { - int location = _gl.GetUniformLocation(_handle, name); + int location = GL.GetUniformLocation(_handle, name); if (location == -1) { throw new Exception($"{name} uniform not found on shader."); } - _gl.Uniform1(location, value); + GL.Uniform1(location, value); } public void SetUniform(string name, Vector3 value) { - int location = _gl.GetUniformLocation(_handle, name); + int location = GL.GetUniformLocation(_handle, name); if (location == -1) { throw new Exception($"{name} uniform not found on shader."); } - _gl.Uniform3(location, value.X, value.Y, value.Z); + GL.Uniform3(location, value.X, value.Y, value.Z); } public void SetUniform(string name, Vector4 value) { - int location = _gl.GetUniformLocation(_handle, name); + int location = GL.GetUniformLocation(_handle, name); if (location == -1) { throw new Exception($"{name} uniform not found on shader."); } - _gl.Uniform4(location, value.X, value.Y, value.Z, value.W); + GL.Uniform4(location, value.X, value.Y, value.Z, value.W); } public int GetUniformLocation(string uniform) { if (!_uniformToLocation.TryGetValue(uniform, out int location)) { - location = _gl.GetUniformLocation(_handle, uniform); + location = GL.GetUniformLocation(_handle, uniform); _uniformToLocation.Add(uniform, location); if (location == -1) { @@ -124,7 +121,7 @@ public int GetAttribLocation(string attrib) { if (!_attribLocation.TryGetValue(attrib, out int location)) { - location = _gl.GetAttribLocation(_handle, attrib); + location = GL.GetAttribLocation(_handle, attrib); _attribLocation.Add(attrib, location); if (location == -1) { @@ -136,18 +133,18 @@ public int GetAttribLocation(string attrib) public void Dispose() { - _gl.DeleteProgram(_handle); + GL.DeleteProgram(_handle); } - private uint LoadShader(ShaderType type, string file) + private int LoadShader(ShaderType type, string file) { var executingAssembly = Assembly.GetExecutingAssembly(); using var stream = executingAssembly.GetManifestResourceStream($"{executingAssembly.GetName().Name}.Resources.{file}"); using var reader = new StreamReader(stream); - uint handle = _gl.CreateShader(type); - _gl.ShaderSource(handle, reader.ReadToEnd()); - _gl.CompileShader(handle); - string infoLog = _gl.GetShaderInfoLog(handle); + var handle = GL.CreateShader(type); + GL.ShaderSource(handle, reader.ReadToEnd()); + GL.CompileShader(handle); + string infoLog = GL.GetShaderInfoLog(handle); if (!string.IsNullOrWhiteSpace(infoLog)) { throw new Exception($"Error compiling shader of type {type}, failed with error {infoLog}"); diff --git a/FModel/Views/Snooper/Skybox.cs b/FModel/Views/Snooper/Skybox.cs index 65c992ff..481108bf 100644 --- a/FModel/Views/Snooper/Skybox.cs +++ b/FModel/Views/Snooper/Skybox.cs @@ -1,12 +1,11 @@ using System; -using Silk.NET.OpenGL; +using OpenTK.Graphics.OpenGL4; namespace FModel.Views.Snooper; public class Skybox : IDisposable { - private uint _handle; - private GL _gl; + private int _handle; private BufferObject _ebo; private BufferObject _vbo; @@ -65,25 +64,23 @@ public class Skybox : IDisposable public Skybox() {} - public void Setup(GL gl) + public void Setup() { - _gl = gl; + _handle = GL.CreateProgram(); - _handle = _gl.CreateProgram(); + _ebo = new BufferObject(Indices, BufferTarget.ElementArrayBuffer); + _vbo = new BufferObject(Vertices, BufferTarget.ArrayBuffer); + _vao = new VertexArrayObject(_vbo, _ebo); - _ebo = new BufferObject(_gl, Indices, BufferTargetARB.ElementArrayBuffer); - _vbo = new BufferObject(_gl, Vertices, BufferTargetARB.ArrayBuffer); - _vao = new VertexArrayObject(_gl, _vbo, _ebo); - - _cubeMap = new Texture(_gl, _textures); - _shader = new Shader(_gl, "skybox"); + _cubeMap = new Texture(_textures); + _shader = new Shader("skybox"); _vao.VertexAttributePointer(0, 3, VertexAttribPointerType.Float, 3, 0); // position } public void Bind(Camera camera) { - _gl.DepthFunc(DepthFunction.Lequal); + GL.DepthFunc(DepthFunction.Lequal); _vao.Bind(); @@ -99,9 +96,9 @@ public void Bind(Camera camera) _shader.SetUniform("cubemap", 0); - _gl.DrawArrays(PrimitiveType.Triangles, 0, 36); + GL.DrawArrays(PrimitiveType.Triangles, 0, 36); - _gl.DepthFunc(DepthFunction.Less); + GL.DepthFunc(DepthFunction.Less); } public void Dispose() @@ -110,6 +107,6 @@ public void Dispose() _vbo.Dispose(); _vao.Dispose(); _shader.Dispose(); - _gl.DeleteProgram(_handle); + GL.DeleteProgram(_handle); } } diff --git a/FModel/Views/Snooper/Snooper.cs b/FModel/Views/Snooper/Snooper.cs index d30b7142..44976058 100644 --- a/FModel/Views/Snooper/Snooper.cs +++ b/FModel/Views/Snooper/Snooper.cs @@ -1,55 +1,17 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Numerics; -using System.Runtime.InteropServices; +using System; using System.Threading; using System.Windows; using CUE4Parse.UE4.Assets.Exports; using CUE4Parse.UE4.Assets.Exports.Material; -using CUE4Parse.UE4.Assets.Exports.SkeletalMesh; -using CUE4Parse.UE4.Assets.Exports.StaticMesh; -using CUE4Parse.UE4.Assets.Exports.Texture; -using CUE4Parse.UE4.Objects.Core.Math; -using CUE4Parse.UE4.Objects.Core.Misc; -using CUE4Parse.UE4.Objects.Engine; -using CUE4Parse.UE4.Objects.UObject; -using CUE4Parse_Conversion.Meshes; -using ImGuiNET; -using Silk.NET.Core; -using Silk.NET.Input; -using Silk.NET.Maths; -using Silk.NET.OpenGL; -using Silk.NET.Windowing; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.PixelFormats; -using Image = SixLabors.ImageSharp.Image; +using OpenTK.Mathematics; +using OpenTK.Windowing.Common; +using OpenTK.Windowing.Desktop; namespace FModel.Views.Snooper; public class Snooper { - private readonly IWindow _window; - private GL _gl; - private SnimGui _imGui; - private Camera _camera; - private IKeyboard _keyboard; - private IMouse _mouse; - private RawImage _icon; - private Options _options; - - private readonly FramebufferObject _framebuffer; - private readonly Skybox _skybox; - private readonly Grid _grid; - - private Shader _shader; - private Shader _outline; - private Vector3 _diffuseLight; - private Vector3 _specularLight; - private readonly Dictionary _models; - - private Vector2D _size; - private float _previousSpeed; + private readonly FWindow _window; public Snooper() { @@ -57,366 +19,23 @@ public Snooper() var x = SystemParameters.MaximizedPrimaryScreenWidth; var y = SystemParameters.MaximizedPrimaryScreenHeight; - var options = WindowOptions.Default; - options.Size = _size = new Vector2D(Convert.ToInt32(x * ratio), Convert.ToInt32(y * ratio)); + var options = NativeWindowSettings.Default; + options.Size = new Vector2i(Convert.ToInt32(x * ratio), Convert.ToInt32(y * ratio)); options.WindowBorder = WindowBorder.Fixed; + options.Location = new Vector2i(Convert.ToInt32(x / 2.0) / (options.Size.X / 2), Convert.ToInt32(y / 2.0) / (options.Size.Y / 2)); + options.NumberOfSamples = Constants.SAMPLES_COUNT; options.Title = "Snooper"; - _window = Silk.NET.Windowing.Window.Create(options); - - unsafe - { - var info = Application.GetResourceStream(new Uri("/FModel;component/Resources/materialicon.png", UriKind.Relative)); - using var image = Image.Load(info.Stream); - var memoryGroup = image.GetPixelMemoryGroup(); - Memory array = new byte[memoryGroup.TotalLength * sizeof(Rgba32)]; - var block = MemoryMarshal.Cast(array.Span); - foreach (var memory in memoryGroup) - { - memory.Span.CopyTo(block); - block = block.Slice(memory.Length); - } - _icon = new RawImage(image.Width, image.Height, array); - } - _window.Load += OnLoad; - _window.Update += OnUpdate; - _window.Render += OnRender; - _window.Closing += OnClose; - _window.FramebufferResize += delegate(Vector2D vector2D) - { - _gl.Viewport(vector2D); - _size = vector2D; - }; - - _options = new Options(); - _framebuffer = new FramebufferObject(_size); - _skybox = new Skybox(); - _grid = new Grid(); - _models = new Dictionary(); + _window = new FWindow(GameWindowSettings.Default, options); } public void Run(CancellationToken cancellationToken, UObject export) { - switch (export) - { - case UStaticMesh st: - { - var guid = st.LightingGuid; - if (!_models.TryGetValue(guid, out _) && st.TryConvert(out var mesh)) - { - _models[guid] = new Model(export, st.Name, st.ExportType, mesh.LODs[0], mesh.LODs[0].Verts); - SetupCamera(mesh.BoundingBox *= Constants.SCALE_DOWN_RATIO); - _options.SelectModel(guid); - } - break; - } - case USkeletalMesh sk: - { - var guid = Guid.NewGuid(); - if (!_models.TryGetValue(guid, out _) && sk.TryConvert(out var mesh)) - { - _models[guid] = new Model(export, sk.Name, sk.ExportType, mesh.LODs[0], mesh.LODs[0].Verts, sk.MorphTargets, mesh.RefSkeleton); - SetupCamera(mesh.BoundingBox *= Constants.SCALE_DOWN_RATIO); - _options.SelectModel(guid); - } - break; - } - case UMaterialInstance mi: - { - var guid = Guid.NewGuid(); - if (!_models.TryGetValue(guid, out _)) - { - _models[guid] = new Cube(export, mi.Name, mi.ExportType, mi); - SetupCamera(new FBox(new FVector(-.65f), new FVector(.65f))); - } - break; - } - case UWorld wd: - { - var persistentLevel = wd.PersistentLevel.Load(); - var length = persistentLevel.Actors.Length; - for (var i = 0; i < length; i++) - { - cancellationToken.ThrowIfCancellationRequested(); - if (persistentLevel.Actors[i].Load() is not { } actor || actor.ExportType == "LODActor" || - !actor.TryGetValue(out FPackageIndex staticMeshComponent, "StaticMeshComponent") || - staticMeshComponent.Load() is not { } staticMeshComp) continue; - - if (!staticMeshComp.TryGetValue(out FPackageIndex staticMesh, "StaticMesh") && actor.Class is UBlueprintGeneratedClass) - foreach (var actorExp in actor.Class.Owner.GetExports()) - if (actorExp.TryGetValue(out staticMesh, "StaticMesh")) - break; - if (staticMesh?.Load() is not UStaticMesh m) - continue; - - Services.ApplicationService.ApplicationView.Status.UpdateStatusLabel($"Actor {i}/{length}"); - - var guid = m.LightingGuid; - var transform = new Transform - { - Position = staticMeshComp.GetOrDefault("RelativeLocation", FVector.ZeroVector) * Constants.SCALE_DOWN_RATIO, - Rotation = staticMeshComp.GetOrDefault("RelativeRotation", FRotator.ZeroRotator), - Scale = staticMeshComp.GetOrDefault("RelativeScale3D", FVector.OneVector) - }; - transform.Rotation.Yaw = -transform.Rotation.Yaw; - - if (_models.TryGetValue(guid, out var model)) - { - model.AddInstance(transform); - } - else if (m.TryConvert(out var mesh)) - { - model = new Model(export, m.Name, m.ExportType, mesh.LODs[0], mesh.LODs[0].Verts, null, null, transform); - - if (actor.TryGetAllValues(out FPackageIndex[] textureData, "TextureData")) - { - for (int j = 0; j < textureData.Length; j++) - { - if (textureData[j].Load() is not { } textureDataIdx) - continue; - - if (textureDataIdx.TryGetValue(out FPackageIndex diffuse, "Diffuse") && - diffuse.Load() is UTexture2D diffuseTexture) - model.Sections[j].Parameters.Diffuse = diffuseTexture; - if (textureDataIdx.TryGetValue(out FPackageIndex normal, "Normal") && - normal.Load() is UTexture2D normalTexture) - model.Sections[j].Parameters.Normal = normalTexture; - if (textureDataIdx.TryGetValue(out FPackageIndex specular, "Specular") && - specular.Load() is UTexture2D specularTexture) - model.Sections[j].Parameters.Specular = specularTexture; - } - } - if (staticMeshComp.TryGetValue(out FPackageIndex[] overrideMaterials, "OverrideMaterials")) - { - var max = model.Sections.Length - 1; - for (var j = 0; j < overrideMaterials.Length; j++) - { - if (j > max) break; - if (overrideMaterials[j].Load() is not UMaterialInterface unrealMaterial) continue; - model.Sections[j].SwapMaterial(unrealMaterial); - } - } - - _models[guid] = model; - } - } - _camera = new Camera(new Vector3(0f, 5f, 5f), Vector3.Zero, 0.01f, 1000f, 5f); - break; - } - default: - throw new ArgumentOutOfRangeException(nameof(export)); - } - - DoLoop(); - } - - public void SwapMaterial(UMaterialInstance mi) - { - if (!_models.TryGetValue(_options.SelectedModel, out var model) || - !_options.TryGetSection(model, out var section)) return; - - section.SwapMaterial(mi); - _options.SwapMaterial(false); - DoLoop(); - } - - private void DoLoop() - { - if (_options.Append) _options.Append = false; _window.Run(); - // if (_window.IsInitialized) - // { - // if (!_window.GLContext.IsCurrent) - // { - // _window.GLContext.MakeCurrent(); - // } - // - // _append = false; - // _window.IsVisible = true; - // var model = _models.Last(); - // model.Value.Setup(_gl); - // _imGui.Increment(model.Key); - // } - // else _window.Initialize(); - // - // while (!_window.IsClosing && _window.IsVisible) - // { - // _window.DoEvents(); - // if (!_window.IsClosing && _window.IsVisible) - // _window.DoUpdate(); - // if (_window.IsClosing || !_window.IsVisible) - // return; - // _window.DoRender(); - // } - // - // _window.DoEvents(); - // if (_window.IsClosing) _window.Reset(); } - private void SetupCamera(FBox box) - { - var far = box.Max.Max(); - var center = box.GetCenter(); - var position = new Vector3(0f, center.Z, box.Max.Y * 3); - var speed = far / 2f; - if (speed > _previousSpeed) - { - _camera = new Camera(position, center, 0.01f, far * 50f, speed); - _previousSpeed = _camera.Speed; - } - } - - private void OnLoad() - { - _window.SetWindowIcon(ref _icon); - _window.Center(); - - var input = _window.CreateInput(); - _keyboard = input.Keyboards[0]; - _mouse = input.Mice[0]; - - _gl = GL.GetApi(_window); - _gl.Enable(EnableCap.Blend); - _gl.Enable(EnableCap.DepthTest); - _gl.Enable(EnableCap.Multisample); - _gl.StencilOp(StencilOp.Keep, StencilOp.Replace, StencilOp.Replace); - _gl.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha); - - _imGui = new SnimGui(_gl, _window, input); - - _framebuffer.Setup(_gl); - _skybox.Setup(_gl); - _grid.Setup(_gl); - - _shader = new Shader(_gl); - _outline = new Shader(_gl, "outline"); - _diffuseLight = new Vector3(0.75f); - _specularLight = new Vector3(0.5f); - foreach (var model in _models.Values) - { - model.Setup(_gl); - } - } - - /// - /// friendly reminder this is called each frame - /// don't do crazy things inside - /// - private void OnRender(double deltaTime) - { - _imGui.Update((float) deltaTime); - - ClearWhatHasBeenDrawn(); // in main window - - _framebuffer.Bind(); // switch to dedicated window - ClearWhatHasBeenDrawn(); // in dedicated window - - _skybox.Bind(_camera); - _grid.Bind(_camera); - - var viewMatrix = _camera.GetViewMatrix(); - var projMatrix = _camera.GetProjectionMatrix(); - - _outline.Use(); - _outline.SetUniform("uView", viewMatrix); - _outline.SetUniform("uProjection", projMatrix); - _outline.SetUniform("viewPos", _camera.Position); - - _shader.Use(); - _shader.SetUniform("uView", viewMatrix); - _shader.SetUniform("uProjection", projMatrix); - _shader.SetUniform("viewPos", _camera.Position); - - _shader.SetUniform("material.diffuseMap", 0); - _shader.SetUniform("material.normalMap", 1); - _shader.SetUniform("material.specularMap", 2); - _shader.SetUniform("material.emissionMap", 3); - - _shader.SetUniform("light.position", _camera.Position); - _shader.SetUniform("light.diffuse", _diffuseLight); - _shader.SetUniform("light.specular", _specularLight); - - foreach (var model in _models.Values.Where(model => model.Show)) - { - model.Bind(_shader); - } - _gl.Enable(EnableCap.StencilTest); // I don't get why this must be here but it works now so... - foreach (var model in _models.Values.Where(model => model.IsSelected && model.Show)) - { - model.Outline(_outline); - } - - _imGui.Construct(ref _options, _size, _framebuffer, _camera, _mouse, _models); - - _framebuffer.BindMsaa(); - _framebuffer.Bind(0); // switch back to main window - _framebuffer.BindStuff(); - - _imGui.Render(); // render ImGui in main window - } - - private void ClearWhatHasBeenDrawn() - { - _gl.ClearColor(1.0f, 0.102f, 0.129f, 1.0f); - _gl.Clear((uint) ClearBufferMask.ColorBufferBit | (uint) ClearBufferMask.DepthBufferBit | (uint) ClearBufferMask.StencilBufferBit); - _gl.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Fill); - } - - private void OnUpdate(double deltaTime) - { - if (ImGui.GetIO().WantTextInput) return; - var multiplier = _keyboard.IsKeyPressed(Key.ShiftLeft) ? 2f : 1f; - var moveSpeed = _camera.Speed * multiplier * (float) deltaTime; - if (_keyboard.IsKeyPressed(Key.W)) - _camera.Position += moveSpeed * _camera.Direction; - if (_keyboard.IsKeyPressed(Key.S)) - _camera.Position -= moveSpeed * _camera.Direction; - if (_keyboard.IsKeyPressed(Key.A)) - _camera.Position -= Vector3.Normalize(Vector3.Cross(_camera.Direction, _camera.Up)) * moveSpeed; - if (_keyboard.IsKeyPressed(Key.D)) - _camera.Position += Vector3.Normalize(Vector3.Cross(_camera.Direction, _camera.Up)) * moveSpeed; - if (_keyboard.IsKeyPressed(Key.E)) - _camera.Position += moveSpeed * _camera.Up; - if (_keyboard.IsKeyPressed(Key.Q)) - _camera.Position -= moveSpeed * _camera.Up; - if (_keyboard.IsKeyPressed(Key.X)) - _camera.ModifyZoom(-.5f); - if (_keyboard.IsKeyPressed(Key.C)) - _camera.ModifyZoom(+.5f); - - if (_keyboard.IsKeyPressed(Key.H)) - { - // because we lose GLContext when the window is invisible after a few seconds (it's apparently a bug) - // we can't use GLContext back on next load and so, for now, we basically have to reset the window - // if we can't use GLContext, we can't generate handles, can't interact with IsVisible, State, etc - // tldr we dispose everything but don't clear models, so the more you append, the longer it takes to load - _options.Append = true; - _window.Close(); - // _window.IsVisible = false; - } - if (_keyboard.IsKeyPressed(Key.Escape)) - _window.Close(); - } - - private void OnClose() + public void SwapMaterial(UMaterialInstance mi) { - _framebuffer.Dispose(); - _grid.Dispose(); - _skybox.Dispose(); - _shader.Dispose(); - _outline.Dispose(); - foreach (var model in _models.Values) - { - model.Dispose(); - } - if (!_options.Append) - { - _models.Clear(); - _options.Reset(); - _previousSpeed = 0f; - } - _imGui.Dispose(); - _window.Dispose(); - _gl.Dispose(); + _window.SwapMaterial(mi); } } diff --git a/FModel/Views/Snooper/Texture.cs b/FModel/Views/Snooper/Texture.cs index f2ca7f39..e87899e5 100644 --- a/FModel/Views/Snooper/Texture.cs +++ b/FModel/Views/Snooper/Texture.cs @@ -1,17 +1,14 @@ -using Silk.NET.OpenGL; -using System; +using System; using System.Windows; using CUE4Parse.UE4.Assets.Exports.Texture; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.PixelFormats; +using OpenTK.Graphics.OpenGL4; using SkiaSharp; namespace FModel.Views.Snooper; public class Texture : IDisposable { - private readonly uint _handle; - private readonly GL _gl; + private readonly int _handle; private readonly TextureType _type; public readonly string Type; @@ -20,52 +17,50 @@ public class Texture : IDisposable public readonly EPixelFormat Format; public readonly uint ImportedWidth; public readonly uint ImportedHeight; - public readonly uint Width; - public readonly uint Height; + public readonly int Width; + public readonly int Height; public string Label; - public Texture(GL gl, TextureType type) + public Texture(TextureType type) { - _gl = gl; - _handle = _gl.GenTexture(); + _handle = GL.GenTexture(); _type = type; - Label = "(?) Click to Copy Path"; } - public Texture(GL gl, uint width, uint height) : this(gl, TextureType.MsaaFramebuffer) + public Texture(uint width, uint height) : this(TextureType.MsaaFramebuffer) { Width = width; Height = height; Bind(TextureUnit.Texture0); - _gl.TexImage2DMultisample(TextureTarget.Texture2DMultisample, Constants.SAMPLES_COUNT, InternalFormat.Rgb, Width, Height, Silk.NET.OpenGL.Boolean.True); + GL.TexImage2DMultisample(TextureTargetMultisample.Texture2DMultisample, Constants.SAMPLES_COUNT, PixelInternalFormat.Rgb, Width, Height, true); - _gl.TexParameter(TextureTarget.Texture2DMultisample, TextureParameterName.TextureMinFilter, (int) GLEnum.Nearest); - _gl.TexParameter(TextureTarget.Texture2DMultisample, TextureParameterName.TextureMagFilter, (int) GLEnum.Nearest); - _gl.TexParameter(TextureTarget.Texture2DMultisample, TextureParameterName.TextureWrapS, (int) GLEnum.ClampToEdge); - _gl.TexParameter(TextureTarget.Texture2DMultisample, TextureParameterName.TextureWrapT, (int) GLEnum.ClampToEdge); + GL.TexParameter(TextureTarget.Texture2DMultisample, TextureParameterName.TextureMinFilter, (int) GLEnum.Nearest); + GL.TexParameter(TextureTarget.Texture2DMultisample, TextureParameterName.TextureMagFilter, (int) GLEnum.Nearest); + GL.TexParameter(TextureTarget.Texture2DMultisample, TextureParameterName.TextureWrapS, (int) GLEnum.ClampToEdge); + GL.TexParameter(TextureTarget.Texture2DMultisample, TextureParameterName.TextureWrapT, (int) GLEnum.ClampToEdge); - _gl.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, TextureTarget.Texture2DMultisample, _handle, 0); + GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, TextureTarget.Texture2DMultisample, _handle, 0); } - public unsafe Texture(GL gl, int width, int height) : this(gl, TextureType.Framebuffer) + public unsafe Texture(int width, int height) : this(TextureType.Framebuffer) { - Width = (uint) width; - Height = (uint) height; + Width = width; + Height = height; Bind(TextureUnit.Texture0); - _gl.TexImage2D(TextureTarget.Texture2D, 0, (int) InternalFormat.Rgb, Width, Height, 0, PixelFormat.Rgb, PixelType.UnsignedByte, null); + GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgb, Width, Height, 0, PixelFormat.Rgb, PixelType.UnsignedByte, null); - _gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int) GLEnum.Linear); - _gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int) GLEnum.Linear); - _gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int) GLEnum.ClampToEdge); - _gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int) GLEnum.ClampToEdge); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int) GLEnum.Linear); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int) GLEnum.Linear); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int) GLEnum.ClampToEdge); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int) GLEnum.ClampToEdge); - _gl.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, TextureTarget.Texture2D, _handle, 0); + GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, TextureTarget.Texture2D, _handle, 0); } - public unsafe Texture(GL gl, byte[] data, uint width, uint height, SKColorType colorType, UTexture2D texture2D) : this(gl, TextureType.Normal) + public unsafe Texture(byte[] data, uint width, uint height, SKColorType colorType, UTexture2D texture2D) : this(TextureType.Normal) { Type = texture2D.ExportType; Name = texture2D.Name; @@ -79,17 +74,17 @@ public unsafe Texture(GL gl, byte[] data, uint width, uint height, SKColorType c fixed (void* d = &data[0]) { - _gl.TexImage2D(TextureTarget.Texture2D, 0, (int) InternalFormat.Rgb, Width, Height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, d); - _gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int) GLEnum.LinearMipmapLinear); - _gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int) GLEnum.Linear); - _gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureBaseLevel, 0); - _gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMaxLevel, 8); + GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgb, Width, Height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, d); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int) GLEnum.LinearMipmapLinear); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int) GLEnum.Linear); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureBaseLevel, 0); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMaxLevel, 8); - _gl.GenerateMipmap(TextureTarget.Texture2D); + GL.GenerateMipmap(GenerateMipmapTarget.Texture2D); } } - public unsafe Texture(GL gl, string[] textures) : this(gl, TextureType.Cubemap) + public unsafe Texture(string[] textures) : this(TextureType.Cubemap) { Bind(TextureUnit.Texture0); @@ -99,7 +94,7 @@ public unsafe Texture(GL gl, string[] textures) : this(gl, TextureType.Cubemap) using var img = Image.Load(info.Stream); Width = (uint) img.Width; // we don't care anyway Height = (uint) img.Height; // we don't care anyway - _gl.TexImage2D(TextureTarget.TextureCubeMapPositiveX + t, 0, InternalFormat.Rgba8, Width, Height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, null); + GL.TexImage2D(TextureTarget.TextureCubeMapPositiveX + t, 0, PixelInternalFormat.Rgba8, Width, Height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, null); img.ProcessPixelRows(accessor => { @@ -107,37 +102,37 @@ public unsafe Texture(GL gl, string[] textures) : this(gl, TextureType.Cubemap) { fixed (void* data = accessor.GetRowSpan(y)) { - gl.TexSubImage2D(TextureTarget.TextureCubeMapPositiveX + t, 0, 0, y, (uint) accessor.Width, 1, PixelFormat.Rgba, PixelType.UnsignedByte, data); + GL.TexSubImage2D(TextureTarget.TextureCubeMapPositiveX + t, 0, 0, y, (uint) accessor.Width, 1, PixelFormat.Rgba, PixelType.UnsignedByte, data); } } }); } - _gl.TexParameter(TextureTarget.TextureCubeMap, TextureParameterName.TextureMinFilter, (int) GLEnum.Linear); - _gl.TexParameter(TextureTarget.TextureCubeMap, TextureParameterName.TextureMagFilter, (int) GLEnum.Linear); - _gl.TexParameter(TextureTarget.TextureCubeMap, TextureParameterName.TextureWrapR, (int) GLEnum.ClampToEdge); - _gl.TexParameter(TextureTarget.TextureCubeMap, TextureParameterName.TextureWrapS, (int) GLEnum.ClampToEdge); - _gl.TexParameter(TextureTarget.TextureCubeMap, TextureParameterName.TextureWrapT, (int) GLEnum.ClampToEdge); + GL.TexParameter(TextureTarget.TextureCubeMap, TextureParameterName.TextureMinFilter, (int) GLEnum.Linear); + GL.TexParameter(TextureTarget.TextureCubeMap, TextureParameterName.TextureMagFilter, (int) GLEnum.Linear); + GL.TexParameter(TextureTarget.TextureCubeMap, TextureParameterName.TextureWrapR, (int) GLEnum.ClampToEdge); + GL.TexParameter(TextureTarget.TextureCubeMap, TextureParameterName.TextureWrapS, (int) GLEnum.ClampToEdge); + GL.TexParameter(TextureTarget.TextureCubeMap, TextureParameterName.TextureWrapT, (int) GLEnum.ClampToEdge); } - public unsafe Texture(GL gl, uint width, uint height, IntPtr data) : this(gl, TextureType.Normal) + public unsafe Texture(uint width, uint height, IntPtr data) : this(TextureType.Normal) { Width = width; Height = height; Bind(TextureTarget.Texture2D); - _gl.TexStorage2D(GLEnum.Texture2D, 1, SizedInternalFormat.Rgba8, Width, Height); - _gl.TexSubImage2D(GLEnum.Texture2D, 0, 0, 0, Width, Height, PixelFormat.Bgra, PixelType.UnsignedByte, (void*) data); + GL.TexStorage2D(TextureTarget2d.Texture2D, 1, SizedInternalFormat.Rgba8, Width, Height); + GL.TexSubImage2D(TextureTarget.Texture2D, 0, 0, 0, Width, Height, PixelFormat.Bgra, PixelType.UnsignedByte, (void*) data); - _gl.TexParameterI(GLEnum.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat); - _gl.TexParameterI(GLEnum.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat); + GL.TexParameterI(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat); + GL.TexParameterI(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat); - _gl.TexParameterI(GLEnum.Texture2D, TextureParameterName.TextureMaxLevel, 1 - 1); + GL.TexParameterI(TextureTarget.Texture2D, TextureParameterName.TextureMaxLevel, 1 - 1); } public void Bind(TextureUnit textureSlot) { - _gl.ActiveTexture(textureSlot); + GL.ActiveTexture(textureSlot); Bind(_type switch { TextureType.Cubemap => TextureTarget.TextureCubeMap, @@ -148,24 +143,24 @@ public void Bind(TextureUnit textureSlot) public void Bind(TextureTarget target) { - _gl.BindTexture(target, _handle); + GL.BindTexture(target, _handle); } - public void SetMinFilter(TextureMinFilter filter) + public void SetMinFilter(int filter) { - _gl.TexParameterI(GLEnum.Texture2D, TextureParameterName.TextureMinFilter, (int)filter); + GL.TexParameterI(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, ref filter); } - public void SetMagFilter(TextureMagFilter filter) + public void SetMagFilter(int filter) { - _gl.TexParameterI(GLEnum.Texture2D, TextureParameterName.TextureMagFilter, (int)filter); + GL.TexParameterI(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, ref filter); } public IntPtr GetPointer() => (IntPtr) _handle; public void Dispose() { - _gl.DeleteTexture(_handle); + GL.DeleteTexture(_handle); } } diff --git a/FModel/Views/Snooper/VertexArrayObject.cs b/FModel/Views/Snooper/VertexArrayObject.cs index c1d49a91..6f2dc2a6 100644 --- a/FModel/Views/Snooper/VertexArrayObject.cs +++ b/FModel/Views/Snooper/VertexArrayObject.cs @@ -1,72 +1,70 @@ using System; using System.Numerics; -using Silk.NET.OpenGL; +using OpenTK.Graphics.OpenGL4; namespace FModel.Views.Snooper; public class VertexArrayObject : IDisposable where TVertexType : unmanaged where TIndexType : unmanaged { - private readonly uint _handle; - private readonly GL _gl; + private readonly int _handle; - public VertexArrayObject(GL gl, BufferObject vbo, BufferObject ebo) + public VertexArrayObject(BufferObject vbo, BufferObject ebo) { - _gl = gl; + _handle = GL.GenVertexArray(); - _handle = _gl.GenVertexArray(); Bind(); vbo.Bind(); ebo.Bind(); } - public unsafe void VertexAttributePointer(uint index, int count, VertexAttribPointerType type, uint vertexSize, int offSet) + public unsafe void VertexAttributePointer(uint index, int count, VertexAttribPointerType type, int vertexSize, int offset) { switch (type) { case VertexAttribPointerType.Int: - _gl.VertexAttribIPointer(index, count, VertexAttribIType.Int, vertexSize * (uint) sizeof(TVertexType), (void*) (offSet * sizeof(TVertexType))); + GL.VertexAttribIPointer(index, count, VertexAttribIntegerType.Int, vertexSize * sizeof(TVertexType), (IntPtr) (offset * sizeof(TVertexType))); break; default: - _gl.VertexAttribPointer(index, count, type, false, vertexSize * (uint) sizeof(TVertexType), (void*) (offSet * sizeof(TVertexType))); + GL.VertexAttribPointer(index, count, type, false, vertexSize * sizeof(TVertexType), offset * sizeof(TVertexType)); break; } - _gl.EnableVertexAttribArray(index); + GL.EnableVertexAttribArray(index); } public void Bind() { - _gl.BindVertexArray(_handle); + GL.BindVertexArray(_handle); } public unsafe void BindInstancing() { Bind(); - var vec4Size = (uint) sizeof(Vector4); - _gl.EnableVertexAttribArray(7); - _gl.VertexAttribPointer(7, 4, VertexAttribPointerType.Float, false, 4 * vec4Size, (void*)0); - _gl.EnableVertexAttribArray(8); - _gl.VertexAttribPointer(8, 4, VertexAttribPointerType.Float, false, 4 * vec4Size, (void*)(1 * vec4Size)); - _gl.EnableVertexAttribArray(9); - _gl.VertexAttribPointer(9, 4, VertexAttribPointerType.Float, false, 4 * vec4Size, (void*)(2 * vec4Size)); - _gl.EnableVertexAttribArray(10); - _gl.VertexAttribPointer(10, 4, VertexAttribPointerType.Float, false, 4 * vec4Size, (void*)(3 * vec4Size)); + var size = sizeof(Vector4); + GL.EnableVertexAttribArray(7); + GL.VertexAttribPointer(7, 4, VertexAttribPointerType.Float, false, 4 * size, 0); + GL.EnableVertexAttribArray(8); + GL.VertexAttribPointer(8, 4, VertexAttribPointerType.Float, false, 4 * size, 1 * size); + GL.EnableVertexAttribArray(9); + GL.VertexAttribPointer(9, 4, VertexAttribPointerType.Float, false, 4 * size, 2 * size); + GL.EnableVertexAttribArray(10); + GL.VertexAttribPointer(10, 4, VertexAttribPointerType.Float, false, 4 * size, 3 * size); - _gl.VertexAttribDivisor(7, 1); - _gl.VertexAttribDivisor(8, 1); - _gl.VertexAttribDivisor(9, 1); - _gl.VertexAttribDivisor(10, 1); + GL.VertexAttribDivisor(7, 1); + GL.VertexAttribDivisor(8, 1); + GL.VertexAttribDivisor(9, 1); + GL.VertexAttribDivisor(10, 1); Unbind(); } public void Unbind() { - _gl.BindVertexArray(0); + GL.BindVertexArray(0); } public void Dispose() { - _gl.DeleteVertexArray(_handle); + GL.DeleteVertexArray(_handle); } } diff --git a/FModel/glfw3.dll b/FModel/glfw3.dll deleted file mode 100644 index 733404cf4a8b7e78334d3e6a18acc6c681077e3c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 216576 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䞧n4FwwHms&J6i*z1MWHn`e4XD0z;2fchB9+98>D| zBAEvXVLpK!8E6n`T0*(0jPdi-iTC^Dqwu)%{y#7l;5l>*W7Ui=TKjO z58asqAB-M+U@>+O-A}wYz?~bkcRsg7auDDz#d|QT<*tVVVZ72Bdl|?m9cP^yqtM$i z!f=u|1?G0DigPhJR(g4e{$~Cyl+~k+R@kWNtN$JnwIn~l_0X}s3HoaIYb!`!Ym%T^4?A{98;Ta&a=!s2^VX_S`pHT*D$)T#mh_ zU94U`ulXfZFN!-t&+Q01;S!Xi^rN}h9$hY(W_O9rB>Sw25{iP}C|8--o7|4uKGNXA z5}EZNs@T{P8x@u{dus9VzNIXF7V-n#nk-EGYYr(j5E9h#2WG*CprAWPM0@OHVK3I~ z6hyX9K1za36oSG z6qw6xN*}c+rIOvnBHShU6nbjL-v!}@HnUf#H>i{Fv@(AAQrJI>!s{t{NVPvusDgjw$bAC1*w zn;I`}!HxuJx9II7a>T_77CtI=eVoHKF4s0L2TvWh5@kp?#IU5%@*~N@hFf#6FJn$) zOw7dCX*l2F6bA+kGKIl2SJngqmX>?5@>zLV7Hp#@f*sHmo`t_>Z-g=Bo?JrM^OnCu0GKsoH z8%Jl7!zc2mfhn{#8zFOkg!gOkd=1RbM9}R&J)6-yeq64p!RFF-EO~u6U;y*KCw9-< z%p)c`>+dM(+!khXHVe0cA4lK~PIK@i5>LEy?4`jC79jXyPhqj*i}J*joD^W)#~xQ< z&Z=+5)Jm)&^D1zRnU$^Y#e3R9&%wVhvC6)77unc>Zg##HYYpe)z!7ic0F0NR4JV&~ z@aP9yX6dr^#adeFvqoHlz;FF=VSE@bF803)b!j-qGrhUM#@`I{N!4QuJ;($9a)gNt ze$ISYWr2>3;U>6@<~9pvJB53dbynP*=qP$*)R#3CV)rdBK}ExhfhH~~`EeZnT9ILk z$On0@0uU;VxnXQe*^O}Y<@qZa!V}4uHrkemnJ~N>&{$U6 zb{H&qP1;YT;HE0PbRikW)XEezU?v3orRj)*u@DK>8~8eocua4~(~!&5{jy`z#ksjK z-xR?wm;o1>N*+0u`292S8#C}($`n5InS-kdmH{1R^PiF(2J=~NaC4NO1}}{tFm{Ob z88i#(0jre#VvPpdBwev~8%^IW{1Y&o*VIsv1l;~;IET5STWA=Frl$5!PB$La0@YHp z2b(^TH&}x$b^&39k|*l=8XPQU_0=*Q+Po5u)B{jcE7qY1M(>9(6k)xr+am}kmZ$Rk zc@&!1aNYw18i0$El>r?u_seJw3)kluPA_mo=8ZNo1Pb=^A8UIXZr&kB@?Yxq9O3gF`qcb=SuM*^EdH3;5~Z7 z83#HV3v1xhWB?4*&03(y0gX8Ta3}$g@qbJNQC}jyKgyO?UOe@Twkxbhj(BR54Qhb>|F_9l*RSmkSs(XZbVk8 zg03}aGyyl+5E4Q(OLoZy5=aRM9u;zIASIAA>?&A9(hcDHv3S&a*IL`E{o7WnRt~QO zkRV>*32GJZjqyNj5xn;QdvE6ZcE8OgBphCdX6KuG=FOWo@4b05v(5nW*-?yF`h+)* zykl^?h499cH?8T51mX3QcPMVR5Z-3;CgEBP@}9YF8N7n~NG(7Q1cz$CKNySQ=)f!* zaM3g#p(fkK8T=M%wu&qUkD=X1A*1wGS6n%9vvoMzG#r1^P1!{X%N ztQwI9M;0T}^sg47e?_P)`l3p~WI!tcl>Lp%;JE<1y0l%v+gG@Eo>8AeLgtf}Qby@% zj`Wc(4;|$-D!3K{-U&p6F8Q+qj20X)u#Pog- zL$;$2t$?hW#Ze`3`yEh3UBzR-yUeA$kGml;rB-0T_-@D@(6ik~!pl~m8Q=VvPGiaU zg}yu&&BEE5P=_0n|5YSoxaJTtZPS2eiM8y%ZOEEXv773c?Uh&ao) z7+W=-9PmhBiuwd5Bn0z0Azg^M!WsFgE5v6!NfQk@38iYrOw+`@bwfMleBIE#`^L5R@bE;3wNGu7>VR!5za1SA!|Zrd{25 z@m7F%==t1J(Z~eCs8rEN9!DNOab8FRAX_dl0RIUzQ1|#YtD|q3*?!8~!aCX`Q=1o}Qv=j%* zuP;^hhRoMOAxMT@E}pOsr7op}1!j59u?^8{b8{dI{fF^(lIu|e%$$H|GeKBja6W0U z$M|R>f|wLOh$bxWIKj7MAYOF$ElD&W;F-Q)<+k)SIktBg;qAX%Vkq7vgW;9~vI3C` zR0@i1p}TSm5!_L--^E9iNToftNX*KGB>pZ%`FFKm!n(lZGi|_xCHoB%ecj7L!fPOL0>eC)D|N#0D$&MFrd%r<2jCO z0j*&n&UnWuzyYBDc^j}5W&BsN>I1$LzJdSueP9VsSWqTRSu3rWRz|zm2|DN=O;&4o z-e%Ffg$)K3tuA@86i88_;ky{9Oa_D)8yfx}1nDMR!L;eFKwZkefUpEaAndr`ON4cv z1-U|lz}$-{_6nj}=xH*WM25@(l-GV6W*^!NjmbSIfu4nlgD7V*B~_hCHYGF0BrEPEw|&#cxweZNX&BOJ&-C`03S3 z>Ct|tDHy83-Ahgw&2cl5p6g3~=%5$s}!hShC zq>(G_2cA4+&gF)Q5;|}-mzrC7F=T$>Le!eTMPH83h+Of=rA!-*9tV@rUG?aMvKbxk zh(|DtX$Ihgh|^?GdmOel{a^(MhRI6&6NFqWw%kjarXlk!q>{soqH%$3(OE>{w^ZO; z7;n$~cZ^@tc;)|$}#CtPnt1Azgvuw~=`c)qA5j5UTljnH&y}LSKr^akXDgi`%4##H~Okb?Kt0R%X|g;Ow#*OD%V>%m}fz0k>QMrIluY8MePF-U6nhW(0GEWmS- zq&P$qX5s!2Gyr5^4&4@4cSG9xZF_K=SFVq2p74^H(!xQxzLoIPX$>Y;6igbBS$Igz zuTau$d$>3>xZ2NYL*|PCOl&M=9Q^h20phlVfHMH2lJ3*WjUsGiX9d^94Qnc`t)!}M_<_t%VVM1H!E$JG21 z6$Kd&l(oPW|12gY&GV{XQz-M37U0s&CrY-EC}@FdW?-G!e$!g!{6gk&lmQIQ5lDSI zQg;-9zf2WZxE23GDXQ6-RQl_HVI2~#1v%c*>VM?=NS7_Pkare!(61iH8TL|5+Rlm$=o zVJZbb2Gu1ixtGk~;uM4^rS1i5u^o&q&sZ9-!{TI#tyHCKqAtz#0IRkOyQ- zY?=g5yM@TWrF3KSi-09;sGP6U%9)*Ss z4~49v08c-NS`fv03atU#u>&UA-p|m!2ODTyF@UEek~YFsn_%7qSsBx4ou*R;-4m#=`u7^!wL2LCGbLJHoeafqCl^S zM$yP~x9krkqaZQzo?O$qT_-ZsJVtZ^n~p#YoUDN0=`=ZDVZ-R)7|;rZdz(uO-s*P|8w(nRe?4++f%H5wSl0YflIrNyF1&`U`ow^GrV4n^RldA(ux2APLNE0ov`$Yt8TZWEh z0Y)&5v_H9t?);(0)hrbyCDNBL-Nlz~oq6^5iSSHKjD#|R5x z1d&;aU{!^&AH_$MNW=lo%}M5*uLm4XGTu zJO(qdI0eK`&jRFefP-NDPZ;;bhIJ>3-cwkg3~Q(e$WVs~I)f1$0qfotA5kKeb}h7& zfFG1#kEa3R7*jApF59zd=pupp7|`ubwCyTv-w>k@TqOOb3jcLeRA_1QiTJX-W)1!Y zm{z1q>`TlUge=lkl={SU4S5RXjFZgTXQ%~@={almJ~vCUW+Oynm^I@ipZO`NAA&Mw zOEB&4G-M>lczz}k%%yA%ndfpbkU%bG((dh`^)}l7i`)^{|si3-#i~lkGS4*R@CmZfN+Y)fCaSe+$Q>zNQbBqI0%a2ocJt zn*rnUL2y_e1U`ygDC4*raOD$ieu9%lsEgvuYG?y$7JV*6_NHQP^Oj`xJ!vOVQie1Qm9DY zgrCeYhC-59j!mMuExD-uOH%nC?kFUA*dNeSQbYmi%(D1uZOvDI_@$lu( zL?Xz9X-s-T=6_gY9R70tTG zmLj8+hM++G^&IFw;o9pUI!KibLSbs!SZHrMQ4oq-{>y;-8q%ae_<{6&+ug5WgOeP-&v#A4&mLtp8;gxrM*PT&1az zJRPsP*a@63+aCm*aVhSrDrH3t9;@D4qHIwwfJ9dd6YR0StsbqUzYpJPpYq762+2#8v&I|bp3Lm1-LyDF4GdBo%N4vRCNIiQ{ zFp%Z?B@$VjQ1;g*Vox&PiX2LwOTN;B1eBHNWF?JQnXs zvEp;J-+2X`>)MOfh=mKgrPFL|k=T|Cd%VIZgo}V>9o!-M>yNEM^ z1F*I$(-VX|tC*a07d1)?nTK&1yNH<3Zu3Yj56r~qIG<2TpK&OcJm*l{FFDfoIMUwmJMZ$B-qEoDjI3cPS_ag-OZmi)>rAkv zVOPedGS%_>*mhg#>&zXW>UTbhhy;`4Q@?XV@IQZraWCOq>1yn-DK8Qg#K>4?p+M|k z-zsR(bl?aoe$$SLRus>S`~=xH(R6aB0o44GerUsY&>{0>3! zzs7E0VY!`xr!p%nN}h=#*HRjKRO5kLXviECx%_IvaAp#t26)SN;)A z76J?+a9`8}5$=f&FBakFQ#hIE0Ek(B-(t*l)3T+Qes-lVMp7&4Z3$-p+17$n=d0VC zAEyJT7}n3Q!3An_G(K^%ML}arls(Senk-K#4x!&6cD(Ki_SwXbPLQkA3h<3=w)iT(%7S)-ANQ zVSl!CHGo7=rwyjRAhcspIGn;}ogapMVA{v9XFZSbz-gkq*l2U4IUh`$wmM|K6bZ36 zw_4T#EdJQxcYlr!4hWggMJDtv)$>dI4)IQpz;~i`Z8$~acM`fkfxlNm<}D};x(C+1 zyv`bkw@~LRs_q3a6r4KmR0yPb?_8~+ym5g2VGeQL?OQn3;2A_aOt!wDy*@H*!C$vJOs7;ODAPHAIJ24{L}!i&uAZ( zpdBuMQp(LVh1aTz?gH%Jp#(H9(*=@_rXZvNAwWCKWBfv#B4ZQ$Jq{ZbdpdoVv7?)<{{LN0Qa-wRE?jlq)^(W+j~wpI+3;=f)s!uTsn!^rYC~eW1M^s z?xArct-L&~%fm{vi!|~?YEq$hHQ{_Ox8F+&_Umv4Tu_d8{zyg9U5e0CL<&7ZKjK@< zr2{0MGZ~|_a%sr0poKN~qLLTTM69t?Nu^fFQd9z(-e!E_M$jAl3%&*yI~7U)L;wR5 z9^>EO0ZJE$oi;g~Xum_Dpi1eXppz%-`EDyf;$et0TmEwNnD`!{HC z8MWADPuqrtzl3xg4oqU%Ka^)>Jt`r$&^U^@eEf8(qy&5|kabcaJid($VWE%a6JAbL z?WNX2k70lw5t;BTz}lnWYwf_GI}xCK`G{dDFy)y(Yaz}-Hrixze2I6u5mj^YrnQ(~ z2IX_#(+LooOpebn4-v5z(}zk5nd7O1Z`KZm-}g*n&4BHuV(eag2<@8k`dSiPd`~3b z8E1059x`tyl^fX59OVh$;}#0Ut^ovUVN4r|U9`W1$B8^-JY!hC!M6xX$3gUB68svo zuzmx4{g(?JK4xaBFW>9|;)LzV?-<8I9RzN$I$~3bzG)|_8`A8<4 z`XqexC7P)r5!YV;{7GA&;SZA*Y%GEf2pcotj|J!3rcv2U#tEYkaw^xRg@AmSDMXOT z{tRW}m4O(e><9+|i>{vS7n>G|*8#7&^dK~YQLNBrHSIz0cHM}_Ty$@ zOsD@^8`CnN7pvaon8J<{IHl}rdKbGDmkJ~1evIWX9QzF$j{QcWIdG7)uB|vGAPmQ} z0WTmid*Ogx*iVH@Qt&T%xVVrokhyCh=_nzEPt+{b1P0-q71$w3E3twI#^wnG<3dFi z-L*^+>4?$c_y59}SP(M*0iB7!O&8YVa0qOLiNEo@J}6Z4gX?ltM8+Sucme`5r7b{iWAo zibQC(K{(P(8ts2O8t)^wS zBS($zOPn*k8DDM7>rftHe0u<1VO^p;9ef&<`W~S;aPbQe)3jtUnJEBR@IdGczPcN? zWWn%Jjlr2yguX~e1bs_D$%pusBr}J}_PJ~}hy4-skx0hV!Fi~l{VZ9Qg*=(?1g`~W z&>oL3CyxD^R=?$H?SL)LH^shC9K0Q_z%3-P(F-#O>f`C}$hCO}kilYK`ypjAe_s?cZd%9D3%;D!i+~Mf( zb10>*#pOGQLm>GSlqI^FAuBlz!PHXk(ZN-FVPMBqJN&lx=>yQUCpu`pJ{`OdAPPr~ zMHCvn=_<9+&m$*}RGA)qj)n{w9HvKGf=3dBk|`|#*Z#;X9+;P*;uZKd@;b&hq-Str z-xmeK5Ra2Hh4A*}vkgWeUsL<{U|%b&euLnNBcAW#B0taw$T9FfgQIakUMSu?v3W?ai}tWmW+c* zBF9;jN!1*eLWZ-ZWx42uYA`_TV90zWYQTCEfK&&`(O*iUKZ$>B&O3 z8qWA2KB#mk)woo(7B2lllummnbaUN=y>XIxzcUv-d@PZ`X^6TATuCG+U+ah)Ywh)36IHr8bS)L{UkwU#+J%QjcP zjzS)wG)*CT%mKUFn}VPHm-gscZTuX~w?Bg?Y=5C28i6?kN@{9885x34iey4F$Y(&G z4lbmm|A(Y>rjhQ3Gp*j~R6c;nzQW%6p~?RF3wGZ-alxyQ6-rQh)7xY&?>&-qxR63$ zhKd)5TJ65Crg{_Y4~<4#$4U04uf|g%1VFyTgM0{}hql&a!-oq*cNSM|#KR`d}Mwf{T1}e@Kxz09I^R*EF`2`KCAPF#fhh|vw zr3iK|`_OwTT9fQONpa%8r*K-9WRZ1K(H;Wr5MT$tveA)(Wa-`$lv2eRnEV3Pb(n8% zL=t6d@G}4vnC(c5!te>zpMo%#FIYfF2lrS_%Ra_z zWnyMpb_Al@Ux&XbbOJ`ww1RG4HZ{M2U!c%8p-}$Y)P)rfApm%I^bol=4*XcGjoTR^ z>2CLBub7ryi&VL{&`M`&z8v3jp%W{}=h%6pmB{}9C20Q@R}rFf|F+$UG0{9Fa19@FWvb?>X*AP_j z5BS1#z$(;1*yDR-4j=){eRLNn;tFq(OIcoq5W+zK1#H#bqMs#x?`lk(yFjyvHipb! zq6VURZOf?y>J*`n_bh6ycZCY}Ng%gtA(EMveT`JUM~KqGvkM-(@>tq(qF_+2Y_=<# z(v}k)BeI2ntt!bPQl^rzjzL7-o}wSz!2B*$f$uW&kxZ5P|H;(Q{{WTJem{QDer8TE z+|y1xzml~5=Ph+vqwBSOkC1*AaD`NBSj*3!eXvSMZ?G*96Rjk0;b1Jk=r{8Z zv;^aFaMMwxFL|+le)(9f&mMCwlrbWP|CoG)~2?z$KW|2`&T%rd@#)*ieAp(!mp8 z+V4;_dQo}>p(3GKa15>x2MS~2-GSMG1q!#{u3Cl4oPnA?xH%o{pr}!y3z|aa|3gX2 z=3;QOp;p>Xy0HlUX^JB-F%CQllCb4wGg6UZ%+Sk;FMVdB-|;_8<_jpTQz;H@3SkXW zO7eCu{xfLMOW4p*UJUNaBiY7#$3&%s5@#SWQc|K1JCY~~Gviy33OgFmFHt9b5#2!5#uE{&ymb4tQ*bi)`5~3N-akl-5jf_nXG+aMbILA^Ij*ctwSCB^ zw7(#gA$Y5PF6GXpeJG06bvQRu2#?=~-3zjAWi9tW5F%Ekx4#!pNC}HfVEYrnSNmbc z>|*^G!l6Q;Oti&qv9C!*b!2*U&snT2C#L?x|Ef%VIj)#)zcn%sXbdT5E)S>vTuVKi zQa2*CL+Pg&+hBWTZ^9ZHZ}8^%E`>mDmd*T{a`Z#b+Y6A`cPWmykM-r>i1Y@p4fW?! zH#nXqBQ<*G=3`~grTsZ`Ee9Gq*LtmNEP@+obS>h2G2>p{NHj}A{m0Iocmx)PvroQ z49et0SKy+7zP)i?BdwAnl=Y$Yl;|oHMMx3FWEygx({&3n!A>>y_tQ`g6qh+f zT$(nbNs+gUO8q&;&3OEIlI)NTfG_`dG)$f$YY3M}3(9wY&ZNTlJ_fXY%7<|n)Q83* z$ZvEbIgy@I(41mKh|ZtbESe*;TOzWXCn6Q?6E*Oj;w4m!l5_$U6EGKXoN)+z(DR6> zcoF3gQAUm`5>dfRIhS2TeF`7m#}iRciKuK5^*To#C8F*XQ8p3vBuDMe<7QkZqEbcF zT8?@}M7<5Dqrd;)u#fXWgrFMYEdgw`S)j_r#JA9W@q86)%5VlBTUiv)W}LMK@u`L( zfrU`J6pSI#%h@2I3O`^-HWUwkpu+H#-x3Vl+iCn16okt$@Lt^#v_c$yqttC$>VJsT z4^V0x+L=RU9t&ar$?3tiy~7`*kYJ;w*Wg8sbcg+2S{bz{11EMEryu6@&?8-g4>Y5k z3JxUw6Nzy?iy{L_t?&RO_riz!c}3V$BJ6e!nL2b_G zkAmYF*yh|m4j+#ki^7{=Ngr?UT2SFQR0xfF?udMRc*fui`KC6NClYTT`J)`4*7}AnSfGpq7N@s{T9tq?K5%h?lO1{O^cJ3CR+fS{-k5c z+pzfEjthK?lPm_$99!glVUg+M^yx>6^f_IoKbq2KsO5u(JoAn^?!%$vCIp6IA@R$R8?eoAHS{k&lypk zAFn>d%ztcj{=2pO#>o6d3Dk&Y>W;2IKa0*^ujPMiakxMJqQuzwkB`oOhL(SsF8{#T z`F|dr|Kqo`{_FB5$IgFpbpE@w{6<~=!Ljq75S_nX%m3JKBKtoicK#Ei^Pi#RU#81H zGZ;74%r0D#2Yx#}3{3)^Xr$pzk*YZDhab*8fW9KIdshdw{X!(~R zKhEqiLFm_3ln%lLLF&khKz}^rXu`+BN20L0`SAK1Dm+H%+GC|_09{SsIYEb)`103X zh;qG2zWjgFhpE|%>Zr-kvqa8cBFnf>R?;iblS{SoUswd5q-qnc#C7?L3W`G;+i&?2 z3U6w^xJO+V*;bTA9csUr(&T4}N{RI)LK>!zMDHW$<0>LAeuSBpJ&)WmOH4(HBrTT- zX*pzWMtY*geNZ~+-Bi531`ks0Znc=2_kIDL+qyRfHXgCNwE$Q3!n>61PnNxoPhp7N z>f0HYH!jEPrCVms#g*xB&d1I6u)32>wu92q)msAz2DGr24c_CN;!#2A^EI=ITHB9Ol|B4Kx zGlQ-SfTJ^#NCQ4auB~usEp;h@7K{X`(cT^k7iu(w+J6~gBd*$0(K$)T1k4y61wNOo zo7z~`L;=J;NlPf1-k0B>Otzp<@gQ{@r6$wH-T3Ih+h|74PeL&6iG!2OGM>B%p%$DK z5J?oN)en0PDg`h)20Yp{E>WD10h@Nq8EPAZz1TaYG$D?*Jqun6LgGb3Wr?X9n${*D ziY_|zP;`Hao)M0QIT`oTr{2q^)lVGZNiO9ojuvbk_q0^4Bq7W?_f)o(QI~ zsYb9E^hW51@p3B;^#qbCtI!qdVEfpAgwOVn%8Ko+Eb?x>z1~Xg3c9y0Viol-h$obn z&22>xv6BoFOd8ln{qNV*poM9$slv^4Dawk=(AyhGs>xBd+g?t48GB0R?MUS7i1YrB zm|+;=UO{XTHD@M3dGZLINu=mM+3F_!qY|uk9UZ6hEp*}WVk+7T(=fD%?83&02A&bo zz%x2GaH?qF5u$;8Y_It8cOl5&IZ_eBk0z`^fQnJ7{mVa-=%N<7j!>K2{rN^?~5fEj>^r%0M@Sy#=1j5X5sf2mhf?snz>SsBT zrLt#&b0EwBrX=~j(;??#&?fSphrCT^^&!GLdhM+sP9gI)?vi&V-sQ#@?p{oU-a*{G z_#$QJnfc$Jan(bSeeG?W?M9IeOz=XHZ5z}MB3ql5?O~Daop|hC4jWAAOs3|e0RU{Y z*p-b?l~GI5pnX#&5B+@m@h!fceGq6tV4@HtI*x?)jQmi&p`Xz9v_NMTmpgMXwxwH? zA`5_!k*{govL1nwtHYGf3O6$ZldMM(@F3-c$NDfl$e!THojt^Jk|TF`Gvsy85M={) z69VUumdq-(WL7j1XL1tpCMz_7xw8l2V*iP8cqRf#t_J|{e3gIdN^77vjjuxkdBvo~D1}cfDH~wZMH9$^DNSi} zPz~HHazo~gNayk|C)6UEg;Bm5AwZ4}Soergj(Z=-LAuxAOUE*DgoyZJTyrO7d4}aC zji3h85Ljs;<-vm|i&jvaB*^g|qqK%vspjQe^GfS7s<{j!R+Kl^bPcC` z9mD4@0gNYL?}s;ZMPFKpEGbEUM83{lIZAZp)^JzakPcn3XkEDg>Jvfb28yl_f8g@p zawYPhJ+qPCw5$yXA*K#gkmn-@rfv|Q72L8`JJ*~NyWs0U zSGlN&K+;wOx%{ijgl`aQ5m(I+zWYgO;a@dN_+BJ+iGS4`;Tt02Fq6>YQeKXyneiMvB8p4MwH-7*+h_+lqK2jWz9wL?OO0Aw_0!2V! zc@E+;Y-xhX!jhI&hccJ)I%x}M;C)b9sGX_QK;t8IC=BR}(=(1&VHLaqCI#@m3ZUeX ztiz=3XJ%+?=?ZvP)zgR=g9teaO39N@T>LE!vmNJc%&CY0FLzC=Kgmf2D@hl?37L1E zio8nFFDMN6)pE;?6FKTJ5tS{XOd=|WqyA4s9WSC1MAUGOx&1~8+?j8@x0V{-;!O*gKgTENSy^9|G`~iI93%JG zL7`BPjCIA@c_Oic0pSU+mpY0mTc1EXcPhlXq z9>4Kre1^=``fQg_wmk!*Yr8g_?GDa1LZ9sd%GTN`+stq_4`#rT2=ti;Ljh5Bn+PpCx+r1NH=2rS(!|7U(yS+sbW0S0{Tyd`SUkW zh?4X{IAYPs7z%&>%LswEft4IY5EM|OklSd(h$$p7=JFrO7bwOni-B;ng+AMiGvUxb zL|zt-G*caI#zr+#lPlEnNFR*%RHvpS;}%@-xXK6MaF?PSvSJUE=HLYoQco&L@hF0> zlgy*StDHH&ISYFJ{C`wTy&57UQZ{X*+_Y%mF$Ubnc`JN`ue4DiOir*CG|*~_&5JtP z?C=lABCR-aJ8LO~futL22vA$eYToaJy~q~wSCtCwY{+~bQiI<{9PZN(!u$`iD2`Kkglsk^gea^n-EJpje@HSm?>neCm%lu+ioQN=_{)>H$ z4Z+|^LS_eoc02z9q2G-(IAoeHcyBxI;f=GeDd2QT@4BhY&v}!P`_7Qr#F_4l>lg+9 z-$UjQqIWz0p3pHopZR6Hb&+%*!Vr6pS_bOc&dKgcgkFJxmUM+kbH9BJq$$y!`w^r# z??=uxc7M?x`#3e^OSoZulOXu++!_BAJo_>Cwa!M6KqC;Cy`*FeNBc)(4r>z z7$1uBNqdNDNVQ|^;aZFRGjoc4ZNvfjHYMg=l4DwVCe+CoQXqpDuk&4Eg{9xE7<^#M zt>9EriCLG-kAud^g@r0|O7*8G2iS-kJ{%re0^PmKqFtfm1(JWg?d!~e% zY&WTFqhw=9q z{$9Y}Q~3K2{@%gg|L|u-o0IVOHNHQ@pBdjOEhATh7nZWcQ=l-&yO{{`W$M^L`GcAE z(J&gx7B8?wllq^9dO?-?!zBuRj(IEwD6G%9qd${6BBny0(`QzEgg)o8a5VHeJm?zT z73Dparz>xIWCr6672FVUo=~L!y*~Y^;q~Ja*Qk?M< zEchFWzadRyco`rS&H7i>74XVLLW}l|yqG|}gPp`g`o=tkn&m5SkTo4) z#mHy`lVT*a9vNWUMV&}TEJq+@>Co`4561gvV9lB>WeZN z2vVG>Mr67gDV*r^TI!))nUY*W)^fOqcP2pO_c6eLgd_uhpjCJ}=Or1~I2pT+NarHJ z28_ReDGq!<67m%a-wfgVH#vSEKu>Y2g>R1V6$@VjoHZCOjFB8SWhLMKPwHtWm})E! zI}xp?&zPT>MaG9mOVpvgWM6*Z0#WpGP&Z)q73744VT=R=$8)T#Pm07lsXIPg%}Nq+ zf$bIAxMXQ~G{6IiAqS-r)q+KmkcP?g8h~>a$`|57QeViz!wKnNr$e=pFm0ghuq*f_ zvf~^NH^%rGe9)uAXdY3oiKxjUY9~i+5m6gNREdasilgopQFn?cuE%d&$5GdasDOwX zFLK?%QHw-WqlhXLQP*(P1r+7WzW^TU=nA-=ECl?L0`M%OFaJFLvf%3+{yGL<)A?&4 z&o72#hZX(ngMtMwZZI-sibFEFZ1<&l+Ke6NqgebCYJO&ubRM3`iJssUAxfV)>x= zl01}>w<8{pnG*Xi+#j+RA*EdwBQ#%gO!iS_A#*F_Y{t%jxkg3I~qH{HPo~bT$!SvXL9W;0{{#w}(`EgV8{{dHN zh{cr?!Lx3l38kU#aeO0&BTpgB^$w&x;XL0%`1Xv&I}J2Vs_4#8Oe%a)A*Q=w1$QGN zI3wsWO#;g1=WMv^o=O5UP2u)6aik(t6dE8Ro>&*oVTc@yS)TB`I?IoXL!*53@F-Y2 z5ZmQg4%pv5>uQSmAhlGnL$KUZtZWNHCn|}&9Pxs3rvjG!WxMY!oZ5cezT1G85Vv`b z#yXf-T2+?t`Sz0dZLX@#B`0qyuG%m`8Jvu}^OCWI!b#lih>JIQ24E*PF5b6uS4sTl z5*w;{{JeIkPs4Nc$tNTB+w+pH%76+u+11*bT=RJ=PR_;Q5a8ppJT9rfwOtMFj1R>WEX^k-N(Z*J5LyR7AH;nf z5m$TT9X81Icz%w~OE*I;wHXyKfyyy2(Cn; z<`&Q7;7w2(2Vcb420tXK88AP43^_>~-~>N_W*(&AwBTtXxdrjJiTK}9{3gV=8B0(o z>M#^!hAw|1cuPNgHHW-s1>eRONprp&Y$0L=3wCfbJwssTzK$~R&1CU-S@;ALW)E!( z8NHxA45NtW(&*8ph%33ld7$R&@p~oFQGyGROl!gAdllm-rd^jNsVf5TY37E|X1{SL zB7&!)hYT4DY}aR@(caO)JoGI1F2*eQR3dCEh7((7KsR1OTW!x4T>eb(6MXI7e*7!G zx8j2NNR18j+e9Odrbb)|TR-rcGb|k8Fz&hYOj2Rl!Y=$5HW1&vM0{Bh@spAH`gy ztD1bFcyEDLn+lRs4i@`HaDv00caLN_$B-5`_Y2r2{{Po#pl`&~)V2)@QodXf|os zXpg1_A0cxYdpEB;BEdlOir-(zqA+Y>B%-#QwIE$cHw0nC)AN?tQ>OvxZwFxt{tc0G zwK4`UWNccA7qT(^jKj#U7%wEpZ+s72DWxpHcL%bz8CwxT)wUTQ#21ZaoAELEwFdZQ zaLsL@+4V5xY)govSr86alJZAT!6gY6HIViSEovY#rf-7|GepDZ!-EI}2fpXI0F)m0 zr;wq#a^D^lY7^)3r;?E>6ez!>81yyQ<*UZ)aeQ+MnYGL+A(bAOeGDH3Ht;r&4L7qg z1AhXth1zg+!s36?;EAh}dF(8BE8zngIY8sGbTs+R(>Q0NxhHslQ*=1{Wt%U_3{-LCk z;jx9Qa^dI)pl_rm08?D9&$q(+Q5w7$d8jS}Nq-`q4e#@zbl_be=_>f!zrdtM^HYPr z`vQ6=Ut&t|xDWWF5AuN#cl-lLcdxD>o<+=lZ)$<*Je*vg~3Rm3c;BU0=EzVlRk9(lR1zu`h)eG^DJA9R*F z)cy>@f_?u-I|7)|ZJZQiKhRcloh-lO4q+kHa zBm@QrmkQ4jfR`7a9q(U`VkNIF%37NLBxa}q*X+)QgnZGjf;%t>7|B#@$)=!{4RjX69p8k!3td%7AK(KE>EFnS-_n#@<^xF**= z#5=VlZM|vrF9zf0&mpe(ZKl=szA%SdS`rwz#pNHIQk=I9mJ$;NmgKcI_E*Lw#xYi^e!Edf8@* z1m!Dp`Ik{O)DRA|ueqLTWxBuYGHT~P(K(kQa$sSeMVc6_(gkp*63$}%L77<9X~jT! zv@ismyG+*rB4CcG(uc7IP;CWxuLPe!zkvXKV!#z2e1zBJn^U#|SWr^--)~{qw@Zxo4m=i z*)xO+fZ;H1@4XEVI6{-=P@c6n?V9g#*V&sst)1^K#>+9vF0#fijB^gyYP!wd^huF> zzSp0S;vBFI3rPYl0mjOU&8dOj^IX29i%x;cV)(AJLMF)HGvP5T#c{BfzHtUmYW}Rg z&>5VR-$2fB#(VaD1vbfYQHpCVS>O?rU8X;`E*R`kjKgtM!VVK8hu?S{K0Ep$>?nM! zAqLSAaDAD#Yw;=6Y&6875F?VHHp8^;ts@rncMfPp-q+5ig?sC8v0OX+Nl)R^wC=sZ zCf`+5kUwb+0v-faHTZ}9OT-<&U=dObU9bSf$8B}BzG-m{*h;%^7Gei1Ki@EBgLk@O z^dYrxb0YTb@Ik1KvoZLx@7`eX z*qt!QP0B%Rfq&RYI5fa8Bqlm+#avCM*9LNp>4?FyL97-33k|;)xELBv_9A%qnt#{u z@8$fvl7HXj-x*LV;+}W@PT=n&`F9tGyve`+<=;*Gy9dAb#A#KjRl*I+N73(#ibN6d z!%s&Y1#}5HVD|pyr3FNHe%U??6ywQ=U`uckLayY<4dDop`0l-GVsMCf<0wu6%EC;A z2BIX8wD40>bvqlBX>+j4G%L7kk0{BCd4(!ChwbsE91H+ilPIkS$5_PtSCU5KXE0Pt z*CXA_rF*M%?~!hcbhk?PZRzfj?vd%DJd1Q^$Z)mtCsRcH+tS@7-EX8jaJYy+M!Kg< zH%GcI>7Fg!YU$oA^Pg@N`IDvl#Tl|b>28+p{nEWvy1$ifjdZ6-H%GcD(!F2SvsAj( z(w!jPEa{#g-GS2GO}Fjf-@DS?D%}?8{!_XIvfZ~zzY4br5}rBIJzu)NlCGNWgj7+_ zNa;GHd#-frq`OeME2VqAbnlezI_W+w-8ZHCU+Ef76ZH&|u0^^drCT7~^QGG$-R07~ zTDrGL_wUkuM7mE)_ciJMSGozOi+Yn)SGFfp`d6MR;?I`;YU$3G?qca)CfysQ`*-QK zNcU;!zAD}KrE8G*JXpG?NVkt{#}ZlpeCb{w-4f}Jk#34~2TAvKN0mOEIwDbmf9?wQgplkObp zI#l>bM7&4hm4J*6Qrw#e<9nuQie~J?i}e}5S4y$l)vR>!%|x9Ro9vp$NsV? zSFP`%(?z`MKTx-L{)H8FwbhoYdN4W*J(j9QcZH|6e!j(CR&22@9B$2-!BuGK>T2Ed zJ<+jHamjKfH@N2)l~^uxH!h%3hS!c9UP}c;#Wyt8*VNXzBWn;nDU|Jy2^Lr?8r_!q z`E`pd)$STFVbzwJ`bJAsEw+)FmWuh+mWs;y3*Dj|^>=)I<3$yX)g!9v=QW^Xm9=%X zo<)}01wAO!8d+vk`<>-cT~y;$I5yT-dKY-+yB935%&V^sceGPFcb&Upf!k8)o>OsQ zExIOQE_>)KA5mCpL1!ZTB|1NO@Nafqk=s*@F`VsgyioO*dm3x!&*pgNd{5&dOGAAv z2CvbLa5u)C;859EKi559EngkC1)MWxN-j=K9|4E%vkb2u$^VPo2E(L!4S+TEUW{Gs z{OC~?1)Q$}+|MYp;8O2lx_&D~yrpmf7_ltpOU6#>y8_dUshF{sP{N??oju z?Ni3htaev=XV0wndK$bQ?uiW7p(91e@G#nNY7TeKm^w37_(a!~nG)S**3@|y%wb<+ zeH~?;T~`RiYBUs9%%6|0&a0T~wp4i=8)@=hRO^{zsem)Ty8a@IXHJF3GN)pJWq!R1 ztMCj%?`0LTJ+iz77gaP^D!nx|nCF)In*A-!ptf&;+hgG}fuBHNpe8jEa6`#zv*kc` z(>SzIwhx03bO%9m&u2Q+>(ZQB*7+6l06GE2N;hbN#XYY9G(?A=(PM)pQ`F9H{{`a%5GyRM9xw zJCBfr$Sj20d6tHXMnC|g!sV#xFhpKAC(_4H#*hIY=DFw9H!hOxYt-f5}ECB4pF<5@VS!pa|u##CgaH`dOcLqVg&7s4{rRVa*^ z&a-(zx@?E)sP$Fli25?Ewj8xCa?^7vYdzKV=@nxtGtw)^R9e$3E2`$gV@Zg zX!uxjN9jf+J!f34$5C%_)X%Q0pYOJm)>qH1^&A9ng`3#z5JtBzBcq~HKYpTp<89W1 z(5N5Y7Dn6ipmLl*04Hea~=uEeSLu*&h$-R~(SKpOxX*ms9bbqWFvFSPW2ef>mpyhJd zqL;ojEUK0y>%vh^IFPFb=7Y~*3KZKG)?`N%`ZS)(B zU)iI&q;G^@KMHsq2!^TfvSwyVHX{apRo7c4Im-{)jEuF)eG6!{Utm(WU82tz) z{-ANm(fzSzNQIj0lq#>3|2PyP2eLFV4s?Q-UcA%j3Bl_iF+h4$_}27)VNlt6Av4B+ zkB$Lnd;+~ai17LTusgj0l%y!p@XG8$A6Y&g3Zz)PVJz(EjmWtV4S?Fd3{}qTV*TXy zu|}gTwnpO+w^Jq1YI{}sY*X7ENzWMnNFhfWnD1A6t!f7%+v~u5=|Z4C)L>Pcqrx*I zQ;z`J`p`xDr09}{?UwE}KN+l+>!kPz2Sh31_JOxu;X|f1w!XhB_#Q^0SIqS-E7tt! z1|JW1cx7kCmRI+IU*DVgVYS89FH!p=7}_rMioNG-rK)|ur+GAevS!4VU%3504u#bI zF=k!x8-Q15{!GsAgT%l7sQ5E$MrOD0ViOMg{OZQ~THe^{Vke}h8wwJ3-$QYE-&++i z*Ar`I_wzF?7LWUbT~vB65OktvncdINpWr1JWtBe#{~U9EPOoi*Whksi?bQ_xFuo!* zJHWXai}n64)u26^k-P1MSnNl4 zSowx7@6ST4_OHTfWy1$sW{mzpKIQ&Jh^JlP>cMBY_JKuO`)$@L8jwt#_7nDRcGzpy zTMEf4yc9-zG|w`H5r^1R*K8KV1{93$qzPJ>1&p>y)9i9`$^48PXpL=s5#B$lP}`^5 z9smBcFQ>-pCirFOu$$Uf1@qV<`v@!V1nP#JOa*&oLa4`WVF#m9C}9Vwsg!$e6{8j) z?I*Poq+ucEMR=noQ`H>u$YpkCI2g^JS~CP4w~gX?0w4R+?S)gO&9F=>9|s_nIxS^( zhsH4g&)C+o*Kq2J%jy8P%h75@Nc& zwy~zJ{wHQ4O5VxJ75qDXv(&Yb+;scAii>gZy98(aqPQ=eY?$3!*-*+nLL85jy;*3K zr^EC@A_O~%)QddxY8O;xjH#68#=-@4FZ-xMzfOyy$@!y6&hLq|7?$@(OZl)D@atj^ ztGBB8=#6V3NlTee({@B0JM0Q#svbnZK9)GG9mLDl6e0UMv8d87ca6|DseTp5((Mat z7f2Hb*3;Pb`@mv1+J-3mwO-4G61#e)YY?XtnMh@Hr6!WIFN{Aby>gF=qF3W8aQ3lc z{_Mk3Jk;dpDyfgju#M95SAdTP=b~%emI?KX80!gA*`)FtEm0}j)>F&{JsF8IXv|O* zM>g%m>8O}vsp7LLk3q+w*Z))dfSI-Kx@t?-Ps@&m_)+VRivO7vqjehs>Ej%BKrPEM zo(@Y#o>u!-<^GsUPZZ7^$cocLZCjXLR91;;kR7GLO&{m>)Hhf%GcvQsA8xufLMe4V zs_?-!hwgxiG>)m*v2K%yq+U{=FX@H($ zwRN`F3pLhWo|Re&VK$~K9e4K%+|KZcvcDMToI1J8QaE|i)G3ooN^s9Z=MASHvx2)5 ze6oJRAG(G|y|_b#kIKJ*9L1tXhyDE0;FJ9md}85`#ue$b8IaG#dLMC={|8Q-H5s!9 zo9z+neU3Fp?1SibLwsangH>PH<75cC5>3A|uCcy;uBEi1alv7!!BF9oon0d+w{G<(`qb!! z{-Ol;JkGD4iW?3O97^92f8CYUO?TS*u)J2ClOgU;)7^$e@R>G#yuDMQRo6Ch({=EwtY66R(yYCwxU1Z?xcQQgWgLzW zjD{bq1NQ;H$+(p<65lK%fO?JY&Zk*^)Og3lK0>}x=c~J7KjBA*4-T)Xt6MK|?1%Ri za_~<-e-~9^dKOhw&&HL-Bt{o{aVv0mR!6{WKiCUZ54j(={>9v)wk=UNruI#`bp3}t zIv;$>%&gHJfJ!f1R9W9R+j90un`Mf39&YfpL>@@r?@g9&Xnd5CH|@apRz|3_UALXD zQTd*%i_!B9*PIDWf>bN0IFs&h*ML)Jl!2lAl!_{R&KMnmB|i?W_&&sB6AF%>nbRc69pft6%jyLeq#F7p>hs<>TUL@`$9-)Qjr1Lk2XAo$mp|6~Py+5q1 znl@;+7>}ZD-R>n5(M5Ir8l5ZUlW2W_!;8z!$6?){UR+E~Juvl~%2tYfkgzq2*a%Un z2`>NKCtD;pKC~>7P14N}?Ra3%bZrC?6&{tl_pdLnXT z{=mSarz!#{($XbYzjM@c2&tzdtWi5hVSt4hN9{a`u<_`A>xdCZg_nBX#+s`$4-oJw zpHq(`5`5t1aD|U%KY=4j8ZzM{-D$U!mrb5BwLFH3@-VjlugsD5R9QN30bX@(JVp_@ zxy>l0YDs#n$kk7|@5hOxZp^CG`KG}~e|-5T_{jYcRbN|^oxyrrb7Cwf1Q?M(5? z8Yb(!d3b~lf-69nP2A^!18sj+#qV~4s`FEWmtMTLrpJKSs2>%edc{6y^05Z5XnP$E zUgfk=XqksQDlA!t8ep0hpwo3{lC*0Hv|A2)s5I+@A3E*;@>|dPo7>yH+wNzQ)}5aB zH>YPBq3Hg0x_@MRagl3k@uVV4`PAaViI%euIn~&YzV>iziC~V-$L~pH-3o+Dfh;!5)M-=3O+M5s zdsur-P}AV@Z5*8FTBr-7hsF=#R$9S1s%EHi4PpGijw4HU*fxj(uUNBxKRT*q=@pBk zj(0_syf3X<4G|sS_LtWl_V@Xz@X1p50VBuvC-~r2Ed9M$5ID7vKQL$*sscykH#X(2D0>!uC~mQ*=(m}R(}pJttaLyl9N!$EZOdLW~DTn3^sVp zpzGywVxFs3myqcG>Wo(~i8}W;ta<(+_ZMa-f^W>_YtdA4f&RMZ*Lpi!a{aiego+Je z`vVm|whX<&xP%W594xGX)u3fuUB$(23k%_g5moIBFU_7wFW##7q`|8QcTH5+RxGf9 z(^l!mkL9Fq_bZWrnM!X)RmuAZZTbUc5%@Kp@GAmdXCG{U>FisgtmYu7b|K`2RU)d@ zQx_)^!a`sS0~Z#AdH^sLUVxIg_p?%uVCh9g&RlD*1%}Y|wRNg(;SW-E(iN1tRh_8t zf!Q>v82lH zP?+u(NMUlW$*<$09KpZUGDe`6hF_XJ?KnhayM=lI1(iVc>Tuycb(*opU){gV(%r_y@bWD1;)qUZ?Od+~hO1FTCTjG* z1$q3t1)sXUW6=_N?+mwo{c>Ttcr327M-*jubNTuHn6%T((xyevH#!*|x?FD5{@^^W znD{yJB277I(p|M4vUYb?*RpMQp=XY_ZjLwn3=CG8{ks;ZEB-~?XQ*{lWok83{%Aw@ zY<1(WM|iLHH`^xoGwj>w_iJ1e93y6Q;_Q3Bht*a0^i2LCd=)*+zG_OC2rKriF0?Qz zd{lanDNbo?BsASs-&iXyXVV+a*mSRV9dxYFZwL2+255*B-sy|62i7CpA-Wk2pHaHk z4~SlN0iP^ODol#_H0`0JnOb*yz9Xb%IT#S?1Rwozk$u1?>sy0QFKmxh_#C*gaSZ&? zb6cJ8r<{$3=wTb7(>gS$a|lUpCwz$Nu$V8zP9Ac@8I_sR>$-bEO4I@lCN~xhA8~3` zBe!zCFnlaiy^WRiF<9;&J<}DzutviR`;WSRVSKV5mXL~uPp(e-QRz!r{oJ{@1Zrx% zw{bqCx%nQ8V+QZY1JMo~Z2J%ad6y`xwTv?4f}*phwi&V38{CBilL3f zAw_#*RYf(9JL9=9=Zq2MPTY4P?{7E=cU-9K-nV4Nrp<^>-j4 zMJ*>iA3${mL~$s%nou=<>PoxRJ)G1fB^q8j7=99NEh4I^T>B`&!gJ@%28IPQ_#Ze3{cXG=QUJR zdC0CQ!e}6TyMrFdyIa+OqLetsF@Z3ZQm1+s79?Q>VOY7W9hL}F>29uT(CC4Bo;ox7 zJazG;3Df8(l~X90%UJ>T?LGynD*Ujn9aLF0k{+t|)0tIzgiS9la~84cLTA3#`~3$! zV(2eBjoNkSfvPy_vQvrh8lYk!MsIcQ3;H7_#%S!Mnvsp8yY5bZ79II-6zJI{ct!0Pq*k25GO`uV?$Nn^e)d@aP2Koe_ zX}0VPTd5`0Q9QrF>tSUh^XB^xDOKCp?Ky_;CCoa(Yjo%NH$4_Q9U%K3HMx4QCgZu%nymmhq8I^m}}K2r|D_;fpeHR7(BzGz|vquc(CkJ_J3B6eJKQ6}H&W)<>l)@jD?%47(#mvTN<=*`9X*LCR$H}uPFU16J6e;_LpsyN z-8U+IiH485Nt!<4oDpTCt<(73GM)TO$FC3IIR!oE9Hn3B!Qq7*NsPJ>J!5gsn&_ED zd`x#zwzgbr^fxmrlQmJD?3*T>Q_i<{(f!$x2iIe3(|XF<^k~5bLH^cpX-&6VZG`CUW{8=Qn%83QKBn=>Cvo{fEKl&9k@8QuS4%DeAN38^wK=^brZ=iI&ZyF1P`w5> z>=Hipjnzz)${O8OwF_$N=N~wjx|(I*-z6>@UKttkVO+VuMc|VPFFeCB8#lWVv~=9@ z0ZJ5dPe`m+Q5JMP-~u%6TNEH=3XP8la{-ZpJC?bw40q)c*9kt^UBQPh?zUHvRri4- z(sxdf^jDjAO|?70C#Ng;j31R@jnw=c9xP-h_~dp8pV2?T2SP_hSMbru^uCd3e|DUr zIK?Q&r?c{+hembL<8SST7h4KPP8~UAWXZ^|Bn6)C;2wqf)|TPOHKvD%61Co_ z@ET>+D?r2W;)?@;CYG`(lfRv_5UPaz9f(z3EbSV7UIjy>|hO z>!|L=XC>K|U$N}iiJj+lY{yO_Ije_d**J-0Nq!L7-bk{ogn)LXU0It*yPMsWEe9Gl zkn%T>g37BV1-h1Bk{v*VK+RjF0nDSIe@VZFHU$ALpZN+!EvBCfDfxb9&dh!7t{(9t zfn@EY-`u%#=gyp&IdkT9ZjRYXYxHLE&pCOYvxBfR4{o>ogLzTMf|XVlv(>1{j(P1>Dw99C+9UG^+pxoX zI+L=cTrU1hVgI#e=|wvslEI%fnfh!yssHLUk;NUD!#3nct1cItrm*c=v&m-e*b+=r zszz@9l)JyLiT-dL{#@d2kq1m>ZeNCW=H^dn7XHZLW7DRsyIflwsp4GN06LfaaZOK7 zVmZ#eb4_i997iLV)N*u2e^%wKQY+Cs04m;AQ&(Hle4o*@X_upov1ZE4x-bp;ht|j@ z+xzSKQx@Rwx8n2D?*0qa8()08bd}|0pLH0oCTaexQ|AZ*!8V6MNa#boY74-wa6CyL zr?kEwlu>D>{27XD^W2wA+)QvZM0V?%ALe;X?C_>>pTo39{W1VXjpq zC2FkuD(_mdyUb6>8k2RQNL3h}UETkNFyWy4UP4R$><)*GTJ*#TNTDn$2^4?l;t_uY zh2*dN42os6oj#c&zl~B_tXo=kxozg@l8l2SYjT`w)2+#xztZvtE2hrx#hC30{Mjju zb5KWbNZxf0k^ZYyY;Hs@*zfAG63bCLo^U4BM4hx*c z+jeZOFaquf={d$dG)FO3z40W_g%Os&- zhr>ci@wR9rg!Aj!Cu1)EI0FlsB-h3jWLihezPN`|v$B6g6uGv|j5E&A{L=bjWo4C1 zAM%w{RaI6RO+7Iv%5sw??C(VzDU8&|)&vTBZW2 z`BjNe1xmfT)a$TtR=mBYcE@&Ob8QK3XZarKlyyU`QoE(s%dRDkH!Etg%WRM9%1Y%r zaYb8&!y;8N*@-DDiACc{tyr6SmmY#lU8S(jU&p!vNsR7t8l2bVsr6ZWr^Z?Tx7W99 zZP?jj(03JUcW$h=DTro*GhU)kc*z6Jic#i1HkUR_XSOkXf=)>v#hS#4ryTME?O#=M zIrnGRmb75d+7qU_bP4JFW6?v~4|m$1M*f?#*(!0gezR7;l%1n4eyB|7v|^UlCG~6t1@KfM{i3gb?N)tH`mE2x9sVktY%N#wZ`n1tu0Pnz-xZ!^LM3doCZpZw}&Ii zP$U?IO-h>S2uB{2A8uoD&o*kZP7=8ImyID4bZV0-R-JzHe6q`csFnVvec>dI?T|T5 z=XA!US;D+*WuB$Sn*4EFQ~tt!I=Aa`)y~zVQ1SL?0_ENvHMUo*A<27DxaGD_=42Lk zWo85Cao@O=MH{}!t^wJ05m*hxoFqW+0l6``-pbM+VFyr-drK?y0cB=b@%CL?>$cX| zs+h6YTe{lI3&3(32ruOM0{PY8NeFaH{^#oK9oy@ii|3Qs9p+@8Yapz94Tt_{3SIe~ z#C5s9lc?Kg1wM_;`jFECa`E?2tEV?T58L_0?&!dg;uY z^9rhiQK845Z=JB(==_`W^X0Dj`Rz@+jeYo1ThKn2oKs;aGcBNKV;oiHq^en?9VflO z3+_P8w&7q6l;@28)v9k7sBhRs6TQ{VbMty_hQ6`3s*LBQ1CH$$-r^mRF3cTTN1mOs z93SOw!}POWDx0#s8`Z$vQ%#uekz8XQGJkdACo@8G6@ofDZpL5@KP>rVPBm3hkwa8q zK^i7@GHQla)Li*9X?-{KBi?PR0w@+)l z=B#kVJMh8DJ@)zii`EIcrIZ@Ss@yVr%qqguo8xwZ4!NcKJTroccK}Q%%czQJewA0M zeH+f^iT1hk-WoY!ZtDU~mX+%fOdHJyP?JKXrp13LgW#qi4y_egQccw%Q#w29l#5AI zo-1)}lb~iMQ{w3Bq_qb5Dd35lpUEHec5>X`QR#KgFZn8@>QWrs#SDY0uwY1RDP zdt#h=fUXesQHji}t|6|0RPH4Jhs4qKLEA@!R^+G=#lAY9u`3i$^dzjW@TkLgms051 za+T4)IkWBD_%l(qb$<2wvA$O7vbWe5hQNR+mtI%~=$30-u946b3)k6OU$%KLt0S_h z6;l{rSG4nZA{cPZfBCus`*@CV?g}RZ=Ij-!#W)M-WZg#AQwT+K0X_R1-()%kmq4=*t;etNI)9cv;j;JO zi|z}>vDQalsgTpYm$FrBP2d%&O2}&A=GVG4F8xa~stYDWUem&eVHy1DD0lTAvSJxz zF8(!+W%8?iZMOQ=`x?J6uady1Qr9o@`-$bePu=RT-Dc<=GZ0caozfJ|c>VPy+@oM0 zYAfkJsXYpsKcRB&uh+T1FX?O29~!`3x4j`O!?Xw5x-u1;SIGjO<`;yNS`X1RiLuz% zRO%Y}yejoStsk#l<4}j7A3OPj*_sSRXtIkeFVnPxT0DkIm8H?isrPYIs*L*MWDoJ@ zQhpcxvW*|qbVJ55gNf8?%^$tr0LRgs>#Y;;=Z{?sY5r8=M-r!UZAO1Kx%$Jy3N-~| zA8pVajohy59&I@4j&lz5C2YZPovs-FgCw@DY@D@SyFO{o=ap-Yney4qA4i98u2jFm zh0w%vpOc!-Gxei&L977cm+`%x%=UI+{Br6?j@djlA!<(DT+D^PT%vj+F6r8%<&jz? z<;sM=kk{@#!H)jkE{~{g$p)wD7LV_7K{I}Oi-(gxjyC7!4>NDv;lHXTdfYtqeNW?b zWsdQ#p8u$H+TaDigFnJHIB?{&LkXhoo}#eq8s)ZHUgr z8MI{gB(%KFK268H-$X6Z#jg%tcPOoJ&!=R_t5@}eooAlU^Te`ZkGWXo}#nGlr%|P=+Mk`y6 zwXCIDni=+`t?%gm491_G=k;OB!8N|W{OxBlXPI3B$5kh6hnchVW)lr!S=40h|Jy4v z`m@*gH6Fj(^>;s6r9}V#8o$QjSE$mxp4hbJmDwJWYLnpt$mZ{}+ zhq5=)-sWD9Z}OSjF(A3*5~TSl$;pao=OASg;}BC(mLHfLXV9h7k7+TLgHqk~$Ock; zPav`}ppApTa!M2J$uej<_QHFgY}qpV5#8sg{ezgx`7JBMmnG%TVJ^^H zd{52xjWtGHw7`MVgUuJ1Xz(tl4yW6dTiD7i7S z(!J+lvI0jJlxt%zniOSG7t~~?A(c2bA2I8yRC*@d=B3NetgmHipDI^gS$=H+V@F#e z91O$SxH7wEl_!Q@+oD0aes=j4$0?ZL%h5gQZDcQP^}||^HTj3HqAJ!&p!z>P|0=Tc z@8WHycZ~F1qp_D=^VapRysT88!*cSkG4L?XVQ$$UjLWS?j>+1K($A}kF^L2A&LN08 zPp=!EUOdY_%%@3^B%JH$Y zHeo)tvA(8350voblGXB?^Dm30IW7|*SLA)I4nVK8k418h8QuA>bbOtU@*jeUHl=lA zukGqfZh7~W-H0;y<(SQWDNy znlj{fg|Vxlo0SI+H$Tdp(|<00K%VcqU){G`%W5y_ zdj-0GUo%mA+VwJZaF|^sXv-#Uf8&c z!@#X$Yn!>X<~GcKtDRdV7(D2WTenF!_O+GZzH>yI#pr-t`x|F#kS=f2UQM~nUX9C4 zO+BnpDqFqEn5}I%@RE-fx{lUn$Vt`sO-W)Zx7I$ftfOh0Jk-lyj@dm~pJ9##wux8c zn+j}`$9|d?v+Qz3FZONOrtdEArc@p7}xY$9&{;dHP=V4;2FsBgw4S9G##eJ?NTOn&M8iy_zkg~lkB`)dP{;QrT+ zgWCFYZGQn)IUEwW_%(hxY&*K^S6Rnv{2Fg#isn~Ehg#=%>s#@j$jZpdpf>1!QF=&a z&P?w=Q4%S;xSGV8zur=%BXCbWs&z`olJ;cBA(}q{b*{KH(4p65rAkrU6u?=3NUZ&B z(fFPV?+o2~w7gcPy`0nd7j=M^Wo2k`12i{(%F1C8$M$pb$Jn*Hl#K`dvF*1u7t|(T zo936bKgcDYRDF_$mTT}ilIR{o4FKfe%rXmifm?;fsaO~DT$WX_oOWj9_@i{(sWho$ zX@2SUht)@C|EKun!^9G`KEBb5Cti_IHm5W#Iyr5-fW%AQuO3=D?e2l+!2y1J4i)40Au}YoE zFW3Gvv%F!RGeg|Imu%%wj;`X9%o6yc%V#drVg4VMeLH?)p{c z&8{!vSUQ%@UgMYMm#Ht7W|!|-b%rUb3QAf{sV{uFWxM7NzO%_otvD%R8-92puOj;b z*k&KtPnLxvprtG~sMz?5ze<10{_&}}oJ3=SWjlPOBO>mu1{pX*<24d)9d(}Hu0OjpSR8Yh#n`o@**YK zzOyZ79>%o-s_RR+J|9x%`kp{jLrLYDHET+mHp=rmj*YSxe}B3z1HA+}ZfhBTzq(~W zIm?)J>ig9-f7WH=4OTb!7O$TACik@%*b@hT104T%9*& zBIxE6XPEr6g5X1 zA8<~KuC!ntXR<`iYP)CDENd27vJIx_?kW4`1)bmWDsDD7f9b9tW`2!L@sRy1wcLQF zK8GZ8uNO1M$*>v3%3>C2I$;(q<;~U}borK-j+0+VRA{3KjN7BOvR2ui@?qj^+15)i zu1a9rI!{k!-=4y{ZH`)TalY1S$q%!BR84|kEe)CMGW%=Ccd==fL*@g90m$f{GPI{9 zNcMx*nrY7Uoy)RLKcNKC?aACv)~@9)3y|Wb=DSPQddn(gFYS=Cf6T|C%r>a`)6v2G zb-ByHUUAc|`t3$biLa*4?5gz|4po*5wmobL+R3*u^AW32S#rqiVJ2GHU>4~7Yy0t~ zrLONE@n_j6?~bWKv-wO`P0>y0Wt&R1SmadZavo)o4AT6mSOZIN_?49@z0T}^@7s27 ziEqd5`bNW7oAaX~uNHQh`klESVXb33mAAN=N5s4FNv*C>Lh3B9%8I2VkXgU<{*3@W zxMTYjH?J2vZTCfS4PJ) zWoeVGK4jNwU?*g(Wf#Ol4NxUBA2`cmE~p*EX(Sp{9L-n1N%wuK9`!W7tbkl`hL@V|-Of%CQXodB>}7 zo1g_GlK8wgFGb656`38KZl+USg;x9&}n{Yd;8uB*Y^mT!;od01KspJr`yR`x^!D#B{e?+Z!IKo?_gHD3$(=V z-hDLrWx-q>Xtw>Z=9tDh!Iae!zvh=7?>RO*K+i00j$*kJ?205`+rMw?Ki72dJX8A` z*ZOobsT=lj--W5IFGcoGX3EmJcBbd1Cnvn*me_IDJfz@Pe{}t7t5SAm-S+cd8S{*UCFnRj2 z4?fI}d2&)z*B`xKEMvbsjusmgB{jX+4O3cHvau(bgkh>bbauVu=4RF>y`KfvDxKd4 zAb#z!YLjDyW6JX+uh`x>=Zu@KPixj_yIgI~^;8#sAXM?mH|Pf}uW1cTE^%P?U-vjP z@2Gkl{VghQvF420$6_v(szDg93Od)=mDPc9CmPiXhw97J5KH$|=%{2m5JQ$B7k?I9 zG6n)SSC6LoW1cs6&$qew^J=X>di~Jrw&&&I&mXw7HdoIkC#|jd+72)8T*UIKb3W3= zpUXcpbXi6RIcJ7+`_k*H74G#_7k@5h{kd$~qS^v8<&P=a#+UkgQ|P}OJ79~6KfA(h z*!U4fM{nAxUbS(XG~IA9`Sr5zL7Y&#w%VJPN2LMReuiD4NVNMk{o3SDTQ>e^1B|*l zU21;m9viYq_Sn`QJk^Dp0nC-e3C`i_L9~`H+P(s=9Rsf7UEy{_-fNJt7-_#1`%>wP z6dlnxMyQwEIG35Ri=K1lKt}$tmq(AK2w(2}>p4v4-l&UL4|M*a|I2&_f^8v3{`aDe z8+D<*;r1+B1ugZKwzkt)aXn7Kn(8f$4`@PVjj7(MF2z)D)rK&&>CLrkPA#;LtKdT4 z<`$7--yC)`Y8}@aB$--@)Nym_{!~LWf7XQ5d`-ahy{wkDXfMx!hxdkD%UBL&_tI_t zCv(6#CZ#e2g{n`kqst|F003TRVbi2{|7Y?G_JEw{fh50pzwJfkmL5QjyGfZxQ(C56 zFxzYcsF%t$UBC4B8>c6oz5u+RCKt;6Zh4XG#fRMU_8o)nH+NdCD$r-{Ko43FKw-AvvyKv=?EvTRhjY& z+9;pPEGvf-Zw<;klNtdHEUfbzgdgd z?NP087^^lnY}$QG=5(&5VL$+=zQ!q+lpwLHls0NwslRNX%$l(#UgglsS6NB><`!M*KnZrt(`33->$L%m?8gXDtsMMujO?8J+1-g_QLj4gxcKAHtepH&Uz})? zWB*rMC9=emvpzXif}H%?)D_;-nN)KnuknlJlS31A@~bWof#ok)dV7_%L5F;D>|Am3 zXLC_NrbnTc{*7r%+(@p%WNj4#9QjdMfE$;xS3#My|nd4U-x%IrwgF;a{2`Q~I7I;X6 zhyDi3?r;PVC&bnWzJC=C8euuQ=*AaD4R7xXZ&iy*h`!(66Hj1fIwL@1U>`ht5+VGR zD?!-*5s4;^L{BUROYKl2u{xCOgl__0rVVwgXwCON7}{^F2n+L?<7(sWBe>yue=M2^ z%h#;vFjjORGJQYXP1|#VVQdghU_mM&=g~RQ@|8Lq$}AEL#Dj*)kj|{W|A790?$$a- zx{`>Q--UGv}6f5Gj`S+m8>pL~XXY7ij&_N53d!;?#}@4{@KNJv+XJC?Voy(3 zP*&Ntki{#Rx-c=0HhIg(N)%7pk>WrCty+j?BpWvj_ffg0Xpp}85GvcA@L0b2@w@EVABgtN(r(Um__`@%N*(MqJ|d)|E%Y% zs^}Ji#Ahp9M~G~9h4dRtQOTU+zm!kwiM^pXNj1s^SL}>ujkf)=7D@FQgjC*`&F4BY=?Y4xjz;%Is<5?fi5r|*77Ag+u>IA*-90b zD!T)+W@eZ8Tqc~K8uSRVX>rO?J`p<071)a^7SM^ZF#zFlgsHtVfYuR->fm@?dnY6o zRbm}q%#=Qm1%dWt*ZyJy{fE(x3WvD`RD0AibWo;fly9-pL)8H9L2{@UM6~Uls4Dx= zc#@rzJC#A51dETxzHqXWlcJMqWq`^r7*0Tq*e_pBw$rQZQb;|6}Q4m$xh#>~| zXm~!_gPKh7S)7MhLhnti)TPuOjYQD4SkwlLT@N-fyCKwysWw0WC!%!Ym|Nf2anVZG@$z*=J4k9Oa!3Wl3JC>?2RIrV{(wyTa5;ksBic35;7bA=A2j zM=NJg_p|Cz)yMX#Zr0Kx)cwvdBGbA7Wz6-wyq`da2GL>V)ybwe|sC?DN{-bsZEkfco-vX6r9u|5}HqOm|b9q^U9R~EhmXqE}B z&(tDyKI5SdlwK#jK`J*MkuN-ckmQDtNN!DqaNFsroi}WY{>S&`ANaPIKOf7XyQyPd zZ)8y~pe~b|+Jh#9RvlFG4$UlHUZ%AtAu^sPc}^|W5eof%C`sBAy8Jrd)|!2Rxayf} zrNlriXouTy7ux5ha2L+@)|2*)b`qgRygeGJl@+rt7H&e9h3X>jI^vCmWxTK?n4sfd zXdws#?G4YsZa2R|)C3>Ec^d;bsx}Y{$W!609-975 z=;Fc4;J)raENmuE)a~ou3O00WMW6W?B>zoaJ&8`8UtII8ja@zQjUlwhpyCvVzhL0Rt}IrCji?lBMU}Y8v&^&Hv%+(;XQgMA=N8Xu&+Q(s zr_@vCDfd)()_6)&`@gb&OaHbLAAV!-mz94ZM)SH4|N5KXoBx$>)YUgN3-M#u^-o;a z=Umsnab5q^b$!Nl{hzLD1G!xc$^-d8dYqiPJ3mD-NiDz0>1C3(Ae{kr;A+hjl#Ee4e(+*t~Z}9*mF#RO#gnMhLuhbE|wLv))J{ zU}#+}<39DKO1Xx15TMQsT3BX%;!%(a+3sxgc=LGiqK`_*nT2fOLVDP#Q^DH19%^R9 ziw;({19Koxhek;s;Oec{4Oe)}FwIfcOS4^eH_v`~xiKO=dX-;?nGDj?ndxFaL$3&_ zA*L)3`#hyik4^)l3?{6O&SHpNmMiXPwum*}iggtgYbxn4#bD-pbwz^WU7=9Fi{J9% zMod_2A~)6fd=RyV>7WA891!)%GeP*J>{KVViYBz222msK#SL8#weCpzE6)H?CrhT{ z0$sbpArfWsr}c!6uD~A3#O8P))`@;h)(bgSbPkhr_*ejQCKzgK{nYuKIlQy+Z(kfA z)}aa!5A6vm3>4 zRR7Vqd1H;y7KQg8z&66e@F*G&H zOV4WPS-tLXB0*!TY}0Vo7*f8kRti@65^Kzx$v4Gc^&&NSU^ZLH3nAWAEgjYG1-`Xb zc}hs*Z!?7AAz%ye4Zz*N`+;u&s$WVYbR~?ERc9Ov_hJ#8drrd|BAMfPJS4+gLf)J# z&Hm_A!zbMF2SbT=4Ad~=U`raMU8aMCwVFsW=P59c7Q#Xq1?y~vyV>tF~j8Orp>xI#~;(@Ze`5T3>xobtL_dfvLW#R30q$g2t{ z-_z8IQDp;!2gT(|H}x`h-pO_z?CI{_Z_j+@bE>vyhIGX*`R)j%nd`_`y`E%Gz|BWQP-2Hc{hz?cYIaL0eBpM=0K!K7yrVGGq8nVFT^KuYC(#|X$FrL;9Y{h3zs#_77jWDyma5?H%+5*b= zS^cRq95Seby26Fzw4ISaB1!#_iLn;uRDICT+ACy{fiV;4N>Dy%5$Gz=)u6?oC7^3S z*MhDC-2_?&x*l``=thtMS_)bYqInj@9QVo-rha*8jg~Yh98^nUVnD7;qSv)J2c-*U zlSl?oI35jA7j7z%IsmViJIfK>C9hcu{Awa-A(%Rh?ulS|8hyJt(Tn6+7rd}dtBx_K zzr3PCv05Rz(9v;xNK&op@dNQaSX{=GyIGaxs9i3UV{-x}9vT4_x~WjIv7ge%@pXjv zVC{3l=%oC|IuUgc6eAw%v2x4nEn3acD@|&N4D+}&k&BfGXr5Rz!Xk{Cmn&9%wTc#; zfwaSj3qkcdnO;Vct1ZM>_2cx{sTHG1WI!)2A?>vAgQ-Uyl|+tDDZ~>oL}yU*0p$k4 ziWL-#>pJop#ad*VRxmN9!QwNiKxCj!Rdy6;2Mtr~I7{z@Ovcie^3pe}`U=Ytm~sN&$lm zscWt0()B&Gk%jeXc|Ys8Lh{q94{{ts@?GxGN2>#uV^K^hYQ51Feh>61c1vFfNAk`JTYPe340oWGK8#RV{98@^RNvpzg=cK4sOcX{wxX($y42npz}_UNz9GC+ z-&fT%Lm#F4q~EHQ@>VQJVunq%D_Z@PJ)yO$0O9TWrF&e8+bCZMe^u=CetY;f8po<` zzrM+0-#FnokSiXgf2dsLjz@)KfuvW@ZNbInhWe(xrLDEwRMSJdA}`ZJ-q(wA$dtOj zS9`hACF>KWa;Wb`P-?Zz30~u|vm%=)lJRp;l^(|hqSEm+WQqj&BEHW}O0_f$CiQa; ze-;<2V)ApDO_co|P0{uq8j@3Obe!eeo%=r)h^+68UbjFzT8_%QTX9U4N3XQG?D*A zKhN|$F}*C$>nZSvf{hs4^L1?Lv^+5_SSb3g5#mdr77Gh*|KPRW?25DJ&3$W79_qhVy(&uPqeUCTEDNg44y^ifLlnvxTAv6{QUH zynHv>jqQ5LFJ_5foLeORCAm<{d8%-9`bdF4-PaG5T62><>~fQ;fhuZu>mB*$+|E@UT-5m z`A)uXzs`6Dar;oi9|hUp=k&uq6nhx;{xSG1UF&?#=_iEaT0Q53;CmG0c%Q=$@3UXa z=L*Ca{1&ZKWk>bA!tICm*~6&khLOg91<5j3&pG|DvTYBeo=a^I;wZ@eoYN0-&=zLN zwfSPE=a|TYT%bB^K;|zkgxpJ>5V1NTJ`O5;0d;}=f`jtzo2nJ!+aSMu2K=^Nb9+QvL^x`X9?2t*@LVxnTv0Y_OiCh>XA7r` zg-Gwb;B1+9{|X__f(CRt;BWVnaxqD5)FYPMJY6jEtP#_P3u5`+JSnsKAlsMTFkRem zu0mXS<_?tc1~If5 zqFO!cyvBQ7VYvUEC1y2F_br`YAm%4$N!q#7)6F#Z47=U{7bIxN>a%=)94+!?S%HXPV+% z4xW2zM8OF2aTI<_+vbS2R}*ua{hvCuVr&%RC~hV(DG8#9il#g!jxMu@rjHg61pn z-UY({xDej~&B^2U+OL-^$rH2eaojdb+;*l&7|BIqQE;J{g*XZjhwq(O(+2re9QJGG z`9kn~fywg}(z9gUd~sXv9I>)PVW4p91M z=82gHW{ctXKwtZFA)Zw4vR^M*1pV}Vtk-R!cR&Wt3(kf7MIAu-Q+cCX7E5a9irRAz zhz(~R6l;=kacl4)$j~k^V|bdkK)tgX^?u2tbH$_Qej*-f{;>#FjEW-9KMC39htdB0 zTpq&^ot(!K|cw87!;xb)SJ&dwqHw`)u?s1nPy+HWGVFe3+emMYP4gE>eOIRIhC*?}nN?!J zv$u%Z&)g~sn#)A$5c)n)Q30pXe!XP*46z`%QY@&rSu8+$X2+b7FunwaA$7Sl#a>Tuj9|0>Ev8tTSRL8mBf#M5NC zydHz!2>C525DU)TDhi*uO^}~BNg_~%?ZjXR*Y5+JS9x<^JD$Pg^e!`QT-)F8p?dhA z=>KG2UMR-CEX1u}ampd5AL6oykhVV z%xln>T!X&k8a#JR*;S(~Z)eEy(vqtY=TlOS%W?->#gZj7o|-FW#iz@*;3Yq5Gk>2a zuPMyZg$3xNNC%rMM!yMN=v$O5t`qiadt0lfc&5 ztL8xV%@s@0PhOv#hqhS+U17eIA?d#s;%lJC<-1T>5KbVRzI>kH5x<_ol5AEgQ1hiao&3E$_1cmiagf0cC%a*nPye*yW|eie)4bvOLBkRP6> z`}S+cGq`V$m)i7|kb~d}X${cUxUS1|plp^vJ{LZNlRVD|5&t^UuHIq4mOAU(AOmhH z5H~>=oetT-{ntXX|4QM#+)fq?-xqQ5CDeV2Yt=09e*yZQE7A8XLEm#V_=j= zDdap+c>A?I4%T6=fDSVkI?Nog@O{u>P-fFBKry^m>R8aFM}+t=s8_{lzh1I(mRRDs zQOq5iJvws)Qde<<%48JDU2-ktLFY6v<0mvqXC76|kfywnE`8=&QTXh2 zV)#d>o1;PuQJmKmi0gW1i^9%1((l=SL;tPbMPcmM_UD$|I$K-^`F$Pw*P^j2M(2#o z8n*W<&*7a*-c}&q7QBM`&=}>9`Vf|Lb42PK>coFR_nU<@(0s+2d14ys0QubqY9@b# zcVF9|<34RA+Mwa+)5hrCavXDqkaII?95ammWvNjpjC1S7;xn%kMbB)2oU9eT^O%?T zZ;b!cI~~_jzRnOsc^)w>AM>43j|T5-*Gv4E+rXHr1^OZ8O=MnW8LyZruBQHtG<&mF za{Z&ePU@7wc_LJ?P%Qb+d1G@&XZx1YoXfLw#H?rLifO2uY0TX&nCB7x*~AaXD%=ye zXZK%nU%t2xeRl!cG4WZxTaMYUGutqlj%ve8MS77({0{U*87Jg|?V5D~=rUtjB*MRhWy1v$Sf95slvnu9^YvR!L zVpsYpUW(}+gju`PBc4D!gi1g1d1iXV%r@kS`pP)>YaSZsRm>D~%clEg(wyW?kOL~7 z*I|DBYoHOtLn!&;!}E)#fe)WYyE`RFr(*u03^9*5ZvkmG%r`rdTmNV^hTQL4z~?j{ zg`4OA=6xTZmoNI}gP`)!+!>9Js{JVA4(L^@ftY}%`At6>|0u4juY*74LWZH=7cInC z1XP4Pq;Q|+NNBF2dKd1~Onm@-NhNd%5X}+MycEqbrPe{Nfqd&x$3epgKL#>(BMwj+ zGy*bO@UGY4-PfRQV(nlE6hoYT_{S(6;4RI67#k25D28;TiEw|6$b;}7$2Dm9pYblF zG4%itBpyUkL4FXWy$?7H8UqbcewKj{XVD!<3%#og`2zVtDfp#;Y0wbdV<7LHNCPMa z8X~$2{-6=WGmLOF{`bBCRE>Bx;&~9A2pNW-+mA{Qz_p^jlE=V&nsKGiW{N z9#9aJ0KEg$5Bd`5d!V0!eg|5xBwrYy*MS;9A<%x%dq5uteIE2}(DR^kpn2Eii<>~} zKuw?s=rK?qXb|)z&~u>Qf)+vEt_0PA+Cck2kApr6`Yh=CpkIJ~4_bU(zVL!-LHC0m z1icybIOqw`)1cF!e+2y!^a5x;)_q8lR!zqsY#T&(&M62*a=(LG;tgnSc2TtmC;*{x5DCjkXvl}z7rF376WW8f)5voa>OuJEOI(N`50qn+dJ#J68e$7r88}QNIEgW7{!S4?3w>j+(;SLN4$RlJr zggTin%OsK{^8#ij)h&Aj%1a;3vh!BovdU5UWJ#gAvpX7p5F0>ork(4D@;39mjZA_u z&vX#p<6_@_qn_0_{;V z=-A%mi*|L@lL0O+5o9J=-yKWtmwxggSiqCDeVep@=M=D1wXnOC;{)Fj4{g*Y);Gc* zo1|^8YJ!->xr174iIN@{pGB{e*Bak`_?{_a|XM<6BLp;blA;hiN8o9J>s)YOhs zR$XpiQ#v34s-&egokrS0uCr@a0i3#Y`qjczS;!qH2e-G%>>*?ip8XcJ8t0o-JIWq> z8(9iM-Oq7XSwT^Cl9Oj|?q=R?6^cV;sZe~Qkxc?+e_@mSl#S$#GZUutFyBZ%IU>QG z2INXLUh|3OFuqyrj5ebpbVm1aGC7=WFSq0YuD4RF!f6H>pxNAfPPcc`j--&1Lfd%v zJPQy$f9#YJyI~>FCS$Bis##?Hi=yPjn6l2gv~?-$X=j~CW3QF|J%aV^7%es!mS6R4 z`C_D$_TX85tBc`AxaV%P{N4h;0faknljV07el1v+_b;>jHmt_@0sGdr*!OJT3V#}yt_ zIH2(J3csuHCkoFg6u0nu7AQ0nu2xvB@P36ch3`=KK7}7s_@u%wEBvm)A1XYju;5mH zZ;?ioo-%d)_)5ONOI-&QMijn9;Q@tDD11`ku)^;ud|u)IQ7BY>Ua0V9g?TC+kEr)Y z6uwDegTnO+mn&SL@P(T>-9J(IErp*@*st&jg>O@MMm_&^g-;7Y|>uVqb>GvV!|GdJlD(qMIdxb*^%hYrFz3Y{KfkN&7Tb16&m4CCsPK9rB zg@23d`e_fwNa%d|NXe-@4D%f)DNg4%z~G*`UxTHCX&5k}gNDizlop=1!4QpjfSzjz zKTcmBj`d-ZU4ta1|0ww~K9X|N{SN=A#3$Ss2GSnR1jnM=t)?)s2 z?QD*k_eEoYJc#9kStg1FQv-bqMEDDhk+9q`lv&zE?x64~R zliv`6WmR`@vCyMTO}HKyn%3Nvs3DWQLOexoWS+;EpQbBti?2RKSHy1dsGC3F#k%~> z(ozgZbhovpnTm-y3T0p|K0kw%lFcxV^qXTif_} zeseq!NpwNwQAI$@tTgRI=31>%Ihp4Rg&wr7&*U{q6HK8v59YUH1l6GqUhWp3%ioCu z$81z5^FA!i2%b`7rH3lQXM}U+>(BC`3TZ7)9LaBv?u>-nX)frIJSb(m!to@mP+`mf zE`2ng%_sV_J*fm)@cV*QN7Nd!aFLi|qG#aiw2{=998JyeJgol+LU zfXWVOo;xAFK`}Y3i3(B0??!`+lbLzN=zGZ*WlpPapQ5LfJ1@Ucn(gAt6opb8!zmtw zEvK-^8RkRCm?>7=aLpm)bwL(;r`UTW^JGq2h*j3LsF5*R_qKuW6n%3PgLW7Mht;}# zb~Q)&4)#9f^D8&OR;HYY#YzFOTTP*e`zU^yo{!}1=;%nO3-P%;A7(4$4e@g;+sa{qo*c`l10sMwFbRTU*gN#))C# z&r#o{VNy!GDvhVTVP(M75n! zOpd@tfVKi89>a5Ny2|eRPD+E_1Jd4p{CacUR+ZUXL|r_vNA~!z*Gv4p)$-v=fE6eT zvk7OS$=>X8c~t}B8nGljk`PT>>Kk$kiU(FR(TR7um1F zdRS{~ZL79t-^y0o@YD+UCz8Qdvb)|IM$gh3UUM^_!nZ~u$qw-@ zin%=zi-`B~JCKF<%lyF$D`U8ugz0{i%R^Z9Ns1RmEV>VGdxv}u3Bsv7KwwR|oS&4R4=`_qB74K} zXoReUV3(RBL^KlW4JTWrr7>7m5v?Jqt%O<~H?G3_C>*vI#KI-riIRQcNJ)DyWbIq z&WhuCnD{JKv|_z-#?{0N+;SG0dvrs4Nuz{ z;i?*}@1+I+4G?XBlwk85|8I^O{j}#d=q~d zLC@4L&5UDfP^_@|vT_Pf@oVzsK8sYCnglgfsmw%entBsyXj@>lMf@q=TL%MSd+-$k z{XRYRc@1Cr9H5y#O_W;y9ejR!MB&(-eBE~!<4`r@XbodgE#sKN;X1xf)ie4wF~&AC zR&QY(Q7B%|*WSAs{SAz%ZH(#dj3YbLwNG8&!#LK+Xf!hpD~#<_{=1aF!qjfQ9%^A6 zy_Ye4AEW;bjD7br4!@C6v@&}AjQ#*)Uz_r8XDkXbjwpKfsuJCu90uj3e)69DAIx=sk?p?`2H&F%JJZ zqjSq~a|G=33UyLLF$S8iGt`+wE z6JHPiGvnCvj6?sznEs){A2AM(GLHV3(fBE&?+jz?Um4RsQ~1A?|5?WBpEDNyAI4FI z;um~v{F2f4?~JiuG4_oy4*dt?=zlVbbBxAs7_0w_(f@zd^?8N=%^3SFWBPZDBQG!( z{hrbHB4eLmI~OA!W$z-N(I{Z7p2p~(&X}6Pm@Z@-p2;{mi*al=z+%j3YNN3Pbs8T*}w6n;28e z7^|17>lKVcH#3f|RJe+<=vGGGZH%d6#-S3%k=2aDw<~|I@-J2XWsK=^lsI0$2hiuvFJ`l?_G?(YQ|IzV|pXwa4q9l9iy>{(YKi~wS_Ugm9g*j zjKg;`jx{hE+Zla37>j%g?_m^;jAIIin)rICS^4i|^zLS?Zefhw%b328arh04qxUl! zZ({VeD)ck@1B`ubjKl4WV?jpI!C171vAUBn7H0H5pzc4&IIJ+%#n#?goh7h|k` zh%pvtOeYwJlZ>N1j79qxt9u#6en#)ZjQ%$>_PvF1=ua3&A7Ly?F;+jy=zp8KuQ2^~ z<^LFC(VsGoC>(o-@;|`neJ7*uU5u%BGsYfg^#2)S-+LH`-^)1Kr|{1i#rqk(A7J!< zkkR)c#@Jsl_8nv#{xIX%6O7)EFvdR0nEn{!$X_vvLyX>jM*m^Pq9e-xD5LKfW9&F% zdVq0wka6?`BMxzQGAWj_&Q_tHb9LF=qK;}}NP z_;D;FtN-hA_~WQV*6=tQk<}kZB(nPB$V68E>Ky)9z{wgOM=P@WV>&dee_am$`W*h7 za`?k!Mb`MY+dLv$NN4ARaQ=<(*Pue!6vBZ6u?T{K&K(=TYjGZF zG5if6olP2nzug8C*>n0Os2Bc(Mc3ktF5HBF1xmwB_z$2{a1-7E!9s32-$ZAN=v{>0 z1Cd<|Is?>>Gc@F;voteqV0RI)4OE5n*zG3SZ7Pv%rP(*at_Z>qzJDps@xV>^8BibG zbcQMllJNr*%Qy{o+evo2N@Q=TA9M=O5{ebz1Kflypi#K}z_TE-$wK(V%?J-S;p&xG ztB0F#t8)8*Pk<;5Y2fq9Jqmm;1g7LO@OPj-JUey^zO->G_z8D4>c+P~XWoS`Ihi{h%ti?e?3V zPYQziIfNnnAm|+2gdYYKfG32X0U2=9*|-J>aH?Z;K2F<@BK$h&6w*ofeb9H{Cj1p> z6mEx2C z+nOT$I%pOA3BM2805{>UKt8y~fZE0s;XNQf{0ZL(O2SRp3pxNdodf(Di1;p@?nb-^tW z?iAd1n^$%_S!6Hk2s$z^>MG$+L3MBwz5r^5oAAlaST%y%ZhwjFGOgQzz795#2zP>3 z!A*ECs0wbvu=0-qzX>9_F#=rUgWQ1aAG@s~vO(l+L>q)Z;YLs&+QLlq9O`$J zo6e%%2O|E{`O|~SO=nGi3~Hulfp_1_l? zJq8+sKOxyxB0EcV8%tz+>EA$$;ZL~GPogkiR0DqtqH?6OzFiXLR>~ zs4t*%rym1RI>&(X+Ytuw5c)wMft&C#(9>`ez87>F?or?aA+!^?2`f8Lj&Kt;g1X=) z+zomQ+=OxEp8`I#hu=%*neXis+|L4gaTb`;L+62S0g;=|1-B|UoeTb)a??59UxO%2 zQ3N)xK;-rT->%$!z`s>)I@^%e#=rynKnicOyS=6TabbAwB@N-L@0ie7fsBu=xjn!lyyia1R5u zeI-INI{Q5Q36Fus;2r{A^M2gR$N5~~lOU2sL%xbrX32f*zI zei=kM-Z1dXf{|NBSA0u-&`62~;6f5B_!EBM1j-w3 zyDcEQogljnA-jDcvODxYprUE{67P5tx&qvUM?n>E6E=SwauseuI%nSuH{nM>R9ETT z{dbgmg#15&dWF0U1AhRbFr&bNPcr|DfcGf3ANaU(_W_?$ZZU*CCLk)WYTz$H#D_89 zeNVA0@&i|X8uhaf0>1(x`v zCqQSA{xtCa{w>-j;(fuH*kcni1PHqaBJD64;ie1SjV z+kb(whMVvQzeIb1oA5t=g|b7w$e{JUb4WAg5vc6~*=_%jO`xLlNE^ZsE&`?CCM*H< z!ELwOL-u^GhmDbE;ZL{@bPn!nU>AtWE(ZLHat{N^CJFfyzAn!rc~uQO0wRCA%^$J_ zbQ7pxHf%%y*MJtoT@Cy)h{6v8|E>^rSl~YbtcNY1RdD-&?*~y?*zNd`ouBx8k4Pd6 z;paeoa1R437I2)^z?5>wVCUs&kbD<#_Ck)6Y`<(!Zn6RMZsqO+u7j-~3SSL;mvY+tMrzgHUvP^Xb)P|BHew*BSJ;y<$heH8-J z_W}OI<9v#)Hr&qq~4<%OK zb?4k!8|b@t-ECd_4MY%0+_4hh_FA84?+oF~+9lm#oFGp`JCgKWm-T@}_v*c+D-9ev z3U^@FuQD0IZ$nguu^}19XF=&Shl=f{31Tb1Nj`@s6F9Jg^F;gAHGHJa3Vf<92+J6I zu{&~4C}G~T-!{+sG=%ozv&CKX_l}hT9EjN)eJ~VXY4n6?H*^Ad>j-ouLMx5i&3HE4 zp8G8uZg-?hSR4sw2K5Ek{O=oIfIt79K4+x)@_3bcQ&ggMK@`IBld?rM>B@bai@5+Lvxg z`_rB2Sh_czN*_q~r4Odl>4Ef6`c!&2eL6joel9(lKARp(pHGWJg@=j`Ek0x%T6M^K zsOnJlp)H4ehguH#4|N`j9qK)lI&|Pr-=Twt(uW2P4IMglX!y|SLnDWtJ2ZOe?4hwk z=MRbg!v3QE#r;PAs(x>OReyE=mVRGUUk@exax5A;Vp-Khg%N&4|g7p9qv7xI(*=8 z-{FIY(}xER4;?-QPMkhGa`?H!qleEP9y@&gusBk9r0B@vBgTj#MAnB01$h z(s?9yr1wbb$bln$M-CoI9~n3@bmY{L;UlMyj2w9moIHDE%*IXQ=&GaMqg6+%k8U~Y zJKA#8f3)*x%q*9KD4BtyLq|^?9X@*c=*ZFMj*cEZdvxsR`J>`k;jyA)i;o${Rvq&m zt2$PFY|AmbjvqMQcl_Y-^znh?L&r}Y zA3lEi_{j0+j*lKcdwlHp`QuPx2Z{z34;TZh2D}4R1Jwgt27Ci81O9=|f!ILrKx*K? zK;OW@f%L$@z|g>{f#HGE10w^^4U7()9T*!pKOhDR2a5(54;q832EBt-gVlpu27QAq zgZ{zJ!PsE$U~2HdVBg@u!Svw3;LzZy!QsKvgCm2_4UP_;9UL1xKPXNVo+vu8_=Is{ z)d}y3suR^Gww&;tXgT3O(Rm_vqW47V#DNoiCk~!SpBOkXbmG*B;S;A%jGTDx#OR5$ zC&o^kKOs&Qo-8`K_@r@i)k*Kks*}|xx198yY&q#a*?BT{GIjFc$)S^{PmZ2Ef3oPw VRZmtw+45xU$pcTK?*9?|{vY>;ojw2n