From a2ba761ae1698bab20da2960a1f959efca8cd1c8 Mon Sep 17 00:00:00 2001 From: zeromus Date: Mon, 27 Jan 2014 00:02:21 +0000 Subject: [PATCH] BizwareGL! --- .../BizHawk.Client.EmuHawk.csproj | 18 +- .../DisplayManager/DisplayManager.cs | 655 +------ .../DisplayManager/DisplaySurface.cs | 254 +++ .../DisplayManager/IDisplayFilter.cs | 20 + .../DisplayManager/OSDManager.cs | 356 ++++ .../SwappableBitmapBufferSet.cs | 75 + BizHawk.Client.EmuHawk/MainForm.Events.cs | 5 +- BizHawk.Client.EmuHawk/MainForm.cs | 163 +- BizHawk.Client.EmuHawk/RenderPanel.cs | 617 ++----- .../Resources/courier16px.bmfc | 55 + .../Resources/courier16px.fnt | 208 +++ .../Resources/courier16px_0.png | Bin 0 -> 4014 bytes .../Lua/Libraries/EmuLuaLibrary.Input.cs | 1 + .../tools/SNES/SNESGraphicsDebugger.cs | 2 +- BizHawk.sln | 32 + .../BizHawk.Bizware.BizwareGL.OpenTK.csproj | 73 + .../BizHawk.Bizware.BizwareGL.OpenTK/Enums.cs | 0 .../IGL_TK.cs | 503 ++++++ Bizware/BizHawk.Bizware.BizwareGL/Art.cs | 24 + .../BizHawk.Bizware.BizwareGL/ArtManager.cs | 165 ++ .../BizHawk.Bizware.BizwareGL/BitmapBuffer.cs | 444 +++++ .../BitmapLoadOptions.cs | 42 + .../BizHawk.Bizware.BizwareGL.csproj | 104 ++ .../Borrowed/BitmapFontParser/BitmapFont.cs | 184 ++ .../BitmapFontParser/BitmapFontLoader.cs | 457 +++++ .../Borrowed/BitmapFontParser/Character.cs | 34 + .../Borrowed/BitmapFontParser/Kerning.cs | 38 + .../Borrowed/BitmapFontParser/Padding.cs | 41 + .../Borrowed/BitmapFontParser/Page.cs | 37 + .../Borrowed/readme.txt | 4 + Bizware/BizHawk.Bizware.BizwareGL/Enums.cs | 573 +++++++ .../GraphicsControl.cs | 42 + .../BizHawk.Bizware.BizwareGL/GuiRenderer.cs | 305 ++++ Bizware/BizHawk.Bizware.BizwareGL/IGL.cs | 204 +++ .../BizHawk.Bizware.BizwareGL/MatrixStack.cs | 77 + Bizware/BizHawk.Bizware.BizwareGL/Pipeline.cs | 81 + .../PipelineUniform.cs | 39 + .../BizHawk.Bizware.BizwareGL/RenderStates.cs | 7 + Bizware/BizHawk.Bizware.BizwareGL/Shader.cs | 29 + .../StringRenderer.cs | 80 + Bizware/BizHawk.Bizware.BizwareGL/TexAtlas.cs | 325 ++++ .../BizHawk.Bizware.BizwareGL/Texture2d.cs | 82 + .../Types/BoundingBox.cs | 426 +++++ .../Types/BoundingFrustum.cs | 513 ++++++ .../Types/BoundingSphere.cs | 363 ++++ .../BizHawk.Bizware.BizwareGL/Types/Enums.cs | 76 + .../Types/MathHelper.cs | 150 ++ .../BizHawk.Bizware.BizwareGL/Types/Matrix.cs | 1515 +++++++++++++++++ .../BizHawk.Bizware.BizwareGL/Types/Plane.cs | 212 +++ .../Types/PlaneHelper.cs | 33 + .../BizHawk.Bizware.BizwareGL/Types/Point.cs | 119 ++ .../Types/Quaternion.cs | 687 ++++++++ .../BizHawk.Bizware.BizwareGL/Types/Ray.cs | 233 +++ .../Types/Rectangle.cs | 312 ++++ .../BizHawk.Bizware.BizwareGL/Types/Size.cs | 368 ++++ .../Types/Vector2.cs | 599 +++++++ .../Types/Vector3.cs | 679 ++++++++ .../Types/Vector4.cs | 709 ++++++++ .../BizHawk.Bizware.BizwareGL/UniformInfo.cs | 12 + .../BizHawk.Bizware.BizwareGL/VertexLayout.cs | 99 ++ .../BizHawk.Bizware.Test.csproj | 99 ++ Bizware/BizHawk.Bizware.Test/Program.cs | 83 + .../BizHawk.Bizware.Test/TestForm.Designer.cs | 48 + Bizware/BizHawk.Bizware.Test/TestForm.cs | 19 + Bizware/BizHawk.Bizware.Test/TestForm.resx | 120 ++ .../TestImages/courier16px.fnt | 208 +++ .../TestImages/courier16px_0.png | Bin 0 -> 3074 bytes .../TestImages/flame1.jpg | Bin 0 -> 11356 bytes .../TestImages/flame2.jpg | Bin 0 -> 9812 bytes .../TestImages/flame3.jpg | Bin 0 -> 7078 bytes .../TestImages/flame4.jpg | Bin 0 -> 7622 bytes .../TestImages/flame5.jpg | Bin 0 -> 7761 bytes .../BizHawk.Bizware.Test/TestImages/smile.png | Bin 0 -> 2187 bytes Bizware/BizHawk.Bizware.sln | 54 + References/OpenTK.Compatibility.dll | Bin 0 -> 3211264 bytes References/OpenTK.GLControl.dll | Bin 0 -> 28672 bytes References/OpenTK.dll | Bin 0 -> 3892736 bytes References/OpenTK.dll.config | 16 + 78 files changed, 13008 insertions(+), 1199 deletions(-) create mode 100644 BizHawk.Client.EmuHawk/DisplayManager/DisplaySurface.cs create mode 100644 BizHawk.Client.EmuHawk/DisplayManager/IDisplayFilter.cs create mode 100644 BizHawk.Client.EmuHawk/DisplayManager/OSDManager.cs create mode 100644 BizHawk.Client.EmuHawk/DisplayManager/SwappableBitmapBufferSet.cs create mode 100644 BizHawk.Client.EmuHawk/Resources/courier16px.bmfc create mode 100644 BizHawk.Client.EmuHawk/Resources/courier16px.fnt create mode 100644 BizHawk.Client.EmuHawk/Resources/courier16px_0.png create mode 100644 Bizware/BizHawk.Bizware.BizwareGL.OpenTK/BizHawk.Bizware.BizwareGL.OpenTK.csproj create mode 100644 Bizware/BizHawk.Bizware.BizwareGL.OpenTK/Enums.cs create mode 100644 Bizware/BizHawk.Bizware.BizwareGL.OpenTK/IGL_TK.cs create mode 100644 Bizware/BizHawk.Bizware.BizwareGL/Art.cs create mode 100644 Bizware/BizHawk.Bizware.BizwareGL/ArtManager.cs create mode 100644 Bizware/BizHawk.Bizware.BizwareGL/BitmapBuffer.cs create mode 100644 Bizware/BizHawk.Bizware.BizwareGL/BitmapLoadOptions.cs create mode 100644 Bizware/BizHawk.Bizware.BizwareGL/BizHawk.Bizware.BizwareGL.csproj create mode 100644 Bizware/BizHawk.Bizware.BizwareGL/Borrowed/BitmapFontParser/BitmapFont.cs create mode 100644 Bizware/BizHawk.Bizware.BizwareGL/Borrowed/BitmapFontParser/BitmapFontLoader.cs create mode 100644 Bizware/BizHawk.Bizware.BizwareGL/Borrowed/BitmapFontParser/Character.cs create mode 100644 Bizware/BizHawk.Bizware.BizwareGL/Borrowed/BitmapFontParser/Kerning.cs create mode 100644 Bizware/BizHawk.Bizware.BizwareGL/Borrowed/BitmapFontParser/Padding.cs create mode 100644 Bizware/BizHawk.Bizware.BizwareGL/Borrowed/BitmapFontParser/Page.cs create mode 100644 Bizware/BizHawk.Bizware.BizwareGL/Borrowed/readme.txt create mode 100644 Bizware/BizHawk.Bizware.BizwareGL/Enums.cs create mode 100644 Bizware/BizHawk.Bizware.BizwareGL/GraphicsControl.cs create mode 100644 Bizware/BizHawk.Bizware.BizwareGL/GuiRenderer.cs create mode 100644 Bizware/BizHawk.Bizware.BizwareGL/IGL.cs create mode 100644 Bizware/BizHawk.Bizware.BizwareGL/MatrixStack.cs create mode 100644 Bizware/BizHawk.Bizware.BizwareGL/Pipeline.cs create mode 100644 Bizware/BizHawk.Bizware.BizwareGL/PipelineUniform.cs create mode 100644 Bizware/BizHawk.Bizware.BizwareGL/RenderStates.cs create mode 100644 Bizware/BizHawk.Bizware.BizwareGL/Shader.cs create mode 100644 Bizware/BizHawk.Bizware.BizwareGL/StringRenderer.cs create mode 100644 Bizware/BizHawk.Bizware.BizwareGL/TexAtlas.cs create mode 100644 Bizware/BizHawk.Bizware.BizwareGL/Texture2d.cs create mode 100644 Bizware/BizHawk.Bizware.BizwareGL/Types/BoundingBox.cs create mode 100644 Bizware/BizHawk.Bizware.BizwareGL/Types/BoundingFrustum.cs create mode 100644 Bizware/BizHawk.Bizware.BizwareGL/Types/BoundingSphere.cs create mode 100644 Bizware/BizHawk.Bizware.BizwareGL/Types/Enums.cs create mode 100644 Bizware/BizHawk.Bizware.BizwareGL/Types/MathHelper.cs create mode 100644 Bizware/BizHawk.Bizware.BizwareGL/Types/Matrix.cs create mode 100644 Bizware/BizHawk.Bizware.BizwareGL/Types/Plane.cs create mode 100644 Bizware/BizHawk.Bizware.BizwareGL/Types/PlaneHelper.cs create mode 100644 Bizware/BizHawk.Bizware.BizwareGL/Types/Point.cs create mode 100644 Bizware/BizHawk.Bizware.BizwareGL/Types/Quaternion.cs create mode 100644 Bizware/BizHawk.Bizware.BizwareGL/Types/Ray.cs create mode 100644 Bizware/BizHawk.Bizware.BizwareGL/Types/Rectangle.cs create mode 100644 Bizware/BizHawk.Bizware.BizwareGL/Types/Size.cs create mode 100644 Bizware/BizHawk.Bizware.BizwareGL/Types/Vector2.cs create mode 100644 Bizware/BizHawk.Bizware.BizwareGL/Types/Vector3.cs create mode 100644 Bizware/BizHawk.Bizware.BizwareGL/Types/Vector4.cs create mode 100644 Bizware/BizHawk.Bizware.BizwareGL/UniformInfo.cs create mode 100644 Bizware/BizHawk.Bizware.BizwareGL/VertexLayout.cs create mode 100644 Bizware/BizHawk.Bizware.Test/BizHawk.Bizware.Test.csproj create mode 100644 Bizware/BizHawk.Bizware.Test/Program.cs create mode 100644 Bizware/BizHawk.Bizware.Test/TestForm.Designer.cs create mode 100644 Bizware/BizHawk.Bizware.Test/TestForm.cs create mode 100644 Bizware/BizHawk.Bizware.Test/TestForm.resx create mode 100644 Bizware/BizHawk.Bizware.Test/TestImages/courier16px.fnt create mode 100644 Bizware/BizHawk.Bizware.Test/TestImages/courier16px_0.png create mode 100644 Bizware/BizHawk.Bizware.Test/TestImages/flame1.jpg create mode 100644 Bizware/BizHawk.Bizware.Test/TestImages/flame2.jpg create mode 100644 Bizware/BizHawk.Bizware.Test/TestImages/flame3.jpg create mode 100644 Bizware/BizHawk.Bizware.Test/TestImages/flame4.jpg create mode 100644 Bizware/BizHawk.Bizware.Test/TestImages/flame5.jpg create mode 100644 Bizware/BizHawk.Bizware.Test/TestImages/smile.png create mode 100644 Bizware/BizHawk.Bizware.sln create mode 100644 References/OpenTK.Compatibility.dll create mode 100644 References/OpenTK.GLControl.dll create mode 100644 References/OpenTK.dll create mode 100644 References/OpenTK.dll.config diff --git a/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj b/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj index 2a62797602..a9e451eda0 100644 --- a/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj +++ b/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj @@ -378,8 +378,10 @@ - - + + + + @@ -1159,6 +1161,7 @@ ToolBox.cs + @@ -1181,6 +1184,14 @@ {f51946ea-827f-4d82-b841-1f2f6d060312} BizHawk.Emulation.DiscSystem + + {5160CFB1-5389-47C1-B7F6-8A0DC97641EE} + BizHawk.Bizware.BizwareGL.OpenTK + + + {9F84A0B2-861E-4EF4-B89B-5E2A3F38A465} + BizHawk.Bizware.BizwareGL + @@ -1262,6 +1273,7 @@ + @@ -1401,7 +1413,7 @@ - + + \ No newline at end of file diff --git a/Bizware/BizHawk.Bizware.BizwareGL.OpenTK/Enums.cs b/Bizware/BizHawk.Bizware.BizwareGL.OpenTK/Enums.cs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Bizware/BizHawk.Bizware.BizwareGL.OpenTK/IGL_TK.cs b/Bizware/BizHawk.Bizware.BizwareGL.OpenTK/IGL_TK.cs new file mode 100644 index 0000000000..6b853f1414 --- /dev/null +++ b/Bizware/BizHawk.Bizware.BizwareGL.OpenTK/IGL_TK.cs @@ -0,0 +1,503 @@ +//regarding binding and vertex arrays: +//http://stackoverflow.com/questions/8704801/glvertexattribpointer-clarification +//http://stackoverflow.com/questions/9536973/oes-vertex-array-object-and-client-state +//http://www.opengl.org/wiki/Vertex_Specification + +//etc +//glBindAttribLocation (programID, 0, "vertexPosition_modelspace"); + +//for future reference: c# tesselators +//http://www.opentk.com/node/437 (AGG#, codes on Tao forums) + +using System; +using System.Threading; +using System.IO; +using System.Collections.Generic; +using sd = System.Drawing; +using sdi = System.Drawing.Imaging; +using swf=System.Windows.Forms; + +using BizHawk.Bizware.BizwareGL; + +using OpenTK; +using OpenTK.Graphics; +using OpenTK.Graphics.OpenGL; + +namespace BizHawk.Bizware.BizwareGL.Drivers.OpenTK +{ + /// + /// OpenTK implementation of the BizwareGL.IGL interface. + /// TODO - can we have more than one of these? could be dangerous. such dangerous things to be possibly reconsidered are marked with HAMNUTS + /// TODO - if we have any way of making contexts, we also need a way of freeing it, and then we can cleanup our dictionaries + /// + public class IGL_TK : IGL + { + static IGL_TK() + { + //make sure OpenTK initializes without getting wrecked on the SDL check and throwing an exception to annoy our MDA's + var toolkitOptions = global::OpenTK.ToolkitOptions.Default; + toolkitOptions.Backend = PlatformBackend.PreferNative; + global::OpenTK.Toolkit.Init(toolkitOptions); + //NOTE: this throws EGL exceptions anyway. I'm going to ignore it and whine about it later + } + + public IGL_TK() + { + OffscreenNativeWindow = new NativeWindow(); + OffscreenNativeWindow.ClientSize = new sd.Size(8, 8); + this.GraphicsContext = new GraphicsContext(GraphicsMode.Default, OffscreenNativeWindow.WindowInfo, 2, 0, GraphicsContextFlags.Default); + MakeDefaultCurrent(); + this.GraphicsContext.LoadAll(); //this is important for reasons unknown + CreateRenderStates(); + } + + void IGL.Clear(ClearBufferMask mask) + { + GL.Clear((global::OpenTK.Graphics.OpenGL.ClearBufferMask)mask); + } + void IGL.ClearColor(sd.Color color) + { + GL.ClearColor(color); + } + + class GLControlWrapper : GraphicsControl + { + //Note: In order to work around bugs in OpenTK which sometimes do things to a context without making that context active first... + //we are going to push and pop the context before doing stuff + + public GLControlWrapper(IGL_TK owner) + { + Owner = owner; + } + + IGL_TK Owner; + + public override swf.Control Control { get { return MyControl; } } + + public override void SetVsync(bool state) + { + //IGraphicsContext curr = global::OpenTK.Graphics.GraphicsContext.CurrentContext; + MyControl.MakeCurrent(); + MyControl.VSync = state; + //Owner.MakeContextCurrent(curr, Owner.NativeWindowsForContexts[curr]); + } + + public override void Begin() + { + Owner.MakeContextCurrent(MyControl.Context, MyControl.WindowInfo); + } + + public override void End() + { + //this slows things down too much + //Owner.MakeDefaultCurrent(); + } + + public override void SwapBuffers() + { + //IGraphicsContext curr = global::OpenTK.Graphics.GraphicsContext.CurrentContext; + MyControl.MakeCurrent(); + MyControl.SwapBuffers(); + //Owner.MakeContextCurrent(curr, Owner.NativeWindowsForContexts[curr]); + } + + public override void Dispose() + { + //TODO - what happens if this context was current? + MyControl.Dispose(); + MyControl = null; + } + + public GLControl MyControl; + } + + GraphicsControl IGL.CreateGraphicsControl() + { + var glc = new GLControl(GraphicsMode.Default, 2, 0, GraphicsContextFlags.Default); + glc.CreateControl(); + + //now the control's context will be current. annoying! fix it. + MakeDefaultCurrent(); + + GLControlWrapper wrapper = new GLControlWrapper(this); + wrapper.MyControl = glc; + return wrapper; + } + + IntPtr IGL.GenTexture() { return new IntPtr(GL.GenTexture()); } + void IGL.FreeTexture(IntPtr texHandle) { GL.DeleteTexture(texHandle.ToInt32()); } + IntPtr IGL.GetEmptyHandle() { return new IntPtr(0); } + IntPtr IGL.GetEmptyUniformHandle() { return new IntPtr(-1); } + + Shader IGL.CreateFragmentShader(string source) + { + int sid = GL.CreateShader(ShaderType.FragmentShader); + CompileShaderSimple(sid,source); + return new Shader(this,new IntPtr(sid)); + } + Shader IGL.CreateVertexShader(string source) + { + int sid = GL.CreateShader(ShaderType.VertexShader); + CompileShaderSimple(sid, source); + return new Shader(this, new IntPtr(sid)); + } + + void IGL.FreeShader(IntPtr shader) { GL.DeleteShader(shader.ToInt32()); } + + class MyBlendState : IBlendState + { + public bool enabled; + public global::OpenTK.Graphics.OpenGL.BlendingFactorSrc colorSource; + public global::OpenTK.Graphics.OpenGL.BlendEquationMode colorEquation; + public global::OpenTK.Graphics.OpenGL.BlendingFactorDest colorDest; + public global::OpenTK.Graphics.OpenGL.BlendingFactorSrc alphaSource; + public global::OpenTK.Graphics.OpenGL.BlendEquationMode alphaEquation; + public global::OpenTK.Graphics.OpenGL.BlendingFactorDest alphaDest; + public MyBlendState(bool enabled, BlendingFactor colorSource, BlendEquationMode colorEquation, BlendingFactor colorDest, + BlendingFactor alphaSource, BlendEquationMode alphaEquation, BlendingFactor alphaDest) + { + this.enabled = enabled; + this.colorSource = (global::OpenTK.Graphics.OpenGL.BlendingFactorSrc)colorSource; + this.colorEquation = (global::OpenTK.Graphics.OpenGL.BlendEquationMode)colorEquation; + this.colorDest = (global::OpenTK.Graphics.OpenGL.BlendingFactorDest)colorDest; + this.alphaSource = (global::OpenTK.Graphics.OpenGL.BlendingFactorSrc)alphaSource; + this.alphaEquation = (global::OpenTK.Graphics.OpenGL.BlendEquationMode)alphaEquation; + this.alphaDest = (global::OpenTK.Graphics.OpenGL.BlendingFactorDest)alphaDest; + } + } + IBlendState IGL.CreateBlendState(BlendingFactor colorSource, BlendEquationMode colorEquation, BlendingFactor colorDest, + BlendingFactor alphaSource, BlendEquationMode alphaEquation, BlendingFactor alphaDest) + { + return new MyBlendState(true, colorSource, colorEquation, colorDest, alphaSource, alphaEquation, alphaDest); + } + + void IGL.SetBlendState(IBlendState rsBlend) + { + var mybs = rsBlend as MyBlendState; + if (mybs.enabled) + { + GL.Enable(EnableCap.Blend); + GL.BlendEquationSeparate(mybs.colorEquation, mybs.alphaEquation); + GL.BlendFuncSeparate(mybs.colorSource, mybs.colorDest, mybs.alphaSource, mybs.alphaDest); + } + else GL.Disable(EnableCap.Blend); + } + + IBlendState IGL.BlendNone { get { return _rsBlendNone; } } + IBlendState IGL.BlendNormal { get { return _rsBlendNormal; } } + + Pipeline IGL.CreatePipeline(Shader vertexShader, Shader fragmentShader) + { + ErrorCode errcode; + int pid = GL.CreateProgram(); + GL.AttachShader(pid, vertexShader.Id.ToInt32()); + errcode = GL.GetError(); + GL.AttachShader(pid, fragmentShader.Id.ToInt32()); + errcode = GL.GetError(); + GL.LinkProgram(pid); + errcode = GL.GetError(); + int n; + GL.GetProgram(pid, GetProgramParameterName.LinkStatus, out n); + + string result = GL.GetProgramInfoLog(pid); + if (result != "") + throw new InvalidOperationException("Error creating pipeline (program link step):\r\n\r\n" + result); + + GL.ValidateProgram(pid); + errcode = GL.GetError(); + + result = GL.GetProgramInfoLog(pid); + if (result != "") + throw new InvalidOperationException("Error creating pipeline (program validate step):\r\n\r\n" + result); + + //get all the uniforms + List uniforms = new List(); + int nUniforms; + int nSamplers = 0; + GL.GetProgram(pid,GetProgramParameterName.ActiveUniforms,out nUniforms); + for (int i = 0; i < nUniforms; i++) + { + int size, length; + ActiveUniformType type; + var sbName = new System.Text.StringBuilder(); + GL.GetActiveUniform(pid, i, 1024, out length, out size, out type, sbName); + string name = sbName.ToString(); + int loc = GL.GetUniformLocation(pid, name); + var ui = new UniformInfo(); + ui.Name = name; + ui.Handle = new IntPtr(loc); + + //automatically assign sampler uniforms to texture units (and bind them) + bool isSampler = (type == ActiveUniformType.Sampler2D); + if (isSampler) + { + ui.SamplerIndex = nSamplers; + GL.Uniform1(loc, nSamplers); + nSamplers++; + } + + uniforms.Add(ui); + } + + + return new Pipeline(this, new IntPtr(pid), uniforms); + } + + VertexLayout IGL.CreateVertexLayout() { return new VertexLayout(this,new IntPtr(0)); } + + void IGL.BindTexture2d(Texture2d tex) + { + GL.BindTexture(TextureTarget.Texture2D, tex.Id.ToInt32()); + } + + unsafe void IGL.BindVertexLayout(VertexLayout layout) + { + StatePendingVertexLayouts[CurrentContext] = layout; + } + + unsafe void IGL.BindArrayData(void* pData) + { + MyBindArrayData(StatePendingVertexLayouts[CurrentContext], pData); + } + + void IGL.DrawArrays(PrimitiveType mode, int first, int count) + { + GL.DrawArrays((global::OpenTK.Graphics.OpenGL.PrimitiveType)mode, first, count); + } + + void IGL.BindPipeline(Pipeline pipeline) + { + GL.UseProgram(pipeline.Id.ToInt32()); + } + + unsafe void IGL.SetPipelineUniformMatrix(PipelineUniform uniform, Matrix mat, bool transpose) + { + GL.UniformMatrix4(uniform.Id.ToInt32(), 1, transpose, (float*)&mat); + } + + unsafe void IGL.SetPipelineUniformMatrix(PipelineUniform uniform, ref Matrix mat, bool transpose) + { + fixed(Matrix* pMat = &mat) + GL.UniformMatrix4(uniform.Id.ToInt32(), 1, transpose, (float*)pMat); + } + + void IGL.SetPipelineUniform(PipelineUniform uniform, Vector4 value) + { + GL.Uniform4(uniform.Id.ToInt32(), value.X, value.Y, value.Z, value.W); + } + + void IGL.SetPipelineUniformSampler(PipelineUniform uniform, IntPtr texHandle) + { + //set the sampler index into the uniform first + GL.Uniform1(uniform.Id.ToInt32(), uniform.SamplerIndex); + //now bind the texture + GL.ActiveTexture((TextureUnit)((int)TextureUnit.Texture0 + uniform.SamplerIndex)); + GL.BindTexture(TextureTarget.Texture2D, texHandle.ToInt32()); + } + + void IGL.TexParameter2d(TextureParameterName pname, int param) + { + GL.TexParameter(TextureTarget.Texture2D, (global::OpenTK.Graphics.OpenGL.TextureParameterName)pname, param); + } + + Texture2d IGL.LoadTexture(sd.Bitmap bitmap) + { + using (var bmp = new BitmapBuffer(bitmap, new BitmapLoadOptions())) + return (this as IGL).LoadTexture(bmp); + } + + Texture2d IGL.LoadTexture(Stream stream) + { + using(var bmp = new BitmapBuffer(stream,new BitmapLoadOptions())) + return (this as IGL).LoadTexture(bmp); + } + + Texture2d IGL.CreateTexture(int width, int height) + { + IntPtr id = (this as IGL).GenTexture(); + return new Texture2d(this, id, width, height); + } + + void IGL.LoadTextureData(Texture2d tex, BitmapBuffer bmp) + { + sdi.BitmapData bmp_data = bmp.LockBits(); + try + { + GL.BindTexture(TextureTarget.Texture2D, tex.Id.ToInt32()); + GL.TexSubImage2D(TextureTarget.Texture2D, 0, 0, 0, bmp.Width, bmp.Height, PixelFormat.Bgra, PixelType.UnsignedByte, bmp_data.Scan0); + } + finally + { + bmp.UnlockBits(bmp_data); + } + } + + Texture2d IGL.LoadTexture(BitmapBuffer bmp) + { + Texture2d ret = null; + IntPtr id = (this as IGL).GenTexture(); + try + { + ret = new Texture2d(this, id, bmp.Width, bmp.Height); + GL.BindTexture(TextureTarget.Texture2D, id.ToInt32()); + //picking a color order that matches doesnt seem to help, any. maybe my driver is accelerating it, or maybe it isnt a big deal. but its something to study on another day + GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, bmp.Width, bmp.Height, 0, PixelFormat.Bgra, PixelType.UnsignedByte, IntPtr.Zero); + (this as IGL).LoadTextureData(ret, bmp); + } + catch + { + (this as IGL).FreeTexture(id); + throw; + } + + //set default filtering.. its safest to do this always + ret.SetFilterNearest(); + + return ret; + } + + Texture2d IGL.LoadTexture(string path) + { + using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) + return (this as IGL).LoadTexture(fs); + } + + Matrix IGL.CreateGuiProjectionMatrix(int w, int h) + { + Matrix ret = Matrix.Identity; + ret.M11 = 2.0f / (float)w; + ret.M22 = 2.0f / (float)h; + return ret; + } + + Matrix IGL.CreateGuiViewMatrix(int w, int h) + { + Matrix ret = Matrix.Identity; + ret.M22 = -1.0f; + ret.M41 = -w * 0.5f; // -0.5f; + ret.M42 = h * 0.5f; // +0.5f; + return ret; + } + + void IGL.SetViewport(int x, int y, int width, int height) + { + ErrorCode errcode; + GL.Viewport(x, y, width, height); + errcode = GL.GetError(); + } + + void IGL.SetViewport(swf.Control control) + { + ErrorCode errcode; + var r = control.ClientRectangle; + errcode = GL.GetError(); + GL.Viewport(r.Left, r.Top, r.Width, r.Height); + } + + //------------------ + + INativeWindow OffscreenNativeWindow; + IGraphicsContext GraphicsContext; + + //--------------- + //my utility methods + + global::OpenTK.Graphics.IGraphicsContext CurrentContext { get { return global::OpenTK.Graphics.GraphicsContext.CurrentContext; } } + + GLControl CastControl(swf.Control swfControl) + { + GLControl glc = swfControl as GLControl; + if (glc == null) + throw new ArgumentException("Argument isn't a control created by the IGL interface", "glControl"); + return glc; + } + + void CompileShaderSimple(int sid, string source) + { + GL.ShaderSource(sid, source); + ErrorCode errcode = GL.GetError(); + GL.CompileShader(sid); + errcode = GL.GetError(); + int n; + GL.GetShader(sid, ShaderParameter.CompileStatus, out n); + string result = GL.GetShaderInfoLog(sid); + if (result != "") + throw new InvalidOperationException("Error compiling shader:\r\n\r\n" + result); + + //HAX??? + GL.Enable(EnableCap.Texture2D); + //GL.PolygonMode(MaterialFace.Back, PolygonMode.Line); //?? + //GL.PolygonMode(MaterialFace.Front, PolygonMode.Line); //?? + } + + Dictionary StateCurrentVertexLayouts = new Dictionary(); + Dictionary StatePendingVertexLayouts = new Dictionary(); + WorkingDictionary> VertexAttribEnables = new WorkingDictionary>(); + void UnbindVertexAttributes() + { + //HAMNUTS: + //its not clear how many bindings we'll have to disable before we can enable the ones we need.. + //so lets just disable the ones we remember we have bound + var currBindings = VertexAttribEnables[CurrentContext]; + foreach (var index in currBindings) + GL.DisableVertexAttribArray(index); + currBindings.Clear(); + } + + unsafe void MyBindArrayData(VertexLayout layout, void* pData) + { + ErrorCode errcode; + + UnbindVertexAttributes(); + + //HAMNUTS (continued) + var currBindings = VertexAttribEnables[CurrentContext]; + StateCurrentVertexLayouts[CurrentContext] = StatePendingVertexLayouts[CurrentContext]; + + foreach (var kvp in layout.Items) + { + GL.VertexAttribPointer(kvp.Key, kvp.Value.Components, (VertexAttribPointerType)kvp.Value.AttribType, kvp.Value.Normalized, kvp.Value.Stride, new IntPtr(pData) + kvp.Value.Offset); + errcode = GL.GetError(); + GL.EnableVertexAttribArray(kvp.Key); + errcode = GL.GetError(); + currBindings.Add(kvp.Key); + } + } + + void MakeDefaultCurrent() + { + MakeContextCurrent(this.GraphicsContext, OffscreenNativeWindow.WindowInfo); + } + + void MakeContextCurrent(IGraphicsContext context, global::OpenTK.Platform.IWindowInfo windowInfo) + { + //TODO - if we're churning through contexts quickly, this will sort of be a memory leak, since they'll be memoized forever in here + //maybe make it a weakptr or something + + //dont do anything if we're already current + IGraphicsContext currentForThread = null; + if (ThreadsForContexts.TryGetValue(Thread.CurrentThread, out currentForThread)) + if (currentForThread == context) + return; + + NativeWindowsForContexts[context] = windowInfo; + context.MakeCurrent(windowInfo); + ThreadsForContexts[Thread.CurrentThread] = context; + } + + Dictionary ThreadsForContexts = new Dictionary(); + Dictionary NativeWindowsForContexts = new Dictionary(); + + void CreateRenderStates() + { + _rsBlendNone = new MyBlendState(false, BlendingFactor.One, BlendEquationMode.FuncAdd, BlendingFactor.Zero, BlendingFactor.One, BlendEquationMode.FuncAdd, BlendingFactor.Zero); + _rsBlendNormal = new MyBlendState(true, + BlendingFactor.SrcAlpha, BlendEquationMode.FuncAdd, BlendingFactor.OneMinusSrcAlpha, + BlendingFactor.One, BlendEquationMode.FuncAdd, BlendingFactor.Zero); + } + + MyBlendState _rsBlendNone, _rsBlendNormal; + + } //class IGL_TK + +} diff --git a/Bizware/BizHawk.Bizware.BizwareGL/Art.cs b/Bizware/BizHawk.Bizware.BizwareGL/Art.cs new file mode 100644 index 0000000000..5992872e23 --- /dev/null +++ b/Bizware/BizHawk.Bizware.BizwareGL/Art.cs @@ -0,0 +1,24 @@ +namespace BizHawk.Bizware.BizwareGL +{ + /// + /// Represents a piece of 2d art. Not as versatile as a texture.. could have come from an atlas. So it comes with a boatload of constraints + /// + public class Art + { + internal Art(ArtManager owner) + { + Owner = owner; + } + + public ArtManager Owner { get; private set; } + public Texture2d BaseTexture { get; internal set; } + + public float Width, Height; + public float u0, v0, u1, v1; + + internal void Initialize() + { + //TBD + } + } +} \ No newline at end of file diff --git a/Bizware/BizHawk.Bizware.BizwareGL/ArtManager.cs b/Bizware/BizHawk.Bizware.BizwareGL/ArtManager.cs new file mode 100644 index 0000000000..0f5e32f5be --- /dev/null +++ b/Bizware/BizHawk.Bizware.BizwareGL/ArtManager.cs @@ -0,0 +1,165 @@ +using System; +using System.IO; +using System.Collections.Generic; + +namespace BizHawk.Bizware.BizwareGL +{ + /// + /// Load resources through here, and they can be grouped together, for purposes of batching and whatnot. + /// You can't use any of the returned Art resources until calling Close on the ArtManager + /// + public class ArtManager : IDisposable + { + public ArtManager(IGL owner) + { + Owner = owner; + Open(); + } + + public void Dispose() + { + //todo + } + + /// + /// Reopens this instance for further resource loading. Fails if it has been closed forever. + /// + public void Open() + { + AssertIsOpen(false); + if (IsClosedForever) throw new InvalidOperationException("ArtManager instance has been closed forever!"); + IsOpened = true; + } + + /// + /// Loads the given stream as an Art instance + /// + public Art LoadArt(Stream stream) + { + return LoadArtInternal(new BitmapBuffer(stream, new BitmapLoadOptions())); + } + + /// + /// Loads the given path as an Art instance. + /// + public Art LoadArt(string path) + { + using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) + return LoadArtInternal(new BitmapBuffer(path, new BitmapLoadOptions())); + } + + Art LoadArtInternal(BitmapBuffer tex) + { + AssertIsOpen(true); + + Art a = new Art(this); + ArtLooseTextureAssociation[a] = tex; + ManagedArts.Add(a); + + return a; + } + + /// + /// Closes this instance for for further resource loading. Will result in a texture atlasing operation. + /// If the close operation is forever, then internal backup copies of resources will be freed, but it can never be reopened. + /// This function may take some time to run, as it is + /// + public unsafe void Close(bool forever = true) + { + AssertIsOpen(true); + IsOpened = false; + IsClosedForever = forever; + + //first, cleanup old stuff + foreach (var tex in ManagedTextures) + tex.Dispose(); + ManagedTextures.Clear(); + + //prepare input for atlas process and perform atlas + //add 2 extra pixels for padding on all sides + List atlasItems = new List(); + foreach (var kvp in ArtLooseTextureAssociation) + atlasItems.Add(new TexAtlas.RectItem(kvp.Value.Width+2, kvp.Value.Height+2, kvp)); + var results = TexAtlas.PackAtlas(atlasItems); + + //this isnt supported yet: + if (results.Atlases.Count > 1) + throw new InvalidOperationException("Art files too big for atlas"); + + //prepare the output buffer + BitmapBuffer bmpResult = new BitmapBuffer(results.Atlases[0].Size); + + //for each item, copy it into the output buffer and set the tex parameters on them + for (int i = 0; i < atlasItems.Count; i++) + { + var item = results.Atlases[0].Items[i]; + var artAndBitmap = (KeyValuePair)item.Item; + var art = artAndBitmap.Key; + var bitmap = artAndBitmap.Value; + + int w = bitmap.Width; + int h = bitmap.Height; + int dx = item.X + 1; + int dy = item.Y + 1; + for (int y = 0; y < h; y++) + for (int x = 0; x < w; x++) + { + int pixel = bitmap.GetPixel(x, y); + bmpResult.SetPixel(x+dx,y+dy,pixel); + } + + var myDestBitmap = bmpResult; + float myDestWidth = (float)myDestBitmap.Width; + float myDestHeight = (float)myDestBitmap.Height; + + art.u0 = dx / myDestWidth; + art.v0 = dy / myDestHeight; + art.u1 = (dx + w) / myDestWidth; + art.v1 = (dy + h) / myDestHeight; + art.Width = w; + art.Height = h; + } + + //if we're closed forever, then forget all the original bitmaps + if (forever) + { + foreach (var kvp in ArtLooseTextureAssociation) + kvp.Value.Dispose(); + ArtLooseTextureAssociation.Clear(); + } + + //create a physical texture + var texture = Owner.LoadTexture(bmpResult); + ManagedTextures.Add(texture); + + //oops, we couldn't do this earlier. + foreach (var art in ManagedArts) + art.BaseTexture = texture; + } + + /// + /// Throws an exception if the instance is not open + /// + private void AssertIsOpen(bool state) { if (IsOpened != state) throw new InvalidOperationException("ArtManager instance is not open!"); } + + public IGL Owner { get; private set; } + + public bool IsOpened { get; private set; } + public bool IsClosedForever { get; private set; } + + /// + /// This is used to remember the original bitmap sources for art files. Once the ArtManager is closed forever, this will be purged + /// + Dictionary ArtLooseTextureAssociation = new Dictionary(); + + /// + /// Physical texture resources, which exist after this ArtManager has been closed + /// + List ManagedTextures = new List(); + + /// + /// All the Arts managed by this instance + /// + List ManagedArts = new List(); + } +} \ No newline at end of file diff --git a/Bizware/BizHawk.Bizware.BizwareGL/BitmapBuffer.cs b/Bizware/BizHawk.Bizware.BizwareGL/BitmapBuffer.cs new file mode 100644 index 0000000000..6588ea8f56 --- /dev/null +++ b/Bizware/BizHawk.Bizware.BizwareGL/BitmapBuffer.cs @@ -0,0 +1,444 @@ +//TODO - introduce Trim for ArtManager +//TODO - add a small buffer reuse manager.. small images can be stored in larger buffers which we happen to have held. use a timer to wait to free it until some time has passed + + +using System; +using System.Threading; +using System.Threading.Tasks; +using System.Diagnostics; +using System.Text.RegularExpressions; +using System.Runtime.InteropServices; +using sd = System.Drawing; +using System.Drawing.Imaging; +using System.Linq; +using System.Drawing; +using System.IO; +using System.Collections.Generic; +using System.Text; + +namespace BizHawk.Bizware.BizwareGL +{ + /// + /// a software-based bitmap, way easier (and faster) to use than .net's built-in bitmap. + /// Only supports a fixed rgba format + /// Even though this is IDisposable, you dont have to worry about disposing it normally (that's only for the Bitmap-mimicking) + /// But you know you can't resist. + /// + public unsafe class BitmapBuffer : IDisposable + { + public int Width, Height; + public int[] Pixels; + + sd.Bitmap WrappedBitmap; + GCHandle CurrLockHandle; + BitmapData CurrLock; + public BitmapData LockBits() //TODO - add read/write semantic, for wraps + { + if(CurrLock != null) + throw new InvalidOperationException("BitmapBuffer can only be locked once!"); + + if (WrappedBitmap != null) + { + CurrLock = WrappedBitmap.LockBits(new sd.Rectangle(0, 0, Width, Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); + return CurrLock; + } + + CurrLockHandle = GCHandle.Alloc(Pixels, GCHandleType.Pinned); + CurrLock = new BitmapData(); + CurrLock.Height = Height; + CurrLock.Width = Width; + CurrLock.Stride = Width * 4; + CurrLock.Scan0 = CurrLockHandle.AddrOfPinnedObject(); + + return CurrLock; + } + + public void UnlockBits(BitmapData bmpd) + { + Debug.Assert(CurrLock == bmpd); + + if (WrappedBitmap != null) + { + WrappedBitmap.UnlockBits(CurrLock); + CurrLock = null; + return; + } + + CurrLockHandle.Free(); + CurrLock = null; + } + + public void Dispose() + { + if (CurrLock == null) return; + UnlockBits(CurrLock); + } + + public int GetPixel(int x, int y) { return Pixels[Width * y + x]; } + public void SetPixel(int x, int y, int value) { Pixels[Width * y + x] = value; } + public Color GetPixelAsColor(int x, int y) + { + int c = Pixels[Width * y + x]; + return Color.FromArgb(c); + } + + /// + /// transforms tcol to 0,0,0,0 + /// + public void Alphafy(int tcol) + { + for (int y = 0, idx = 0; y < Height; y++) + for (int x = 0; x < Width; x++, idx++) + { + if (Pixels[idx] == tcol) + Pixels[idx] = 0; + } + } + + /// + /// copies this bitmap and trims out transparent pixels, returning the offset to the topleft pixel + /// + public BitmapBuffer Trim() + { + int x, y; + return Trim(out x, out y); + } + + /// + /// copies this bitmap and trims out transparent pixels, returning the offset to the topleft pixel + /// + public BitmapBuffer Trim(out int xofs, out int yofs) + { + int minx = int.MaxValue; + int maxx = int.MinValue; + int miny = int.MaxValue; + int maxy = int.MinValue; + for (int y = 0; y < Height; y++) + for (int x = 0; x < Width; x++) + { + int pixel = GetPixel(x, y); + int a = (pixel >> 24) & 0xFF; + if (a != 0) + { + minx = Math.Min(minx, x); + maxx = Math.Max(maxx, x); + miny = Math.Min(miny, y); + maxy = Math.Max(maxy, y); + } + } + + if (minx == int.MaxValue || maxx == int.MinValue || miny == int.MaxValue || minx == int.MinValue) + { + xofs = yofs = 0; + return new BitmapBuffer(0, 0); + } + + int w = maxx - minx + 1; + int h = maxy - miny + 1; + BitmapBuffer bbRet = new BitmapBuffer(w, h); + for (int y = 0; y < h; y++) + for (int x = 0; x < w; x++) + { + bbRet.SetPixel(x, y, GetPixel(x + minx, y + miny)); + } + + xofs = minx; + yofs = miny; + return bbRet; + } + + /// + /// increases dimensions of this bitmap to the next higher power of 2 + /// + public void Pad() + { + int widthRound = nexthigher(Width); + int heightRound = nexthigher(Height); + if (widthRound == Width && heightRound == Height) return; + int[] NewPixels = new int[heightRound * widthRound]; + + for (int y = 0, sptr = 0, dptr = 0; y < Height; y++) + { + for (int x = 0; x < Width; x++) + NewPixels[dptr++] = Pixels[sptr++]; + dptr += (widthRound - Width); + } + + Pixels = NewPixels; + Width = widthRound; + Height = heightRound; + } + + /// + /// Creates a BitmapBuffer image from the specified filename + /// + public BitmapBuffer(string fname, BitmapLoadOptions options) + { + using (var fs = new FileStream(fname, FileMode.Open, FileAccess.Read, FileShare.Read)) + LoadInternal(fs, null, options); + } + + /// + /// loads an image from the specified stream + /// + public BitmapBuffer(Stream stream, BitmapLoadOptions options) + { + LoadInternal(stream, null, options); + } + + /// + /// Initializes the BitmapBuffer from a System.Drawing.Bitmap + /// + public BitmapBuffer(sd.Bitmap bitmap, BitmapLoadOptions options) + { + if (options.AllowWrap && bitmap.PixelFormat == PixelFormat.Format32bppArgb) + { + Width = bitmap.Width; + Height = bitmap.Height; + WrappedBitmap = bitmap; + } + else LoadInternal(null, bitmap, options); + } + + void LoadInternal(Stream stream, sd.Bitmap bitmap, BitmapLoadOptions options) + { + bool cleanup = options.CleanupAlpha0; + bool needsPad = true; + + var colorKey24bpp = options.ColorKey24bpp; + using (Bitmap loadedBmp = bitmap == null ? new Bitmap(stream) : null) //sneaky! + { + Bitmap bmp = loadedBmp; + if (bmp == null) + bmp = bitmap; + + //if we have a 24bpp image and a colorkey callback, the callback can choose a colorkey color and we'll use that + if (bmp.PixelFormat == PixelFormat.Format24bppRgb && colorKey24bpp != null) + { + int colorKey = colorKey24bpp(bmp); + int w = bmp.Width; + int h = bmp.Height; + InitSize(w, h); + BitmapData bmpdata = bmp.LockBits(new sd.Rectangle(0, 0, w, h), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); + Color[] palette = bmp.Palette.Entries; + int* ptr = (int*)bmpdata.Scan0.ToPointer(); + int stride = bmpdata.Stride; + fixed (int* pPtr = &Pixels[0]) + { + for (int idx = 0, y = 0; y < h; y++) + for (int x = 0; x < w; x++) + { + int srcPixel = ptr[idx]; + if (srcPixel == colorKey) + srcPixel = 0; + pPtr[idx++] = srcPixel; + } + } + + bmp.UnlockBits(bmpdata); + } + if (bmp.PixelFormat == PixelFormat.Format8bppIndexed || bmp.PixelFormat == PixelFormat.Format4bppIndexed) + { + int w = bmp.Width; + int h = bmp.Height; + InitSize(w, h); + BitmapData bmpdata = bmp.LockBits(new sd.Rectangle(0, 0, w, h), ImageLockMode.ReadOnly, PixelFormat.Format8bppIndexed); + Color[] palette = bmp.Palette.Entries; + byte* ptr = (byte*)bmpdata.Scan0.ToPointer(); + int stride = bmpdata.Stride; + fixed (int* pPtr = &Pixels[0]) + { + for (int idx = 0, y = 0; y < h; y++) + for (int x = 0; x < w; x++) + { + int srcPixel = ptr[idx]; + if (srcPixel != 0) + { + int color = palette[srcPixel].ToArgb(); + + //make transparent pixels turn into black to avoid filtering issues and other annoying issues with stray junk in transparent pixels. + //(yes, we can have palette entries with transparency in them (PNGs support this, annoyingly)) + if (cleanup) + { + if ((color & 0xFF000000) == 0) color = 0; + pPtr[idx] = color; + } + } + idx++; + } + } + + bmp.UnlockBits(bmpdata); + } + else + { + //dump the supplied bitmap into our pixels array + int width = bmp.Width; + int height = bmp.Height; + InitSize(width, height); + BitmapData bmpdata = bmp.LockBits(new sd.Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); + int* ptr = (int*)bmpdata.Scan0.ToInt32(); + int stride = bmpdata.Stride / 4; + LoadFrom(width, stride, height, (byte*)ptr, options); + bmp.UnlockBits(bmpdata); + needsPad = false; + } + } + + if (needsPad && options.Pad) + Pad(); + } + + + /// + /// Loads the BitmapBuffer from a source buffer, which is expected to be the right pixel format + /// + public unsafe void LoadFrom(int width, int stride, int height, byte* data, BitmapLoadOptions options) + { + bool cleanup = options.CleanupAlpha0; + Width = width; + Height = height; + Pixels = new int[width * height]; + fixed (int* pPtr = &Pixels[0]) + { + for (int idx = 0, y = 0; y < Height; y++) + for (int x = 0; x < Width; x++) + { + int src = y * stride + x; + int srcVal = ((int*)data)[src]; + + //make transparent pixels turn into black to avoid filtering issues and other annoying issues with stray junk in transparent pixels + if (cleanup) + { + if ((srcVal & 0xFF000000) == 0) srcVal = 0; + pPtr[idx++] = srcVal; + } + } + } + + if (options.Pad) + Pad(); + } + + /// + /// premultiplies a color + /// + public static int PremultiplyColor(int srcVal) + { + int b = (srcVal >> 0) & 0xFF; + int g = (srcVal >> 8) & 0xFF; + int r = (srcVal >> 16) & 0xFF; + int a = (srcVal >> 24) & 0xFF; + r = (r * a) >> 8; + g = (g * a) >> 8; + b = (b * a) >> 8; + srcVal = b | (g << 8) | (r << 16) | (a << 24); + return srcVal; + } + + /// + /// initializes an empty BitmapBuffer, cleared to all 0 + /// + public BitmapBuffer(int width, int height) + { + InitSize(width, height); + } + + public BitmapBuffer() { } + + /// + /// clears this instance to (0,0,0,0) -- without allocating a new array (to avoid GC churn) + /// + public unsafe void ClearWithoutAlloc() + { + //http://techmikael.blogspot.com/2009/12/filling-array-with-default-value.html + //this guy says its faster + + int size = Width * Height; + byte fillValue = 0; + ulong fillValueLong = 0; + + fixed (int* ptr = &Pixels[0]) + { + ulong* dest = (ulong*)ptr; + int length = size; + while (length >= 8) + { + *dest = fillValueLong; + dest++; + length -= 8; + } + byte* bDest = (byte*)dest; + for (byte i = 0; i < length; i++) + { + *bDest = fillValue; + bDest++; + } + } + } + + /// + /// just a temporary measure while refactoring emuhawk + /// + public void AcceptIntArray(int[] arr) + { + //should these be copied? + Pixels = arr; + } + + /// + /// initializes an empty BitmapBuffer, cleared to all 0 + /// + public BitmapBuffer(Size size) + { + InitSize(size.Width, size.Height); + } + + void InitSize(int width, int height) + { + Pixels = new int[width * height]; + Width = width; + Height = height; + } + + /// + /// returns the next higher power of 2 than the provided value, for rounding up POW2 textures. + /// + int nexthigher(int k) + { + k--; + for (int i = 1; i < 32; i <<= 1) + k = k | k >> i; + int candidate = k + 1; + return candidate; + } + + + /// + /// Dumps this BitmapBuffer to a System.Drawing.Bitmap + /// + public unsafe Bitmap ToSysdrawingBitmap() + { + Bitmap bmp = new Bitmap(Width, Height, PixelFormat.Format32bppArgb); + var bmpdata = bmp.LockBits(new sd.Rectangle(0, 0, Width, Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb); + + int* ptr = (int*)bmpdata.Scan0.ToPointer(); + int stride = bmpdata.Stride; + fixed (int* pPtr = &Pixels[0]) + { + for (int idx = 0, y = 0; y < Height; y++) + for (int x = 0; x < Width; x++) + { + int srcPixel = pPtr[idx]; + ptr[idx] = srcPixel; + idx++; + } + } + + bmp.UnlockBits(bmpdata); + return bmp; + } + + } + +} \ No newline at end of file diff --git a/Bizware/BizHawk.Bizware.BizwareGL/BitmapLoadOptions.cs b/Bizware/BizHawk.Bizware.BizwareGL/BitmapLoadOptions.cs new file mode 100644 index 0000000000..f0c2712ed8 --- /dev/null +++ b/Bizware/BizHawk.Bizware.BizwareGL/BitmapLoadOptions.cs @@ -0,0 +1,42 @@ +using System; +using System.Drawing; +using System.Text; + +namespace BizHawk.Bizware.BizwareGL +{ + public class BitmapLoadOptions + { + /// + /// A callback to be issued when a 24bpp image is detected, which will allow you to return a colorkey + /// + public Func ColorKey24bpp; + + /// + /// Specifies whether palette entry 0 (if there is a palette) shall represent transparent (Alpha=0) + /// + public bool TransparentPalette0 = true; + + /// + /// Specifies whether (r,g,b,0) pixels shall be turned into (0,0,0,0). + /// This is useful for cleaning up junk which you might not know you had littering purely transparent areas, which can mess up a lot of stuff during rendering. + /// + public bool CleanupAlpha0 = true; + + /// + /// Applies the Premultiply post-process (not supported yet; and anyway it could be done as it loads for a little speedup, in many cases) + /// + public bool Premultiply = false; + + /// + /// Applies Pad() post-process + /// + public bool Pad = false; + + /// + /// Allows the BitmapBuffer to wrap a System.Drawing.Bitmap, if one is provided for loading. + /// This System.Drawing.Bitmap must be 32bpp and these other options may be valid (since this approach is designed for quickly getting things into textures) + /// Ownership of the bitmap remains with the user. + /// + public bool AllowWrap = true; + } +} \ No newline at end of file diff --git a/Bizware/BizHawk.Bizware.BizwareGL/BizHawk.Bizware.BizwareGL.csproj b/Bizware/BizHawk.Bizware.BizwareGL/BizHawk.Bizware.BizwareGL.csproj new file mode 100644 index 0000000000..cb2da100f7 --- /dev/null +++ b/Bizware/BizHawk.Bizware.BizwareGL/BizHawk.Bizware.BizwareGL.csproj @@ -0,0 +1,104 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {9F84A0B2-861E-4EF4-B89B-5E2A3F38A465} + Library + Properties + BizHawk.Bizware.BizwareGL + BizHawk.Bizware.BizwareGL + v4.0 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + x86 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Code + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Bizware/BizHawk.Bizware.BizwareGL/Borrowed/BitmapFontParser/BitmapFont.cs b/Bizware/BizHawk.Bizware.BizwareGL/Borrowed/BitmapFontParser/BitmapFont.cs new file mode 100644 index 0000000000..1adde4cdec --- /dev/null +++ b/Bizware/BizHawk.Bizware.BizwareGL/Borrowed/BitmapFontParser/BitmapFont.cs @@ -0,0 +1,184 @@ +//public domain assumed from cyotek.com + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Drawing; + +namespace Cyotek.Drawing.BitmapFont +{ + public class BitmapFont : IEnumerable + { + #region  Public Member Declarations + + public const int NoMaxWidth = -1; + + #endregion  Public Member Declarations + + #region  Public Constructors + + public BitmapFont() + { } + + #endregion  Public Constructors + + #region  Public Methods + + public IEnumerator GetEnumerator() + { + foreach (KeyValuePair pair in this.Characters) + yield return pair.Value; + } + + public int GetKerning(char previous, char current) + { + Kerning key; + int result; + + key = new Kerning(previous, current, 0); + if (!this.Kernings.TryGetValue(key, out result)) + result = 0; + + return result; + } + + public Size MeasureFont(string text) + { + return this.MeasureFont(text, BitmapFont.NoMaxWidth); + } + + public Size MeasureFont(string text, double maxWidth) + { + char previousCharacter; + string normalizedText; + int currentLineWidth; + int currentLineHeight; + int blockWidth; + int blockHeight; + List lineHeights; + + previousCharacter = ' '; + normalizedText = this.NormalizeLineBreaks(text); + currentLineWidth = 0; + currentLineHeight = this.LineHeight; + blockWidth = 0; + blockHeight = 0; + lineHeights = new List(); + + foreach (char character in normalizedText) + { + switch (character) + { + case '\n': + lineHeights.Add(currentLineHeight); + blockWidth = Math.Max(blockWidth, currentLineWidth); + currentLineWidth = 0; + currentLineHeight = this.LineHeight; + break; + default: + Character data; + int width; + + data = this[character]; + width = data.XAdvance + this.GetKerning(previousCharacter, character); + + if (maxWidth != BitmapFont.NoMaxWidth && currentLineWidth + width >= maxWidth) + { + lineHeights.Add(currentLineHeight); + blockWidth = Math.Max(blockWidth, currentLineWidth); + currentLineWidth = 0; + currentLineHeight = this.LineHeight; + } + + currentLineWidth += width; + currentLineHeight = Math.Max(currentLineHeight, data.Bounds.Height + data.Offset.Y); + previousCharacter = character; + break; + } + } + + // finish off the current line if required + if (currentLineHeight != 0) + lineHeights.Add(currentLineHeight); + + // reduce any lines other than the last back to the base + for (int i = 0; i < lineHeights.Count - 1; i++) + lineHeights[i] = this.LineHeight; + + // calculate the final block height + foreach (int lineHeight in lineHeights) + blockHeight += lineHeight; + + return new Size(Math.Max(currentLineWidth, blockWidth), blockHeight); + } + + public string NormalizeLineBreaks(string s) + { + return s.Replace("\r\n", "\n").Replace("\r", "\n"); + } + + #endregion  Public Methods + + #region  Public Properties + + public int AlphaChannel { get; set; } + + public int BaseHeight { get; set; } + + public int BlueChannel { get; set; } + + public bool Bold { get; set; } + + public IDictionary Characters { get; set; } + + public string Charset { get; set; } + + public string FamilyName { get; set; } + + public int FontSize { get; set; } + + public int GreenChannel { get; set; } + + public bool Italic { get; set; } + + public IDictionary Kernings { get; set; } + + public int LineHeight { get; set; } + + public int OutlineSize { get; set; } + + public bool Packed { get; set; } + + public Padding Padding { get; set; } + + public Page[] Pages { get; set; } + + public int RedChannel { get; set; } + + public bool Smoothed { get; set; } + + public Point Spacing { get; set; } + + public int StretchedHeight { get; set; } + + public int SuperSampling { get; set; } + + public Size TextureSize { get; set; } + + public Character this[char character] + { get { return this.Characters[character]; } } + + public bool Unicode { get; set; } + + #endregion  Public Properties + + #region  Private Methods + + IEnumerator IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + + #endregion  Private Methods + } +} diff --git a/Bizware/BizHawk.Bizware.BizwareGL/Borrowed/BitmapFontParser/BitmapFontLoader.cs b/Bizware/BizHawk.Bizware.BizwareGL/Borrowed/BitmapFontParser/BitmapFontLoader.cs new file mode 100644 index 0000000000..28e33ce7b2 --- /dev/null +++ b/Bizware/BizHawk.Bizware.BizwareGL/Borrowed/BitmapFontParser/BitmapFontLoader.cs @@ -0,0 +1,457 @@ +//public domain assumed from cyotek.com + +using System; +using System.Reflection; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Xml; + +namespace Cyotek.Drawing.BitmapFont +{ + // Parsing class for bitmap fonts generated by AngelCode BMFont + // http://www.angelcode.com/products/bmfont/ + + public static class BitmapFontLoader + { + #region  Public Class Methods + + ///// + ///// Loads a bitmap font from a file, attempting to auto detect the file type + ///// + ///// Name of the file to load. + ///// + //public static BitmapFont LoadFontFromFile(string fileName) + //{ + // BitmapFont result; + + // if (string.IsNullOrEmpty(fileName)) + // throw new ArgumentNullException("fileName", "File name not specified"); + // else if (!File.Exists(fileName)) + // throw new FileNotFoundException(string.Format("Cannot find file '{0}'", fileName), fileName); + + // using (FileStream file = File.OpenRead(fileName)) + // { + // using (TextReader reader = new StreamReader(file)) + // { + // string line; + + // line = reader.ReadLine(); + + // if (line.StartsWith("info ")) + // result = BitmapFontLoader.LoadFontFromTextFile(fileName); + // else if (line.StartsWith(" + /// Loads a bitmap font from a text file. + /// + /// Name of the file to load. + /// + public static BitmapFont LoadFontFromTextFile(string fileName) + { + BitmapFont font; + IDictionary pageData; + IDictionary kerningDictionary; + IDictionary charDictionary; + string resourcePath; + string[] lines; + + if (string.IsNullOrEmpty(fileName)) + throw new ArgumentNullException("fileName", "File name not specified"); + else if (!File.Exists(fileName)) + throw new FileNotFoundException(string.Format("Cannot find file '{0}'", fileName), fileName); + + pageData = new SortedDictionary(); + kerningDictionary = new Dictionary(); + charDictionary = new Dictionary(); + font = new BitmapFont(); + + resourcePath = Path.GetDirectoryName(fileName); + lines = File.ReadAllLines(fileName); + + foreach (string line in lines) + { + string[] parts; + + parts = BitmapFontLoader.Split(line, ' '); + + if (parts.Length != 0) + { + switch (parts[0]) + { + case "info": + font.FamilyName = BitmapFontLoader.GetNamedString(parts, "face"); + font.FontSize = BitmapFontLoader.GetNamedInt(parts, "size"); + font.Bold = BitmapFontLoader.GetNamedBool(parts, "bold"); + font.Italic = BitmapFontLoader.GetNamedBool(parts, "italic"); + font.Charset = BitmapFontLoader.GetNamedString(parts, "charset"); + font.Unicode = BitmapFontLoader.GetNamedBool(parts, "unicode"); + font.StretchedHeight = BitmapFontLoader.GetNamedInt(parts, "stretchH"); + font.Smoothed = BitmapFontLoader.GetNamedBool(parts, "smooth"); + font.SuperSampling = BitmapFontLoader.GetNamedInt(parts, "aa"); + font.Padding = BitmapFontLoader.ParsePadding(BitmapFontLoader.GetNamedString(parts, "padding")); + font.Spacing = BitmapFontLoader.ParsePoint(BitmapFontLoader.GetNamedString(parts, "spacing")); + font.OutlineSize = BitmapFontLoader.GetNamedInt(parts, "outline"); + break; + case "common": + font.LineHeight = BitmapFontLoader.GetNamedInt(parts, "lineHeight"); + font.BaseHeight = BitmapFontLoader.GetNamedInt(parts, "base"); + font.TextureSize = new Size + ( + BitmapFontLoader.GetNamedInt(parts, "scaleW"), + BitmapFontLoader.GetNamedInt(parts, "scaleH") + ); + font.Packed = BitmapFontLoader.GetNamedBool(parts, "packed"); + font.AlphaChannel = BitmapFontLoader.GetNamedInt(parts, "alphaChnl"); + font.RedChannel = BitmapFontLoader.GetNamedInt(parts, "redChnl"); + font.GreenChannel = BitmapFontLoader.GetNamedInt(parts, "greenChnl"); + font.BlueChannel = BitmapFontLoader.GetNamedInt(parts, "blueChnl"); + break; + case "page": + int id; + string name; + string textureId; + + id = BitmapFontLoader.GetNamedInt(parts, "id"); + name = BitmapFontLoader.GetNamedString(parts, "file"); + textureId = Path.GetFileNameWithoutExtension(name); + + pageData.Add(id, new Page(id, Path.Combine(resourcePath, name))); + break; + case "char": + Character charData; + + charData = new Character + { + Char = (char)BitmapFontLoader.GetNamedInt(parts, "id"), + Bounds = new Rectangle + ( + BitmapFontLoader.GetNamedInt(parts, "x"), + BitmapFontLoader.GetNamedInt(parts, "y"), + BitmapFontLoader.GetNamedInt(parts, "width"), + BitmapFontLoader.GetNamedInt(parts, "height") + ), + Offset = new Point + ( + BitmapFontLoader.GetNamedInt(parts, "xoffset"), + BitmapFontLoader.GetNamedInt(parts, "yoffset") + ), + XAdvance = BitmapFontLoader.GetNamedInt(parts, "xadvance"), + TexturePage = BitmapFontLoader.GetNamedInt(parts, "page"), + Channel = BitmapFontLoader.GetNamedInt(parts, "chnl") + }; + charDictionary.Add(charData.Char, charData); + break; + case "kerning": + Kerning key; + + key = new Kerning((char)BitmapFontLoader.GetNamedInt(parts, "first"), (char)BitmapFontLoader.GetNamedInt(parts, "second"), GetNamedInt(parts, "amount")); + + if (!kerningDictionary.ContainsKey(key)) + kerningDictionary.Add(key, key.Amount); + break; + } + } + } + + font.Pages = BitmapFontLoader.ToArray(pageData.Values); + font.Characters = charDictionary; + font.Kernings = kerningDictionary; + + return font; + } + + /// + /// Loads a bitmap font from an XML file. + /// + /// Name of the file to load. + /// + public static BitmapFont LoadFontFromXmlFile(Stream stream) + { + XmlDocument document; + BitmapFont font; + IDictionary pageData; + IDictionary kerningDictionary; + IDictionary charDictionary; + XmlNode root; + XmlNode properties; + + document = new XmlDocument(); + pageData = new SortedDictionary(); + kerningDictionary = new Dictionary(); + charDictionary = new Dictionary(); + font = new BitmapFont(); + + document.Load(stream); + root = document.DocumentElement; + + // load the basic attributes + properties = root.SelectSingleNode("info"); + font.FamilyName = properties.Attributes["face"].Value; + font.FontSize = Convert.ToInt32(properties.Attributes["size"].Value); + font.Bold = Convert.ToInt32(properties.Attributes["bold"].Value) != 0; + font.Italic = Convert.ToInt32(properties.Attributes["italic"].Value) != 0; + font.Unicode = Convert.ToInt32(properties.Attributes["unicode"].Value) != 0; + font.StretchedHeight = Convert.ToInt32(properties.Attributes["stretchH"].Value); + font.Charset = properties.Attributes["charset"].Value; + font.Smoothed = Convert.ToInt32(properties.Attributes["smooth"].Value) != 0; + font.SuperSampling = Convert.ToInt32(properties.Attributes["aa"].Value); + font.Padding = BitmapFontLoader.ParsePadding(properties.Attributes["padding"].Value); + font.Spacing = BitmapFontLoader.ParsePoint(properties.Attributes["spacing"].Value); + font.OutlineSize = Convert.ToInt32(properties.Attributes["outline"].Value); + + // common attributes + properties = root.SelectSingleNode("common"); + font.BaseHeight = Convert.ToInt32(properties.Attributes["lineHeight"].Value); + font.LineHeight = Convert.ToInt32(properties.Attributes["base"].Value); + font.TextureSize = new Size + ( + Convert.ToInt32(properties.Attributes["scaleW"].Value), + Convert.ToInt32(properties.Attributes["scaleH"].Value) + ); + font.Packed = Convert.ToInt32(properties.Attributes["packed"].Value) != 0; + font.AlphaChannel = Convert.ToInt32(properties.Attributes["alphaChnl"].Value); + font.RedChannel = Convert.ToInt32(properties.Attributes["redChnl"].Value); + font.GreenChannel = Convert.ToInt32(properties.Attributes["greenChnl"].Value); + font.BlueChannel = Convert.ToInt32(properties.Attributes["blueChnl"].Value); + + // load texture information + foreach (XmlNode node in root.SelectNodes("pages/page")) + { + Page page; + + page = new Page(); + page.Id = Convert.ToInt32(node.Attributes["id"].Value); + page.FileName = node.Attributes["file"].Value; + + pageData.Add(page.Id, page); + } + font.Pages = BitmapFontLoader.ToArray(pageData.Values); + + // load character information + foreach (XmlNode node in root.SelectNodes("chars/char")) + { + Character character; + + character = new Character(); + character.Char = (char)Convert.ToInt32(node.Attributes["id"].Value); + character.Bounds = new Rectangle + ( + Convert.ToInt32(node.Attributes["x"].Value), + Convert.ToInt32(node.Attributes["y"].Value), + Convert.ToInt32(node.Attributes["width"].Value), + Convert.ToInt32(node.Attributes["height"].Value) + ); + character.Offset = new Point + ( + Convert.ToInt32(node.Attributes["xoffset"].Value), + Convert.ToInt32(node.Attributes["yoffset"].Value) + ); + character.XAdvance = Convert.ToInt32(node.Attributes["xadvance"].Value); + character.TexturePage = Convert.ToInt32(node.Attributes["page"].Value); + character.Channel = Convert.ToInt32(node.Attributes["chnl"].Value); + + charDictionary.Add(character.Char, character); + } + font.Characters = charDictionary; + + // loading kerning information + foreach (XmlNode node in root.SelectNodes("kernings/kerning")) + { + Kerning key; + + key = new Kerning((char)Convert.ToInt32(node.Attributes["first"].Value), (char)Convert.ToInt32(node.Attributes["second"].Value), Convert.ToInt32(node.Attributes["amount"].Value)); + + if (!kerningDictionary.ContainsKey(key)) + kerningDictionary.Add(key, key.Amount); + } + font.Kernings = kerningDictionary; + + return font; + } + + #endregion  Public Class Methods + + #region  Private Class Methods + + /// + /// Returns a boolean from an array of name/value pairs. + /// + /// The array of parts. + /// The name of the value to return. + /// + private static bool GetNamedBool(string[] parts, string name) + { + return BitmapFontLoader.GetNamedInt(parts, name) != 0; + } + + /// + /// Returns an integer from an array of name/value pairs. + /// + /// The array of parts. + /// The name of the value to return. + /// + private static int GetNamedInt(string[] parts, string name) + { + return Convert.ToInt32(BitmapFontLoader.GetNamedString(parts, name)); + } + + /// + /// Returns a string from an array of name/value pairs. + /// + /// The array of parts. + /// The name of the value to return. + /// + private static string GetNamedString(string[] parts, string name) + { + string result; + + result = string.Empty; + name = name.ToLowerInvariant(); + + foreach (string part in parts) + { + int nameEndIndex; + + nameEndIndex = part.IndexOf("="); + if (nameEndIndex != -1) + { + string namePart; + string valuePart; + + namePart = part.Substring(0, nameEndIndex).ToLowerInvariant(); + valuePart = part.Substring(nameEndIndex + 1); + + if (namePart == name) + { + if (valuePart.StartsWith("\"") && valuePart.EndsWith("\"")) + valuePart = valuePart.Substring(1, valuePart.Length - 2); + + result = valuePart; + break; + } + } + } + + return result; + } + + /// + /// Creates a Padding object from a string representation + /// + /// The string. + /// + private static Padding ParsePadding(string s) + { + string[] parts; + + parts = s.Split(','); + + return new Padding() + { + Left = Convert.ToInt32(parts[3].Trim()), + Top = Convert.ToInt32(parts[0].Trim()), + Right = Convert.ToInt32(parts[1].Trim()), + Bottom = Convert.ToInt32(parts[2].Trim()) + }; + } + + /// + /// Creates a Point object from a string representation + /// + /// The string. + /// + private static Point ParsePoint(string s) + { + string[] parts; + + parts = s.Split(','); + + return new Point() + { + X = Convert.ToInt32(parts[0].Trim()), + Y = Convert.ToInt32(parts[1].Trim()) + }; + } + + /// + /// Splits the specified string using a given delimiter, ignoring any instances of the delimiter as part of a quoted string. + /// + /// The string to split. + /// The delimiter. + /// + private static string[] Split(string s, char delimiter) + { + string[] results; + + if (s.Contains("\"")) + { + List parts; + int partStart; + + partStart = -1; + parts = new List(); + + do + { + int partEnd; + int quoteStart; + int quoteEnd; + bool hasQuotes; + + quoteStart = s.IndexOf("\"", partStart + 1); + quoteEnd = s.IndexOf("\"", quoteStart + 1); + partEnd = s.IndexOf(delimiter, partStart + 1); + + if (partEnd == -1) + partEnd = s.Length; + + hasQuotes = quoteStart != -1 && partEnd > quoteStart && partEnd < quoteEnd; + if (hasQuotes) + partEnd = s.IndexOf(delimiter, quoteEnd + 1); + + parts.Add(s.Substring(partStart + 1, partEnd - partStart - 1)); + + if (hasQuotes) + partStart = partEnd - 1; + + partStart = s.IndexOf(delimiter, partStart + 1); + } while (partStart != -1); + + results = parts.ToArray(); + } + else + results = s.Split(new char[] { delimiter }, StringSplitOptions.RemoveEmptyEntries); + + return results; + } + + /// + /// Converts the given collection into an array + /// + /// Type of the items in the array + /// The values. + /// + private static T[] ToArray(ICollection values) + { + T[] result; + + // avoid a forced .NET 3 dependency just for one call to Linq + + result = new T[values.Count]; + values.CopyTo(result, 0); + + return result; + } + + #endregion  Private Class Methods + } +} diff --git a/Bizware/BizHawk.Bizware.BizwareGL/Borrowed/BitmapFontParser/Character.cs b/Bizware/BizHawk.Bizware.BizwareGL/Borrowed/BitmapFontParser/Character.cs new file mode 100644 index 0000000000..ce412a4bd2 --- /dev/null +++ b/Bizware/BizHawk.Bizware.BizwareGL/Borrowed/BitmapFontParser/Character.cs @@ -0,0 +1,34 @@ +//public domain assumed from cyotek.com + +using System.Drawing; + +namespace Cyotek.Drawing.BitmapFont +{ + public struct Character + { + #region  Public Methods + + public override string ToString() + { + return this.Char.ToString(); + } + + #endregion  Public Methods + + #region  Public Properties + + public int Channel { get; set; } + + public Rectangle Bounds { get; set; } + + public Point Offset { get; set; } + + public char Char { get; set; } + + public int TexturePage { get; set; } + + public int XAdvance { get; set; } + + #endregion  Public Properties + } +} diff --git a/Bizware/BizHawk.Bizware.BizwareGL/Borrowed/BitmapFontParser/Kerning.cs b/Bizware/BizHawk.Bizware.BizwareGL/Borrowed/BitmapFontParser/Kerning.cs new file mode 100644 index 0000000000..c831a67a1c --- /dev/null +++ b/Bizware/BizHawk.Bizware.BizwareGL/Borrowed/BitmapFontParser/Kerning.cs @@ -0,0 +1,38 @@ +//public domain assumed from cyotek.com + +namespace Cyotek.Drawing.BitmapFont +{ + public struct Kerning + { + #region  Public Constructors + + public Kerning(char firstCharacter, char secondCharacter, int amount) + : this() + { + this.FirstCharacter = firstCharacter; + this.SecondCharacter = secondCharacter; + this.Amount = amount; + } + + #endregion  Public Constructors + + #region  Public Methods + + public override string ToString() + { + return string.Format("{0} to {1} = {2}", this.FirstCharacter, this.SecondCharacter, this.Amount); + } + + #endregion  Public Methods + + #region  Public Properties + + public char FirstCharacter { get; set; } + + public char SecondCharacter { get; set; } + + public int Amount { get; set; } + + #endregion  Public Properties + } +} diff --git a/Bizware/BizHawk.Bizware.BizwareGL/Borrowed/BitmapFontParser/Padding.cs b/Bizware/BizHawk.Bizware.BizwareGL/Borrowed/BitmapFontParser/Padding.cs new file mode 100644 index 0000000000..20fab383d1 --- /dev/null +++ b/Bizware/BizHawk.Bizware.BizwareGL/Borrowed/BitmapFontParser/Padding.cs @@ -0,0 +1,41 @@ +//public domain assumed from cyotek.com + +namespace Cyotek.Drawing.BitmapFont +{ + public struct Padding + { + #region  Public Constructors + + public Padding(int left, int top, int right, int bottom) + : this() + { + this.Top = top; + this.Left = left; + this.Right = right; + this.Bottom = bottom; + } + + #endregion  Public Constructors + + #region  Public Methods + + public override string ToString() + { + return string.Format("{0}, {1}, {2}, {3}", this.Left, this.Top, this.Right, this.Bottom); + } + + #endregion  Public Methods + + #region  Public Properties + + public int Top { get; set; } + + public int Left { get; set; } + + public int Right { get; set; } + + public int Bottom { get; set; } + + #endregion  Public Properties + } +} diff --git a/Bizware/BizHawk.Bizware.BizwareGL/Borrowed/BitmapFontParser/Page.cs b/Bizware/BizHawk.Bizware.BizwareGL/Borrowed/BitmapFontParser/Page.cs new file mode 100644 index 0000000000..d18c45a39e --- /dev/null +++ b/Bizware/BizHawk.Bizware.BizwareGL/Borrowed/BitmapFontParser/Page.cs @@ -0,0 +1,37 @@ +//public domain assumed from cyotek.com + +using System.IO; + +namespace Cyotek.Drawing.BitmapFont +{ + public struct Page + { + #region  Public Constructors + + public Page(int id, string fileName) + : this() + { + this.FileName = fileName; + this.Id = id; + } + + #endregion  Public Constructors + + #region  Public Methods + + public override string ToString() + { + return string.Format("{0} ({1})", this.Id, Path.GetFileName(this.FileName)); + } + + #endregion  Public Methods + + #region  Public Properties + + public string FileName { get; set; } + + public int Id { get; set; } + + #endregion  Public Properties + } +} diff --git a/Bizware/BizHawk.Bizware.BizwareGL/Borrowed/readme.txt b/Bizware/BizHawk.Bizware.BizwareGL/Borrowed/readme.txt new file mode 100644 index 0000000000..15126b183e --- /dev/null +++ b/Bizware/BizHawk.Bizware.BizwareGL/Borrowed/readme.txt @@ -0,0 +1,4 @@ +BitmapFontParser +http://cyotek.com/blog/angelcode-bitmap-font-parsing-using-csharp +License not stated. I'm content with calling it public domain. As future work, the code could be replaced easily, so I dont consider this a big deal. It's most trivial code. +It loads fonts created by http://www.angelcode.com/products/bmfont/ \ No newline at end of file diff --git a/Bizware/BizHawk.Bizware.BizwareGL/Enums.cs b/Bizware/BizHawk.Bizware.BizwareGL/Enums.cs new file mode 100644 index 0000000000..d19bab6379 --- /dev/null +++ b/Bizware/BizHawk.Bizware.BizwareGL/Enums.cs @@ -0,0 +1,573 @@ +using System; + +namespace BizHawk.Bizware.BizwareGL +{ + // Summary: + // Used in GL.BlendFunc, GL.BlendFuncSeparate + public enum BlendingFactor + { + // Summary: + // Original was GL_ZERO = 0 + Zero = 0, + // + // Summary: + // Original was GL_ONE = 1 + One = 1, + // + // Summary: + // Original was GL_SRC_COLOR = 0x0300 + SrcColor = 768, + // + // Summary: + // Original was GL_ONE_MINUS_SRC_COLOR = 0x0301 + OneMinusSrcColor = 769, + // + // Summary: + // Original was GL_SRC_ALPHA = 0x0302 + SrcAlpha = 770, + // + // Summary: + // Original was GL_ONE_MINUS_SRC_ALPHA = 0x0303 + OneMinusSrcAlpha = 771, + // + // Summary: + // Original was GL_DST_ALPHA = 0x0304 + DstAlpha = 772, + // + // Summary: + // Original was GL_ONE_MINUS_DST_ALPHA = 0x0305 + OneMinusDstAlpha = 773, + // + // Summary: + // Original was GL_DST_COLOR = 0x0306 + DstColor = 774, + // + // Summary: + // Original was GL_ONE_MINUS_DST_COLOR = 0x0307 + OneMinusDstColor = 775, + // + // Summary: + // Original was GL_SRC_ALPHA_SATURATE = 0x0308 + SrcAlphaSaturate = 776, + // + // Summary: + // Original was GL_CONSTANT_COLOR_EXT = 0x8001 + ConstantColorExt = 32769, + // + // Summary: + // Original was GL_CONSTANT_COLOR = 0x8001 + ConstantColor = 32769, + // + // Summary: + // Original was GL_ONE_MINUS_CONSTANT_COLOR = 0x8002 + OneMinusConstantColor = 32770, + // + // Summary: + // Original was GL_ONE_MINUS_CONSTANT_COLOR_EXT = 0x8002 + OneMinusConstantColorExt = 32770, + // + // Summary: + // Original was GL_CONSTANT_ALPHA = 0x8003 + ConstantAlpha = 32771, + // + // Summary: + // Original was GL_CONSTANT_ALPHA_EXT = 0x8003 + ConstantAlphaExt = 32771, + // + // Summary: + // Original was GL_ONE_MINUS_CONSTANT_ALPHA_EXT = 0x8004 + OneMinusConstantAlphaExt = 32772, + // + // Summary: + // Original was GL_ONE_MINUS_CONSTANT_ALPHA = 0x8004 + OneMinusConstantAlpha = 32772, + // + // Summary: + // Original was GL_SRC1_ALPHA = 0x8589 + Src1Alpha = 34185, + // + // Summary: + // Original was GL_SRC1_COLOR = 0x88F9 + Src1Color = 35065, + // + // Summary: + // Original was GL_ONE_MINUS_SRC1_COLOR = 0x88FA + OneMinusSrc1Color = 35066, + // + // Summary: + // Original was GL_ONE_MINUS_SRC1_ALPHA = 0x88FB + OneMinusSrc1Alpha = 35067, + } + + // Summary: + // Used in GL.Arb.BlendEquation, GL.BlendEquation and 2 other functions + public enum BlendEquationMode + { + // Summary: + // Original was GL_FUNC_ADD = 0x8006 + FuncAdd = 32774, + // + // Summary: + // Original was GL_MIN = 0x8007 + Min = 32775, + // + // Summary: + // Original was GL_MAX = 0x8008 + Max = 32776, + // + // Summary: + // Original was GL_FUNC_SUBTRACT = 0x800A + FuncSubtract = 32778, + // + // Summary: + // Original was GL_FUNC_REVERSE_SUBTRACT = 0x800B + FuncReverseSubtract = 32779, + } + + // Summary: + // Used in GL.BlitFramebuffer, GL.Clear and 1 other function + [Flags] + public enum ClearBufferMask + { + // Summary: + // Original was GL_NONE = 0 + None = 0, + // + // Summary: + // Original was GL_DEPTH_BUFFER_BIT = 0x00000100 + DepthBufferBit = 256, + // + // Summary: + // Original was GL_ACCUM_BUFFER_BIT = 0x00000200 + AccumBufferBit = 512, + // + // Summary: + // Original was GL_STENCIL_BUFFER_BIT = 0x00000400 + StencilBufferBit = 1024, + // + // Summary: + // Original was GL_COLOR_BUFFER_BIT = 0x00004000 + ColorBufferBit = 16384, + // + // Summary: + // Original was GL_COVERAGE_BUFFER_BIT_NV = 0x00008000 + CoverageBufferBitNv = 32768, + } + + // Summary: + // Used in GL.TexParameter, GL.TexParameterI and 5 other functions + public enum TextureParameterName + { + // Summary: + // Original was GL_TEXTURE_BORDER_COLOR = 0x1004 + TextureBorderColor = 4100, + // + // Summary: + // Original was GL_TEXTURE_MAG_FILTER = 0x2800 + TextureMagFilter = 10240, + // + // Summary: + // Original was GL_TEXTURE_MIN_FILTER = 0x2801 + TextureMinFilter = 10241, + // + // Summary: + // Original was GL_TEXTURE_WRAP_S = 0x2802 + TextureWrapS = 10242, + // + // Summary: + // Original was GL_TEXTURE_WRAP_T = 0x2803 + TextureWrapT = 10243, + // + // Summary: + // Original was GL_TEXTURE_PRIORITY = 0x8066 + TexturePriority = 32870, + // + // Summary: + // Original was GL_TEXTURE_PRIORITY_EXT = 0x8066 + TexturePriorityExt = 32870, + // + // Summary: + // Original was GL_TEXTURE_DEPTH = 0x8071 + TextureDepth = 32881, + // + // Summary: + // Original was GL_TEXTURE_WRAP_R_EXT = 0x8072 + TextureWrapRExt = 32882, + // + // Summary: + // Original was GL_TEXTURE_WRAP_R_OES = 0x8072 + TextureWrapROes = 32882, + // + // Summary: + // Original was GL_TEXTURE_WRAP_R = 0x8072 + TextureWrapR = 32882, + // + // Summary: + // Original was GL_DETAIL_TEXTURE_LEVEL_SGIS = 0x809A + DetailTextureLevelSgis = 32922, + // + // Summary: + // Original was GL_DETAIL_TEXTURE_MODE_SGIS = 0x809B + DetailTextureModeSgis = 32923, + // + // Summary: + // Original was GL_TEXTURE_COMPARE_FAIL_VALUE = 0x80BF + TextureCompareFailValue = 32959, + // + // Summary: + // Original was GL_SHADOW_AMBIENT_SGIX = 0x80BF + ShadowAmbientSgix = 32959, + // + // Summary: + // Original was GL_DUAL_TEXTURE_SELECT_SGIS = 0x8124 + DualTextureSelectSgis = 33060, + // + // Summary: + // Original was GL_QUAD_TEXTURE_SELECT_SGIS = 0x8125 + QuadTextureSelectSgis = 33061, + // + // Summary: + // Original was GL_CLAMP_TO_BORDER = 0x812D + ClampToBorder = 33069, + // + // Summary: + // Original was GL_CLAMP_TO_EDGE = 0x812F + ClampToEdge = 33071, + // + // Summary: + // Original was GL_TEXTURE_WRAP_Q_SGIS = 0x8137 + TextureWrapQSgis = 33079, + // + // Summary: + // Original was GL_TEXTURE_MIN_LOD = 0x813A + TextureMinLod = 33082, + // + // Summary: + // Original was GL_TEXTURE_MAX_LOD = 0x813B + TextureMaxLod = 33083, + // + // Summary: + // Original was GL_TEXTURE_BASE_LEVEL = 0x813C + TextureBaseLevel = 33084, + // + // Summary: + // Original was GL_TEXTURE_MAX_LEVEL = 0x813D + TextureMaxLevel = 33085, + // + // Summary: + // Original was GL_TEXTURE_CLIPMAP_CENTER_SGIX = 0x8171 + TextureClipmapCenterSgix = 33137, + // + // Summary: + // Original was GL_TEXTURE_CLIPMAP_FRAME_SGIX = 0x8172 + TextureClipmapFrameSgix = 33138, + // + // Summary: + // Original was GL_TEXTURE_CLIPMAP_OFFSET_SGIX = 0x8173 + TextureClipmapOffsetSgix = 33139, + // + // Summary: + // Original was GL_TEXTURE_CLIPMAP_VIRTUAL_DEPTH_SGIX = 0x8174 + TextureClipmapVirtualDepthSgix = 33140, + // + // Summary: + // Original was GL_TEXTURE_CLIPMAP_LOD_OFFSET_SGIX = 0x8175 + TextureClipmapLodOffsetSgix = 33141, + // + // Summary: + // Original was GL_TEXTURE_CLIPMAP_DEPTH_SGIX = 0x8176 + TextureClipmapDepthSgix = 33142, + // + // Summary: + // Original was GL_POST_TEXTURE_FILTER_BIAS_SGIX = 0x8179 + PostTextureFilterBiasSgix = 33145, + // + // Summary: + // Original was GL_POST_TEXTURE_FILTER_SCALE_SGIX = 0x817A + PostTextureFilterScaleSgix = 33146, + // + // Summary: + // Original was GL_TEXTURE_LOD_BIAS_S_SGIX = 0x818E + TextureLodBiasSSgix = 33166, + // + // Summary: + // Original was GL_TEXTURE_LOD_BIAS_T_SGIX = 0x818F + TextureLodBiasTSgix = 33167, + // + // Summary: + // Original was GL_TEXTURE_LOD_BIAS_R_SGIX = 0x8190 + TextureLodBiasRSgix = 33168, + // + // Summary: + // Original was GL_GENERATE_MIPMAP = 0x8191 + GenerateMipmap = 33169, + // + // Summary: + // Original was GL_GENERATE_MIPMAP_SGIS = 0x8191 + GenerateMipmapSgis = 33169, + // + // Summary: + // Original was GL_TEXTURE_COMPARE_SGIX = 0x819A + TextureCompareSgix = 33178, + // + // Summary: + // Original was GL_TEXTURE_MAX_CLAMP_S_SGIX = 0x8369 + TextureMaxClampSSgix = 33641, + // + // Summary: + // Original was GL_TEXTURE_MAX_CLAMP_T_SGIX = 0x836A + TextureMaxClampTSgix = 33642, + // + // Summary: + // Original was GL_TEXTURE_MAX_CLAMP_R_SGIX = 0x836B + TextureMaxClampRSgix = 33643, + // + // Summary: + // Original was GL_TEXTURE_LOD_BIAS = 0x8501 + TextureLodBias = 34049, + // + // Summary: + // Original was GL_DEPTH_TEXTURE_MODE = 0x884B + DepthTextureMode = 34891, + // + // Summary: + // Original was GL_TEXTURE_COMPARE_MODE = 0x884C + TextureCompareMode = 34892, + // + // Summary: + // Original was GL_TEXTURE_COMPARE_FUNC = 0x884D + TextureCompareFunc = 34893, + // + // Summary: + // Original was GL_TEXTURE_SWIZZLE_R = 0x8E42 + TextureSwizzleR = 36418, + // + // Summary: + // Original was GL_TEXTURE_SWIZZLE_G = 0x8E43 + TextureSwizzleG = 36419, + // + // Summary: + // Original was GL_TEXTURE_SWIZZLE_B = 0x8E44 + TextureSwizzleB = 36420, + // + // Summary: + // Original was GL_TEXTURE_SWIZZLE_A = 0x8E45 + TextureSwizzleA = 36421, + // + // Summary: + // Original was GL_TEXTURE_SWIZZLE_RGBA = 0x8E46 + TextureSwizzleRgba = 36422, + } + + // Summary: + // Not used directly. + public enum TextureMinFilter + { + // Summary: + // Original was GL_NEAREST = 0x2600 + Nearest = 9728, + // + // Summary: + // Original was GL_LINEAR = 0x2601 + Linear = 9729, + // + // Summary: + // Original was GL_NEAREST_MIPMAP_NEAREST = 0x2700 + NearestMipmapNearest = 9984, + // + // Summary: + // Original was GL_LINEAR_MIPMAP_NEAREST = 0x2701 + LinearMipmapNearest = 9985, + // + // Summary: + // Original was GL_NEAREST_MIPMAP_LINEAR = 0x2702 + NearestMipmapLinear = 9986, + // + // Summary: + // Original was GL_LINEAR_MIPMAP_LINEAR = 0x2703 + LinearMipmapLinear = 9987, + // + // Summary: + // Original was GL_FILTER4_SGIS = 0x8146 + Filter4Sgis = 33094, + // + // Summary: + // Original was GL_LINEAR_CLIPMAP_LINEAR_SGIX = 0x8170 + LinearClipmapLinearSgix = 33136, + // + // Summary: + // Original was GL_PIXEL_TEX_GEN_Q_CEILING_SGIX = 0x8184 + PixelTexGenQCeilingSgix = 33156, + // + // Summary: + // Original was GL_PIXEL_TEX_GEN_Q_ROUND_SGIX = 0x8185 + PixelTexGenQRoundSgix = 33157, + // + // Summary: + // Original was GL_PIXEL_TEX_GEN_Q_FLOOR_SGIX = 0x8186 + PixelTexGenQFloorSgix = 33158, + // + // Summary: + // Original was GL_NEAREST_CLIPMAP_NEAREST_SGIX = 0x844D + NearestClipmapNearestSgix = 33869, + // + // Summary: + // Original was GL_NEAREST_CLIPMAP_LINEAR_SGIX = 0x844E + NearestClipmapLinearSgix = 33870, + // + // Summary: + // Original was GL_LINEAR_CLIPMAP_NEAREST_SGIX = 0x844F + LinearClipmapNearestSgix = 33871, + } + + // Summary: + // Not used directly. + public enum TextureMagFilter + { + // Summary: + // Original was GL_NEAREST = 0x2600 + Nearest = 9728, + // + // Summary: + // Original was GL_LINEAR = 0x2601 + Linear = 9729, + // + // Summary: + // Original was GL_LINEAR_DETAIL_SGIS = 0x8097 + LinearDetailSgis = 32919, + // + // Summary: + // Original was GL_LINEAR_DETAIL_ALPHA_SGIS = 0x8098 + LinearDetailAlphaSgis = 32920, + // + // Summary: + // Original was GL_LINEAR_DETAIL_COLOR_SGIS = 0x8099 + LinearDetailColorSgis = 32921, + // + // Summary: + // Original was GL_LINEAR_SHARPEN_SGIS = 0x80AD + LinearSharpenSgis = 32941, + // + // Summary: + // Original was GL_LINEAR_SHARPEN_ALPHA_SGIS = 0x80AE + LinearSharpenAlphaSgis = 32942, + // + // Summary: + // Original was GL_LINEAR_SHARPEN_COLOR_SGIS = 0x80AF + LinearSharpenColorSgis = 32943, + // + // Summary: + // Original was GL_FILTER4_SGIS = 0x8146 + Filter4Sgis = 33094, + // + // Summary: + // Original was GL_PIXEL_TEX_GEN_Q_CEILING_SGIX = 0x8184 + PixelTexGenQCeilingSgix = 33156, + // + // Summary: + // Original was GL_PIXEL_TEX_GEN_Q_ROUND_SGIX = 0x8185 + PixelTexGenQRoundSgix = 33157, + // + // Summary: + // Original was GL_PIXEL_TEX_GEN_Q_FLOOR_SGIX = 0x8186 + PixelTexGenQFloorSgix = 33158, + } + + public enum VertexAttributeType + { + // Summary: + // Original was GL_BYTE = 0x1400 + Byte = 5120, + // + // Summary: + // Original was GL_UNSIGNED_BYTE = 0x1401 + UnsignedByte = 5121, + // + // Summary: + // Original was GL_SHORT = 0x1402 + Short = 5122, + // + // Summary: + // Original was GL_UNSIGNED_SHORT = 0x1403 + UnsignedShort = 5123, + // + // Summary: + // Original was GL_INT = 0x1404 + Int = 5124, + // + // Summary: + // Original was GL_UNSIGNED_INT = 0x1405 + UnsignedInt = 5125, + // + // Summary: + // Original was GL_FLOAT = 0x1406 + Float = 5126, + // + // Summary: + // Original was GL_DOUBLE = 0x140A + Double = 5130, + // + // Summary: + // Original was GL_HALF_FLOAT = 0x140B + HalfFloat = 5131, + // + // Summary: + // Original was GL_FIXED = 0x140C + Fixed = 5132, + // + // Summary: + // Original was GL_UNSIGNED_INT_2_10_10_10_REV = 0x8368 + UnsignedInt2101010Rev = 33640, + // + // Summary: + // Original was GL_INT_2_10_10_10_REV = 0x8D9F + Int2101010Rev = 36255, + } + + // Summary: + // Used in GL.Apple.DrawElementArray, GL.Apple.DrawRangeElementArray and 38 + // other functions + public enum PrimitiveType + { + // Summary: + // Original was GL_POINTS = 0x0000 + Points = 0, + // + // Summary: + // Original was GL_LINES = 0x0001 + Lines = 1, + // + // Summary: + // Original was GL_LINE_LOOP = 0x0002 + LineLoop = 2, + // + // Summary: + // Original was GL_LINE_STRIP = 0x0003 + LineStrip = 3, + // + // Summary: + // Original was GL_TRIANGLES = 0x0004 + Triangles = 4, + // + // Summary: + // Original was GL_TRIANGLE_STRIP = 0x0005 + TriangleStrip = 5, + // + // Summary: + // Original was GL_TRIANGLE_FAN = 0x0006 + TriangleFan = 6, + // + // Summary: + // Original was GL_QUADS = 0x0007 + Quads = 7, + // + // Summary: + // Original was GL_QUAD_STRIP = 0x0008 + QuadStrip = 8, + // + // Summary: + // Original was GL_POLYGON = 0x0009 + Polygon = 9, + } + +} \ No newline at end of file diff --git a/Bizware/BizHawk.Bizware.BizwareGL/GraphicsControl.cs b/Bizware/BizHawk.Bizware.BizwareGL/GraphicsControl.cs new file mode 100644 index 0000000000..3882501134 --- /dev/null +++ b/Bizware/BizHawk.Bizware.BizwareGL/GraphicsControl.cs @@ -0,0 +1,42 @@ +using System; +using swf = System.Windows.Forms; + +namespace BizHawk.Bizware.BizwareGL +{ + /// + /// Represents + /// + public abstract class GraphicsControl : IDisposable + { + /// + /// Gets the control that this interface is wrapping + /// + public abstract swf.Control Control { get; } + + public static implicit operator swf.Control(GraphicsControl ctrl) { return ctrl.Control; } + + /// + /// Sets whether presentation operations on this control will vsync + /// + public abstract void SetVsync(bool state); + + /// + /// Swaps the buffers for this control + /// + public abstract void SwapBuffers(); + + /// + /// Makes this control current for rendering operations. + /// Note that at this time, the window size shouldnt change until End() or else something bad might happen + /// Please be aware that this might change the rendering context, meaning that some things you set without calling BeginControl/EndControl might not be affected + /// + public abstract void Begin(); + + /// + /// Ends rendering on the specified control. + /// + public abstract void End(); + + public abstract void Dispose(); + } +} \ No newline at end of file diff --git a/Bizware/BizHawk.Bizware.BizwareGL/GuiRenderer.cs b/Bizware/BizHawk.Bizware.BizwareGL/GuiRenderer.cs new file mode 100644 index 0000000000..98caddda1b --- /dev/null +++ b/Bizware/BizHawk.Bizware.BizwareGL/GuiRenderer.cs @@ -0,0 +1,305 @@ +//http://stackoverflow.com/questions/6893302/decode-rgb-value-to-single-float-without-bit-shift-in-glsl + +using System; +using sd=System.Drawing; + +namespace BizHawk.Bizware.BizwareGL +{ + /// + /// A simple renderer useful for rendering GUI stuff. + /// When doing GUI rendering, run everything through here (if you need a GL feature not done through here, run it through here first) + /// Call Begin, then draw, then End, and dont use other Renderers or GL calls in the meantime, unless you know what youre doing. + /// This can perform batching (well.. maybe not yet), which is occasionally necessary for drawing large quantities of things. + /// + public class GuiRenderer : IDisposable + { + public GuiRenderer(IGL owner) + { + Owner = owner; + + VertexLayout = owner.CreateVertexLayout(); + VertexLayout.DefineVertexAttribute(0, 2, BizwareGL.VertexAttributeType.Float, false, 16, 0); + VertexLayout.DefineVertexAttribute(1, 2, BizwareGL.VertexAttributeType.Float, false, 16, 8); + VertexLayout.Close(); + + _Projection = new MatrixStack(); + _Modelview = new MatrixStack(); + + var vs = Owner.CreateVertexShader(DefaultVertexShader); + var ps = Owner.CreateFragmentShader(DefaultPixelShader); + CurrPipeline = DefaultPipeline = Owner.CreatePipeline(vs, ps); + } + + public void Dispose() + { + VertexLayout.Dispose(); + VertexLayout = null; + DefaultPipeline.Dispose(); + DefaultPipeline = null; + } + + /// + /// Sets the pipeline for this GuiRenderer to use. We won't keep possession of it. + /// This pipeline must work in certain ways, which can be discerned by inspecting the built-in one + /// + public void SetPipeline(Pipeline pipeline) + { + if (IsActive) + throw new InvalidOperationException("Can't change pipeline while renderer is running!"); + + Flush(); + CurrPipeline = pipeline; + } + + /// + /// Restores the pipeline to the default + /// + public void SetDefaultPipeline() + { + SetPipeline(DefaultPipeline); + } + + public void SetModulateColorWhite() + { + SetModulateColor(sd.Color.White); + } + + public void SetModulateColor(sd.Color color) + { + Flush(); + CurrPipeline["uModulateColor"].Set(new Vector4(color.R / 255.0f, color.G / 255.0f, color.B / 255.0f, color.A / 255.0f)); + } + + public void SetBlendState(IBlendState rsBlend) + { + Flush(); + Owner.SetBlendState(rsBlend); + } + + MatrixStack _Projection, _Modelview; + public MatrixStack Projection + { + get { return _Projection; } + set + { + _Projection = value; + _Projection.IsDirty = true; + } + } + public MatrixStack Modelview + { + get { return _Modelview; } + set + { + _Modelview = value; + _Modelview.IsDirty = true; + } + } + + /// + /// begin rendering, initializing viewport and projections to the given dimensions + /// + public void Begin(int width, int height) + { + Begin(); + + Projection = Owner.CreateGuiProjectionMatrix(width, height); + Modelview = Owner.CreateGuiViewMatrix(width, height); + Owner.SetViewport(0, 0, width, height); + } + + /// + /// Begins rendering + /// + public void Begin() + { + //uhhmmm I want to throw an exception if its already active, but its annoying. + + if(CurrPipeline == null) + throw new InvalidOperationException("Pipeline hasn't been set!"); + + IsActive = true; + Owner.BindVertexLayout(VertexLayout); + Owner.BindPipeline(CurrPipeline); + + //clear state cache + sTexture = null; + Modelview.Clear(); + Projection.Clear(); + SetModulateColorWhite(); + } + + /// + /// Use this, if you must do something sneaky to openGL without this GuiRenderer knowing. + /// It might be faster than End and Beginning again, and certainly prettier + /// + public void Flush() + { + //no batching, nothing to do here yet + } + + /// + /// Ends rendering + /// + public void End() + { + if (!IsActive) + throw new InvalidOperationException("GuiRenderer is not active!"); + IsActive = false; + } + + /// + /// Draws a subrectangle from the provided texture. For advanced users only + /// + public void DrawSubrect(Texture2d tex, float x, float y, float w, float h, float u0, float v0, float u1, float v1) + { + DrawSubrectInternal(tex, x, y, w, h, u0, v0, u1, v1); + } + + /// + /// draws the specified Art resource + /// + public void Draw(Art art) { DrawInternal(art, 0, 0, art.Width, art.Height, false, false); } + + /// + /// draws the specified Art resource with the specified offset. This could be tricky if youve applied other rotate or scale transforms first. + /// + public void Draw(Art art, float x, float y) { DrawInternal(art, x, y, art.Width, art.Height, false, false); } + + /// + /// draws the specified Art resource with the specified offset, with the specified size. This could be tricky if youve applied other rotate or scale transforms first. + /// + public void Draw(Art art, float x, float y, float width, float height) { DrawInternal(art, x, y, width, height, false, false); } + + /// + /// draws the specified Art resource with the specified offset. This could be tricky if youve applied other rotate or scale transforms first. + /// + public void Draw(Art art, Vector2 pos) { DrawInternal(art, pos.X, pos.Y, art.Width, art.Height, false, false); } + + /// + /// draws the specified texture2d resource. + /// + public void Draw(Texture2d tex) { DrawInternal(tex, 0, 0, tex.Width, tex.Height); } + + /// + /// draws the specified Art resource with the given flip flags + /// + public void DrawFlipped(Art art, bool xflip, bool yflip) { DrawInternal(art, 0, 0, art.Width, art.Height, xflip, yflip); } + + unsafe void DrawInternal(Texture2d tex, float x, float y, float w, float h) + { + Art art = new Art(null); + art.Width = w; + art.Height = h; + art.u0 = art.v0 = 0; + art.u1 = art.v1 = 1; + art.BaseTexture = tex; + DrawInternal(art,x,y,w,h,false,false); + } + + unsafe void DrawInternal(Art art, float x, float y, float w, float h, bool fx, bool fy) + { + float u0,v0,u1,v1; + if(fx) { u0 = art.u1; u1 = art.u0; } + else { u0 = art.u0; u1 = art.u1; } + if(fy) { v0 = art.v1; v1 = art.v0; } + else { v0 = art.v0; v1 = art.v1; } + + float[] data = new float[16] { + x,y, u0,v0, + x+art.Width,y, u1,v0, + x,y+art.Height, u0,v1, + x+art.Width,y+art.Height, u1,v1 + }; + + Texture2d tex = art.BaseTexture; + if(sTexture != tex) + CurrPipeline["uSampler0"].Set(sTexture = tex); + + if (_Projection.IsDirty) + { + CurrPipeline["um44Projection"].Set(ref _Projection.Top); + _Projection.IsDirty = false; + } + if (_Modelview.IsDirty) + { + CurrPipeline["um44Modelview"].Set(ref _Modelview.Top); + _Modelview.IsDirty = false; + } + + fixed (float* pData = &data[0]) + { + Owner.BindArrayData(pData); + Owner.DrawArrays(PrimitiveType.TriangleStrip, 0, 4); + } + } + + unsafe void DrawSubrectInternal(Texture2d tex, float x, float y, float w, float h, float u0, float v0, float u1, float v1) + { + float[] data = new float[16] { + x,y, u0,v0, + x+w,y, u1,v0, + x,y+h, u0,v1, + x+w,y+h, u1,v1 + }; + + if (sTexture != tex) + CurrPipeline["uSampler0"].Set(sTexture = tex); + + if (_Projection.IsDirty) + { + CurrPipeline["um44Projection"].Set(ref _Projection.Top); + _Projection.IsDirty = false; + } + if (_Modelview.IsDirty) + { + CurrPipeline["um44Modelview"].Set(ref _Modelview.Top); + _Modelview.IsDirty = false; + } + + fixed (float* pData = &data[0]) + { + Owner.BindArrayData(pData); + Owner.DrawArrays(PrimitiveType.TriangleStrip, 0, 4); + } + } + + public bool IsActive { get; private set; } + public IGL Owner { get; private set; } + + VertexLayout VertexLayout; + Pipeline CurrPipeline, DefaultPipeline; + + //state cache + Texture2d sTexture; + + public readonly string DefaultVertexShader = @" +uniform mat4 um44Modelview, um44Projection; + +attribute vec2 aPosition; +attribute vec2 aTexcoord; + +varying vec2 vTexcoord0; + +void main() +{ + vec4 temp = vec4(aPosition,0,1); + gl_Position = um44Projection * (um44Modelview * temp); + vTexcoord0 = aTexcoord; +}"; + + public readonly string DefaultPixelShader = @" +uniform sampler2D uSampler0; +uniform vec4 uModulateColor; + +varying vec2 vTexcoord0; + +void main() +{ + vec4 temp = texture2D(uSampler0,vTexcoord0); + temp *= uModulateColor; + gl_FragColor = temp; +}"; + + } +} \ No newline at end of file diff --git a/Bizware/BizHawk.Bizware.BizwareGL/IGL.cs b/Bizware/BizHawk.Bizware.BizwareGL/IGL.cs new file mode 100644 index 0000000000..3e6f739d6f --- /dev/null +++ b/Bizware/BizHawk.Bizware.BizwareGL/IGL.cs @@ -0,0 +1,204 @@ +using System; +using System.IO; +using sd=System.Drawing; +using swf=System.Windows.Forms; + +namespace BizHawk.Bizware.BizwareGL +{ + + /// + /// This is a wrapper over hopefully any OpenGL bindings.. + /// And possibly, quite possibly, Direct3d.. even though none of your shaders would work. (could use nvidia CG, native dlls in necessary since this would only be for windows) + /// TODO - This really needs to be split up into an internal and a user interface. so many of the functions are made to support the smart wrappers + /// Maybe make a method that returns an interface used for advanced methods (and IGL_TK could implement that as well and just "return this:") + /// + public interface IGL + { + /// + /// Returns an a control optimized for drawing onto the screen. + /// + GraphicsControl CreateGraphicsControl(); + + /// + /// Clears the specified buffer parts + /// + /// + void Clear(ClearBufferMask mask); + + /// + /// Sets the current clear color + /// + void ClearColor(sd.Color color); + + /// + /// generates a texture handle + /// + IntPtr GenTexture(); + + /// + /// returns an empty handle + /// + IntPtr GetEmptyHandle(); + + /// + /// returns an empty uniform handle + /// + IntPtr GetEmptyUniformHandle(); + + /// + /// compile a fragment shader. This is the simplified method. A more complex method may be added later which will accept multiple sources and preprocessor definitions independently + /// + Shader CreateFragmentShader(string source); + + /// + /// compile a vertex shader. This is the simplified method. A more complex method may be added later which will accept multiple sources and preprocessor definitions independently + /// + Shader CreateVertexShader(string source); + + /// + /// Creates a complete pipeline from the provided vertex and fragment shader handles + /// + Pipeline CreatePipeline(Shader vertexShader, Shader fragmentShader); + + /// + /// Binds this pipeline as the current used for rendering + /// + void BindPipeline(Pipeline pipeline); + + /// + /// Sets a uniform sampler to use use the provided texture handle + /// + void SetPipelineUniformSampler(PipelineUniform uniform, IntPtr texHandle); + + /// + /// Sets a uniform value + /// + void SetPipelineUniformMatrix(PipelineUniform uniform, Matrix mat, bool transpose); + + /// + /// Sets a uniform value + /// + void SetPipelineUniformMatrix(PipelineUniform uniform, ref Matrix mat, bool transpose); + + /// + /// sets a uniform value + /// + void SetPipelineUniform(PipelineUniform uniform, Vector4 value); + + /// + /// Binds this VertexLayout for use in rendering (in OpenGL's case, by glVertexAttribPointer calls) + /// + void BindVertexLayout(VertexLayout layout); + + /// + /// Binds array data for use with the currently-bound VertexLayout + /// + unsafe void BindArrayData(void* pData); + + /// + /// Draws based on the currently set VertexLayout and ArrayData + /// + void DrawArrays(PrimitiveType mode, int first, int count); + + /// + /// Frees the provided shader handle + /// + void FreeShader(IntPtr shader); + + /// + /// frees the provided texture handle + /// + void FreeTexture(IntPtr texHandle); + + /// + /// Binds this texture as the current texture2d target for parameter-specification + /// + /// + void BindTexture2d(Texture2d texture); + + /// + /// Sets a 2d texture parameter + /// + void TexParameter2d(TextureParameterName pname, int param); + + /// + /// creates a vertex layout resource + /// + VertexLayout CreateVertexLayout(); + + /// + /// Creates a blending state object + /// + IBlendState CreateBlendState(BlendingFactor colorSource, BlendEquationMode colorEquation, BlendingFactor colorDest, + BlendingFactor alphaSource, BlendEquationMode alphaEquation, BlendingFactor alphaDest); + + /// + /// retrieves a blend state for opaque rendering + /// Alpha values are copied from the source fragment. + /// + IBlendState BlendNone { get; } + + /// + /// retrieves a blend state for normal (non-premultiplied) alpha blending. + /// Alpha values are copied from the source fragment. + /// + IBlendState BlendNormal { get; } + + /// + /// Sets the current blending state object + /// + void SetBlendState(IBlendState rsBlend); + + /// + /// Creates a texture with the specified dimensions + /// TODO - pass in specifications somehow + /// + Texture2d CreateTexture(int width, int height); + + /// + /// Loads the texture with new data. This isnt supposed to be especially versatile, it just blasts a bitmap buffer into the texture + /// + void LoadTextureData(Texture2d tex, BitmapBuffer bmp); + + /// + /// Loads a texture from disk + /// + Texture2d LoadTexture(string path); + + /// + /// Loads a texture from the stream + /// + Texture2d LoadTexture(Stream stream); + + /// + /// Loads a texture from the BitmapBuffer + /// + Texture2d LoadTexture(BitmapBuffer buffer); + + /// + /// Loads a texture from the System.Drawing.Bitmap + /// + Texture2d LoadTexture(sd.Bitmap bitmap); + + /// + /// sets the viewport according to the provided specifications + /// + void SetViewport(int x, int y, int width, int height); + + /// + /// sets the viewport according to the client area of the provided control + /// + void SetViewport(swf.Control control); + + /// + /// generates a proper 2d othographic projection for the given destination size, suitable for use in a GUI + /// + Matrix CreateGuiProjectionMatrix(int w, int h); + + /// + /// generates a proper view transform for a standard 2d ortho projection, including half-pixel jitter if necessary and + /// re-establishing of a normal 2d graphics top-left origin. suitable for use in a GUI + /// + Matrix CreateGuiViewMatrix(int w, int h); + } +} diff --git a/Bizware/BizHawk.Bizware.BizwareGL/MatrixStack.cs b/Bizware/BizHawk.Bizware.BizwareGL/MatrixStack.cs new file mode 100644 index 0000000000..a24ee4c4e5 --- /dev/null +++ b/Bizware/BizHawk.Bizware.BizwareGL/MatrixStack.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; + +namespace BizHawk.Bizware.BizwareGL +{ + public class MatrixStack + { + public MatrixStack() + { + LoadIdentity(); + IsDirty = false; + } + + public static implicit operator Matrix(MatrixStack ms) { return ms.Top; } + public static implicit operator MatrixStack(Matrix m) { return new MatrixStack(m); } + + public MatrixStack(Matrix matrix) { LoadMatrix(matrix); } + + public bool IsDirty; + + Stack stack = new Stack(); + + /// + /// This is made public for performance reasons, to avoid lame copies of the matrix when necessary. Don't mess it up! + /// + public Matrix Top; + + /// + /// Resets the matrix stack to an empty identity matrix stack + /// + public void Clear() + { + stack.Clear(); + LoadIdentity(); + IsDirty = true; + } + + /// + /// Clears the matrix stack and loads the specified value + /// + public void Clear(Matrix value) + { + stack.Clear(); + Top = value; + IsDirty = true; + } + + public void LoadMatrix(Matrix value) { Top = value; IsDirty = true; } + + public void LoadIdentity() { Top = Matrix.Identity; IsDirty = true; } + + public void Pop() { Top = stack.Pop(); IsDirty = true; } + public void Push() { stack.Push(Top); IsDirty = true; } + + public void RotateAxis(Vector3 axisRotation, float angle) { Top = Matrix.CreateFromAxisAngle(axisRotation, angle) * Top; IsDirty = true; } + + public void Scale(Vector3 scale) { Top = Matrix.CreateScale(scale) * Top; IsDirty = true; } + public void Scale(Vector2 scale) { Top = Matrix.CreateScale(scale.X, scale.Y, 1) * Top; IsDirty = true; } + public void Scale(float x, float y, float z) { Top = Matrix.CreateScale(x, y, z) * Top; IsDirty = true; } + public void Scale(float ratio) { Scale(ratio, ratio, ratio); IsDirty = true; } + public void Scale(float x, float y) { Scale(x, y, 1); IsDirty = true; } + + public void RotateAxis(float x, float y, float z, float degrees) { MultiplyMatrix(Matrix.CreateFromAxisAngle(new Vector3(x, y, z), degrees)); IsDirty = true; } + public void RotateY(float degrees) { MultiplyMatrix(Matrix.CreateRotationY(degrees)); IsDirty = true; } + public void RotateX(float degrees) { MultiplyMatrix(Matrix.CreateRotationX(degrees)); IsDirty = true; } + public void RotateZ(float degrees) { MultiplyMatrix(Matrix.CreateRotationZ(degrees)); IsDirty = true; } + + public void Translate(Vector2 v) { Translate(v.X, v.Y, 0); IsDirty = true; } + public void Translate(Vector3 trans) { Top = Matrix.CreateTranslation(trans) * Top; IsDirty = true; } + public void Translate(float x, float y, float z) { Top = Matrix.CreateTranslation(x, y, z) * Top; IsDirty = true; } + public void Translate(float x, float y) { Translate(x, y, 0); IsDirty = true; } + public void Translate(Point pt) { Translate(pt.X, pt.Y, 0); IsDirty = true; } + + public void MultiplyMatrix(MatrixStack ms) { MultiplyMatrix(ms.Top); IsDirty = true; } + public void MultiplyMatrix(Matrix value) { Top = value * Top; IsDirty = true; } + } +} \ No newline at end of file diff --git a/Bizware/BizHawk.Bizware.BizwareGL/Pipeline.cs b/Bizware/BizHawk.Bizware.BizwareGL/Pipeline.cs new file mode 100644 index 0000000000..7131349800 --- /dev/null +++ b/Bizware/BizHawk.Bizware.BizwareGL/Pipeline.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; + +namespace BizHawk.Bizware.BizwareGL +{ + public class Pipeline : IDisposable + { + public Pipeline(IGL owner, IntPtr id, IEnumerable uniforms) + { + Owner = owner; + Id = id; + + //create the uniforms from the info list we got + UniformsDictionary = new SpecialWorkingDictionary(this); + foreach(var ui in uniforms) + { + UniformsDictionary[ui.Name] = new PipelineUniform(this, ui); + } + } + + /// + /// Allows us to create PipelineUniforms on the fly, in case a non-existing one has been requested. + /// GLSL will optimize out unused uniforms, and we wont have a record of it in the uniforms population loop + /// + class SpecialWorkingDictionary : Dictionary + { + public SpecialWorkingDictionary(Pipeline owner) + { + Owner = owner; + } + + Pipeline Owner; + public new PipelineUniform this[string key] + { + get + { + PipelineUniform temp; + if (!TryGetValue(key, out temp)) + { + var ui = new UniformInfo(); + ui.Handle = Owner.Owner.GetEmptyUniformHandle(); + temp = this[key] = new PipelineUniform(Owner,ui); + } + + return temp; + } + + internal set + { + base[key] = value; + } + } + } + + SpecialWorkingDictionary UniformsDictionary; + IDictionary Uniforms { get { return UniformsDictionary; } } + + public PipelineUniform this[string key] + { + get { return UniformsDictionary[key]; } + } + + public IGL Owner { get; private set; } + public IntPtr Id { get; private set; } + + ///// + ///// Makes the pipeline current + ///// + //public void BindData() + //{ + // Owner.BindPipeline(this); + //} + + public void Dispose() + { + //todo + } + + + } +} \ No newline at end of file diff --git a/Bizware/BizHawk.Bizware.BizwareGL/PipelineUniform.cs b/Bizware/BizHawk.Bizware.BizwareGL/PipelineUniform.cs new file mode 100644 index 0000000000..282b8144bc --- /dev/null +++ b/Bizware/BizHawk.Bizware.BizwareGL/PipelineUniform.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; + +namespace BizHawk.Bizware.BizwareGL +{ + public class PipelineUniform + { + internal PipelineUniform(Pipeline owner, UniformInfo info) + { + Owner = owner; + Id = info.Handle; + SamplerIndex = info.SamplerIndex; + } + + public Pipeline Owner { get; private set; } + public IntPtr Id { get; private set; } + public int SamplerIndex { get; private set; } + + public void Set(Matrix mat, bool transpose = false) + { + Owner.Owner.SetPipelineUniformMatrix(this, mat, transpose); + } + + public void Set(Vector4 vec, bool transpose = false) + { + Owner.Owner.SetPipelineUniform(this, vec); + } + + public void Set(ref Matrix mat, bool transpose = false) + { + Owner.Owner.SetPipelineUniformMatrix(this, ref mat, transpose); + } + + public void Set(Texture2d tex) + { + Owner.Owner.SetPipelineUniformSampler(this, tex.Id); + } + } +} \ No newline at end of file diff --git a/Bizware/BizHawk.Bizware.BizwareGL/RenderStates.cs b/Bizware/BizHawk.Bizware.BizwareGL/RenderStates.cs new file mode 100644 index 0000000000..cb2ba8624e --- /dev/null +++ b/Bizware/BizHawk.Bizware.BizwareGL/RenderStates.cs @@ -0,0 +1,7 @@ +using System; +using System.Collections.Generic; + +namespace BizHawk.Bizware.BizwareGL +{ + public interface IBlendState { } +} \ No newline at end of file diff --git a/Bizware/BizHawk.Bizware.BizwareGL/Shader.cs b/Bizware/BizHawk.Bizware.BizwareGL/Shader.cs new file mode 100644 index 0000000000..9dc8656a96 --- /dev/null +++ b/Bizware/BizHawk.Bizware.BizwareGL/Shader.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; + +namespace BizHawk.Bizware.BizwareGL +{ + /// + /// Just a lifecycle-managed wrapper around shader handles + /// + public class Shader : IDisposable + { + public Shader(IGL owner, IntPtr id) + { + Owner = owner; + Id = id; + } + + public IGL Owner { get; private set; } + public IntPtr Id { get; private set; } + public bool Disposed { get; private set; } + + public void Dispose() + { + if (Disposed) return; + Disposed = true; + Owner.FreeShader(Id); + Id = Owner.GetEmptyHandle(); + } + } +} \ No newline at end of file diff --git a/Bizware/BizHawk.Bizware.BizwareGL/StringRenderer.cs b/Bizware/BizHawk.Bizware.BizwareGL/StringRenderer.cs new file mode 100644 index 0000000000..527863d43e --- /dev/null +++ b/Bizware/BizHawk.Bizware.BizwareGL/StringRenderer.cs @@ -0,0 +1,80 @@ +//http://www.angelcode.com/products/bmfont/ +//http://cyotek.com/blog/angelcode-bitmap-font-parsing-using-csharp + +using System; +using sd=System.Drawing; +using System.Collections.Generic; +using System.IO; + +namespace BizHawk.Bizware.BizwareGL +{ + public class StringRenderer : IDisposable + { + public StringRenderer(IGL owner, Stream xml, params Stream[] textures) + { + Owner = owner; + FontInfo = Cyotek.Drawing.BitmapFont.BitmapFontLoader.LoadFontFromXmlFile(xml); + + //load textures + for(int i=0;i TexturePages = new List(); + + } +} \ No newline at end of file diff --git a/Bizware/BizHawk.Bizware.BizwareGL/TexAtlas.cs b/Bizware/BizHawk.Bizware.BizwareGL/TexAtlas.cs new file mode 100644 index 0000000000..9730b21d70 --- /dev/null +++ b/Bizware/BizHawk.Bizware.BizwareGL/TexAtlas.cs @@ -0,0 +1,325 @@ +using System; +using System.Text; +using System.Text.RegularExpressions; +using System.IO; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using System.Drawing; + +namespace BizHawk.Bizware.BizwareGL +{ + public class TexAtlas + { + public class RectItem + { + public RectItem(int width, int height, object item) + { + Width = width; + Height = height; + Item = item; + } + public int X, Y; + public int Width, Height; + public int TexIndex; + public object Item; + } + + + class TryFitParam + { + public TryFitParam(int _w, int _h) { this.w = _w; this.h = _h; } + public int w, h; + public bool ok = true; + public RectangleBinPack rbp = new RectangleBinPack(); + public List nodes = new List(); + } + + public class PackedAtlasResults + { + public class SingleAtlas + { + public Size Size; + public List Items; + } + public List Atlases = new List(); + } + + public static int MaxSizeBits = 16; + + /// + /// packs the supplied RectItems into an atlas. Modifies the RectItems with x/y values of location in new atlas. + /// + public static PackedAtlasResults PackAtlas(IEnumerable items) + { + PackedAtlasResults ret = new PackedAtlasResults(); + ret.Atlases.Add(new PackedAtlasResults.SingleAtlas()); + + //initially, we'll try all the items; none remain + List currentItems = new List(items); + List remainItems = new List(); + + RETRY: + + //this is where the texture size range is determined. + //we run this every time we make an atlas, in case we want to variably control the maximum texture output size. + //ALSO - we accumulate data in there, so we need to refresh it each time. ... lame. + List todoSizes = new List(); + for (int i = 3; i <= MaxSizeBits; i++) + { + for (int j = 3; j <= MaxSizeBits; j++) + { + int w = 1 << i; + int h = 1 << j; + TryFitParam tfp = new TryFitParam(w, h); + todoSizes.Add(tfp); + } + } + + //run the packing algorithm on each potential size + Parallel.ForEach(todoSizes, (param) => + { + var rbp = new RectangleBinPack(); + rbp.Init(16384, 16384); + param.rbp.Init(param.w, param.h); + + foreach (var ri in currentItems) + { + RectangleBinPack.Node node = param.rbp.Insert(ri.Width, ri.Height); + if (node == null) + { + param.ok = false; + } + else + { + node.ri = ri; + param.nodes.Add(node); + } + } + }); + + //find the best fit among the potential sizes that worked + long best = long.MaxValue; + TryFitParam tfpFinal = null; + foreach (TryFitParam tfp in todoSizes) + { + if (tfp.ok) + { + long area = (long)tfp.w * (long)tfp.h; + long perimeter = (long)tfp.w + (long)tfp.h; + if (area < best) + { + best = area; + tfpFinal = tfp; + } + else if (area == best) + { + //try to minimize perimeter (to create squares, which are nicer to look at) + if (tfpFinal == null) + { } + else if (perimeter < tfpFinal.w + tfpFinal.h) + { + best = area; + tfpFinal = tfp; + } + } + } + } + + //did we find any fit? + if (best == long.MaxValue) + { + //nope - move an item to the remaining list and try again + remainItems.Add(currentItems[currentItems.Count - 1]); + currentItems.RemoveAt(currentItems.Count - 1); + goto RETRY; + } + + //we found a fit. setup this atlas in the result and drop the items into it + var atlas = ret.Atlases[ret.Atlases.Count - 1]; + atlas.Size.Width = tfpFinal.w; + atlas.Size.Height = tfpFinal.h; + atlas.Items = new List(items); + foreach (var item in currentItems) + { + object o = item.Item; + var node = tfpFinal.nodes.Find((x) => x.ri == item); + item.X = node.x; + item.Y = node.y; + item.TexIndex = ret.Atlases.Count - 1; + } + + //if we have any items left, we've got to run this again + if (remainItems.Count > 0) + { + //move all remaining items into the clear list + currentItems.Clear(); + currentItems.AddRange(remainItems); + remainItems.Clear(); + + ret.Atlases.Add(new PackedAtlasResults.SingleAtlas()); + goto RETRY; + } + + if (ret.Atlases.Count > 1) + Console.WriteLine("Created animset with >1 texture ({0} textures)", ret.Atlases.Count); + + return ret; + } + + //original file: RectangleBinPack.cpp + //author: Jukka Jylänki + class RectangleBinPack + { + /** A node of a binary tree. Each node represents a rectangular area of the texture + we surface. Internal nodes store rectangles of used data, whereas leaf nodes track + rectangles of free space. All the rectangles stored in the tree are disjoint. */ + public class Node + { + // Left and right child. We don't really distinguish which is which, so these could + // as well be child1 and child2. + public Node left; + public Node right; + + // The top-left coordinate of the rectangle. + public int x; + public int y; + + // The dimension of the rectangle. + public int width; + public int height; + + public RectItem ri; + }; + + /// Starts a new packing process to a bin of the given dimension. + public void Init(int width, int height) + { + binWidth = width; + binHeight = height; + root = new Node(); + root.left = root.right = null; + root.x = root.y = 0; + root.width = width; + root.height = height; + } + + + /// Inserts a new rectangle of the given size into the bin. + /** Running time is linear to the number of rectangles that have been already packed. + @return A pointer to the node that stores the newly added rectangle, or 0 + if it didn't fit. */ + public Node Insert(int width, int height) + { + return Insert(root, width, height); + } + + /// Computes the ratio of used surface area. + float Occupancy() + { + int totalSurfaceArea = binWidth * binHeight; + int usedSurfaceArea = UsedSurfaceArea(root); + + return (float)usedSurfaceArea / totalSurfaceArea; + } + + private Node root; + + // The total size of the bin we started with. + private int binWidth; + private int binHeight; + + /// @return The surface area used by the subtree rooted at node. + private int UsedSurfaceArea(Node node) + { + if (node.left != null || node.right != null) + { + int usedSurfaceArea = node.width * node.height; + if (node.left != null) + usedSurfaceArea += UsedSurfaceArea(node.left); + if (node.right != null) + usedSurfaceArea += UsedSurfaceArea(node.right); + + return usedSurfaceArea; + } + + // This is a leaf node, it doesn't constitute to the total surface area. + return 0; + } + + + /// Inserts a new rectangle in the subtree rooted at the given node. + private Node Insert(Node node, int width, int height) + { + + // If this node is an internal node, try both leaves for possible space. + // (The rectangle in an internal node stores used space, the leaves store free space) + if (node.left != null || node.right != null) + { + if (node.left != null) + { + Node newNode = Insert(node.left, width, height); + if (newNode != null) + return newNode; + } + if (node.right != null) + { + Node newNode = Insert(node.right, width, height); + if (newNode != null) + return newNode; + } + return null; // Didn't fit into either subtree! + } + + // This node is a leaf, but can we fit the new rectangle here? + if (width > node.width || height > node.height) + return null; // Too bad, no space. + + // The new cell will fit, split the remaining space along the shorter axis, + // that is probably more optimal. + int w = node.width - width; + int h = node.height - height; + node.left = new Node(); + node.right = new Node(); + if (w <= h) // Split the remaining space in horizontal direction. + { + node.left.x = node.x + width; + node.left.y = node.y; + node.left.width = w; + node.left.height = height; + + node.right.x = node.x; + node.right.y = node.y + height; + node.right.width = node.width; + node.right.height = h; + } + else // Split the remaining space in vertical direction. + { + node.left.x = node.x; + node.left.y = node.y + height; + node.left.width = width; + node.left.height = h; + + node.right.x = node.x + width; + node.right.y = node.y; + node.right.width = w; + node.right.height = node.height; + } + // Note that as a result of the above, it can happen that node.left or node.right + // is now a degenerate (zero area) rectangle. No need to do anything about it, + // like remove the nodes as "unnecessary" since they need to exist as children of + // this node (this node can't be a leaf anymore). + + // This node is now a non-leaf, so shrink its area - it now denotes + // *occupied* space instead of free space. Its children spawn the resulting + // area of free space. + node.width = width; + node.height = height; + return node; + } + }; + + } + + +} \ No newline at end of file diff --git a/Bizware/BizHawk.Bizware.BizwareGL/Texture2d.cs b/Bizware/BizHawk.Bizware.BizwareGL/Texture2d.cs new file mode 100644 index 0000000000..7270c6f256 --- /dev/null +++ b/Bizware/BizHawk.Bizware.BizwareGL/Texture2d.cs @@ -0,0 +1,82 @@ +using System; + +namespace BizHawk.Bizware.BizwareGL +{ + /// + /// A full-scale 2D texture, with mip levels and everything. + /// In OpenGL tradition, this encapsulates the sampler state, as well, which is equal parts annoying and convenient + /// + public class Texture2d : IDisposable + { + //not sure if I need this idea. + //public class Maker + //{ + // public Maker(Texture2d tex) + // { + // MyTexture = tex; + // } + // public void SetWidth(int width) + // { + // MyTexture.Width = width; + // } + // public void SetHeight(int width) + // { + // MyTexture.Height = height; + // } + + // Texture2d MyTexture; + //} + + public void Dispose() + { + Owner.FreeTexture(Id); + Id = Owner.GetEmptyHandle(); + } + + public Texture2d(IGL owner, IntPtr id, int width, int height) + { + Owner = owner; + Id = id; + Width = width; + Height = height; + } + + public void LoadFrom(BitmapBuffer buffer) + { + } + + public void SetMinFilter(TextureMinFilter minFilter) + { + Owner.BindTexture2d(this); + Owner.TexParameter2d(TextureParameterName.TextureMinFilter, (int)minFilter); + } + + public void SetMagFilter(TextureMagFilter magFilter) + { + Owner.BindTexture2d(this); + Owner.TexParameter2d(TextureParameterName.TextureMagFilter, (int)magFilter); + } + + public void SetFilterLinear() + { + SetMinFilter(TextureMinFilter.Linear); + SetMagFilter(TextureMagFilter.Linear); + } + + public void SetFilterNearest() + { + SetMinFilter(TextureMinFilter.Nearest); + SetMagFilter(TextureMagFilter.Nearest); + } + + public IGL Owner { get; private set; } + public IntPtr Id { get; private set; } + + //note.. it is commonly helpful to have these as floats, since we're more often using them for rendering than for raster logic + public float Width { get; private set; } + public float Height { get; private set; } + + public int IntWidth { get { return (int)Width; } } + public int IntHeight { get { return (int)Height; } } + } +} \ No newline at end of file diff --git a/Bizware/BizHawk.Bizware.BizwareGL/Types/BoundingBox.cs b/Bizware/BizHawk.Bizware.BizwareGL/Types/BoundingBox.cs new file mode 100644 index 0000000000..cf917f3ee4 --- /dev/null +++ b/Bizware/BizHawk.Bizware.BizwareGL/Types/BoundingBox.cs @@ -0,0 +1,426 @@ +#region License +/* +MIT License +Copyright © 2006 The Mono.Xna Team + +All rights reserved. + +Authors: +Olivier Dufour (Duff) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +#endregion License + +using System; +using System.Collections.Generic; +using System.ComponentModel; + +namespace BizHawk.Bizware.BizwareGL +{ + public struct BoundingBox : IEquatable + { + + #region Public Fields + + public Vector3 Min; + public Vector3 Max; + public const int CornerCount = 8; + + #endregion Public Fields + + + #region Public Constructors + + public BoundingBox(Vector3 min, Vector3 max) + { + this.Min = min; + this.Max = max; + } + + #endregion Public Constructors + + + #region Public Methods + + public ContainmentType Contains(BoundingBox box) + { + //test if all corner is in the same side of a face by just checking min and max + if (box.Max.X < Min.X + || box.Min.X > Max.X + || box.Max.Y < Min.Y + || box.Min.Y > Max.Y + || box.Max.Z < Min.Z + || box.Min.Z > Max.Z) + return ContainmentType.Disjoint; + + + if (box.Min.X >= Min.X + && box.Max.X <= Max.X + && box.Min.Y >= Min.Y + && box.Max.Y <= Max.Y + && box.Min.Z >= Min.Z + && box.Max.Z <= Max.Z) + return ContainmentType.Contains; + + return ContainmentType.Intersects; + } + + public void Contains(ref BoundingBox box, out ContainmentType result) + { + result = Contains(box); + } + + public ContainmentType Contains(BoundingFrustum frustum) + { + //TODO: bad done here need a fix. + //Because question is not frustum contain box but reverse and this is not the same + int i; + ContainmentType contained; + Vector3[] corners = frustum.GetCorners(); + + // First we check if frustum is in box + for (i = 0; i < corners.Length; i++) + { + this.Contains(ref corners[i], out contained); + if (contained == ContainmentType.Disjoint) + break; + } + + if (i == corners.Length) // This means we checked all the corners and they were all contain or instersect + return ContainmentType.Contains; + + if (i != 0) // if i is not equal to zero, we can fastpath and say that this box intersects + return ContainmentType.Intersects; + + + // If we get here, it means the first (and only) point we checked was actually contained in the frustum. + // So we assume that all other points will also be contained. If one of the points is disjoint, we can + // exit immediately saying that the result is Intersects + i++; + for (; i < corners.Length; i++) + { + this.Contains(ref corners[i], out contained); + if (contained != ContainmentType.Contains) + return ContainmentType.Intersects; + + } + + // If we get here, then we know all the points were actually contained, therefore result is Contains + return ContainmentType.Contains; + } + + public ContainmentType Contains(BoundingSphere sphere) + { + if (sphere.Center.X - Min.X > sphere.Radius + && sphere.Center.Y - Min.Y > sphere.Radius + && sphere.Center.Z - Min.Z > sphere.Radius + && Max.X - sphere.Center.X > sphere.Radius + && Max.Y - sphere.Center.Y > sphere.Radius + && Max.Z - sphere.Center.Z > sphere.Radius) + return ContainmentType.Contains; + + double dmin = 0; + + if (sphere.Center.X - Min.X <= sphere.Radius) + dmin += (sphere.Center.X - Min.X) * (sphere.Center.X - Min.X); + else if (Max.X - sphere.Center.X <= sphere.Radius) + dmin += (sphere.Center.X - Max.X) * (sphere.Center.X - Max.X); + if (sphere.Center.Y - Min.Y <= sphere.Radius) + dmin += (sphere.Center.Y - Min.Y) * (sphere.Center.Y - Min.Y); + else if (Max.Y - sphere.Center.Y <= sphere.Radius) + dmin += (sphere.Center.Y - Max.Y) * (sphere.Center.Y - Max.Y); + if (sphere.Center.Z - Min.Z <= sphere.Radius) + dmin += (sphere.Center.Z - Min.Z) * (sphere.Center.Z - Min.Z); + else if (Max.Z - sphere.Center.Z <= sphere.Radius) + dmin += (sphere.Center.Z - Max.Z) * (sphere.Center.Z - Max.Z); + + if (dmin <= sphere.Radius * sphere.Radius) + return ContainmentType.Intersects; + + return ContainmentType.Disjoint; + } + + public void Contains(ref BoundingSphere sphere, out ContainmentType result) + { + result = this.Contains(sphere); + } + + public ContainmentType Contains(Vector3 point) + { + ContainmentType result; + this.Contains(ref point, out result); + return result; + } + + public void Contains(ref Vector3 point, out ContainmentType result) + { + //first we get if point is out of box + if (point.X < this.Min.X + || point.X > this.Max.X + || point.Y < this.Min.Y + || point.Y > this.Max.Y + || point.Z < this.Min.Z + || point.Z > this.Max.Z) + { + result = ContainmentType.Disjoint; + }//or if point is on box because coordonate of point is lesser or equal + else if (point.X == this.Min.X + || point.X == this.Max.X + || point.Y == this.Min.Y + || point.Y == this.Max.Y + || point.Z == this.Min.Z + || point.Z == this.Max.Z) + result = ContainmentType.Intersects; + else + result = ContainmentType.Contains; + + + } + + public static BoundingBox CreateFromPoints(IEnumerable points) + { + if (points == null) + throw new ArgumentNullException(); + + // TODO: Just check that Count > 0 + bool empty = true; + Vector3 vector2 = new Vector3(float.MaxValue); + Vector3 vector1 = new Vector3(float.MinValue); + foreach (Vector3 vector3 in points) + { + vector2 = Vector3.Min(vector2, vector3); + vector1 = Vector3.Max(vector1, vector3); + empty = false; + } + if (empty) + throw new ArgumentException(); + + return new BoundingBox(vector2, vector1); + } + + public static BoundingBox CreateFromSphere(BoundingSphere sphere) + { + Vector3 vector1 = new Vector3(sphere.Radius); + return new BoundingBox(sphere.Center - vector1, sphere.Center + vector1); + } + + public static void CreateFromSphere(ref BoundingSphere sphere, out BoundingBox result) + { + result = BoundingBox.CreateFromSphere(sphere); + } + + public static BoundingBox CreateMerged(BoundingBox original, BoundingBox additional) + { + return new BoundingBox( + Vector3.Min(original.Min, additional.Min), Vector3.Max(original.Max, additional.Max)); + } + + public static void CreateMerged(ref BoundingBox original, ref BoundingBox additional, out BoundingBox result) + { + result = BoundingBox.CreateMerged(original, additional); + } + + public bool Equals(BoundingBox other) + { + return (this.Min == other.Min) && (this.Max == other.Max); + } + + public override bool Equals(object obj) + { + return (obj is BoundingBox) ? this.Equals((BoundingBox)obj) : false; + } + + public Vector3[] GetCorners() + { + return new Vector3[] { + new Vector3(this.Min.X, this.Max.Y, this.Max.Z), + new Vector3(this.Max.X, this.Max.Y, this.Max.Z), + new Vector3(this.Max.X, this.Min.Y, this.Max.Z), + new Vector3(this.Min.X, this.Min.Y, this.Max.Z), + new Vector3(this.Min.X, this.Max.Y, this.Min.Z), + new Vector3(this.Max.X, this.Max.Y, this.Min.Z), + new Vector3(this.Max.X, this.Min.Y, this.Min.Z), + new Vector3(this.Min.X, this.Min.Y, this.Min.Z) + }; + } + + public void GetCorners(Vector3[] corners) + { + if (corners == null) + { + throw new ArgumentNullException("corners"); + } + if (corners.Length < 8) + { + throw new ArgumentOutOfRangeException("corners", "Not Enought Corners"); + } + corners[0].X = this.Min.X; + corners[0].Y = this.Max.Y; + corners[0].Z = this.Max.Z; + corners[1].X = this.Max.X; + corners[1].Y = this.Max.Y; + corners[1].Z = this.Max.Z; + corners[2].X = this.Max.X; + corners[2].Y = this.Min.Y; + corners[2].Z = this.Max.Z; + corners[3].X = this.Min.X; + corners[3].Y = this.Min.Y; + corners[3].Z = this.Max.Z; + corners[4].X = this.Min.X; + corners[4].Y = this.Max.Y; + corners[4].Z = this.Min.Z; + corners[5].X = this.Max.X; + corners[5].Y = this.Max.Y; + corners[5].Z = this.Min.Z; + corners[6].X = this.Max.X; + corners[6].Y = this.Min.Y; + corners[6].Z = this.Min.Z; + corners[7].X = this.Min.X; + corners[7].Y = this.Min.Y; + corners[7].Z = this.Min.Z; + } + + public override int GetHashCode() + { + return this.Min.GetHashCode() + this.Max.GetHashCode(); + } + + public bool Intersects(BoundingBox box) + { + bool result; + Intersects(ref box, out result); + return result; + } + + public void Intersects(ref BoundingBox box, out bool result) + { + if ((this.Max.X >= box.Min.X) && (this.Min.X <= box.Max.X)) + { + if ((this.Max.Y < box.Min.Y) || (this.Min.Y > box.Max.Y)) + { + result = false; + return; + } + + result = (this.Max.Z >= box.Min.Z) && (this.Min.Z <= box.Max.Z); + return; + } + + result = false; + return; + } + + public bool Intersects(BoundingFrustum frustum) + { + return frustum.Intersects(this); + } + + public bool Intersects(BoundingSphere sphere) + { + if (sphere.Center.X - Min.X > sphere.Radius + && sphere.Center.Y - Min.Y > sphere.Radius + && sphere.Center.Z - Min.Z > sphere.Radius + && Max.X - sphere.Center.X > sphere.Radius + && Max.Y - sphere.Center.Y > sphere.Radius + && Max.Z - sphere.Center.Z > sphere.Radius) + return true; + + double dmin = 0; + + if (sphere.Center.X - Min.X <= sphere.Radius) + dmin += (sphere.Center.X - Min.X) * (sphere.Center.X - Min.X); + else if (Max.X - sphere.Center.X <= sphere.Radius) + dmin += (sphere.Center.X - Max.X) * (sphere.Center.X - Max.X); + + if (sphere.Center.Y - Min.Y <= sphere.Radius) + dmin += (sphere.Center.Y - Min.Y) * (sphere.Center.Y - Min.Y); + else if (Max.Y - sphere.Center.Y <= sphere.Radius) + dmin += (sphere.Center.Y - Max.Y) * (sphere.Center.Y - Max.Y); + + if (sphere.Center.Z - Min.Z <= sphere.Radius) + dmin += (sphere.Center.Z - Min.Z) * (sphere.Center.Z - Min.Z); + else if (Max.Z - sphere.Center.Z <= sphere.Radius) + dmin += (sphere.Center.Z - Max.Z) * (sphere.Center.Z - Max.Z); + + if (dmin <= sphere.Radius * sphere.Radius) + return true; + + return false; + } + + public void Intersects(ref BoundingSphere sphere, out bool result) + { + result = Intersects(sphere); + } + + public PlaneIntersectionType Intersects(Plane plane) + { + //check all corner side of plane + Vector3[] corners = this.GetCorners(); + float lastdistance = Vector3.Dot(plane.Normal, corners[0]) + plane.D; + + for (int i = 1; i < corners.Length; i++) + { + float distance = Vector3.Dot(plane.Normal, corners[i]) + plane.D; + if ((distance <= 0.0f && lastdistance > 0.0f) || (distance >= 0.0f && lastdistance < 0.0f)) + return PlaneIntersectionType.Intersecting; + lastdistance = distance; + } + + if (lastdistance > 0.0f) + return PlaneIntersectionType.Front; + + return PlaneIntersectionType.Back; + + } + + public void Intersects(ref Plane plane, out PlaneIntersectionType result) + { + result = Intersects(plane); + } + + public Nullable Intersects(Ray ray) + { + return ray.Intersects(this); + } + + public void Intersects(ref Ray ray, out Nullable result) + { + result = Intersects(ray); + } + + public static bool operator ==(BoundingBox a, BoundingBox b) + { + return a.Equals(b); + } + + public static bool operator !=(BoundingBox a, BoundingBox b) + { + return !a.Equals(b); + } + + public override string ToString() + { + return string.Format("{{Min:{0} Max:{1}}}", this.Min.ToString(), this.Max.ToString()); + } + + #endregion Public Methods + } +} diff --git a/Bizware/BizHawk.Bizware.BizwareGL/Types/BoundingFrustum.cs b/Bizware/BizHawk.Bizware.BizwareGL/Types/BoundingFrustum.cs new file mode 100644 index 0000000000..463bfc0b9a --- /dev/null +++ b/Bizware/BizHawk.Bizware.BizwareGL/Types/BoundingFrustum.cs @@ -0,0 +1,513 @@ +#region License +/* +MIT License +Copyright © 2006 The Mono.Xna Team + +All rights reserved. + +Authors: +Olivier Dufour (Duff) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +#endregion License + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Runtime.InteropServices; +using System.Text; + +namespace BizHawk.Bizware.BizwareGL +{ + [Serializable, TypeConverter(typeof(ExpandableObjectConverter))] + public class BoundingFrustum : IEquatable + { + #region Private Fields + + private Matrix matrix; + private Plane bottom; + private Plane far; + private Plane left; + private Plane right; + private Plane near; + private Plane top; + private Vector3[] corners; + + #endregion Private Fields + + #region Public Fields + public const int CornerCount = 8; + #endregion + + #region Public Constructors + + public BoundingFrustum(Matrix value) + { + this.matrix = value; + CreatePlanes(); + CreateCorners(); + } + + #endregion Public Constructors + + + #region Public Properties + + public Plane Bottom + { + get { return this.bottom; } + } + + public Plane Far + { + get { return this.far; } + } + + public Plane Left + { + get { return this.left; } + } + + public Matrix Matrix + { + get { return this.matrix; } + set + { + this.matrix = value; + this.CreatePlanes(); // FIXME: The odds are the planes will be used a lot more often than the matrix + this.CreateCorners(); // is updated, so this should help performance. I hope ;) + } + } + + public Plane Near + { + get { return this.near; } + } + + public Plane Right + { + get { return this.right; } + } + + public Plane Top + { + get { return this.top; } + } + + #endregion Public Properties + + + #region Public Methods + + public static bool operator ==(BoundingFrustum a, BoundingFrustum b) + { + if (object.Equals(a, null)) + return (object.Equals(b, null)); + + if (object.Equals(b, null)) + return (object.Equals(a, null)); + + return a.matrix == (b.matrix); + } + + public static bool operator !=(BoundingFrustum a, BoundingFrustum b) + { + return !(a == b); + } + + public ContainmentType Contains(BoundingBox box) + { + ContainmentType result; + this.Contains(ref box, out result); + return result; + } + + public void GetCorners(Vector3[] corners) + { + throw new NotImplementedException(); + } + + public void Contains(ref BoundingBox box, out ContainmentType result) + { + // FIXME: Is this a bug? + // If the bounding box is of W * D * H = 0, then return disjoint + if (box.Min == box.Max) + { + result = ContainmentType.Disjoint; + return; + } + + int i; + ContainmentType contained; + Vector3[] corners = box.GetCorners(); + + // First we assume completely disjoint. So if we find a point that is contained, we break out of this loop + for (i = 0; i < corners.Length; i++) + { + this.Contains(ref corners[i], out contained); + if (contained != ContainmentType.Disjoint) + break; + } + + if (i == corners.Length) // This means we checked all the corners and they were all disjoint + { + result = ContainmentType.Disjoint; + return; + } + + if (i != 0) // if i is not equal to zero, we can fastpath and say that this box intersects + { // because we know at least one point is outside and one is inside. + result = ContainmentType.Intersects; + return; + } + + // If we get here, it means the first (and only) point we checked was actually contained in the frustum. + // So we assume that all other points will also be contained. If one of the points is disjoint, we can + // exit immediately saying that the result is Intersects + i++; + for (; i < corners.Length; i++) + { + this.Contains(ref corners[i], out contained); + if (contained != ContainmentType.Contains) + { + result = ContainmentType.Intersects; + return; + } + } + + // If we get here, then we know all the points were actually contained, therefore result is Contains + result = ContainmentType.Contains; + return; + } + + // TODO: Implement this + public ContainmentType Contains(BoundingFrustum frustum) + { + if (this == frustum) // We check to see if the two frustums are equal + return ContainmentType.Contains;// If they are, there's no need to go any further. + + throw new NotImplementedException(); + } + + public ContainmentType Contains(BoundingSphere sphere) + { + ContainmentType result; + this.Contains(ref sphere, out result); + return result; + } + + public void Contains(ref BoundingSphere sphere, out ContainmentType result) + { + float val; + ContainmentType contained; + + // We first check if the sphere is inside the frustum + this.Contains(ref sphere.Center, out contained); + + // The sphere is inside. Now we need to check if it's fully contained or not + // So we see if the perpendicular distance to each plane is less than or equal to the sphere's radius. + // If the perpendicular distance is less, just return Intersects. + if (contained == ContainmentType.Contains) + { + val = PlaneHelper.PerpendicularDistance(ref sphere.Center, ref this.bottom); + if (val < sphere.Radius) + { + result = ContainmentType.Intersects; + return; + } + + val = PlaneHelper.PerpendicularDistance(ref sphere.Center, ref this.far); + if (val < sphere.Radius) + { + result = ContainmentType.Intersects; + return; + } + + val = PlaneHelper.PerpendicularDistance(ref sphere.Center, ref this.left); + if (val < sphere.Radius) + { + result = ContainmentType.Intersects; + return; + } + + val = PlaneHelper.PerpendicularDistance(ref sphere.Center, ref this.near); + if (val < sphere.Radius) + { + result = ContainmentType.Intersects; + return; + } + + val = PlaneHelper.PerpendicularDistance(ref sphere.Center, ref this.right); + if (val < sphere.Radius) + { + result = ContainmentType.Intersects; + return; + } + + val = PlaneHelper.PerpendicularDistance(ref sphere.Center, ref this.top); + if (val < sphere.Radius) + { + result = ContainmentType.Intersects; + return; + } + + // If we get here, the sphere is fully contained + result = ContainmentType.Contains; + return; + } + //duff idea : test if all corner is in same side of a plane if yes and outside it is disjoint else intersect + // issue is that we can have some times when really close aabb + + + + // If we're here, the the sphere's centre was outside of the frustum. This makes things hard :( + // We can't use perpendicular distance anymore. I'm not sure how to code this. + throw new NotImplementedException(); + } + + public ContainmentType Contains(Vector3 point) + { + ContainmentType result; + this.Contains(ref point, out result); + return result; + } + + public void Contains(ref Vector3 point, out ContainmentType result) + { + float val; + // If a point is on the POSITIVE side of the plane, then the point is not contained within the frustum + + // Check the top + val = PlaneHelper.ClassifyPoint(ref point, ref this.top); + if (val > 0) + { + result = ContainmentType.Disjoint; + return; + } + + // Check the bottom + val = PlaneHelper.ClassifyPoint(ref point, ref this.bottom); + if (val > 0) + { + result = ContainmentType.Disjoint; + return; + } + + // Check the left + val = PlaneHelper.ClassifyPoint(ref point, ref this.left); + if (val > 0) + { + result = ContainmentType.Disjoint; + return; + } + + // Check the right + val = PlaneHelper.ClassifyPoint(ref point, ref this.right); + if (val > 0) + { + result = ContainmentType.Disjoint; + return; + } + + // Check the near + val = PlaneHelper.ClassifyPoint(ref point, ref this.near); + if (val > 0) + { + result = ContainmentType.Disjoint; + return; + } + + // Check the far + val = PlaneHelper.ClassifyPoint(ref point, ref this.far); + if (val > 0) + { + result = ContainmentType.Disjoint; + return; + } + + // If we get here, it means that the point was on the correct side of each plane to be + // contained. Therefore this point is contained + result = ContainmentType.Contains; + } + + public bool Equals(BoundingFrustum other) + { + return (this == other); + } + + public override bool Equals(object obj) + { + BoundingFrustum f = obj as BoundingFrustum; + return (object.Equals(f, null)) ? false : (this == f); + } + + public Vector3[] GetCorners() + { + return corners; + } + + public override int GetHashCode() + { + return this.matrix.GetHashCode(); + } + + public bool Intersects(BoundingBox box) + { + throw new NotImplementedException(); + } + + public void Intersects(ref BoundingBox box, out bool result) + { + throw new NotImplementedException(); + } + + public bool Intersects(BoundingFrustum frustum) + { + throw new NotImplementedException(); + } + + public bool Intersects(BoundingSphere sphere) + { + throw new NotImplementedException(); + } + + public void Intersects(ref BoundingSphere sphere, out bool result) + { + throw new NotImplementedException(); + } + + public PlaneIntersectionType Intersects(Plane plane) + { + throw new NotImplementedException(); + } + + public void Intersects(ref Plane plane, out PlaneIntersectionType result) + { + throw new NotImplementedException(); + } + + public Nullable Intersects(Ray ray) + { + throw new NotImplementedException(); + } + + public void Intersects(ref Ray ray, out Nullable result) + { + throw new NotImplementedException(); + } + + public override string ToString() + { + StringBuilder sb = new StringBuilder(256); + sb.Append("{Near:"); + sb.Append(this.near.ToString()); + sb.Append(" Far:"); + sb.Append(this.far.ToString()); + sb.Append(" Left:"); + sb.Append(this.left.ToString()); + sb.Append(" Right:"); + sb.Append(this.right.ToString()); + sb.Append(" Top:"); + sb.Append(this.top.ToString()); + sb.Append(" Bottom:"); + sb.Append(this.bottom.ToString()); + sb.Append("}"); + return sb.ToString(); + } + + #endregion Public Methods + + + #region Private Methods + + private void CreateCorners() + { + this.corners = new Vector3[8]; + this.corners[0] = IntersectionPoint(ref this.near, ref this.left, ref this.top); + this.corners[1] = IntersectionPoint(ref this.near, ref this.right, ref this.top); + this.corners[2] = IntersectionPoint(ref this.near, ref this.right, ref this.bottom); + this.corners[3] = IntersectionPoint(ref this.near, ref this.left, ref this.bottom); + this.corners[4] = IntersectionPoint(ref this.far, ref this.left, ref this.top); + this.corners[5] = IntersectionPoint(ref this.far, ref this.right, ref this.top); + this.corners[6] = IntersectionPoint(ref this.far, ref this.right, ref this.bottom); + this.corners[7] = IntersectionPoint(ref this.far, ref this.left, ref this.bottom); + } + + private void CreatePlanes() + { + // Pre-calculate the different planes needed + this.left = new Plane(-this.matrix.M14 - this.matrix.M11, -this.matrix.M24 - this.matrix.M21, + -this.matrix.M34 - this.matrix.M31, -this.matrix.M44 - this.matrix.M41); + + this.right = new Plane(this.matrix.M11 - this.matrix.M14, this.matrix.M21 - this.matrix.M24, + this.matrix.M31 - this.matrix.M34, this.matrix.M41 - this.matrix.M44); + + this.top = new Plane(this.matrix.M12 - this.matrix.M14, this.matrix.M22 - this.matrix.M24, + this.matrix.M32 - this.matrix.M34, this.matrix.M42 - this.matrix.M44); + + this.bottom = new Plane(-this.matrix.M14 - this.matrix.M12, -this.matrix.M24 - this.matrix.M22, + -this.matrix.M34 - this.matrix.M32, -this.matrix.M44 - this.matrix.M42); + + this.near = new Plane(-this.matrix.M13, -this.matrix.M23, -this.matrix.M33, -this.matrix.M43); + + + this.far = new Plane(this.matrix.M13 - this.matrix.M14, this.matrix.M23 - this.matrix.M24, + this.matrix.M33 - this.matrix.M34, this.matrix.M43 - this.matrix.M44); + + this.NormalizePlane(ref this.left); + this.NormalizePlane(ref this.right); + this.NormalizePlane(ref this.top); + this.NormalizePlane(ref this.bottom); + this.NormalizePlane(ref this.near); + this.NormalizePlane(ref this.far); + } + + private static Vector3 IntersectionPoint(ref Plane a, ref Plane b, ref Plane c) + { + // Formula used + // d1 ( N2 * N3 ) + d2 ( N3 * N1 ) + d3 ( N1 * N2 ) + //P = ------------------------------------------------------------------------- + // N1 . ( N2 * N3 ) + // + // Note: N refers to the normal, d refers to the displacement. '.' means dot product. '*' means cross product + + Vector3 v1, v2, v3; + float f = -Vector3.Dot(a.Normal, Vector3.Cross(b.Normal, c.Normal)); + + v1 = (a.D * (Vector3.Cross(b.Normal, c.Normal))); + v2 = (b.D * (Vector3.Cross(c.Normal, a.Normal))); + v3 = (c.D * (Vector3.Cross(a.Normal, b.Normal))); + + Vector3 vec = new Vector3(v1.X + v2.X + v3.X, v1.Y + v2.Y + v3.Y, v1.Z + v2.Z + v3.Z); + return vec / f; + } + + private void NormalizePlane(ref Plane p) + { + float factor = 1f / p.Normal.Length(); + p.Normal.X *= factor; + p.Normal.Y *= factor; + p.Normal.Z *= factor; + p.D *= factor; + } + + #endregion + } +} diff --git a/Bizware/BizHawk.Bizware.BizwareGL/Types/BoundingSphere.cs b/Bizware/BizHawk.Bizware.BizwareGL/Types/BoundingSphere.cs new file mode 100644 index 0000000000..b1895286db --- /dev/null +++ b/Bizware/BizHawk.Bizware.BizwareGL/Types/BoundingSphere.cs @@ -0,0 +1,363 @@ +#region License +/* +MIT License +Copyright © 2006 The Mono.Xna Team + +All rights reserved. + +Authors: +Olivier Dufour (Duff) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +#endregion License + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.ComponentModel; + +namespace BizHawk.Bizware.BizwareGL +{ + public struct BoundingSphere : IEquatable + { + #region Public Fields + + public Vector3 Center; + public float Radius; + + #endregion Public Fields + + + #region Constructors + + public BoundingSphere(Vector3 center, float radius) + { + this.Center = center; + this.Radius = radius; + } + + #endregion Constructors + + + #region Public Methods + + public BoundingSphere Transform(Matrix matrix) + { + BoundingSphere sphere = new BoundingSphere(); + sphere.Center = Vector3.Transform(this.Center, matrix); + sphere.Radius = this.Radius * ((float)Math.Sqrt((double)Math.Max(((matrix.M11 * matrix.M11) + (matrix.M12 * matrix.M12)) + (matrix.M13 * matrix.M13), Math.Max(((matrix.M21 * matrix.M21) + (matrix.M22 * matrix.M22)) + (matrix.M23 * matrix.M23), ((matrix.M31 * matrix.M31) + (matrix.M32 * matrix.M32)) + (matrix.M33 * matrix.M33))))); + return sphere; + } + + public void Transform(ref Matrix matrix, out BoundingSphere result) + { + result.Center = Vector3.Transform(this.Center, matrix); + result.Radius = this.Radius * ((float)Math.Sqrt((double)Math.Max(((matrix.M11 * matrix.M11) + (matrix.M12 * matrix.M12)) + (matrix.M13 * matrix.M13), Math.Max(((matrix.M21 * matrix.M21) + (matrix.M22 * matrix.M22)) + (matrix.M23 * matrix.M23), ((matrix.M31 * matrix.M31) + (matrix.M32 * matrix.M32)) + (matrix.M33 * matrix.M33))))); + } + + public ContainmentType Contains(BoundingBox box) + { + //check if all corner is in sphere + bool inside = true; + foreach (Vector3 corner in box.GetCorners()) + { + if (this.Contains(corner) == ContainmentType.Disjoint) + { + inside = false; + break; + } + } + + if (inside) + return ContainmentType.Contains; + + //check if the distance from sphere center to cube face < radius + double dmin = 0; + + if (Center.X < box.Min.X) + dmin += (Center.X - box.Min.X) * (Center.X - box.Min.X); + + else if (Center.X > box.Max.X) + dmin += (Center.X - box.Max.X) * (Center.X - box.Max.X); + + if (Center.Y < box.Min.Y) + dmin += (Center.Y - box.Min.Y) * (Center.Y - box.Min.Y); + + else if (Center.Y > box.Max.Y) + dmin += (Center.Y - box.Max.Y) * (Center.Y - box.Max.Y); + + if (Center.Z < box.Min.Z) + dmin += (Center.Z - box.Min.Z) * (Center.Z - box.Min.Z); + + else if (Center.Z > box.Max.Z) + dmin += (Center.Z - box.Max.Z) * (Center.Z - box.Max.Z); + + if (dmin <= Radius * Radius) + return ContainmentType.Intersects; + + //else disjoint + return ContainmentType.Disjoint; + + } + + public void Contains(ref BoundingBox box, out ContainmentType result) + { + result = this.Contains(box); + } + + public ContainmentType Contains(BoundingFrustum frustum) + { + //check if all corner is in sphere + bool inside = true; + + Vector3[] corners = frustum.GetCorners(); + foreach (Vector3 corner in corners) + { + if (this.Contains(corner) == ContainmentType.Disjoint) + { + inside = false; + break; + } + } + if (inside) + return ContainmentType.Contains; + + //check if the distance from sphere center to frustrum face < radius + double dmin = 0; + //TODO : calcul dmin + + if (dmin <= Radius * Radius) + return ContainmentType.Intersects; + + //else disjoint + return ContainmentType.Disjoint; + } + + public ContainmentType Contains(BoundingSphere sphere) + { + float val = Vector3.Distance(sphere.Center, Center); + + if (val > sphere.Radius + Radius) + return ContainmentType.Disjoint; + + else if (val <= Radius - sphere.Radius) + return ContainmentType.Contains; + + else + return ContainmentType.Intersects; + } + + public void Contains(ref BoundingSphere sphere, out ContainmentType result) + { + result = Contains(sphere); + } + + public ContainmentType Contains(Vector3 point) + { + float distance = Vector3.Distance(point, Center); + + if (distance > this.Radius) + return ContainmentType.Disjoint; + + else if (distance < this.Radius) + return ContainmentType.Contains; + + return ContainmentType.Intersects; + } + + public void Contains(ref Vector3 point, out ContainmentType result) + { + result = Contains(point); + } + + public static BoundingSphere CreateFromBoundingBox(BoundingBox box) + { + // Find the center of the box. + Vector3 center = new Vector3((box.Min.X + box.Max.X) / 2.0f, + (box.Min.Y + box.Max.Y) / 2.0f, + (box.Min.Z + box.Max.Z) / 2.0f); + + // Find the distance between the center and one of the corners of the box. + float radius = Vector3.Distance(center, box.Max); + + return new BoundingSphere(center, radius); + } + + public static void CreateFromBoundingBox(ref BoundingBox box, out BoundingSphere result) + { + result = CreateFromBoundingBox(box); + } + + public static BoundingSphere CreateFromFrustum(BoundingFrustum frustum) + { + return BoundingSphere.CreateFromPoints(frustum.GetCorners()); + } + + public static BoundingSphere CreateFromPoints(IEnumerable points) + { + if (points == null) + throw new ArgumentNullException("points"); + + float radius = 0; + Vector3 center = new Vector3(); + // First, we'll find the center of gravity for the point 'cloud'. + int num_points = 0; // The number of points (there MUST be a better way to get this instead of counting the number of points one by one?) + + foreach (Vector3 v in points) + { + center += v; // If we actually knew the number of points, we'd get better accuracy by adding v / num_points. + ++num_points; + } + + center /= (float)num_points; + + // Calculate the radius of the needed sphere (it equals the distance between the center and the point further away). + foreach (Vector3 v in points) + { + float distance = ((Vector3)(v - center)).Length(); + + if (distance > radius) + radius = distance; + } + + return new BoundingSphere(center, radius); + } + + public static BoundingSphere CreateMerged(BoundingSphere original, BoundingSphere additional) + { + Vector3 ocenterToaCenter = Vector3.Subtract(additional.Center, original.Center); + float distance = ocenterToaCenter.Length(); + if (distance <= original.Radius + additional.Radius)//intersect + { + if (distance <= original.Radius - additional.Radius)//original contain additional + return original; + if (distance <= additional.Radius - original.Radius)//additional contain original + return additional; + } + + //else find center of new sphere and radius + float leftRadius = Math.Max(original.Radius - distance, additional.Radius); + float Rightradius = Math.Max(original.Radius + distance, additional.Radius); + ocenterToaCenter = ocenterToaCenter + (((leftRadius - Rightradius) / (2 * ocenterToaCenter.Length())) * ocenterToaCenter);//oCenterToResultCenter + + BoundingSphere result = new BoundingSphere(); + result.Center = original.Center + ocenterToaCenter; + result.Radius = (leftRadius + Rightradius) / 2; + return result; + } + + public static void CreateMerged(ref BoundingSphere original, ref BoundingSphere additional, out BoundingSphere result) + { + result = BoundingSphere.CreateMerged(original, additional); + } + + public bool Equals(BoundingSphere other) + { + return this.Center == other.Center && this.Radius == other.Radius; + } + + public override bool Equals(object obj) + { + if (obj is BoundingSphere) + return this.Equals((BoundingSphere)obj); + + return false; + } + + public override int GetHashCode() + { + return this.Center.GetHashCode() + this.Radius.GetHashCode(); + } + + public bool Intersects(BoundingBox box) + { + return box.Intersects(this); + } + + public void Intersects(ref BoundingBox box, out bool result) + { + result = Intersects(box); + } + + public bool Intersects(BoundingFrustum frustum) + { + if (frustum == null) + throw new NullReferenceException(); + + throw new NotImplementedException(); + } + + public bool Intersects(BoundingSphere sphere) + { + float val = Vector3.Distance(sphere.Center, Center); + if (val > sphere.Radius + Radius) + return false; + return true; + } + + public void Intersects(ref BoundingSphere sphere, out bool result) + { + result = Intersects(sphere); + } + + public PlaneIntersectionType Intersects(Plane plane) + { + float distance = Vector3.Dot(plane.Normal, this.Center) + plane.D; + if (distance > this.Radius) + return PlaneIntersectionType.Front; + if (distance < -this.Radius) + return PlaneIntersectionType.Back; + //else it intersect + return PlaneIntersectionType.Intersecting; + } + + public void Intersects(ref Plane plane, out PlaneIntersectionType result) + { + result = Intersects(plane); + } + + public Nullable Intersects(Ray ray) + { + return ray.Intersects(this); + } + + public void Intersects(ref Ray ray, out Nullable result) + { + result = Intersects(ray); + } + + public static bool operator == (BoundingSphere a, BoundingSphere b) + { + return a.Equals(b); + } + + public static bool operator != (BoundingSphere a, BoundingSphere b) + { + return !a.Equals(b); + } + + public override string ToString() + { + return string.Format(CultureInfo.CurrentCulture, "{{Center:{0} Radius:{1}}}", this.Center.ToString(), this.Radius.ToString()); + } + + #endregion Public Methods + } +} diff --git a/Bizware/BizHawk.Bizware.BizwareGL/Types/Enums.cs b/Bizware/BizHawk.Bizware.BizwareGL/Types/Enums.cs new file mode 100644 index 0000000000..493e3abf00 --- /dev/null +++ b/Bizware/BizHawk.Bizware.BizwareGL/Types/Enums.cs @@ -0,0 +1,76 @@ +#region License +/* +MIT License +Copyright © 2006 - 2007 The Mono.Xna Team + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +#endregion License + +using System; + +namespace BizHawk.Bizware.BizwareGL +{ + + public enum CurveLoopType + { + Constant, + Cycle, + CycleOffset, + Oscillate, + Linear + } + + public enum CurveContinuity + { + Smooth, + Step + } + + public enum CurveTangent + { + Flat, + Linear, + Smooth + } + + public enum TargetPlatform + { + Unknown, + Windows, + Xbox360, + Zune + } + + public enum PlaneIntersectionType + { + Front, + Back, + Intersecting + } + + public enum ContainmentType + { + Disjoint, + Contains, + Intersects + } +} diff --git a/Bizware/BizHawk.Bizware.BizwareGL/Types/MathHelper.cs b/Bizware/BizHawk.Bizware.BizwareGL/Types/MathHelper.cs new file mode 100644 index 0000000000..2b5022292d --- /dev/null +++ b/Bizware/BizHawk.Bizware.BizwareGL/Types/MathHelper.cs @@ -0,0 +1,150 @@ +#region License +/* +MIT License +Copyright © 2006 The Mono.Xna Team + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +#endregion License + +using System; +using System.Collections.Generic; +using System.Text; + +namespace BizHawk.Bizware.BizwareGL +{ + public static class MathHelper + { + public const float E = (float)Math.E; + public const float Log10E = 0.4342945f; + public const float Log2E = 1.442695f; + public const float Pi = (float)Math.PI; + public const float PiOver2 = (float)(Math.PI / 2.0); + public const float PiOver4 = (float)(Math.PI / 4.0); + public const float TwoPi = (float)(Math.PI * 2.0); + + public static float Barycentric(float value1, float value2, float value3, float amount1, float amount2) + { + return value1 + (value2 - value1) * amount1 + (value3 - value1) * amount2; + } + + public static float CatmullRom(float value1, float value2, float value3, float value4, float amount) + { + // Using formula from http://www.mvps.org/directx/articles/catmull/ + // Internally using doubles not to lose precission + double amountSquared = amount * amount; + double amountCubed = amountSquared * amount; + return (float)(0.5f * (2.0f * value2 + + (value3 - value1) * amount + + (2.0f * value1 - 5.0f * value2 + 4.0f * value3 - value4) * amountSquared + + (3.0f * value2 - value1 - 3.0f * value3 + value4) * amountCubed)); + } + + public static float Clamp(float value, float min, float max) + { + return value > max ? max : (value < min ? min : value); + } + + public static float Distance(float value1, float value2) + { + return Math.Abs(value1 - value2); + } + + public static float Hermite(float value1, float tangent1, float value2, float tangent2, float amount) + { + // All transformed to double not to lose precission + // Otherwise, for high numbers of param:amount the result is NaN instead of Infinity + double v1 = value1, v2 = value2, t1 = tangent1, t2 = tangent2, s = amount, result; + double sCubed = s * s * s; + double sSquared = s * s; + + if (amount == 0f) + result = value1; + else if (amount == 1f) + result = value2; + else + result = (2.0f * v1 - 2.0f * v2 + t2 + t1) * sCubed + + (3.0f * v2 - 3.0f * v1 - 2.0f * t1 - t2) * sSquared + + t1 * s + + v1; + return (float)result; + } + + + public static float Lerp(float value1, float value2, float amount) + { + return value1 + (value2 - value1) * amount; + } + + public static float Max(float value1, float value2) + { + return Math.Max(value1, value2); + } + + public static float Min(float value1, float value2) + { + return Math.Min(value1, value2); + } + + public static float SmoothStep(float value1, float value2, float amount) + { + // It is expected that 0 < amount < 1 + // If amount < 0, return value1 + // If amount > 1, return value2 + float result = MathHelper.Clamp(amount, 0f, 1f); + result = MathHelper.Hermite(value1, 0f, value2, 0f, result); + return result; + } + + public static float ToDegrees(float radians) + { + // This method uses double precission internally, + // though it returns single float + // Factor = 180 / pi + return (float)(radians * 57.295779513082320876798154814105); + } + + public static float ToRadians(float degrees) + { + // This method uses double precission internally, + // though it returns single float + // Factor = pi / 180 + return (float)(degrees * 0.017453292519943295769236907684886); + } + + + public static float WrapAngle(float angle) + { + angle = (float)Math.IEEERemainder((double)angle, 6.2831854820251465); //2xPi precission is double + if (angle <= -3.141593f) + { + angle += 6.283185f; + return angle; + } + if (angle > 3.141593f) + { + angle -= 6.283185f; + } + return angle; + } + + } +} diff --git a/Bizware/BizHawk.Bizware.BizwareGL/Types/Matrix.cs b/Bizware/BizHawk.Bizware.BizwareGL/Types/Matrix.cs new file mode 100644 index 0000000000..9238029847 --- /dev/null +++ b/Bizware/BizHawk.Bizware.BizwareGL/Types/Matrix.cs @@ -0,0 +1,1515 @@ +#region License +/* +MIT License +Copyright © 2006 The Mono.Xna Team + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +#endregion License + +using System; +using System.ComponentModel; +using System.Runtime.InteropServices; + +namespace BizHawk.Bizware.BizwareGL +{ + [Serializable] + [StructLayout(LayoutKind.Sequential)] + + public struct Matrix : IEquatable + { + #region Public Fields + + public float M11; + public float M12; + public float M13; + public float M14; + public float M21; + public float M22; + public float M23; + public float M24; + public float M31; + public float M32; + public float M33; + public float M34; + public float M41; + public float M42; + public float M43; + public float M44; + + #endregion Public Fields + + + #region Static Properties + + private static Matrix identity = new Matrix(1f, 0f, 0f, 0f, 0f, 1f, 0f, 0f, 0f, 0f, 1f, 0f, 0f, 0f, 0f, 1f); + public static Matrix Identity { + get { return identity; } + } + + #endregion Static Properties + + + #region Public Properties + + public Vector3 Backward { + get { return new Vector3(this.M31, this.M32, this.M33); } + set { + this.M31 = value.X; + this.M32 = value.Y; + this.M33 = value.Z; + } + } + + public Vector3 Down { + get { return new Vector3(-this.M21, -this.M22, -this.M23); } + set { + this.M21 = -value.X; + this.M22 = -value.Y; + this.M23 = -value.Z; + } + } + + public Vector3 Forward { + get { return new Vector3(-this.M31, -this.M32, -this.M33); } + set { + this.M31 = -value.X; + this.M32 = -value.Y; + this.M33 = -value.Z; + } + } + + public Vector3 Left { + get { return new Vector3(-this.M11, -this.M12, -this.M13); } + set { + this.M11 = -value.X; + this.M12 = -value.Y; + this.M13 = -value.Z; + } + } + + public Vector3 Right { + get { return new Vector3(this.M11, this.M12, this.M13); } + set { + this.M11 = value.X; + this.M12 = value.Y; + this.M13 = value.Z; + } + } + + public Vector3 Translation { + get { return new Vector3(this.M41, this.M42, this.M43); } + set { + this.M41 = value.X; + this.M42 = value.Y; + this.M43 = value.Z; + } + } + + public Vector3 Up { + get { return new Vector3(this.M21, this.M22, this.M23); } + set { + this.M21 = value.X; + this.M22 = value.Y; + this.M23 = value.Z; + } + } + + #endregion Public Properties + + + #region Constructors + /// + /// Constructor for 4x4 Matrix + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + public Matrix(float m11, float m12, float m13, float m14, float m21, float m22, float m23, float m24, + float m31, float m32, float m33, float m34, float m41, float m42, float m43, float m44) + { + this.M11 = m11; + this.M12 = m12; + this.M13 = m13; + this.M14 = m14; + this.M21 = m21; + this.M22 = m22; + this.M23 = m23; + this.M24 = m24; + this.M31 = m31; + this.M32 = m32; + this.M33 = m33; + this.M34 = m34; + this.M41 = m41; + this.M42 = m42; + this.M43 = m43; + this.M44 = m44; + } + + #endregion Constructors + + #region Public Static Methods + + public static Matrix CreateWorld(Vector3 position, Vector3 forward, Vector3 up) + { + Matrix ret; + CreateWorld(ref position, ref forward, ref up, out ret); + return ret; + } + + public static void CreateWorld(ref Vector3 position, ref Vector3 forward, ref Vector3 up, out Matrix result) + { + Vector3 x, y, z; + Vector3.Normalize(ref forward, out z); + Vector3.Cross(ref forward, ref up, out x); + Vector3.Cross(ref x, ref forward, out y); + x.Normalize(); + y.Normalize(); + + result = new Matrix(); + result.Right = x; + result.Up = y; + result.Forward = z; + result.Translation = position; + result.M44 = 1f; + } + + public static Matrix CreateShadow(Vector3 lightDirection, Plane plane) + { + Matrix ret; + CreateShadow(ref lightDirection, ref plane, out ret); + return ret; + } + + public static void CreateShadow(ref Vector3 lightDirection, ref Plane plane, out Matrix result) + { + // Formula: + // http://msdn.microsoft.com/en-us/library/bb205364(v=VS.85).aspx + + Plane p = Plane.Normalize(plane); + float d = Vector3.Dot(p.Normal, lightDirection); + + result.M11 = -1 * p.Normal.X * lightDirection.X + d; + result.M12 = -1 * p.Normal.X * lightDirection.Y; + result.M13 = -1 * p.Normal.X * lightDirection.Z; + result.M14 = 0; + result.M21 = -1 * p.Normal.Y * lightDirection.X; + result.M22 = -1 * p.Normal.Y * lightDirection.Y + d; + result.M23 = -1 * p.Normal.Y * lightDirection.Z; + result.M24 = 0; + result.M31 = -1 * p.Normal.Z * lightDirection.X; + result.M32 = -1 * p.Normal.Z * lightDirection.Y; + result.M33 = -1 * p.Normal.Z * lightDirection.Z + d; + result.M34 = 0; + result.M41 = -1 * p.D * lightDirection.X; + result.M42 = -1 * p.D * lightDirection.Y; + result.M43 = -1 * p.D * lightDirection.Z; + result.M44 = d; + } + + public static void CreateReflection(ref Plane value, out Matrix result) + { + // Formula: + // http://msdn.microsoft.com/en-us/library/bb205356(v=VS.85).aspx + + Plane p = Plane.Normalize(value); + + result.M11 = -2 * p.Normal.X * p.Normal.X + 1; + result.M12 = -2 * p.Normal.X * p.Normal.Y; + result.M13 = -2 * p.Normal.X * p.Normal.Z; + result.M14 = 0; + result.M21 = -2 * p.Normal.Y * p.Normal.X; + result.M22 = -2 * p.Normal.Y * p.Normal.Y + 1; + result.M23 = -2 * p.Normal.Y * p.Normal.Z; + result.M24 = 0; + result.M31 = -2 * p.Normal.Z * p.Normal.X; + result.M32 = -2 * p.Normal.Z * p.Normal.Y; + result.M33 = -2 * p.Normal.Z * p.Normal.Z + 1; + result.M34 = 0; + result.M41 = -2 * p.D * p.Normal.X; + result.M42 = -2 * p.D * p.Normal.Y; + result.M43 = -2 * p.D * p.Normal.Z; + result.M44 = 1; + } + + public static Matrix CreateReflection(Plane value) + { + Matrix ret; + CreateReflection(ref value, out ret); + return ret; + } + + public static Matrix CreateFromYawPitchRoll(float yaw, float pitch, float roll) + { + Matrix matrix; + Quaternion quaternion; + Quaternion.CreateFromYawPitchRoll(yaw, pitch, roll, out quaternion); + CreateFromQuaternion(ref quaternion, out matrix); + return matrix; + } + + public static void CreateFromYawPitchRoll(float yaw, float pitch, float roll, out Matrix result) + { + Quaternion quaternion; + Quaternion.CreateFromYawPitchRoll(yaw, pitch, roll, out quaternion); + CreateFromQuaternion(ref quaternion, out result); + } + + public static void Transform(ref Matrix value, ref Quaternion rotation, out Matrix result) + { + Matrix matrix = CreateFromQuaternion(rotation); + Matrix.Multiply(ref value, ref matrix, out result); + } + + public static Matrix Transform(Matrix value, Quaternion rotation) + { + Matrix ret; + Transform(ref value, ref rotation, out ret); + return ret; + } + + public bool Decompose(out Vector3 scale, out Quaternion rotation, out Vector3 translation) + { + translation.X = this.M41; + translation.Y = this.M42; + translation.Z = this.M43; + float xs, ys, zs; + + if (Math.Sign(M11 * M12 * M13 * M14) < 0) + xs = -1f; + else + xs = 1f; + + if (Math.Sign(M21 * M22 * M23 * M24) < 0) + ys = -1f; + else + ys = 1f; + + if (Math.Sign(M31 * M32 * M33 * M34) < 0) + zs = -1f; + else + zs = 1f; + + scale.X = xs * (float)Math.Sqrt(this.M11 * this.M11 + this.M12 * this.M12 + this.M13 * this.M13); + scale.Y = ys * (float)Math.Sqrt(this.M21 * this.M21 + this.M22 * this.M22 + this.M23 * this.M23); + scale.Z = zs * (float)Math.Sqrt(this.M31 * this.M31 + this.M32 * this.M32 + this.M33 * this.M33); + + if (scale.X == 0.0 || scale.Y == 0.0 || scale.Z == 0.0) + { + rotation = Quaternion.Identity; + return false; + } + + Matrix m1 = new Matrix(this.M11/scale.X, M12/scale.X, M13/scale.X, 0, + this.M21/scale.Y, M22/scale.Y, M23/scale.Y, 0, + this.M31/scale.Z, M32/scale.Z, M33/scale.Z, 0, + 0, 0, 0, 1); + + rotation = Quaternion.CreateFromRotationMatrix(m1); + return true; + } + + /// + /// Adds second matrix to the first. + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + public static Matrix Add(Matrix matrix1, Matrix matrix2) + { + matrix1.M11 += matrix2.M11; + matrix1.M12 += matrix2.M12; + matrix1.M13 += matrix2.M13; + matrix1.M14 += matrix2.M14; + matrix1.M21 += matrix2.M21; + matrix1.M22 += matrix2.M22; + matrix1.M23 += matrix2.M23; + matrix1.M24 += matrix2.M24; + matrix1.M31 += matrix2.M31; + matrix1.M32 += matrix2.M32; + matrix1.M33 += matrix2.M33; + matrix1.M34 += matrix2.M34; + matrix1.M41 += matrix2.M41; + matrix1.M42 += matrix2.M42; + matrix1.M43 += matrix2.M43; + matrix1.M44 += matrix2.M44; + return matrix1; + } + + + /// + /// Adds two Matrix and save to the result Matrix + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + public static void Add(ref Matrix matrix1, ref Matrix matrix2, out Matrix result) + { + result.M11 = matrix1.M11 + matrix2.M11; + result.M12 = matrix1.M12 + matrix2.M12; + result.M13 = matrix1.M13 + matrix2.M13; + result.M14 = matrix1.M14 + matrix2.M14; + result.M21 = matrix1.M21 + matrix2.M21; + result.M22 = matrix1.M22 + matrix2.M22; + result.M23 = matrix1.M23 + matrix2.M23; + result.M24 = matrix1.M24 + matrix2.M24; + result.M31 = matrix1.M31 + matrix2.M31; + result.M32 = matrix1.M32 + matrix2.M32; + result.M33 = matrix1.M33 + matrix2.M33; + result.M34 = matrix1.M34 + matrix2.M34; + result.M41 = matrix1.M41 + matrix2.M41; + result.M42 = matrix1.M42 + matrix2.M42; + result.M43 = matrix1.M43 + matrix2.M43; + result.M44 = matrix1.M44 + matrix2.M44; + } + + + public static Matrix CreateBillboard(Vector3 objectPosition, Vector3 cameraPosition, + Vector3 cameraUpVector, Nullable cameraForwardVector) + { + Matrix ret; + CreateBillboard(ref objectPosition, ref cameraPosition, ref cameraUpVector, cameraForwardVector, out ret); + return ret; + } + + public static void CreateBillboard(ref Vector3 objectPosition, ref Vector3 cameraPosition, + ref Vector3 cameraUpVector, Vector3? cameraForwardVector, out Matrix result) + { + Vector3 translation = objectPosition - cameraPosition; + Vector3 backwards, right, up; + Vector3.Normalize(ref translation, out backwards); + Vector3.Normalize(ref cameraUpVector, out up); + Vector3.Cross(ref backwards, ref up, out right); + Vector3.Cross(ref backwards, ref right, out up); + result = Matrix.Identity; + result.Backward = backwards; + result.Right = right; + result.Up = up; + result.Translation = translation; + } + + public static Matrix CreateConstrainedBillboard(Vector3 objectPosition, Vector3 cameraPosition, + Vector3 rotateAxis, Nullable cameraForwardVector, Nullable objectForwardVector) + { + throw new NotImplementedException(); + } + + + public static void CreateConstrainedBillboard(ref Vector3 objectPosition, ref Vector3 cameraPosition, + ref Vector3 rotateAxis, Vector3? cameraForwardVector, Vector3? objectForwardVector, out Matrix result) + { + throw new NotImplementedException(); + } + + + public static Matrix CreateFromAxisAngle(Vector3 axis, float angle) + { + throw new NotImplementedException(); + } + + + public static void CreateFromAxisAngle(ref Vector3 axis, float angle, out Matrix result) + { + throw new NotImplementedException(); + } + + + public static Matrix CreateFromQuaternion(Quaternion quaternion) + { + Matrix ret; + CreateFromQuaternion(ref quaternion, out ret); + return ret; + } + + + public static void CreateFromQuaternion(ref Quaternion quaternion, out Matrix result) + { + result = Matrix.Identity; + + result.M11 = 1 - 2 * (quaternion.Y * quaternion.Y + quaternion.Z * quaternion.Z); + result.M12 = 2 * (quaternion.X * quaternion.Y + quaternion.W * quaternion.Z); + result.M13 = 2 * (quaternion.X * quaternion.Z - quaternion.W * quaternion.Y); + result.M21 = 2 * (quaternion.X * quaternion.Y - quaternion.W * quaternion.Z); + result.M22 = 1 - 2 * (quaternion.X * quaternion.X + quaternion.Z * quaternion.Z); + result.M23 = 2 * (quaternion.Y * quaternion.Z + quaternion.W * quaternion.X); + result.M31 = 2 * (quaternion.X * quaternion.Z + quaternion.W * quaternion.Y); + result.M32 = 2 * (quaternion.Y * quaternion.Z - quaternion.W * quaternion.X); + result.M33 = 1 - 2 * (quaternion.X * quaternion.X + quaternion.Y * quaternion.Y); + } + + + public static Matrix CreateLookAt(Vector3 cameraPosition, Vector3 cameraTarget, Vector3 cameraUpVector) + { + Matrix ret; + CreateLookAt(ref cameraPosition, ref cameraTarget, ref cameraUpVector, out ret); + return ret; + } + + + public static void CreateLookAt(ref Vector3 cameraPosition, ref Vector3 cameraTarget, ref Vector3 cameraUpVector, out Matrix result) + { + // http://msdn.microsoft.com/en-us/library/bb205343(v=VS.85).aspx + + Vector3 vz = Vector3.Normalize(cameraPosition - cameraTarget); + Vector3 vx = Vector3.Normalize(Vector3.Cross(cameraUpVector, vz)); + Vector3 vy = Vector3.Cross(vz, vx); + result = Matrix.Identity; + result.M11 = vx.X; + result.M12 = vy.X; + result.M13 = vz.X; + result.M21 = vx.Y; + result.M22 = vy.Y; + result.M23 = vz.Y; + result.M31 = vx.Z; + result.M32 = vy.Z; + result.M33 = vz.Z; + result.M41 = -Vector3.Dot(vx, cameraPosition); + result.M42 = -Vector3.Dot(vy, cameraPosition); + result.M43 = -Vector3.Dot(vz, cameraPosition); + } + + public static Matrix CreateOrthographic(float width, float height, float zNearPlane, float zFarPlane) + { + Matrix ret; + CreateOrthographic(width, height, zNearPlane, zFarPlane, out ret); + return ret; + } + + + public static void CreateOrthographic(float width, float height, float zNearPlane, float zFarPlane, out Matrix result) + { + result.M11 = 2 / width; + result.M12 = 0; + result.M13 = 0; + result.M14 = 0; + result.M21 = 0; + result.M22 = 2 / height; + result.M23 = 0; + result.M24 = 0; + result.M31 = 0; + result.M32 = 0; + result.M33 = 1 / (zNearPlane - zFarPlane); + result.M34 = 0; + result.M41 = 0; + result.M42 = 0; + result.M43 = zNearPlane / (zNearPlane - zFarPlane); + result.M44 = 1; + } + + + public static Matrix CreateOrthographicOffCenter(float left, float right, float bottom, float top, float zNearPlane, float zFarPlane) + { + Matrix ret; + CreateOrthographicOffCenter(left, right, bottom, top, zNearPlane, zFarPlane, out ret); + return ret; + } + + + public static void CreateOrthographicOffCenter(float left, float right, float bottom, float top, + float zNearPlane, float zFarPlane, out Matrix result) + { + result.M11 = 2 / (right - left); + result.M12 = 0; + result.M13 = 0; + result.M14 = 0; + result.M21 = 0; + result.M22 = 2 / (top - bottom); + result.M23 = 0; + result.M24 = 0; + result.M31 = 0; + result.M32 = 0; + result.M33 = 1 / (zNearPlane - zFarPlane); + result.M34 = 0; + result.M41 = (left + right) / (left - right); + result.M42 = (bottom + top) / (bottom - top); + result.M43 = zNearPlane / (zNearPlane - zFarPlane); + result.M44 = 1; + } + + + public static Matrix CreatePerspective(float width, float height, float zNearPlane, float zFarPlane) + { + throw new NotImplementedException(); + } + + + public static void CreatePerspective(float width, float height, float zNearPlane, float zFarPlane, out Matrix result) + { + throw new NotImplementedException(); + } + + + public static Matrix CreatePerspectiveFieldOfView(float fieldOfView, float aspectRatio, float nearPlaneDistance, float farPlaneDistance) + { + Matrix ret; + CreatePerspectiveFieldOfView(fieldOfView, aspectRatio, nearPlaneDistance, farPlaneDistance, out ret); + return ret; + } + + + public static void CreatePerspectiveFieldOfView(float fieldOfView, float aspectRatio, float nearPlaneDistance, float farPlaneDistance, out Matrix result) + { + // http://msdn.microsoft.com/en-us/library/bb205351(v=VS.85).aspx + // http://msdn.microsoft.com/en-us/library/bb195665.aspx + + result = new Matrix(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + + if (fieldOfView < 0 || fieldOfView > 3.14159262f) + throw new ArgumentOutOfRangeException("fieldOfView", "fieldOfView takes a value between 0 and Pi (180 degrees) in radians."); + + if (nearPlaneDistance <= 0.0f) + throw new ArgumentOutOfRangeException("nearPlaneDistance", "You should specify positive value for nearPlaneDistance."); + + if (farPlaneDistance <= 0.0f) + throw new ArgumentOutOfRangeException("farPlaneDistance", "You should specify positive value for farPlaneDistance."); + + if (farPlaneDistance <= nearPlaneDistance) + throw new ArgumentOutOfRangeException("nearPlaneDistance", "Near plane distance is larger than Far plane distance. Near plane distance must be smaller than Far plane distance."); + + float yscale = (float)1 / (float)Math.Tan(fieldOfView / 2); + float xscale = yscale / aspectRatio; + + result.M11 = xscale; + result.M22 = yscale; + result.M33 = farPlaneDistance / (nearPlaneDistance - farPlaneDistance); + result.M34 = -1; + result.M43 = nearPlaneDistance * farPlaneDistance / (nearPlaneDistance - farPlaneDistance); + } + + + public static Matrix CreatePerspectiveOffCenter(float left, float right, float bottom, float top, float zNearPlane, float zFarPlane) + { + throw new NotImplementedException(); + } + + + public static void CreatePerspectiveOffCenter(float left, float right, float bottom, float top, float nearPlaneDistance, float farPlaneDistance, out Matrix result) + { + throw new NotImplementedException(); + } + + + public static Matrix CreateRotationX(float radians) + { + Matrix returnMatrix = Matrix.Identity; + + returnMatrix.M22 = (float)Math.Cos(radians); + returnMatrix.M23 = (float)Math.Sin(radians); + returnMatrix.M32 = -returnMatrix.M23; + returnMatrix.M33 = returnMatrix.M22; + + return returnMatrix; + } + + + public static void CreateRotationX(float radians, out Matrix result) + { + result = Matrix.Identity; + + result.M22 = (float)Math.Cos(radians); + result.M23 = (float)Math.Sin(radians); + result.M32 = -result.M23; + result.M33 = result.M22; + } + + + public static Matrix CreateRotationY(float radians) + { + Matrix returnMatrix = Matrix.Identity; + + returnMatrix.M11 = (float)Math.Cos(radians); + returnMatrix.M13 = (float)Math.Sin(radians); + returnMatrix.M31 = -returnMatrix.M13; + returnMatrix.M33 = returnMatrix.M11; + + return returnMatrix; + } + + + public static void CreateRotationY(float radians, out Matrix result) + { + result = Matrix.Identity; + + result.M11 = (float)Math.Cos(radians); + result.M13 = (float)Math.Sin(radians); + result.M31 = -result.M13; + result.M33 = result.M11; + } + + + public static Matrix CreateRotationZ(float radians) + { + Matrix returnMatrix = Matrix.Identity; + + returnMatrix.M11 = (float)Math.Cos(radians); + returnMatrix.M12 = (float)Math.Sin(radians); + returnMatrix.M21 = -returnMatrix.M12; + returnMatrix.M22 = returnMatrix.M11; + + return returnMatrix; + } + + + public static void CreateRotationZ(float radians, out Matrix result) + { + result = Matrix.Identity; + + result.M11 = (float)Math.Cos(radians); + result.M12 = (float)Math.Sin(radians); + result.M21 = -result.M12; + result.M22 = result.M11; + } + + + public static Matrix CreateScale(float scale) + { + Matrix returnMatrix = Matrix.Identity; + + returnMatrix.M11 = scale; + returnMatrix.M22 = scale; + returnMatrix.M33 = scale; + + return returnMatrix; + } + + + public static void CreateScale(float scale, out Matrix result) + { + result = Matrix.Identity; + + result.M11 = scale; + result.M22 = scale; + result.M33 = scale; + } + + + public static Matrix CreateScale(float xScale, float yScale, float zScale) + { + Matrix returnMatrix = Matrix.Identity; + + returnMatrix.M11 = xScale; + returnMatrix.M22 = yScale; + returnMatrix.M33 = zScale; + + return returnMatrix; + } + + + public static void CreateScale(float xScale, float yScale, float zScale, out Matrix result) + { + result = Matrix.Identity; + + result.M11 = xScale; + result.M22 = yScale; + result.M33 = zScale; + } + + + public static Matrix CreateScale(Vector3 scales) + { + Matrix returnMatrix = Matrix.Identity; + + returnMatrix.M11 = scales.X; + returnMatrix.M22 = scales.Y; + returnMatrix.M33 = scales.Z; + + return returnMatrix; + } + + + public static void CreateScale(ref Vector3 scales, out Matrix result) + { + result = Matrix.Identity; + + result.M11 = scales.X; + result.M22 = scales.Y; + result.M33 = scales.Z; + } + + + public static Matrix CreateTranslation(float xPosition, float yPosition, float zPosition) + { + Matrix returnMatrix = Matrix.Identity; + + returnMatrix.M41 = xPosition; + returnMatrix.M42 = yPosition; + returnMatrix.M43 = zPosition; + + return returnMatrix; + } + + + public static void CreateTranslation(float xPosition, float yPosition, float zPosition, out Matrix result) + { + result = Matrix.Identity; + + result.M41 = xPosition; + result.M42 = yPosition; + result.M43 = zPosition; + } + + + public static Matrix CreateTranslation(Vector3 position) + { + Matrix returnMatrix = Matrix.Identity; + + returnMatrix.M41 = position.X; + returnMatrix.M42 = position.Y; + returnMatrix.M43 = position.Z; + + return returnMatrix; + } + + + public static void CreateTranslation(ref Vector3 position, out Matrix result) + { + result = Matrix.Identity; + + result.M41 = position.X; + result.M42 = position.Y; + result.M43 = position.Z; + } + + public static Matrix Divide(Matrix matrix1, Matrix matrix2) + { + Matrix inverse = Matrix.Invert(matrix2), result; + + result.M11 = matrix1.M11 * inverse.M11 + matrix1.M12 * inverse.M21 + matrix1.M13 * inverse.M31 + matrix1.M14 * inverse.M41; + result.M12 = matrix1.M11 * inverse.M12 + matrix1.M12 * inverse.M22 + matrix1.M13 * inverse.M32 + matrix1.M14 * inverse.M42; + result.M13 = matrix1.M11 * inverse.M13 + matrix1.M12 * inverse.M23 + matrix1.M13 * inverse.M33 + matrix1.M14 * inverse.M43; + result.M14 = matrix1.M11 * inverse.M14 + matrix1.M12 * inverse.M24 + matrix1.M13 * inverse.M34 + matrix1.M14 * inverse.M44; + + result.M21 = matrix1.M21 * inverse.M11 + matrix1.M22 * inverse.M21 + matrix1.M23 * inverse.M31 + matrix1.M24 * inverse.M41; + result.M22 = matrix1.M21 * inverse.M12 + matrix1.M22 * inverse.M22 + matrix1.M23 * inverse.M32 + matrix1.M24 * inverse.M42; + result.M23 = matrix1.M21 * inverse.M13 + matrix1.M22 * inverse.M23 + matrix1.M23 * inverse.M33 + matrix1.M24 * inverse.M43; + result.M24 = matrix1.M21 * inverse.M14 + matrix1.M22 * inverse.M24 + matrix1.M23 * inverse.M34 + matrix1.M24 * inverse.M44; + + result.M31 = matrix1.M31 * inverse.M11 + matrix1.M32 * inverse.M21 + matrix1.M33 * inverse.M31 + matrix1.M34 * inverse.M41; + result.M32 = matrix1.M31 * inverse.M12 + matrix1.M32 * inverse.M22 + matrix1.M33 * inverse.M32 + matrix1.M34 * inverse.M42; + result.M33 = matrix1.M31 * inverse.M13 + matrix1.M32 * inverse.M23 + matrix1.M33 * inverse.M33 + matrix1.M34 * inverse.M43; + result.M34 = matrix1.M31 * inverse.M14 + matrix1.M32 * inverse.M24 + matrix1.M33 * inverse.M34 + matrix1.M34 * inverse.M44; + + result.M41 = matrix1.M41 * inverse.M11 + matrix1.M42 * inverse.M21 + matrix1.M43 * inverse.M31 + matrix1.M44 * inverse.M41; + result.M42 = matrix1.M41 * inverse.M12 + matrix1.M42 * inverse.M22 + matrix1.M43 * inverse.M32 + matrix1.M44 * inverse.M42; + result.M43 = matrix1.M41 * inverse.M13 + matrix1.M42 * inverse.M23 + matrix1.M43 * inverse.M33 + matrix1.M44 * inverse.M43; + result.M44 = matrix1.M41 * inverse.M14 + matrix1.M42 * inverse.M24 + matrix1.M43 * inverse.M34 + matrix1.M44 * inverse.M44; + + return result; + } + + + public static void Divide(ref Matrix matrix1, ref Matrix matrix2, out Matrix result) + { + Matrix inverse = Matrix.Invert(matrix2); + result.M11 = matrix1.M11 * inverse.M11 + matrix1.M12 * inverse.M21 + matrix1.M13 * inverse.M31 + matrix1.M14 * inverse.M41; + result.M12 = matrix1.M11 * inverse.M12 + matrix1.M12 * inverse.M22 + matrix1.M13 * inverse.M32 + matrix1.M14 * inverse.M42; + result.M13 = matrix1.M11 * inverse.M13 + matrix1.M12 * inverse.M23 + matrix1.M13 * inverse.M33 + matrix1.M14 * inverse.M43; + result.M14 = matrix1.M11 * inverse.M14 + matrix1.M12 * inverse.M24 + matrix1.M13 * inverse.M34 + matrix1.M14 * inverse.M44; + + result.M21 = matrix1.M21 * inverse.M11 + matrix1.M22 * inverse.M21 + matrix1.M23 * inverse.M31 + matrix1.M24 * inverse.M41; + result.M22 = matrix1.M21 * inverse.M12 + matrix1.M22 * inverse.M22 + matrix1.M23 * inverse.M32 + matrix1.M24 * inverse.M42; + result.M23 = matrix1.M21 * inverse.M13 + matrix1.M22 * inverse.M23 + matrix1.M23 * inverse.M33 + matrix1.M24 * inverse.M43; + result.M24 = matrix1.M21 * inverse.M14 + matrix1.M22 * inverse.M24 + matrix1.M23 * inverse.M34 + matrix1.M24 * inverse.M44; + + result.M31 = matrix1.M31 * inverse.M11 + matrix1.M32 * inverse.M21 + matrix1.M33 * inverse.M31 + matrix1.M34 * inverse.M41; + result.M32 = matrix1.M31 * inverse.M12 + matrix1.M32 * inverse.M22 + matrix1.M33 * inverse.M32 + matrix1.M34 * inverse.M42; + result.M33 = matrix1.M31 * inverse.M13 + matrix1.M32 * inverse.M23 + matrix1.M33 * inverse.M33 + matrix1.M34 * inverse.M43; + result.M34 = matrix1.M31 * inverse.M14 + matrix1.M32 * inverse.M24 + matrix1.M33 * inverse.M34 + matrix1.M34 * inverse.M44; + + result.M41 = matrix1.M41 * inverse.M11 + matrix1.M42 * inverse.M21 + matrix1.M43 * inverse.M31 + matrix1.M44 * inverse.M41; + result.M42 = matrix1.M41 * inverse.M12 + matrix1.M42 * inverse.M22 + matrix1.M43 * inverse.M32 + matrix1.M44 * inverse.M42; + result.M43 = matrix1.M41 * inverse.M13 + matrix1.M42 * inverse.M23 + matrix1.M43 * inverse.M33 + matrix1.M44 * inverse.M43; + result.M44 = matrix1.M41 * inverse.M14 + matrix1.M42 * inverse.M24 + matrix1.M43 * inverse.M34 + matrix1.M44 * inverse.M44; + } + + + public static Matrix Divide(Matrix matrix1, float divider) + { + float inverseDivider = 1.0f / divider; + + matrix1.M11 = matrix1.M11 * inverseDivider; + matrix1.M12 = matrix1.M12 * inverseDivider; + matrix1.M13 = matrix1.M13 * inverseDivider; + matrix1.M14 = matrix1.M14 * inverseDivider; + matrix1.M21 = matrix1.M21 * inverseDivider; + matrix1.M22 = matrix1.M22 * inverseDivider; + matrix1.M23 = matrix1.M23 * inverseDivider; + matrix1.M24 = matrix1.M24 * inverseDivider; + matrix1.M31 = matrix1.M31 * inverseDivider; + matrix1.M32 = matrix1.M32 * inverseDivider; + matrix1.M33 = matrix1.M33 * inverseDivider; + matrix1.M34 = matrix1.M34 * inverseDivider; + matrix1.M41 = matrix1.M41 * inverseDivider; + matrix1.M42 = matrix1.M42 * inverseDivider; + matrix1.M43 = matrix1.M43 * inverseDivider; + matrix1.M44 = matrix1.M44 * inverseDivider; + + return matrix1; + } + + + public static void Divide(ref Matrix matrix1, float divider, out Matrix result) + { + float inverseDivider = 1.0f / divider; + result.M11 = matrix1.M11 * inverseDivider; + result.M12 = matrix1.M12 * inverseDivider; + result.M13 = matrix1.M13 * inverseDivider; + result.M14 = matrix1.M14 * inverseDivider; + result.M21 = matrix1.M21 * inverseDivider; + result.M22 = matrix1.M22 * inverseDivider; + result.M23 = matrix1.M23 * inverseDivider; + result.M24 = matrix1.M24 * inverseDivider; + result.M31 = matrix1.M31 * inverseDivider; + result.M32 = matrix1.M32 * inverseDivider; + result.M33 = matrix1.M33 * inverseDivider; + result.M34 = matrix1.M34 * inverseDivider; + result.M41 = matrix1.M41 * inverseDivider; + result.M42 = matrix1.M42 * inverseDivider; + result.M43 = matrix1.M43 * inverseDivider; + result.M44 = matrix1.M44 * inverseDivider; + } + + public static Matrix Invert(Matrix matrix) + { + Invert(ref matrix, out matrix); + return matrix; + } + + + public static void Invert(ref Matrix matrix, out Matrix result) + { + // + // Use Laplace expansion theorem to calculate the inverse of a 4x4 matrix + // + // 1. Calculate the 2x2 determinants needed and the 4x4 determinant based on the 2x2 determinants + // 2. Create the adjugate matrix, which satisfies: A * adj(A) = det(A) * I + // 3. Divide adjugate matrix with the determinant to find the inverse + + float det1 = matrix.M11 * matrix.M22 - matrix.M12 * matrix.M21; + float det2 = matrix.M11 * matrix.M23 - matrix.M13 * matrix.M21; + float det3 = matrix.M11 * matrix.M24 - matrix.M14 * matrix.M21; + float det4 = matrix.M12 * matrix.M23 - matrix.M13 * matrix.M22; + float det5 = matrix.M12 * matrix.M24 - matrix.M14 * matrix.M22; + float det6 = matrix.M13 * matrix.M24 - matrix.M14 * matrix.M23; + float det7 = matrix.M31 * matrix.M42 - matrix.M32 * matrix.M41; + float det8 = matrix.M31 * matrix.M43 - matrix.M33 * matrix.M41; + float det9 = matrix.M31 * matrix.M44 - matrix.M34 * matrix.M41; + float det10 = matrix.M32 * matrix.M43 - matrix.M33 * matrix.M42; + float det11 = matrix.M32 * matrix.M44 - matrix.M34 * matrix.M42; + float det12 = matrix.M33 * matrix.M44 - matrix.M34 * matrix.M43; + + float detMatrix = (float)(det1*det12 - det2*det11 + det3*det10 + det4*det9 - det5*det8 + det6*det7); + + float invDetMatrix = 1f / detMatrix; + + Matrix ret; // Allow for matrix and result to point to the same structure + + ret.M11 = (matrix.M22*det12 - matrix.M23*det11 + matrix.M24*det10) * invDetMatrix; + ret.M12 = (-matrix.M12*det12 + matrix.M13*det11 - matrix.M14*det10) * invDetMatrix; + ret.M13 = (matrix.M42*det6 - matrix.M43*det5 + matrix.M44*det4) * invDetMatrix; + ret.M14 = (-matrix.M32*det6 + matrix.M33*det5 - matrix.M34*det4) * invDetMatrix; + ret.M21 = (-matrix.M21*det12 + matrix.M23*det9 - matrix.M24*det8) * invDetMatrix; + ret.M22 = (matrix.M11*det12 - matrix.M13*det9 + matrix.M14*det8) * invDetMatrix; + ret.M23 = (-matrix.M41*det6 + matrix.M43*det3 - matrix.M44*det2) * invDetMatrix; + ret.M24 = (matrix.M31*det6 - matrix.M33*det3 + matrix.M34*det2) * invDetMatrix; + ret.M31 = (matrix.M21*det11 - matrix.M22*det9 + matrix.M24*det7) * invDetMatrix; + ret.M32 = (-matrix.M11*det11 + matrix.M12*det9 - matrix.M14*det7) * invDetMatrix; + ret.M33 = (matrix.M41*det5 - matrix.M42*det3 + matrix.M44*det1) * invDetMatrix; + ret.M34 = (-matrix.M31*det5 + matrix.M32*det3 - matrix.M34*det1) * invDetMatrix; + ret.M41 = (-matrix.M21*det10 + matrix.M22*det8 - matrix.M23*det7) * invDetMatrix; + ret.M42 = (matrix.M11*det10 - matrix.M12*det8 + matrix.M13*det7) * invDetMatrix; + ret.M43 = (-matrix.M41*det4 + matrix.M42*det2 - matrix.M43*det1) * invDetMatrix; + ret.M44 = (matrix.M31*det4 - matrix.M32*det2 + matrix.M33*det1) * invDetMatrix; + + result = ret; + } + + + public static Matrix Lerp(Matrix matrix1, Matrix matrix2, float amount) + { + throw new NotImplementedException(); + } + + + public static void Lerp(ref Matrix matrix1, ref Matrix matrix2, float amount, out Matrix result) + { + throw new NotImplementedException(); + } + + public static Matrix Multiply(Matrix matrix1, Matrix matrix2) + { + Matrix result; + + result.M11 = matrix1.M11 * matrix2.M11 + matrix1.M12 * matrix2.M21 + matrix1.M13 * matrix2.M31 + matrix1.M14 * matrix2.M41; + result.M12 = matrix1.M11 * matrix2.M12 + matrix1.M12 * matrix2.M22 + matrix1.M13 * matrix2.M32 + matrix1.M14 * matrix2.M42; + result.M13 = matrix1.M11 * matrix2.M13 + matrix1.M12 * matrix2.M23 + matrix1.M13 * matrix2.M33 + matrix1.M14 * matrix2.M43; + result.M14 = matrix1.M11 * matrix2.M14 + matrix1.M12 * matrix2.M24 + matrix1.M13 * matrix2.M34 + matrix1.M14 * matrix2.M44; + + result.M21 = matrix1.M21 * matrix2.M11 + matrix1.M22 * matrix2.M21 + matrix1.M23 * matrix2.M31 + matrix1.M24 * matrix2.M41; + result.M22 = matrix1.M21 * matrix2.M12 + matrix1.M22 * matrix2.M22 + matrix1.M23 * matrix2.M32 + matrix1.M24 * matrix2.M42; + result.M23 = matrix1.M21 * matrix2.M13 + matrix1.M22 * matrix2.M23 + matrix1.M23 * matrix2.M33 + matrix1.M24 * matrix2.M43; + result.M24 = matrix1.M21 * matrix2.M14 + matrix1.M22 * matrix2.M24 + matrix1.M23 * matrix2.M34 + matrix1.M24 * matrix2.M44; + + result.M31 = matrix1.M31 * matrix2.M11 + matrix1.M32 * matrix2.M21 + matrix1.M33 * matrix2.M31 + matrix1.M34 * matrix2.M41; + result.M32 = matrix1.M31 * matrix2.M12 + matrix1.M32 * matrix2.M22 + matrix1.M33 * matrix2.M32 + matrix1.M34 * matrix2.M42; + result.M33 = matrix1.M31 * matrix2.M13 + matrix1.M32 * matrix2.M23 + matrix1.M33 * matrix2.M33 + matrix1.M34 * matrix2.M43; + result.M34 = matrix1.M31 * matrix2.M14 + matrix1.M32 * matrix2.M24 + matrix1.M33 * matrix2.M34 + matrix1.M34 * matrix2.M44; + + result.M41 = matrix1.M41 * matrix2.M11 + matrix1.M42 * matrix2.M21 + matrix1.M43 * matrix2.M31 + matrix1.M44 * matrix2.M41; + result.M42 = matrix1.M41 * matrix2.M12 + matrix1.M42 * matrix2.M22 + matrix1.M43 * matrix2.M32 + matrix1.M44 * matrix2.M42; + result.M43 = matrix1.M41 * matrix2.M13 + matrix1.M42 * matrix2.M23 + matrix1.M43 * matrix2.M33 + matrix1.M44 * matrix2.M43; + result.M44 = matrix1.M41 * matrix2.M14 + matrix1.M42 * matrix2.M24 + matrix1.M43 * matrix2.M34 + matrix1.M44 * matrix2.M44; + + return result; + } + + + public static void Multiply(ref Matrix matrix1, ref Matrix matrix2, out Matrix result) + { + result.M11 = matrix1.M11 * matrix2.M11 + matrix1.M12 * matrix2.M21 + matrix1.M13 * matrix2.M31 + matrix1.M14 * matrix2.M41; + result.M12 = matrix1.M11 * matrix2.M12 + matrix1.M12 * matrix2.M22 + matrix1.M13 * matrix2.M32 + matrix1.M14 * matrix2.M42; + result.M13 = matrix1.M11 * matrix2.M13 + matrix1.M12 * matrix2.M23 + matrix1.M13 * matrix2.M33 + matrix1.M14 * matrix2.M43; + result.M14 = matrix1.M11 * matrix2.M14 + matrix1.M12 * matrix2.M24 + matrix1.M13 * matrix2.M34 + matrix1.M14 * matrix2.M44; + + result.M21 = matrix1.M21 * matrix2.M11 + matrix1.M22 * matrix2.M21 + matrix1.M23 * matrix2.M31 + matrix1.M24 * matrix2.M41; + result.M22 = matrix1.M21 * matrix2.M12 + matrix1.M22 * matrix2.M22 + matrix1.M23 * matrix2.M32 + matrix1.M24 * matrix2.M42; + result.M23 = matrix1.M21 * matrix2.M13 + matrix1.M22 * matrix2.M23 + matrix1.M23 * matrix2.M33 + matrix1.M24 * matrix2.M43; + result.M24 = matrix1.M21 * matrix2.M14 + matrix1.M22 * matrix2.M24 + matrix1.M23 * matrix2.M34 + matrix1.M24 * matrix2.M44; + + result.M31 = matrix1.M31 * matrix2.M11 + matrix1.M32 * matrix2.M21 + matrix1.M33 * matrix2.M31 + matrix1.M34 * matrix2.M41; + result.M32 = matrix1.M31 * matrix2.M12 + matrix1.M32 * matrix2.M22 + matrix1.M33 * matrix2.M32 + matrix1.M34 * matrix2.M42; + result.M33 = matrix1.M31 * matrix2.M13 + matrix1.M32 * matrix2.M23 + matrix1.M33 * matrix2.M33 + matrix1.M34 * matrix2.M43; + result.M34 = matrix1.M31 * matrix2.M14 + matrix1.M32 * matrix2.M24 + matrix1.M33 * matrix2.M34 + matrix1.M34 * matrix2.M44; + + result.M41 = matrix1.M41 * matrix2.M11 + matrix1.M42 * matrix2.M21 + matrix1.M43 * matrix2.M31 + matrix1.M44 * matrix2.M41; + result.M42 = matrix1.M41 * matrix2.M12 + matrix1.M42 * matrix2.M22 + matrix1.M43 * matrix2.M32 + matrix1.M44 * matrix2.M42; + result.M43 = matrix1.M41 * matrix2.M13 + matrix1.M42 * matrix2.M23 + matrix1.M43 * matrix2.M33 + matrix1.M44 * matrix2.M43; + result.M44 = matrix1.M41 * matrix2.M14 + matrix1.M42 * matrix2.M24 + matrix1.M43 * matrix2.M34 + matrix1.M44 * matrix2.M44; + } + + + public static Matrix Multiply(Matrix matrix1, float factor) + { + matrix1.M11 *= factor; + matrix1.M12 *= factor; + matrix1.M13 *= factor; + matrix1.M14 *= factor; + matrix1.M21 *= factor; + matrix1.M22 *= factor; + matrix1.M23 *= factor; + matrix1.M24 *= factor; + matrix1.M31 *= factor; + matrix1.M32 *= factor; + matrix1.M33 *= factor; + matrix1.M34 *= factor; + matrix1.M41 *= factor; + matrix1.M42 *= factor; + matrix1.M43 *= factor; + matrix1.M44 *= factor; + return matrix1; + } + + + public static void Multiply(ref Matrix matrix1, float factor, out Matrix result) + { + result.M11 = matrix1.M11 * factor; + result.M12 = matrix1.M12 * factor; + result.M13 = matrix1.M13 * factor; + result.M14 = matrix1.M14 * factor; + result.M21 = matrix1.M21 * factor; + result.M22 = matrix1.M22 * factor; + result.M23 = matrix1.M23 * factor; + result.M24 = matrix1.M24 * factor; + result.M31 = matrix1.M31 * factor; + result.M32 = matrix1.M32 * factor; + result.M33 = matrix1.M33 * factor; + result.M34 = matrix1.M34 * factor; + result.M41 = matrix1.M41 * factor; + result.M42 = matrix1.M42 * factor; + result.M43 = matrix1.M43 * factor; + result.M44 = matrix1.M44 * factor; + } + + + public static Matrix Negate(Matrix matrix) + { + matrix.M11 = -matrix.M11; + matrix.M12 = -matrix.M12; + matrix.M13 = -matrix.M13; + matrix.M14 = -matrix.M14; + matrix.M21 = -matrix.M21; + matrix.M22 = -matrix.M22; + matrix.M23 = -matrix.M23; + matrix.M24 = -matrix.M24; + matrix.M31 = -matrix.M31; + matrix.M32 = -matrix.M32; + matrix.M33 = -matrix.M33; + matrix.M34 = -matrix.M34; + matrix.M41 = -matrix.M41; + matrix.M42 = -matrix.M42; + matrix.M43 = -matrix.M43; + matrix.M44 = -matrix.M44; + return matrix; + } + + + public static void Negate(ref Matrix matrix, out Matrix result) + { + result.M11 = matrix.M11; + result.M12 = matrix.M12; + result.M13 = matrix.M13; + result.M14 = matrix.M14; + result.M21 = matrix.M21; + result.M22 = matrix.M22; + result.M23 = matrix.M23; + result.M24 = matrix.M24; + result.M31 = matrix.M31; + result.M32 = matrix.M32; + result.M33 = matrix.M33; + result.M34 = matrix.M34; + result.M41 = matrix.M41; + result.M42 = matrix.M42; + result.M43 = matrix.M43; + result.M44 = matrix.M44; + } + + public static Matrix Subtract(Matrix matrix1, Matrix matrix2) + { + matrix1.M11 -= matrix2.M11; + matrix1.M12 -= matrix2.M12; + matrix1.M13 -= matrix2.M13; + matrix1.M14 -= matrix2.M14; + matrix1.M21 -= matrix2.M21; + matrix1.M22 -= matrix2.M22; + matrix1.M23 -= matrix2.M23; + matrix1.M24 -= matrix2.M24; + matrix1.M31 -= matrix2.M31; + matrix1.M32 -= matrix2.M32; + matrix1.M33 -= matrix2.M33; + matrix1.M34 -= matrix2.M34; + matrix1.M41 -= matrix2.M41; + matrix1.M42 -= matrix2.M42; + matrix1.M43 -= matrix2.M43; + matrix1.M44 -= matrix2.M44; + return matrix1; + } + + public static void Subtract(ref Matrix matrix1, ref Matrix matrix2, out Matrix result) + { + result.M11 = matrix1.M11 - matrix2.M11; + result.M12 = matrix1.M12 - matrix2.M12; + result.M13 = matrix1.M13 - matrix2.M13; + result.M14 = matrix1.M14 - matrix2.M14; + result.M21 = matrix1.M21 - matrix2.M21; + result.M22 = matrix1.M22 - matrix2.M22; + result.M23 = matrix1.M23 - matrix2.M23; + result.M24 = matrix1.M24 - matrix2.M24; + result.M31 = matrix1.M31 - matrix2.M31; + result.M32 = matrix1.M32 - matrix2.M32; + result.M33 = matrix1.M33 - matrix2.M33; + result.M34 = matrix1.M34 - matrix2.M34; + result.M41 = matrix1.M41 - matrix2.M41; + result.M42 = matrix1.M42 - matrix2.M42; + result.M43 = matrix1.M43 - matrix2.M43; + result.M44 = matrix1.M44 - matrix2.M44; + } + + public static Matrix Transpose(Matrix matrix) + { + Matrix result; + + result.M11 = matrix.M11; + result.M12 = matrix.M21; + result.M13 = matrix.M31; + result.M14 = matrix.M41; + + result.M21 = matrix.M12; + result.M22 = matrix.M22; + result.M23 = matrix.M32; + result.M24 = matrix.M42; + + result.M31 = matrix.M13; + result.M32 = matrix.M23; + result.M33 = matrix.M33; + result.M34 = matrix.M43; + + result.M41 = matrix.M14; + result.M42 = matrix.M24; + result.M43 = matrix.M34; + result.M44 = matrix.M44; + + return result; + } + + + public static void Transpose(ref Matrix matrix, out Matrix result) + { + result.M11 = matrix.M11; + result.M12 = matrix.M21; + result.M13 = matrix.M31; + result.M14 = matrix.M41; + + result.M21 = matrix.M12; + result.M22 = matrix.M22; + result.M23 = matrix.M32; + result.M24 = matrix.M42; + + result.M31 = matrix.M13; + result.M32 = matrix.M23; + result.M33 = matrix.M33; + result.M34 = matrix.M43; + + result.M41 = matrix.M14; + result.M42 = matrix.M24; + result.M43 = matrix.M34; + result.M44 = matrix.M44; + } + + #endregion Public Static Methods + + #region Public Methods + + public float Determinant() + { + float minor1, minor2, minor3, minor4, minor5, minor6; + + minor1 = M31 * M42 - M32 * M41; + minor2 = M31 * M43 - M33 * M41; + minor3 = M31 * M44 - M34 * M41; + minor4 = M32 * M43 - M33 * M42; + minor5 = M32 * M44 - M34 * M42; + minor6 = M33 * M44 - M34 * M43; + + return M11 * (M22 * minor6 - M23 * minor5 + M24 * minor4) - + M12 * (M21 * minor6 - M23 * minor3 + M24 * minor2) + + M13 * (M21 * minor5 - M22 * minor3 + M24 * minor1) - + M14 * (M21 * minor4 - M22 * minor2 + M23 * minor1); + } + + public bool Equals(Matrix other) + { + return (this.M11 == other.M11) && (this.M12 == other.M12) && + (this.M13 == other.M13) && (this.M14 == other.M14) && + (this.M21 == other.M21) && (this.M22 == other.M22) && + (this.M23 == other.M23) && (this.M24 == other.M24) && + (this.M31 == other.M31) && (this.M32 == other.M32) && + (this.M33 == other.M33) && (this.M34 == other.M34) && + (this.M41 == other.M41) && (this.M42 == other.M42) && + (this.M43 == other.M43) && (this.M44 == other.M44); + } + + #endregion Public Methods + + #region Operators + + public static Matrix operator +(Matrix matrix1, Matrix matrix2) + { + matrix1.M11 += matrix2.M11; + matrix1.M12 += matrix2.M12; + matrix1.M13 += matrix2.M13; + matrix1.M14 += matrix2.M14; + matrix1.M21 += matrix2.M21; + matrix1.M22 += matrix2.M22; + matrix1.M23 += matrix2.M23; + matrix1.M24 += matrix2.M24; + matrix1.M31 += matrix2.M31; + matrix1.M32 += matrix2.M32; + matrix1.M33 += matrix2.M33; + matrix1.M34 += matrix2.M34; + matrix1.M41 += matrix2.M41; + matrix1.M42 += matrix2.M42; + matrix1.M43 += matrix2.M43; + matrix1.M44 += matrix2.M44; + return matrix1; + } + + public static Matrix operator /(Matrix matrix1, Matrix matrix2) + { + Matrix inverse = Matrix.Invert(matrix2), result; + + result.M11 = matrix1.M11 * inverse.M11 + matrix1.M12 * inverse.M21 + matrix1.M13 * inverse.M31 + matrix1.M14 * inverse.M41; + result.M12 = matrix1.M11 * inverse.M12 + matrix1.M12 * inverse.M22 + matrix1.M13 * inverse.M32 + matrix1.M14 * inverse.M42; + result.M13 = matrix1.M11 * inverse.M13 + matrix1.M12 * inverse.M23 + matrix1.M13 * inverse.M33 + matrix1.M14 * inverse.M43; + result.M14 = matrix1.M11 * inverse.M14 + matrix1.M12 * inverse.M24 + matrix1.M13 * inverse.M34 + matrix1.M14 * inverse.M44; + + result.M21 = matrix1.M21 * inverse.M11 + matrix1.M22 * inverse.M21 + matrix1.M23 * inverse.M31 + matrix1.M24 * inverse.M41; + result.M22 = matrix1.M21 * inverse.M12 + matrix1.M22 * inverse.M22 + matrix1.M23 * inverse.M32 + matrix1.M24 * inverse.M42; + result.M23 = matrix1.M21 * inverse.M13 + matrix1.M22 * inverse.M23 + matrix1.M23 * inverse.M33 + matrix1.M24 * inverse.M43; + result.M24 = matrix1.M21 * inverse.M14 + matrix1.M22 * inverse.M24 + matrix1.M23 * inverse.M34 + matrix1.M24 * inverse.M44; + + result.M31 = matrix1.M31 * inverse.M11 + matrix1.M32 * inverse.M21 + matrix1.M33 * inverse.M31 + matrix1.M34 * inverse.M41; + result.M32 = matrix1.M31 * inverse.M12 + matrix1.M32 * inverse.M22 + matrix1.M33 * inverse.M32 + matrix1.M34 * inverse.M42; + result.M33 = matrix1.M31 * inverse.M13 + matrix1.M32 * inverse.M23 + matrix1.M33 * inverse.M33 + matrix1.M34 * inverse.M43; + result.M34 = matrix1.M31 * inverse.M14 + matrix1.M32 * inverse.M24 + matrix1.M33 * inverse.M34 + matrix1.M34 * inverse.M44; + + result.M41 = matrix1.M41 * inverse.M11 + matrix1.M42 * inverse.M21 + matrix1.M43 * inverse.M31 + matrix1.M44 * inverse.M41; + result.M42 = matrix1.M41 * inverse.M12 + matrix1.M42 * inverse.M22 + matrix1.M43 * inverse.M32 + matrix1.M44 * inverse.M42; + result.M43 = matrix1.M41 * inverse.M13 + matrix1.M42 * inverse.M23 + matrix1.M43 * inverse.M33 + matrix1.M44 * inverse.M43; + result.M44 = matrix1.M41 * inverse.M14 + matrix1.M42 * inverse.M24 + matrix1.M43 * inverse.M34 + matrix1.M44 * inverse.M44; + + return result; + } + + public static Matrix operator /(Matrix matrix1, float divider) + { + float inverseDivider = 1.0f / divider; + + matrix1.M11 = matrix1.M11 * inverseDivider; + matrix1.M12 = matrix1.M12 * inverseDivider; + matrix1.M13 = matrix1.M13 * inverseDivider; + matrix1.M14 = matrix1.M14 * inverseDivider; + matrix1.M21 = matrix1.M21 * inverseDivider; + matrix1.M22 = matrix1.M22 * inverseDivider; + matrix1.M23 = matrix1.M23 * inverseDivider; + matrix1.M24 = matrix1.M24 * inverseDivider; + matrix1.M31 = matrix1.M31 * inverseDivider; + matrix1.M32 = matrix1.M32 * inverseDivider; + matrix1.M33 = matrix1.M33 * inverseDivider; + matrix1.M34 = matrix1.M34 * inverseDivider; + matrix1.M41 = matrix1.M41 * inverseDivider; + matrix1.M42 = matrix1.M42 * inverseDivider; + matrix1.M43 = matrix1.M43 * inverseDivider; + matrix1.M44 = matrix1.M44 * inverseDivider; + + return matrix1; + } + + public static bool operator ==(Matrix matrix1, Matrix matrix2) + { + return (matrix1.M11 == matrix2.M11) && (matrix1.M12 == matrix2.M12) && + (matrix1.M13 == matrix2.M13) && (matrix1.M14 == matrix2.M14) && + (matrix1.M21 == matrix2.M21) && (matrix1.M22 == matrix2.M22) && + (matrix1.M23 == matrix2.M23) && (matrix1.M24 == matrix2.M24) && + (matrix1.M31 == matrix2.M31) && (matrix1.M32 == matrix2.M32) && + (matrix1.M33 == matrix2.M33) && (matrix1.M34 == matrix2.M34) && + (matrix1.M41 == matrix2.M41) && (matrix1.M42 == matrix2.M42) && + (matrix1.M43 == matrix2.M43) && (matrix1.M44 == matrix2.M44); + } + + public static bool operator !=(Matrix matrix1, Matrix matrix2) + { + return (matrix1.M11 != matrix2.M11) || (matrix1.M12 != matrix2.M12) || + (matrix1.M13 != matrix2.M13) || (matrix1.M14 != matrix2.M14) || + (matrix1.M21 != matrix2.M21) || (matrix1.M22 != matrix2.M22) || + (matrix1.M23 != matrix2.M23) || (matrix1.M24 != matrix2.M24) || + (matrix1.M31 != matrix2.M31) || (matrix1.M32 != matrix2.M32) || + (matrix1.M33 != matrix2.M33) || (matrix1.M34 != matrix2.M34) || + (matrix1.M41 != matrix2.M41) || (matrix1.M42 != matrix2.M42) || + (matrix1.M43 != matrix2.M43) || (matrix1.M44 != matrix2.M44); + } + + public static Matrix operator *(Matrix matrix1, Matrix matrix2) + { + Matrix result; + + result.M11 = matrix1.M11 * matrix2.M11 + matrix1.M12 * matrix2.M21 + matrix1.M13 * matrix2.M31 + matrix1.M14 * matrix2.M41; + result.M12 = matrix1.M11 * matrix2.M12 + matrix1.M12 * matrix2.M22 + matrix1.M13 * matrix2.M32 + matrix1.M14 * matrix2.M42; + result.M13 = matrix1.M11 * matrix2.M13 + matrix1.M12 * matrix2.M23 + matrix1.M13 * matrix2.M33 + matrix1.M14 * matrix2.M43; + result.M14 = matrix1.M11 * matrix2.M14 + matrix1.M12 * matrix2.M24 + matrix1.M13 * matrix2.M34 + matrix1.M14 * matrix2.M44; + + result.M21 = matrix1.M21 * matrix2.M11 + matrix1.M22 * matrix2.M21 + matrix1.M23 * matrix2.M31 + matrix1.M24 * matrix2.M41; + result.M22 = matrix1.M21 * matrix2.M12 + matrix1.M22 * matrix2.M22 + matrix1.M23 * matrix2.M32 + matrix1.M24 * matrix2.M42; + result.M23 = matrix1.M21 * matrix2.M13 + matrix1.M22 * matrix2.M23 + matrix1.M23 * matrix2.M33 + matrix1.M24 * matrix2.M43; + result.M24 = matrix1.M21 * matrix2.M14 + matrix1.M22 * matrix2.M24 + matrix1.M23 * matrix2.M34 + matrix1.M24 * matrix2.M44; + + result.M31 = matrix1.M31 * matrix2.M11 + matrix1.M32 * matrix2.M21 + matrix1.M33 * matrix2.M31 + matrix1.M34 * matrix2.M41; + result.M32 = matrix1.M31 * matrix2.M12 + matrix1.M32 * matrix2.M22 + matrix1.M33 * matrix2.M32 + matrix1.M34 * matrix2.M42; + result.M33 = matrix1.M31 * matrix2.M13 + matrix1.M32 * matrix2.M23 + matrix1.M33 * matrix2.M33 + matrix1.M34 * matrix2.M43; + result.M34 = matrix1.M31 * matrix2.M14 + matrix1.M32 * matrix2.M24 + matrix1.M33 * matrix2.M34 + matrix1.M34 * matrix2.M44; + + result.M41 = matrix1.M41 * matrix2.M11 + matrix1.M42 * matrix2.M21 + matrix1.M43 * matrix2.M31 + matrix1.M44 * matrix2.M41; + result.M42 = matrix1.M41 * matrix2.M12 + matrix1.M42 * matrix2.M22 + matrix1.M43 * matrix2.M32 + matrix1.M44 * matrix2.M42; + result.M43 = matrix1.M41 * matrix2.M13 + matrix1.M42 * matrix2.M23 + matrix1.M43 * matrix2.M33 + matrix1.M44 * matrix2.M43; + result.M44 = matrix1.M41 * matrix2.M14 + matrix1.M42 * matrix2.M24 + matrix1.M43 * matrix2.M34 + matrix1.M44 * matrix2.M44; + + return result; + } + + public static Matrix operator *(Matrix matrix, float scaleFactor) + { + matrix.M11 = matrix.M11 * scaleFactor; + matrix.M12 = matrix.M12 * scaleFactor; + matrix.M13 = matrix.M13 * scaleFactor; + matrix.M14 = matrix.M14 * scaleFactor; + matrix.M21 = matrix.M21 * scaleFactor; + matrix.M22 = matrix.M22 * scaleFactor; + matrix.M23 = matrix.M23 * scaleFactor; + matrix.M24 = matrix.M24 * scaleFactor; + matrix.M31 = matrix.M31 * scaleFactor; + matrix.M32 = matrix.M32 * scaleFactor; + matrix.M33 = matrix.M33 * scaleFactor; + matrix.M34 = matrix.M34 * scaleFactor; + matrix.M41 = matrix.M41 * scaleFactor; + matrix.M42 = matrix.M42 * scaleFactor; + matrix.M43 = matrix.M43 * scaleFactor; + matrix.M44 = matrix.M44 * scaleFactor; + return matrix; + } + + public static Matrix operator *(float scaleFactor, Matrix matrix) + { + matrix.M11 = matrix.M11 * scaleFactor; + matrix.M12 = matrix.M12 * scaleFactor; + matrix.M13 = matrix.M13 * scaleFactor; + matrix.M14 = matrix.M14 * scaleFactor; + matrix.M21 = matrix.M21 * scaleFactor; + matrix.M22 = matrix.M22 * scaleFactor; + matrix.M23 = matrix.M23 * scaleFactor; + matrix.M24 = matrix.M24 * scaleFactor; + matrix.M31 = matrix.M31 * scaleFactor; + matrix.M32 = matrix.M32 * scaleFactor; + matrix.M33 = matrix.M33 * scaleFactor; + matrix.M34 = matrix.M34 * scaleFactor; + matrix.M41 = matrix.M41 * scaleFactor; + matrix.M42 = matrix.M42 * scaleFactor; + matrix.M43 = matrix.M43 * scaleFactor; + matrix.M44 = matrix.M44 * scaleFactor; + return matrix; + } + + public static Matrix operator -(Matrix matrix1, Matrix matrix2) + { + matrix1.M11 -= matrix2.M11; + matrix1.M12 -= matrix2.M12; + matrix1.M13 -= matrix2.M13; + matrix1.M14 -= matrix2.M14; + matrix1.M21 -= matrix2.M21; + matrix1.M22 -= matrix2.M22; + matrix1.M23 -= matrix2.M23; + matrix1.M24 -= matrix2.M24; + matrix1.M31 -= matrix2.M31; + matrix1.M32 -= matrix2.M32; + matrix1.M33 -= matrix2.M33; + matrix1.M34 -= matrix2.M34; + matrix1.M41 -= matrix2.M41; + matrix1.M42 -= matrix2.M42; + matrix1.M43 -= matrix2.M43; + matrix1.M44 -= matrix2.M44; + return matrix1; + } + + + public static Matrix operator -(Matrix matrix) + { + matrix.M11 = -matrix.M11; + matrix.M12 = -matrix.M12; + matrix.M13 = -matrix.M13; + matrix.M14 = -matrix.M14; + matrix.M21 = -matrix.M21; + matrix.M22 = -matrix.M22; + matrix.M23 = -matrix.M23; + matrix.M24 = -matrix.M24; + matrix.M31 = -matrix.M31; + matrix.M32 = -matrix.M32; + matrix.M33 = -matrix.M33; + matrix.M34 = -matrix.M34; + matrix.M41 = -matrix.M41; + matrix.M42 = -matrix.M42; + matrix.M43 = -matrix.M43; + matrix.M44 = -matrix.M44; + return matrix; + } + + #endregion + + #region Object Overrides + + public override bool Equals(object obj) + { + if (obj is Matrix) + return this == (Matrix)obj; + return false; + } + + public override int GetHashCode() + { + throw new NotImplementedException(); + } + + public override string ToString() + { + return "{ {M11:" + M11 + " M12:" + M12 + " M13:" + M13 + " M14:" + M14 + "}" + + " {M21:" + M21 + " M22:" + M22 + " M23:" + M23 + " M24:" + M24 + "}" + + " {M31:" + M31 + " M32:" + M32 + " M33:" + M33 + " M34:" + M34 + "}" + + " {M41:" + M41 + " M42:" + M42 + " M43:" + M43 + " M44:" + M44 + "} }"; + } + + #endregion + + } +} diff --git a/Bizware/BizHawk.Bizware.BizwareGL/Types/Plane.cs b/Bizware/BizHawk.Bizware.BizwareGL/Types/Plane.cs new file mode 100644 index 0000000000..9970380f34 --- /dev/null +++ b/Bizware/BizHawk.Bizware.BizwareGL/Types/Plane.cs @@ -0,0 +1,212 @@ +#region License +/* +MIT License +Copyright © 2006 The Mono.Xna Team + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +#endregion License + +using System; +using System.ComponentModel; +namespace BizHawk.Bizware.BizwareGL +{ + + [Serializable] + public struct Plane : IEquatable + { + #region Public Fields + + public float D; + public Vector3 Normal; + + #endregion Public Fields + + + #region Constructors + + public Plane(Vector4 value) + : this(new Vector3(value.X, value.Y, value.Z), value.W) + { + + } + + public Plane(Vector3 normal, float d) + { + Normal = normal; + D = d; + } + + public Plane(Vector3 a, Vector3 b, Vector3 c) + { + Vector3 ab = b - a; + Vector3 ac = c - a; + + Vector3 cross = Vector3.Cross(ab, ac); + Normal = Vector3.Normalize(cross); + D = -(Vector3.Dot(cross, a)); + } + + public Plane(float a, float b, float c, float d) + : this(new Vector3(a, b, c), d) + { + + } + + #endregion Constructors + + + #region Public Methods + + public float Dot(Vector4 value) + { + return ((((this.Normal.X * value.X) + (this.Normal.Y * value.Y)) + (this.Normal.Z * value.Z)) + (this.D * value.W)); + } + + public void Dot(ref Vector4 value, out float result) + { + result = (((this.Normal.X * value.X) + (this.Normal.Y * value.Y)) + (this.Normal.Z * value.Z)) + (this.D * value.W); + } + + public float DotCoordinate(Vector3 value) + { + return ((((this.Normal.X * value.X) + (this.Normal.Y * value.Y)) + (this.Normal.Z * value.Z)) + this.D); + } + + public void DotCoordinate(ref Vector3 value, out float result) + { + result = (((this.Normal.X * value.X) + (this.Normal.Y * value.Y)) + (this.Normal.Z * value.Z)) + this.D; + } + + public float DotNormal(Vector3 value) + { + return (((this.Normal.X * value.X) + (this.Normal.Y * value.Y)) + (this.Normal.Z * value.Z)); + } + + public void DotNormal(ref Vector3 value, out float result) + { + result = ((this.Normal.X * value.X) + (this.Normal.Y * value.Y)) + (this.Normal.Z * value.Z); + } + + public static void Transform(ref Plane plane, ref Quaternion rotation, out Plane result) + { + throw new NotImplementedException(); + } + + public static void Transform(ref Plane plane, ref Matrix matrix, out Plane result) + { + throw new NotImplementedException(); + } + + public static Plane Transform(Plane plane, Quaternion rotation) + { + throw new NotImplementedException(); + } + + public static Plane Transform(Plane plane, Matrix matrix) + { + throw new NotImplementedException(); + } + + public void Normalize() + { + float factor; + Vector3 normal = Normal; + Normal = Vector3.Normalize(Normal); + factor = (float)Math.Sqrt(Normal.X * Normal.X + Normal.Y * Normal.Y + Normal.Z * Normal.Z) / + (float)Math.Sqrt(normal.X * normal.X + normal.Y * normal.Y + normal.Z * normal.Z); + D = D * factor; + } + + public static Plane Normalize(Plane value) + { + Plane ret; + Normalize(ref value, out ret); + return ret; + } + + public static void Normalize(ref Plane value, out Plane result) + { + float factor; + result.Normal = Vector3.Normalize(value.Normal); + factor = (float)Math.Sqrt(result.Normal.X * result.Normal.X + result.Normal.Y * result.Normal.Y + result.Normal.Z * result.Normal.Z) / + (float)Math.Sqrt(value.Normal.X * value.Normal.X + value.Normal.Y * value.Normal.Y + value.Normal.Z * value.Normal.Z); + result.D = value.D * factor; + } + + public static bool operator !=(Plane plane1, Plane plane2) + { + return !plane1.Equals(plane2); + } + + public static bool operator ==(Plane plane1, Plane plane2) + { + return plane1.Equals(plane2); + } + + public override bool Equals(object other) + { + return (other is Plane) ? this.Equals((Plane)other) : false; + } + + public bool Equals(Plane other) + { + return ((Normal == other.Normal) && (D == other.D)); + } + + public override int GetHashCode() + { + return Normal.GetHashCode() ^ D.GetHashCode(); + } + + public PlaneIntersectionType Intersects(BoundingBox box) + { + return box.Intersects(this); + } + + public void Intersects(ref BoundingBox box, out PlaneIntersectionType result) + { + result = Intersects(box); + } + + public PlaneIntersectionType Intersects(BoundingFrustum frustum) + { + return frustum.Intersects(this); + } + + public PlaneIntersectionType Intersects(BoundingSphere sphere) + { + return sphere.Intersects(this); + } + + public void Intersects(ref BoundingSphere sphere, out PlaneIntersectionType result) + { + result = Intersects(sphere); + } + + public override string ToString() + { + return string.Format("{{Normal:{0} D:{1}}}", Normal, D); + } + + #endregion + } +} diff --git a/Bizware/BizHawk.Bizware.BizwareGL/Types/PlaneHelper.cs b/Bizware/BizHawk.Bizware.BizwareGL/Types/PlaneHelper.cs new file mode 100644 index 0000000000..533ad89e72 --- /dev/null +++ b/Bizware/BizHawk.Bizware.BizwareGL/Types/PlaneHelper.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace BizHawk.Bizware.BizwareGL +{ + internal class PlaneHelper + { + /// + /// Returns a value indicating what side (positive/negative) of a plane a point is + /// + /// The point to check with + /// The plane to check against + /// Greater than zero if on the positive side, less than zero if on the negative size, 0 otherwise + public static float ClassifyPoint(ref Vector3 point, ref Plane plane) + { + return point.X * plane.Normal.X + point.Y * plane.Normal.Y + point.Z * plane.Normal.Z + plane.D; + } + + /// + /// Returns the perpendicular distance from a point to a plane + /// + /// The point to check + /// The place to check + /// The perpendicular distance from the point to the plane + public static float PerpendicularDistance(ref Vector3 point, ref Plane plane) + { + // dist = (ax + by + cz + d) / sqrt(a*a + b*b + c*c) + return (float)Math.Abs((plane.Normal.X * point.X + plane.Normal.Y * point.Y + plane.Normal.Z * point.Z) + / Math.Sqrt(plane.Normal.X * plane.Normal.X + plane.Normal.Y * plane.Normal.Y + plane.Normal.Z * plane.Normal.Z)); + } + } +} diff --git a/Bizware/BizHawk.Bizware.BizwareGL/Types/Point.cs b/Bizware/BizHawk.Bizware.BizwareGL/Types/Point.cs new file mode 100644 index 0000000000..10fa723fa1 --- /dev/null +++ b/Bizware/BizHawk.Bizware.BizwareGL/Types/Point.cs @@ -0,0 +1,119 @@ +#region License +/* +MIT License +Copyright © 2006 The Mono.Xna Team + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +#endregion License + +using System; +using System.ComponentModel; + + +namespace BizHawk.Bizware.BizwareGL +{ + [Serializable] + + public struct Point : IEquatable + { + #region Private Fields + + private static Point zeroPoint = new Point(); + + #endregion Private Fields + + + #region Public Fields + + public int X; + public int Y; + + #endregion Public Fields + + + #region Properties + + public static Point Zero + { + get { return zeroPoint; } + } + + #endregion Properties + + + #region Constructors + + /// + /// Makes new Point with integer accurate + /// + /// + /// A + /// + /// + /// A + /// + public Point(int x, int y) + { + this.X = x; + this.Y = y; + } + + #endregion Constructors + + + #region Public methods + + public static bool operator ==(Point a, Point b) + { + return a.Equals(b); + } + + public static bool operator !=(Point a, Point b) + { + return !a.Equals(b); + } + + public bool Equals(Point other) + { + return ((X == other.X) && (Y == other.Y)); + } + + public override bool Equals(object obj) + { + return (obj is Point) ? Equals((Point)obj) : false; + } + + public override int GetHashCode() + { + return X ^ Y; + } + + public override string ToString() + { + return string.Format("{{X:{0} Y:{1}}}", X, Y); + } + + #endregion + } +} + + diff --git a/Bizware/BizHawk.Bizware.BizwareGL/Types/Quaternion.cs b/Bizware/BizHawk.Bizware.BizwareGL/Types/Quaternion.cs new file mode 100644 index 0000000000..1edd9befbf --- /dev/null +++ b/Bizware/BizHawk.Bizware.BizwareGL/Types/Quaternion.cs @@ -0,0 +1,687 @@ +#region License +/* +MIT License +Copyright © 2006 The Mono.Xna Team + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +#endregion License + +using System; +using System.ComponentModel; +using System.Text; + +namespace BizHawk.Bizware.BizwareGL +{ + [Serializable] + public struct Quaternion : IEquatable + { + public float X; + public float Y; + public float Z; + public float W; + static Quaternion identity = new Quaternion(0, 0, 0, 1); + + + public Quaternion(float x, float y, float z, float w) + { + this.X = x; + this.Y = y; + this.Z = z; + this.W = w; + } + + + public Quaternion(Vector3 vectorPart, float scalarPart) + { + this.X = vectorPart.X; + this.Y = vectorPart.Y; + this.Z = vectorPart.Z; + this.W = scalarPart; + } + + public static Quaternion Identity + { + get{ return identity; } + } + + + public static Quaternion Add(Quaternion quaternion1, Quaternion quaternion2) + { + quaternion1.X += quaternion2.X; + quaternion1.Y += quaternion2.Y; + quaternion1.Z += quaternion2.Z; + quaternion1.W += quaternion2.W; + return quaternion1; + } + + + public static void Add(ref Quaternion quaternion1, ref Quaternion quaternion2, out Quaternion result) + { + result.W = quaternion1.W + quaternion2.W; + result.X = quaternion1.X + quaternion2.X; + result.Y = quaternion1.Y + quaternion2.Y; + result.Z = quaternion1.Z + quaternion2.Z; + } + + public static Quaternion Concatenate(Quaternion value1, Quaternion value2) + { + Quaternion quaternion; + quaternion.X = ((value2.X * value1.W) + (value1.X * value2.W)) + (value2.Y * value1.Z) - (value2.Z * value1.Y); + quaternion.Y = ((value2.Y * value1.W) + (value1.Y * value2.W)) + (value2.Z * value1.X) - (value2.X * value1.Z); + quaternion.Z = ((value2.Z * value1.W) + (value1.Z * value2.W)) + (value2.X * value1.Y) - (value2.Y * value1.X); + quaternion.W = (value2.W * value1.W) - ((value2.X * value1.X) + (value2.Y * value1.Y)) + (value2.Z * value1.Z); + return quaternion; + } + + public void Conjugate() + { + this.X = -this.X; + this.Y = -this.Y; + this.Z = -this.Z; + } + + public static Quaternion Conjugate(Quaternion value) + { + Quaternion quaternion; + quaternion.X = -value.X; + quaternion.Y = -value.Y; + quaternion.Z = -value.Z; + quaternion.W = value.W; + return quaternion; + } + + public static void Conjugate(ref Quaternion value, out Quaternion result) + { + result.X = -value.X; + result.Y = -value.Y; + result.Z = -value.Z; + result.W = value.W; + } + + public static void Concatenate(ref Quaternion value1, ref Quaternion value2, out Quaternion result) + { + result.X = ((value2.X * value1.W) + (value1.X * value2.W)) + (value2.Y * value1.Z) - (value2.Z * value1.Y); + result.Y = ((value2.Y * value1.W) + (value1.Y * value2.W)) + (value2.Z * value1.X) - (value2.X * value1.Z); + result.Z = ((value2.Z * value1.W) + (value1.Z * value2.W)) + (value2.X * value1.Y) - (value2.Y * value1.X); + result.W = (value2.W * value1.W) - ((value2.X * value1.X) + (value2.Y * value1.Y)) + (value2.Z * value1.Z); + } + + public static Quaternion CreateFromYawPitchRoll(float yaw, float pitch, float roll) + { + Quaternion quaternion; + quaternion.X = (((float)Math.Cos((double)(yaw * 0.5f)) * (float)Math.Sin((double)(pitch * 0.5f))) * (float)Math.Cos((double)(roll * 0.5f))) + (((float)Math.Sin((double)(yaw * 0.5f)) * (float)Math.Cos((double)(pitch * 0.5f))) * (float)Math.Sin((double)(roll * 0.5f))); + quaternion.Y = (((float)Math.Sin((double)(yaw * 0.5f)) * (float)Math.Cos((double)(pitch * 0.5f))) * (float)Math.Cos((double)(roll * 0.5f))) - (((float)Math.Cos((double)(yaw * 0.5f)) * (float)Math.Sin((double)(pitch * 0.5f))) * (float)Math.Sin((double)(roll * 0.5f))); + quaternion.Z = (((float)Math.Cos((double)(yaw * 0.5f)) * (float)Math.Cos((double)(pitch * 0.5f))) * (float)Math.Sin((double)(roll * 0.5f))) - (((float)Math.Sin((double)(yaw * 0.5f)) * (float)Math.Sin((double)(pitch * 0.5f))) * (float)Math.Cos((double)(roll * 0.5f))); + quaternion.W = (((float)Math.Cos((double)(yaw * 0.5f)) * (float)Math.Cos((double)(pitch * 0.5f))) * (float)Math.Cos((double)(roll * 0.5f))) + (((float)Math.Sin((double)(yaw * 0.5f)) * (float)Math.Sin((double)(pitch * 0.5f))) * (float)Math.Sin((double)(roll * 0.5f))); + return quaternion; + } + + public static void CreateFromYawPitchRoll(float yaw, float pitch, float roll, out Quaternion result) + { + result.X = (((float)Math.Cos((double)(yaw * 0.5f)) * (float)Math.Sin((double)(pitch * 0.5f))) * (float)Math.Cos((double)(roll * 0.5f))) + (((float)Math.Sin((double)(yaw * 0.5f)) * (float)Math.Cos((double)(pitch * 0.5f))) * (float)Math.Sin((double)(roll * 0.5f))); + result.Y = (((float)Math.Sin((double)(yaw * 0.5f)) * (float)Math.Cos((double)(pitch * 0.5f))) * (float)Math.Cos((double)(roll * 0.5f))) - (((float)Math.Cos((double)(yaw * 0.5f)) * (float)Math.Sin((double)(pitch * 0.5f))) * (float)Math.Sin((double)(roll * 0.5f))); + result.Z = (((float)Math.Cos((double)(yaw * 0.5f)) * (float)Math.Cos((double)(pitch * 0.5f))) * (float)Math.Sin((double)(roll * 0.5f))) - (((float)Math.Sin((double)(yaw * 0.5f)) * (float)Math.Sin((double)(pitch * 0.5f))) * (float)Math.Cos((double)(roll * 0.5f))); + result.W = (((float)Math.Cos((double)(yaw * 0.5f)) * (float)Math.Cos((double)(pitch * 0.5f))) * (float)Math.Cos((double)(roll * 0.5f))) + (((float)Math.Sin((double)(yaw * 0.5f)) * (float)Math.Sin((double)(pitch * 0.5f))) * (float)Math.Sin((double)(roll * 0.5f))); + } + + public static Quaternion CreateFromAxisAngle(Vector3 axis, float angle) + { + float sin_a = (float)Math.Sin(angle / 2.0f); + return new Quaternion(axis.X * sin_a,axis.Y * sin_a,axis.Z * sin_a,(float)Math.Cos(angle / 2.0f)); + } + + + public static void CreateFromAxisAngle(ref Vector3 axis, float angle, out Quaternion result) + { + float sin_a = (float)Math.Sin(angle / 2.0f); + result.X = axis.X * sin_a; + result.Y = axis.Y * sin_a; + result.Z = axis.Z * sin_a; + result.W = (float)Math.Cos(angle / 2.0f); + } + + + public static Quaternion CreateFromRotationMatrix(Matrix matrix) + { + Quaternion result; + if ((matrix.M11 + matrix.M22 + matrix.M33) > 0.0F) + { + float M1 = (float)System.Math.Sqrt((double)(matrix.M11 + matrix.M22 + matrix.M33 + 1.0F)); + result.W = M1 * 0.5F; + M1 = 0.5F / M1; + result.X = (matrix.M23 - matrix.M32) * M1; + result.Y = (matrix.M31 - matrix.M13) * M1; + result.Z = (matrix.M12 - matrix.M21) * M1; + return result; + } + if ((matrix.M11 >= matrix.M22) && (matrix.M11 >= matrix.M33)) + { + float M2 = (float)System.Math.Sqrt((double)(1.0F + matrix.M11 - matrix.M22 - matrix.M33)); + float M3 = 0.5F / M2; + result.X = 0.5F * M2; + result.Y = (matrix.M12 + matrix.M21) * M3; + result.Z = (matrix.M13 + matrix.M31) * M3; + result.W = (matrix.M23 - matrix.M32) * M3; + return result; + } + if (matrix.M22 > matrix.M33) + { + float M4 = (float)System.Math.Sqrt((double)(1.0F + matrix.M22 - matrix.M11 - matrix.M33)); + float M5 = 0.5F / M4; + result.X = (matrix.M21 + matrix.M12) * M5; + result.Y = 0.5F * M4; + result.Z = (matrix.M32 + matrix.M23) * M5; + result.W = (matrix.M31 - matrix.M13) * M5; + return result; + } + float M6 = (float)System.Math.Sqrt((double)(1.0F + matrix.M33 - matrix.M11 - matrix.M22)); + float M7 = 0.5F / M6; + result.X = (matrix.M31 + matrix.M13) * M7; + result.Y = (matrix.M32 + matrix.M23) * M7; + result.Z = 0.5F * M6; + result.W = (matrix.M12 - matrix.M21) * M7; + return result; + } + + + public static void CreateFromRotationMatrix(ref Matrix matrix, out Quaternion result) + { + if ((matrix.M11 + matrix.M22 + matrix.M33) > 0.0F) + { + float M1 = (float)System.Math.Sqrt((double)(matrix.M11 + matrix.M22 + matrix.M33 + 1.0F)); + result.W = M1 * 0.5F; + M1 = 0.5F / M1; + result.X = (matrix.M23 - matrix.M32) * M1; + result.Y = (matrix.M31 - matrix.M13) * M1; + result.Z = (matrix.M12 - matrix.M21) * M1; + return; + } + if ((matrix.M11 >= matrix.M22) && (matrix.M11 >= matrix.M33)) + { + float M2 = (float)System.Math.Sqrt((double)(1.0F + matrix.M11 - matrix.M22 - matrix.M33)); + float M3 = 0.5F / M2; + result.X = 0.5F * M2; + result.Y = (matrix.M12 + matrix.M21) * M3; + result.Z = (matrix.M13 + matrix.M31) * M3; + result.W = (matrix.M23 - matrix.M32) * M3; + return; + } + if (matrix.M22 > matrix.M33) + { + float M4 = (float)System.Math.Sqrt((double)(1.0F + matrix.M22 - matrix.M11 - matrix.M33)); + float M5 = 0.5F / M4; + result.X = (matrix.M21 + matrix.M12) * M5; + result.Y = 0.5F * M4; + result.Z = (matrix.M32 + matrix.M23) * M5; + result.W = (matrix.M31 - matrix.M13) * M5; + return; + } + float M6 = (float)System.Math.Sqrt((double)(1.0F + matrix.M33 - matrix.M11 - matrix.M22)); + float M7 = 0.5F / M6; + result.X = (matrix.M31 + matrix.M13) * M7; + result.Y = (matrix.M32 + matrix.M23) * M7; + result.Z = 0.5F * M6; + result.W = (matrix.M12 - matrix.M21) * M7; + } + + + public static Quaternion Divide(Quaternion quaternion1, Quaternion quaternion2) + { + Quaternion result; + + float w5 = 1.0F / ((quaternion2.X * quaternion2.X) + (quaternion2.Y * quaternion2.Y) + (quaternion2.Z * quaternion2.Z) + (quaternion2.W * quaternion2.W)); + float w4 = -quaternion2.X * w5; + float w3 = -quaternion2.Y * w5; + float w2 = -quaternion2.Z * w5; + float w1 = quaternion2.W * w5; + + result.X = (quaternion1.X * w1) + (w4 * quaternion1.W) + ((quaternion1.Y * w2) - (quaternion1.Z * w3)); + result.Y = (quaternion1.Y * w1) + (w3 * quaternion1.W) + ((quaternion1.Z * w4) - (quaternion1.X * w2)); + result.Z = (quaternion1.Z * w1) + (w2 * quaternion1.W) + ((quaternion1.X * w3) - (quaternion1.Y * w4)); + result.W = (quaternion1.W * quaternion2.W * w5) - ((quaternion1.X * w4) + (quaternion1.Y * w3) + (quaternion1.Z * w2)); + return result; + } + + + public static void Divide(ref Quaternion quaternion1, ref Quaternion quaternion2, out Quaternion result) + { + float w5 = 1.0F / ((quaternion2.X * quaternion2.X) + (quaternion2.Y * quaternion2.Y) + (quaternion2.Z * quaternion2.Z) + (quaternion2.W * quaternion2.W)); + float w4 = -quaternion2.X * w5; + float w3 = -quaternion2.Y * w5; + float w2 = -quaternion2.Z * w5; + float w1 = quaternion2.W * w5; + + result.X = (quaternion1.X * w1) + (w4 * quaternion1.W) + ((quaternion1.Y * w2) - (quaternion1.Z * w3)); + result.Y = (quaternion1.Y * w1) + (w3 * quaternion1.W) + ((quaternion1.Z * w4) - (quaternion1.X * w2)); + result.Z = (quaternion1.Z * w1) + (w2 * quaternion1.W) + ((quaternion1.X * w3) - (quaternion1.Y * w4)); + result.W = (quaternion1.W * quaternion2.W * w5) - ((quaternion1.X * w4) + (quaternion1.Y * w3) + (quaternion1.Z * w2)); + } + + + public static float Dot(Quaternion quaternion1, Quaternion quaternion2) + { + return (quaternion1.X * quaternion2.X) + (quaternion1.Y * quaternion2.Y) + (quaternion1.Z * quaternion2.Z) + (quaternion1.W * quaternion2.W); + } + + + public static void Dot(ref Quaternion quaternion1, ref Quaternion quaternion2, out float result) + { + result = (quaternion1.X * quaternion2.X) + (quaternion1.Y * quaternion2.Y) + (quaternion1.Z * quaternion2.Z) + (quaternion1.W * quaternion2.W); + } + + + public override bool Equals(object obj) + { + return (obj is Quaternion) ? this == (Quaternion)obj : false; + } + + + public bool Equals(Quaternion other) + { + if ((X == other.X) && (Y == other.Y) && (Z == other.Z)) + return W == other.W; + return false; + } + + + public override int GetHashCode() + { + return X.GetHashCode() + Y.GetHashCode() + Z.GetHashCode() + W.GetHashCode(); + } + + + public static Quaternion Inverse(Quaternion quaternion) + { + Quaternion result; + float m1 = 1.0F / ((quaternion.X * quaternion.X) + (quaternion.Y * quaternion.Y) + (quaternion.Z * quaternion.Z) + (quaternion.W * quaternion.W)); + result.X = -quaternion.X * m1; + result.Y = -quaternion.Y * m1; + result.Z = -quaternion.Z * m1; + result.W = quaternion.W * m1; + return result; + } + + + public static void Inverse(ref Quaternion quaternion, out Quaternion result) + { + float m1 = 1.0F / ((quaternion.X * quaternion.X) + (quaternion.Y * quaternion.Y) + (quaternion.Z * quaternion.Z) + (quaternion.W * quaternion.W)); + result.X = -quaternion.X * m1; + result.Y = -quaternion.Y * m1; + result.Z = -quaternion.Z * m1; + result.W = quaternion.W * m1; + } + + + public float Length() + { + return (float)System.Math.Sqrt((double)((X * X) + (Y * Y) + (Z * Z) + (W * W))); + } + + + public float LengthSquared() + { + return (X * X) + (Y * Y) + (Z * Z) + (W * W); + } + + + public static Quaternion Lerp(Quaternion quaternion1, Quaternion quaternion2, float amount) + { + Quaternion result; + float f2 = 1.0F - amount; + if (((quaternion1.X * quaternion2.X) + (quaternion1.Y * quaternion2.Y) + (quaternion1.Z * quaternion2.Z) + (quaternion1.W * quaternion2.W)) >= 0.0F) + { + result.X = (f2 * quaternion1.X) + (amount * quaternion2.X); + result.Y = (f2 * quaternion1.Y) + (amount * quaternion2.Y); + result.Z = (f2 * quaternion1.Z) + (amount * quaternion2.Z); + result.W = (f2 * quaternion1.W) + (amount * quaternion2.W); + } + else + { + result.X = (f2 * quaternion1.X) - (amount * quaternion2.X); + result.Y = (f2 * quaternion1.Y) - (amount * quaternion2.Y); + result.Z = (f2 * quaternion1.Z) - (amount * quaternion2.Z); + result.W = (f2 * quaternion1.W) - (amount * quaternion2.W); + } + float f4 = (result.X * result.X) + (result.Y * result.Y) + (result.Z * result.Z) + (result.W * result.W); + float f3 = 1.0F / (float)System.Math.Sqrt((double)f4); + result.X *= f3; + result.Y *= f3; + result.Z *= f3; + result.W *= f3; + return result; + } + + + public static void Lerp(ref Quaternion quaternion1, ref Quaternion quaternion2, float amount, out Quaternion result) + { + float m2 = 1.0F - amount; + if (((quaternion1.X * quaternion2.X) + (quaternion1.Y * quaternion2.Y) + (quaternion1.Z * quaternion2.Z) + (quaternion1.W * quaternion2.W)) >= 0.0F) + { + result.X = (m2 * quaternion1.X) + (amount * quaternion2.X); + result.Y = (m2 * quaternion1.Y) + (amount * quaternion2.Y); + result.Z = (m2 * quaternion1.Z) + (amount * quaternion2.Z); + result.W = (m2 * quaternion1.W) + (amount * quaternion2.W); + } + else + { + result.X = (m2 * quaternion1.X) - (amount * quaternion2.X); + result.Y = (m2 * quaternion1.Y) - (amount * quaternion2.Y); + result.Z = (m2 * quaternion1.Z) - (amount * quaternion2.Z); + result.W = (m2 * quaternion1.W) - (amount * quaternion2.W); + } + float m4 = (result.X * result.X) + (result.Y * result.Y) + (result.Z * result.Z) + (result.W * result.W); + float m3 = 1.0F / (float)System.Math.Sqrt((double)m4); + result.X *= m3; + result.Y *= m3; + result.Z *= m3; + result.W *= m3; + } + + + public static Quaternion Slerp(Quaternion quaternion1, Quaternion quaternion2, float amount) + { + Quaternion result; + float q2, q3; + + float q4 = (quaternion1.X * quaternion2.X) + (quaternion1.Y * quaternion2.Y) + (quaternion1.Z * quaternion2.Z) + (quaternion1.W * quaternion2.W); + bool flag = false; + if (q4 < 0.0F) + { + flag = true; + q4 = -q4; + } + if (q4 > 0.999999F) + { + q3 = 1.0F - amount; + q2 = flag ? -amount : amount; + } + else + { + float q5 = (float)System.Math.Acos((double)q4); + float q6 = (float)(1.0 / System.Math.Sin((double)q5)); + q3 = (float)System.Math.Sin((double)((1.0F - amount) * q5)) * q6; + q2 = flag ? (float)-System.Math.Sin((double)(amount * q5)) * q6 : (float)System.Math.Sin((double)(amount * q5)) * q6; + } + result.X = (q3 * quaternion1.X) + (q2 * quaternion2.X); + result.Y = (q3 * quaternion1.Y) + (q2 * quaternion2.Y); + result.Z = (q3 * quaternion1.Z) + (q2 * quaternion2.Z); + result.W = (q3 * quaternion1.W) + (q2 * quaternion2.W); + return result; + } + + + public static void Slerp(ref Quaternion quaternion1, ref Quaternion quaternion2, float amount, out Quaternion result) + { + float q2, q3; + + float q4 = (quaternion1.X * quaternion2.X) + (quaternion1.Y * quaternion2.Y) + (quaternion1.Z * quaternion2.Z) + (quaternion1.W * quaternion2.W); + bool flag = false; + if (q4 < 0.0F) + { + flag = true; + q4 = -q4; + } + if (q4 > 0.999999F) + { + q3 = 1.0F - amount; + q2 = flag ? -amount : amount; + } + else + { + float q5 = (float)System.Math.Acos((double)q4); + float q6 = (float)(1.0 / System.Math.Sin((double)q5)); + q3 = (float)System.Math.Sin((double)((1.0F - amount) * q5)) * q6; + q2 = flag ? (float)-System.Math.Sin((double)(amount * q5)) * q6 : (float)System.Math.Sin((double)(amount * q5)) * q6; + } + result.X = (q3 * quaternion1.X) + (q2 * quaternion2.X); + result.Y = (q3 * quaternion1.Y) + (q2 * quaternion2.Y); + result.Z = (q3 * quaternion1.Z) + (q2 * quaternion2.Z); + result.W = (q3 * quaternion1.W) + (q2 * quaternion2.W); + } + + + public static Quaternion Subtract(Quaternion quaternion1, Quaternion quaternion2) + { + quaternion1.X -= quaternion2.X; + quaternion1.Y -= quaternion2.Y; + quaternion1.Z -= quaternion2.Z; + quaternion1.W -= quaternion2.W; + return quaternion1; + } + + + public static void Subtract(ref Quaternion quaternion1, ref Quaternion quaternion2, out Quaternion result) + { + result.X = quaternion1.X - quaternion2.X; + result.Y = quaternion1.Y - quaternion2.Y; + result.Z = quaternion1.Z - quaternion2.Z; + result.W = quaternion1.W - quaternion2.W; + } + + + public static Quaternion Multiply(Quaternion quaternion1, Quaternion quaternion2) + { + Quaternion result; + float f12 = (quaternion1.Y * quaternion2.Z) - (quaternion1.Z * quaternion2.Y); + float f11 = (quaternion1.Z * quaternion2.X) - (quaternion1.X * quaternion2.Z); + float f10 = (quaternion1.X * quaternion2.Y) - (quaternion1.Y * quaternion2.X); + float f9 = (quaternion1.X * quaternion2.X) + (quaternion1.Y * quaternion2.Y) + (quaternion1.Z * quaternion2.Z); + result.X = (quaternion1.X * quaternion2.W) + (quaternion2.X * quaternion1.W) + f12; + result.Y = (quaternion1.Y * quaternion2.W) + (quaternion2.Y * quaternion1.W) + f11; + result.Z = (quaternion1.Z * quaternion2.W) + (quaternion2.Z * quaternion1.W) + f10; + result.W = (quaternion1.W * quaternion2.W) - f9; + return result; + } + + + public static Quaternion Multiply(Quaternion quaternion1, float scaleFactor) + { + quaternion1.X *= scaleFactor; + quaternion1.Y *= scaleFactor; + quaternion1.Z *= scaleFactor; + quaternion1.W *= scaleFactor; + return quaternion1; + } + + + public static void Multiply(ref Quaternion quaternion1, float scaleFactor, out Quaternion result) + { + result.X = quaternion1.X * scaleFactor; + result.Y = quaternion1.Y * scaleFactor; + result.Z = quaternion1.Z * scaleFactor; + result.W = quaternion1.W * scaleFactor; + } + + + public static void Multiply(ref Quaternion quaternion1, ref Quaternion quaternion2, out Quaternion result) + { + float f12 = (quaternion1.Y * quaternion2.Z) - (quaternion1.Z * quaternion2.Y); + float f11 = (quaternion1.Z * quaternion2.X) - (quaternion1.X * quaternion2.Z); + float f10 = (quaternion1.X * quaternion2.Y) - (quaternion1.Y * quaternion2.X); + float f9 = (quaternion1.X * quaternion2.X) + (quaternion1.Y * quaternion2.Y) + (quaternion1.Z * quaternion2.Z); + result.X = (quaternion1.X * quaternion2.W) + (quaternion2.X * quaternion1.W) + f12; + result.Y = (quaternion1.Y * quaternion2.W) + (quaternion2.Y * quaternion1.W) + f11; + result.Z = (quaternion1.Z * quaternion2.W) + (quaternion2.Z * quaternion1.W) + f10; + result.W = (quaternion1.W * quaternion2.W) - f9; + } + + + public static Quaternion Negate(Quaternion quaternion) + { + Quaternion result; + result.X = -quaternion.X; + result.Y = -quaternion.Y; + result.Z = -quaternion.Z; + result.W = -quaternion.W; + return result; + } + + + public static void Negate(ref Quaternion quaternion, out Quaternion result) + { + result.X = -quaternion.X; + result.Y = -quaternion.Y; + result.Z = -quaternion.Z; + result.W = -quaternion.W; + } + + + public void Normalize() + { + float f1 = 1.0F / (float)System.Math.Sqrt((double)((this.X * this.X) + (this.Y * this.Y) + (this.Z * this.Z) + (this.W * this.W))); + this.X *= f1; + this.Y *= f1; + this.Z *= f1; + this.W *= f1; + } + + + public static Quaternion Normalize(Quaternion quaternion) + { + Quaternion result; + float f1 = 1.0F / (float)System.Math.Sqrt((double)((quaternion.X * quaternion.X) + (quaternion.Y * quaternion.Y) + (quaternion.Z * quaternion.Z) + (quaternion.W * quaternion.W))); + result.X = quaternion.X * f1; + result.Y = quaternion.Y * f1; + result.Z = quaternion.Z * f1; + result.W = quaternion.W * f1; + return result; + } + + + public static void Normalize(ref Quaternion quaternion, out Quaternion result) + { + float f1 = 1.0F / (float)System.Math.Sqrt((double)((quaternion.X * quaternion.X) + (quaternion.Y * quaternion.Y) + (quaternion.Z * quaternion.Z) + (quaternion.W * quaternion.W))); + result.X = quaternion.X * f1; + result.Y = quaternion.Y * f1; + result.Z = quaternion.Z * f1; + result.W = quaternion.W * f1; + } + + + public static Quaternion operator +(Quaternion quaternion1, Quaternion quaternion2) + { + quaternion1.X += quaternion2.X; + quaternion1.Y += quaternion2.Y; + quaternion1.Z += quaternion2.Z; + quaternion1.W += quaternion2.W; + return quaternion1; + } + + + public static Quaternion operator /(Quaternion quaternion1, Quaternion quaternion2) + { + Quaternion result; + + float w5 = 1.0F / ((quaternion2.X * quaternion2.X) + (quaternion2.Y * quaternion2.Y) + (quaternion2.Z * quaternion2.Z) + (quaternion2.W * quaternion2.W)); + float w4 = -quaternion2.X * w5; + float w3 = -quaternion2.Y * w5; + float w2 = -quaternion2.Z * w5; + float w1 = quaternion2.W * w5; + + result.X = (quaternion1.X * w1) + (w4 * quaternion1.W) + ((quaternion1.Y * w2) - (quaternion1.Z * w3)); + result.Y = (quaternion1.Y * w1) + (w3 * quaternion1.W) + ((quaternion1.Z * w4) - (quaternion1.X * w2)); + result.Z = (quaternion1.Z * w1) + (w2 * quaternion1.W) + ((quaternion1.X * w3) - (quaternion1.Y * w4)); + result.W = (quaternion1.W * quaternion2.W * w5) - ((quaternion1.X * w4) + (quaternion1.Y * w3) + (quaternion1.Z * w2)); + return result; + } + + + public static bool operator ==(Quaternion quaternion1, Quaternion quaternion2) + { + return quaternion1.X == quaternion2.X + && quaternion1.Y == quaternion2.Y + && quaternion1.Z == quaternion2.Z + && quaternion1.W == quaternion2.W; + } + + + public static bool operator !=(Quaternion quaternion1, Quaternion quaternion2) + { + return quaternion1.X != quaternion2.X + || quaternion1.Y != quaternion2.Y + || quaternion1.Z != quaternion2.Z + || quaternion1.W != quaternion2.W; + } + + + public static Quaternion operator *(Quaternion quaternion1, Quaternion quaternion2) + { + Quaternion result; + float f12 = (quaternion1.Y * quaternion2.Z) - (quaternion1.Z * quaternion2.Y); + float f11 = (quaternion1.Z * quaternion2.X) - (quaternion1.X * quaternion2.Z); + float f10 = (quaternion1.X * quaternion2.Y) - (quaternion1.Y * quaternion2.X); + float f9 = (quaternion1.X * quaternion2.X) + (quaternion1.Y * quaternion2.Y) + (quaternion1.Z * quaternion2.Z); + result.X = (quaternion1.X * quaternion2.W) + (quaternion2.X * quaternion1.W) + f12; + result.Y = (quaternion1.Y * quaternion2.W) + (quaternion2.Y * quaternion1.W) + f11; + result.Z = (quaternion1.Z * quaternion2.W) + (quaternion2.Z * quaternion1.W) + f10; + result.W = (quaternion1.W * quaternion2.W) - f9; + return result; + } + + + public static Quaternion operator *(Quaternion quaternion1, float scaleFactor) + { + quaternion1.X *= scaleFactor; + quaternion1.Y *= scaleFactor; + quaternion1.Z *= scaleFactor; + quaternion1.W *= scaleFactor; + return quaternion1; + } + + + public static Quaternion operator -(Quaternion quaternion1, Quaternion quaternion2) + { + quaternion1.X -= quaternion2.X; + quaternion1.Y -= quaternion2.Y; + quaternion1.Z -= quaternion2.Z; + quaternion1.W -= quaternion2.W; + return quaternion1; + } + + + public static Quaternion operator -(Quaternion quaternion) + { + quaternion.X = -quaternion.X; + quaternion.Y = -quaternion.Y; + quaternion.Z = -quaternion.Z; + quaternion.W = -quaternion.W; + return quaternion; + } + + + public override string ToString() + { + StringBuilder sb = new StringBuilder(32); + sb.Append("{X:"); + sb.Append(this.X); + sb.Append(" Y:"); + sb.Append(this.Y); + sb.Append(" Z:"); + sb.Append(this.Z); + sb.Append(" W:"); + sb.Append(this.W); + sb.Append("}"); + return sb.ToString(); + } + + } +} diff --git a/Bizware/BizHawk.Bizware.BizwareGL/Types/Ray.cs b/Bizware/BizHawk.Bizware.BizwareGL/Types/Ray.cs new file mode 100644 index 0000000000..ecfdb39c26 --- /dev/null +++ b/Bizware/BizHawk.Bizware.BizwareGL/Types/Ray.cs @@ -0,0 +1,233 @@ +#region License +/* +MIT License +Copyright © 2006 The Mono.Xna Team + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +#endregion License + +using System; +using System.ComponentModel; + +namespace BizHawk.Bizware.BizwareGL +{ + [Serializable] + public struct Ray : IEquatable + { + #region Public Fields + + public Vector3 Direction; + public Vector3 Position; + + #endregion + + + #region Public Constructors + + public Ray(Vector3 position, Vector3 direction) + { + this.Position = position; + this.Direction = direction; + } + + #endregion + + + #region Public Methods + + public override bool Equals(object obj) + { + return (obj is Ray) ? this.Equals((Ray)obj) : false; + } + + + public bool Equals(Ray other) + { + return this.Position.Equals(other.Position) && this.Direction.Equals(other.Direction); + } + + + public override int GetHashCode() + { + return Position.GetHashCode() ^ Direction.GetHashCode(); + } + + + public float? Intersects(BoundingBox box) + { + //first test if start in box + if (Position.X >= box.Min.X + && Position.X <= box.Max.X + && Position.Y >= box.Min.Y + && Position.Y <= box.Max.Y + && Position.Z >= box.Min.Z + && Position.Z <= box.Max.Z) + return 0.0f;// here we concidere cube is full and origine is in cube so intersect at origine + + //Second we check each face + Vector3 maxT = new Vector3(-1.0f); + //Vector3 minT = new Vector3(-1.0f); + //calcul intersection with each faces + if (Position.X < box.Min.X && Direction.X != 0.0f) + maxT.X = (box.Min.X - Position.X) / Direction.X; + else if (Position.X > box.Max.X && Direction.X != 0.0f) + maxT.X = (box.Max.X - Position.X) / Direction.X; + if (Position.Y < box.Min.Y && Direction.Y != 0.0f) + maxT.Y = (box.Min.Y - Position.Y) / Direction.Y; + else if (Position.Y > box.Max.Y && Direction.Y != 0.0f) + maxT.Y = (box.Max.Y - Position.Y) / Direction.Y; + if (Position.Z < box.Min.Z && Direction.Z != 0.0f) + maxT.Z = (box.Min.Z - Position.Z) / Direction.Z; + else if (Position.Z > box.Max.Z && Direction.Z != 0.0f) + maxT.Z = (box.Max.Z - Position.Z) / Direction.Z; + + //get the maximum maxT + if (maxT.X > maxT.Y && maxT.X > maxT.Z) + { + if (maxT.X < 0.0f) + return null;// ray go on opposite of face + //coordonate of hit point of face of cube + float coord = Position.Z + maxT.X * Direction.Z; + // if hit point coord ( intersect face with ray) is out of other plane coord it miss + if (coord < box.Min.Z || coord > box.Max.Z) + return null; + coord = Position.Y + maxT.X * Direction.Y; + if (coord < box.Min.Y || coord > box.Max.Y) + return null; + return maxT.X; + } + if (maxT.Y > maxT.X && maxT.Y > maxT.Z) + { + if (maxT.Y < 0.0f) + return null;// ray go on opposite of face + //coordonate of hit point of face of cube + float coord = Position.Z + maxT.Y * Direction.Z; + // if hit point coord ( intersect face with ray) is out of other plane coord it miss + if (coord < box.Min.Z || coord > box.Max.Z) + return null; + coord = Position.X + maxT.Y * Direction.X; + if (coord < box.Min.X || coord > box.Max.X) + return null; + return maxT.Y; + } + else //Z + { + if (maxT.Z < 0.0f) + return null;// ray go on opposite of face + //coordonate of hit point of face of cube + float coord = Position.X + maxT.Z * Direction.X; + // if hit point coord ( intersect face with ray) is out of other plane coord it miss + if (coord < box.Min.X || coord > box.Max.X) + return null; + coord = Position.Y + maxT.Z * Direction.Y; + if (coord < box.Min.Y || coord > box.Max.Y) + return null; + return maxT.Z; + } + } + + + public void Intersects(ref BoundingBox box, out float? result) + { + result = Intersects(box); + } + + + public float? Intersects(BoundingFrustum frustum) + { + throw new NotImplementedException(); + } + + + public float? Intersects(BoundingSphere sphere) + { + float? result; + Intersects(ref sphere, out result); + return result; + } + + public float? Intersects(Plane plane) + { + throw new NotImplementedException(); + } + + public void Intersects(ref Plane plane, out float? result) + { + throw new NotImplementedException(); + } + + public void Intersects(ref BoundingSphere sphere, out float? result) + { + // Find the vector between where the ray starts the the sphere's centre + Vector3 difference = sphere.Center - this.Position; + + float differenceLengthSquared = difference.LengthSquared(); + float sphereRadiusSquared = sphere.Radius * sphere.Radius; + + float distanceAlongRay; + + // If the distance between the ray start and the sphere's centre is less than + // the radius of the sphere, it means we've intersected. N.B. checking the LengthSquared is faster. + if (differenceLengthSquared < sphereRadiusSquared) + { + result = 0.0f; + return; + } + + Vector3.Dot(ref this.Direction, ref difference, out distanceAlongRay); + // If the ray is pointing away from the sphere then we don't ever intersect + if (distanceAlongRay < 0) + { + result = null; + return; + } + + // Next we kinda use Pythagoras to check if we are within the bounds of the sphere + // if x = radius of sphere + // if y = distance between ray position and sphere centre + // if z = the distance we've travelled along the ray + // if x^2 + z^2 - y^2 < 0, we do not intersect + float dist = sphereRadiusSquared + distanceAlongRay * distanceAlongRay - differenceLengthSquared; + + result = (dist < 0) ? null : distanceAlongRay - (float?)Math.Sqrt(dist); + } + + + public static bool operator !=(Ray a, Ray b) + { + return !a.Equals(b); + } + + + public static bool operator ==(Ray a, Ray b) + { + return a.Equals(b); + } + + + public override string ToString() + { + return string.Format("{{Position:{0} Direction:{1}}}", Position.ToString(), Direction.ToString()); + } + #endregion + } +} diff --git a/Bizware/BizHawk.Bizware.BizwareGL/Types/Rectangle.cs b/Bizware/BizHawk.Bizware.BizwareGL/Types/Rectangle.cs new file mode 100644 index 0000000000..69924053e0 --- /dev/null +++ b/Bizware/BizHawk.Bizware.BizwareGL/Types/Rectangle.cs @@ -0,0 +1,312 @@ +#region License +/* +MIT License +Copyright © 2006 The Mono.Xna Team + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +#endregion License + +using System; +using System.Globalization; +using System.ComponentModel; + + +namespace BizHawk.Bizware.BizwareGL +{ + + + public struct Rectangle : IEquatable + { + #region Private Fields + + private static Rectangle emptyRectangle = new Rectangle(); + + #endregion Private Fields + + + #region Public Fields + + public int X; + public int Y; + public int Width; + public int Height; + + #endregion Public Fields + + + #region Public Properties + + public Point Center + { + get + { + return new Point(this.X + (this.Width / 2), this.Y + (this.Height / 2)); + } + } + + public Point Location + { + get + { + return new Point(this.X, this.Y); + } + set + { + this.X = value.X; + this.Y = value.Y; + } + } + + public bool IsEmpty + { + get + { + return ((((this.Width == 0) && (this.Height == 0)) && (this.X == 0)) && (this.Y == 0)); + } + } + + public static Rectangle Empty + { + get { return emptyRectangle; } + } + + public int Left + { + get { return this.X; } + } + + public int Right + { + get { return (this.X + this.Width); } + } + + public int Top + { + get { return this.Y; } + } + + public int Bottom + { + get { return (this.Y + this.Height); } + } + + #endregion Public Properties + + + #region Constructors + + public Rectangle(int x, int y, int width, int height) + { + this.X = x; + this.Y = y; + this.Width = width; + this.Height = height; + } + + #endregion Constructors + + + #region Public Methods + + public static Rectangle Union(Rectangle value1, Rectangle value2) + { + throw new NotImplementedException(); + } + + public static void Union(ref Rectangle value1, ref Rectangle value2, out Rectangle result) + { + throw new NotImplementedException(); + } + + public static Rectangle Intersect(Rectangle value1, Rectangle value2) + { + throw new NotImplementedException(); + } + + public void Intersects(ref Rectangle value, out bool result) + { + result = (((value.X < (this.X + this.Width)) && (this.X < (value.X + value.Width))) && (value.Y < (this.Y + this.Height))) && (this.Y < (value.Y + value.Height)); + } + + public bool Contains(Point value) + { + return ((((this.X <= value.X) && (value.X < (this.X + this.Width))) && (this.Y <= value.Y)) && (value.Y < (this.Y + this.Height))); + } + + public bool Contains(Rectangle value) + { + return ((((this.X <= value.X) && ((value.X + value.Width) <= (this.X + this.Width))) && (this.Y <= value.Y)) && ((value.Y + value.Height) <= (this.Y + this.Height))); + } + + public void Contains(ref Rectangle value, out bool result) + { + result = (((this.X <= value.X) && ((value.X + value.Width) <= (this.X + this.Width))) && (this.Y <= value.Y)) && ((value.Y + value.Height) <= (this.Y + this.Height)); + } + + public bool Contains(int x, int y) + { + return ((((this.X <= x) && (x < (this.X + this.Width))) && (this.Y <= y)) && (y < (this.Y + this.Height))); + } + + public void Contains(ref Point value, out bool result) + { + result = (((this.X <= value.X) && (value.X < (this.X + this.Width))) && (this.Y <= value.Y)) && (value.Y < (this.Y + this.Height)); + } + + public static void Intersect(ref Rectangle value1, ref Rectangle value2, out Rectangle result) + { + throw new NotImplementedException(); + } + + public static bool operator ==(Rectangle a, Rectangle b) + { + return ((a.X == b.X) && (a.Y == b.Y) && (a.Width == b.Width) && (a.Height == b.Height)); + } + + public static bool operator !=(Rectangle a, Rectangle b) + { + return !(a == b); + } + + /// + /// Moves Rectangle for both Point values. + /// + /// + /// A + /// + public void Offset(Point offset) + { + X += offset.X; + Y += offset.Y; + } + + /// + /// Moves rectangle for both values. + /// + /// + /// A + /// + /// + /// A + /// + public void Offset(int offsetX, int offsetY) + { + X += offsetX; + Y += offsetY; + } + + /// + /// Grows the Rectangle. Down right point is in the same position. + /// + /// + /// A + /// + /// + /// A + /// + public void Inflate(int horizontalValue, int verticalValue) + { + X -= horizontalValue; + Y -= verticalValue; + Width += horizontalValue * 2; + Height += verticalValue * 2; + } + + /// + /// It checks if two rectangle intersects. + /// + /// + /// A + /// + /// + /// A + /// + public bool Intersects(Rectangle rect) + { + if(this.X <= rect.X) + { + if((this.X + this.Width) > rect.X) + { + if(this.Y < rect.Y) + { + if((this.Y + this.Height) > rect.Y) + return true; + } + else + { + if((rect.Y + rect.Height) > this.Y) + return true; + } + } + } + else + { + if((rect.X + rect.Width) > this.X) + { + if(this.Y < rect.Y) + { + if((this.Y + this.Height) > rect.Y) + return true; + } + else + { + if((rect.Y + rect.Height) > this.Y) + return true; + } + } + } + return false; + } + + + public bool Equals(Rectangle other) + { + return this == other; + } + + /// + /// Returns true if recangles are same + /// + /// + /// A + /// + /// + /// A + /// + public override bool Equals(object obj) + { + return (obj is Rectangle) ? this == ((Rectangle)obj) : false; + } + + public override string ToString() + { + return string.Format("{{X:{0} Y:{1} Width:{2} Height:{3}}}", X, Y, Width, Height); + } + + public override int GetHashCode() + { + return (this.X ^ this.Y ^ this.Width ^ this.Height); + } + + #endregion Public Methods + } +} diff --git a/Bizware/BizHawk.Bizware.BizwareGL/Types/Size.cs b/Bizware/BizHawk.Bizware.BizwareGL/Types/Size.cs new file mode 100644 index 0000000000..0524e0e10e --- /dev/null +++ b/Bizware/BizHawk.Bizware.BizwareGL/Types/Size.cs @@ -0,0 +1,368 @@ +// +// System.Drawing.Size.cs +// +// Author: +// Mike Kestner (mkestner@speakeasy.net) +// +// Copyright (C) 2001 Mike Kestner +// Copyright (C) 2004 Novell, Inc. http://www.novell.com +// + +//The MIT license: + +// +// Copyright (C) 2004 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Runtime.Serialization; +using System.Runtime.InteropServices; +using System.ComponentModel; + +namespace BizHawk.Bizware.BizwareGL +{ + [Serializable] + [ComVisible(true)] + public struct Size + { + + // Private Height and width fields. + private int width, height; + + // ----------------------- + // Public Shared Members + // ----------------------- + + /// + /// Empty Shared Field + /// + /// + /// + /// An uninitialized Size Structure. + /// + + public static readonly Size Empty; + + /// + /// Ceiling Shared Method + /// + /// + /// + /// Produces a Size structure from a SizeF structure by + /// taking the ceiling of the Width and Height properties. + /// + + public static Size Ceiling(SizeF value) + { + int w, h; + checked + { + w = (int)Math.Ceiling(value.Width); + h = (int)Math.Ceiling(value.Height); + } + + return new Size(w, h); + } + + /// + /// Round Shared Method + /// + /// + /// + /// Produces a Size structure from a SizeF structure by + /// rounding the Width and Height properties. + /// + + public static Size Round(SizeF value) + { + int w, h; + checked + { + w = (int)Math.Round(value.Width); + h = (int)Math.Round(value.Height); + } + + return new Size(w, h); + } + + /// + /// Truncate Shared Method + /// + /// + /// + /// Produces a Size structure from a SizeF structure by + /// truncating the Width and Height properties. + /// + + public static Size Truncate(SizeF value) + { + int w, h; + checked + { + w = (int)value.Width; + h = (int)value.Height; + } + + return new Size(w, h); + } + + /// + /// Addition Operator + /// + /// + /// + /// Addition of two Size structures. + /// + + public static Size operator +(Size sz1, Size sz2) + { + return new Size(sz1.Width + sz2.Width, + sz1.Height + sz2.Height); + } + + /// + /// Equality Operator + /// + /// + /// + /// Compares two Size objects. The return value is + /// based on the equivalence of the Width and Height + /// properties of the two Sizes. + /// + + public static bool operator ==(Size sz1, Size sz2) + { + return ((sz1.Width == sz2.Width) && + (sz1.Height == sz2.Height)); + } + + /// + /// Inequality Operator + /// + /// + /// + /// Compares two Size objects. The return value is + /// based on the equivalence of the Width and Height + /// properties of the two Sizes. + /// + + public static bool operator !=(Size sz1, Size sz2) + { + return ((sz1.Width != sz2.Width) || + (sz1.Height != sz2.Height)); + } + + /// + /// Subtraction Operator + /// + /// + /// + /// Subtracts two Size structures. + /// + + public static Size operator -(Size sz1, Size sz2) + { + return new Size(sz1.Width - sz2.Width, + sz1.Height - sz2.Height); + } + + /// + /// Size to Point Conversion + /// + /// + /// + /// Returns a Point based on the dimensions of a given + /// Size. Requires explicit cast. + /// + + public static explicit operator Point(Size size) + { + return new Point(size.Width, size.Height); + } + + /// + /// Size to SizeF Conversion + /// + /// + /// + /// Creates a SizeF based on the dimensions of a given + /// Size. No explicit cast is required. + /// + + public static implicit operator SizeF(Size p) + { + return new SizeF(p.Width, p.Height); + } + + + // ----------------------- + // Public Constructors + // ----------------------- + + /// + /// Size Constructor + /// + /// + /// + /// Creates a Size from a Point value. + /// + + public Size(Point pt) + { + width = pt.X; + height = pt.Y; + } + + /// + /// Size Constructor + /// + /// + /// + /// Creates a Size from specified dimensions. + /// + + public Size(int width, int height) + { + this.width = width; + this.height = height; + } + + // ----------------------- + // Public Instance Members + // ----------------------- + + /// + /// IsEmpty Property + /// + /// + /// + /// Indicates if both Width and Height are zero. + /// + + [Browsable(false)] + public bool IsEmpty + { + get + { + return ((width == 0) && (height == 0)); + } + } + + /// + /// Width Property + /// + /// + /// + /// The Width coordinate of the Size. + /// + + public int Width + { + get + { + return width; + } + set + { + width = value; + } + } + + /// + /// Height Property + /// + /// + /// + /// The Height coordinate of the Size. + /// + + public int Height + { + get + { + return height; + } + set + { + height = value; + } + } + + /// + /// Equals Method + /// + /// + /// + /// Checks equivalence of this Size and another object. + /// + + public override bool Equals(object obj) + { + if (!(obj is Size)) + return false; + + return (this == (Size)obj); + } + + /// + /// GetHashCode Method + /// + /// + /// + /// Calculates a hashing value. + /// + + public override int GetHashCode() + { + return width ^ height; + } + + /// + /// ToString Method + /// + /// + /// + /// Formats the Size as a string in coordinate notation. + /// + + public override string ToString() + { + return String.Format("{{Width={0}, Height={1}}}", width, height); + } + +#if NET_2_0 + public static Size Add (Size sz1, Size sz2) + { + return new Size (sz1.Width + sz2.Width, + sz1.Height + sz2.Height); + + } + + public static Size Subtract (Size sz1, Size sz2) + { + return new Size (sz1.Width - sz2.Width, + sz1.Height - sz2.Height); + } +#endif + + } +} diff --git a/Bizware/BizHawk.Bizware.BizwareGL/Types/Vector2.cs b/Bizware/BizHawk.Bizware.BizwareGL/Types/Vector2.cs new file mode 100644 index 0000000000..b03b511b1c --- /dev/null +++ b/Bizware/BizHawk.Bizware.BizwareGL/Types/Vector2.cs @@ -0,0 +1,599 @@ +#region License +/* +MIT License +Copyright © 2006 The Mono.Xna Team + +All rights reserved. + +Authors + * Alan McGovern + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +#endregion License + +using System; +using System.ComponentModel; + +using System.Text; +using System.Runtime.InteropServices; + +namespace BizHawk.Bizware.BizwareGL +{ + [Serializable] + [StructLayout(LayoutKind.Sequential)] + + public struct Vector2 : IEquatable + { + #region Private Fields + + private static Vector2 zeroVector = new Vector2(0f, 0f); + private static Vector2 unitVector = new Vector2(1f, 1f); + private static Vector2 unitXVector = new Vector2(1f, 0f); + private static Vector2 unitYVector = new Vector2(0f, 1f); + + #endregion Private Fields + + + #region Public Fields + + public float X; + public float Y; + + #endregion Public Fields + + + #region Properties + + public static Vector2 Zero + { + get { return zeroVector; } + } + + public static Vector2 One + { + get { return unitVector; } + } + + public static Vector2 UnitX + { + get { return unitXVector; } + } + + public static Vector2 UnitY + { + get { return unitYVector; } + } + + #endregion Properties + + + #region Constructors + + /// + /// Constructor foe standard 2D vector. + /// + /// + /// A + /// + /// + /// A + /// + public Vector2(float x, float y) + { + this.X = x; + this.Y = y; + } + + /// + /// Constructor for "square" vector. + /// + /// + /// A + /// + public Vector2(float value) + { + this.X = value; + this.Y = value; + } + + #endregion Constructors + + + #region Public Methods + + public static void Reflect(ref Vector2 vector, ref Vector2 normal, out Vector2 result) + { + float dot = Dot(vector, normal); + result.X = vector.X - ((2f * dot) * normal.X); + result.Y = vector.Y - ((2f * dot) * normal.Y); + } + + public static Vector2 Reflect(Vector2 vector, Vector2 normal) + { + Vector2 result; + Reflect(ref vector, ref normal, out result); + return result; + } + + public static Vector2 Add(Vector2 value1, Vector2 value2) + { + value1.X += value2.X; + value1.Y += value2.Y; + return value1; + } + + public static void Add(ref Vector2 value1, ref Vector2 value2, out Vector2 result) + { + result.X = value1.X + value2.X; + result.Y = value1.Y + value2.Y; + } + + public static Vector2 Barycentric(Vector2 value1, Vector2 value2, Vector2 value3, float amount1, float amount2) + { + return new Vector2( + MathHelper.Barycentric(value1.X, value2.X, value3.X, amount1, amount2), + MathHelper.Barycentric(value1.Y, value2.Y, value3.Y, amount1, amount2)); + } + + public static void Barycentric(ref Vector2 value1, ref Vector2 value2, ref Vector2 value3, float amount1, float amount2, out Vector2 result) + { + result = new Vector2( + MathHelper.Barycentric(value1.X, value2.X, value3.X, amount1, amount2), + MathHelper.Barycentric(value1.Y, value2.Y, value3.Y, amount1, amount2)); + } + + public static Vector2 CatmullRom(Vector2 value1, Vector2 value2, Vector2 value3, Vector2 value4, float amount) + { + return new Vector2( + MathHelper.CatmullRom(value1.X, value2.X, value3.X, value4.X, amount), + MathHelper.CatmullRom(value1.Y, value2.Y, value3.Y, value4.Y, amount)); + } + + public static void CatmullRom(ref Vector2 value1, ref Vector2 value2, ref Vector2 value3, ref Vector2 value4, float amount, out Vector2 result) + { + result = new Vector2( + MathHelper.CatmullRom(value1.X, value2.X, value3.X, value4.X, amount), + MathHelper.CatmullRom(value1.Y, value2.Y, value3.Y, value4.Y, amount)); + } + + public static Vector2 Clamp(Vector2 value1, Vector2 min, Vector2 max) + { + return new Vector2( + MathHelper.Clamp(value1.X, min.X, max.X), + MathHelper.Clamp(value1.Y, min.Y, max.Y)); + } + + public static void Clamp(ref Vector2 value1, ref Vector2 min, ref Vector2 max, out Vector2 result) + { + result = new Vector2( + MathHelper.Clamp(value1.X, min.X, max.X), + MathHelper.Clamp(value1.Y, min.Y, max.Y)); + } + + /// + /// Returns float precison distanve between two vectors + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + public static float Distance(Vector2 value1, Vector2 value2) + { + return (float)Math.Sqrt((value1.X - value2.X) * (value1.X - value2.X) + (value1.Y - value2.Y) * (value1.Y - value2.Y)); + } + + + public static void Distance(ref Vector2 value1, ref Vector2 value2, out float result) + { + result = (float)Math.Sqrt((value1.X - value2.X) * (value1.X - value2.X) + (value1.Y - value2.Y) * (value1.Y - value2.Y)); + } + + public static float DistanceSquared(Vector2 value1, Vector2 value2) + { + return (value1.X - value2.X) * (value1.X - value2.X) + (value1.Y - value2.Y) * (value1.Y - value2.Y); + } + + public static void DistanceSquared(ref Vector2 value1, ref Vector2 value2, out float result) + { + result = (value1.X - value2.X) * (value1.X - value2.X) + (value1.Y - value2.Y) * (value1.Y - value2.Y); + } + + /// + /// Devide first vector with the secund vector + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + public static Vector2 Divide(Vector2 value1, Vector2 value2) + { + value1.X /= value2.X; + value1.Y /= value2.Y; + return value1; + } + + public static void Divide(ref Vector2 value1, ref Vector2 value2, out Vector2 result) + { + result.X = value1.X / value2.X; + result.Y = value1.Y / value2.Y; + } + + public static Vector2 Divide(Vector2 value1, float divider) + { + float factor = 1.0f / divider; + value1.X *= factor; + value1.Y *= factor; + return value1; + } + + public static void Divide(ref Vector2 value1, float divider, out Vector2 result) + { + float factor = 1.0f / divider; + result.X = value1.X * factor; + result.Y = value1.Y * factor; + } + + public static float Dot(Vector2 value1, Vector2 value2) + { + return value1.X * value2.X + value1.Y * value2.Y; + } + + public static void Dot(ref Vector2 value1, ref Vector2 value2, out float result) + { + result = value1.X * value2.X + value1.Y * value2.Y; + } + + public override bool Equals(object obj) + { + return (obj is Vector2) ? this == ((Vector2)obj) : false; + } + + public bool Equals(Vector2 other) + { + return this == other; + } + + public override int GetHashCode() + { + return (int)(this.X + this.Y); + } + + public static Vector2 Hermite(Vector2 value1, Vector2 tangent1, Vector2 value2, Vector2 tangent2, float amount) + { + value1.X = MathHelper.Hermite(value1.X, tangent1.X, value2.X, tangent2.X, amount); + value1.Y = MathHelper.Hermite(value1.Y, tangent1.Y, value2.Y, tangent2.Y, amount); + return value1; + } + + public static void Hermite(ref Vector2 value1, ref Vector2 tangent1, ref Vector2 value2, ref Vector2 tangent2, float amount, out Vector2 result) + { + result.X = MathHelper.Hermite(value1.X, tangent1.X, value2.X, tangent2.X, amount); + result.Y = MathHelper.Hermite(value1.Y, tangent1.Y, value2.Y, tangent2.Y, amount); + } + + public float Length() + { + return (float)Math.Sqrt((double)(X * X + Y * Y)); + } + + public float LengthSquared() + { + return X * X + Y * Y; + } + + public static Vector2 Lerp(Vector2 value1, Vector2 value2, float amount) + { + return new Vector2( + MathHelper.Lerp(value1.X, value2.X, amount), + MathHelper.Lerp(value1.Y, value2.Y, amount)); + } + + public static void Lerp(ref Vector2 value1, ref Vector2 value2, float amount, out Vector2 result) + { + result = new Vector2( + MathHelper.Lerp(value1.X, value2.X, amount), + MathHelper.Lerp(value1.Y, value2.Y, amount)); + } + + public static Vector2 Max(Vector2 value1, Vector2 value2) + { + return new Vector2( + MathHelper.Max(value1.X, value2.X), + MathHelper.Max(value1.Y, value2.Y)); + } + + public static void Max(ref Vector2 value1, ref Vector2 value2, out Vector2 result) + { + result = new Vector2( + MathHelper.Max(value1.X, value2.X), + MathHelper.Max(value1.Y, value2.Y)); + } + + public static Vector2 Min(Vector2 value1, Vector2 value2) + { + return new Vector2( + MathHelper.Min(value1.X, value2.X), + MathHelper.Min(value1.Y, value2.Y)); + } + + public static void Min(ref Vector2 value1, ref Vector2 value2, out Vector2 result) + { + result = new Vector2( + MathHelper.Min(value1.X, value2.X), + MathHelper.Min(value1.Y, value2.Y)); + } + + public static Vector2 Multiply(Vector2 value1, Vector2 value2) + { + value1.X *= value2.X; + value1.Y *= value2.Y; + return value1; + } + + public static Vector2 Multiply(Vector2 value1, float scaleFactor) + { + value1.X *= scaleFactor; + value1.Y *= scaleFactor; + return value1; + } + + public static void Multiply(ref Vector2 value1, float scaleFactor, out Vector2 result) + { + result.X = value1.X * scaleFactor; + result.Y = value1.Y * scaleFactor; + } + + public static void Multiply(ref Vector2 value1, ref Vector2 value2, out Vector2 result) + { + result.X = value1.X * value2.X; + result.Y = value1.Y * value2.Y; + } + + public static Vector2 Negate(Vector2 value) + { + value.X = -value.X; + value.Y = -value.Y; + return value; + } + + public static void Negate(ref Vector2 value, out Vector2 result) + { + result.X = -value.X; + result.Y = -value.Y; + } + + public void Normalize() + { + float factor = 1f / (float)Math.Sqrt((double)(X * X + Y * Y)); + X *= factor; + Y *= factor; + } + + public static Vector2 Normalize(Vector2 value) + { + float factor = 1f / (float)Math.Sqrt((double)(value.X * value.X + value.Y * value.Y)); + value.X *= factor; + value.Y *= factor; + return value; + } + + public static void Normalize(ref Vector2 value, out Vector2 result) + { + float factor = 1f / (float)Math.Sqrt((double)(value.X * value.X + value.Y * value.Y)); + result.X = value.X * factor; + result.Y = value.Y * factor; + } + + public static Vector2 SmoothStep(Vector2 value1, Vector2 value2, float amount) + { + return new Vector2( + MathHelper.SmoothStep(value1.X, value2.X, amount), + MathHelper.SmoothStep(value1.Y, value2.Y, amount)); + } + + public static void SmoothStep(ref Vector2 value1, ref Vector2 value2, float amount, out Vector2 result) + { + result = new Vector2( + MathHelper.SmoothStep(value1.X, value2.X, amount), + MathHelper.SmoothStep(value1.Y, value2.Y, amount)); + } + + public static Vector2 Subtract(Vector2 value1, Vector2 value2) + { + value1.X -= value2.X; + value1.Y -= value2.Y; + return value1; + } + + public static void Subtract(ref Vector2 value1, ref Vector2 value2, out Vector2 result) + { + result.X = value1.X - value2.X; + result.Y = value1.Y - value2.Y; + } + + public static Vector2 Transform(Vector2 position, Matrix matrix) + { + Transform(ref position, ref matrix, out position); + return position; + } + + public static void Transform(ref Vector2 position, ref Matrix matrix, out Vector2 result) + { + result = new Vector2((position.X * matrix.M11) + (position.Y * matrix.M21) + matrix.M41, + (position.X * matrix.M12) + (position.Y * matrix.M22) + matrix.M42); + } + + public static Vector2 Transform(Vector2 value, Quaternion rotation) + { + throw new NotImplementedException(); + } + + public static void Transform(ref Vector2 value, ref Quaternion rotation, out Vector2 result) + { + throw new NotImplementedException(); + } + + public static void Transform(Vector2[] sourceArray, ref Matrix matrix, Vector2[] destinationArray) + { + throw new NotImplementedException(); + } + + public static void Transform(Vector2[] sourceArray, ref Quaternion rotation, Vector2[] destinationArray) + { + throw new NotImplementedException(); + } + + public static void Transform(Vector2[] sourceArray, int sourceIndex, ref Matrix matrix, Vector2[] destinationArray, int destinationIndex, int length) + { + throw new NotImplementedException(); + } + + public static void Transform(Vector2[] sourceArray, int sourceIndex, ref Quaternion rotation, Vector2[] destinationArray, int destinationIndex, int length) + { + throw new NotImplementedException(); + } + + public static Vector2 TransformNormal(Vector2 normal, Matrix matrix) + { + Vector2.TransformNormal(ref normal, ref matrix, out normal); + return normal; + } + + public static void TransformNormal(ref Vector2 normal, ref Matrix matrix, out Vector2 result) + { + result = new Vector2((normal.X * matrix.M11) + (normal.Y * matrix.M21), + (normal.X * matrix.M12) + (normal.Y * matrix.M22)); + } + + public static void TransformNormal(Vector2[] sourceArray, ref Matrix matrix, Vector2[] destinationArray) + { + throw new NotImplementedException(); + } + + public static void TransformNormal(Vector2[] sourceArray, int sourceIndex, ref Matrix matrix, Vector2[] destinationArray, int destinationIndex, int length) + { + throw new NotImplementedException(); + } + + public override string ToString() + { + StringBuilder sb = new StringBuilder(24); + sb.Append("{X:"); + sb.Append(this.X); + sb.Append(" Y:"); + sb.Append(this.Y); + sb.Append("}"); + return sb.ToString(); + } + + #endregion Public Methods + + + #region Operators + + public static Vector2 operator -(Vector2 value) + { + value.X = -value.X; + value.Y = -value.Y; + return value; + } + + + public static bool operator ==(Vector2 value1, Vector2 value2) + { + return value1.X == value2.X && value1.Y == value2.Y; + } + + + public static bool operator !=(Vector2 value1, Vector2 value2) + { + return value1.X != value2.X || value1.Y != value2.Y; + } + + + public static Vector2 operator +(Vector2 value1, Vector2 value2) + { + value1.X += value2.X; + value1.Y += value2.Y; + return value1; + } + + + public static Vector2 operator -(Vector2 value1, Vector2 value2) + { + value1.X -= value2.X; + value1.Y -= value2.Y; + return value1; + } + + + public static Vector2 operator *(Vector2 value1, Vector2 value2) + { + value1.X *= value2.X; + value1.Y *= value2.Y; + return value1; + } + + + public static Vector2 operator *(Vector2 value, float scaleFactor) + { + value.X *= scaleFactor; + value.Y *= scaleFactor; + return value; + } + + + public static Vector2 operator *(float scaleFactor, Vector2 value) + { + value.X *= scaleFactor; + value.Y *= scaleFactor; + return value; + } + + + public static Vector2 operator /(Vector2 value1, Vector2 value2) + { + value1.X /= value2.X; + value1.Y /= value2.Y; + return value1; + } + + + public static Vector2 operator /(Vector2 value1, float divider) + { + float factor = 1 / divider; + value1.X *= factor; + value1.Y *= factor; + return value1; + } + + #endregion Operators + } +} diff --git a/Bizware/BizHawk.Bizware.BizwareGL/Types/Vector3.cs b/Bizware/BizHawk.Bizware.BizwareGL/Types/Vector3.cs new file mode 100644 index 0000000000..7a93a379fc --- /dev/null +++ b/Bizware/BizHawk.Bizware.BizwareGL/Types/Vector3.cs @@ -0,0 +1,679 @@ +#region License +/* +MIT License +Copyright © 2006 The Mono.Xna Team + +All rights reserved. + +Authors: + * Alan McGovern + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +#endregion License + +using System; +using System.ComponentModel; + +using System.Text; +using System.Runtime.InteropServices; + +namespace BizHawk.Bizware.BizwareGL +{ + [Serializable] + [StructLayout(LayoutKind.Sequential)] + + public struct Vector3 : IEquatable + { + #region Private Fields + + private static Vector3 zero = new Vector3(0f, 0f, 0f); + private static Vector3 one = new Vector3(1f, 1f, 1f); + private static Vector3 unitX = new Vector3(1f, 0f, 0f); + private static Vector3 unitY = new Vector3(0f, 1f, 0f); + private static Vector3 unitZ = new Vector3(0f, 0f, 1f); + private static Vector3 up = new Vector3(0f, 1f, 0f); + private static Vector3 down = new Vector3(0f, -1f, 0f); + private static Vector3 right = new Vector3(1f, 0f, 0f); + private static Vector3 left = new Vector3(-1f, 0f, 0f); + private static Vector3 forward = new Vector3(0f, 0f, -1f); + private static Vector3 backward = new Vector3(0f, 0f, 1f); + + #endregion Private Fields + + + #region Public Fields + + public float X; + public float Y; + public float Z; + + #endregion Public Fields + + + #region Properties + + public static Vector3 Zero + { + get { return zero; } + } + + public static Vector3 One + { + get { return one; } + } + + public static Vector3 UnitX + { + get { return unitX; } + } + + public static Vector3 UnitY + { + get { return unitY; } + } + + public static Vector3 UnitZ + { + get { return unitZ; } + } + + public static Vector3 Up + { + get { return up; } + } + + public static Vector3 Down + { + get { return down; } + } + + public static Vector3 Right + { + get { return right; } + } + + public static Vector3 Left + { + get { return left; } + } + + public static Vector3 Forward + { + get { return forward; } + } + + public static Vector3 Backward + { + get { return backward; } + } + + #endregion Properties + + + #region Constructors + + public Vector3(float x, float y, float z) + { + this.X = x; + this.Y = y; + this.Z = z; + } + + + public Vector3(float value) + { + this.X = value; + this.Y = value; + this.Z = value; + } + + + public Vector3(Vector2 value, float z) + { + this.X = value.X; + this.Y = value.Y; + this.Z = z; + } + + + #endregion Constructors + + + #region Public Methods + + public static Vector3 Add(Vector3 value1, Vector3 value2) + { + value1.X += value2.X; + value1.Y += value2.Y; + value1.Z += value2.Z; + return value1; + } + + public static void Add(ref Vector3 value1, ref Vector3 value2, out Vector3 result) + { + result.X = value1.X + value2.X; + result.Y = value1.Y + value2.Y; + result.Z = value1.Z + value2.Z; + } + + public static Vector3 Barycentric(Vector3 value1, Vector3 value2, Vector3 value3, float amount1, float amount2) + { + return new Vector3( + MathHelper.Barycentric(value1.X, value2.X, value3.X, amount1, amount2), + MathHelper.Barycentric(value1.Y, value2.Y, value3.Y, amount1, amount2), + MathHelper.Barycentric(value1.Z, value2.Z, value3.Z, amount1, amount2)); + } + + public static void Barycentric(ref Vector3 value1, ref Vector3 value2, ref Vector3 value3, float amount1, float amount2, out Vector3 result) + { + result = new Vector3( + MathHelper.Barycentric(value1.X, value2.X, value3.X, amount1, amount2), + MathHelper.Barycentric(value1.Y, value2.Y, value3.Y, amount1, amount2), + MathHelper.Barycentric(value1.Z, value2.Z, value3.Z, amount1, amount2)); + } + + public static Vector3 CatmullRom(Vector3 value1, Vector3 value2, Vector3 value3, Vector3 value4, float amount) + { + return new Vector3( + MathHelper.CatmullRom(value1.X, value2.X, value3.X, value4.X, amount), + MathHelper.CatmullRom(value1.Y, value2.Y, value3.Y, value4.Y, amount), + MathHelper.CatmullRom(value1.Z, value2.Z, value3.Z, value4.Z, amount)); + } + + public static void CatmullRom(ref Vector3 value1, ref Vector3 value2, ref Vector3 value3, ref Vector3 value4, float amount, out Vector3 result) + { + result = new Vector3( + MathHelper.CatmullRom(value1.X, value2.X, value3.X, value4.X, amount), + MathHelper.CatmullRom(value1.Y, value2.Y, value3.Y, value4.Y, amount), + MathHelper.CatmullRom(value1.Z, value2.Z, value3.Z, value4.Z, amount)); + } + + public static Vector3 Clamp(Vector3 value1, Vector3 min, Vector3 max) + { + return new Vector3( + MathHelper.Clamp(value1.X, min.X, max.X), + MathHelper.Clamp(value1.Y, min.Y, max.Y), + MathHelper.Clamp(value1.Z, min.Z, max.Z)); + } + + public static void Clamp(ref Vector3 value1, ref Vector3 min, ref Vector3 max, out Vector3 result) + { + result = new Vector3( + MathHelper.Clamp(value1.X, min.X, max.X), + MathHelper.Clamp(value1.Y, min.Y, max.Y), + MathHelper.Clamp(value1.Z, min.Z, max.Z)); + } + + public static Vector3 Cross(Vector3 vector1, Vector3 vector2) + { + Vector3 result; + result.X = vector1.Y * vector2.Z - vector2.Y * vector1.Z; + result.Y = vector2.X * vector1.Z - vector1.X * vector2.Z; + result.Z = vector1.X * vector2.Y - vector2.X * vector1.Y; + return result; + } + + public static void Cross(ref Vector3 vector1, ref Vector3 vector2, out Vector3 result) + { + result.X = vector1.Y * vector2.Z - vector2.Y * vector1.Z; + result.Y = vector2.X * vector1.Z - vector1.X * vector2.Z; + result.Z = vector1.X * vector2.Y - vector2.X * vector1.Y; + } + + public static float Distance(Vector3 value1, Vector3 value2) + { + return (float)Math.Sqrt((value1.X - value2.X) * (value1.X - value2.X) + + (value1.Y - value2.Y) * (value1.Y - value2.Y) + + (value1.Z - value2.Z) * (value1.Z - value2.Z)); + } + + public static void Distance(ref Vector3 value1, ref Vector3 value2, out float result) + { + result = (float)Math.Sqrt((value1.X - value2.X) * (value1.X - value2.X) + + (value1.Y - value2.Y) * (value1.Y - value2.Y) + + (value1.Z - value2.Z) * (value1.Z - value2.Z)); + } + + public static float DistanceSquared(Vector3 value1, Vector3 value2) + { + return (value1.X - value2.X) * (value1.X - value2.X) + + (value1.Y - value2.Y) * (value1.Y - value2.Y) + + (value1.Z - value2.Z) * (value1.Z - value2.Z); ; + } + + public static void DistanceSquared(ref Vector3 value1, ref Vector3 value2, out float result) + { + result = (value1.X - value2.X) * (value1.X - value2.X) + + (value1.Y - value2.Y) * (value1.Y - value2.Y) + + (value1.Z - value2.Z) * (value1.Z - value2.Z); + } + + public static Vector3 Divide(Vector3 value1, Vector3 value2) + { + value1.X /= value2.X; + value1.Y /= value2.Y; + value1.Z /= value2.Z; + return value1; + } + + public static Vector3 Divide(Vector3 value1, float value2) + { + float factor = 1.0f / value2; + value1.X *= factor; + value1.Y *= factor; + value1.Z *= factor; + return value1; + } + + public static void Divide(ref Vector3 value1, float divisor, out Vector3 result) + { + float factor = 1.0f / divisor; + result.X = value1.X * factor; + result.Y = value1.Y * factor; + result.Z = value1.Z * factor; + } + + public static void Divide(ref Vector3 value1, ref Vector3 value2, out Vector3 result) + { + result.X = value1.X / value2.X; + result.Y = value1.Y / value2.Y; + result.Z = value1.Z / value2.Z; + } + + public static float Dot(Vector3 vector1, Vector3 vector2) + { + return vector1.X * vector2.X + vector1.Y * vector2.Y + vector1.Z * vector2.Z; + } + + public static void Dot(ref Vector3 vector1, ref Vector3 vector2, out float result) + { + result = vector1.X * vector2.X + vector1.Y * vector2.Y + vector1.Z * vector2.Z; + } + + public override bool Equals(object obj) + { + return (obj is Vector3) ? this == (Vector3)obj : false; + } + + public bool Equals(Vector3 other) + { + return this == other; + } + + public override int GetHashCode() + { + return (int)(this.X + this.Y + this.Z); + } + + public static Vector3 Hermite(Vector3 value1, Vector3 tangent1, Vector3 value2, Vector3 tangent2, float amount) + { + value1.X = MathHelper.Hermite(value1.X, tangent1.X, value2.X, tangent2.X, amount); + value1.Y = MathHelper.Hermite(value1.Y, tangent1.Y, value2.Y, tangent2.Y, amount); + value1.Z = MathHelper.Hermite(value1.Z, tangent1.Z, value2.Z, tangent2.Z, amount); + return value1; + } + + public static void Hermite(ref Vector3 value1, ref Vector3 tangent1, ref Vector3 value2, ref Vector3 tangent2, float amount, out Vector3 result) + { + result.X = MathHelper.Hermite(value1.X, tangent1.X, value2.X, tangent2.X, amount); + result.Y = MathHelper.Hermite(value1.Y, tangent1.Y, value2.Y, tangent2.Y, amount); + result.Z = MathHelper.Hermite(value1.Z, tangent1.Z, value2.Z, tangent2.Z, amount); + } + + public float Length() + { + return (float)Math.Sqrt((double)(X * X + Y * Y + Z * Z)); + } + + public float LengthSquared() + { + return X * X + Y * Y + Z * Z; + } + + public static Vector3 Lerp(Vector3 value1, Vector3 value2, float amount) + { + return new Vector3( + MathHelper.Lerp(value1.X, value2.X, amount), + MathHelper.Lerp(value1.Y, value2.Y, amount), + MathHelper.Lerp(value1.Z, value2.Z, amount)); + } + + public static void Lerp(ref Vector3 value1, ref Vector3 value2, float amount, out Vector3 result) + { + result = new Vector3( + MathHelper.Lerp(value1.X, value2.X, amount), + MathHelper.Lerp(value1.Y, value2.Y, amount), + MathHelper.Lerp(value1.Z, value2.Z, amount)); + } + + public static Vector3 Max(Vector3 value1, Vector3 value2) + { + return new Vector3( + MathHelper.Max(value1.X, value2.X), + MathHelper.Max(value1.Y, value2.Y), + MathHelper.Max(value1.Z, value2.Z)); + } + + public static void Max(ref Vector3 value1, ref Vector3 value2, out Vector3 result) + { + result = new Vector3( + MathHelper.Max(value1.X, value2.X), + MathHelper.Max(value1.Y, value2.Y), + MathHelper.Max(value1.Z, value2.Z)); + } + + public static Vector3 Min(Vector3 value1, Vector3 value2) + { + return new Vector3( + MathHelper.Min(value1.X, value2.X), + MathHelper.Min(value1.Y, value2.Y), + MathHelper.Min(value1.Z, value2.Z)); + } + + public static void Min(ref Vector3 value1, ref Vector3 value2, out Vector3 result) + { + result = new Vector3( + MathHelper.Min(value1.X, value2.X), + MathHelper.Min(value1.Y, value2.Y), + MathHelper.Min(value1.Z, value2.Z)); + } + + public static Vector3 Multiply(Vector3 value1, Vector3 value2) + { + value1.X *= value2.X; + value1.Y *= value2.Y; + value1.Z *= value2.Z; + return value1; + } + + public static Vector3 Multiply(Vector3 value1, float scaleFactor) + { + value1.X *= scaleFactor; + value1.Y *= scaleFactor; + value1.Z *= scaleFactor; + return value1; + } + + public static void Multiply(ref Vector3 value1, float scaleFactor, out Vector3 result) + { + result.X = value1.X * scaleFactor; + result.Y = value1.Y * scaleFactor; + result.Z = value1.Z * scaleFactor; + } + + public static void Multiply(ref Vector3 value1, ref Vector3 value2, out Vector3 result) + { + result.X = value1.X * value2.X; + result.Y = value1.Y * value2.Y; + result.Z = value1.Z * value2.Z; + } + + public static Vector3 Negate(Vector3 value) + { + value.X = -value.X; + value.Y = -value.Y; + value.Z = -value.Z; + return value; + } + + public static void Negate(ref Vector3 value, out Vector3 result) + { + result.X = -value.X; + result.Y = -value.Y; + result.Z = -value.Z; + } + + public void Normalize() + { + float factor = 1f / (float)Math.Sqrt((double)(X * X + Y * Y + Z * Z)); + X *= factor; + Y *= factor; + Z *= factor; + } + + public static Vector3 Normalize(Vector3 value) + { + float factor = 1f / (float)Math.Sqrt((double)(value.X * value.X + value.Y * value.Y + value.Z * value.Z)); + value.X *= factor; + value.Y *= factor; + value.Z *= factor; + return value; + } + + public static void Normalize(ref Vector3 value, out Vector3 result) + { + float factor = 1f / (float)Math.Sqrt((double)(value.X * value.X + value.Y * value.Y + value.Z * value.Z)); + result.X = value.X * factor; + result.Y = value.Y * factor; + result.Z = value.Z * factor; + } + + public static Vector3 Reflect(Vector3 vector, Vector3 normal) + { + float dotTimesTwo = 2f * Dot(vector, normal); + vector.X = vector.X - dotTimesTwo * normal.X; + vector.Y = vector.Y - dotTimesTwo * normal.Y; + vector.Z = vector.Z - dotTimesTwo * normal.Z; + return vector; + } + + public static void Reflect(ref Vector3 vector, ref Vector3 normal, out Vector3 result) + { + float dotTimesTwo = 2f * Dot(vector, normal); + result.X = vector.X - dotTimesTwo * normal.X; + result.Y = vector.Y - dotTimesTwo * normal.Y; + result.Z = vector.Z - dotTimesTwo * normal.Z; + } + + public static Vector3 SmoothStep(Vector3 value1, Vector3 value2, float amount) + { + return new Vector3( + MathHelper.SmoothStep(value1.X, value2.X, amount), + MathHelper.SmoothStep(value1.Y, value2.Y, amount), + MathHelper.SmoothStep(value1.Z, value2.Z, amount)); + } + + public static void SmoothStep(ref Vector3 value1, ref Vector3 value2, float amount, out Vector3 result) + { + result = new Vector3( + MathHelper.SmoothStep(value1.X, value2.X, amount), + MathHelper.SmoothStep(value1.Y, value2.Y, amount), + MathHelper.SmoothStep(value1.Z, value2.Z, amount)); + } + + public static Vector3 Subtract(Vector3 value1, Vector3 value2) + { + value1.X -= value2.X; + value1.Y -= value2.Y; + value1.Z -= value2.Z; + return value1; + } + + public static void Subtract(ref Vector3 value1, ref Vector3 value2, out Vector3 result) + { + result.X = value1.X - value2.X; + result.Y = value1.Y - value2.Y; + result.Z = value1.Z - value2.Z; + } + + public override string ToString() + { + StringBuilder sb = new StringBuilder(32); + sb.Append("{X:"); + sb.Append(this.X); + sb.Append(" Y:"); + sb.Append(this.Y); + sb.Append(" Z:"); + sb.Append(this.Z); + sb.Append("}"); + return sb.ToString(); + } + + public static Vector3 Transform(Vector3 position, Matrix matrix) + { + Transform(ref position, ref matrix, out position); + return position; + } + + public static void Transform(ref Vector3 position, ref Matrix matrix, out Vector3 result) + { + result = new Vector3((position.X * matrix.M11) + (position.Y * matrix.M21) + (position.Z * matrix.M31) + matrix.M41, + (position.X * matrix.M12) + (position.Y * matrix.M22) + (position.Z * matrix.M32) + matrix.M42, + (position.X * matrix.M13) + (position.Y * matrix.M23) + (position.Z * matrix.M33) + matrix.M43); + } + + public static Vector3 Transform(Vector3 value, Quaternion rotation) + { + throw new NotImplementedException(); + } + + public static void Transform(Vector3[] sourceArray, ref Matrix matrix, Vector3[] destinationArray) + { + throw new NotImplementedException(); + } + + public static void Transform(Vector3[] sourceArray, ref Quaternion rotation, Vector3[] destinationArray) + { + throw new NotImplementedException(); + } + + public static void Transform(Vector3[] sourceArray, int sourceIndex, ref Matrix matrix, Vector3[] destinationArray, int destinationIndex, int length) + { + throw new NotImplementedException(); + } + + public static void Transform(Vector3[] sourceArray, int sourceIndex, ref Quaternion rotation, Vector3[] destinationArray, int destinationIndex, int length) + { + throw new NotImplementedException(); + } + + public static void Transform(ref Vector3 value, ref Quaternion rotation, out Vector3 result) + { + throw new NotImplementedException(); + } + + public static void TransformNormal(Vector3[] sourceArray, ref Matrix matrix, Vector3[] destinationArray) + { + throw new NotImplementedException(); + } + + public static void TransformNormal(Vector3[] sourceArray, int sourceIndex, ref Matrix matrix, Vector3[] destinationArray, int destinationIndex, int length) + { + throw new NotImplementedException(); + } + + public static Vector3 TransformNormal(Vector3 normal, Matrix matrix) + { + TransformNormal(ref normal, ref matrix, out normal); + return normal; + } + + public static void TransformNormal(ref Vector3 normal, ref Matrix matrix, out Vector3 result) + { + result = new Vector3((normal.X * matrix.M11) + (normal.Y * matrix.M21) + (normal.Z * matrix.M31), + (normal.X * matrix.M12) + (normal.Y * matrix.M22) + (normal.Z * matrix.M32), + (normal.X * matrix.M13) + (normal.Y * matrix.M23) + (normal.Z * matrix.M33)); + } + + #endregion Public methods + + + #region Operators + + public static bool operator ==(Vector3 value1, Vector3 value2) + { + return value1.X == value2.X + && value1.Y == value2.Y + && value1.Z == value2.Z; + } + + public static bool operator !=(Vector3 value1, Vector3 value2) + { + return value1.X != value2.X + || value1.Y != value2.Y + || value1.Z != value2.Z; + } + + public static Vector3 operator +(Vector3 value1, Vector3 value2) + { + value1.X += value2.X; + value1.Y += value2.Y; + value1.Z += value2.Z; + return value1; + } + + public static Vector3 operator -(Vector3 value) + { + value = new Vector3(-value.X, -value.Y, -value.Z); + return value; + } + + public static Vector3 operator -(Vector3 value1, Vector3 value2) + { + value1.X -= value2.X; + value1.Y -= value2.Y; + value1.Z -= value2.Z; + return value1; + } + + public static Vector3 operator *(Vector3 value1, Vector3 value2) + { + value1.X *= value2.X; + value1.Y *= value2.Y; + value1.Z *= value2.Z; + return value1; + } + + public static Vector3 operator *(Vector3 value, float scaleFactor) + { + value.X *= scaleFactor; + value.Y *= scaleFactor; + value.Z *= scaleFactor; + return value; + } + + public static Vector3 operator *(float scaleFactor, Vector3 value) + { + value.X *= scaleFactor; + value.Y *= scaleFactor; + value.Z *= scaleFactor; + return value; + } + + public static Vector3 operator /(Vector3 value1, Vector3 value2) + { + value1.X /= value2.X; + value1.Y /= value2.Y; + value1.Z /= value2.Z; + return value1; + } + + public static Vector3 operator /(Vector3 value, float divider) + { + float factor = 1 / divider; + value.X *= factor; + value.Y *= factor; + value.Z *= factor; + return value; + } + + #endregion + } +} diff --git a/Bizware/BizHawk.Bizware.BizwareGL/Types/Vector4.cs b/Bizware/BizHawk.Bizware.BizwareGL/Types/Vector4.cs new file mode 100644 index 0000000000..feef9703fb --- /dev/null +++ b/Bizware/BizHawk.Bizware.BizwareGL/Types/Vector4.cs @@ -0,0 +1,709 @@ +#region License +/* +MIT License +Copyright © 2006 The Mono.Xna Team + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +#endregion License + +using System; +using System.ComponentModel; + +using System.Text; +using System.Runtime.InteropServices; + +namespace BizHawk.Bizware.BizwareGL +{ + [Serializable] + [StructLayout(LayoutKind.Sequential)] + + public struct Vector4 : IEquatable + { + #region Private Fields + + private static Vector4 zeroVector = new Vector4(); + private static Vector4 unitVector = new Vector4(1f, 1f, 1f, 1f); + private static Vector4 unitXVector = new Vector4(1f, 0f, 0f, 0f); + private static Vector4 unitYVector = new Vector4(0f, 1f, 0f, 0f); + private static Vector4 unitZVector = new Vector4(0f, 0f, 1f, 0f); + private static Vector4 unitWVector = new Vector4(0f, 0f, 0f, 1f); + + #endregion Private Fields + + + #region Public Fields + + public float X; + public float Y; + public float Z; + public float W; + + #endregion Public Fields + + + #region Properties + + public static Vector4 Zero + { + get { return zeroVector; } + } + + public static Vector4 One + { + get { return unitVector; } + } + + public static Vector4 UnitX + { + get { return unitXVector; } + } + + public static Vector4 UnitY + { + get { return unitYVector; } + } + + public static Vector4 UnitZ + { + get { return unitZVector; } + } + + public static Vector4 UnitW + { + get { return unitWVector; } + } + + #endregion Properties + + + #region Constructors + + public Vector4(float x, float y, float z, float w) + { + this.X = x; + this.Y = y; + this.Z = z; + this.W = w; + } + + public Vector4(Vector2 value, float z, float w) + { + this.X = value.X; + this.Y = value.Y; + this.Z = z; + this.W = w; + } + + public Vector4(Vector3 value, float w) + { + this.X = value.X; + this.Y = value.Y; + this.Z = value.Z; + this.W = w; + } + + public Vector4(float value) + { + this.X = value; + this.Y = value; + this.Z = value; + this.W = value; + } + + #endregion + + + #region Public Methods + + public static Vector4 Add(Vector4 value1, Vector4 value2) + { + value1.W += value2.W; + value1.X += value2.X; + value1.Y += value2.Y; + value1.Z += value2.Z; + return value1; + } + + public static void Add(ref Vector4 value1, ref Vector4 value2, out Vector4 result) + { + result.W = value1.W + value2.W; + result.X = value1.X + value2.X; + result.Y = value1.Y + value2.Y; + result.Z = value1.Z + value2.Z; + } + + public static Vector4 Barycentric(Vector4 value1, Vector4 value2, Vector4 value3, float amount1, float amount2) + { + return new Vector4( + MathHelper.Barycentric(value1.X, value2.X, value3.X, amount1, amount2), + MathHelper.Barycentric(value1.Y, value2.Y, value3.Y, amount1, amount2), + MathHelper.Barycentric(value1.Z, value2.Z, value3.Z, amount1, amount2), + MathHelper.Barycentric(value1.W, value2.W, value3.W, amount1, amount2)); + } + + public static void Barycentric(ref Vector4 value1, ref Vector4 value2, ref Vector4 value3, float amount1, float amount2, out Vector4 result) + { + result = new Vector4( + MathHelper.Barycentric(value1.X, value2.X, value3.X, amount1, amount2), + MathHelper.Barycentric(value1.Y, value2.Y, value3.Y, amount1, amount2), + MathHelper.Barycentric(value1.Z, value2.Z, value3.Z, amount1, amount2), + MathHelper.Barycentric(value1.W, value2.W, value3.W, amount1, amount2)); + } + + public static Vector4 CatmullRom(Vector4 value1, Vector4 value2, Vector4 value3, Vector4 value4, float amount) + { + return new Vector4( + MathHelper.CatmullRom(value1.X, value2.X, value3.X, value4.X, amount), + MathHelper.CatmullRom(value1.Y, value2.Y, value3.Y, value4.Y, amount), + MathHelper.CatmullRom(value1.Z, value2.Z, value3.Z, value4.Z, amount), + MathHelper.CatmullRom(value1.W, value2.W, value3.W, value4.W, amount)); + } + + public static void CatmullRom(ref Vector4 value1, ref Vector4 value2, ref Vector4 value3, ref Vector4 value4, float amount, out Vector4 result) + { + result = new Vector4( + MathHelper.CatmullRom(value1.X, value2.X, value3.X, value4.X, amount), + MathHelper.CatmullRom(value1.Y, value2.Y, value3.Y, value4.Y, amount), + MathHelper.CatmullRom(value1.Z, value2.Z, value3.Z, value4.Z, amount), + MathHelper.CatmullRom(value1.W, value2.W, value3.W, value4.W, amount)); + } + + public static Vector4 Clamp(Vector4 value1, Vector4 min, Vector4 max) + { + return new Vector4( + MathHelper.Clamp(value1.X, min.X, max.X), + MathHelper.Clamp(value1.Y, min.Y, max.Y), + MathHelper.Clamp(value1.Z, min.Z, max.Z), + MathHelper.Clamp(value1.W, min.W, max.W)); + } + + public static void Clamp(ref Vector4 value1, ref Vector4 min, ref Vector4 max, out Vector4 result) + { + result = new Vector4( + MathHelper.Clamp(value1.X, min.X, max.X), + MathHelper.Clamp(value1.Y, min.Y, max.Y), + MathHelper.Clamp(value1.Z, min.Z, max.Z), + MathHelper.Clamp(value1.W, min.W, max.W)); + } + + public static float Distance(Vector4 value1, Vector4 value2) + { + return (float)Math.Sqrt((value1.W - value2.W) * (value1.W - value2.W) + + (value1.X - value2.X) * (value1.X - value2.X) + + (value1.Y - value2.Y) * (value1.Y - value2.Y) + + (value1.Z - value2.Z) * (value1.Z - value2.Z)); + } + + public static void Distance(ref Vector4 value1, ref Vector4 value2, out float result) + { + result = (float)Math.Sqrt((value1.W - value2.W) * (value1.W - value2.W) + + (value1.X - value2.X) * (value1.X - value2.X) + + (value1.Y - value2.Y) * (value1.Y - value2.Y) + + (value1.Z - value2.Z) * (value1.Z - value2.Z)); + } + + public static float DistanceSquared(Vector4 value1, Vector4 value2) + { + return (value1.W - value2.W) * (value1.W - value2.W) + + (value1.X - value2.X) * (value1.X - value2.X) + + (value1.Y - value2.Y) * (value1.Y - value2.Y) + + (value1.Z - value2.Z) * (value1.Z - value2.Z); + } + + public static void DistanceSquared(ref Vector4 value1, ref Vector4 value2, out float result) + { + result = (value1.W - value2.W) * (value1.W - value2.W) + + (value1.X - value2.X) * (value1.X - value2.X) + + (value1.Y - value2.Y) * (value1.Y - value2.Y) + + (value1.Z - value2.Z) * (value1.Z - value2.Z); + } + + public static Vector4 Divide(Vector4 value1, Vector4 value2) + { + value1.W /= value2.W; + value1.X /= value2.X; + value1.Y /= value2.Y; + value1.Z /= value2.Z; + return value1; + } + + public static Vector4 Divide(Vector4 value1, float divider) + { + float factor = 1f / divider; + value1.W *= factor; + value1.X *= factor; + value1.Y *= factor; + value1.Z *= factor; + return value1; + } + + public static void Divide(ref Vector4 value1, float divider, out Vector4 result) + { + float factor = 1f / divider; + result.W = value1.W * factor; + result.X = value1.X * factor; + result.Y = value1.Y * factor; + result.Z = value1.Z * factor; + } + + public static void Divide(ref Vector4 value1, ref Vector4 value2, out Vector4 result) + { + result.W = value1.W / value2.W; + result.X = value1.X / value2.X; + result.Y = value1.Y / value2.Y; + result.Z = value1.Z / value2.Z; + } + + public static float Dot(Vector4 vector1, Vector4 vector2) + { + return vector1.X * vector2.X + vector1.Y * vector2.Y + vector1.Z * vector2.Z + vector1.W * vector2.W; + } + + public static void Dot(ref Vector4 vector1, ref Vector4 vector2, out float result) + { + result = vector1.X * vector2.X + vector1.Y * vector2.Y + vector1.Z * vector2.Z + vector1.W * vector2.W; + } + + public override bool Equals(object obj) + { + return (obj is Vector4) ? this == (Vector4)obj : false; + } + + public bool Equals(Vector4 other) + { + return this.W == other.W + && this.X == other.X + && this.Y == other.Y + && this.Z == other.Z; + } + + public override int GetHashCode() + { + return (int)(this.W + this.X + this.Y + this.Y); + } + + public static Vector4 Hermite(Vector4 value1, Vector4 tangent1, Vector4 value2, Vector4 tangent2, float amount) + { + value1.W = MathHelper.Hermite(value1.W, tangent1.W, value2.W, tangent2.W, amount); + value1.X = MathHelper.Hermite(value1.X, tangent1.X, value2.X, tangent2.X, amount); + value1.Y = MathHelper.Hermite(value1.Y, tangent1.Y, value2.Y, tangent2.Y, amount); + value1.Z = MathHelper.Hermite(value1.Z, tangent1.Z, value2.Z, tangent2.Z, amount); + + return value1; + } + + public static void Hermite(ref Vector4 value1, ref Vector4 tangent1, ref Vector4 value2, ref Vector4 tangent2, float amount, out Vector4 result) + { + result.W = MathHelper.Hermite(value1.W, tangent1.W, value2.W, tangent2.W, amount); + result.X = MathHelper.Hermite(value1.X, tangent1.X, value2.X, tangent2.X, amount); + result.Y = MathHelper.Hermite(value1.Y, tangent1.Y, value2.Y, tangent2.Y, amount); + result.Z = MathHelper.Hermite(value1.Z, tangent1.Z, value2.Z, tangent2.Z, amount); + } + + public float Length() + { + return (float)Math.Sqrt((double)(X * X + Y * Y + Z * Z + W * W)); + } + + public float LengthSquared() + { + return X * X + Y * Y + Z * Z + W * W; + } + + public static Vector4 Lerp(Vector4 value1, Vector4 value2, float amount) + { + return new Vector4( + MathHelper.Lerp(value1.X, value2.X, amount), + MathHelper.Lerp(value1.Y, value2.Y, amount), + MathHelper.Lerp(value1.Z, value2.Z, amount), + MathHelper.Lerp(value1.W, value2.W, amount)); + } + + public static void Lerp(ref Vector4 value1, ref Vector4 value2, float amount, out Vector4 result) + { + result = new Vector4( + MathHelper.Lerp(value1.X, value2.X, amount), + MathHelper.Lerp(value1.Y, value2.Y, amount), + MathHelper.Lerp(value1.Z, value2.Z, amount), + MathHelper.Lerp(value1.W, value2.W, amount)); + } + + public static Vector4 Max(Vector4 value1, Vector4 value2) + { + return new Vector4( + MathHelper.Max(value1.X, value2.X), + MathHelper.Max(value1.Y, value2.Y), + MathHelper.Max(value1.Z, value2.Z), + MathHelper.Max(value1.W, value2.W)); + } + + public static void Max(ref Vector4 value1, ref Vector4 value2, out Vector4 result) + { + result = new Vector4( + MathHelper.Max(value1.X, value2.X), + MathHelper.Max(value1.Y, value2.Y), + MathHelper.Max(value1.Z, value2.Z), + MathHelper.Max(value1.W, value2.W)); + } + + public static Vector4 Min(Vector4 value1, Vector4 value2) + { + return new Vector4( + MathHelper.Min(value1.X, value2.X), + MathHelper.Min(value1.Y, value2.Y), + MathHelper.Min(value1.Z, value2.Z), + MathHelper.Min(value1.W, value2.W)); + } + + public static void Min(ref Vector4 value1, ref Vector4 value2, out Vector4 result) + { + result = new Vector4( + MathHelper.Min(value1.X, value2.X), + MathHelper.Min(value1.Y, value2.Y), + MathHelper.Min(value1.Z, value2.Z), + MathHelper.Min(value1.W, value2.W)); + } + + public static Vector4 Multiply(Vector4 value1, Vector4 value2) + { + value1.W *= value2.W; + value1.X *= value2.X; + value1.Y *= value2.Y; + value1.Z *= value2.Z; + return value1; + } + + public static Vector4 Multiply(Vector4 value1, float scaleFactor) + { + value1.W *= scaleFactor; + value1.X *= scaleFactor; + value1.Y *= scaleFactor; + value1.Z *= scaleFactor; + return value1; + } + + public static void Multiply(ref Vector4 value1, float scaleFactor, out Vector4 result) + { + result.W = value1.W * scaleFactor; + result.X = value1.X * scaleFactor; + result.Y = value1.Y * scaleFactor; + result.Z = value1.Z * scaleFactor; + } + + public static void Multiply(ref Vector4 value1, ref Vector4 value2, out Vector4 result) + { + result.W = value1.W * value2.W; + result.X = value1.X * value2.X; + result.Y = value1.Y * value2.Y; + result.Z = value1.Z * value2.Z; + } + + public static Vector4 Negate(Vector4 value) + { + value.X = -value.X; + value.Y = -value.Y; + value.Z = -value.Z; + value.W = -value.W; + return value; + } + + public static void Negate(ref Vector4 value, out Vector4 result) + { + result.X = -value.X; + result.Y = -value.Y; + result.Z = -value.Z; + result.W = -value.W; + } + + public void Normalize() + { + float factor = 1f / (float)Math.Sqrt((double)(X * X + Y * Y + Z * Z + W * W)); + + W = W * factor; + X = X * factor; + Y = Y * factor; + Z = Z * factor; + } + + public static Vector4 Normalize(Vector4 vector) + { + float factor = 1f / (float)Math.Sqrt((double)(vector.X * vector.X + vector.Y * vector.Y + vector.Z * vector.Z + vector.W * vector.W)); + + vector.W = vector.W * factor; + vector.X = vector.X * factor; + vector.Y = vector.Y * factor; + vector.Z = vector.Z * factor; + + return vector; + } + + public static void Normalize(ref Vector4 vector, out Vector4 result) + { + float factor = 1f / (float)Math.Sqrt((double)(vector.X * vector.X + vector.Y * vector.Y + vector.Z * vector.Z + vector.W * vector.W)); + + result.W = vector.W * factor; + result.X = vector.X * factor; + result.Y = vector.Y * factor; + result.Z = vector.Z * factor; + } + + public static Vector4 SmoothStep(Vector4 value1, Vector4 value2, float amount) + { + return new Vector4( + MathHelper.SmoothStep(value1.X, value2.X, amount), + MathHelper.SmoothStep(value1.Y, value2.Y, amount), + MathHelper.SmoothStep(value1.Z, value2.Z, amount), + MathHelper.SmoothStep(value1.W, value2.W, amount)); + } + + public static void SmoothStep(ref Vector4 value1, ref Vector4 value2, float amount, out Vector4 result) + { + result = new Vector4( + MathHelper.SmoothStep(value1.X, value2.X, amount), + MathHelper.SmoothStep(value1.Y, value2.Y, amount), + MathHelper.SmoothStep(value1.Z, value2.Z, amount), + MathHelper.SmoothStep(value1.W, value2.W, amount)); + } + + public static Vector4 Subtract(Vector4 value1, Vector4 value2) + { + value1.W -= value2.W; + value1.X -= value2.X; + value1.Y -= value2.Y; + value1.Z -= value2.Z; + return value1; + } + + public static void Subtract(ref Vector4 value1, ref Vector4 value2, out Vector4 result) + { + result.W = value1.W - value2.W; + result.X = value1.X - value2.X; + result.Y = value1.Y - value2.Y; + result.Z = value1.Z - value2.Z; + } + + public static Vector4 Transform(Vector2 position, Matrix matrix) + { + Vector4 result; + Transform(ref position, ref matrix, out result); + return result; + } + + public static Vector4 Transform(Vector2 value, Quaternion rotation) + { + throw new NotImplementedException(); + } + + public static Vector4 Transform(Vector3 value, Quaternion rotation) + { + throw new NotImplementedException(); + } + + public static Vector4 Transform(Vector4 value, Quaternion rotation) + { + throw new NotImplementedException(); + } + + public static void Transform(ref Vector2 value, ref Quaternion rotation, out Vector4 result) + { + throw new NotImplementedException(); + } + + public static void Transform(ref Vector3 value, ref Quaternion rotation, out Vector4 result) + { + throw new NotImplementedException(); + } + + public static void Transform(ref Vector4 value, ref Quaternion rotation, out Vector4 result) + { + throw new NotImplementedException(); + } + + public static void Transform(Vector4[] sourceArray, ref Quaternion rotation, Vector4[] destinationArray) + { + throw new NotImplementedException(); + } + + public static void Transform(Vector4[] sourceArray, ref Matrix matrix, Vector4[] destinationArray) + { + throw new NotImplementedException(); + } + + public static void Transform(Vector4[] sourceArray, int sourceIndex, ref Matrix matrix, Vector4[] destinationArray, int destinationIndex, int length) + { + throw new NotImplementedException(); + } + + public static void Transform(Vector4[] sourceArray, int sourceIndex, ref Quaternion rotation, Vector4[] destinationArray, int destinationIndex, int length) + { + throw new NotImplementedException(); + } + + public static Vector4 Transform(Vector3 position, Matrix matrix) + { + Vector4 result; + Transform(ref position, ref matrix, out result); + return result; + } + + public static Vector4 Transform(Vector4 vector, Matrix matrix) + { + Transform(ref vector, ref matrix, out vector); + return vector; + } + + public static void Transform(ref Vector2 position, ref Matrix matrix, out Vector4 result) + { + result = new Vector4((position.X * matrix.M11) + (position.Y * matrix.M21) + matrix.M41, + (position.X * matrix.M12) + (position.Y * matrix.M22) + matrix.M42, + (position.X * matrix.M13) + (position.Y * matrix.M23) + matrix.M43, + (position.X * matrix.M14) + (position.Y * matrix.M24) + matrix.M44); + } + + public static void Transform(ref Vector3 position, ref Matrix matrix, out Vector4 result) + { + result = new Vector4((position.X * matrix.M11) + (position.Y * matrix.M21) + (position.Z * matrix.M31) + matrix.M41, + (position.X * matrix.M12) + (position.Y * matrix.M22) + (position.Z * matrix.M32) + matrix.M42, + (position.X * matrix.M13) + (position.Y * matrix.M23) + (position.Z * matrix.M33) + matrix.M43, + (position.X * matrix.M14) + (position.Y * matrix.M24) + (position.Z * matrix.M34) + matrix.M44); + } + + public static void Transform(ref Vector4 vector, ref Matrix matrix, out Vector4 result) + { + result = new Vector4((vector.X * matrix.M11) + (vector.Y * matrix.M21) + (vector.Z * matrix.M31) + (vector.W * matrix.M41), + (vector.X * matrix.M12) + (vector.Y * matrix.M22) + (vector.Z * matrix.M32) + (vector.W * matrix.M42), + (vector.X * matrix.M13) + (vector.Y * matrix.M23) + (vector.Z * matrix.M33) + (vector.W * matrix.M43), + (vector.X * matrix.M14) + (vector.Y * matrix.M24) + (vector.Z * matrix.M34) + (vector.W * matrix.M44)); + } + + public override string ToString() + { + StringBuilder sb = new StringBuilder(32); + sb.Append("{X:"); + sb.Append(this.X); + sb.Append(" Y:"); + sb.Append(this.Y); + sb.Append(" Z:"); + sb.Append(this.Z); + sb.Append(" W:"); + sb.Append(this.W); + sb.Append("}"); + return sb.ToString(); + } + + #endregion Public Methods + + + #region Operators + + public static Vector4 operator -(Vector4 value) + { + value.X = -value.X; + value.Y = -value.Y; + value.Z = -value.Z; + value.W = -value.W; + return value; + } + + public static bool operator ==(Vector4 value1, Vector4 value2) + { + return value1.W == value2.W + && value1.X == value2.X + && value1.Y == value2.Y + && value1.Z == value2.Z; + } + + public static bool operator !=(Vector4 value1, Vector4 value2) + { + return value1.W != value2.W + || value1.X != value2.X + || value1.Y != value2.Y + || value1.Z != value2.Z; + } + + public static Vector4 operator +(Vector4 value1, Vector4 value2) + { + value1.W += value2.W; + value1.X += value2.X; + value1.Y += value2.Y; + value1.Z += value2.Z; + return value1; + } + + public static Vector4 operator -(Vector4 value1, Vector4 value2) + { + value1.W -= value2.W; + value1.X -= value2.X; + value1.Y -= value2.Y; + value1.Z -= value2.Z; + return value1; + } + + public static Vector4 operator *(Vector4 value1, Vector4 value2) + { + value1.W *= value2.W; + value1.X *= value2.X; + value1.Y *= value2.Y; + value1.Z *= value2.Z; + return value1; + } + + public static Vector4 operator *(Vector4 value1, float scaleFactor) + { + value1.W *= scaleFactor; + value1.X *= scaleFactor; + value1.Y *= scaleFactor; + value1.Z *= scaleFactor; + return value1; + } + + public static Vector4 operator *(float scaleFactor, Vector4 value1) + { + value1.W *= scaleFactor; + value1.X *= scaleFactor; + value1.Y *= scaleFactor; + value1.Z *= scaleFactor; + return value1; + } + + public static Vector4 operator /(Vector4 value1, Vector4 value2) + { + value1.W /= value2.W; + value1.X /= value2.X; + value1.Y /= value2.Y; + value1.Z /= value2.Z; + return value1; + } + + public static Vector4 operator /(Vector4 value1, float divider) + { + float factor = 1f / divider; + value1.W *= factor; + value1.X *= factor; + value1.Y *= factor; + value1.Z *= factor; + return value1; + } + + #endregion Operators + } +} diff --git a/Bizware/BizHawk.Bizware.BizwareGL/UniformInfo.cs b/Bizware/BizHawk.Bizware.BizwareGL/UniformInfo.cs new file mode 100644 index 0000000000..91cf9de274 --- /dev/null +++ b/Bizware/BizHawk.Bizware.BizwareGL/UniformInfo.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; + +namespace BizHawk.Bizware.BizwareGL +{ + public class UniformInfo + { + public IntPtr Handle; + public string Name; + public int SamplerIndex; + } +} \ No newline at end of file diff --git a/Bizware/BizHawk.Bizware.BizwareGL/VertexLayout.cs b/Bizware/BizHawk.Bizware.BizwareGL/VertexLayout.cs new file mode 100644 index 0000000000..f549d98298 --- /dev/null +++ b/Bizware/BizHawk.Bizware.BizwareGL/VertexLayout.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; + +namespace BizHawk.Bizware.BizwareGL +{ + //TEMP until its in bizhawk main + public class WorkingDictionary : Dictionary where V : new() + { + public new V this[K key] + { + get + { + V temp; + if (!TryGetValue(key, out temp)) + { + temp = this[key] = new V(); + } + + return temp; + } + + set + { + base[key] = value; + } + } + + public WorkingDictionary() { } + + } + + public class VertexLayout : IDisposable + { + //TODO - could refactor to use vertex array objects? check opengl profile requirements (answer: 3.0. dont want to do this.) + + public VertexLayout(IGL owner, IntPtr id) + { + Owner = owner; + Id = id; + Items = new MyDictionary(); + } + + public void Dispose() + { + //nothing to do yet.. + } + + public void Bind() + { + Owner.BindVertexLayout(this); + } + + public void DefineVertexAttribute(int index, int components, VertexAttributeType attribType, bool normalized, int stride, int offset=0) + { + if (Closed) + throw new InvalidOperationException("Type is Closed and is now immutable."); + Items[index] = new LayoutItem { Components = components, AttribType = attribType, Normalized = normalized, Stride = stride, Offset = offset }; + } + + /// + /// finishes this VertexLayout and renders it immutable + /// + public void Close() + { + Closed = true; + } + + public class LayoutItem + { + public int Components { get; internal set; } + public VertexAttributeType AttribType { get; internal set; } + public bool Normalized { get; internal set; } + public int Stride { get; internal set; } + public int Offset { get; internal set; } + } + + public class MyDictionary : WorkingDictionary + { + public new LayoutItem this[int key] + { + get + { + return base[key]; + } + + internal set + { + base[key] = value; + } + } + } + + public MyDictionary Items { get; private set; } + bool Closed = false; + + public IGL Owner { get; private set; } + public IntPtr Id { get; private set; } + } +} \ No newline at end of file diff --git a/Bizware/BizHawk.Bizware.Test/BizHawk.Bizware.Test.csproj b/Bizware/BizHawk.Bizware.Test/BizHawk.Bizware.Test.csproj new file mode 100644 index 0000000000..ab2262117e --- /dev/null +++ b/Bizware/BizHawk.Bizware.Test/BizHawk.Bizware.Test.csproj @@ -0,0 +1,99 @@ + + + + Debug + x86 + 8.0.30703 + 2.0 + {B9925307-6A8F-4CD0-8EFB-0617FF0AE5AD} + Exe + Properties + BizHawk.Bizware.Test + BizHawk.Bizware.Test + v4.0 + Client + 512 + + + x86 + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + + + x86 + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + Form + + + TestForm.cs + + + + + {5160CFB1-5389-47C1-B7F6-8A0DC97641EE} + BizHawk.Bizware.BizwareGL.OpenTK + + + {9F84A0B2-861E-4EF4-B89B-5E2A3F38A465} + BizHawk.Bizware.BizwareGL + + + + + + + + TestForm.cs + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Bizware/BizHawk.Bizware.Test/Program.cs b/Bizware/BizHawk.Bizware.Test/Program.cs new file mode 100644 index 0000000000..2dcf882c52 --- /dev/null +++ b/Bizware/BizHawk.Bizware.Test/Program.cs @@ -0,0 +1,83 @@ +using System; +using System.Drawing; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using BizHawk.Bizware.BizwareGL; + +namespace BizHawk.Bizware.Test +{ + class Program + { + static unsafe void Main(string[] args) + { + BizHawk.Bizware.BizwareGL.IGL igl = new BizHawk.Bizware.BizwareGL.Drivers.OpenTK.IGL_TK(); + + + + List testArts = new List(); + ArtManager am = new ArtManager(igl); + foreach (var name in typeof(Program).Assembly.GetManifestResourceNames()) + if (name.Contains("flame")) + testArts.Add(am.LoadArt(typeof(Program).Assembly.GetManifestResourceStream(name))); + var smile = am.LoadArt(typeof(Program).Assembly.GetManifestResourceStream("BizHawk.Bizware.Test.TestImages.smile.png")); + am.Close(true); + StringRenderer sr; + using (var xml = typeof(Program).Assembly.GetManifestResourceStream("BizHawk.Bizware.Test.TestImages.courier16px.fnt")) + using (var tex = typeof(Program).Assembly.GetManifestResourceStream("BizHawk.Bizware.Test.TestImages.courier16px_0.png")) + sr = new StringRenderer(igl, xml, tex); + + GuiRenderer gr = new GuiRenderer(igl); + + TestForm tf = new TestForm(); + GraphicsControl c = igl.CreateGraphicsControl(); + tf.Controls.Add(c); + c.Control.Dock = System.Windows.Forms.DockStyle.Fill; + tf.FormClosing += (object sender, System.Windows.Forms.FormClosingEventArgs e) => + { + tf.Controls.Remove(c); + c.Dispose(); + c = null; + }; + tf.Show(); + + c.SetVsync(false); + + DateTime start = DateTime.Now; + int wobble = 0; + for (; ; ) + { + if (c == null) break; + + c.Begin(); + + igl.ClearColor(Color.Red); + igl.Clear(BizwareGL.ClearBufferMask.ColorBufferBit); + + int frame = (int)((DateTime.Now - start).TotalSeconds) % testArts.Count; + + gr.Begin(c.Control.ClientSize.Width, c.Control.ClientSize.Height); + sr.RenderString(gr, 0, 0, "60 fps"); + gr.Modelview.Translate((float)Math.Sin(wobble / 360.0f) * 50, 0); + gr.Modelview.Translate(100, 100); + gr.Modelview.Push(); + gr.Modelview.Translate(testArts[frame].Width, 0); + gr.Modelview.Scale(-1, 1); + wobble++; + gr.SetModulateColor(Color.Yellow); + gr.DrawFlipped(testArts[frame], true, false); + gr.SetModulateColorWhite(); + gr.Modelview.Pop(); + gr.SetBlendState(igl.BlendNormal); + gr.Draw(smile); + gr.End(); + + c.SwapBuffers(); + c.End(); + + System.Windows.Forms.Application.DoEvents(); + } + } + } +} diff --git a/Bizware/BizHawk.Bizware.Test/TestForm.Designer.cs b/Bizware/BizHawk.Bizware.Test/TestForm.Designer.cs new file mode 100644 index 0000000000..5cf840d839 --- /dev/null +++ b/Bizware/BizHawk.Bizware.Test/TestForm.Designer.cs @@ -0,0 +1,48 @@ +namespace BizHawk.Bizware.Test +{ + partial class TestForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.SuspendLayout(); + // + // TestForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(292, 273); + this.Name = "TestForm"; + this.Text = "TestForm"; + this.ResumeLayout(false); + + } + + #endregion + + + } +} \ No newline at end of file diff --git a/Bizware/BizHawk.Bizware.Test/TestForm.cs b/Bizware/BizHawk.Bizware.Test/TestForm.cs new file mode 100644 index 0000000000..b5054cd7dc --- /dev/null +++ b/Bizware/BizHawk.Bizware.Test/TestForm.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Windows.Forms; + +namespace BizHawk.Bizware.Test +{ + public partial class TestForm : Form + { + public TestForm() + { + InitializeComponent(); + } + } +} diff --git a/Bizware/BizHawk.Bizware.Test/TestForm.resx b/Bizware/BizHawk.Bizware.Test/TestForm.resx new file mode 100644 index 0000000000..29dcb1b3a3 --- /dev/null +++ b/Bizware/BizHawk.Bizware.Test/TestForm.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Bizware/BizHawk.Bizware.Test/TestImages/courier16px.fnt b/Bizware/BizHawk.Bizware.Test/TestImages/courier16px.fnt new file mode 100644 index 0000000000..0e8d5e8a1f --- /dev/null +++ b/Bizware/BizHawk.Bizware.Test/TestImages/courier16px.fnt @@ -0,0 +1,208 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Bizware/BizHawk.Bizware.Test/TestImages/courier16px_0.png b/Bizware/BizHawk.Bizware.Test/TestImages/courier16px_0.png new file mode 100644 index 0000000000000000000000000000000000000000..e9e267cf8ee181769d8defec181ce225aae1e015 GIT binary patch literal 3074 zcmds(_ahXHb&{yEng_=h&h$PU z<)1FsKB9I+T;wau7DZL;kpJ$vSTI_QR!`^dg2G~Y3PqyWxSqp`OufJ^r>EbqhtXCJ zovbJ0gaA7ZUwSk0+OF;srzC^-*Uu3C*xc0wg~9~bRz3#R`cb+npspX9p!owzw`Oe2 z2jXGW0#LvaQ+V&5gJ6b`d=^n)y*lSsz7D;szIk-s2EtKQp+UXELAN0jGqNSjnG$q% zNEQEk?y|=WxaB=))>!mGKXp)o9G9oQbLqqbi{tI2PwN8nVaMP$vrvN|E3Mv)BZzHQiX=WXgy;@X)iksi<3i5FWQKI4bcsFj)jRm6AWy;v;@6=wYVu1c|Y%1n_(IkqSP-_pt|vX>6J->7jE=rVhBXv z?Y>_38Z)ysS#H?($xV&hN={(?jR+TpijO^PG3bP~kb%F=3v2J0l22oYH#S))c|sjb zU=5kY@SootY43z2^$ zs_!ob&d&oR#bj-^J=!6^76tN$nmNj7U-&fQNl<>bADK?!vPv=>kygZ`sZ-Ne(A{{@ zp|W2Ug75zDI^0fJ@Qx*OZBed2ur^9Ua$wx=cL*pv&pFJ_a5$L5w_#fWv$06{Ram|v z-R=ggFXEi{S;1DOZYoMSj<4hpBm91|Bh-ncN|R6nZLL`zHo=MP2VKn^jY+l8*M4HuRAd1h;0{(VYseO(-0*>EWDdn zYp;fhRY&D|&~uQt1Z1+jQjg?a^wOA*kGrfeUXoa+lrrdnq0|ebXQ+xS57%r}|H+|N zfz4rOGlLpMKrLh@Pz`D!ei9Ftwj(DVWP!1OOMQji>1z_%AkVP%j;OX^yN@$c-LHh z%}(KA)9@3n(?4k?j`;@OPAdG!;L+JDLQWn88MUesCnFu*w+_EYqjbM#Lw8b#7jJb6-njQ1ir()n>~kIz9Fon zpoYcA$qV$ybxA00la?t}q%muJWd+M6b}6h<><-x@e#2RFllv~ZvtO>lkbMKMa2URC zI}_b-BtZ=WnT@6+av+ zWXGNu1?;9Q< zexW0sKM6}b+|=zhrmEqq_~Y&=*Bmd|sUw+REHyJC^iC7ZWyCOGUV4EX&iSdl@(nb|r*=x6aru7Xo;3{fLQq9uDTti2zG<-XnPzaH4{!ba=! zsVQ=u^1?y4ZrVw}gI8NSw2L5N+M+6N6`j-_TguO-^gDLA4CfrJzY^C)w*T>1TT3Ue zyx^6fs1ITqH9&{9%^gu56WpRtyg)NX^j}H$Z-tH6JZe3VN!471*P$eO%9b)^_;dT6 z-qBaU&aY08h5!9g`sLKFSNMXf_?7)KPc(+8-TuCD;XcRC`Ni43hO8!KhqKikxxBE$ z8QHt(v5d+%cq~|2ghGsnf=Z0YQRgeL51pBu?gPx9vl?`R4BO%_;&}C}vHeH895^!@DVQv|J@e4XHkbwC zSKoi7YF8H#VfPc1pBwQckIBy{rT^norG2#aNwrZc&kz%vm6n*(ad3MepU7*TatcYh zU}$U?SL12v|CjFRivsE$u4;#VaLd04*Dlj;Qee(IgL}fc1IRcXLr?E-f})7${=8gQ z^VzS>CKaDRe#vde->EMbp+H~a>~b>hxY5^4iO8;LFE+>aOnIJ_UoxUSM#+09<4v1K zcMEj|Hld@4V$fFlpBwo-iIdTHPTd>La_WaBu6*W{F^ZQ2A6Ge1WFABkh|UtieU3bs zYrnTlet~hfKWLA%>Of-GxHN@ku7ynIsd&qz|_#vpdNvW F`yU8eq7DE6 literal 0 HcmV?d00001 diff --git a/Bizware/BizHawk.Bizware.Test/TestImages/flame1.jpg b/Bizware/BizHawk.Bizware.Test/TestImages/flame1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0645716f530fa9a9166b5cb094b7b50a12dd9cd4 GIT binary patch literal 11356 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 literal 0 HcmV?d00001 diff --git a/Bizware/BizHawk.Bizware.Test/TestImages/flame2.jpg b/Bizware/BizHawk.Bizware.Test/TestImages/flame2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8fa880ce44530670ad00c874d38f9af1e4eb7fc4 GIT binary patch literal 9812 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 z zOp}?5<9{BR#)=|$x5uLQn1;#Bo0|@woHx*oA;Jp`{&|n)H1k(#r+Rych4=Iz4~-D{ z32->V@D+Q=R0kM31`{#c7GJ zPJei8lmDR0$)2dnH-a^!P`;Z~?ZVlIyvAyMRwg|q5_`dfj#sQSB1QQ{=m7aBP}Wx* zjxRgi-e=-Ol}gRldajV4>*cY1#x`HGZ}!mdIgDLwy)4e`J-g)bLn&_nuh)&3ml5^o zzMi}i@80AJec zvZ(2D7FmPmy0BCQw9OiDwRpAjDA(v->EXY-bd0b5zClcKB5949uOOT&{6AerT%?g0 zG3Rr)Do4cxQd()CDYe~y2iqCm;oC7*SE*)=fk@|*^^q3*PcFj*mXN8nl+gVwEvvRC z-a8Ms-rGCxs5erX$jvVJN?g(E!=#6kC!aPkgGH~ZWszajJxLXFC70!!&d{cY_&<%1 zQkX~j$ldGf)39@vE_Ls=8>An;o-0@JdrEAvptB!To{2!@ojUAQaV7;4GwB{BRP1j; zb@H3UI(AMs#Wp1EwYWx=(K@?zi;~8FHtk0Aud^j&7S2Yyiyi;lguE+-%aX zrTx8bA-6!$g$n`iV5N%jPunGDZm9iNZ0*|*dwXK1#yZBU5ECryJ|m)))iK?oP2_&ujG z-sV(EIW6n?Q3>NxQ;&bkun*xHJgMc;GV7TB+Z5v~bi~7FHNpCIU1X(f5Vr@3UyL7S zT78EhbnV5Kp#9zoM(C8WaZg~N7bnS`Mg04TMt}(INWiQm1SZ~ixaDmEv1VhmOP!$y z*T0Y5Mh~5fRBD+00TtWSY1r)2Gd{aRpKa5G8JuxyYYYH=6O|B4fZj_ut`K0MrIQdE zD5vLi_2Fgx5%rVoDv*P=6w~p}l>D5})vGWV%8$dD)?A*!dHGnyV#^5a^vm8C!1blR z{H`LnF;MyddbW|7V|4OBRmg0-q5e{-W0BI%h9vSx+RYb1cxzae?5+Ij4ck5qkz-f$ zdbJ9tpN`>;J+425!lnms3avN_pzeYpty(fWGTKzy9O}vrZSZ} z=>c#mkllQ%u$1@ zBq<{u5+@_BUr6@%k09(LJksz8{I=L-n>kIiVI~Z)d&OgII96df0T*1~NWBgZx4%;- z!tSPjpTpSqw&^^@8#(?_C+AGSFw?JtnW6Sa#XZDGM0 zL?_7;o4BBvYe-J$zGyNW1(H1?8YGJz99k`UbA_{e1=e!GR?s1zQF~th4hBz)-UXDB z^wN?p2Og#G0#%nE6&~kTNNK1%MIVbh(!_^|W<#3iJ2D7d3D{RwEIa8`oeFL;3m~M5zx?#wBs{H}DsKs%3a!mv zPbrn+6#v?vpVXZhDmxLOb}}@)$Ddm!CcFzzf6q~tfk+Ijbv8D(bQmLBg-4^=s{=mX zlDwrsWY@X<8C%115en>V@ph9|3$Us42B)r_J7IR)#jqk&{%P6x?@G#lGX~jx|9=26 Crpg-t literal 0 HcmV?d00001 diff --git a/Bizware/BizHawk.Bizware.Test/TestImages/flame3.jpg b/Bizware/BizHawk.Bizware.Test/TestImages/flame3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d50f2f426e5b6cf1bad4b64e9962ba833c6928ef GIT binary patch literal 7078 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 literal 0 HcmV?d00001 diff --git a/Bizware/BizHawk.Bizware.Test/TestImages/flame4.jpg b/Bizware/BizHawk.Bizware.Test/TestImages/flame4.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a4f1722056c1d76e81090b18cb70a686b95b4644 GIT binary patch literal 7622 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 literal 0 HcmV?d00001 diff --git a/Bizware/BizHawk.Bizware.Test/TestImages/flame5.jpg b/Bizware/BizHawk.Bizware.Test/TestImages/flame5.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2a8d7e6d955bd8d2e6c99ee5fd2a4e1a91cb490d GIT binary patch literal 7761 zcmbVQ2Uru^ww@4bC<=t$I{`)EfD{cqG-)9L1W`eX5D+N}5~LqNLk9tgl+Z)yN)<(r z-jos$5D@8tpdd&Sc=6nK?>od(TY1{jW7^?KS`L*zpW-(n!xx4*-Ec zfIa009Df8{5qI5O0KmiqkO2UI9-s#C0yLB}5C!TWzJHwSfusTOAM;d{i}C=a2iPbu zrQm0?r{L!^e;myn+&pi~nIh1_e!s~n$;rzD$L|3hKt@1VLYP-ZLR8|+X&D6#H(d=i zHH|Pk2V=MNNJ3U>WO5?$v9r z?!DvkS-u;`DvQ}Y#=HyH4QBtJp+W1 zazo8YfC>Z#Q&EFyXsD?vcL!0f1JtZEY$EbnwCqR+I#F+^LRexBy_j}Y3x~zvSMlGj z`-C$PEO>JF$!}HgzZS5VMUEMuH!{m|C zcVpudA7|(07Z#V6Kdo$Re%spqzO%cx|C1L80RN5FKbZZ(ibgDB1evjQ5xU@lHJqcRsK4dkYA=XcfsPzx5U@5Puqv!M{_c5X!giQra$ z1RyWgCPM+Fo-~09_dg(mM)3}GV{(8>9>T~TdVVyhz1PT3Wa9U*ti`025T%}qNz@)b zG$sb=d)H#<^B9^w2W<~23+{{Nq08JO&b)%;NOg(DHhW=gCS)HN*jS4t_)e#%lx|Bz zI%WF&9vC-ES)pT!%yjHZ;iHSEoD{m+;@akU5zo)ucE6q`xJcki43HJ^@DxzPhgFOn zc5SS8L@4pVqsNW`$vAiU@^Zf2*M~H}y~#cbRH8ZH;eC}NCwtnnoU4RRFh*vrX2_&d z`j9+eoFlz%pvMe7dS&Fmt;hyNB7naFn3$iC@~~?);-Z-_YrHMcVOFpHsQSXqTd;@E z@1Rzs^WQ92kA@jmVY=0`xqjP9g=Yb764o6S+YxNPCtAKkNN?;ioqbl( zAt!e03yikfOxs;<)=bT(b)7gNEg%uVI*8zqaz!Gb04huSv}iQESBRiE5tG~ymAiqC zS*&oh8l^d@+jg%t;}GLg*z8Mv<1o<_PE?tmbIrv8(tm~SZOW{d_|SG?XmEJu58}x% zsQOHve>u#sx&NFYn{;o(Yg~U;fSiiPsgNmwqiPQB1~}d16wkxB-wvAA4p$-w1n(i7 zNoGPgj$-{p2L#j{WPoPR+HI#eV4&*eDDiwn52j@q;WFO4U{ZZJuAyVAUxsW~+3a#y zMZoe234cV?wn3M}7B-sdU$PM`*lJ`s=R<#Y^H!~56VxZ1neCw8qqGpHXN6Oj3! zAf+fd(2H)$_9oQAV=V@4nSewn{^%5F9O7e_X!-&s4}EM@UWAciweHwXh0(-#!=+1oC!GYMA5CF;i?`H6!RqhRAV+XyH4J*Sc2%l=6uDHuc-WmJq>C1Wn`5 zSMw8ZXXT2UW^;4?Rl{37ksd6nQlYRt#=fgIwZ!ftOX<+Lw52iBY<#b=|Al0;(#b|1 z)pMwHLB(#7h8bM>m^iH}{IEpN>6@stK%f=2<08MT=axW;t+*qU=%@3Vj7lj)T=MNgT77MHS*5|&{5Os>b zJ~P}OGM{oK%u$v}$!5PG)_AKrNou}%2;o1t4!Fb7U*AY~kEa{J07! z_R4KwB;hQuMM_HY?E1`^p$vnYkB5xZT&=vHtf0~&1_WBdX!YRk_FmcdF}0G|F{~8!Meq0c1+be3xP0?JW^;tF?jQ{JK<;bSTXQFO2-K+p1wIK1t5pK)_U{ zn*HYCWK@x!l0B);oY`q4tMK~vCxozh9$;^=wQ50ip4y_5Kq9R7CWcQA^(|V{t1r(V z3|~ITd+E;MWM_>Wgax2kmbS`VW^oL?M`R<>OFI7 z79L9hoE~1ls*TU<(a&Wfd)cbs8rtfD8!l?&I>wF(v{tB$VG+YNqroPvBhG?wd1<-5 z)lg80roBD7$8Vspd>ujGkfIFYUg972bWG*{t#(^IBBv;6AT?14*tSrSBqX^lIij`z2_9jl%ynzF0qm)TA|3{0bwfQj&-+monAKvkGgkaRhMVUNV23! z4wrD-UF1kqnYqvRFAHyXl^rc_xTo=Zn~^ z>oE69?jw5z#@?rV#`W0RosVH@-5++D)eIRgdhsCp?h&;3*69f< zf@sLy?fkUsk4o1s7OCpHnh_ihF^ASCB`RZ^frv=;hT>&RhuIZf&+13KFiHtQDINX= z%vEzUazmkBu%``{U8@5pP-die?D|Z&%?uf)uobrgKhZXfNObaW5{4n~zzIZlh?32k z%~OV9*`&Mqo-EN3sfwQ0b8dv@$=$UqyG;|APAk6@a92Ki7J4C29B_`n));lYIdU{G zj(UZm`s^)sUHjE*eU6lEHIFrtfV<0$dzDlo@4#j5LUOWlNMF7!V`gVRf-B+b*(}Pt@l}bP+F;5JcDYQ#o;q2Y!enr|F3I^{pj&#OQNN)aAm7_0qs1*sxEj1D98i-?3E5_&28g%JRN2eXy zYAD}@M<_H?f4hLgC;xLS(UDb`sPX&t90Wt~?vWzx69=CiNc+3cyAIR&w|0-N?VR{l z9ubpOqVhg4`%BSgi4ye>Glm_}N43?ez!GITCnGJ%qgU(*&r23$+kA`2-g3z>j%~LBY##JBg&&WBZ zmXvt0#d(PPSl6qSY_Y)-ZJeq(c|L~xEOR%3bmksE{)KQf;DHJYOSC|mAWq+hsd@?n zOrS7x)%LC66->MJljtsZt9e^A$JH-kd{~td4%=hk2;Q>g@MAfUYBJD2zW@%Cr3(KS z`GhJ(A8Uk-lLp|hhX>u=N8GzYwl>!=^FCA1KyqSX8{^(c%a!QsFZx{SIah+s6_)lO zl^_5VW9hc0Y~*aoWHxQ#y~pc%>35I!3-R{7R8z8=a^DaxI=xOsegsb{bKg9VP*67Xl-(cF^{W1Ibjp1@`gdQHD{3w^J-=>CkDXh( zWrit%LZDn7BTK7>RR!deA;A||fz9p!Y( zPNhuIW1wwRiRtpK3QSPS`v7~>=D4t9pd+2r)^$l^a|jh*s8Tq=HZWXhtgJmZVxk@~ zFp^+dd%g^HI1#Xl91rPy8f5mZnA!dkCA)5W$NH?c8r#j8wp^9Hl>NX!avJ+7R<2vkSp3_)NatYZkl~54 zMh0Otc*ikuLe6LH9C#?HHv0LM&RzW# z%;mfXwQ;4Ix#a5#-XpP~fuw8OXO2FRWJ-a}Zk+|ExtCWS&vHo}6cTEA?g{NJ!op9D zpYrE??;Ptd)q}XG0{K3}1nh#pk_Nj(n!vd|8n!ie+7eig0k{k{%o8VF_CRJlMz?kF z{T?}%O!mtN=sg@~HV!BiTwuz@!I*xv2A%EQRg-HkU~^%bIBTC&M~L8w4;n)ybn<;1 z4BGnUA&&{!a)y@Q5;{+*p^ZxW7iizC`!3fQ4C_YIfAN_$WU*Aas&+7(wvRo3caega`Jashmj@SRlg zjp12q(uMWu=&K-4_`r=sjX-9oHbckhV(}Z zFD6SRJPlf#iQMMk*vy`)|Ll!oZn855nuX&rw6V6*0x+Hw=(Cf+x;sjttmiRL`Sqpv z%pm=W(LyF31@&B#$!!-aG`CKN85Tcr?cB*r4pBY9H8EvDjI|MOZ7Ibo)vt*mi$Uyb zAu8%t0f3Yf{)ig89x<*+V5c2aBQ7qRwu-aQ1Gc8;pNvn{li?qAi>$eE@*KQ zGonc=^&sR91`CRL@<8Zt7}J5QPd?_rmD0Ai=HJv<;0Y&ealRB&1w6q8T?q@@P(?|F*s*+{Obr-%G*Qx z=i(AvrXlPcuVsRq&TR^+J zZM{2b&mgC8J`FicEpJBo?EH=a$fK>&AA7_Ko>msEE%g*1hTjL*JGi}C2gub1ML1X= ztUB@!gIFLn84=Jkwt&|bDNR3U;*1kdNkpML+;lHLxNSzLKoO)W%Grr(E+S44u#mW0 zc!S{&k_9Bt({p2E`w#U=g5#;W2%b8z|3(lQ!ZagtSBdffGxpffM#WImf35hyRWInU9(YRmVVC@~Q>e zRU?RE3-fav%$0}lrFJgi@Hz3(53S$PhU$FOXav{KW+N+lWlvm17;4HVmG6!Re%~kG zP|!`Ha1L(DEh1W`a^7*gfABg~m)Jr+yX;n4c7D&m`*3mVJ2B62!S5_V*R%0ermH^h z>nB_r>eWw}BlSHKLm8&xmWmcET+(>HLtjY0UBPI2GIOxLi0T_}d=GzyV#NVQfB&Y$ z;+=unQ1Sgcg%^2^8n5`k#@`y*4TvUI-;G*#n)=|r^TRD#@={*0z5sgUN8qr;eF;02 z`P*W|BJcDsM^#59D17*$RMm)29d z#1-bi4TZ97aZ+LGw0lr#o>FX*!ZJVo|UjakQl5nFxKv->w+1-zs|__M>GrAi4qfvm)09l4R+ zD`qHD5BsGe?4VBlwWWn&U;}@IH=O>#l}xR--9~2|Q-mz;(|_aE_Lw#s9Q(GZdIRjgqdb zDiu#6yTzWcgk~9e((%XXE5YjC@033hBIIvoAKCQsXN|YyjrViBW&K_yUP}_tnm-2A z(mq!@3fc~z^8N1I)v~llo|*SWU*CaEzSrlmGS|*xVb*m(t?quao;TlkD!6T`GHq@g zyC9muK8ZIme90^(annE-#vAJF5SOh)Use1e%0;6MO+NBqRbCgJ{xI#iEgU$mzVf@^ zY)R5V<;&*`E4{YMN2i3m)Sj>M{g-k4%IP9gQWXwcg*gKc z6{k8*nJD+4ef`~RqeR%=9%-C}h-Fkh2Fhy;MU@$xFQI&e2jd0yVR{!=ueoH z*~T)ydLq5|>V8P$MQ{2nPCdbyect|_h0{!R(~_}M-BJsk4qGLuH|D1kd+19n%J2t~4j%?B7jezZgg%vj11w|QrSU3;-ci0uaG8z}TU)xIQ}WM; z%&~)-G*IM!0}HBiD6d|7Kt(`bG^>a<^B7nm(CK}gvl8fV8?hbUxhW2Rv=AIwI{Imf zt>;EYdY1=VHFC;Vq*A9{Kj91ztY>5jS@ zGC+{q6nxpoLvfk^j^a>Ihzvu1G4~yc*FgRjPJ@4~PeV7l3N4D)H0e#>S#R%|n3>m4 z=3{a9Oq$LH?vx4dGsKg2u1R+5f6WjoxKwD=IO`1WkePVS=I6*PagOr?g#A;5X(h%Q1Al;?Xfw$AwQkrtBBbFs~ zLfUHkmv@Wtd1ShEaQuw@h=?kHLrrqNMj|-C|D;0xeFypVNcU4P*)Z3=SJI)gY46_E(1|IM6#y8g4eS;4$q1<_vCxaY{>bX&6c!^wO3>zD5+GJ2<3zc%O- zRkrv+H5>$U z{Y8lVPwH%`6serxzmGI}u^1|-X64-RJ~~p|8MSb2)s~&DvOz$5!^S&7%+f6V!3@hH zoNq2T4lkYDD3butr|-TP0g#Mf{aDIgU;cJ!V3e;lN9G_|JxP^wL{Ip`(*D{ dsD}-dRh8OPmSfn~X`<&bM3MC1~eLu7G*vVy391QbjlQL33FwTYI**0WJaY!q$1Qj=ID zUeSt1NGb`UN>~bvOnfc8)=raDk_uc3E zhFBmZgp?RG;YQ>UD~TGSk+?(rNVE|x#1*2R*hrKR!9NuNdtw%Gg6Oc7%b~hb67g?eF9p{8R*fGn z?RzEo5J^kor+84ihFoV3ZBggfWlS=){c}+4MP2{G%8+wQvgp^>o99tFq|E& zz>00bB!3*DOx5;Hfl=wKv|&u9&X|OpxVe1ZNP{1~OGWB~rW^4=rogJJ($R! z42c|^mKDGR%<5okKTHw2SGZGa-frMZeD~R8I5}AHf5Woj5xTB)ZF_YsH~lW2iQFaR zo#%>&yEkY|mEc*WbY>_#-Q~PkF~qZ5iB$=m)F`XY-XhC3O}EGKrR;e|XE>=Z+J=9*O)j5B-3_3COoY@CWeuh+0? zkv~~KZv|LS_w9*ECX37|84f<3>c!6Ia>U<{wQt5CB#>toXh$niRWTYZ{ZLD;Q_(Fe zjYP1&Jx?B~W0W{psqI#zas_W&ZtIKuZSkw(4!1BXfM=FV$4$*Fy{{|BshcRu4P>X@ ztq9!|p*T7SBA3@MjbQ?V2}<1)m=s*1rUa^rN4ATm-A zx%zHd?4T2AIzCYl2@yojE}a`G8f0k6_9P2?$127m)1f6qmm(*3>jV*JL8N+mzUm2) z%v2wQs2sSllS~eep>`HUCXwR9iiEC2_$HGr3H28fWigSiB4g^Cc(I!9Itt6Z$61sS zat{a(*BJ9&8o@-)=+}{K>X!@_yJ4QTnCvvd;v#AGL97MoQbZfC6m3^ri1tq^lMN0r zYFlrMi{v6Lq)*Co5?M>{hz zqhZ+&CeSUdpMHbd>2Gt1GOslLT}+H|H#qvz@Wmurh#8FS2*xP71j>2X@PI$PuH{tS`Cxq}(PI%xyZqj-(d9NQu3%kV{9GR^SzQftiU zbj_~5choG2nu;w5@bcxw@2evd#1aQG;O=U}B=!Y?G;slCoLf%^5;m^dblJ}+U-p~04&(ywq5dkAZy*YPazAX_(#XD|KJIl3(s z>TnlGDDSI%p2E7hWjJ41gO($Wf&kj;F5|m>XHmOq8x|DLMp$47djNdPe`=3TB+z!L z083tt;BNv*U=1N7`YhB$6Kxi04oJ{H9scK|TT}7;Gb+A$Bxy*cQaC#}!Nb`Nwl>1g zB!~)i21_Cr#P2^ipY2OWOa$LZ-Xn^LehCG!f@otc_Wn&;bTk);Ryxh~Sv<>Zqrs=D zB7vvv(|g9T>(k{1vLMS}Xgv-P0Ytw?2(g{$U<PFy1p` N002ovPDHLkV1k?}30D9B literal 0 HcmV?d00001 diff --git a/Bizware/BizHawk.Bizware.sln b/Bizware/BizHawk.Bizware.sln new file mode 100644 index 0000000000..e455cd8008 --- /dev/null +++ b/Bizware/BizHawk.Bizware.sln @@ -0,0 +1,54 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BizHawk.Bizware.BizwareGL", "BizHawk.Bizware.BizwareGL\BizHawk.Bizware.BizwareGL.csproj", "{9F84A0B2-861E-4EF4-B89B-5E2A3F38A465}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BizHawk.Bizware.Test", "BizHawk.Bizware.Test\BizHawk.Bizware.Test.csproj", "{B9925307-6A8F-4CD0-8EFB-0617FF0AE5AD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BizHawk.Bizware.BizwareGL.OpenTK", "BizHawk.Bizware.BizwareGL.OpenTK\BizHawk.Bizware.BizwareGL.OpenTK.csproj", "{5160CFB1-5389-47C1-B7F6-8A0DC97641EE}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|Mixed Platforms = Debug|Mixed Platforms + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|Mixed Platforms = Release|Mixed Platforms + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {9F84A0B2-861E-4EF4-B89B-5E2A3F38A465}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9F84A0B2-861E-4EF4-B89B-5E2A3F38A465}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9F84A0B2-861E-4EF4-B89B-5E2A3F38A465}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {9F84A0B2-861E-4EF4-B89B-5E2A3F38A465}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {9F84A0B2-861E-4EF4-B89B-5E2A3F38A465}.Debug|x86.ActiveCfg = Debug|Any CPU + {9F84A0B2-861E-4EF4-B89B-5E2A3F38A465}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9F84A0B2-861E-4EF4-B89B-5E2A3F38A465}.Release|Any CPU.Build.0 = Release|Any CPU + {9F84A0B2-861E-4EF4-B89B-5E2A3F38A465}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {9F84A0B2-861E-4EF4-B89B-5E2A3F38A465}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {9F84A0B2-861E-4EF4-B89B-5E2A3F38A465}.Release|x86.ActiveCfg = Release|Any CPU + {B9925307-6A8F-4CD0-8EFB-0617FF0AE5AD}.Debug|Any CPU.ActiveCfg = Debug|x86 + {B9925307-6A8F-4CD0-8EFB-0617FF0AE5AD}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 + {B9925307-6A8F-4CD0-8EFB-0617FF0AE5AD}.Debug|Mixed Platforms.Build.0 = Debug|x86 + {B9925307-6A8F-4CD0-8EFB-0617FF0AE5AD}.Debug|x86.ActiveCfg = Debug|x86 + {B9925307-6A8F-4CD0-8EFB-0617FF0AE5AD}.Debug|x86.Build.0 = Debug|x86 + {B9925307-6A8F-4CD0-8EFB-0617FF0AE5AD}.Release|Any CPU.ActiveCfg = Release|x86 + {B9925307-6A8F-4CD0-8EFB-0617FF0AE5AD}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {B9925307-6A8F-4CD0-8EFB-0617FF0AE5AD}.Release|Mixed Platforms.Build.0 = Release|x86 + {B9925307-6A8F-4CD0-8EFB-0617FF0AE5AD}.Release|x86.ActiveCfg = Release|x86 + {B9925307-6A8F-4CD0-8EFB-0617FF0AE5AD}.Release|x86.Build.0 = Release|x86 + {5160CFB1-5389-47C1-B7F6-8A0DC97641EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5160CFB1-5389-47C1-B7F6-8A0DC97641EE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5160CFB1-5389-47C1-B7F6-8A0DC97641EE}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {5160CFB1-5389-47C1-B7F6-8A0DC97641EE}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {5160CFB1-5389-47C1-B7F6-8A0DC97641EE}.Debug|x86.ActiveCfg = Debug|Any CPU + {5160CFB1-5389-47C1-B7F6-8A0DC97641EE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5160CFB1-5389-47C1-B7F6-8A0DC97641EE}.Release|Any CPU.Build.0 = Release|Any CPU + {5160CFB1-5389-47C1-B7F6-8A0DC97641EE}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {5160CFB1-5389-47C1-B7F6-8A0DC97641EE}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {5160CFB1-5389-47C1-B7F6-8A0DC97641EE}.Release|x86.ActiveCfg = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/References/OpenTK.Compatibility.dll b/References/OpenTK.Compatibility.dll new file mode 100644 index 0000000000000000000000000000000000000000..9efd281bd6b9ddd3b80ff093fbb3004132022522 GIT binary patch literal 3211264 zcmeEv4V+w6dH2nH?e1(cu$!=(4@i~(Vc2{KgN``r5TY&`aYcj`qDDlGz{XtNAhiiO zL~5x;3=|aG_!=u}R8&;dsHmv4(w0_A!D7p+5z(Sji%JzOt>OLupXZ!A_ntd@XQIHy zx8(QB&OGNi&;R*2&-u9Lp1Jo0>#hv)K@b%1|B*+6;Pd$8zt!?L`_Fc;D@$&y1fS{q z#^Ilzb;>smKmFYEHjli1DtztKD_=MAs#k8>6kafL&TB@d-nePxyiFs|e&LHpUKhUl zHLK>#DIaN}Pkl}hoH8pP9P+liPIKDs337+`&*~3?+m8)`5iQ+xzx#e13kIb8V6~eC zv|s+M1|Q}B_JQNz*XlnO0(9$BzG+?jJruYv6zaI(;9^wV`Z97{FuO%{|7|1)dSkVF zK|eP}ue#thZ@K{V1-Dwi=&RV-ssxdBt> zY!HfO~h-&yhp)w!p9cK)k= z@~WS&Y5e;^_wIhv#Cul%VBrJf@4NLir;ZLS|IFb(Uw7x(Pwjiz)Z!0Zv*X1Nd}4Cz zm*!r+^RM>KdBt|~)?@m=-@p1z@1FP3H=ljf#P=V1(o>#(^o6G$@$}<=e(I4MfAPF4 zKJvA}7yr>m&O75rAAH$&&;PqOe&m>`1&8fk{EmM=(REd_F(1+w6Ca+?>R!nTF@liSL_Lqqxe=0*4t9*RGs zK`{5&TrdSa03JAK*1n;|L9i%1EUF)ZF82@4sxHCju}h;GBgq=G2XocKQFL7Y)Qf1= zQheW{8uQ^1Aj+kcrOk%~L1{~&B$#g=gwD~L)(NqTQXXBn`6!AlR(D4i=e90m@zPv= zn|wjR(XK$gprGmsM+o&`*YFmvy+OPCx zIz!GHKC^#1Q1E8=^fup(23Ch{#pNx$75EyNFAc8_(=Evq3BCp=CY=yD(=v!nn#-bS*tP13cNoBAj^M|CBNM%U&xufpfHC*hAxcX4?XYrrFtEK8rj(XHV{q2KL)dA1F6ZAB{ewBBdzTlhvw~nb>gTM$@eof) zmqz#GMQmmN)c7p)+b5&OmM9>wqokVi;ZwjFy{cNr$BMx!B2ix1PY+7ldtvbCW?@o>>IjTIEdt|DX|%Fr1lbFVJ#ufSX}>ka%<{SVY&VXWwW6qSb0ESHM4+pB*LaE#nCVU0HQGncP6QD=0*k=qV} zJGsKPgYm&(et@4G=H&|tFgIv{d|5a3RYW^ir9(2oRbglu=))lKd@WO&@1LA?ARfuP^9fC8(?-S4yMnrtWE{!Q{3ge1(-m`4zKO{cv>^ z9K=A)@=?bA1Hb{;fI;pShR-}|J3lwS0I22R(nUzkLiH|)j9m3r6m1pyBm`IJ{EqVs z^186D3O~yEksqK=6$UOB?ogE#0fNcjf?O>iLJJ7#!jALiD?mX3Ff1$|Ax8m9sIU#o zghEN|eV4U2e*t2fE00{T9XhXWXQ8KT{zwU~iM{eiY_6VB3QBR#v*Aef=RHAbVQDLs zj^rsMrRO*$YFEreHR8qKyEOpwaaMp4g!di>FlHUX>?>U^If(o&Z)e=1M2i}da>W4A z=eloTYH==j{ld2+AhXJ)p;!W4TN=)Zd56AK66)I0{G7Cnr55D2GRT&yjzOXJkwSoM z56ZyKEm^(!WPIh?s*oGu`swZKp=jHYq&Gvnyba5lLasW3hJX9HWF6h25t~_@8^CAP z{K|u;cEQ-G5980~=b~f_x&!<#8S zsBrr5XrWjs4jolKy)=1L|3uHk&}cokl`SIeG0CN?P%nk>sOB98s9+9sRthV7sxL!1 zrr?+31Itl1OkO2Mg4J+2M6#6oi&$B|0$(Ny_;NPCjILsQhiOx3bUc?gh)-EsW*%#5 z{NuFLw>;>ddu%yq%)sSuIv4?3*|YgI z;2n$zg&Xk2ycU0!i4fMtbfAjJl;8~gu41)O>MwF#T$(+Ym&%3exeyteTgXoxoDW`a z&I1*mPbe&*3*jWb%`FZT=k^TE?Oom&=o#qUIk!|PLz@N3d)1!YA6M8=Joh8e3sVi zAMCHbp&euP;Oy!Y7@IfakF;lBa2(>u`Ow;6JJI%&)}2>I_~hT}(#Gg(^#X`i-^f1~ z;?H^I(g>Gw$LUA8v>mZ3l(vuHuVkO$x6OZms@oCF!tljRyz0+5Onwh(BzahU0VfNR zBeSafgGg4lV9?dFAW=3o4^gk)4x?9$DnCd5lir+=$tTK|S7SAUW70K~WkVw+v`5SQ zp^;wDBeSAWK7?%uAI5~f;46q9ruqRx_f}$}J{zXx4w_Yc6MWXh#-LQFqAQfjgR{_m z*);fILsb8e-*dLp^f0wb0@OR054V7~w;%aXsBXpAeGA2jR>W++UIbpa4S$rzMJiIV zG`BQZ5)(ipCred>8ZZP40{)Q>5ne1(9AAfqbes*hM@1nbs#hSxBju50L3j!1x#+dq zu+kd&CB_{fZ9lR4GLjF1j3HEC&O)~6&|tp09D?{*p&v*PQitQC71x1c_(v$HUXIA_ zEmb!`{M(8L&Rf7KSe!{6(_U)vCMCZnN&Ycv#kiLWIRf7%Ut0YW5N}2s+R@a;sPG0B zg>MCOG?<0X9|X?}4mvaFKbtj!2ZHsd%tF`Tc#{kM4?Zzm4^*!}_3%pk$!)#vn{r&}h|Ga+xBER3r?_4Up16naR^aj61USgeyeod2R_dX{F z2a5RrE-+5`HvZ%eQEM;y5-QlqCbyMVK}jv%3qJt$Fz zS%9V6ZbCIIs!vwGd7)S+UBQKb%9hR?-u!253>INJ0KbHRm*L^Q2kM>+M$eo1SJvCP ztwgMSGPjj3Diy=7A_ob;xa77nviets+1yr!D0dM9*hZfEs#v~2erVfQQ0D|#hAfVZ z$!%q+So8)OWYtK#s*p}t?pNj(3@S+Fqhy@UZDnguo>{$$$&=g4uy6Y?{$Q!iHX0sv zEpn779zIj7QegG`#PyFn@<{F|+enPenUXfH+1N-YzHr|oj|}mWgV zyKPM4Ql+?!DO{?QwlRH6m7Z-(-BP7@8`Bm`VHwVN5p@!SGF-*%TpGPUw-wPVtUPX> zOd9zWhueAMS(rEMMD-5V*R|j~MRXfQ{OcpZ%e+3~nhPTnuOSCyJ+u9A<^`8D+mT#_ z`EP$cf`p~@lJa{>!%OD8r?ef@Q(<&>0n6Dc=Ax+;_!BK>5iVZ!VaO}wirWAz7k@03 zi$D2-rXhcH6IaCN5@IS2A-rArH)8dlTQMh>{~*6`-Yt#h-oWpd^H09|=P+>;ZBqy^ zKiyP%X?(EU@-;?}Q_Xo5ckZce6W4!+#Y z(q@(~C^*&?$QKk)jU?LXfqWU0IWrexX;qR{(WtC3{uXv(@xe8B|I`D_l_%4l z`B)1M2H|`09edOH>R;kZ^?mq*TEAv3OH!Z!D|`#A0V%>NQSxRN9N`?UkedHV=Yl=DJX^SM{yM^&*i1j_4zmA z`cnGJ1^iNd8$3iyFV;Y6A2|#7*qIoZx(L{<>tzWw_x?WsJn1G_w_&lLvjy{WRjk8G zbFjeB8VhnEmKLSUt9?N({GbTMuATb=>cOI6pgN32E$#>`occTzR?o~sW^FHwTMe1b zpGT!Fxl;A5_|$ch!sfVU!y=M8+b%n0-O2q#&Q<&;()r(Z34M%+k91x_$Jg4))$)j} zwWBijgOyC`y}7Nc@VT;D-3^a()eqtCHga;gxR+d;mA0{_*2lHCv`Jq6;^qdnz^ZST zpqvB*%vM{EBkUo0ZbVbAIv=aWL-(V z=Jo4TG)X>{rWUrHz>ng#r%~xzUe3w|=+Mv*9kn|MxKwlW8dj+kaJ6kKjJbkJ$(C@Q z?Fmjrn<9y^ZfgK~1@7g11a3>FZN3iQw&Zzr|5@l*Icszf`vwH9U}oKX)iZ+9n{Pu} z=Bl3odoU>Gs-MP}a1@MD>WfVL7)`9x0vae3cR*mEBV`9f>{Yhdc1ma4ifUTPoXo(` zKJmIziknBPFmRnmyKbJ=QFS9+j#}1{Nea|#`HDEPlEgW={8n6w74lmT#^35k5v?kQ z3E5&O=mmYNtXa%!+2&`U%8N$N&uwMPuI$fkejUKJNi$2)=q0(WZ0(f;ShTUdt50PG zNdRGtay~(F%D!91a!+&@()y5V_vbdv^T zC59k2V~7mK!e|?_0ey?DA-4TP^LYBtmin@@&#K*WaniQg1|x* z!W%3QNv=HkDf<=NY*Gm%M7c$6B(**#*;>7sUZ~6lmUAt76k<3ZePr`j!7P`Ao2z~u zglwoFUB1Te6fI%Te3jJOE*Tl`?!2l;+vNi+DdgW~1iZX`9QMiH=Jprz8@gI~J9=f| z_9HpBAOXtTPXlTAXR3mhTT%`8u=K@I>C#x~=_sw<2~#+i>$nZQ1Lj32Us0_7D>#OV zj_R*|4k(7|hvaYgdC=h(@Ml?Wss_jqO;%ocurQ|@EI=D$`-&pD@Jp=SDwtY>3c5Y@ zWvN9U6!(Lyc@zGW_xAKuzk)B{mU%_*9n242|JI;~0pZ%1v0ey!Z^sw)J_moFPT|r@ z+ypMolUqs`RI#q(LZ- z53ac5yxQLmZ)fniF31NHu<=l~2U{=h+K_YYjdZQQBw8e?RLkt1@UMZ~D}Ps2O7nU} zlVbPcw)_w#FO4BSg2SK#(fPlK&QU9uEHgLvK;8ThNsG;(@D_Y9n)y=oZ}1fY%#|x4 zx0tp`e$1Bq$RQF-OKwz=_H2_aPezSIwn#xMIdV2pBxRXCGQqSOwlLJcci1+qy3)TF zdzO+aJ$C&zJJ|XsnkTgN(;aQ)c-VNfEXOSatcuGkXG;X~zmq%mp4seR*LAJD$9NlH z#ZoP-sSp0Avm#sH5%KH-7oxm za7S{$t^O1X!g5I`3*4AyT>b&fEhRLMvmgQaUnaShQohC2jpBdw|8i6-||(sqtmx@Zc)Gnim~bWeAJWK zTl>!dZW7Mz8|WM8#awmkke#cz#(W84!GGoqj3q0dtp`vqSaAR|J1MG%I0sPe2YNk!UmX}4|4&>y~8=~Rqw_iRxas%ZA|L8vsoJ6 zjFC;Izw*jTX?~01A+l1w`YmurHyspI{{>(0fyE6!fK;x&7guScm$l`g5c6WWihC&~ zif{e4$Zh?Oe(WXhYAe3=yIOdUetb_q?q!uvv&zF(!{pK+xcFHw1w|uT6m4lyRBNf~ zn}0X)(@thB!nCmq>#VoI{Anqr;@%NaakX;Vs3;#UD&C_N zrdLs8y$$B?(_Y+vn5g*fv|cn8i;5q2^5PuUyUd({db!DiW@#SH(ma~Am3b5n9xA%d zo>o^=D`3GiO>68}0tD$RSU2(}*g)>0e*?5Md)Yh>%~-lKymuHoIQQuFO51wZ=B3`) zAsy;{Nb9ZZRPPkk`+%qS2(j-z+V?~JDVyhNy}h04y-)S7@${zfKB)Ch>r`*8>b)=0 zTi|tt#IF#GUv4;Gy0dyW?gKJk{$f|vNKZ~4<_FhkZs=Wnoy7m^x4(%8N5{~-nyY71oju&Ouu9^JxH(Fk`HO&ulO-btIVnT zX1(Rrm3*a;g>Vf?=qm)zfxctu=TUQ36#H;JTtr(f@9i790;03Zg`?&cR^SMfGWwVI z;YQCDmvKzv8JPmwx93hlo4q&a)p4i5>+j$=iCrO8to!zf z-kBcRetcWoF?8;)?KSROB5=6bgG4vyN~FT|X{yGN7!}V^*7;g-Lpv%|-WKJ>shYuP z0gy|`YM3`l#X@*h8?tWxKw)+&Oq^<%KeUq9s$_y3Ge)`C%MbUkLb{}3-fR(WR)hpu zH*Znhx2f(Vh4G(fKp#+ElDa~94=XQD)y&o8h2=gNlpr>k_lZ6s}=4 zjvc+hlaVX%7SIM#C%*Y#Yl+d9atC*qlOp6X+-VI62co8!n*mY zgeAO51A4QtZXq#l{)&VfvuS_;owhzrZB12i zzBH7NyDylE0C9KZixw56q~*)g+u9-P1(O09H-Af97uCIVsBZqAWgEl$mBOY!YJ?Ax zs+rqJg%2t5k3u|5qG7O|2MY^fxtu2J=9|I|D`dj=JlDv~nCe6T{!&8c zw>;0n@j4W1-4wfF{@Lmu6cx~sf4_P1j zD3H)+`=Q5f8m#j*oHvMysQqj|+^2AREL_d7AQzHu`JdGfJ%FZp#3{Tn!eGC!{SfEh zY5q--J{IJJZt2VFhaN!F6bQh4xsCe9OwrP~l)9sRN!|2Fo$zk4P_J>^HB+1G0BpBX zgS!sc3#sm}nW8m(2Nbu4ZFkKylNe274KIdt9M?=e0dh^&=an|WnjMAT#p{7-;x#u| zu)?j^dxGT=rn_+JjHEH^3l=OrFmc9B%ZExaWFR`muab)2L%(JGIwN%#znQ}sH!aYpir;P(AJ~C zz#h5w-ID8P-5_BJbTF^6`+`MB9@tnDChiaR92t~R#tfm+nv3aS%MNU;JYqKtANkKDU8~?X_7dPl_&qq*2^xpq zbbPCc_*VFfd~QL8>%9oOoLy3LF)2s;nU!7_-)_-dd-VlNqW#PR8!wMIE|5qg*1nsS zUWpgdx#RdS8jH7!^@eF;ojpEugVc(1_V_UKSQ9301I7miMaGBh{9O@?Cx7bUV!COX zm}cj1w`i@nX6NsLgO|u(-z?-W$uDJ*D)0k@W~_ z*{B4!EiC4kS)(Al8Gj3L#>}%?CAVtHb6O>LXvxW~lDk#`Ro>R??~X035^Myxy>6v>3#OJaOZY~ey%C$+uF;1dxHi~?>iIlXX-V;sSn>N(8hP4?fdTYlLo}{D9iUW z$VKYK_4I;cO6Ob@3HU*gHh%bQ-w&T(H%SvB8?yZHL2AZGJMEzx9&xd7ZX~B}wyV20 zsk@P`teh1Q_)D2K{`zd+U!Om%4O#xCLHOEv}pV}FO(ebA5HX}?YEI-+zLyBWk8Ggdic#~?`TTGx(k^6o=k?6A6B z=S9!?Js2#EuCZJ9fucUv1(NHl3)|cWIuLNijdcR?vw=wT+e({*n%8LnqrLqNUi92` z<8YgPTPe7X+X=^xFdgXUsSskb4Y*mwWB*L4MrEj*n*2U1q$*pzR#dY2TtV zlD0#qdyq4SGj3YCk$Vh8$9s@1i6`RMK7O%%pEiTv^?v^0n*BmA16}`ZyYCYXTHPSE z`f%I5_X7xP!nFL6jSL1w`tuX+ck zc_DTw@dY8SB~dqDB#{rVOH(z2pUxxfH>y;OSY^HjPIxmJtIW;(ev5wpI=|nc0)H#g zdzEo38Tavf-P}gT#_%Dn_K#BSVP)M;Vlv#PtbY<#t^%p1xkIFIEmS&9ERBl5W|P zbxzO&Xqayi0OxfV5@O8!i;`TSE@ny1{HthKN}_J=7IwJI>MO`SR@#Ph8Q&2eoUMTi zr^hGgal@7uq*%_KbUH_p03t@+>{Y#WwIWWfGWUQNHpy9K?&J3fe#c2xktu}hROmlQ zZVabL)yz*xgUo*EX5I$@mrP@9#v6UWQidz1RWqX6-m<{o;@$ziQJHLc^9)XWjA6RstDgE^9fJ^frMY^>l6 z^f)o2x3eb#+dCqAF}q|LY$4Cp{zYUUdP^F@ajBY>iEXE{K?uM zi~~_m&EU-}@O!;__ayahi+JbovV0Q(_(qAi-PX4_dph5|I?lI*DP8S-#(~tgu4Y?CE^->NwvLrgY_79Ei5m%?awxE^#Nq$Z9K*fIk#z<4>GD zoj+b3=TCcEvhpwvr2afz{kg7te?$WQP^67NarSincy*jV?QO~OCk~|kG}WIy-TNaF z@P{I8{E4%t^T(^>{Aq7XmOpVI+A?8I5`WyYUBjE5U%mW&9-jp;P{hNj`zyuSt}1`v z-+L!6Em*h6kqys-=HFtkjB>Mw?&*K*vGFJina}j0x#6uzyT{^o+p3Y>tea`~V~5=| zczc7+!*|ePKHr7&4f}U7^*cJfhixU0K@?$RdB3qJ&)ng7Tr4|KTSa_hJzty3J$CxW z%vjVa;a%!qiO=MeIT3bRcIi&6BFK*{O-s_&Ze>qyX}z0TQ?j;#=w*$V-rq&`xh=hW zTY681zxa=HAhTB#@#HuANP=Gb{ESK=_FRqr=(!sGM$t-l7V-QH!r5*lalrLb@LXcD zFQ#g0|MTlVueso(E6*KaEnIu8Jsm;hic1Efl;&1H;8>aNcY1CkOZW37w$tn82ratnW?Z}uAEM7m zs$ovmh=lvdbZ3Z8k(m3xt-egI6I1}IZb^o z9n8+?)|>Pzef^_;i2!V-1kH78Y5Ih7IB_=SNaqsX!N;*QEL%PL8;$X{@w-~$rxH*^ z5ifo()c7T;AbzKuq47K2DyvYLYb%c1)1CKH0qCNHr;Fn@_6!nLByVlK4%giiVvD=( z9s>UUUUz3Aba~x90~mO;k!=P#oqSG7n=SM+wXzG;;$)yOhPXY7cF!q^w!Ax_C`N~S z{4;>jG*)Rbq~jQT)=4n~eT;ZPEdyt2UVdjHaKYy46H7()p=F1fkJj$er?`!Rl=wUQa5iDUEx(!)zz3R0#HQ> zs`AFId9);Gw<_Idv_@Eh;f9vMXy3I}Sv<>Smj| zb^|ezRMY&C@US0!tJ>80I@n6URElVjz3bp)mFOxjc}A<?*ZfJ-NNp_tu zH^@FG(+S0mOP$Wi%pA^`ak_Ca$ROZi(Cc@9DxpFv+zWQB-)VV4io5D}2_QCS!{F@z zvY@}8n!8zJs{o9lgr-Dfubrb3Rib{^Fn?xs9W@ji7L?Lt{omf zU3GJn_!XAuR@86lS9*=7*F+)!w<~{92#%OUnx(i=%eU@+$$PetpFHbxs^3%P$pxUzBL? zSE5SXulPCXc60D`YE0{_Dpc(M9NY<|+c|jVaBx=DMgnv22DVHp*FWZRViVZ0T-WkK z$l|VYA_1g1alOrn3*#7BIiUi`2}-ojiA0rnPI!Izlg=-b^h?VNA&a~6D*@!$WUn6* zRbnxGlyoK5*muz{L}-YQ%Wti*r8qC8(#HB5vaB_B9Efh!&9@|E;X)wCFtX+=k$^E2 zA%>gp?R*ty$L1^b$DUj8>bU!n?QO}r9~lQyfBr@NS)B9-zwvJ~|LQXgMFRd%B<{~@ zoqywO`19Ns$LsKUVwj`7I?t0jbuG>da4fcK+S;Pe_lpBjPlt1p-=?$fnqw90PmgyU z&P`?_xUsIoxyc#8s2fq559u^ctL$YU!n!oNJ6D+X{5sZ0lT(0U##$gJ&z7;zuZywK z?M^<8b(-e;l3KXrb%t_r`%T!>gND|B7Dut>n3`Tw>wU`v5dCkM|FRg@#xb(y zbQORxlxQErM3pFp@Qcs(dj&pEg^lTZGVa$vI8mZ!zh|5OR_p9Ih&OoiG$~%1D~C$2 zg8_RnZ_2af!P}=ie&9*-x_5bGaO89ON^sSEhSP1&mOr{0-I%&CII{V_Pv=_1_C;vd zHtToSp8c*p-9p|rXecdtP_iZYvH)$vLxaVQ;2_tAt0O(G5pA~YC%R$}28)mV{m0IF zUZ(9rtzUjwa!jpMjAa9I(OTxLoF|aERNPkS^HoKAdTT{>(>KujI0={0Q{na=a*flC#pm- zL{4)V{2w>6_P=&Tx>HqvM3+d@Ao*mbj1%nrwBZjy5CWaq6TT|14VYApY> z9QRa+&`tg&fV>z#=DM0@nQfqRx(T&o8b*~CLORW} zpP9d{&$RG=QJm`L#gZ{$c^;zHG-qhXDTafj>*i&oH-@W7)y&IDg*9cZCovh;m36kT zno7JL~y+l z=LvC+66XuCL5WEr&L^?TYy=T*;`dH*4k;GJc?(4LH_n*|UB-C^Fd9ac;%Ku5@C_2c zOWF{dg>xA>b#s9PEZmW%YUWK+ahF0Qh%Lg|t(*k0O*l6wCqZl%&P~cm5SIw&7Ud*} zw+QDp+JM99Q6z#tHV^*r>TgSzQ>enY&`^EmX1gMz=@j>C7CeRUfmTwe*Z3khwk;X{$n?vUpYV$ zqu=??AkLTz1#(q_fJ9{M@VkxRG>lQxv|%cw{fbk%vtUnMv|_49XTGiDiC#Y*6D(Nc zo~Kv}@Er9_U3wCuI}M7zBv^35{~!4Ad&kqQnIwG>dDn!DG}{n+Gr^15hUMS7xokg} z7c5xWiszGBxck#I77OoXIpV;^V$6r(Blos&Mz>vf(MK|$AAYYlzF-9~=8Z6JZ0PX( z@Gg)v=X-;NSdc}~vmVE~c`qqw*~J6QAzmxn#UtABf2v=8iu1=?PX zV9^;*=ttPGX6Om<%%H3_1D49I9Em@>NHR;uKHK0?GfN-EIr{+TGSQDK#osY)?D#bt zEULBI4QsU+{(h%|#heo?ir(+pBk#>^*?eGPvdwrc!m~*d+o%JglP6oEVSXf|;l^-1 zmxy(9zmVYuQVsJHp(evk%6veX7b^9jQkN?A3#E1_^^j86DD`Wlc9B|R9u^80h~aK! z|Bte-C*}4bc%7J@Q}xnF>SIYv0nVqC;rqLI9fUc-XRk5)L;)@Y!#z$1>+>22^@9S| z%v`lwxVo0)r9jvIo%dRNkcKG`7SAENjVkM=BxHCODd={7X6?6#2n?f4+)q3A`Rs=2 zwJP_j%Cf4wkCfAQV%xatZ)T)E^id$LZ?(qBXRkH0MFB7MH--+si{R{8Eel$)&OK_SRw01O7c<=k3FyD(mAG=s6I&342fb~ zgAM@~4LV;vT}-OG_7n*iMv=I$wmp4z(=2o~blqT}1$TDG}Fe+tp`Jn5CjY+7(yk;h1Pjwe69t1=w*5 zTQ^dYmx6BF)(06gRZ%XB^z~F+H>2wC22#%7tiCGsVHgGC{@S+n*<)r!OXVh2xk^=D zNUE#06#?j^L|mtBTc2Gs&!vWNFBNpNhsx@JXMMuMrobj_Rsinn)_zup{jA^4exL2ze~OAf5ZTXyTu8b*`#pfV*+77NI58gxNovfT zOWtequPwYbTu**OOE7^5Su^K}_H)RS`9q(xpY?kcy32Dr!Vvm^hX*_NNQ zz~xVqbyHiIS7ITbQZQ!Drwj6=?QjGAsF_J|>U?#|VRBqaW9r;b0r*6Tgijn-;FHH5 zGaIP@?+(C_vWZ$;4%-}kox)97xSC->E+pMu8+!l^GerQj+NFf3nazTCSptvaNZLO= zC(8Qpj{*t*IF7(SkKHgASamy8-5W*SRiaMXlYNZyLt3BC`4oUYN+k4g41qqM-RVB; zg<_67$8il%V;gsRZPnq~|CtEx`mn<}jv2sc7*$#b=`@}ye<>r8e5(9iKgV6)6a)`c7Z6&fK*DdAs%H!MHEX@$}C(G{;o{zEHyRg?SEN zQdOKU{4IDpe#4635IW6}*wB|XZSBzCfv1!-%ntDdH=_wsH&+N5K1`}%-f5YG3xs)% zGKWYt&AUnQr6{AM8)lcST-VCiYUMFfZvB$A-j(_Y5d|2cy4kG=6N>j?rPjysverXV zA9xf{yz7WC8E#O#k1O63siwJJcyQ)I(lzr*(xD-Nn7Vl(Yp$aN)KNrQ*p2gcuJhQd z%%@O4yo3r?nH%{1QvLpEe!on=e}><8@O#aC4&QU(&J@uwHZuO2i!BCKpsr9 zKcBwG!b^^R1(Kf+5z20rFb@|3?A9E*fFFP)0qW*p0_4MeM5vhofy*t5baQUcT1!v>{!t>~pIu9MY+NR*x)L?i%v@1d zR&|m6S!)SOKp#aC`c~8DvH-@|V`d&T=yI^4Iu8+@gPzW;HlzeVDU#4>+t6c=nIUS> z_23ZIxN*X^;jqFjv~V@Uf?PR>XxaxMWSw0)OFQ{6o5WTB=p%f^w{I(P%5y?oN$#YJxr9=M5)XBtTtqQ=%heG zr)@)zJ#H2gU)m6RKy^_gzQy|D5`}D7$eLk6E+pMuJ9+?3GeQ8qDyd0~nmJqm*N|{N zX0;=$!$;On_-NbFV>iuGr*2Hu9U1scC5%%6 z-^EucrC;o~%-Y5~MsBG;0m;}>-|n5y4Hm6(`|-WO$_P{U&G8eME|A=szb|O}4$9Jj zgEf)x2k$~*Q1B+Jqv@sVBkNdJH&yXGTu+MYvu7bT>tt^5-wz?zz4_EF78dslj4TES zS45CugZwdbb_5tUS<*DG6nV7t8qy7Oj?&>6NjEp!{is)ihaqhoEDmWk!-Aa9 ztue7F@%MkmulrFRz?j)U09}AgP+`rSE0F6+==zRptgN|<0x*gaiE-A>T^<`-U{s*% zj}6q~e6;J2jS9EP!qp55av|xKk6CL-51?UQPXMex3?XV}O7JdC;APEQtPlSvknqpW zTOJ$xQC8g!Rd=DNyGGP?HE&S>`Y4gmw_5sv@z@RXCaZ6k>f0jvb|>{^T{lty`Y4gm zXRjMQcEem`^7Oo}*xsY_L{kY$ez6Q|eG>p-r4QKT~0LU^k^F6zTmLvo!ti}RvvjgjM(v?=USyF zWQ;HoUMU0RJ`zpy4x7+ny!y}t! zmouSGwolj0Uy8j=G807IyiX*i&Ucef$y<}2iG3~wZ} zX1+=)yop5Je2v6pc(bx@7S=6Fd|ik;l(#8*mi!WI3>_R6iLkEb{*!io96GS0qd~vepTY;Rl5%RrovSgD_qU6AQzHu&Er|? zFb|+^{*?gva48XL=5B#oCUDWZC~F-?0r*FWgnxD&=CSMM|EjuCRrhUCw@TGT`m*-& zC;@#GN$6XRK7n2NLezEDh7^E4N+k5zHuTuz=7&_^t^wDn(jSS^^`eyV?Q}iF`p`*%gihOr9(&yU znE37*^BhqlzQy+Pexi_@EM(2F0M|qbo3i?%2hcP>Cjj^IE+IzEJSc#dl5jp|wIi#; zN7hgHXxq_aH_d-Kb(g8SUx>OLB)V!x)`va{B=p&K^x2)(@V``Z+?wGk!2T9%_zoyt zt>I??qiL+tVo1j^_xzWJV=}y!bi+KXbhw)&+NbmL_f!HcL=m=7!~C}exlS?v$CB44 zFqvcNd)9iLpc25O2r-*xpMq=*_bBKi7W77v&Q6Z6X-qvkqXNLBgk%!eEqYI%{S)`O zQ&pNKC`o6T3~y4kvy={RCfQYdR04V_qVdUDkXsZpZ^>H|m|5{r31Cvh`oilM^ucM@ z%mvjVmo2ST?ri{Qm;ZBv>@{+xxjl!zU`cdN|G>t}BaRCYNB%Ruo0Z;>@xqpD zDHhJ~E@=V&{;xm4eyFxVU%Cx?VnUS?f6MDy;cl-LT-@dchdfu!DO`Y2S2jsSU`Wb*8i=7N0C%Q~-uhA{yhf?@i8ACGoAPumbV;2=B(9?0b{l zAh|x&>E7hbV@;U24Y)VSpy1-V)0jRKizmkuH<}Dh6VvQ5y<4Y{mVE6~AAY zdBpWF_|AW(oy(PS$oOI0#xIEV>d&;J$IS8M>sH2PtTASu%o5#b*uj#9d5U06hF6hv z<0toyFn&H&co?>Jf~mv078RtVHI8S^4JnYid79vc*Qy0i7pf3mM^e}EJb#)so~j58 zqfGm`AyuVro~deYXzAg3Wjj}#=zP7=LN<&R;gN8i zvz*>rBfVNw2wBoyjN5=bM{o<_9Rz8ZlP!670y8VdDuEbNL>vEk7UW)nxLmREd%pAI z{;0YZ;k51j&OPsF@Eo8%JQ6XeW%h#+Jr$8pmS%Mq= zVr^-JMcp>eFLk;{Bi&k52wBq7oi!GuK*r3=t;0CyQ6o;nyh2ODhQM~UR#XwhnlkNU zovJcsUfEK;rll(u?^ijW*IL+y(IRY|ih#?5ta!Hpd5!h(G=kvyf^ic5>OC9A9rtWr zt4#Q|&Kl8lWko5{`wIHJXU0DLNGe^Ur-54cdaSPVB3+ zmn|!XEb2CfX`qy@Fw(Up(q+qvA&a`zl?F=bdPAh^l1P^=!%p{p=t={nbWKINE{$~A zvSP@hZv9FF)yxG14|foyX8ur!YugZ8g>#*9VnoyY2|2u-c(*d&DmC|#aQpjN_n}xF zBM0ltsOau19f5HI_r5%K)BGvnc`xx^YGADNcd{HGQ2#Q8yU)Vaq#zfP=J?LUA3 zq_3&pf%1T3*-!%dD3Z{(TI1uf$IPEmgD${xhl`6>iOv!U_~v4k)rORSPKqRS+BWpq zW9DjV&?SDEYP>dlhr(4XT+Ofm_k+bEXU|jH#^7R!fd|kq?;-&1B`zcc9*-$_ixYTU zC#7SmZOHoYj{*t*Y#Vy)hWQJtZmFu(Se7silLYw>NM4->*jmv$QpITwQE*> zi3EZ{k$Ah>9QN71iYRv;MID04ago2(KtK9{ZLe`_VqHt{oE~*EvZ2|wuj=nLe2@uq zpTsJ9IMD>b+LzYNk2S96s6rRlto|Yrh$}_nakaVZvwanjPmiLG3G)+iBid!!q%OBB zjoQHG^G|IXY_aatjg;g=7IfQZd=RhC`0PhTN8S8F+ir&%>e`O$sPwq5>tT^V+fgLm zc3ejxAAPp3B5J!wQAgAswog1{+vA$3J)|TbvLN0bS$!f6@@tLNuI^(c5{MN=+QiCd z*G*DISF!RzT;ACB@Y!|qu)49^i$&HLB_i;HGI2lbIOww<4;^FXf5Z=Yl-Tw3C0$ZC z`_$Jxp07`Co8Onq^`nTuSIWeFU4k^L&IXoR%xt9i+rwvj$0*2O2k{VLpkn)n$ zeB~`x-a@68k!qMjg(`%r+K_d#SnJlwba|XL?@4{MKLz6LZ^v?=@5CR#Ri`aPgXz)*^` ziLKA}Rdf|wAH?OgZ9kvgG*##3dU3?nx3L{17bb78CgW)U>NX3yjja#jblA1h3aevN zq(e&bAq(O}@nrR5hv!Gud?h0AgEDbH?0n_3>*lf7 zF=pzm)Vn6;OKi+Kfi+0&aqt&UG2_e4oByPO99U^$@6Cl?i^bu6f7SP4uV|J>D~1+hcjlJ zZj3(+0s{U-?1G>y!H8!>x-HwsuJxXeN}GojG@~x(GH;KUc}~yVU_o@=u_svWaxZ!b zP-i5UdkfmU=c94};EbD=4|vZ910nDEh|Y^X5x+KLQeWWOvwi$BpOtZjrk~5b!6TES zp3A)s&&ue4qIuUF3}rnlV+JsACeQZ1kd9-mY#hCZbi-V%ba-PM5@#yN#^(R-KuU>)$I~> zbyXMXwx+lC&yVLoluU8d^xh`MV;T~}>L0qCPdLZ59zpAF1>0A6r5+Q^(91cfEl zaMrA#TrTZ6Z*vY`w%<&|*+X-dFX-*t*>6bpgkQsq-V^4*?;AdIPG9ei^J?E4U7W8z zeI=r_I9DA8pL8=$v#rp7f>%P<)S6=O`h2w@s;VcU+RBo_Lu$*TrRo|`{XIfei>R=o zr>FWXDT|_dY`8<8C6OTTS}Sla?3MP45q0w+a`NH2KCqHh!+cbDh46ebQ{?rQ zd!cd@gM>IxO&+^U^(opy(g&;KPk}0&p3NJKfOB6 z&$uP6m}mJJ2cj)w=63N$_Ko)qNaMxd379@ptoN#{ROwFS1jW>^Qoy~U=OX`$z@x^wO6e1Fs0)1zlYtIYGVWS*at$+DTgr)}1K`N*1>%^jW<_D>odxBkfu z{#NUs4k%r%e`WxqX{=KG6atQ8t)016>|kM+YsC&Izb~;GGk3K56`pOyK{4L1D(N4c zYR7V77%wR1`qlC1SLYzN`TX=sVYZ_4iJQeX+Qf+94)192FPVH!h2n8JXclsnRnlQ}*QpOs2?<}pZ86ZX&6o}L^dwf^&= zC!;>&EN`G}nt!%I4hQKhe^;{5#uvZsQ)UUTK_(Z9IT-Wd97!qK*^S(od8j#lQoG7Ak(sPk^ zo#eCOpYrX!4xjIvAoV$KXRRC3Aa7AWZtLC;k$@i*Y2$~__Wkhrb(1uqD?id8?^Hhq zxzNKUZH#J|_X&j;SCctrKB^=W{rvVN*bB`^&nC>Lw~_Y{!L25)0Y-esQ0KP6U|#fW z!hFE@l5-C^@3xRL5!@Wx8_YY@{r365z^EHB90!Fo&NpeC_oS(sxtF|ftRh`0M&JOR zN{y3+*M16xmy3}^oI1q*-&x(o{(Q*nckD9}x{Un{VAPE$&4+XvIle+>hj3H3*Dq2Z z{gwi*-^Q-f;_T_JU%Wc*`Xz2jch@g*ARUuWRBvviH!X~;>oJjlKNM->PnFXL@GFl0EL=mDS=WeX2l2xJ=m9I_Z9y@*P zqlMsDJEpa{%{C-&xpkd%Xl|mDy7BRlqutn62$nf4H$G+}wEE~FhaFfL4Wot^LOP8l z#D$DRc+I>t@iyVyubdcBH_}YM8g!*XiA+?}YToKepUE zl$#)H=52D3eZM~NJVAU;I6L+7J4V#a7s$zn*OHZ_I;}sBkyGt%{c#Q+BNMww=xY6u ziO}WxV+Jr9MwJ#qI*q7(P|x?D#-H*b|2kY-?PhHkwh73@!e-Ajb-1q0L}oJoUO{0e6H;#0? z)4h%xwS`>9@7}LiMMtSRpk&2rCNY}E8eRFqhs-G zJv(}x7f~07PS1|+4o%yq)B9Ft9xL(e=%hJ#b~Izu`PtFOB;wn8&geN_gk8>NKWDUC zG}m68o*jMQ;3b|j>YIha%Vd6Gm zd|*&ye8_(G`N~*4j>TQFn4lkxe}+K_eczz4xo=ftZW>SBz*MT5Gy zi>z+^llt(F0&V>B*}i{1KWRWL###QQLAI(F*U^ja+DRnf2SwWW;j?`|e16>|O^9+O z%a1h30!LY2GdMaD_6ecTL}`at%_hOgZ|w!^WvWP1aQ-9^TA+#o8Lr5*DoDW+&I-6 z%pY<0Y+ApqIuLNijCFzs0~iD}+*hR)Z)!vQopA0Yr)lQ9n|+IU`_iY!%p%JN@F;7v zD_PvCQ`b6vN!=VRb;5PzM>(5yk5mMZE0l1#G9Ufco;iuLr@Ke$)p7So6Q)GAWc8mo z5N*Ma9o3y3;!cE-<&Q|fABwc`C(fSEAFqz{r@bv%{=|XQpQoxnJG=KsB;XH4+V~S^ zPv?(U$NAIVmMnkbK(uATtcg-mZZ+QG{OaX>>|g`N8#y0bDb99Px%u6DC)TR08#^Xk z!TClW`f>*dA6x8aC+)s1X*ag>T6Wv2k=?ADY4>A?-883IyTb=)aW`iMtOPQMB5XCi z|H1P{yw^}D7yB(NU6fjK{;n81yzMKNd0o#dFkjN=74%GGmTsPL%+k#)l|zno^Fr-3 z57Xr&)i7shK*GU;B_8_zGma1GbDDaFLIn^GN@zGRt`y`QM;!BT95GQPeipIAnTD5$ zE$#w!A@F09r0xX_2AO9XG7(zyW9FHL8Nk3Z>TNU7Y3`r2_Mh9VC(hE!E>KI8fkJM% zSY^JKy90`1wBLV@))F&-(KJ?RF{I|murH{J0bP6^Lm z8^c7EDD;o7uJ2mE8hR#P+kxkv(sP0ydr|@TMG4O@o?k(mC#t}&7rsQ>{PEKzb8aFJ z?{Rt9)Z_gw54F4yvKW1%a~>vuJpXMTCaT18{&&}fR|nhnI?dU;pw%apho&JjmQO#l zGV7cSpTUvU#wq|HN_alnHcnKDwejPpE8WIFa&5d`{daAw(J-pC5YlPPA`jZkzLccSJ+m!Lz){e`GDzSd_ORMcFYI9>nmM#^5E=si5m8dc!x?=sPq2u?jT|e3dvtg57 z1UHP97eW?C{_~n9-H-H|CILkM@zP;;VC^P?i0;Jisg%$42rEPZNeg=Gm}%rv>ye{G zKgUR%Cd_c?1GQvvu)3D0Lck0q+WXSY^){B*_opUs=6yBxg^ zX2ngH_rf6R3?%o^KXx5Vwu8ZT74KHOr~Gg;N79YbN5EGp0_CyNC1j30Vu=!_4aw2KD%yiQ5AQo zimvWKi3oI1Ca#0yJ)R5hvjsDDX29o9YeUw(nl#AYs3UhLV|5~JO8v}7pQ9xb@Pi_8 zKUS+BK0ET`r1t!}NtzJFa!p$su0k@GRU~FZ!~9tM*chI}-(%n>ujzy>F-d_{pfdlX7v*%2BBg zeH4ht)#j+rUTfxv0)2ncgR1i&)%kE-XI5KEedwe>T&HbIpS{)$hys0&P;OX~#>K_9 z=UmnRWEosS){T_pr68VTS?%eAG|jLm#z~Te)LJ(S)YGLc8~8kkto9TM7)Ft}ueLpX zcGE0!Do0i2VXCsGDxos@~|v~B9MC(IJj5xr`sp_&g@&1*!n%cZP#6#?j_ zL|m_JSD!s$jt~ve`)<~XCLIqrp4+xPinRcHnuV&?lB(Q7s;jmY0qCSeT&HbYpItN0r-tx; zD(Gf?$LfG*eZs>gUBB7&ozIT=Y%8q)UZCO+TJf3`eOl_nAqvo;_2w&rpg-{5SSfA{ZzR@q!$5hfIY|tDCV1O`&1QAO;W0e*|I*!=Agg+df%!dp4bK|CYr~Hl>%cMd6k{J10 z3=ToNavsDpRV){Mf^3e5DGB!f4l)h%Mb#2sMJ|lUt>~ExjT4RaTwK3AQ zDbi)jiXn@-)s+TH>3V&n%S5_tSutc$x4P0mHSxSW_q$6E6(@k00I7OJnQqICY=O)0pt%(_xHQj(W~ zZu8d%akkq0_1TY#j;8spIE;mEgWmdd-Q26qI!10Q=2`hA5(oxG;_Yg4*k}7HqA}@F z)FGH07w!A`{>`@6X;FJg3HIOA&B%sk+rE0G%+o%|g!!SwD%x~hN3iy#b#uSQb%QGG zroV^;;!2TtTx~AQ&yk!L0k?_qhrGShqw{#cTTCx?MkCIu=)Hm+XlwEQ#VqA z`<@z-$X>3M(&OdD(gr?=v-h#oQ8&NTw!2CVb#0fmuPPE~JBq~H&hD%FY+pqbi$_sM z)E>4^{K~e+&Zs@4BpL^x2PxjxqBa@k4GA+(2K_C3W+N`g)`1Yt}t<5rMCi ziTi5rq5JH|Lr0X~JnzZ-qkP{GOuX5ei2I`~!TnLs&#ZgsK8VX3dw)A`b=(^1kdl1J zf_NWV({@bL``bRqm??^((r9-OC|y!Fz3R+fRpiE`to#%S1cV~-HnaKZvwam&KYLVl z%vLuZ@Zyz~dm;iqC=>U?=AO^4o5x;%!9LA=}>usU*h zHzLDOH&T)hSrFHem3wKBx!NG*|Ht0jz}Z!m=i(>lYi7H(vU8gbO95uye~jl#q%OcXUC3y2yu7eTSbii$L?Sh1xoT2xeQv6m{fRH02J|~!D;=kedBXibT@B6$T>s_CF?X?MlN6@j0s?tJf%x~+_K^A|+V9?q2Q$i_vVbVS$h zS9Um#`7g_V4ZDsJW~%zh!>G-<`OH-;M7MC4`-WoEN=`{K;_V@CK9dHyT3f)~{yU4^ z_fLd_1CLY0w_7)-4vQl@+#Gh7v*Vt~4#_DQX2k8tn*ib^q(rbk=-8W9a!QgB zkKMd+F9h-J$IZDu-00 zS?D_WCUNd0C~n)2QUbr>dO(GEdiDc2hw^cStGUl#HYB5Hm{#o5Fy65bNPmy?N?9uh z08M)X1(wVx5paZ>DYH!AL|5XmKS<;0xjr($K5``NV}AgBgW{(Bw2GThai0}&Efp8Z zW4x#3>Dg0qKpt5V@)+-s7Zgw0y_BHeaGg|{pA(tu0-4+oNz1%Wd`1q)Buhdj`)E7xv0BG6U2!L<6UQURH{cXY9mB7oZL(;=O zG9>JCbr=-4?3bOmYgOD=MBI%cuBSRA1LTn-AyJ! zP44SJ$QJDDVobcu5cl#;kQHUU)6J8+*9GtODLj53VQA1a1hsTV&$Lq(7jCh>)Sz{0 zP&#kIZU&v%!t#cFA9>8Z8HpcANwEg9^%mc@ji7Ox&fPQT(hq}j-@Vv}Z{fxm>93&y za}U`Mf^MkuO$mHEH-$%k4b3(83M%Cv1bxF^CKqQ}-kFw2H0{kI%d~l?7L4)h-nXHB zruPg7`*|0dupkJ@!yxqI$tC1T7dP!*l`*OJcKy1u&TPp_Lx(Q+rGJI|enx*IFygZw zov(DcFFh9l-PZAiLJ`-c<=&S*3m8o+Lb0Jp$4%JZha=2={N1q2_%g1UzVg-@aUiy@rmgDB5&QIIXpfP%)({RTLl)mgsVi+l8;uu3 zSFI**H|;@PPKZ!oT#mQ&V)3B)7%}qa9qyXfd4zg(_rz9D1z4FG05TtFd62Yj&yC}73uU2F4?%y(Hm)Vq}Ti z>c)()xNUcNk@u*`_o>KxRb*6ed3!Lz0-5BA%RG;5xn{%Smi?g0G!IfpD*0(Coi-CZ zeBQL5QH2^zwd@7%u<#O|P!5to9esqPQ0p=GdF7^+oRVbpT1O$sk&w~0pY;c?TRan; zPXDSepJI8S(>I;nx1)^J(X^6NGR)}J?lj0~8UCdok{5aF)F6LRpnY2zg?_#DC zP~pdXSIdZqbX}%>H)(gqjtbD5>74a;SoecuI|F+TRX*3s&TZLO3hlJ1aeK3AUnNO1 z#8k^(D5=e6nHFx)!WCNhTE{uAg|E}X2~E9TQw>dR)KpVb7ip@csW)h<&D0wEMoIBB z?iww=Sc@l_@@+hCY!!O6aWcf)xEou;;x+b7B7o;&*LoSTarMnC1>BT_Yg)-ENk*@I zR|wLwZxNxm@`)mwc9R;po~hUzB5y4wEKrO*aXVdK78Ya5=0t8#k#AFx8=3Nc%^OpN z9`eW#m*>XRuz0P#L0*_6smk0Gmzh_WLJyf_h|6?!85Xa#(;`6UNtaTJ&hujR zxtSDz+~OdcR&q*`5%+!GJShZe+qa8g%#*fKYSV5}OE2%}&~0oI4k$*JxUH@}!{W9z zUgUNad6|l|Dzc|}lCVG~dEzo%orcBh?K?z9bd++JO5UoHcZ*~nOL_Gw43JBXxLjAS zVexvqO(aC;8TW{!RNcOlRDiwK!8Wbrlq93qx(z`l?Yl&{ob$P!f}8eoHTVXmyuEpC zRp_A@8RGW3x($mb?Ylc7Z&Z=*QIR(>)l=OH17wmTF4NU*SlqC$kzMOWx}SZKBw9?g z?8QuAu{X&i_efrVKI#n4rDNYutwrBYt(BLLlvh>fm;HxT2j-VYuiJy+ZBU`){&IrN z-CyX}dMV4k{+TjAFQ>@LeGg2o>z}v3Fra&Hr$9c|=9tUp(gvFAUJ7+TD^qv5H}K$e zZ&-ZZ12e=a_1acR>}F!Zz7q*^Eq}M{cIUxcGLmh3g_c&#Z7dGcllDqijyc1fq)6wq z>@Fwa9+i+t;xV?An)_K8rgm%DJ}vv8rXEz74>{ER8OetIFiXut%J>mwd@P-6*=rrl z<64M=+K4A}AS;E3iYzPYw+ereDKnf(Oxhb*^41cz9Ly+-TbhF?l_WmYBCLy8hW4V* zg}L95ex`U0BC{s+Xf9-6a}l0l#0vKt@OXl7@#3hQ=UUR`+48y9ke09K)&l{P_LpdY zJ}6zH4)~;)v`kEjY+^r~#?Vpu5j{|^~{WJkkXA^{I*v|-FBZ0?$HEo~nKaw8yks)Cp`&HN%6u0b6 zPFz#PeOAP^L|o+0b2H_+XWc4~90_^nsXXFAov2v$5YbP{S-gzzyWTA7_=iqypvD%6 ze*f^q)BHDf%7qmk%kLlNB6QXh{_Z#KX&wz2Evt&->T5cG%Ki#HA%TZibJsGjVL!(_ zvyO?T{erm4T+CG4{+6UL+p(pKHGNA%D8vU5;x@ip&IMlcLG5FZW?0Vx&^ZogopFqZM{ zrlIIVz1>iJObispGQQn(G~i5HZv)&(C!l`2ooSsHRe<;2V}bX)-QCW#c1O~7+3ig0 z?BPt=j%=K1r6anYX+19GzbBrg|GFdf=D*p)nX(<(@E;uk{|)dNr=F?qOFs-+68Vo^ zj*r1+@WseZ44!<3@6!hgr$@NHQ|}GU`|;!zM>kgDJHAQN@Eu?JsQY((Pf7T<^Bvz- z;9>R2Na2)*_ergQHHFq&zxQviXuiI>eaH9c!AqQ@3f01SEBcG;t;INJgn6s_Wgs2T zO9Ouo_4}A^2Ojr68Yw*GZ13~I=o^JY1K#JoBl@_0N;J+L-FS&JTWK>f_n^-h`o3fy&&xP`-;0I|!<#7|)-F^oHhb$)AVJK2~C! zOsa!<2z}Ij9+G4`=HFsrpy2DhH~;4M(`P5*H@=3vcE%XaUqkke=;JuQpFVo<5`9Km zE%ej$S@(WA&FbU&n9swQ=-ub}>-MLo;yk`?zkbG8&tJD69ICf1f8BocVJ6n?+3I24 zP9HM%kJST~ed34oK2NsueCrRRFpQUjtB3qMbDj~k2QU4`8QZ|ZReql->#m#bNZyWx z%Z^T*Dci9D&&TKpJRj@t?9)}#>-Tda{|!~V2loaZRJ%tCi=%zI{=#a%Cs6f!f4w02 zHPS#~@l(AIk4~)hHf|42YSSq=ZSXAh*R;8n>6U$trp@h4!Y4d~*X>M?oVW~TT3lmnP#A?BogzJl;@|9!-nv@h!9>}SrDT^r>fU>+7} zJhOo_2yFcL&%Pu^!804?5ur+~ug?!?u%GcLsn*+Y2K*>h1@;xc%(<{o%@%i z=rB)tLZz%rl$k1Ddx!1`J6@HbfgMAnnzHL-`DRo&6MPrM%EkUAjwW`0!iInb_OHm& z;k5ys8BfcbktQbZbpa0KO+@l~pj&TWpFl7Ta%bm?A7hvs6P!|C%`Dlubg_E9D20c5 zZ8^X6MAx)$5e&1=>EX})^88);P6IuECoE_#NyI~10ODnxc*FE~R7{jPT!WT)D3d+&A#2kanAh8zxS|da@$~Y1j`C#N1Ca5{b6`AWL|A(}OH* z+K;KYB5#`rQyOfZdkW;HsSka0EQ zuPL>gpkFg>>U)>{Po^qgr zBnvx8j78)-0e@dgzM2D#ku>d{BFEf9F=6T(T6UY3#ZwLYO_rJ4S(Hdj*gr&xd4vTM z_K*1cG5&7Z9|{_dzYnF8P5UTIH=D~@*swol%2+0v_Aw@=&2BCFcPYA-3AWR6A?M8b ze~`B1L+mE-#5y=_SHp$eGv~PozK!=6a?hO40!GWK(6XV?@Zns!(#JfSPPObjciqop z>Qf(U)0we`H<|FVfwCZw$P>gGt_@?XrAR_Shf7Y=4ymz^tFiG^+a9mRR?GpGhUrN= z;>s}=)ObBpR?dkXnqj30bEfQAM1wwY5vj8B+IFGWXBlb2^rSs0!Z8ynE1h#nho+@8 zVNSz7jWp(B7B}s4Mh;JxamPvoo{oVm#L1>W2&(pXLaP#JPaqQkC zT`Rb`b3pTwR}rUalgjuxeBS&y4x||8s6l(xAdiu^4iFC5LzWDC;>9!B6VP~jvUSO` zCk{ki+O{S3@a7p>6sDW@*=p}?z1u4su$L?u_Qs26vNxdd_GasnXKx&cx-{&0w8z{< za}tS`eT90$6p#0@s{2NHW2W-JSTa$78#5Pn#>@l>-guf^N4h|q@5>I0EaFB^{TcTGO%$U3{GEK7j}P{Fo^E@L2_TJuH@c8p6USg*lAfdKo`Nz!8FB>i z#{LodCP<)fbpKzM7`P-E0~bTUlZb&XC_Tl%EMT;)6IwAe9P5wV_OdzYt1VB?S6xtg z^3^P0w5=10&omzMRkD?Yb#{=x+LDZ`%b#3abwLSrWUQz9@>#%WTPL()Xn5EUUnagm z*Jn`}>xVVJEZM@-tshPRX+OMG{cd)9xx~ophm`@!kR!VvPLM?XaLc~a$+|W!i~Hi~ zK4166l>xHI5y;}+IQskq31sQ!_>q(ae+A>_c3)q&KYF?}=55$ov@SmGABuHYhnh@vbsE0)sY>)3ZXT9_NA?IvAOWhB1 z;xiQO*Sfv)-3`Uhb-TUuJ$pEl*2~5o1D$}t3)@_X~AEo48VnP|93^}snFF_KEzh7{&hWuUB-TGx^fGl!k%Sw>UlB^q?U88Zk^45OJ z0K3Q$*yZ+S6C|+9uQQLFtn?W02_J`xpmr<{HNR|_9mNoz$EL?XeIALiJ~u%Ui^C%+>od--M%=EvI8+AMMUKEO7l#RwSR5WX zS?M^u$;V+6YR6(j^Rb4H+n*PQ3827!7l#RwLn^CfKkMqN71uW}4wV7*MUFsU7l#Rw zSR5WXSv`#XUsS`R^Kh5KoP!_xyP@QBvJfH8`e27+ZE=-x2tSm?FJU+#JUmJ_5~Hm z7k}s+ob5P02iNf=2}Z@*_De3t%pO|DIaT+$yyJmVWKsizxqOl&n#;HCm%Y4elk&J0 zOzW*{LB|7mWJ=28S`dCpl0cp=2Gt&}F`PetC-Ud@fQtFk6;urKV}8i} zh9nN-C2gOsjT{f`BU92ow>Cl4=He?(pU$^DZ-k6^FXG#Ow=);rk#sH6?cSr= z!pQ-2$*!TW|$< zI_7oj-nS&dv@LwyS!r%f;^egj#{;Fvl-U-NB(b*O?c?)M^a?2dAeMyqYzLvd9<(WHFoUDJltqqk2;>e`p zgl{3@*3E?yBvHHs;(A&0CV&n`7If)yCgodVy59u36Sl;o zjJ9<`@y!+nRhQ>}T{Qgf<*T;+J>To!P2cr0Uv?bmgvpZG=O;;`K0lP!+x$EUc5w3U z_SW4)>pJsiHxG5iFooqYJ)bP!x4pc3JMtj5khdOiJWzs6N&DP-AV~r}bv@9wM^;|P ze{Rn8`)V#!zfb+=*K@7_4^@(W$XkCT!IFMBGVNL z($ly#3m9$dgjNg26A(XhvHs|(7{i?3StI~qD>zXWJ6^Lgb!GbM|LXSjAqki^yw z31Np-R`<2TUy9+r$&BN}q_HOB=Xts3*>e$m9CcYc%mPNss?f5b(R{Zl_uPN3hX1|% zREq9g&iJn9UooD1I5m>t6s;Rhi0)i26_z7Ld1kcaUs2CR=)_a*cc5nhqiIEG$Oao2k>5Okd-pDDZUxNPYN(v+I_)U7V|>>r1yB zl>v5#g%$%ctit+~=0@^=4weZvz6-FTveO+JS)n2x1cZXJ@Z>Nh2Pi_DAlereY zeFf?@bZIq*XZ1*Wr1Q9AAkpS`HH3k6%m9`1!}i z_lESo<8-5nT;6%!BkhJaS+m{H&91Fj9#`-FLwpD>Tcth_!@InW1^CA$wyki`2y-?wx9h_USi5e}#!K!Hw|k1dy9mh5P} z;o33XFYEJ&1d!9I+xypJYI&?i7eR~IWVq+p=#JD|Y|I`G-Ybl}!FLTA49EP}Y3pHR zg!P%_KmRO}dry28xxjfC;l2d^jXz8EMR=CVq8^{6dM*dZu%8W?lXhVgJ7$@vkA6g- z1+yQS@%gQ1gZ^ZT>(99qeHM^(p5JB&J?t{kANXggZa;C68V&P=yPtn?Ja7V;l6t%S%Opu`|MJMn>(rmyzdR|bzX`2B zzklfp@HSx54|)5SNih1MZBO?8Y$S2=_JbV{{7I%veku zX-^fc&01<3rdDd%rCJtGwd`uO)@)^APje;ZKvj}Ot5W#5zIE%x1WC?ZDWvmrrG!#` zZJsw*N&r!(mc=bw@>NMo!#qab8DM3AUE~Pta%X@OB(Te$ML%+~q`ywQ$9tyO3AJ}Y zZRq6RXyW{nhH)mG{7*38rUl)f7 zk|++3q%71|mp$3D#BRR;+XGu-({;b5&E31sMeu9EE_>IrfYGulv}|Z}*S+gp4gVYY zsb$-)rQ9KI3;SLzMYCXWEanqje$DFINj6WcuW@VJ^L$@(mty<=TJy_>*_a`$R|P9( z^aP|^_9977n--Hz`({a+DW;@-uFeeOA)E+~r8+I-Iz{ovEP=m&E?IN%|qqQL-@D4+1#s?-%-B(O!@n0^6uFbdc-{$ zI2@3N_fEQdHp61H8!hM9hI=c&OL1U-$gwvyrz9DSQ?CQZ(DdDyI);WI4SO$P&BJQa zAF4@@FcsnD*(3~5iX0g>g~g#w-*YxS8rdW{CBuwfZAydOr)E5^Al@%|b_fUTAWMcF zVR2~3A3Hmqi0qJ@l3_-#cBDbRuXY^h-45Y^9c0O{BP_<^&dII8STD>?YmJu-T= zBLvxE?-x6QR#YWq!#=<=Q`5wsNMeYI_4ZE_TxM8v_et(_dAc$^RGv1Y%w22$tV3%S zY1W^22+b1B`imIVGR=B0Mzw-jxbIG~aNix1ZTr`nte6(l(#|S{7vS#;rJc=PRq4O> zlGPP>du4zB=ymeUs=R#Os>*<753cMV(Ck6Q;(AQc+``LY@)_-mq^*}zlyYH{tlBy} z@%Oefw&DL)^>*VDcDdF<|ML66rfUBQC6) zxeik<9aNm^17-IVspC_96!=57IPZ_A6&9}X_qtaL&+j5jpDgtzLF?_5!oukLlRw}1 z@jb-!xTVj4r3T@7UxoBE>k#w9!YifCJn8Ck^Rp8Q3s-mQ>+}xp!BmaI!X4qK9^F{< z`50{E{2FtMEcdqU|METG2JH!xX=y`e0fXmCGM>R&94X)ihkx{H>{wR+)9v1fZYaLJ zb-OoW_HZU6PoTq~6UNurGo5dy%`RrNZNp`l-ArR#?*6`$W5H-o9=3?KJBczJE6A%Aw52K`%Y-DXq|R)O5OKOS>K&Mdhin8)d?@r?8^^1-NxMWK9^_+9y-PkM= z(V63cdsetcMFZxZNIjQVku1L+foD2^bz}^{BL1SCj1-!7UsSgeVqR-%-#{bpY!|vqX#e1_ovlD-%p>>=lS0Qd|}Gx z@$WJ|IHTY5zsq=VsNVPa-(@`dFcaSc%vKNIWuy;T51r={<#g9FCDq`%l!|$L2EPv% zvYtzneK*hrMg5%pTw*pc+Ex|E{pvJ4@t)Q(Ulj^POtt(w;d#DF?W)x5%d~m>vFeF~ zimT|(@-8TOaW$J5ZEt?X(C|cD9rINfSFVzsuiEzIZe%bA=({jIXv677eT&e)-^GDxz-iH~_l8RO>=%Zuudu|l2R*r~rYQT_Q$)E_hB^~ZS0&`Do# z@qF-s2a7**&3fi>v6Js-N0X2jU&-sdrrR@*ZYX{~pxZN#*~6K%UN)RgC&1~)e22H+ z#2N93NY`cBcWwJ77guI2eHo^w?3<%jjv3DsX|nPr?OP%`vyN2h9M)53U70bbeYZmR z2)_|9S@vYFE2Fu47nGjn|FeM6woWMSZq%Cigt}weELsSE;>v1GnxAR=Wf&Pgs?4#TEH;FM_nl{^2#Mhm~TLqNoDtQ)RJ^H=U z@*dBw@aWi}6(F8nF@$o*P>8n(c&{N|4d+%esc4JRU64!HQub0=L})*)`d*l+uXNMt zvo^W<>hnf7m;3{D4jlMkx48(CagX;D$V=IvpmfjT~P z`z2|TN&9ad)AxvBzK-4g#lJ^&e62H~ZCzf)Fe_fid2yEp+Gzi`f@|DiN6lPMz?t$p z_P8x6!UfceMqcbmnXr$UJ=^E`V4nvI?aLsU zv_A7N$osF3eYZ#U`Ao=TX3zF{KG^5MLi;jECaupr4Dt?i?7Jhf&u2m&Gkdnr^T9q3 z7TT9VGHHG0VUYLJj(vAV_W4Z6V`k6xc|O?Z!9x2oNG7e%JPh&*6+K%HmF|k{^O=yx z%%1J@d@-=lz6_E{>oX67yg42F?vCv9nUKfKp6&B|u+M{q_GOSvTAz6s!9EWb+Lu8xX?^A)kk_zP zT4eT7?}iY0T>clMGOb95PWP4|V4A zcjz1xPugh;z-}$xWW1D8JU!>`CBpjg-dI_pnG0`^b>6k8@M#8x*(fyPUY|RouHp+>IhGsw12u&ye>q@h2G| zj~oek=c&A)xMkn#`o`|b`>EuvA((X zgFmiRxH}zOLo$kn=}!G|uATO`K7tGYTJ~xu@NO08Nv&Xaf+)CHxdXF;=o(Y8)##n5n^1sjleO65+{YU)7dP@jL3 zaF2Qo?=lYgm*f1sqz}`BIrow-Yx}!H2jn@9%5sM_e+CKaxpxNTBKUgga%a#iV6?0XEgKqr%ufg0g}++oo%%XRKV6rIrzf67Jmn(v7*Df+ z(XuMEY-n`Xc*@nVr*@Z%@N?s*mi?HHv8H%3+!iF;_Ifu0n<|UL^rZc`E5{P1Mv8P! z(|%fL%mmAmsVV#Uh#%Y08%U9rH)+2>y0@0zT7Ij!m{cvz!Tem34SOpI%qEsa^X0tx zE9o(RB}3NyH7t(it8PsdlxNG&TT=x9_-rx_&^6Viw4z~uT};{{CPg;ot*OWWo5+!{ z$*rk^;z|1(6rgLWt(4;D$Zkz_o5Ef0;2M@0MZrHr9Vh)bV5e z9-zjS06q0Nxd=V>IkSM#vMLn!1Jd~|`wdqyw`C+T7wvZb@o{DMO9FGll7uUxx#-xq ze^0tv;I~)x{tf@aRyP#y&u(W&XAfu6dfDiH=>%;4bbHUUzbZ{omQ+L4_@YM#(K;2FMd-}+S+^e*I)-i6Nw%Y_pV zr|i#gL_0;|%eiNQ^7$ea5XDmzqUEy6lEXe)_29lq`mR{rH%Wm=`|IZ!K5Z}Q^|?JU z7DK)}8-@=>@Td2Fnt^B13D4jjX*xyPWd1!TV=;fS zO?sa$n!%^1G`t_O?j`9Jt>f4HdrpoXyu>{xp<1}-g#P0Gcegt?76$$v>hIjSv1|sP z=ij+;Fo+$$=ij+;^kF9M+{jW7cW%&sSSKuYE;i#?X{n?9HgR%LN4`xoW~iV1Hqk3u zr*87w#Lm9MTdDzi|mzam8)xtcCKI5LxF`vsCXTZmK+F3r*!zc@5qIaL?-*0tFD$YA&Z7Ya$ z)uG${RtJOV+fKLpt&Tp-#8{i99>!Ywub25w7Q2swV|5UIYp)Y4J=a0QE@haTHY=oQ zpCU1D)-ctu%cN+Mi8c0A*BtOBPG&~iKGji}DH31}*Ztii$AXn0dAOKN!8~c4*LO*pQdI) zfw+{5lI}@A*RQEpIs6f8X^L*%dpb(j~&JPVv%Z{r)W)sy3lHKlUTj^{` z-qUs|km9=ozF+S4ytp?sKX&CmFP?p@#Pi~$I(S}8A8`(oe-B=g&G+KR{EH@A=*_?R zbHr7N_>J$6ZkaKL^Y6jy9nr^e{ylg{4_;!9m{tq(a{8?Mygbe7{y{O-Z6Gsb%U-Gc{*>TS!vd+_MPOx!(~tsd?kqz@VU$LfK7GyM=fQyBE?(`@JY z)*nV;7%vA=4>)}70^aOmy@cteZA-bigQdw-%busa<}RsH%niFg-%%D!&&ZSgd?!T` zJl{!`hl+ST@}BQxfUI@aJWOj^Hb|cDN+0drR%L;$zR2LK-b31q7Qm50;(B}5@-bf_4i4xc;;_y_ zUp4Lfw0+#0Y#--lwhw*3NI1|w$P#ZK=V|*0i$f7n`#7XB-mhldr)ESlcVme9__;-==5gg)Nq9YPsVzstE2bocDoT-*QSyE-w$VfKrDN5U7p&z^R`e31lbIA~|f7j24#Yq9N`N1KS(Xvl*Ca$3s zojHq(hf`S!mDf6z@lkj+ni=uB%8Q2(1UvaEv|`o~CQLW&X)0txN2faM&$C-NpeR`~ zd>a;rBBJ(t2xYYG)4Z7%iy^)pINz>hDOA4Hsob=ZQ<98ceH(&!8E($G+R4}&$&j3q zVMbg=-kdW8nY5=n!>}CKPM~y7)1E0bW|s=;rF{qo972|iIt+_L5k1vm2;%+a+F)4R zv}dUqdjfyujU~bYJIE8a!;M#A@sW}7bhYF9z>d7JL|9-4dE$1s@hU8C+QTnn(mtaT zdN;;w;WwIHyE=yyP3-IIJ7mRY-8%Q`c5iVv6#smz+u8Hk!RHu@hr0S-UrJKkFyMnt+UpN#L; z2XQz0)F%^n-B8lHvQE?QPRt(8r1i29cXR^gneU}k()-oAzm@<} zjB5oOFIEFO!r+`bjiGyA$^d1^K@8~W_2rxl`X)%A@7h<94&Ox4FCR~vB_}`_uBp+q zS;izW*mvaVuMCh)4%L5y!&sq^uXV|BCPlAq@75>>Fv;TViH}VgceU5%us-}=8oHK; zYgzs4a3S{_@Lf?l@w#BCkJnEB8ttdeBF?0B!ubJlIsujXWp$3Zb{=a$V}o^YK4K%i zH?Rjr)|3a|As41tO!57umy$1oB;HqNe0%ICqK4~CDrWCcvWC2Gv-|4k8*5!q^7`u8 z#AsVp9N%f9;feju3}3xf8cpP@4QL4QS!$2I>VndfuVw+GZB;0IMZ>W&{yXhM7}M+c ziFU?tweU8?;$?`%;_`W`Dg)8IR|BKho!nn2)=JAq%B!mL%UUqFa$tUW^g8yKgXQHT z6_ix8WG+f7S~6E8@$Ucr2fVP+znL3-1E9+LtqSj;%vx!%oKKFqOJDST-;6%%VKvHC zvJBblVO(}SFN(hJ+gDf#9TNNZJs|nIUbM{jTSphplyyePo2`b9KpW{RY=Ny?pG+O6 z96nbhux`u4#rl6?svmcGQ8(XRP5busyuwKrb^P__C#{Q4h7Lvpt*>(mC%yWZe7GI$ zy|3_M{M{z85Pr|Ywf9apFu1ZG-GDUqPPKPJE2?H`qu7rqywkPmpD}FQoOrZwQuK~z zpPZYC@DDx|9Om4@NzwQ0e?Bl-XQ#lo?{dD)tBEdihxf=euhDpT(?NZUCmQ#=p!h!0 ze=jt?&YMk)wpGP39@231i`Bwe@T-gg={4JKy^xZ7tOj;+LzM~Vc(Ed_E7N`}oXTbR~_g=Zd_*%(4l3dJsKjz*mmy6Kx z;fW{v`9-;~!u2UpUpEUFEvrJyVl>a+=I&3um*ax#>CE0>RPcVv-Ji-u==dpje`*#m zuxlzqq8Kf0GdG_8L;5xEsGE|Vn)g#~Jmn&E{FEC{vw+dEDzt29G~+4PPZvlZ=KZuJ z*@5-sr(A>{{WJ?0IOD3_x1rJF6LuXU##A|Iw(b955$-lKkFcy^uVvai&IIoq<9V{Y zd(BAB-AT~PPLjW;jAtcrcW_YLwjU!rzwB4!4mRihdbc$9UP1p03O9s8!ZjqLXqfKp zUb6t8X+J@MC9{YK4SOR~W<^If?c@Gy`tD-gepBSXSI zSBF7y%l?iNcfE@Hs))Nu#Pw8%WPm(!B;>g|42q}h*C;^hu(H|gRiSr^&|5@kFYS=@ zkV%GwOjn0N@s#~K@ud#&y`@`4OxPB`p^&#Z$cAM`(JHdL6rsoBFg+kaJQ6|+mlJtpFIGZD3cJb9#tJThd-3yP=giTXTBx8sHfDF*NU4i*+8UihCK zjS=W2hjqCa!>De z2nXySONJd`acD4vL~SN_GIgl*N?@4)SjoSJ=gbck8r>qvSip3FP_PsfX3UCtxKLg zaUiwlIcm@Cz1t%ku!k%e_QZ>4vL~SN_GIglXHOi6x~#V^7JJGvbHe?!4|%)#xj#{O z59TQqJk=f?s?7DIdX~Y_R@g#@FvZ8em40b0{%phH#O7Izlc68yL_;`?_GV`V+&qO7 zYeSek>iN0Js0dSR7IALV!*^2yK>BLZz9sUNStn2t7Tf30Oo!<_8KP_^$;vTF`sszB+0}TFGL)*RpmtlF`y5AF)1fym+DdVg(8XZS>kVDm#F>@j6gLa|n5+qT*9Z6XdBMBeA$NTW6qz^S88`p9B^Uh)> zfYgWYbw1o1$H?=cGQfxA$o64^B=TW8_HS0%z-rG=#J(%2NWP0b{E>5}>6q1ZTM|qa z3))(eB-Yjrjl8z~Wi_Z`*1(>a-g#}!@xTdWO6u*}T9PDcYe!lhpN;3=8^71t&vR4$ z5y>R1il121XCxzq{CnejLGsT^y4@Q;`&jF3TnCJ`bPD3*m_OrwnJHq@^*ob5IR>yV zcY7GJi8J=flkhc36hu(e|A|dd!O^+<8giS#vWyW56Kbe>&EW{N#w&L zDJwR9bA5?9#UK0ndP3_*8MRQU=&Xj_lY-ki_gda%#Ql#yx2$p(f+pmNA+RF zTuKm;t?Mv83s28QuaoC1jtPz=mpYPj(WT&juk;C(L5D04)<{+9d*EfSamS8BvHrf` z!z=Tjys2B%U)|T6jstd*C28k&Z>JK8ouq=DT;dL@duK!DKYKg3#_i1Wt>b{5Wa-$+ zbv&PqV?CTC!5p2H$pA`9({(iC=bAVDAU!gZtz z#2r>yZM($1#AKQy-ZtGUa_c}>4m(H?TL*@We%+hThihq6pji{i(|M+wOKCC0u{_q- zC)oU)JC~i23tht^I34n(to^ldKmx%l6YUFDq}JuMDt@9D!Y~ zT_#9kZTU#bda<+Xp}1XnK2!$SMUKEOS6>N|sJ>!-C=OJyf6k_Bx%{$7hRhIDd;?#*889MVp&R^2=E}g$PCRk1`bq>e)6T#2<3+8!g zk|QZg#-aT0&)x0}=iYGq8<@LcQEW(Z{nJPx|9gMEqWS)AppgH)zoQ2)@%_0_Eqw2f z{$kuLKI53bJKy^Y9pxuHVRWVAp;v!%yojCQ`I>|?TI62WB3o9o%97LDxxEaKJGGgZ zTUgD#j8Vz}MaU8BH>s-|qf#VI`(qJfEZ?GU+JDv5E~Z+xDDMrG%{^LJ($|RYWeS-7 zOb*X`rq4WL^@#JO!UC1ZLzT$Gvx4s?PrA5mPf(E+Gc+a&ov1R0nd(udus|kxGGvCu zlXiuZiIW8DRqAOfb%RLl#ZF;>Ombw%42xU#GL>m8-_mW^cQ9#oF#*5odop?FXh{!o zWRRLB&#?0=&g}-p6ZXTPHxCJXw{^lE(ablShrtu;lzUnyu#!q=3>BV&I^uu!cLdEJ&*8_1 z_)(Uj!J)b4dXd0$&Ie0BSI*7|{vDb4=7tEnN7{z{Dv``hWK1L`?AK6Y_6j1;-i$#o z*Ty{e@(m{*=HCVanm-w3qMb2Q+j9?Rb~3<@w7g0C&B&qVc2(Y=H)0ITct*&x!4YKB zzCnG(KAFDCAjuMkm{VUJ26=+b`s4hXI^ET&zdNG(^O>kWX2$D}bLhE*vx2$Gi|03aoymd_qq;0?B1e^VW(phgXp3>_L?ZX@sv>}%|)2}xezgTai zNsgo}#H`nc{)0Z>RXd^bLr&$k&#Oq5#;rcnpbyiwYuKiNrtE$3+IyUi%*w+9U8OTm zM859+9U+&!g4s6eh z+ceN7`=`!{n}^Dqhbo(gs+-L)AxChIi|_ckXCF#deE<{*Iy!J>5V56aoVMNPYjjks zV*6#i<~;{i$_-8Ci`d3zUiUz^iXAbsESw6 zqWVktNh#s)fK>f`XmHy0&%M8wB>asPT1MZe&-WY){7s&uzqfmTE0OqHshq!$th|l( zFVw!xK5ES}+Bs8x%RZ>|W`$OY?=u;H>F0*}9A8=B4)UlwFz#rd>7GxeNJ2q}OU_37 zmuk?o4{;db{UXKNMz|HsMTbSf@*sO~v_n&e2z694JGF6#;Nnjfj1P0zbk19{uv-BD*eZzOpV&paLOO@g=rl^~~FUs+LrnRK4?{6lPXvj9#Sr4H7q)0+Zhf7Y|HoV2V)#79t_a>;f9^uEV z8ul8d%%k#Ut6u7c^r#y$WYtYj+_Jx@+L+;|h`1X>+$aEq=!5*WXTJQu{WH1huY^U-fs^g&WYd00Irk`MGWBIGyH?AR zso&AE>$NPI`l^=Qq-Dv}*R||cEsLkt*t?``b9uA5gT<+gru{wTzMI9Jahmh!bYG+M zXyHIWl7#{3$7#;F(9eX$p$N1woeLa78EfqCiy719X>*?%8f3^^OrKkEegr-LfE+O6 zL1#u&b4rrIxu@5Y^RTopbj}=tcwZb&87=!AF}7^>)6^i@wBOYleTaqLhP>x{LJvof zAzq*FRzHQsNdYlG9YPr``#opkqiSLz*|dMGCO*N!sGiQvtS7xgO&DM!IpQ{+r#6Pg zfrL&h96}ju?fqf}8Yk}fsxE^m8CgyHr)ujk2_jqb+Kw>5R&vB`b?q}O4kUDJJ%lpW z+CLX7&^~du*b=ohm6dLze@QNwGVV-iTFEI%Mz3u&1PR(`SbRuiwCxYYV9fF+Xl9%;`QpVJh+^ju%D`&co7q+~lj)}YTQzg7ni=Vk*N%h* zc9JJK&0-hST&^}}eL zTgel*mCs@@#|(>)gbb|zI^PQz%J|sOdCnDn4S3g+`X**K6u%Da_D#&$!RHW~n( zfR5>y@Ays3Fe1`*nf6`FK3C()+^w!eU!nU}9QV@C9CR(IJZL~xr_flD}E60;` z-M|9qsthog96{Z1Oo2~QB!QSH1`fTP=R0%mOZcASQraF}11bmXAxmHn$0pd5B5Ap- zvM_a?qaAuVFL3tUpRgyd?I;KAAxmHn=Z&x@MbdIvWnt>rbExG6ZD-QAoqbMKyr<;s zWll$rytb19Nw=L8$sv)0x2T-54^r7)+Kw`yZpabTjcYq8l0ZyPZ6^g1^U2I|UgXT# zpYVNNOe+WMAxmJ7Yda~DmOWB(UhM37C}B@tOe+WMAxmJ7Yda|tyashd3+RDUw4Xr)6K_oc0Kn?WOG~0}LidP&Y27QzU_yp4v_d zB<7Qu<(%)#c{Jhsytbnpu!k&xJ+AGfNLuzt$$6=>=dpx6d2L5IU=LXWdtBQ|k+kfQ zkrT9?Dc^P;cdE9mX5n+o348O}P6{LzC$8c+)% ziX;%zQ`<>_#C$TdoR>Ru4kUb^*LIWx_K+p8$F-dlNy{E7Ij?Z`6!~eK$Zy;)NY6oa zzd$))4_N|x-1*HENy{D?ISspm_Ly5~O~YOxiQAZH+E?j2WkY-iHfUq>dEYZG$2G<1 z&U?+mnPFb5ANeJ(jb(t02rkav5X8qm&kv=2t7jmT0g8|#@U1%&og#^Uvv(-Vl zL6yL6px-SS0(kt1xnCQP6c%QDvv(-^NO*T7eJ^XEurTABy+;GiRLmQA--C|8?&xIX zA+wJ;e3p^8-|iy0;sw>xO5S&>`DMwD>NhV&5o@+*(u`1flj`E*WGw__#Yy(}&Z98tg5vLc$ogjKY+|&nQ@mnmc-Ti= z>-&hu-FE|8REUSK5G{abP^B_l~cW{gpTnmE~EWF((J_e_jDO5OW*E zK%9y}uT1HZDfIky7-git4Wlw*AQJ6?Eq~!`S%#aGVzx+j$uPG^TjD_K_y-AHGAoi8 zdFMZc0~V7-{h@w$=Rf1c&^3AYzpI-$9HszqchVloE=g{W_QZiymqed7Aj2U9Jp@6h zC4bkD=Ho40TJ6WIygn@fL_sb4Q8^<#Z4T%QtRIu4De6l|{#jFATnPtsBa7C>^@>p? zM$>*=Q#GbMUS7QjJ@Ck&D*m!lag73gP65|3<=P~_4U?y%umF=hiupR8$eb>j2|Yi% zQ4VM}_55t&4EhZ$K>(Oh99>mj%_>VyZ+%G$MD$La)&4J$!zOsUJN4PNE)SoKl9Qtx z>$=~omj>c|83W~d>6=J z>j%hd_FxeGoMxbKYBZ<$`M@l}ul)7^|I0LasAlLt#_3|boBcfeZKD4inwzeJMHzL# z*Smc^tiWN`ta>=D)~KgJ9Tl|89$%6iFe>~V$X zgNJ(@L-SIX4C;faZg}Vxh{yhic&7u8dmlsdYF$-{v5GF_<@iP3OT-Kwxu6+*?M%&> z9;!{#4Dep;#3t?U#t8e#sQt6r&;B{Xew{dU!;2}>u$NM6vx)eL1U}*?_LxgqluR}3 zkA!OtKNJK%6y-Y_JD?NhJ}iO+mJMP&U;C;)_d!EXcSR=MOB`JSd%`{fG_y%RNc2-x z=uW2Ewjh%foNc*_>6V?NX>&J|y^P<=09DDMgHh38+@p{sm%Nur*x5PDKhru^Ieh>z$Z^$L4sNUzY5iVk7~ic3tHRxy!GW*pKDep$t%l9K-;<*VnCE z6C}_#TDN*Sw^wvPkzH9e)D1{!?PX<;$`|<_wjax&Ks_q;A+RO5)OJeVv#qu@H)} zGbAO8S?DYPW zPP0i4P|{0pMIPRP#M?=P3+n>Vi$`%8oNhw`!IJ~R;d+_K!x(3D(kt}##T<~sKE;^(=mD*e}9vbq9o zR`&OgUMJJ}_~*n2SN0ERb~d_vo0Xt>-wqKCd6JBb$ zm>ToMN~@d`;4IodSs zIwpQ#syZx5N)wyx7WPuf_H(&hj7Y!G$FK7~HqvXM{cK0Mvld^oCVxrtex~-5g^syR zxo`$_;Cv+G_;pjd7(cnW0X<)Aa-(~B!-W%1^K+Yw``2?3I&+&7Pd>UZT2>V=8ybzE zc!qS3H)`UUlDLTp_|A_Ve3qUbJM>vPIneczgW z`SAd~ei^-fS$q9K^!jBtq*Z1f4@8XfIwf_y&UmYct-B(>SLTUcNLf2y=(B!I#>8I5wr*kF?#H+K)g< z`yrU?u6>($f8=Ko>1t95!}jxm9%w_&54GM~?710@#@tCa<(0MUdCsGEiATL&Poh5( zAn_-m0)JlB@e%2qKb0`_XRJS(k@j%w-LTK4U(DU~N+O~6?5<`&<3GD5!|n0DYG`nJ zXzsMRN4)zp@J`z2X+>ckY3_A(RV{oNI_#F%k9ShFPA64MPFmT0xk>9Fy00oawrXcO zTXoY}+{`-ChoU|$ng^ynEt;olX#Jt5q9&t%6Yr_mzf}tj==H&<&MWhV$|GfA^_=s| zo_VAk0C@fnJt_XQqlkAj`ib|)NB28Ff5td6_eHUD5cF;P0v9jle#d{f#ySz?S~O+F zhOO+ejto8yM7v}FWR7+DJSl6Bysa18$*>B|K6-w}0=$EvRb(EN8u}UfUIC&@1#0HL zmvpYCD6>;je%y=oy))7tZoSvq7j-;o_R}{RSxx(5p)wDV!0(@M?=HQ6BJ*ga$0VK% zoW%R}+iP4uBLw0^rH~0V`Y}&;K^|RF9pUMAE=BZ@>C>=lX_0xD28mqV)8d{`dR?M> zLgWAhS!hxM!mn$dmC3Jn&;|TW`x2_~1M>)Vz?xeWa*6XL@u+C$vCqxK)_34Y58cS1 zy2SuI|C&tx>#?w`*??c>h^N3W{Lg-m$|P3hx>sZ#<*pa}uj`=L9?>6(I2<+)f({BdL{ zM(8KR*4PN$mU8)$5X)$9g=4kMBZ`T|PJAALZL!$qS9~6U$-)ft2+S~MnnvX72!GTH zOT0h6@BJYM6{0;sb3oLO>UvM)1N(!>1Eweks{9-icT5uubov~cn8Ovs=o#pG8 z>{I`F2EUlCQJp0AJ0ErSo6E&^SC7m^Ju>fSs>dw!3Hx_EU$aimWbnD}I@BoI;{4L; z>bB~fLT!7o^iGVIb9P>JdZBDB{#jnDe+Bmp*emh7xP#fI4}_~vjOA?WM~)f5Kl}Tt z^>2Z^^S#5T6rQ%W(8s`8@7M=Hx2msxL8(w({Syq-)Z;f%{t?EiN+8KOHz6-3#LPuz zt!n1sk1!8b`^*p$eXD<5HOJw{*u<7?Uxx6xRdYO&BjuG(8!p%Jx=L-{z^c>w2d-J$ zUnrW-@z#W|3mZ_yOFM<)Ljx1EUmoxAAJ zZKM48KKVJupYQMd{6OdDZuz-@1RvzjD?WsO51jbTfkLr(#fRlLR@8Hfr!3vJkVJb( zRHq`v9R%OD2)Sl4{tx=mH(4hNOG-o{< z>31-_jwO87x3qx66id1Cs$W?3d2^6AivPBq1R3>JbCCb0xtiE@HZR*L57$S^O)KR(aA1c{MK|G;uEi?G^oVYpKND6@|rKEA$`nm>jA zEWj*<=uG(v#?sL`CmW%>HfL%=dJ2PJWDsUzt+F1+ONMyNBT=U zzKNO~yJ+n|GTwl$f16nwLK*OSj zA6KDyEUKS_mNhyuxbms{F3|KNOb;%&U}SLq;QX379W;#})PD)` zfqn*3t3QeE^W+1g&xpFuNYO?08$svV^89{tCdlXaOM7Hj6`uQMv|{}I9{&FUe&A$D0lkj= z$NanT|3Bcz7m;7nY@z>qq^FU7C3+d@)bX42pFsLl{Jk@lzZm`cI^^Mm#C+`z%=zpY zqysb0fI8-x_-AnCal>=yVGLkn^$9v`PQ8WX>c592a}JDRjp@c4$NDT3!U%oFcv9h{ zXR0dPi#vY~szJue|A81-HNRN@0u0b$=~n#OUcUwZF1;DOHd^sc46{r3;=Wp=1Y#67y_`Tl#sS;&5`s-vQx@zs5iH3&CI9A;Z!R7MbUe^p=@PW!Q?5{@To> zGOWc&=U~LD<6BYH`ls=aD!hXVZ+5@0js3nZ_WL9J&AP#VKmL~gac=!39N74xRtI%y zF_ElF3Fg@40&G4=zZTVprVW?uq#t7{yPwo7ro8(}-RqC_LAgj9V9_1sTw>ADIihV+zc@BgE-pB$uej|T z1oC)sTbqBj6n8M#Ru8)%e<_1}Y$x-^FE7?7kSr~nD3wdOD07P?{kvvq5$$Pd z3IEhzi!Nq-NvZxrNE%;K+#!UI7k7M&&=X7NU{zQD7sT7vhd3`g51v>xuej~G^ax2- z4i>kq@d@P`-=xAJ?Rro+;*V2-HB0AQ1;KM7!ISkbGN38ERR57vfm~EYSeEFV3GI&i zD~J<`hvVk#_+3z8Oh2wDzv|C}5@3=E*7*M7w&zo+7vP`bHoRh6+xbHNtWVKLJD9zc z{fGRs?L|nM7vrCC%zG;ZwA8{!(U!}r2e(!S3YTreBCEE2v{o)v%-g^}I9wbZ?K3c> z)@QD!`it?8qMEgU8L6PJtkhqEpYw{PzW#wax(Dx>5V1B2IAZ`jmE`&EgIkUNTeo zcRWhHj4Ar_Ea%VB+R77EUe&xDh2|Cbr#f~)&3VhLL%Q)-&d67iUG`XB1%B*|%^xlt zIB^)N;6cu-P*@o*nAgBntBa%K$a*!h28ZWVs-u|3V+&+4-3DY)&6Qun{OY!kfy=B1 z{n+@1;*Qtg$Le`S?Z3x%GC7X*U8R88q5P-yT^c=ObnBc#;j+>-8==Hj%$hITwt%%( zX36uAEQck5a$jT>;D0AP0brF)qfJD zWYR3q7i#fH1wka>7mBu5Q-d+6i~6V*o{PU9m-;wvPJIhdF2P^p{`#5rxfd|kPq2~F z0It?=24T5=0nMK(mOvwQSlzY>IonHK2FAsoLB@qdZ>USmTbS^T7A^kMaPs&vbR6;n z88`Y2`GJg2`3(87PAOKy#Y~@YV9xj`;<{S+mpO&PFR6w&got&mTYXDs`!Hej&CE{p_`$?VC z0xul9U@#4Y+_`=IZ9eo{XntFg)biHO3 zPgq{t$z*jW=@6S+zlaW_z8Wfz`~8?3fVbcym!YZ6?H@g*_L=^%Q|5i9|EgQTFgCVr zEl6wC`b+R0$Qf0^l}1vH0kuOq^*GQI8f z{P{T{#cpJ6+eW5uku-KRUHbDu`L6}#Uyw9LWVAl=E;ld7@1=zo6$)=KuaIm)`0Re4 zJ-(t?8m${PebiT9;VZE36RfY|6?+MJ#joIBRP>dgsBgnRm^#I>%Jp|5j|0v^7^p?{ zD z8mqsXs@x1Z;4`PXw6=6b!CVX)wdNfl(EhPhKr|G73HHRdPr%2ztC}}~1rsQjdNTpA zomzhvwT;$gzP!)81q6NUF3D{+ksRY+xq!8|{8xJUs#T?zo2#JO^fKg}S20ugy`eFR z+^fUfv(E#4A4=W|pfY}zUvAJ+YNa^qC5eQ@_AAPzmrJ<4LTxM--UL~ngpHM2^#xdZ zF}?~D_)*2I1?Z1H_v!tmg|2W>R^gZ{T%dmuBelW_#XvRO(F0El&yMyt?8e{m?N;|%VKMT$}zN#U&K%;QwaV~wze_bSDsdQrz-B?I> zz|-9YywwBMQF968tz1~z!Hlu92C8G4(^p*}KZ-lpmJD9wtf3OWf!}lW&&b^2xzZ2V zH^Le9H{+jG80{^41>JWl?qCa6vox|br+zY;^gPY4V`@|#DOWihVNZ+oQy8i0pT5$L zI{~ouc&r@kCBQeA;-4|;sz&C`pU2J$`xVp3`%i!R(>`+#M2((#W?$c^?gw&Xj(|SR zT`|sH=HuMXwr%)f?gq!o!9I%s)I=Y~`~HGOe0&P=QQW}}&TK*{Ui_`frq6o3{9l#r zp7r80-EABko%>9*E)c&4#QhgaA9_a3Cs$S^W8Nrstlv<^)cXR1xo+*0+Qd*SHSetI zHcYp$MZbd`?fmj}>~u*zwhhzb+SN>AzE+3Um~pUk2F=@$IlpqX{3*jH$Qx*UYN zAfnWvWpzcRtg7H(VO5IATwl(j(UIyiZ$bPh&pH zw?e<6{;4MZajEtAi;e}JV{V^IF_(2xZIX_^YWT}q>60A(!hlM@&+ju|LTWx14%{Oh zICleTxqsk-x&3O(xz`=|=}%o1VJxkUoq0wSXeXeK1oZ5ahN^uUV8h50(PyIut91re z{Tbjp=Nee=%A`uq8gkr{nz$?`WAx06u{XDJ%4yhd-VT6~f#CrSU59dkfHRXiI4})! z&xlRA+fg>ZAI702#Y8lIMHA>8Irqdgpzvw-Raj9-&s>t8Sf)Q?I&ka)P;GP+*KnBk z;b;B5Xm?R!2NK+aLtj!gm}X%B0B))9YT#lkem>S}=Z+4poG91d4x&DlvEhLOCw&*2 z^&2jzM!$CaChBBWu{z#|871gA9yGoyt2$+Bd{zIBU8w!KK6|1JpI=+~T!=cOM^R;9 zCxrb$b?m}gb@cSFkBV<*vY{H<&^MzESH*1*gJ1*yX4#JamT(&zZ2{s9z?7vI6(*YKZG9TN*o0T2)60vbBU+WA{g!Y0LHylIF7 zCe=~JyM|NMz16Xz%%Tj2>e}Aw0@X(*Jl8x$2o}1mDG*$9rDQE~S?Eq{*T4gSsZUbs z#l->ot+pQ0&e9j`*&^h%awkGpj`+;@Joqmpvw?SOzOlH2e~m#-aR-aHT|viSS|s@&p>|udiNtMeKgGi7@E5vGj1j`M2V9tp zqmm+U^zIMElk+AX=tx1I!#$Hq0X;E^`VKJyL~e~x%Nv7LcEpYpc#Fm&fOBp6=Idc!3}c_PKRGy6hU zwmn|P4T}pqWeGm6e7LocR*Nm2E8q1slRrNqdiNoPG(X(z` zcol~{nkg-Ats*%=yJ=2F7Pek~7ZuBx`aN(;*Cjtt@Ld-0z}!73DVi5Q2g!#014+D8 z6W@`U(4NxYf~)^knXBco0BUY5w)g8nilbD@%9zqKZNke9;0L;e2c^N_*6>_x7@ z&f8uX7xh`pKw;_A^j)nf`-d@M=JgcAJ1SR050MdF^4FpB=p;^}qdJdAouHRpbgJT) z&KN9Uk5K-z-;0UDB(0B^Oqn-|Ws5=gbT4zj+m&YUvdJ}*>}4xe_ImqA34!J!y0-zdtE$d`Z|=Qw=Qk5( z24<3|B%;CVBp?ntrBj_m)Tq(MmRg*dRG`?Xkf~QDm0FS<2il?{qM}korIiW_Dk>@} zDgr81{3(b(^$*duw9=~7wzQ=czW06C+UM+j?#)dAzwddzd7imv|E#s%wbuSQ`|Pv# zK8n1~B7djI8!hr3Mc%^5*6h2AybY1&{pi_{@;#-zGf&aD0}l`^_YP;qZ~!roeV*>(Su z)T+MQcvnHM`%PIFY7g-_p4}Jh8}1#-4vrP|ChFsjCzzY}@kB$hb+oa1^#*v5^d9KaKd%ZTurRSr+7F z`}lS7b|f1{3EDYLCni$R4^j&82P!v1#^sQauH5E2aA@#)YQUnxQl1e%_<;+-aqe(sNtuCopKNaeHNijgNN0r^&Sw?6;psuXC8@cd7s^5ebS`}|HMTQf`5=K{%xPUmik zs<-})QGAq8(bj)j>=zdMAB+9F#eQkA|FGDKS@0#}&;L|LT*q5IB!X@nf8u$(@?O<3 zA4`OR=wKE&y+gC%D;|t~iQA)O4Bfs8dG{fTR9?Q{H)(y`Xl28%RaQZC<@oF#DX@sX zyPvpI*?%#iwUUwP?7vO#5ym?9sUiq{D#^O{sWaLCn8B^X4DDW_t+B}eD)I#Xf@u&)&|DodvN<6BsHGwK5{f}tG6Bt4!_^?42P${_AB7m_C3tvtDCn6 z1AlNUrNBUUr^_>+uB(hc(Z-3?>rlw&JsxLDx2ZsU*4ZnSfACO-Gf){{Z#Xk18v75Z z7T1S#I8Q?8wNOB=ztxIj&2?WToG0=Bzdi|FxZeg!ebVFMl!NY*4n|7SClw5FpF~X5 z%i+quqkP||cANCx?AX2c9w_zR?AXzJTgRUE-t5@Xdt1leU-aHM2Z&pRz4Kl2vT$q7HLx)b*8qL2!Hp;nH`EzK5XKu`U-*QWkzBJrZdD*(-TGxPR zuou&cS0TO|cbb8-dXaoR0z-XCGVY3aZ?f^2k)Dyp#Vh+;H-My{uagFvzl4%}3A2fF zzXSb;V#(F#kRD!R@NlH5FPeRmSeJB3MoTyKG*{rhlhJlOqj4H$$RlSVqID?s%AOUK z$_HpO$y$gFtiS#>D=N)T0TL?)P6f=S!KLjDDE}3N&uaLayB_RlU$_39BR#FX;2G>) zIuX;Rt!qhsm}D)TgoKIKKI9&E9URoV^lb<+iVbB7i=SC=oG*_5$~mSE#9{dt+`4!;_1+{=l!d>$<|zS9to0J(gbe^s1*< zE!`fhG#2|P9C{VXd9-rJ%Fz`V@QqgQ80*0{yLc4HCLczD4GcNsL11sxk@RBB8TTW7 z$U{HEzG_qGvi-r`GL8#49}*ZZHlYow01w-`cPVN=iMK5-u)=8Rp{7PdB;A0yj5OW1L{X%#Sgun-Qm|C`Sr)V zqP#}X(GT2)FQenZokhnN-qzuxV-ICF(m)yZq6}ZjGTg{A92~=x)5s7eaGTe%tDUK? z-}I^#mD*zS=ZsEgcd~n`wZ6n5GHmZSz7$OuzL2u}_>#|M95a6ievF^Z5ANAFyI&gV zvAuN^ZfQPe*3dfs4^yU|*W1YKIlXV??UjDaOj)*`8z^LQ7Bm0Pcsy*a?|UXLlUq6A zy!%7Q8m>2wBL!UaBwn$+u=!+5*h3CYdMhwh-OKBq=QDNMBDH;rg{-+GOR}>X6^#m@4OO)V7;lpC{^%J{V}Jw4Hz9C#bD&biXOi~(CIp>`!s5M;9W`F5k5tdt zfLy+>c^XJ@pIhEM&HX^z5W`-D!;VDm=nAA)wm8jV^K8_TT?_jt4#Q;r-M!f}{j#t) z-#mv@Lw!d~eEinqrpHHDAd7D5YyLZan{UR?#-Y&_*#e{}#`PA}3g0`SFMtiy$9@;g z!Bq0oNSs?V*`mjN|A6$YpS1aCTt5ccva;HICS>gxz)BOU)oA`3epP-*A$DV-`E*?H zcEjMCcL##WOIMjKcoRw9=FjkhByu5%m$}`l8dqWo9Vu!BotadNsLo~yw8{K=o6Px+ zGD#Q9G%Aw`d%31-5m)}XZmxXu7YTw4VN$S$3Bka>i_#-27fOEk7RX#nuBCp7!D1DX ztaUdMBPg7LCcerc11)jACb&BbI5YP}!7`SVmxuZ6%vw~2CQup1$9gviT`}exS;cKM zzYBi!YJ3*lhV+lCK0XVH1C6lp1gp{>t9sPfG3dg3cd8DV_}+yGpk}Qs6iOT3^9ww_ zPim{94q$cEp7tA4jZ?SzfCa%>}e7W)tV+Iqar3x}+LcRmBqQM@Z%Z2l=* zYEl13*~|Z#(%1kR%@gr!ZK$V$9V?Yj$WKi=#4yHDPl}i$)+UqGYIV|!#H(Sa%7bkI za_Zyybp7lT!DSdv*sy9vsEv6;R1)jOPBqoWPO!iGW&1Wi;VBL8rB?R4d1G}69;xpb z>*-gpKEL33BRva){q-m|w2@1>mN(bu2kS$ljowhNSath?7I|A?wkjvH6qwVEVYE>= zWx8*~{bo7*QMtMH(Pgbk7Ql$CksC$!3wU@JB)Vp)t zqiUBi-h2UNvA)~A;*)Bd>__yXoTBVSx}3Puril;oYeGrH*V?7m+J)Byk;?9@ z`+Nu8=R4>=-$D2J1M8lMH{BEIrh6jXba&Zzx~FRkv7U&j5}9`qQ?)a{BBrXOd%#oR z&;0&TWjDSq}F)bPUUxO+lhEUMnCbGV|_SQ2ov8`)jB3wYMj*bwJ zM1Z(&LK4NteUmqePJPpU^taxlq`&naCH<`jDd}&0NLT$0A<^IDC;FT8M1Sv}?j>YY z?}D!0Lj^6piwgRAA61>a6EY+2podj~pn{ctK+g-FW89=2qkjw9cn24>@g6Q{<6T_P z#``$>i89N5rmN^8T}7AZD!M>d(dBWB%zAT4UN6qA^CEs( zLVg9v&p=Ug9iQe*k)Huj^CEuPNqz;$&p=V7DGiAJ zrIF9_R6YaoxUATt+JmM@bgOyuFs#spc|^8)$h<-i<`LCuCG!d$m`6mbqs%MtCoie? zU@Gn+JUcYs#-F#~XSj|z-ZY8#FwRdr7gx^55BF5h;5_=-{HZC3$%<-o5$vOtp5`A? z3{JLao{m7hoR8NG2?6INZ9f%dLk{LNzKkb{=4-&Me#+9mlBdyfOZ#e`MvE=&Yk3+i z<=hI({ONx0aOc+c?n9>h>#nTr&GHm~Agt|uWt>EDwY|$lQW!?Clw2THO9d(=6&R8t zftIA+HPw)Ir2>_b3Jgh+Kuc1$n+haNDo`n@z>pLPv?S|~u|dM>Db?P6))vfQy_9n@ zoo9-;qoW|V%BdGllb0Uk#E;6bR@u)pMrf?Rl4qp-VEl@nWAL!yJV^Vn*ubRNT2 zJ{sgP+p>3KRe+jYt?nhO&9heb(iI_0HlKgRRw(vkWrtG-S>N*+&4LJ5_(*se; zJQl~Rm}_;x20&@zHwjJH(!Q^HKdU(={1kJyIGo}S;G^a35Ee3)H-}KjeQuO1UMDf$ z-zw)iiRJ!w$J`+K1k07Y_{VsE2e~|Mg5GGHFP}UeukDwghji|o`dwbH;vUb&0QPtu zil4B@bN6dey!%K{l{NcLe4w0Sn-D_DWV_p&hG3^ECWwV{niRQoKlveDn$yaJu4jxPE$1hA%Ru&;();NV=| zT$`KvMkHSvU|m8_1&eAcpVGbZzQd;A>-?p4ZE=2Z9H&5?4`*Tt*Gq8H=Ia>5x~mIt zotW=$R&aORRa?YsB#a0Ldh?8VOIX;oFoXrU>dE^?>$)z??PJHzWX}FGnO7w9!ZSyK za~`A}^?Drp*ub%mfJOBCRz;7+zUt8))6`sb^a%EFH$MpPnfX09Qfs31dT3Z3*Ts|Z zwY}yg^bdE~zY2kR^L_Yjz862}3Ve^my2{JnY)2!~H`EiZ$ZBsi8U6H6*tpB zrZ+E=1RlM4DG1p+8JPD?CJzyIPzHPt=5i?uiM-4i{f0dhi8Nm3jBSI&BaHxa#(kG_ z*V>tD?Yy;^wFErh0eHRx@O&rWgxP=-UITX4tAfr8F(fAYLkbF z+%J*ag!NKrsCu_>u!d0vb~gLFgoCs(e|vDSC|BWq0clYlf-nc-m4N(cuEYBRxw=%L zlEU$toD`AQWamz6a>8sCD&e&X6)FjzdpRG=KaSq0Dx~vb?TLxh*wwHG#Y8IZdRU99 z>cLkgNBpQHWRIJa*PEnCbTp^)?m& zy^&QXuY!<>JfN7z1Asf5^$vS)in{Cb>E!nY8Q{D{1~_k$m_(+Ga+`J)T!gFO5?lor z!1;w*<&Ra`K2J$-FK|s=Gp?y?22^wPOb}H8kufeZ22`a*7F7z7F)lI&RM%kxb$GGs z;JRBU%o%FoV}?41cYCY?kwVa*$dTPft52j5DkyT$?sj87(`tNoyG3?jyG8cE?B=X; zce_c^-ELB}+kFD2AwP}&5px#2OjT=>)yY!g6ZvT$sCf~;>?OYfgM$f zgMG$qg(&BeWdP=Ob;aB+ud)Mt)X8ff_!z6}mwZwje7b^f&`0e2mrgOFk(K zK3&1btKABqFWdN&an0{>C`7x-u|J?{-qa7+TXeZ!;=v)3gcw!#P6IJ26gmy+^9Qa^ zs0CITI3Gzqzi2L$oL*M({BQ(zAhn72An(r)z^WUADOPK9Q>+perdXxIwC>eOU*!WL zEAlWPvV!D9F4tqe(Cc1sl1p;ucx_*pFXHqO_2AVjv$8!-Pe*EsdfsNLP}l1!BSPjq zCW``k^5S~t_d-jPh|n@bo!=RmC;+>k?7w#agyXKKe9flChkLro?84sh0 zV6D4C`BWziENqVScO8#&#*}V~bI!QHVjd-%&de)tm`ADR5#|*bKG&>Oe)&p6%{8Y< ze69;~&0IIlxn?ec)1vR1f|_goIC@*oHGcwE%{6}vfqFR~bIl2X-QT$mg0hRb=G`9w zca!UH)hQJd&QS*KEM}df3>vBDQI;8=%k}5ZOXry>qBPG;=475(3Z&eeI&w!lw^IBe z!MQC-ePhba=ed+yDJi!lS=F1x&FtrxCcrcLkKqyX**L8F4188Q<8hqpT7^gOf$AtG zM*dzT>QfWH?%@P`NlmsHrw zI@?p(4NI?6I}d$+em7;^(!+F<^QogNIDjj?bW&RBrIXT1FP)V3wdAE!ui+mBe1E6q z{V5%6`U_msrh1EpHR|=|8>}fo_c~6gH(oTtJCTe&PZP?%>tzS7^jj3A@geH>EZ*mz zmCM2VxPO8q5^?e;&{}_kSS-)F5hTV^t#`_rVj zd{J}t(kn2DZw09LDb`*+NCgyZz5#Q)lNVO^Qg469LY>2V6%rQjRd~Di_bLoF1$(1` zpej%XU!+ig4}16`g)$K3MK4lNuB`tT;mZD2?F~QvE=4V5nR~TDT6jnDA(*zjBgzP` zN0jk<63R=$4`IUAxm%N>yUTwb;ujx zsIz=Nzp|y8@6OrM#EoO1`Even;{No>JFDXliu1I|d+d$G6CMoiuF(Fr(3SWLFCQwE z2PIE?-VTGD&!UVbPREweM)mZCf$GHkA(iUHF~4ZwOv~o|>NfaWt<5X8up{&{+yi&L zm2YKpbph)q@7OlD{@jIMJGpn_h4a`iamRK@LLpzjxlvuHF3c|=ilfMPX})n0dm4{; z;=LCuyKlk~y#`D>y+rBFGYuk~&{lr>BYf9{#Z>Ul^ z;(M!}wPpDfs|`7iMPu;P6D&mh?ebm#Q_rH(q-?IEd#>iY*e$f_@ zE$&$UgTUf-09iYhcOr3;dp2H#0A3Tkn3tY?FX07yHcn$U`^V_JANfgFyN|<|%^2fD zuPj-DhWi(X2Ns717l(%yE!hOGTpS)=9A33Je26FKIJL@hU5euaO;l;mYpBUyO51ZD zeio{GDPqrSElj>W=UccNxs!-otoXD-nchW8d`vG`d=#M@y{lHJ(?Pz=l>C@pxE`ir-Ne3#j|cst0C=^g7d zRtI4P`HZ-KVtt%f^z!Ec=tblH5pU2ZlJ7Fx%2&7@oodDymON`x3_rac>iO;oL-$<&O&KXpXDr;Di#J)7Ah09DX{Vz3%M*SU@0qLSysSOR=~2XfR$H23S^|fQdY3bvI3T}0+wY3EM)~O%L-U| z^&=J|1(vb`mSqJjWd$tD3RubtSeB(2cKDw$$&vH3@l<*?L)`RlNzciXF6hZlhZ4%~ z!66Vn>b!fiDxr-fZmtzHnEHJW=<(GT{a%6JPAsUme109m_46D*)iC;P9t%`EBkgcc z$1@V8;X-k^P+m^?iDA7XCVQHzpm1qDyHX#P4dI7aK`If#ZGQ6G#Dz*C6k z^4NCX3^`wb4a1H54x<-yb8FT49Rqo+r#`=9FpuHrJH&CuV6GkxUKoYgD5ZCy`S2v= z`o4wckxHpm=XQc&Qjwne+)mJLQiOu%c7pI|MA5c;^$i*KvCjR<^?zwc?>3)AM%cu; zk@fB)l`m&hJ63=OM#|XAyqS#q5vn|U)_R)D9Ia%pLuq0`a2RnSYI7NQ% z&b&;VK(N&Uw$J8mcEOI$xP^e;yl%NHc8XApW@=bIS}oKm*i5kpj$6no*=Vq_X(a-4 zf}E}A1n8K%S@t$6_|jqphlq{TcDkbxFi*RzyEixHhcMwY@)XRK)%v`4@l^@i@IqrQ zPGWmr;D#`^=!N+>ODW6}&0I-SnUY9PMF~YoL4pB?q+Gg`nlDM09}?*~y#z?n&Y}b? z>R?r)tout~>u9a;Y`?X$8oz2>`67JFb2zYh|BGl5_-M;h)e_l;F+W)O66<)a`BZqf zc_e-Y@CBnyA%YLZ7m=aS+UN>7ebjnmOwrNW)K-wt5Rm9%?rtNYK_Jn^3M(a%G0i?` ziem++%`}4g1W$82)xQ8;#_l>Y)b}W4XS*xde7a0jf6O^&_Mya^=hq zb8=&Tl*f#DqcIZp<4zX3R$^)*QzrXJ$^$yw1su`7s_d=Eo`4T#XBu zX<%+M6|O0XHdBLIGuTX#wWiy=7LlCUZ8qj$voQyojoEGHO*&_Gn~gcxY|O!CV|JSl zkD1+OV-7YObFkT%-RAYiOp|h(sbXO>F$Ln0q5LgJc`$Oj5foS;nJMq`;1l&Qo><^*LbF_Jk! znM#aguBJ@hJY$)wDN~7&%+-{s#7O39%2Z+`a}8ylR+71fGL;y~Ttk^kjAX8%OeID# z`FXo>9gKS1yD?hI?>BbF=5OOOXK&v0^zpI&jla=1(*Neej@^Kxbw~P}7s2n%zhIg0 zLw~)~6Zp^{j^iBb!I_fvjs2t@e>~2F8o?_1F&w$OcIx_YH0Ro>EAe@}*R$j|oWb9T zxwCn}k%#r8pEN!;xN)i<;Xz@H4JpjSEGV{BL);eNSUviYUN!PCMbW6HYr3)3SMhcD2*4cG}fWyE>+2wFm7Qr(NT;Yn*mXOv~zAojeai_(60y zxD454rCo+`u`$d|*bhn)X^k-H_E9T@NxYA!2$MrkYC0wIS%`7dR;~FwhIBF(LwrSu zzAf3ab@*ViXN!H*`H#oLHNU~Ohe#g7o-JZ|412a{gU4*o7T$AIb-pJ1Pn(CjZ=9m+ zz%2DOQNr)r1W%emp>Ya7am7b{fB&di6xl?q{QRXo)EX}vd118GE78V*3t^gOF)M?I)%=&b@-J{@E_vvU^$c;mCk_~qE9z&OP4GPh5e z8}=|0X}ruCf~w50U#w|jqW$7ls`EJ)`FT5W>bFG04P3@UlAXIxWNr1doyKQr zHlM+Fi-~?XtqmO73$cGCI~$ID2{NJN=)_DfI;TvFE+I5WJKfb>?n!l$+Q38tCe@*e z-EU@QTp!$pNqr~_wQ?>AwURCiHPS?=SBZ0irlr8$NSN>@kN91L#zGqx(+8J<@FhvmgU1W@l zi~&`HD%N7vDp&`gzhJdWk?ZRv7whYDvB47(O$vzwMJ|#3Y&U(iYW~3bitGXD%XRVH z^(DoA^*zUZ(B@yRN2gq@KIcX2(S^hH=n8N>Is=^@Pky3;niug)3-T*Keg?cm%ztON zK3HE)*=8HhDq*ayU#>5g0$5+JE7q6uDm}nQnPxHh7^~}-d;$iauHfU2q+iwoWHB9#YC<_fEc;jrHL*sJpBW`FJURXp+Xue+aHTDJQbL zmv+d*+BJ@VmRm*M-J45%FGC)euTk5^`O+EP#K7_yNY}V@2?2~tcQgK?YHj!7eCK0{ z7FNzntF@lp>xwLoDDR@Mzd7QC@hpl+!X<&K9?5YsPm(lDORF>^VYRZXi<~AFWkj(g z>BW-Vy*^jm3&m1|C%s5j-~GrknJ49Xm4sP05y_%nWl=^HOF9eRE(TunF@W2JZye`g ztDqsfiR?wsR@kcb;nm%*XA{8nO}w&eEyN*GmX{MqNFazLs&9RYdR9D#)s~)z_Hhe7 zfnlGwXJLPiEIxYZv)_bgq#2eoc{An4hm9d5ydY@3QG$HG2ki|B*3GjSV z+nk0Jd6Ti#a~7xCDAu*hM4jYW0xw4ZP4i4%diMP>lTpL0VOXApjlja9x~f_m_Lg9D zA!d{zCfeKCF1aQ*l8Y2A=2l-I9;9$(ZiI1H;EREr(UW3MPZH$U(M(E+wkatx$95t` zc(*%K3};+1nz6-TvhO|AUA=((0X5&7rvxpp$Wx@G>?5By-I&pnVop!W?JFVH%78V! zxHFx^MElBc#ucL(TMUNf_fl;Swb^|>y9Ax(#Fk+-2-dQ0sUA(@FaeT>&WB7POf@WQ z`ws%CcSIufut=>7spAbIJEJSSQ~Cg}Y)4g{wam5KVfpkZ#uj^p{wOwp=L1hklB^`w zIwc7}Nvw8iOI4k<`nN7`T5Tzox44h)O9d|OV>zc17x%HGsUydfzkd+@p8VUT-=qHM zH-B$`{UVJ4oSyHtI$r1{i^52H$)a35nZm1kh1w#W)TutEx|h^QlR}}RB^T19jd!&7+D-zg6ZNacpCphgO0c~J>UZbh z8}+-d07>5ON}Epou2gF4chMnBFUwe~4*6l$A+vAi*%&kFxfVe7^Y(+(CCSk#yBPgb9gu&w4iO>W5uRr=X%lXKIoC{8!=@rukX&c?|Qj z1n?NfBw1TM##b;TFU#aa^a)GxXGcdh|UKR%t9Iz&ZkLh%zSogy?Xe zB}qW|q@E?2u$9BZ!>fi5v7}sXye>W8;8usFRxPQbO z^oiuh`o#3Y?I1sQ2l+9*W63|l3i26o|HS&l`l~DsKp&Tvc!NHX z{8*ouUbr3P$MmuO;_V>cWy(J>z2tO|AJaRQ{3BkiOE0&0=6L^O!JJ;b7}uqXh0>xv z%ULW{EDWYBR3>W2Q7`AUjsz)7s{or^3gm}e2X z$WknD5xB@w3|ZPeOb;=OkpfFulAdP;EM)~O;|f^H3RspEu=3(279$0gvI3T61uSI+ zEXxX5$_iMP6|nN+CKe+FR$i<{R=`qLz%s6YrL2HuS&DJdcfMQyY=*QBTuFyu1Z_#Y z|BqCTT2ZOoiu-?Ur++JJfFXTJYw!r3MrbQQ`H`(UmvlZKdUJ+MS6hb}k~j$00?>H| zw5~E_DTNC_MKcG%l}aovW!1V%B?S%x3Q4&%iJxzimgkE>igacbkf;HqYr}Mt$m9oK zPx=zZSHX4gT}|Iw>-=xXZA@l8+1apOWL#Vi8Q|nza~u3zTPyN0^GyS6M{9X^ zco-{%HT8yrfo1dG1;q5j;YFrP}jM_4IbBc~oaIZrPzV^#8-5BW&u` zyIEl!fC=Ks#K$4|;?=ma9&yb)E1C?~dQXM4R$ifeoxHTclJ{F6W>_Cb+k!Rk-_2uO z8~+cA`P%q@R>arF-=#=c$JzYXz_z@(`7?y`b@J}FuueYGxwmp5bpATakE&E2zY^PS z!`{s4!KGJYKx`Xt?DWbf4lZ4oC)xI4B>h#M)FUZKdVii&@AFG{H|*nf@<8#^TLk;ChzGId&lX7|re)Nu!rq2pGj)dD z^oVUkU~@@pd9#B4%<5oI<>BxNdDX;hZv!!-_UHQ=YGmTRh7JOiXLXwU8YmAt6nOA% zwfR=IW8UwO!ozlJmcm;KQh2~_-BQ@`BZY_Rsv+CHh8IBIH(1|7SBrr4v{;-^1e0ym zv0>h`zC#F>jwS!{p=AiDS^!~Xztx>?SSFxXE!++kc3>H$hc@hD5XwWxiYkGrEdN!5 z8Idh!q}-?{MmQyRpxf2ZcN@P6A0EX0%62U19^pmpI~GEcZVJ3Bsp~)B4J%S#`w2RW zc`!GxsE`xM5B)|K)skA_P3%(O{fm1P_}xL@qp%gV8=DS#)uPxIFGv2jSYK;R9oF=9 z5O9xzK55Db_bB8M+oM3IV0VEo*1n1o#{LAl9ND_qXdua^!N}IdilxYw`pqw*GpO1p zZ0|wP_9cu`W$jDg(F?XOfg#_Qz+kiAIttqLm#~ZXD#*rZ%iPnYP}@C<*tQQrS` zY96&c2YJ+5RQp~75G;Q-<IQj~N^3bGGkzma+ZF zW5)KAgpCwA+xHT-zfaiyC1Ineob3k*+dn33KlK<5$!(*0wPvu5sI_LWjlycpV4JbI zZN?UCGqzxxu?5?V&22NbV4JZ8+l(#PW^8Vou?5?VE!bvk!8WR%+eU>7+lW)xM)txs zV{_Y#E!bvk!8T(Hwi%n-W^BPWV+*z!Td>X8+%{tiwi#Qn&Der%EQs7Ts!-TQ$%SoX zC~Pw}x6RmsZN?UCGqzxxvAJ!=7Hl)NV4JZ8+lYccVBP<>(Qdo1`@bPx_y11J@8;|N^(4**=jJ6y_I3aH<9SHr+IJ=LF)}1_?Yk2B z0O^TZ_s^TM&W#F*Tu-k=KA?s~%2Z+`Q|tbf7|GPSe?22ddTQOj5+j*f_piiArq=x{ zF_Niu|9TdVWNO{N5+j*f_piiArq=x{F_Iar`^OnZ>ZdCQSo8mv@VVyO|6g45PmE;E zzd%{qTqHk;HUBF4ZfpJ}0&D(tw@KFf^9B{I^;cB#qILd%1*i>Y@Vx-n`Rkgl^Iy<9 z|LwNUUs$%zUvMrhU*lg$vo-#t*BXBb?zG1LUBw#zmmFY?zrf=){!EYtTJn!I{_<6q zHU4Da8h^X4_Ujs5ZD7SFV{80HgEjtkby}?PXWVGn6l{&ZXt2iLu1kSHU1>gU>{-9X|Rtli8bg(m>f3! za0EtvD>wn9LkF?OpWe~uK=clY_InKPkZ7UD@D7PKc&y_Ze@ePV1cC{Yn+@8t>TFJOV|xk3#UE zNV|9OW`sYKG*@lZH=R<;UWIH-fLz0QjCY~_o@GfppD?&oAIj63mm+J^;L-_E(qCKB zTcadwnK#Ak$-V3Dd0ee=>tx@0bX%2EhSm-D&aa&4pO{~_?&MRkS*PCD zkI)MzP9E#QeyX{Vo;k&b8hxkSI@!Da^^ke&qx+8PZ^*Ss>sz<-g+m_eJEd<^ZG7X3 z38t^Ve^sS2$Lxud@r@SYojh@DV|=K0Vs5Z^-QhTrW&O(Fmqzcpfm2T*HF)_fUd0aZ zN~Hn2@Zkb_zWHbvjOmFxwky9@IScvUk=?5R0hoSx(X0vOp%MmbwY0P-i0j>8i&wTS_*#2GEj#k60mkb!mOR!k#as8?Ujro@h z^oB_9JaX{Ud@n={^-Ua8oj?nK4Ep)uxay3yGg#qmNecX2vrzL2qK+N20;@@!PNspP zd8HI$9ULj=;e5{bF?YJ!d_S(cnj7esSv_ORmdBxHH`dh>cew+ z>Xo&DM(3k^kW@V>)ifp~R82mFOtlMqE~fH*$kiIK4JBcnt!0F@GK}J^NnE3tr5h&+I$78etbgI^u_s zMMgyWD5#)6V=1EI%-2fgoLoL*oDjoxGadPECJ3hr7IQ|<3@qlHoCsJnR##H%Cul^@ zLH%+D8f5g4Ox?^hTSN2PZUc5GQ35DXdmGG1WrzD`KiqnxA?){CNZFz1KFJAE7N+6om~8kwnBIiHJoK5tGDw zt11rrU8qVE`^AKZ%D{dZBZK|={<1}#q5KRl-&~&uEAncz;T@Adevs_y6SkFoQ*AyK z9n(W_5c|+t1&55v=MPdh+tcC2{=f_AF5DtnuA#zM0bviFiq{GV-}X`QS^?puMN*GD zx09r{cpFdZjJNTm=6D-V<#%l3iFj)#iF9ixiEwKtsq7QIesQY=y#NJxu?~uf^nz<| zJron^3|HT}C?>K7yZ+XXgzuyW{ZYT==m-Hx1c>`6BvE|aM|s2O)JN?{f9pj``ddFz z(%*WLlK$40bk*Mw68%knqQ6N`^!I-1UP4CoF6in#RnXGAs-U0uRoE2-nGtu;!>T|~ z!Ad`%=LOF;*VdMOZzE-T8wwgBs!HJHj;M;?OB_*kT^d^z`?~V}3UXal7wf9JR9Dr7 zx~eXdV=V47ydBAK#(>)cX;DQM8RH^j!0iNC zRJBFMxX2jD?WeA}O?0s4?TJG=Xk6y1GDitaun#eRKQ#jG6vRKciA zsE2rTv3hC63-c_#k^4X0xz?|CuAtJX%6N zUhULd^2t!}=?Xqx^ZPveOzzK4#)f!MwE+;jN}ANu3$CHZQqPtfdt`v6fT##99J20^gN%@;<(|dK$*}R{O&E-s+S#33ETkIk_G| z2iHh@fT>d$hA}@XFXb~HMr6#7%1`->hrt>1qw-Wf(qoi#hVO107-6l=xFRe5dNdm4mV z-L;C63Z!bOK&7MtLlX6?nLtZYPn!y)U8z8&qyj@yB+!!71*ZZ@lL}NyDljBP0xik< zX6z}mx=OWe+o|@H?Lt%~mP{&f>boGf3aT?CG4+i$@}8Kf^L&!ZuvXd6b4O^fzmn&s z{b2lxo^9{|;@!>8bX2ml7-X>Ib{1m>`hBMl0UqM>%e~xsuad9_Sw>lc_?8SWj1nAC zUKk}fqN)bAmS`K=X>vb)$j>l(=kyA+b$jQ;5Z`woc`tRnV{#^hn&)w*^kR5$8#1_^ z|Ji+A#W z(!(S=ckbDyS#8@>DtxRBeMKJUxA%oSPH#ti+u}S{TFc{MQRILyUgnH_gCsk^oKf&sS63u{W959VyB&*;AuOJ&(dr@l;e=x z1{_F^nyNY0{TpI@JpV@TqV{i9{^j%i%Q_SDq0EI_c|nY-W6D!5D-~+uR<9;bnO2j8 z&;%rwUv3<`u&P5UO|IMuc3CHSh-F^Zp7j!p-w}7+u2;CcJFHXig8U} zF`!b`SRnGZ$QTzH11kQuv&1OC`T@>9NiJrea6*DvQnqyXkZbj4gqt_S8& zDAPQ{ITXg~`X!%$!KW+uc(slL<;?krqvQGyS6b&58rJ?=L6?3Ja{*M&IT0gd(HMxy zcY17d!QBC(0y%J{m`RGdoSr+B`Y{TYmC}h3HU9_ z+{!Og+w$M)6AXtzFaQb>_81|SpO6ymhtDKGfu=u1 zeGE8KvxW~kR(gAJN&^{Hb_*1|rG}3^RQHnI66b0)#EI{5RgsvNzG~xDV#I54t518V zvaC3^+L#>0YC0NjTLOtVJo;yf;#uJds^IVbcH=5j;^3Lw>Vw%Y|KXNT3FQVNY3P$ep#5eV~gR>iI3lU-1NjKKmI{q z^({7^!H=%I1mTTCqbss2VWVPPKUUQV^nWWms8dy%VP_0kspui#wIh)iIWJSKJOY{> zh~Da8hcig4&6^>Y&pQHWG@nk@SdvH~KDO`v4KCbXE(-f#jEeeF3@E4-#%8=Uj|VG~ z$b}?c=60)+T#2nnq^K2iW>zhtI-4aiS(eE!=cF>{JIW+oEYqk=Cj7&B;6+^d=eoJ_ z&8-Q73}I5RCN@xC?rFCY=<xnhNjqF|QRrEU$zBPBp=7mF6z;pT#7S~F1+wNk~9Jt8z&y>XGfE|+frEhvF zFUB0(-^*uBI;X%{i<*|EIC5>WEeBtU0C?1NI#sH<-0^pBe%w$%y8E+F1b2wxE5sXC zt$_Tzpw*g{MDaV-oEtk$)bgQM{e1Hio{kf1Y1+zuvwAGdyWztS^&Mk9{dpPJ=NF7* zq-SBUzg|ReQ;diP>qDarY`ZljSk?Q2MtR#}wkk8T6qwU3WwenxZMtv7y=}#IBL;s- zb7O-v8<7!ta88z1C)*gf{|;O(^LUJLBVwBi#X4s)pOjed82mTQB59d|%t=r!O|Fpzbr_y-kN0s2i;z>2-O<-){gjyPpEwvYH>q zikG4tVr=o(y!7mQH!m0~UdGmWb2!e@XzaL0BebwnTSP2(;cQDkeb$&l%-BNA8ADX8 z+$ZrY_=JCaGsRh3lPpO{;nZ!U1X7-pr${1F_zoh{2eLRP5!T}cxqbv?EZCEzr%U#v zShOceVacxM=gi}T$dbynvc|Qt!nOVAoBP3J#_F2TSX+}CD{Epq=$gxgg?yu7CtLTQ8=+IgxB7e*XsVBc{62j@Alp4a!4`Dje!uG-~ur<*K;P_&(d01>77MsF) zSHm4EKVi>>>rZ**n1SzVuSWmHXDRL((>b2BO8Y(bu`MV`7>@$iKDJ*a2}((9z)BLL zlGuXnIjUOW^V$y_gYSt_4BO9;g4nKx6vg&7q%ij2Kpc)s&jt?vu={4e=PRqgx|}mg zJHfx-b4F=2^3US#9_^edcK0x9T`qKBfAs=sgRsm+bB;|Y&gx#{u&{Ah*fz6kfvOu$quH4JxTS-3xU&*da{(hLl#i_ z8beFce9t7u;%5NwtH8@Hhf4Wbs4}Y4S*WtC(^;r8uk9>U%)H4)%XqjBNqvN$T5aj zd6_f%4U+5P=ZtNG#5>BIao^?Kc6@WfZ09#8ymo-IvzCD8I{?pj0G{szoG=@3!fU|J zdR5SQA%?^|H#yv_L`+3v&so2Q`fvm4gBM2F!$W`J#ehn}9EMmV1ThJ@DSUjKl$hlf ziGI2AiX`^y`*e0KPTrX#9XzY;$HBSP(nr5tvCw(&8S+VG+wgrfjDPih_+SmB9NtUl zql3oJci9{)%2jxCOj?wUOf*O0J+l01F2ueXxx7@ll0x!YofMVVYUfUCb;4|wE8(@u z6)FtxFHeCl_{T|nstW16So2^aHFh^Gg;{m{(&3gA6 z@N>Cs21sw50n%G%fb`~xO*xd)sg1iTF2+@HDXxkOnbf%Ak5%fvo}Wf|!RzHW<9g@J zfNHQ_0!39pWQ>c90ab00MU_KjjEjr`)p^*#GJmn_;JRDK%s1u2$JBie@AgT6!Bx00gVIfL zF3c#fm`BN`GxG`@=25D7gn0#qFX+(BbUs6_1s!P;pJ_S&@!PomEyk1Flfj8wz2vMZ zs5Kpf=ykOxV+a8)>gY$HUMAq4jD%wH7kI$Z9*o_+U}*B)5>vPXBTeD{iz3CVf&BC- z_mev)-ETp6l=fTD3(0;9^?)fiAJ5#G&aD)GKyYqLQa_k-^LZ}iR!YikNmjjPVe;c) zARlbu0UHC2!>-T6=e0BR?cir4e*lw0_2#dUgpSi!gA1oH<;8$bQ(ls!MD6Dw{Kl^m zYx#HH;%zgF>&|+ZTTW!nv!<4(eiM;%vuJW|YMF8?CFNF9>^GHv=y%#{(yA4EPYZJA z+N)K_CWx}rIu^1CI2ZX1Yf1E%>n<<722`o#N=eJyl6)f$Bi3W2l4P@YLbg+=gBL6* z+R|fvODajON+g-{ypOa!N0#K0WUJqy`L6L~SZZ^Up`Xt0fLB2M{K2?+9Hyu^{K~ge zF_Q12VkF-{#Ynz$%BEb=$MjVA{?)Lohcm@Z*BnUn+7X5qr1j<>_`DnEsrc+cel(&P zv=H=5s!+ZXV+XEuS`?+RA?oGJdokzBO%C2k{{|$Hh=mnst-nAlmS^1n5@V_6$upr5 zJ2oHN(0s%ecIYLjz3*NROpb$e?)ye!;(a-(YBBBeu`0rE1#CR!?NDrv>;v-oDtjK_ z7>j>lLRZjbe2# znYH$?7fTblIFam10N9o-3tX|-iyQP_Dv+cz2kF#P={3;S7HqXJ=EY4iSM(*4nb1jc zoE}SNdXUU;bq}qUBRa2dl=*vTR=mFPd8iKiS>H%`?du!QgQx!ybu6rJ1R_^AhIO5} z+v2sEx$72eZD#JW+$&lWDqh;s#;e4L*Wy~&Y47p&vmWwyU|m=b$?(CgheUr{t%Nh8 zsuTM@4wZ2E1TX8>@4HslL-u`2*HiXg=hx5j+WaWmZ?)M1ShabEeqV}TEP`x)3^}dW zVVy0PQ(nVn&bOU=$j&R-dHp1>T=l2`5{Rb+1&}~sB`AP|Plp5rknpjPpa2rC2?+`y zfi_8o0!W~BN>G6PNnhm;(LV0v{jvFRsJrQRYpqYvy4ELY$v%EYwRJ5sjtLozRhyr} z@A$aowU%?T*4YTIZhacLM&J4jf|I7pCb;W5a$|uhFVc8ZPxE?3XvVMOiwjuZasv|f zeFpLB9{&FNbxc)ops~KvQ>n~9y}=~>PvCzO{&_3rtK?ny-&87a)w#2ce``QxC_jJJLpiH&VW ztQtPDyXSg%ebb;_Rh>~}Ut+e6Q?>wue^|yx!7$_cE%^DZr|kX{6o2!}Oq^(bg_q`6 z@!R?ugG+N`m6tXDmcjAnEx1e`apslPCKe+{EmT*eZAwaKM8gX7*FFiV?tKoI~06)MLtsNKr)@ZPe`pd z8W6{*#QA683~lP^%W?VwjyM{(iDmHiO0VJ6D#ZDT;k1!Z?wX)S5cGaRL(p4*o$G7g zd2oH(C_2jdfXjKC%bCrTf&*k*Oq!g^t|CS2os7(6AC$%Q*1H&mPW0_s*R39O`;dsB z++{9wCi}4ST~XpgxsQPFl`4d*1K$Un&*X|=rA zlO;KcKGy`Q&qQ)Ml*PwM2Xk-8$uwT&H#fX2+9*1f#V1_O7hKM4rWBS1lTa3)Bt`2> zjLc-$O1*mPtBmfiEIuV7D0iz1oyk7!eBUhbCANPi@O{Vm+REa(K=A{o=vWq?4OBmB zljCJ^eW3WqlAJ`J8v@l&A~_w(;zrWJ+@I!T<+5m_=vWq?b2E_viLkH zTK~z&O!ft-S8x3fqx&n1n?wZVe(6GIvcGY@6;Dg~65GER_t6eA@$i9TNmREH&Ueg}5nh4#F(#Gd*#bkPHirB@@|`Ub*-y{&KJ zGSoL#A0O-4_-Gs+-*fTG{zLm)H=x_@Z{5hg|Fv*T>vIT=^i0f+^^Ek4)Gs(~>KlAs zUQu~9>gL6fC#b*Ni^=&_MidMz`eA%NRX9k6zB`g?aQn zj9!;V-^=LjdGrcIF}Gf=JPbEV>2x3RZuxUOnCQG@8S+PrF7s}*wEi&Uo}1<}y3Edg z>3HV8D$QkdnO*qO?=kn)X)dG7?5LOaGWRuUE~CrrwwEqs?s;i0qs#2amo8`SYtvjt zm)U_YeVV!Fr@4$SvoBw|g}JXwa~WM`C%$wib1z798C_-vzEl}U?(5TBMwi)fFFl;O zZ%A_)U1q1fG|k*Mrn!tRv$I}0fw>o^xr{Ee172EW?witFMwi*mE}hTZH>bIbF0<2J zx|F$ZNpl%pW-q*S4RbF_a~WM`_q+5J=Ke*R%jhzD;-x#7`_?p<(PehQOZPDMZD}r} z%j|xa4w*pi#c3|1%j|ZSHZk|@X)dG7>~fd3GWQ*6E~CrrYL`xB?mN?5Mwi)rE?vaj zOVV6Mm)S`!-NM|zOmi7sW|z5yap=o#`KvUS(Pj3bOGh*JU1=_(%j`UtPGat*X)dG7 z>_?Z*XYOC8xr{Ee16}$wbKjljGP=ypbLnp8UY6!Ey39^<>Bu$6y*$ljbeSFI(gJhe zljbtI%#M=t2(~Bfk?0$%%{$O<4K%W+vAg>wel}GPoy2V7w-MwS>dhwlR2#|Wu}LK1 zrb0P_6%ey{%6W<57*?)@2B^zJv7M@z!^7 z!RWWY^*scR?3sWmig*wWi7Y6gj*@}td_4Y`0r4hZ9ZZph}7ns`<%*`D!-zfi*37BL^Ft-?HDSIaSgkzpyn4c^# zPfReo)ip8!lMD%_*QHW6o_8E`fe5AO*6dS-3MWa0yp7;@d5*zOCoQmbNwfY+>F@Nfp{i zx4w-$u#NPIZQbfyCm!17eOoy@x6SKgDSvx*J@r=K*0b1JqUKw(8wy*_jBV-Gw~+_7 zkUqDCdU@Yg%HE#cNDTFDJ-b?G8Sdu_+$_fJ*0+%dxTH^Ty>BaJ&t{(|2KN28&N0j{ z6qx76nBCeAd4Ng!1k>9_DSI}%DPW#wn154XUJzq;tJCBGCg~GQud}7>+3bq}^FqV? zQh|9L@39%W?wFJxFpu0Ti-?==s@~hhXeI(op@Lm zy>BaL=WWExuarNVeI?j+DYf2iadmDzx3-kFHT!B|+vTxs-TF52z&6q+wsorqop_?Y zt(=|P=JlkMKb3urdbh5m%3HI)EsVH|Dezh7$GWu#vcV3rSPmToN`<_zvbaoq2cD1fE%x@H!H>Q}~`Wv5pxfKEKDmwvA- zXXkCj`?pg5*6e#!-JSz(H}k$<81rK@=3we8xnK)9a$BgE*Vj_^*6asFvFCt04fpl} z_pTJTTb(5raLJM2dYvt0&tyL&3d-_l26{&U`U`;uTf6lQWCA7`5=^hNrRxUS~_$v)SJh1m)Vg&p`j70ImKWdZ}P%w?2qGz$ATw>1A5V zp3VM|7%0k_VWiPcXeaOWD)eKM|wW8aBj#E)YkVa&Ub;xqwNI z1k=m2ls%pO#4yJV^R5DOZHn2gJjn%2awM2uo~7)q*}o9Q+Uenj`>zG=5h-rBG9?#q z$&uiCnU=D*W61RCmZ zw{}V?zR}WTe*@4xTH^Ty44 z4fDSX%oAeFZskfIV3Iz;^l~j_&u0GzkP12 zkl(m1(kqhxRLIBtHs)%|YbV58+puy|Progx7~I&`KmK|q4@#HDZM{L=LHC=TdHSzi zVE%YFO>jMK#G^1Ku6fZJ^d!jqTYlS&X6SyvU07 zzH-r?Zbh4yE!$B08kbjUKB!ode# zZnNTTFIOfHN*9%@iA&4%XIm^F-0)y-;ATKmX_{;3<)?bhfm}E&Xy=|7Vr?P%w zUSya91?I&i%x-m=Y``Q-g6Z|9ls%haJCw#vtxF6RJEFoA&!tQWc6KXI@&J?c3Ff1$ zu9mWAGi{g!S0Ve4aOfSz;_H?$27`4__hImMUcr{ZF?zxp* zz$8b4>E&6 zT)-tqg6m~k%HEnCMihH)y~%LL3*4Ji+-~JcF5r?Q!S!-2WzS?2M8R|G*9>%Z0s3`; zhWgvBostQdWJoZ*Tua$A*&4_EmSL_fFu$8%b}Lsh0h0^~rk86edwcdUf`sRr+YR{P z1@MoV5^U{OuH*qO=@VQp*HZTO>=DEWgYP>H_wWMut{AslxsnICq)%|YTua%r**aoy zVE8k`Twh@RBF5}iuH*qG=@U#Z*HZRu_Q-&Fk6|8BVBQ;Jb}Lu%0F(3yrk86edp3I% zF;K3p`wa8Z1!naH2U(`%0Ve4aOfSk_VWiPcXea zOWD)eV~BxyLqlv9h@(t7xc--1z$8b4>E&6#OR1?~|kZnt(yF5r?Q!SymNWpB-nB8v6DM;h+Y1@2^u+pS#51zd6@ zxL&TM?3wIwMB%)lfgV$UP6;&hN!{8hnSe=#1k=m4ls%K-AOc}d8|GAjIg?;^D_1fB zlMD%_muo3|dv+{ALjOB!z)vWEk7r7-wOhH82e_n9aJ^hh+1s-x5+n4#TMc)*z}*(( zb}Lu%0GIR$u9s^mdp7$uVrbsbFpn!RPlz$Ql`DCGN%{oS%e9m}oBevgJkc;`3e1HV zvs<~62biQ!Fuhz$+1s+;5T;EJo%i~C$yj(8T8 z*eH8Cdy;@!rxIf-dop9K7cnxE%_?hG>vTr9Wt$_FUdklOramm+c70wceZ&MnGQ|3@ ze4$S{dpg@<`YajdQzFbWV@#HBJErC|GBE*@3^69l7ntSjsqA>7w9caZ>FlYdK*rc? z_B6$?O7R@Vw`WhcxE0K~Ok=s&{x$lc-nQ1${9t@giae?T#&;qSbC2aZx*li>;*)s*Ut92o9wq?&!)a<@U(0VpMQ2C1u z&}50r!ppy$J)Lb6PU~Vr#n~-r?uL2^lR};AR{mlFCK+N(FaL7(boLy>yp#Yl9>eMv zMyIpiWah5c<&4c{+eM1{a3$m0v*%jeG`xyw2Uj1A4@#1!R3=^@+DK-y-!in;)dv3C zG59qCKbZPpY``W<3ES&K8_9Hb0>O5*t|iQL_B(=ZUB}qg?0Jlt4{nqi`8-6oHCW_= z7UYl?yiaky#n&j5vS+j36(+{sHxXkhJCX6$&5X=u&$rB7t*`;G%di(>?C&Pn-P)e|NhD&jeUs=~Q+}Z2}2DYnpCn2_HC&f;_i)o?m zck73Z3qB@C$;V#4Z6q_<$%fVXnL(ctL;r$ldEM$(zQzV@vXrpBeA`H7v)?zc#nwFr zergPUFVlkE92d0T18iJiT)-wr37caB+ymN3X0sO>)~?olhW!UIcJ-tPyPMCA3)ti+ zVY|=UNM^E~hSh2iW_$Lc7kwvTgcZM>KOM+S;Hn0sRqjv*inD8t z+n)WrAeS`sS3Au%(M)Bh(S+7Gl|MQ=9g)^rhNrS!$~e3&GMz0lXIJY8d+^A1vv~usQ zJPf?-ABKAd5|D`O^z0m=V=qsHeiqR=7BYOcn-2IkIF9)tn5;2A+Ya&fLx#P_gOx`^ zPyS_pns{PyxUo3gJKQ(iZ$8P)Cxfi-=9B*Y_@uKOXh#v$jQeEXs=z2hwmtUsX6G?x#d0xI;LiiUqazaxBZCYJ zV@8$%-anSIr?b}*WLN7FgFL^0yi|}mCjHy~9@gATE?|-)!K5#NS<0TxUPlzponCIJ z7Zj*hGUZ_GBo8o2pJ2M3rR>@4^~B)Z=~agLh63~I7_*z56HK?Wls%KZkrpG@PXKz+Y>qf?AvbQLd8nsF}o~J;j>n&!BpTq4Eu_?hELBu zjquzP6NhIWa&&&?dDx@vnP+Quc@EMVFo1_0)6f}4lAmwZ4s<~_i95@lSZ}hoW$%$} ztJ1>~elX)laiNIF!6G70G&YR0F@ChM;kN9(5!Pse)om_QT)-knjKw|>?vW%ib5^9-iQ`&ut&W+r33xz$HhF%RU!2 z#@WCPj|=aQu#QNu`25+9Wo=ztz#>PC<>UG|J1@KK*$0H>eczD;JD9%DxBx~Df}u^2 zFM1YANO0pZhml-24$yCDhzA7OBrd81G*#~3Hqf40Go~w-!m}Dwp zvYw*vNl1Wc6?J>|p%`rOkS)g5C5 zCYef@?%RYU^6k|z=1d8*o1Ml8Ofr=)-Ohw0vhyP`=4=VGo1Ml8Ofr=)-Ohw0w{u(e zQDIt#dORURf7-46iwSUKAYABAz5U18QTzW`1hrMx1*~rK1>yo0IRX~v@4NLGop@G* z$L9;;Z0Of7`S0BrMvRk_{Mrc4LY0%a;CnQn3x;DmKC}FZ5?XUkZ zMqrYugz4=nA<5g-_UuzJ=1C>YZhf;c0+UQ7Oz)c$5@3eD`O`7xsU^&AzBNW*lBtC0 zeO5ve`Svq0=8H<0-F$0|z$8-%(|wzeWFyjW}f-U4I+2a07NFx7T9|JDQnu8lR7$Y#rRKj%sB_z53PRMR>|5+b!rh)QI zevWroPui#4>>lon040-wl5Z{eXeXw-Qj(r9Ksq72F#$d6{~_-C11+zrdapBcXNG%+ zs}4HCr~{6II^cv?y~eR zkJ&S#D^au8{4UirSK^yCFK!h}kIX?aD}L-PkM z%yoe2!wWiu0r>#K@`|dDnm+`Fea^@>k4e!`vA3g83=~l{(a*)-docre4j$N{cUN(Qu7D77Y_1X zTxapDh{JI!x^?bJPjejpiaFd5lPl(4 z0eFA`vi4cR*@s-+KD}u#acCa2HZ$Lv6A@<=f`L&NclWKif%$~B`B2%Wh&Y=N?6CRQ z*5<=ynLFui4_CAzgPAAjKX$-{D+{t z*&&dH^E55&SVBXybh&=goI6hoo?Un#z{7o)W~ax@J-ZMqI9zDuaow{EN6bGm7|(Ea zy}rw0VNHZ*Gyb0X@Fw+^Dt-OYf0JBk(~u3#MgB55be}=G?}F}v2619E#E;&vW!t@T zTV?JMtWROQNU-D_nX%|Q>oOaMSkBK>SJoFb-B*#H|H8QKOTxsFUJ_>9O=q66>732@ zm@q#4zIgUY_~66d)DA6(L*i=U(xxpG<*g^* zJ4YY#N1MRLzm;r=Yn?W*4*Yl6u@62^kuKlG5pne@Xfw<&o|yDQGn{f{YiPbb|^ zi*6@p$bP0_ZRBThvDT(`jq69|LFN0N{=Tys`2m%_zUOpKZGG3(rE{G~#_bgT86=v$ zaDUmTizDV!Ej>HFEHg>;8N92HwSIbjAp0eyOH851%U13Kr;)DR8=oVT{&LJT8rmEbs`4{dzE9e(P^AIj%CLVB4Y4^po^F^ppCxJ?^w|fy`)4n*W z-fRAqC!(pVU)(y>(LEzu$@Xhc}&L2tntRw6_5Ss%0J zFpyTw=WI!H1lNTGRt#fXFL!VXAoQ}eBpZJLuISdWO>9jU}fe3XFcfavb z4S1nY;n<_iYH<`r!h}+UY2z4(@M;`#WAk|*OzJonS^%l_=|#FQP`+P>HZ@z9b?;vqivg zM1PgQZ}lbAmd8=HYEH6c&DHEq{(Q>{xu`=_M?F4k7@Gx6^cq<16JyaLp3qP{Piy;P zAfo!>q6WNHsPu}hC=w==B1{|GKt%P$Z91ISS5zoNPXfe&zL4!G`>J18ei=CED%v++7mS3O+u&F zm?nyZ38e_rj%k62AJc~BiERLHX2g8_oS2IrWV_#;>^zcmwlfxo&R9<+9m<%jOQbcnV+Ki3{}W zMSf=jecYUCt+@B`@E$^Q7Yja>R!2c z8eiOpi-CDM>l|zD$K}ZE;!9cFJ%A$e*PY4XerLG-A}p@SxOXKuK!NoQ_s zR#gwo=?t{iJSezlDBOo4TnS+_-whJcEInhXr=Gf_(%zb~{TxX)3z+XLKMyRsJHLPO))c8#kC z=D7^Bso5c@dlc$UMs4%B*0!uixX{tKwr%6;fjQga?h@Q{6z=W_x7NO_N4U_@xVC-c z>XCUK1L4iHW6fEDy;ouHiLh&Zm-Pr6IvU%yaa=tzU&KIo?`*BvE7<2M?Bxi%*3PU) z*wE3~ww>eZRr7oXqQ?zZ1bA5i4+OYRA7(Yeg^I?t?HpIHnin|SRl$9s!rdR@*4mlX z2p1|E*S2$9ebBs!ak!o7`HyBO=qn0+gd)ee*3PU%*ig{eww>eZgXYDI!tG2ilr>|) zeu=_9ps{Q1%u0j}1&wXnIj$a>^B4t3oXtVO9VpyG8n@P;S&49=pmA+G$JImg(iZoy z;J!@Z9?`h9c4j5Qg@VSl?HpGR&C7uz^X^fA%(Z9Eh->a|7?wv=Jut5T#@fc_5&%$p1kW+-N2NW2$KH{iW4P4H<-frYE4Vct zwDM~__Z&m8XZ_*`w=s+w>@)qhCPHC{2n~(s*0gTVdeFQw0BJ54V)=Khw5&sbf<~ZJ zeFA>BQa=--*3GX9;N)FgS~p)Iu!VJVPRZdzGmoELHxCp1=1Py9dnP4TaJbOQ+{f|{s{S?KiWPYRnzB!fN`w3P7uGmMZ6v*?RZ%m z1E58i&JM#0*jgG@SKbM3# zt#ltrX>b0xXj(4wbEnfdg}IIHC1u2jx;!pG^F)hN+UKwVcV9ljc@^Qjns5rwB$Xa^ zu_m{8x8+ONXuEmd8$VdFxhRxyYhJwJ2Y~3YC1x)+9DmyTW!$GnDinY0#ThSi-x@o; z1kb8#yfewhm%!(RO?h$c<011p)$mx_+=N!g8PQ&{gEI+t4jM?>%(C*?+;8@&tI>b~74UNB z8W^J}$Kq=GKo{dQw+iYT6zXj#a_jV3`>+yWLP2BN*u~WY^F~J5)Z7k)p1)_W=M=+YZ1@};kdryd4o8zEG zxX=i2?Hm_X56o8rh31=k1@x<0(EAwF&8x_twB0ajEf^>fCKLip}4?@6wI$_F&_#rYq%#69+eRes07?PgbAW!phK9D4={I2{G#d=b0IJ`Hai7ztwr30l6D@bayKE{w~x*3;FcXR{+2*=&r=w<;E68>?;(tVJB4(U(I`=aiO1 zT+OjWJ=&Z&#`VkQ+u(Y008R%NE9TpoRC5qj;nlLal(i0_YHZ%cg7(q#!ze==%Cnhh zkJ3I@^e6z(;cOAT)2zR=#|=a%u_WMQ^Bo%Uh`5oKk)1w%h$8s`L&}fml77wqlAiKE z5b^$7zdGf{=G~g-QQ?`EDV|u%r16%uj3^RM7@B}*Ne}*`?@&7hBE(ZZSdsFK<)l4V zjA5S5g^zLZNTp%IT=S6<5c7E3O}z_bAUVhUfjp zHdZ6wq0-m)oQ@jXbaiQLi)5r}md$s9L~{w;4=z^C3it3+goCrkxcrq~GEr`u=fMw4xMZ(d8oTg{bSK9Mn`lzJvyBZcp z=1RU2M%SQ-`?kz+7$ehuc$oJXtVfif<8oQeM{)J4`G7E)Z>|N(s`;S1zAksYV!l`0 zydJgj)m2h=gVgD(f%%ZwZEi$mbh&IkjN0ZV)CCuN%}1!FxmhZXv(zne@qK)8Yv$tn zx#l*hQ5P%bqo~=~+>Wa7YS~=PT6c(6aPb3NbEnj(i{s`8+1{pkZl@)gk(cb%=H@PF z^b*22wH0GK&wrWk%Sz+s;5L7p9#jXCB=Cu}b2j;N9_1RoSzCo(@*mh>9@FZ1{^^98 z;ZMYaNN4!ZXyYh~#V}YnUiVGJN8M+VlBA3z;CV;wD}4BW{>i6Y918FHn=U?=H$4b@e85F0}V3bUaRs zy=98;mvEkuy-L$?G@=GVF3qa%`d#UJJGPCeb#fzI+V5tyxRCfu{u;=JYoX z^hXz{yPq;o-(#4U?=ftn&mYXo_ZYUhkq5)`)^>p2W`{oXt1lGpA>#Y_Df#AkEzNJ` zQQfhV^~Pg`)0M|`r?;45OZt8<#RcthqV09YVG((xzUzTJvfuS6#M^p3VZ2We4$URB z45{=N!m0TkXA5G!9yYP3!TPdYUixt{9B10)CAa^ydiw|u&nL`xPTSrOKZRHq{CIaF zZ9Sj-!u$`vR9CRGZRkR7o|si%anUQ?%<_oWvcVKkiObBkE}*#ZIJiJVe0HThX}`4I zw%wBR>9NA0y@d433ZHzxWWVEX$-b|LO_fizp;?^DjL);`Yfcyv+os>fUs}SfUyD;b z_+tE851VujGj~M#?WW?8s-hG(ApDxu#&PpQ)ia2@SP|q`noFy7AH#a~^_X5;> zi|5R5k@XiX+P28@Ya#1)#*W`B(j-xZuAD(kqBo#6#rYbII6eF(hy7?+;6I}sw`MQz zvtW)g>bg@s-VLII72++;i} zai7KZ`kV#$(AhHx8KB+r6#eQ8h1MJg#a|0ijcnYY*WoBEmoee0L zeZREcxL*!J^-Bw`AYf!=}n7I)hODghR9BW9J#f}3 zo`t|FJ<L*r%tud{3yVdph+w+Hc_8SvzhaD5dPvbijcu_IpKHd2EaNLHqt+ zY0EEQ^opT8)|2Hkjqa<+$H&omzaH$={nB)+g77X{?$~$R(6;-_`8n96eC=;jP8}S@ ztj!mZ%?%Yl6PIY7K~reIU<31KiEGYA_&h_~rnH53{;DR?&m2r93*R<^--6zO$T500 zSf z&fEO4q8P*L$1vp1GvxP|rqSEk@(fKYq8PKf<;Lb(MYMT98Z<5I*PfzB?Fl;*v}Z|A zwNoIXXmY-E${jF2rfAML4@!Gx3VXGu=%GDfsDFEw^i(^AB8q6#o&~uB=Enn~4++ss zA?$VKt*C(~?DXeZ(o;M`kuuMM+=}@L=Gi<9g5eeYva^ao^9UqzWo=HsINNtG<8_MI zqMU}ED5s~}6JU4tDH)mR=c)l##o*&}&6h-0*LlMtu^uQDEyV0Bzm?oa` zJB_M6t8!!W-!#w7!ZR&XJZt$Uio_F!BA(OeseBAfSMoM5H#XO6o(sY=EmJ&e`6r6R z6NVz5)99(Z4NO<^HZM0YKPfz$B~Xkm^WR1h4fkFF^s4U_WZzAg(8jX)DY)5ehm+C8 zPjk%XpzYOHZzEYTt?XymaHk+JzTjdh?e zmS_nE|#yY66YU7t^5f(HeEaW)NM~TST+@i4#^~Dk`!h%K~D-ofeMAle` z`(lX}VL>C0m57YZuWPI$eX&G~u%MC0N<SRsGhRACef!EXO;9Wed z@#x*~*d+T=sUMi%W;XQ8A;LAd9GU;6xLz(?lOoR*IgxUeoM0{D3L~29q&nb&oF%TY zNzb)VzhZue*)&&xNO(0ew=1Ss3e%*Buw5ISpXK@yUhA_SafKPpby6L0-6vdQlb&m# ze!ux$=7VobUj;Tv!N~lcVtiZ}Cqn@!ENU=m%9%pWMO z*9q68$aAgbC2J8^7|~oO)ls<|oAg|5F30sF^M}kvzN3A;u*nqNsW{&tQc02LT+2qq8~n2mhD`$l1tDf(l@`6iJ{iah69ezF#Eh7rwqQXQ4k zu}RO_=5$=YYCgtn=wk#ogGzioGIuGqw+P#$h{nu5V>Ig#UzpK+Yk8g1QF$F#d%iZW z)> z9hGmfNzZ4W@Qv$-=5FT0-xR$AbkdTM`9F&Dox(XOQk-k~#d^dUW;EwXb->x?Tx`;F zF4T|ApD8|f37@1$@p)-BCqKYB$$G>GW;CB#PUdvLXCQ49S9?A-C*%5&`E$kRZsC&@ zDL(tNe2z08)+0VJqxsZwGN%JRuMj?Qwdb=hUq3SUC_eWHpQK3fsf~fGM|@yL^Qq-q zP6vGK7#LT3J~rRt`h(^#n2-N0hskq-K+>;{3y|(^fJ>m{Cn){?W;BMC> zu}ROpP=C<;74z}GMRC7y&y|kMy^8w-!aXTc++Uo{+{ zFVv6B|5bb*6h29j;`5R$pTo?D^@tD5Xg-tbfR7zVVw0Xvp?+vS!F+hGcnEaTl9Bmq z#ra|3oD?a}wQ+^@h%?M+&XekZ^FA4CVw0Y8p?+lUQ+yr~K1tDByS}WAE38L+U`F$q zR0n+QSQDG{d~7q_g1&L*e$S)+8{w1NIbiO$j>{qF0zBuqcb`RDO^Q6U5P9^c;4C^7 zIbi-S09+ElK4;NZk?^3D#Y;sFn78YxX{qJwq4`u{pQmQ zgLCHtg8LbTdoaMQwJU28E;KZ*ZP&PZU>;$ZP0b-e{j5Sg%&6@;rPj8rN4U_@xVCNM z>Vf&3#XTap|EX|~M!2>1Wj(@$j>fg^8&{9aqYNZxz!wVk=N0xv5q7POS&y)xqp@up z$JHZ4Gdz9bQO;s77VO7yk&GLcMA)@fj_99OTJ4Ge@2v1#ASkPakGA z!i9>)we1{NubNE`_j19VSGZS%xV3g>HNu68#0p^2XqJn@&ShB5mnQVmbVx;YYgOn8Y@|&K!-3OA7EG>QT4!V2L}C?;w=E6mx4Df`^S`AS3Oht-XhetC z{+8mC1CZu+A(sEWKP~GJpr8?2PVV>qg5MzD5y1K1QoKW8X}_2I4f33l!-r-bKl@vX zPf75bJ3V&pw-jRqhYPJdu4~JK=F|YJxl4fZvDLB;0}6Qz7u$h3O)#3f*?9K(R1tAT zAs85Szd!Tjr@P-K+sQW9ntNb#WS+{GGGN`yFn&E*n+I5pIw4fh3Ee)y&I57v$UIHq z+!x}kQeRJ>uaWzItVTFcaX47(*3O%9I@rU!95@T&i89H2QE$(iV)c=Ey7K-0(D&Nh z!D<8q73Vu}_K6>H^~mf}I1hw4wYibi2nQ+}$Igv$^~jvAa2^bCYHiMHgaZ|gW7|Bg z9+@)~&O;$iZGFRPgaZ|gW7juv^~gL!;XEAT)bfPY2nQ+}$L2v?Juu%g3A!e7EPL zakcl|=4)KPYM#Zu(_Uf2TQP%Ye|N#kxPcIodLVS;p!*%JWpgHsHk)B_*__1}3%D4W zXRA;xF=D&ttmPByQLLcjVx`8FxO!xsqky&xkX!%P@`=?52PzJyE1z;YDxczN-zGMn z;`)(!uJV0H=zA@nSdDy#iuS#hPdOcxPjR*P-R4tVziRfd@03qF+3{>XSs9m4kgVm? zsyQ1*@w;xjV038C!L`h*yBUW3>RzKmi^2m9gokyo53p<7sCv~r4=DIux3dIwZ;QGI zB@VN;W`_=8LO#I6xK8_msCvbG5imA3dj;{_7I7IR?S2Psny%sHULdpx6B+>~+Ke!x z>J{^R!CVo{cMSj<9D7Y&v?kL2qwJWp;7a9SsZP%!JU|tLq`dzoNfWD*!J;0zY zmul?_CBlS4fN9$`svem0EapMM9JH8+0?b;wLWwY;5MbJNjj9LcrHpy3IShp8a%5h{ zwbF5qKq6Dr&Ih%13|k_{UzpUe0vZnxjHCxEz^PMfO4= zn-nny=9k*qkhO>_jA*Wt>VPZqlh*06Nzb)Vzhcg3HqAvK5?+nWKE?E6VVV>Xwriu> z+KlyxE6ixFlj?x$KH(ah^jr(|`^_tu5AF+G0yas($m~~)FBQf~k>^{>N!B91FrxWR zs-yBaHtG4=JdW%4n=fWIxF2DJZ&WZcuT*?57rsf6=UdB5)*`+zqWMm$qjEVm>G|4R zj_U{JRm^5na|M`0mm@P&T(1^WFm?fKaA zkGOth#){7k!Y3(Gd}@0-)+0VJqxnp#13q@27n}5a3iTuNTE*u^;gb|8KDFFoJ>mm1 zn$M&p!nP(e3Bx?r*<~Mdc+52G@n{d=5)Zvo^{35o{!DRxPD|_ulU?5e3Bx? zXFqZw`6i&8JF*_}ff>!GmXkRh@UiEPakb~OPjWJ@ADK5OKDP;t%wRp@12dY>q&nbZ$C22i=ToR3nnTQo=ZbqkCoLJ7uT-4x70yYK z;#?b7SdTcvjOIM44mj_Vu_iX@ITz|j=BpH+`-D$Yr1;dv71kp@Fr)cQsslcDtcgu} zK85;`d5hw6zwk+l6rbAM$9lvEW;CB#PUdvL$Ig9mwdZ4VGOiz)!-~%X!Y3(Gd}?za z>k%KA(R^z8meTi=ina%p^&7L;`l%?<+F9p-J@GEU&l6^M`1C% z8kw)>IytA^aFhhNJ#bE0%X8MEynu$zi&~!NbX1W%JW&e&f=#`+)0$VZ$Ho1@a+$L5bMZ929zf2>&mlT!KEyiMe# z!ImJKD|6qw!n!8?zOy|4C6*`I4TreU{Mf7pD3@LHQ+qmPa) z(bvefOC0j$WAk<$haD1!w9N6r8YhjzWqdzNv?va+62)N}J@CZ(hvJZ!c5x`xA2t`p z`LI*skS`ybH64dt5{I%g5#& zIu2(^9MUouhg!Re7R3QpqBu;Wr}7~&?c!kbA*mmmi!`r2!YeIvylU+vTEq)hB3{$z zo!0jEz%<=NEPZ1%FE=)gCb?Hgre$h;sFV4cuu1Sp0<4g)5Np%ZQQ)v*nFer zxhy=>GR3pjE}}?0VJPA`jh<@Hz;vZO^KxVJPR(;gc&25FXRTdCk$A#T#B&-w)t-Ur zN_*zz#^z$pa{!)-6V^iMoFmU|h$3-6~*hh#H*H*B|Vjsfk-7M^KxVJEt=#| zNTy|~{c5=-io_F!BA(OeshkW<|Ftrhg8)1LFWxw(Y}tKs?u^s4>hMA%q1-v&3E18_39 zSTWzu1e$}W3a>`yQm&Il{vil(T;!XPwS5`uQFB2D&E@X#p{>&MZ5fMlI%;1QSI7IZ zxPD~brJO%3&O1M9zOx$n4wb&X=X6e`SlNAnfMxru zNH}_s)AW+LaAe-cCWXW0mP2>HV=bZpjlQ{?(^0vrt4p~X$w=;w%=;CGD|&NaE#d%; zz8rEoYV3%s)R?l^XIIaU(6Wa~sw|H9tg={D2|l$FzE?9|fkp|JJWgxv}|x z=6S8~Ov@BcjQ8mpSiTt}io_F!Cg54pgZ~)!>D(m{A)fZv)g%uT&sfe4{vYIgx()=F zO>ptOTys5YR?Jl#j^>8+YT0~<1#d*v0rO!N++;t_eN(ZJ`bYcwH|h7YWxlq?C~z<& zfty9|$rQKqC~owf@0@A&sVcKtZj4yjQW1M>rnwAS2-y8Y${<&qwHybA>*^FywH&Kd7U(Lr-fTZCUd zxCf>5Gh5J$`4PU_)ZB;5Rr8~ANxz?k>!G>UUem80Jb;q1`7tSx+2lc#slHL$ zCx{|7F_cu_P-jVr$k_b2hI8JnNbSP%Eb5-q}lMjk5>pp`S0(SdVI~+L$d`gawTV3w?{mq(p>%LP%q6c$;dk8kT4g7Buo$ zi3t6SkH*^E7fZAV3mSQ>M1+1yM`JDY#S$&Tf<_)I5jkLfQn2jMx&$!pW8L4HvYG@4 zEdh+aHI<4SFh3OlZWll|{-U3ye3~C}r_dk+Hc! zH{MRb<;r@FV`7Y4fFaeUSj*JLq>>)yZhICRh|nBgI*ZNAjm^(!p1Xu+TBdl``lKil zPZ)}LPNS#RD1qt9nk_GP!2GP@Ip6FC(QIL`_DI)kvi1;T`0YIyYI6#GuEw*Zx0Cej z_!x>PqUc-Qa!1Y2X`(Xao&~n$8mtH9H*~R1P5Z5^Q?*@cVc2BiP8YmWU5Qss_jKZp z^(w8qGGfKw1kTIK%GL9!wS^d*@F}%_P4`vA=W&(a+D(4@8{u8F-1#?wCA9jt zX<_r#6r<8_?_zw?BM7JdJ%F9rG4}KioXfmZR1Sx<28s8eiJw+o{1}TzNACP$8p8n$X&kKgKi#P z9XCHO!+~V^k{k!LzvR9bD!qiLPi;j%6T=$KyuX@5Qh^F)rFHTB82qbD!+M*As2e=Lc67oXt<%521Fwatw@82h%m(Qn5mwy9kCLg5}FzMr3xZ=ToE{8k>-9XlD< zA1j=$ez8gQi#WxWHkIe2Vy;bSuQLvd$Rm|v$RnF#g?L-9Cye(A!lAi@HYk<;LO3tR#nQ*CG#r!wR7tooW0hQzk%xA|GW7N>ad#rU-zHtGCp z?uhi;O~oNqMJaAT_%*AIvH1mcBH1kC#35JK=N_F{qSHrMk!3yQ*rvIzbM%tlX%ruO zjvk26KBjbzo|hY&U(_U5gk)Mqc+TJ2*`z2EPZ)}LPNN5&=TRQewlpx^?q0>OPPqf- zCdG5U8GvZEka)KD@Jq)WVhrc#Fy!`W)9dYQd4{GHQTWv@H!!~>M4MGmj4qFxUzXa! zvD$v<^b(SF?tuJ8yV6jyc+UJ5*$dDHVvB4A7P4@(1lZgF$;GY5xmwQcj2(aeK?|R% zO66Q_5Wf zoFSX%{FE~!pT_2PF)f|!N*H%%yR~P!MTevs{o-|uTWSHrmDQj4Ex z5f^AgTx|RT5f#5XG~hMNreFL-kuaeYVcPfwB0her=6~DxHP-@fVE#a^o9l47Z2l0J z&Gm9|Ctuuvi?R75tH?ilek01}3g32XC0+C(uA}e9BKK{#gXWKwbvj1A$=TmJ_dM!P z+&4D6F;~kv1Sn{PcD!?A(W?2FV5~Ja!}{3VCD+nlZb4a}_7*LQJv3D8)jME`$iRGD zu$o&1`cE|UZ7A#2Z$yo-p%P)+c27ix=1&ETez3l|UEuHbCG->acc5(5{2yD^+{y0b ze}~Enxu`=_M?HRjer*0s6TJ(T`@~qZh$l1@PmHg&PX!{XPyM+Dyj!UBimfOTCX^yf z8{0s{_o=bDM~Cwsi}}=ZxA1eloV)&G0j;QuA>`8-7QTDv=LcuUTA^m_V`m@5?<=r= zadwuhV~d`HHMsq8&-qXKI)_+&JQdR8WD8V?fBtfhwDb14UFXg{o=gVgKupznxT)yl z*14U#We!8koR7{<;-WX$$HynmovwULY}+_^`XPJgae;qm`;8O_I@f%n_uJWa1h%;t zhalTGs7UXiIu&WElYoJ5TLnu@7xv{qn|;#?~*l&0&!wIDs0(y z@7z|6^(m6ysGg%1tPfD5E(q4~<@96vsFaw==TwUSfa5QoSd*pi%4icLBQF;&n@@p6vkdoxizDV=T6%VTSz(e` zgY6}3>NlA^+GRYpC|z<3da@{ju3Cqc#JH~jqm~1#?8m+k1G=?bsOe&Exqx#o9^dWQ z*A9tt+ZL1;C0yJg#T9$TzG)8j4rcAyac@F<7}p=k+H(V*Wk+L!a)o0G-Opxq-9T5? zMZ4L2D}|3SAa@=x7FVAx(b-8NvSL2RV9gPRAx<0T@J{CnV&k^eEaLb; zFvn->+>@RrYxPz0pEg*{QCQ7>8_kM1s}R&yA%nr;-d&yF{7gu}DxpdVml-V6cZeHUbixBTY$D&=$fyWAhAWzj^TDFdcIpH8T`&4A@7N^!fqMErM5cadE`a0mTi)U^TfHO1t0w1E;~LLtD!8jCQa>Vf%ci}|2neoc${P=I-U7V{(A?oc93 zCVf$>i}@%JqRXNA`qqUF7ov;! zyuv;(jkTNwK#P2UhWFvwlvlO($>|^$vA?1AiK~4dvF#Vt56oLFug!3KD3>4G__0kN zYi!BUmfC_)^W64lxgF;=g&J{#O2Dm_2RR*ZqvsdFEw1+5Y@S5*tLAOOZN6C&$Fpl6 zE9B-Ah^F&WyY^W&-vF!4c9=YB-p&v_ILcmezS+TG`g=^JJ&jVv%QsN8u?oJilM$oP z$}tGVK)xA;oJrRf(x=&gJ8y_^aJEU$=8$fDW+40O{Bu&m8)pX2T>E_fJjP>w&aY&1 zF;=$^3~3vulMUo7t!J~JB&Ng z=-4yk{093=zhu#pHp#5KPBZ`Q=ck`3`^;0w);VOW)~;-t+SNILeO5Q|sEI zkRW|~N2zto-tdAw*}t4VciKx@zn((&ZQJBr;Ijjqk-VPcIT4(V13gpnOu<_*YY6pP zvkMU(nq#<<#mH`yxH+vhk3xsWBFOu(2;+*KN2BT$^A2DfYt9nHi(14zC~3#`+L{4c zgb9rR)2Vav1LUZ?CfuI;>eltLX+)xG4Y!12K(ioU;v{sj4btspzwzDPgY{{)K zvWw>`wOu-I$=VJ|#0?6b8|JOre4Eoj8`!m7TZOOY@FAlR8ZGWugEY!#ksCYlRayF-@Q$6C%pi?~3;b5S`PRS%5;isbBtf_qtudr^p6%UNgXEV>d)Q#_so9t5>kqtW6PdHX&HGxoU0|oAb?uYcwIOnBT@W*!@x z(J?Z*Z2kk6%@`MZ&9N`0v$@?D@&Dg}|DT2bFPn?#{~MY;nTv*N_DW4~@r_)wEH&z4 zVBU!u%Jmi0MVG7QVyUGU>`@(G56w5(dRl_5LLx1p-@x)RbV|G*Qu(rP(Q-qPOUr0R z5xHgrk>v7QZS7cUgUeO(Z5E?Bfa>^qV7}dI9~AB2vd}+7#x*$i`FDPp_wnQBkR?uOc z4%Tj|j-1Qoba4L!Yd6}T#ntQw>aljq)Q`+}C^|=kPEthr&NqzJsc&+2#CpUBW;CBk zb>Qzl@i#W<`4s9$=G}_VQQ?ymDL%D59qSPvn9+PD)d3&7M~h8*K85;`F^bQH!Y3(G zd@w(x?QzbQXTNYoI?2&oAi7N^&@kc;&YMkNs1I7ti4h`au&>b#0O?H zpGkGV$KKnCO?p0s`jL5$;&ZX^Ns1JoTHj_p;sZ09&!jrwWBYq-((@_QkIZ)}K9>le zq)74EpKYJxoNug0d|*cNsf`CY9q_@qBjsCM?fLA>*N@EQiqECOCn-{VYR?L>9`S)0 z&8IdVqNi0fC)dzlaY$eaP0_&UFavpQ~~gr=S-<277%9XO$lW%E8b z+FTAVql@=*%@tCkE>_GHOs=^SRrFI{a&;B12Ijl@YEyGuD!)f6uNEs;;;Okusy-l9 z*QQrv^Fgs9)Ax0d$dzHf>wbqwY*9kM4ibX%XnMWVNFVJ^_ud23w5Ycy%;Hz4+}M1t zrg^>4Ov`%uD~iMuh9=-y(o_BiA{Cx_xv{xQ^SnWLre%sJ=DPGAfXt7gNIYRE;yH~T zcw!!;_6$rD&(gQl@^S<7A>rBF2#V2Vevc~}uFFGDclmy6_z7){&4+b!+$7DBmi6gd zqD5^1D^XiuyiMB>cNge~%GiTcPvu-7Qpvfz+}M0Xle}3-re!MUYW|8M`3pl6@GR*m zo`Fb(XI^e>j%%K`2+y=k@vQX`Q6!!)6!Dx!PxY<9bfs_Q<;LdwG|yXwXIiFs*4k4P zi6;z2Jg3o9?HQP^v}ay!Y`$OfyiItfWj%R{BJqTw33!(D6wg4U!ZR;7HXqeIZx^0v znc`W?KT#x}Fck5eMo;B!V7ijGdAVit$KcuA)X`f6mJ%XP9;+5rKMS@&<=vL1yDItZCNJ3#)YdpQ}mb2@6B z99PHd4H~Z!N69 zp}uO)W)ig6-vtI^bB8QMktK+qq8X<@<7f-&u`(he}`Hb2=(- zbag3jA{qBS&hxq1R^a~7EHfj1GGS0Ec|j|)Dl&1&*u2oo;LsaVxw2LBA}iMnAroI8 zFe|oR@?!)c+qLv*)=wHHT3$HZPMBp>?EGrskb-b{+eDyp9!PL=}cqpT&2br_n=S ze=YD9te@62_4O}%qq5&AH#RTVJdX;`v`q25D9iJs%u^J}85mN2bnz_d!T)y%&p@QY zGcR|*tlAcq2E7nOvxPo)u^vcsSN4|H7!ie`Hg_?nPNVl^(!<`8az1C8eX7bFFy{yS zE)ss3!e0EW9`S>r{`^XM%J-Z|%rBK$HLth)np;6Fz8;!4*m}CrcN-*fC2wqHZWo!j zWMB@8OmhbWqRSQYCa!JnL}hq2FmJY+cZp_jxoR%3*v;Ljj<1L2kgX^G?}0>GvSPkc z(C-y=eKjy&Wzp{w^x$&Uyv3p4FX-X*&>U{j9}x7oWMsaYWu*T+2%$_7j}zVB;bK#i zT`+^}a%;KTnlh(@wZVDRZ)nXGS5r=H)Zg}s>qq8m6rYELPg2yA59<*hnCZ(Wr=$49 z)iIyAeq_E@@p)MIBt<>>upaS&nZA5-I*LzR9rKCnN9Ks)^N8?CihA;4J>mm1efi{c z6rZ>{<`dVC%-1PCj|!ipNb#wytyz!wz)W90IUVq^`X=VlKQeDsd^QW8q^Kt!)+0VJ)0a<9NAZcPV?J^H$Q)IC7KBey z)RPbE5g(Z8%O|I!_{7yQpSXTx-lq6037@1$@u@wl!Ft38W;CDLz1f@&_}FJP;%d+5 zMKV9d^&|5QiqCf8lN2dFwfn5BM|@yL^O;l!eD;aIu}ROTP(LznSA2E|pQK3fsl}T0 zh!4zYK9lNzkBxt9((@_QkIaRN&rac!6e&Knd*`f2d|*cNskKi|2Yl?k^SIjc*(dE6 z*ALAa^Wl?&U7(Ye(7mVJw+Wo0yZr?1;P#Wf|3u%9vfm~+QFg}Wm^dw~&fRc3SJvm; zOe|3&zzQ0H+phcmZb=V&Q>;B{4<3m4^EPY0Q*LbDp-G-4B-1kGFV+U>Ju`XtO%#bI z3{AkZqzC`8Mj)Pn2=SD|&y;5@CkI($bCKq`M|h@XJ$Z^E@r0oXc$V}O&p@QYGnNyc z^va3wZ1#d;bh%=_k!zb}RK{2Dl)4qE(^ms?v9&r7tHI@}`6j8IZ&pzqU(@Rqwx0Hv z`yr8*jLo-*jC8gkq2FCG}%-iKa0E4L3g2`i*E2 z2WU*dp`@ocBq9-qq<(C^RdX2k{3g_m`t#haP@A!POX9JPSe3_RU zoA+y)R|w6tOpRSQS4!tgxmPKQ#1n=l;91gBJOhym&%7KxK*v0rE5R_n`Yx%vO6tO^ z@0PmbQm3y5=KCdf&26ZRE?3M)Me}yi46m-1x;vy!UyaQVh*e3vJ0U^q(eAaCs8Q^p zqGGSc_e5lDeo%wm1u%zI8{b8Xu%Mx^aBrfPA0<6Ct|TJSxRTV5%@1h~cWVwcUqp*I zKtpj*ITwidoEw{KG~hh|RQ;nCH&G-^C@IXYxRvzat6fh9B9(PkEGO%%RrABbX}-A^ zd{)hmxa<3J*JJbR^5HJY(j)e}$}V?m&wq#_#Q;hw2H;|!2@FJti#!u}!2E{&SeSqw z6;OA7w05t^DiSD^1QdGeUP>ymV*ZBlnhp5a)yVuUUkUbR`TkX(vvyV^WT*f+I!lkM z2j+gpS!)&q_U{zzlECu*^f?qSa892ak^5?_N4U_@xQH>G@yFF8^Y;p9y8!j!$7+NF z6^C;w`F5V*=xYD`#>o7G0^7l04y(qAwFnCu8q0Fh)r2K!6a1FP0}LylhjRC=>9;&~ z3SC-%Y~$}j;61;k1^4m2oRZtWLo>G)Icd)QmdD0(m*((YV*HtZ%;GOCE^TXec^{S# zJNd5uHs?c0(fI-GydTd#SH4j&HV+B7WbgoRefq0tQP81*pp$QIOt$?s5gD0(RqeaV z%=)x1YY`MQGVQCYefy5hziD3k`|=Vk0)s}LS0XYp|E_oqd-Gx~f`Uedm#+4_=zURO ztgI4(!#B)*iEy&6UBb49iO#A@UhRGeQp7qk5-t{$0F z70zA#aafITppwCftB2+^;0T?&1%`4?zUKu#={Fx_%!3-iK!sZrI#02D=skW_y}=C+ ztLD?pi5?|8fLC$H<}W3k=zAsfG|zoJ$>hHm*9y8`2x;95{uv_vwS8 zMa-bV%#uEsh>Xp>8tVa#Ra+N{7GXgn!orwNXIF{H(EMK?wB|t|t(s5RlI9^?7v7<^ zLN0y~P2<;o*Jy11T620Bmixp{w1^Wl6eqRj2t@pvV{GozfR6~3Uh8pDBupqpm^O}q z2w}R1)czkZn@0gQFdH~M*P2Vw4@PDqUrPQM6!jTbS&3YM0$hQP>gRFwirK^{&E4a{ULinRw7I&XiRXY^+#O2*DMf9bB!P_D#UA1)Q)F0OjaXIs5nfu7SYu- z&Puq4W*fjHqSraB4Rcsu;dcl&zws%fwpy+M9>VA-4u zwB~vM?KMl_d;|W!Y#z@QH_F8m_~Is!dLpE5#{bFw#&aJ(hwnGx&uoX-#$%g~@o05y zY4b7pU9@9M3&*gkSUOpF-6G1TkS@Nvg!e#~PELM5US1yUvPq<2PU8FcIebG2?|?3y zywK^6QVn^W!aUYCtuc?arOj)?V{K_+P4l=luU)+w1q1k~8X@EP}aW{w6zMBFiat#V_jpse}-IS<$uXzf|H+RDP*gPA6 z8}0YNm+%q5T$#)r*mEL&=~<41Pi&DVu*06rrl-c*z_cH0$sdg6opNLI98Gh(&`is$ zy{^5rC=yQ?ig?!M$&w!Y#~4TRWFX?lcg3?Rw`!g%JlC2X;22+z%^n?xof3z%OvRzr z-l9lxfT1W3)9B$$$L z=&AFz#I)mW+dZjYF)v_d%_=ypnimSswPwF?e39loL|MzR)<&X79HFB)sy0eQ24+Rj znvsCNSc8ul+^xN8aS}DchDwBOc?5B&aV{sE1MHFu^y)lywg)5hfHgCb-hNC$3&KFJqKVYt0cr z&{O1cEo6_P%&#|VZ7ga843#L(wjU-U1G6e%&4q$}zQ(>Nz^=8is1Y_)B5d2niOAUO z(_j|^%;i&!r)UutG!)ip?RXQ2`1xgQUZDXmVRmi*sqqsB9p$1Pb}yJaRgUhpw*ih~(P+$d`dfa~&wGm@mi0#^(C;YT1lg z@CH-`7c1s7qID4GCv=9I`TY`>aJziS>0Lg(?(ziu{&D;JB?GgQP15uGSlF(br^@A8 zbFVCPpC;FmNB5!3kCC2X20zw zBF-oT1EaB@19QOIyrpbYM4U|sR&DMzSCGx-N_>!U*?br5H&@|;J{yCJx5?GA`EIT{ zE^^<)7gvkimAF`Iu92$`;Oba&tq6XQ1+SBf@8yf@MUF2%*6`J`xr(c95V_#uLtJyC z)C3nF=9-(t9AA8_;j3ly5w5x!RYP+e7t;4{K?#r9_&y`N4__g z(}7?18})Iu_j{lC9n}xb_qTrEru}|l)(80>A9ToX$a}xx1D+A4*hJL>^HE^Xw<&K2 z0Lf+hmk7DO0MTk+IB2dGt8}ciwlu%i+yUc<%@24!8zqhI6i}@1@%xUoJw>Za?4akz zK6!5)V;`;avtrJEEpAZU{Bh-%vj4^88|`c|>m-i(#^gKr*aj`KFZ|Pa;;wvA9=_ll zans|<_6n+$FT&NfW!7b9e;)Q1^jG%jXZ^PB_)x_)wvV~%QqGl)c{h%j9}F6o4h@^T zq|5M2)+rC+`KJ5!d-@cLkDce^}((e(9xk=hLs~Wd&*5!Wf zTecsxZ%*u}xdzS1pM;)YvmN*zY2Y7@8~9#sU>E;d|7}&NeW8|b->jInpUxS`=mi5s z-@cK3wC`K8{g-u1{Z~v<`--`+eLq>Tw`jeIRDJs*muW1{y6oDQ?HBuR)-~UMzsmLB zoY+zGBWe5IC++*Aar@riyM0?#YG0`3+czuLr+uO5+c&a5W&4UrY9ui?N&9A1Dh_R)WR`{u>?n-TcU68xEKlh)?jJZno^*OpFNYaT!gUt)eNYI%Dz<3Zd0o913X zwrb}lu>r~gD03EIU$cw!k@s{yUlXMmwpi&|pY-+CfcxQ^5bH|`3;kWse)9X`te^h) zG>R9MUp#Az`;ry1H>35#9QtL+Zq0;_0j*PN6gUPREAzWGUG4(wbn+E>!oOLg(J0?F zGac{J*a~&m_f%r|wsrAN&yhOg?aW4Ah4C)gm3Ze(fLDiKrEy>AZgIaqucIY?y_I5X zD7G8a!cTrjp80M(Q~zxHqWo+-w;BB!{QNnapE!Tj+n+bT;}hGcW?oig+c+VRc3WNZ zY}@e(oqJzSn45Xc*#59`Kg@31F4qciriEZRB#3jDZ;Qlx6Y<{E^4`>!H?4K&wp4g8 zZtCXe#vYn;YpERcyA9QTSI#-t??~T;O*R!#h9ho1?!|e&XWU7@%?kDN-6taMMcdtR zZ(~tSA&$Fm=Y@@wE-jmFXKy{JZ?2p{x$iFRJGOgq>T6k-`>lV<_Jj3L zUX0d1C-MA8=`d%m3!9pbBV_&4JR)uWU*a}@w0E1gs?_FC%eQ$}tWTRm(YJYIf66u& zlho#7Zjv_7s>W@eb-7=gm+c2_o)@DwU*I(+xA}i{ZO+d%`SotI0lzDAiTQ6)liM}T zX4LBSQ0W<+Sf5%Sx_&+6<{Mh;)QGwRp6DzMfstaa%mf1yLe*_w5@8po1YtdX#TCGa+q_~epk-9c6~={ zU5yrHxXbU8!7a1a?Mg)#_a&>}t`22LbNN=&ABi<#uiyX$hi5`9x zHqX|m@5N~ztj9T!#F*zs0nE&e)1Cu$+3tulZQ`AUp!KvuFaim05-1Z41{CAj!l6z4{5B1~K8yMD*#KxNSgOOI4~{p*+ouBLI% zEb}nZ)z&1=a%bF&w!7or#-f@+91GvhmASFY=QC)$Q0G8J@nCNJsnXoIUFOE0&d-fI zCYT#z6ErtMJvTSjM1!%qG&i;e{M;B|xv^SjoZfG!Zl?E*+AJl#n4D~GESo^Taeb8L zMlEO)-%|+sO?htACPH_0ALY5REE>eSG&e%s%|NlUz;f{}&0&>zJF_8y-gBe&EHu%> zuhQHoM*ZBF=D~W*jSiH?Dgiu!b7PlnKOf}x=QOZ63qk9rW3+1MaQ+H*wYWFr?~ypm zCEk9_EZW}Yy(`&p{f_2FfeYrwe7`H-Bhe<(oNMO;+BcR(gFc?yH}c%(nRUdyIDegh zxEF1A$Gwe3HHA19zMU&`V^^+3b7N6_%-9xlx`W;Fm?$nw^*luQETA zohRqzfxlfmTjCoq=aF5U!MpK;G5|MBd>riJ-FuCk8HhthhcMRx0%hyY=NgKL+S%+Xtn3iqstWg{c#a>U`YMu%22CeBv+(@9LMaZ5^Zod` zagEl&8dfH`fE?bhY{!g^J^F4fH(lQ)J7Gu#IqDVC=X~1~_sW_=Hg&mQ_#7fT@fk1A z^(U%riuU`pO&g29F=srRpzzU+t%c{lrXE{c;C!3A zela<&2f+;172ic;&lNP@Z3w32g?4*P zdoTS^C6+Yyp-LQkZl=c$H@A-+Znlq-QLwjA%@(OSnRK>D&B=BofKw;Ah0e+JtfqS! z6K&j(Pfx_Xz7y+HFYKw?na;=i#Cs-g+bFIo;)tt_crR{mqPT9KHm=*|dtr2pb+ie0 zQOeAO{l~hE-D6$7zjcgtWWR0CS;mFhy#W`ATL0{kFMudK_Pp--Qp4_*FP-hsvtRYd zZMuaOP6FsZb}Z}V>-kux)%y=Hdf9UO4>}K&G|=aP!Ehb!KQ>hNA8v-1jE$Coynb_Q zYqI0IqDGEYSjYG2>i)xFXP0+u$B(VW{XsC`2WPZ8NPPD;vxoJ$RJ8BTl4iqjk$GV^=m9S+EbJGT%jxX+kV|^X^~*-jdc#YwubO?Bj4B18Lhj<37Q-07`@s_$Ersv za;*H^41DCA>bIEZ?ajVk`1t0=_h3&XwmNk-p8EuTo`uxr%+L0?m$S5OZRzB-rKPoI zx7^hEx!O&gvmk?gkvnH9-P6fip?f;8pzf`>wFlkP=~U6UrF%ND5r0o7!hJ2p2k(uR z?z^b&rOyRY^R!A4(=vI%N4G{P*>7Gt&#+^t<1gJg0!L^HCJ@mNOmYR=<>G+gv>vnNoMrQi>sE5aV?x48? z`fcvGvlqRZnDw5H4i2v~C24|l4!4e(9Cw>aP7tH@R>r>T6NT8*;x_a$6CRFzm!Gx0 zc!-eq)2&BJalk^jj~&-{>7I@j4-Oh~eYbp1CpDAHP1kq%o=&$4$JDn?aZjZUWm7k7 zo8q1)m8>3!n$J`WlaqSgGDW6|%Jz?+Zbg3}58(T~FbYR5o z<+2GDjje^Vf4!uP$F0zOlz4hn>X?kNP>sW({R zauj*%-{se_FXgQpvnI!td~S*5bISGKQq02GXTuZp-;$qQV{4b6t`C*%_v71480UJJaT~HD8W*Lu38a_p566bXxFYT>aiY7)X2Gzr$H|4~&knjt*0t zk<3h(J6}{AJ+;oR-qQ&Y^8LAEtmA{1K6bkDrFKup+4tu$nRR)Oe5u`6Dc=KZ!Dq>r z8g{RI>1>Cd{i;W9*YD}{h=)HnD)%3`r_;lh+kenK9g)^|R)XQ9?>(K=OtcK-HH2GR z^F5tz71r^6y1M^pR~Lc%xjtRo9|VC-_8%=mN1ra*cVo1=Ns{SvZH(;xqXo`U;~4oo zt~^tw!(Cd7y8VZ}r_-epwU6Gt)6&X+V^qwz6-bT*)>eKjqx19j^ja;^!lTkm`#6NI z_9@wS?NhhY>1XAhj*CREF}HRfVVe8sEqM2sJ0139V{WOPCmnNZzSi#P^oWNabL$wr zc(?t!*D@sP*X4UUj)tsg^10vk>t0KX1Y2*c^*tSjUCU=bH`edB1Q{JX@6du2^H#M} zWy#ZT4XI<*BNaJT0e_m*VUf-l463Q>>*Hn{Y9pcOdaiF)gSw|_pM}q z2it9TNOCS^=QI5bRzKK#f#dBLH zAJ{%EYrBh2w{7I}PPU!TCmBF6q6NL5hensrPtEF!Z(aJ}_OvWFyPl}*OJI`ZR^_%w(&muuB>h6cX#YzPMfEXcOTO?D9g^O4I3M4wLm#bx34iAuDU9Zdv5}8HhRUkK2qFUR^_)4wpZha^2fPT0T!ifVddOa6ZsJV>Z@A@GahB+pj|N!Z z*Xk}Yzm(q@tjhiZK6LF3Vhgl4fOc+gP!r|$bXe)$;4N^0iqUtr z>-{1mauKrc=^=ZO`Q_Z+V884Q%5|5RU&-$chW+;ju?5;2Ks&cLsEKlWcI^#X1AcE1 zU`@F<&}M0%6O)te4az3aju@Bi-arf53D;BL7-0PRB=#Z2y@56nx(h7yAHO#!i~4xi z_6AUo_6FWrV0G;cy7W8Ny1JXn;mT!vUsIb613=v2*OYsMPP-jzDLS|+n}A=?ukQ`& z`W^8qt>ML}pYYQB?uggacGqKX;K9Op~f(9t>5%eS=Cqcj|Y=t+sx5o|W>TD{iyc#yX&DZ_wo*Y;^4nx@@Sufrtis z13y&rn5gy!owCS_`rbgB2!~)d&eit@b^Ue?SRGQC7i9h3plp_9TVAELy%@+1l_9U` z^pTU(?hTx0wS1p;Z_s61t)11%Sxw2bkL_clYj4nHqs@!z&=TcEx2)n& z)Hco_8)|P*ghVbv_C4+1peVcX+--Aswhw>i=FB!>X^Z_SZu*RNGvbB+6gpR#U(IeM z>|@tswu|Sj>3pL4-R-;?+Eu`WJYGZX8+>=WQ$^#Jo_~ss_-`~sxb8haeMeN?e<;1L znwqC!Q%uWF13uC@Psew+JIw^Kp=Y&H1=S)5XMkJ$Mnh^Qa-VU}B>%DZ;BPc^tFVsC zhtmC^){NWpMFa>F99+BTk$IwaapogpIzA=aiz>_oGktt4?RRtt{EM=2?yON94EBv` zgqCx7Y?^2@nR#9YHF$rv^o&jjOf!#w`V}`~^LoOa_Pg7C?7Kcuh&?S3LMuUb*iC#N zKWpz1hY0yN&_25JyW4&2xOb%J8AB~D3-Ewk*muj{Xh_XODIWA){zgN$3hVf`DV|km zL)p}2J3fcVPJ1$^))$p`a6*KRwkg`*76hAjW0_oc;~W28PY4{eOnY`TKj-tJ@{Uw! zD$l{aA7H=H(51q0#n?*t#pmB)Bs|{M$-xUU_I(HF2k?Nd84M)N(D+T0ns z86~hA|0~am&SV>T{z%1d8H1`W`usm9wMXb~?rpy_8gwu^mU;AxWmudyFG9 z+3pxe%HIv?SnIY=NaypTV;osG+>X4j;JR_dHCCQiN8ZnDf9m$45v;a1k!OU01Pt~j zQ@7WQxcI)z3vnMj*mT)%j(h;yF$$?7hFbzN{bqm+8W=91VfBYbfXnr2ER>+)#bd4{yd#ioy_ZVLQp=MA}aGyV< zMPBJQU|R55#+P-&?lHd9uus(ZGBZYJf9N^SboPgyc6!CN_RW?adDCOuu78WG2gdm{ zHw;R1gUmnjEv}xnURtsx^Gt7BIJ2fX1@A8kvVIOBSf_5A7cdS2;(a#RX zeR+PNa17XSPT%MJMV?_;ANv?j`e(D#=25RBuqcoE>nPr| z)M@Xq=x3k93@EQpEbp1@;9iD2=g{k@ig`olJ=}l#>$Lqx-#twGk6S$kLshiSmHsoE zowolJj=wtlkMlI&KCoYX253h6Z61~Ucm1dCXWM^fv(p*R6R^&$#&bsdSm(YBb8ziD zZ|k_uefbv%8*8Q2>9KpSbI)IAjL!bhbA8&`A9~v971tB6&h3orj2Kww?(1s1o@X7f z^@j)y6ulwt0-g;$~u2XQ-9n99jxxea9 zw3n@|cj|Ujf5M))@JSDvRMkmD@C`{>R8`OAfnod8x?!mPRKw^suB!etvn`!hJ%+d> zulni){4qAEmJELTtVeqV{AaX<=W^f6Yg+zIwqEgv&gK1ej+kGIpZRSL;9=Zi#Ud~VqN+j@3__4%efT>fVji2j(6Oqw%hRz6>ZWr z2+T#U=Eb^lMPM|iC zALr_4&~<&xVg7p|Wl`n@Sz1?9464H?%eK7q3PGO{3Ro@Q>t};?edOe{?*ux_rF??z zY0s~^Y^!(X)DBB6p=fd5$41vXfn7G*yr>Q>QC@V*Dt9&{EzJZ!EV6=cF`mHMD60tN5ph|O15>o6wEN!4j=W9lg}MwO+dfRoi&QRq1@4n zbmD)~jt^Zn^}B#N_Bhb8E$&y^hz;(?O#3ciAN#IP6k<=4eP|^U9*%vNpLAHNkBEZj zqe6sy9B}@)h0dHy@yG8q_pyWLB;s$2hWX zxE*<4!FA(Eoma;=GPC`u+lxjpb(=ji?yGzGvM3JrCR4Z9jJS{ayMVH6u+JPwAim)5 z0`~B!p>b?o-UaN5y)N$p_Q0OHKhybYYd)B5gS?-*KlijhQ@i2$iPF0|EjfBuC!fc_ zp1R#;#Kv@lQoE<>;!v~SV|)QbVQH}Ls`Pews2-ma|)h65b4ajfRc^LyMVpqF<;f+1x(FE$9R6elKb|hcLDp^!8x#h z7qDA}v7tjbLNV%f%;){BKYyXYvqyT~j_}rZEon>d-zn^82jl*`ee9`!l`i}I+yj^a&Ao&0kRef));u6F_Z*lC{u?{!qgyrJ_R z?mzu?+HphQJxuf7tsa9Nm3{En-D8vfGn<{Z{}hhDXbMt~sb|+^vLf&5KkH+^&7+e2 zc7Iy)v(2N~>~zNS1gvwXeU?i4L46&Kb?&;n3)q4e*DJtYmv;esU}LQ`)4PB@F*@H_ z3s!vkc#*!_(*vWkKlEImcJ_y!c6!Bi#x*D~I^#Mc2G*v%-UaN@MzGcEUBDi;+WETI z(MB|1_t#N*)iGbYnJYgZsJxoVPCH-sI)aGi>;5{Lx4zM=-vw-&DVVK;^H0rt7CWjx zVNYC`nrGiP&h#!|8<65yuliGsN3StY^{1I_>AdPO#3gyvS0~_)u}QUL@GfA_SlaPK zzU?!Et>j(69{9<-fE_v;&s~~(9*u$cGrwMZ?sw^=HTo@(rNy;{1QJyV-7t+snd)ZGF zC>{IhG`#HRO2aO^8#>mT?sG2q)MdYy%fUO$Dp7o{iEM*2#;X4+XN=8>RXx8&uA%O& zs+G3QOU2Cx>F|kZon@Rz^l{cW4f^@1(9wO`O_xrlaZ#HuzVo0TWgamt{H@XBp`THt zADlsU(+|-}Ne_BXa7d-PFPeRYemw%Qn9!g9oS@^gh6>};b3)iLCpd$e`B1Z@S88%~ zf3qFwthHkX&$J!g>7-;wLw~gQ?Bq{oD{{m`e+hr0J!=xp5LqqAo$%3eM9eVe_(dIp zGtBs{s1#h0bzeuYs1e+r6EWD(Pq~Zrfmt-%e~93mu-E$mOrkm`OEV3t)2hCw?iiZp z5Y-ai=ve+FJi9ogVr`vAf-gQnoD^g`dJ*a68F3XxMrD}bbU<$e11z= zQ(f%(ysBL+my*?7d%3Tb{H(2YiILtN*DeV^bf;s^7s4X0_zeB|FE^n-g%6~{WNWgd za_MU_Jojr&c^)FL?i0D+_4!}bmU==qyQgFBcY973VVQiN#F%E1+EP`ZQsP<_p|kFS zFHu{#@onB2bJ^ZWUgITd3!9XIfKim=+ll#{pRZS=LJhMF&*w>K zrD9W6mnpb2>oQSa@H+u4$EWgA$QsPWSmO6r^d+)CAkpvg|v#nkQ z3w##1yxuvG3NGw(c)WMOG>s~=02f$J{8|el@CzFBpToNi$V0PGbQxinE)C4L-#OB_ zTF?-Mm>-rC^Y>mye3IuB@g>o-*B+&rXP1J{hn_P+1A93h?roJ;p2Xnl~dk&0+hEKkM&*OauDdS6$r~IU9y+U$y5+8XgBQ%oaDfdN! z-#n#D>EcgG^3(yRpCoR)=$rFY5;>FPsf^J{+n$sumE9`WX(!pD zQneyzX6|9=NeJ3;uZzK(_irix?|55Cym`MzAwAwi?e9-N;KBR3`~%NJ+W9~(yi`fe ziQRm!E$w`eBAqtbHR-)1Ra$qyZ*&i+J`+C0z0?$aNHtzwa_xm7H$2oy;dhENZ$Jp#!qWYt49+RGr zb#_K*Bw3fazaKC8I@>Iz%RG~0U3S3fCz+SL=i> zGeh)~ud_+j2Vk9TXvclNPQK3m-|?2bo=J~4eSY_&pXBSY1J6U+`5-ZW8m?80QtAyq@+PmBGDj=zd~aUrJM61srR7wh znD>j)&i5Ij-Z%e*E(7d%JFL?20%mW-kwNUSW|)T(QbTG;?9cx-{gBa@qM%#-v>nBzSYX-2EgAKH~tQYUQ^@m zz~no6my$TFC0x&3@TsoHIyx}wqklnM@d&>x*eo%TPU2Yv+p+B6S%^7kXo0~zF_ZTTX#8m4XeA~jkql$+9?@AI>{ z?$AR;uZv`?_%<}^BJh#+e4jx+CKDgWxYZrW>L2FAx7a-IgWj=j^vDrB*K29eOLA_7 zUUaD!{O(VAq;PH}Wqir%Eg$De^es%WedTgrE?K>mDLP5+vxkUIO8424+-FZ2Us8S3 zRH#&*4(C>Mb$j18ORDb-(eY9Lc%Gq1UVMnS@H%hmqy9PIJouQeJb5q{{>kO_F<)hj zR?>J%@%~ZLcuEnUFTL{`M!89Adq)8Z?F{KXL$s2gTfw`6jdLr0_)mUrC3T!h^BL9e z$?}=LqHD`~-Qxwji5b=p9Y6VuL{gkdz14-)O7g{2vim%K^4Wpr!B1Ry@nGaLUF+`m zZT#f3OwnqaeKWIGZkv5G!-J3YIzmcuX7zq1@t`k4EU&uy5!dVgQ~nkuU&p1%Uux?a z@R4L4_oML9wtSJ=8oF)yA~jk**6UvS{V~_;KMEg{ldsp4@R6E-gI<#LdPeCbt#_5> zqdv2F)h$Wu-4yZpiZfp|sLp*G=j9C1@>B1odLO_~y_+gdfBL8SQK=9emm65nnjOb> zf7e%@!=7sfX!*)>prup<>ir7L`+7-mfcGWEZ%G_rK78dlPkkJKJeNUw{^BjQxzb;} zrG|5gpZP}aN9!)3`=2}?v1|Rl=#P6QEyj}UPRhleMqD0PI))#*(lN(n621*T%nZJy`leFaPZ~r5G<%8t99z1- z18w#oZ#vks6u-Y}&flg-iA*bz)b_njk{_l9enoRs2YkEBB`{I7& z1ugM=hXaeO_)C$tA{7N=|C~-<> z29lR^$?z$0iZcijQsUH;R+@2|F`TMjrjTN7ewiw)Nk5z$71Lv* zFs%L-j=nhXp6``BCf^6rcANt#H(tgye(LY&0PK=9VU6{H`b%0=f6Iqj(m4kx)(4rv zXVj}MuK`KEKG2V#Bs~Wx>eT~F$Hn(K6;$Z(6DNjWMrg4rkJQW?qUQ_+>?4q+$Jzw3pu` z&1V^+<0qe)i`Z22nJ%rnpBeid?shuRJot$#FCGlPOfIjVe3mI%ZL=@UWctlUdH&7x z(}O8Ix0h_ba+fzVJm4JQ@wiWj_Y^3zv--Yh;z5xXey+dQgm}Ub}&h6aEkI(YAb%+S!1%<%`s4;e1RR?3~eafNijI#`u^_`WTnKzomSh z0rXD%5$PqZcT+sKm$cqZ5udL(^HqapXkN|`EkE^cs%Q56)VrzT^rwIGinlH20LA?X zk2)}e=Kz!DInQ%wMoFNb!%Fx0+I*ZfXBVF`l}0oBTe$!M(d()wn&-v{b+JqGiOb zu6=j@Rlm#>PJjB(`1%6R(z)0=GqjX_6wp$39M%1EqNVIO9;oMx4o>tbl23^x9L7-B-a-|3O!|gk%V4q{sg>oeUVYT zYTc5;)|jnsNfp-QufA%G6z7Kw;Z*CERQImbx+PUuvwkQ~E3<{eF)wkz%UrnFo^v(D zGxjzzfYTJuz^TkM>hb~=OvdB+N1$hlXHR-Ld z4@COaX#X4~ujkI9>w((SbRF05y-ItA`{GM*)qPMX(1++&5#3PN7(K2Tjfw#s12HMCTpmPuUL;o7Ie=)|`oGcoDCD2kX4+Ub>cLebE$*aiD z5%2Mj_Rp1D%25Fo*~k#Z+oSye(}`fp0b}p3*48qaS@EktqCs1yP+iJY@w@ec?vajk zsWQ)s3OJ$*7?m!baV5HpR_U(No)rpP4jPTxb9@!fE(jN7$8VH$Lxb`H-JuNnr#-KX zD2No~6yz4Pi?6`Q8tnzy7rzbH6SWr;V}<(e5D4}CHt3J?oU;OTSYj4nLY7FNC-DFs z7KEb-^m0f+II^*E0tQahUb0ZW2b6WX(4PZcp+FsykOD}h3mt;4WeDgtoQ#3vwe^y& zkoQlzyx9Tx7Ua!#5my2dRR94Q?42FxCgshJ>bT{QY+cy#+P_Ui?_;8X1x?6mrzu30 zaD)&rW}1+dXqtx*O(AHb_OhTUV&^hza9&o<0`fNEdE>}zH(8e1$eTIH=)aX6$lNAa-0#FfR-gE zRb+aR$KwkDLw$b7btim>(|F9U1-beJYelTjwZQH)*R~6k#>8BkX?%@ZPzWjFcM-(% zV|M{Xb_axF_s%4%w2N&>qZU$WX(}z`eS2;~X&04HL<fRefEl>dPya3n)?sAQXKmv1J;P zh^-i$gi&l@#}_qg#|u1V14LQscBonNn7NP>}^z+;v(9XO?iGNVq^P==ha zC+WjwT$R?2<&5750UNcb>QXDv=Et7|6p052#h;L0q(9k)G-`P&t=s(45>P}75DG2C zi=<^5lJMQhT0RqG&y}<39iS>=<}iZ)i1V89@*7(J&{Jcg=2C!(H~{mu1#+-<3I`4* zSlVw#ZgUvM5u200Es(3nCun^}mYyuqrb;BW8ONs+=_9bvgyE>?q?T>LE`xu9L>nk3o-Z>QS21%x8zuF|AP8+g>kKCtWu0N| zCVPo}3*>*YtF^yT>!7Bk`@p3DJml5ipcN`c zd5Ddo>(^`j#84yxApF|I2}W5zsoietC(9UpJEBM!N8F~5uYwfgHHStq{~_NFC;Qm< z3zl)MDgjA4U=jw>=ks*nkOz8$UR38vqNaKIgr6DslaCv(bu>x(n2^+=QRf$aXqQve zXJGAmjRf@jasp4(C39g?QMqNBA?jpDe{njBX)sD(Kq}c9XQwtd5HYhgqrNBr=f)3dX8(< zN>nk|3o-3&M9(7RKxC2AYaVrjgPgMFHw_^<#lE@ZE9tmKt&1w>D?(0tnn;S46~H!(aBS4`3-X~oPl!A!agm{b$DCi% zfjfwFhdfI8#R)a%JkK$cv>rmd+1axT!m|sqXUE@#gl_GPT2C=vY{z_vu}*D@-+JW} zfsE7vL>9kI2d*>GmHEYnTCZ2#`X_0b?Qdhp6qZg9|p%u}`@x*pv6$7^LmF6r@mAedt#aZzGhRE`B;Z#dx=yGQ^0|&_ z(8^gp5u)EgIzQ_!1|lf{04a1B%pdmqbfWHYf>GAO)^6h6v=+AZ*J!<^W#aq5w)MJR zJ4E&ugyX?EG{W9Ob4rBoOYwevVp7oo^=SprrfEfA6z#1YAA?M}$*(qE+=%F@J(A&B{^ zLF=bT+D%AmYa-gZU27frzK+*k3`ep8jVf!ZFp#xQ$ZAEIkkzGsl-8f+nH`=T?*(aW z4Gmg_B5}Ep*w&Pg*UKjvisS_nRo+x#6hF42Ovo$!*xElz8^F@c3eSr774ljd8nl6m zy#0i{wx)!%-__7scLSE_1*8XbkFqS499|%b-mkrt=Mb=6otF0*^ ztCwFg6v+xCs;sHPD86h(nUGcbvbDcKJDjBCoEGyUXfh9h}_MwQpgubp5NzqWQK z!Sb+Q(^4+p2$7sy8?+-8*~bgnZB2^oUcSn3Bse)hIHbCuFuYDKcaK#a=s#eH(@&nSn-?IaL_QjQtv_H?1fWGQ0FQYPCXUv8u=OXs&Xw9a@pH-xez~gxx^YkK#^R)Vv#Ey zILK8e__m?e<#Ow9)JCatEfR9s+jP0S{7^uVT)<+HD;+q+4{fM*xuhT3`Wv;;s$80o z%igBT<>gNTisS+oi(KizDSl`}t;;3-(AM9m9j(f>M95`t)8+E=Cjmur0gFYhbl?;} zw4v7Jl749GZ`8)9a$PCpvbX7SdHIuoBDsLYB3C+aiXYlg>vBmywDmV?^{QM~3AyZT zx?EoVB%nwxV6n)R4xHkLHq^RY(hqI@joMgMuBAdQdz&tomp=(8k_%WYa-{>O_@NE8 zE|>H}TYsZ=j4Ic1A(y>Pm&?na1Qf{yEEc)afm8g@hFX_P`k}4AQ9D+Z>qa4$y-k

wQ$;Wbux_2oa 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≫`+#UvjTyfrji^TVrVi5(Lc?=u#zzxk-#t} zRSaVMWd_DZ#`v=Y##7pgQNZ7BP}oU6k<)ZRg2LoXQxN$6nV{IeHK*w}33BreG&zC) ztpxe9knWA`+X-*X>eo*CRc=u(yH9ROd;@t%r*KiYQ@i3$(f*}H?TVr+I%PAmqHK0q z;YCH!l2bcHi=svCDBw01i-5y3vu~1K`ykftqBy9gcn`$hBKMAOqd#2+_BAdlx*6X~ z2DUdYx(@EpJZM26vKSbSyxfGLn>w)D;Im>O4Y0B=dUv!8x z6(ueT`gds_tfTH<(R^g$dl_SDD#g$+A!&Y<6tSr3UAh)uS)O2g073A4_-o>8BE@_2 z+cY{FX?nZ|#m{#79w~{xBR(jwqLJeGd-T!un)49A+4Sb4rl)A+9YxtCO>c`YU7EfW zUphsKJLUARDvA{4tjLSx#NP*e>;w9npBw*>KH9g7|A#)}AJLyq;cdlzw}tv{3l!y$ zDJTOJMaUFuI`=CIBM)a$!lSGjIOO6$h_WTc6MerV5Tcx!1v^oFiF1BM!B~(~7>&@2 zXepV#Mt?|uCC!@>R|SzS##II7?no&1I`wki3kB*3&)Kjy<|@iyS;SkEIB5>jm&KU7 zBKgJb`*+Q4w<3=xgB(uYIq?wnw<|fdeY;#@`<;HhLa~9+ZLeSq^CzwkSFgeI5k%`8 zqSZ8t<}I3Im{e>C4I3c%?-z*;rK`LgAr1Y0dbEP3tSW9xpsozYc2P@gKhp`uKBtR* z)c9GVkuJVWTr`Fk4;zD8SHnl_D2%4I;i3&6v1-{iLbTx{HcYnFh&Fu0hRe2E(T0!M z2-#LA+VBypk!>SI8$M#SvTc-T!$+)6wv85T_=t^^ZAXhXe8fh{wlSg&AF=@a0oM^*G>{!`$ylBHmtUtrIftyZEVYM)cjfM0Ga=os2pJdR4}_%($8&22fkqA1+fu5T#5 zhsNyd)|Azg66Y86eG%n}EUK=8R2jD16eu9k=zGV&jeihzV;~R-{Q1E^yD$x#8*Bl| ziuU4D$kVTxrjzI*TVF1;CHl^jaQWgh-p;)%YINB!<{5o!EEx0A6^ys14_3DP`dl7Y=zmOYejeka; zXCf{?%96T^smX&BZSPTg!8VWHaFIX1RTY}lG&4ga~INhrVOoH#U zfMhhnxKA5HX@{xi(kF6LXZo*y7w+uG#QK$iLO;gZuj%LDozoimMiyxn0UrzE3jx9h zPNGlz6S+G)ps?w22>l!YiHGPSyuMI2wl7U+A`~l!gHh)-I788T5ivQAWfa#vqWI|z&s7`xpn)ozL+Ml0K6G(d;(~aFy$+$zvycmv4Qb`R zQ!uhIHke@YwgqzfRgvr?6$H5Fdltc;P4LaHlZEN`s7PcnD35nY@{2o#bLd7Xm8{{| z8#JVSyNcZ0cJZzBwQsut??-aucN0i_GhNd-9#T=5Tb!$GWb|Lnc`F=1xYC{qy|a&= zv?0chCSJszTA1DFNc8o|tH`EZfdTaDQIaD^yfM-OKzOhpg?AhC8G`dGsNq-mBcEyr z%1t3EL5POBDEA^uZzPLCRCi}^TdNdTrvW*tFGH+`RzLy6Ai)&l=(6#*f1-=GbwqwNC_+HYQD}dfX(>J2~ zU3~vmeE$UBv&5K>@jbwZqp#mt)B0ZwDUZHBrMC4;{>tRO!q;HSrx+X&fV=TMTYUcy zzVE}B^83c#UfcQuz6M)9Li2v{eJ8$$#PA*X9u(i-$M?M$Q~uJ6$B$_J4qt;UAE5br z`qQs5*zzy5m1Qjs(6n2ow34^7}4g9DLAWoatEu_|`dyE9AkOqNY3;x`yv( z1Va(wow3u25c_y2kVkOHpTQDxL8V>iFC_UZk^Ch#fC&zAAfe%20{DAc%5e{&*Y}>$ zuk&y1*FrSQorEMc1eq%sS~`=Q8@$HOhEkpTuSWk_=0?)$#>VxrwEbx2<#`=o4Gp1} z6*jhlN&3=lM7H<&#i7=@j6e}J7|06LOvEp>bOt7V+YUk4D6$^4a; z`MSr>CsI;x7N3jdSu@Yd`zs6sym&PhLnUgwNXg*{3f; zzwlZ9YV%pp0?#@xo44Z<76{Yu%?1oAAGvc`y;* zLZb~2xE!$hSoUukyFnkj%{2B(G1i>hUcp#G`!02Q`HE#+TYTl0M9SG$WRKNXKpT1o zk6ymAQOfcmjdSu9Yd`x+v(;DF7nTwV;VZU&_N8mkFMNfE+I$7Hz_ZQ^dB>|@Fa8N2 z%~#|buc428WjA^qt~bg4J+j|)t=nJsmDhPN5&w!t8@_TgVD+)Oue_m;{mwM@w;0RFF4X3+B^oAt(ZN6P0JHwzA=x% zWh*L*VADSmW4<+y!DTCEj$qTvi80@q$KbLRb4Rf0!^D`q<}tWz#e@-T>QSuA{=In& zE?Y5C1e>Z7V}3A?!DTDvhHa6)Z{-I``Ay>!WA~ZI;<6PjO5D>7QJ>8r#TTAHb2aM_B) z7Hq00(e)HIkHKXt5*l=XB01PJIWacdJQkO&NEX4S8xvz9<}tWzMR1ah6Jv7BV{qAu zXbm>eSrO^Gx#lsrY(>-tneNb6T+o2^QqGV&xGmXxI8K ze@0v1{?Oh2Map1e#oDNtyoC}3yKOl7IYE=u!aa0bZ*5u zC0KA!E7mT-g5uU4{8`evlRvw(?&8m`tyq=>3%a#psS+&c-in1&u%Jil=k(bxG^j_Q zbB&O#v>Por!XtU9Q`Y7C{rk(dv5&Z5qBCT2#R{lPFDtIo``;O&z0{?S%uq6#xDo#)mH%{;tU*UTBqf|8li zD|XbB&D%%Xa#GH<K;lms8!O)NGMlmPnm3}R{LnL&`ShIj z3O|RQae~UvA>R@0r@uU=uKfHF4RGULwPoBk>g;J&eoCj7-}BpNY|NdF(ml)(F#Gwzxao1IeB%|VK80)aZI8$^eg1v=mN^SW}H9;DB3vbkV&sg*1x z8>dWs^wqOdQB1Lw8J;c;W|pnlr82YfD>78%(qG${xuv}OVQ*PgCVa&y4Le(@L@=}B z=FPRV{dus>YmRy6yBfu7*jb&tmoF@(tx%M>tLk&ir&y__<<7k(?G-Lp)#0 zg5m}!yhE+XeDuwh`A`p)JD5=D=bc4_z>@i#`ROY7;cR0}n?|g7AD+w+%~5{LJCD^u=QZT84$M$ZD2o_%W-mYN ztwU;PW_c7QG|vf@qw32C4_-HlIsyT`^y{v0!ZF20GB|m(*9W!b2~I?Ac4B5KCF0-O z+_<%NXe!L~P?*6yEhbE6hAWwt&INU(il=LlW@@-vcJdC=ms3nVx3GrmKA)QDsW9_s zuQ4h!?3Sc}hfQp->dms*B$ zFmE_1r`S<98_G|wq0I4)PhLtDUd~uHO6rCy8!$w3_WjaOXDnM&Q+C5Q)Ykp5Gl5-o zR*|;<`@_lBj`FK#pPLFlWs0q^XcCUYl?_)_;88fvqrI*`JUJOkCO>;keAC>H&a0__ zV@MsWD2GcM4k<1}@eMOnUHOWeZ?2_pq#SPhxbNY%0Tfc54cD=A@6piIMdrC+{qpT8F3+oD@4bE^*VQ+!JXafePBQZJmlvX{ zT2sU8CJ{SM>&aUj4Rg(J=5mSWlarXD5}RR4uXs=8go{nNj+fQhyhKjVJV%=* zYG*GwRA=U|2oWlp*gi6Fc!ixjaLTxTaSEQoQ;$l;W%~(cIEn`k9&BcYiz!rDB8qup zbckY+QYiV`9EP-2Q;tOK&tdxO%zTDOSuB~sc8Kr~-b%yv11&0N)Vd^pF8Na0g9zka zKSrsiYQ-A6A_+5r!1h`Z&+sTpHBK`ToSiyS*bs4MW`H9zSnG>2^Q+iDu=;+zhL@Ve z%ux>?WA#&NopIeRn~l`s(&W9)oD6j5gr~XjkM4uJyja7VMb67eE&*M)L1bm&gO|e` zd3o_`T`MbYKD3TpD31(h;@eD2&63oz6r!zBs zB9~e_Qf20FM`q6WR*xj)9ooy-QR5Lw6kinVZS|K*M@~>jikRkvheZJI=M~cgXj`*p z#&HKLj9}YEq+PZTJ1(z}1`qzLaNIJke&_sWuhkM_gUx}aRtEwrv9)$x ztyq0HZCEigPK)bx#~o2arF&OQPt#DpGh1|D#k}EKEarkE6kg1`4#i@Hgsn}%3g%Ic2XgsZD?*2B0*epxR!IhYxLQ{Z zE$EFk39qbqFPis%?;CqgEtskmQ>UzFO)_vM~j>^xxrvkCx86YY{p!_n|luZh6c*i?D0b1=bD}^4K-weLvG&>FhTn6n>0)94Zm~&|aCSjRilhyPUi??v!g%GkPoG|in>(+I zjXY_qTEPq7Pd0ppiyFQ$NJTj^=#w5kBW@=<)T4ggrc~Z%suR2n9(=+mF6YFYc`@w7 zyD`hwT*2F(i0>Cil&!<2h(oq5TO;n^+U|Ro^R}YXoFG-RawM@F2uRh8)Y&<96juvY zb~@kplaV5oGdsMJlN+d}@MeEHJh#GTDYg#@fDV%zo7pLqON3DhKZdCc_0aj{I@XL( z4`&sJ%Au@`&P$Nq>|v#H;e(&iy6e0sR!BlWTvBj`J@kxDBRQf3KdzhO+~II1-lP=| zex$jMPNJT{gL6hI>fwxd$$7)ARw?8A{|2&)* zG9f0q1ehB;$0+R3F$OtZfvRAK_l^v9xP0$9acTmu2=>Kgh(yP`8L6>ztilfRzt9e| z!^W~`hy9rJWHS~Y6>!>DOU+mf3Ol%|B-nZX3(^ixRy2jziA@(~7S%@iXTQJFrXA+f zVWUfUjFay;&R53?c3yh%mXS11&XdMM`6t|glVD;(FRR4e45w0^ld?mxD%c^25#_rt zJ`DNvc-E}7RM(^Yc)?E~@GRzX{f;FkWey|WhvnO-h;8ttU9rF_^HlMU?eR#PE%_y_RlMJ(0zB_WaG`i!Y_#*#GvI5)qxJJ z)TZa*gCE#~JHu4NQT#sTypCzqd7=6k*vdYlnTJ;cdL7uUj^xwpV|Li*7Ou8Q2{T1uAWF3~A z8)}@7vAF3Q z@XZ$9C#FnXTXwHFD#kbT)%X_SlmT8&5xh*FUa#I*MxLi73!iR8f~J)*ox@^3J^O=k zw%Fzlr`sHSXIW-nljHE(_od^zn;m$9V1^<_U5f)dF~gf}JSkvDNi4*jN`Jv6*yHeV_9u-J;7c{lR+I8nh0;2 zzg+JBFD5lQ+{wgzgvyWhTrB?UID4Pw$P0%PmYiav4Mz+`+T$6^K1qWYhdDh(UM4C= zGIrVI))r6OhR1O6^8Fvrx4JJe;&eWZRbkto*lw#KsxH~3e0a%Z;-d#S#9vfC4q-m%d<-_O zN+cgUuuybJU}bN^Y)*>+(qv*mWqeC40uoh%jGZaON>5^i)<}lKqC_jEd3h1C{b0$8 z+xn<0l&E>Zct=gArslWRkLxiSypI zWvmn@A6bi}mH{6*7rf+Iv)b9T2~$iKqsx}|04hK=n zt_Cj-#)HvQ;pIH=Qb15N8N()G?`01MGVA55*cnn|JPA7JrSyB3bRVc8+cz_Gf+g34 zG7F4!AV#{3QW)t$n?SayCx8R%C%@Mxft2$n%@o~9g@|Trp%my#? zsuEG51mqGC6`JIQAJ<_6m7=C}P)gY}IBo`}!8zcih zHj>*Y4qiGDRKHPthgs+a%Xq05E)v*Ali)9Wf1KJ-t?K@=2wA5sdXbaiJn+()FsVy; znuP`r-UyF-QU(w&mnq3vMK8MqQSMMR5Uk;?#y7j)O8cA-J`N%m*sN^#uwqcC48|H- z+|Z<@JMsq|Pqgu}AGdA}&;5(F!#6KdCr#j`*qU%f@@HQ$l7B5GVW$|niyp^K5)~=gvdx*{X8h_Q+kc#476F?%i?D;rQ-kz{Kzl~ zf8+FGXF?ihKA7kt6hh}TWKzKv4~+B6k$iL#NNdYZK<;oz?q$Cfb&M-7!~q|*#Dlqn z%9WP|;H4`Qqd$6ro5?fK6+1phxkATcn3ts>>BLLKmtI8i?#0VO@X}4_g*S|OW&5gV|<<&wWv0w}LjpOReUpl_Gd7I|~!4H?V zL{ZvFl(xM8#nnD`SH$fA;>DKaz`dU%cyc4xZYSxu7wWv2jtq&ilF!DIjm0q?=lu%( ze1qL#oR14_f3nlmOH+XI7mzfphcqohgi?}M13T@hA<5(#s=3Z*Z_nKl%zX zgM%ggip6{X@+Hm?;iNZcg@jnKLi$X+1kbpb9t^eM)k~okymTqlvW@=6x&a37#?#;N z6dG5&>=M!yy(x2JtOxbt9VxWu!#lTp8&i(5{Sh4KuBk zk#1oow=&W_%oJDBTa^Hv7tGP|=k+7j_|;XRivB)nYgCOQk}Q zlAHLAQlUtfoA`B7;Ye4}6^?WhUExS~(M3P+qrVFa?}M5~as}xPg?IxF-tkOq^2>1g zKm~dIqrsd=&aZEy*HP!?;9b<8>9B(db~<1kA%pz(GrZnEnv)yJ{q=49B7a7ZBWxHY zc-S7VK+r7@RS&%uetQnnML$9C|SE8$)HIjO6)=m0?G<}>08nv z5F3DR^p3|$ZlO1W#fIQpCEgK6?~SX%#d&nW78K?awoBU87s&W!bv5*DK&ZHXR*Ca< zRj{lbz2Yz=Uu|c)(i!0w*fGjcDt|}o{ISb~*=54QWs-FQ_za=uw zz>jws%l93ss0QLygxeXj@LPRezP}#{Xo-xQ~QAamR93Ejj>q_ zK(qjZ-{Jv`$Ye4za%O3gcquE<7(IuHRwSJB!FXnK7-u}rVVu@F4ay3%Bifi(gYJw(+jVy74MA>>n}k?3iyF$L}b9l1ZsZ|YWF~= zf#N-=_>I6?!g(%i7ca%j#g_ew=gd(2V7kidm=)_zpJeW<=#4;P2)$pDJ6oFRwLLL# zC-Xv^ae-an;_P<05SwiWf8qRAR!F&4<~W)AY<dML|+R zniTUJ=EoVNC+!}pT{_3;O<&Q9vZhXDfq5?AEO(E%DoQF5%%(@eT|%i9G^99?uf}p9C6OjDMPCcKWgxhX@ibDs<)lk zg1Pf&&Y7lDKWW;0l$2l&|ILu&k8V2Wf|+yA9XM*{{5j{$t0*%!r^DEw!PAFMKVtgf zhff_k=$yglj2n40{r|bWt0;xgyhXG-o=iVE5f6+CEC|e}|HT7y(G#d+zGyK-ukaTC z@P4+yi5cgJe#0UVILM4v%8#D#(dW!RCnpHn?P%E==zu?q>2MkFVkofQn_ zQtxSn^d_@VFc^-skK|=%Q{to1p`vhh$H>8lk36wc&N=6okMDfSrPn=J+4OGjJ2&2S zP;traqb9#Hr0<3$%kTK>At&GPaGwQFopk;D%O9HZ^lQ0m29IvOZ0f7ist9%GER`nc z2GCk$Z`~5eC;oy(^m@D6z;}0N{r`XeUjqkF139<6Ubtq>w>`RTcx7Dur;j}G_|*Bu zMb*2fe^!3@xZ&qsJM!|%?yLX%{ck6)n)%WBhh6Z+nA(2%kA8mot#3|!e(3M-%3Zc& zgt>~zWsFK_ph(3`=6pHD3AgSpZ2faR|cm%_qTO_c;=Eb zBi;85Tl>swyRQ1+&YQvyzP-Nl_kXC|dBFz{{`!Hb<*j$E-#%<)-NlXXk2`kP?(L7B zeenqeGd~&m$zsdD%W?+XxN6<$)iZAiWQF^^+w%R>duOey?p~=iZocd1!x#Kx>F6gf zJOAou*7ly$rT676n}&=ldu#2p@6G8(KiskD(tAS_BM-Nq(e34-D`(Xuz05hbYmaLV zes|@{4bvWYu;z&1Q-=)v`o(#F7}@Kp<8Lfz-1e{aE1vE4!i;y1yQ%t(6As@#@~EfI z-Br2j-W4U6o%l@ImX&3ue_uR$^R;U}?loi6RW}s2+kMtes~5cgTGhNqub%PmpFG;F z<0m6SgRVR}#rdr{ej_wOBc=+M7zyP@cheSed6 z-Q0#PcRYV?WZTh49lm}@hhCvqmOQ`q?R6K-e(u!sdSs2f$)w;VhduG>U3301p>Wvo zug@*I?9~pdYn~kWr>Pa!T=!Jron1l`hi650zjjA#agXoDwaglMd-uKP{if*SS8spi zjLA0*>9+CetB346x%;DYC$H#xT;A40#s@weanHSFd&(~Fd29cPul%R5TlTTT16h$@ z?)`Mm;k$d?a8~#BrNeq(_jzc4;Kfu?tNEIC+p|vyT(;(g{?l5gzOdu8qi=jD_~CJ*HqYUZDGq9 z+eXisGvLMD8}jq#FJFE6hQD;XWk$b?!reMN(|YTUqW$!n2d|EN;-P0xpS0y)3ufsV?)J!ld%oCu!AYxf@4u<m%X~@ ~# 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