forked from MoonsideGames/MoonWorks
				
			Compare commits
	
		
			112 Commits 
		
	
	
		
			instancing
			...
			main
		
	
	| Author | SHA1 | Date | 
|---|---|---|
| 
							
							
								
								 | 
						bb7e45b9a3 | |
| 
							
							
								
								 | 
						4cedf768f7 | |
| 
							
							
								
								 | 
						d986b3013f | |
| 
							
							
								
								 | 
						42e3ac91af | |
| 
							
							
								
								 | 
						0df2944ccf | |
| 
							
							
								
								 | 
						e50fb472b1 | |
| 
							
							
								
								 | 
						df3f38a67b | |
| 
							
							
								
								 | 
						eaa9266521 | |
| 
							
							
								
								 | 
						4dbd5a2cbe | |
| 
							
							
								
								 | 
						2e890fd696 | |
| 
							
							
								
								 | 
						385783a846 | |
| 
							
							
								
								 | 
						450b08cbd8 | |
| 
							
							
								
								 | 
						528fb7ac7c | |
| 
							
							
								
								 | 
						fcd08fe231 | |
| 
							
							
								
								 | 
						e961a18a83 | |
| 
							
							
								
								 | 
						772a0378bb | |
| 
							
							
								
								 | 
						40fb313d12 | |
| 
							
							
								
								 | 
						a736ed031d | |
| 
							
							
								
								 | 
						b2a0ca3515 | |
| 
							
							
								
								 | 
						36a88afe52 | |
| 
							
							
								
								 | 
						6c93350f7f | |
| 
							
							
								
								 | 
						352bb34f82 | |
| 
							
							
								
								 | 
						de7d76c03d | |
| 
							
							
								
								 | 
						18d92aeec8 | |
| 
							
							
								
								 | 
						e616b0fa62 | |
| 
							
							
								
								 | 
						1d27a9e4a4 | |
| 
							
							
								
								 | 
						514a0bed29 | |
| 
							
							
								
								 | 
						78252d1f6c | |
| 
							
							
								
								 | 
						daae1a34b9 | |
| 
							
							
								
								 | 
						2e5657789c | |
| 
							
							
								
								 | 
						0c76c568a4 | |
| 
							
							
								
								 | 
						abdcac1608 | |
| 
							
							
								
								 | 
						d8064862bf | |
| 
							
							
								
								 | 
						b223c31c8b | |
| 
							
							
								
								 | 
						dd79090028 | |
| 
							
							
								
								 | 
						653f90c29f | |
| 
							
							
								
								 | 
						402c26131d | |
| 
							
							
								
								 | 
						b026b9e81f | |
| 
							
							
								
								 | 
						e0f05881b0 | |
| 
							
							
								
								 | 
						7e18764942 | |
| 
							
							
								
								 | 
						1bff459be6 | |
| 
							
							
								
								 | 
						be77e8bad1 | |
| 
							
							
								
								 | 
						7f6b6a7bae | |
| 
							
							
								
								 | 
						1de3c73bb7 | |
| 
							
							
								
								 | 
						e77c87c772 | |
| 
							
							
								
								 | 
						088e7c4b6f | |
| 
							
							
								
								 | 
						f298a5ec11 | |
| 
							
							
								
								 | 
						0cd2c799ee | |
| 
							
							
								
								 | 
						81cd397013 | |
| 
							
							
								
								 | 
						e73c7ede55 | |
| 
							
							
								
								 | 
						83f1cc24db | |
| 
							
							
								
								 | 
						1d86d0c210 | |
| 
							
							
								
								 | 
						dbbd6540ab | |
| 
							
							
								
								 | 
						74ae295036 | |
| 
							
							
								
								 | 
						36ddb03d8f | |
| 
							
							
								
								 | 
						bf3ad0c8b0 | |
| 
							
							
								
								 | 
						f761d4f76e | |
| 
							
							
								
								 | 
						4c731401ff | |
| 
							
							
								
								 | 
						071518732e | |
| 
							
							
								
								 | 
						1adb76d5c7 | |
| 
							
							
								
								 | 
						5ff7da927a | |
| 
							
							
								
								 | 
						0fd3365d1d | |
| 
							
							
								
								 | 
						affb592c15 | |
| 
							
							
								
								 | 
						7e79e4a11d | |
| 
							
							
								
								 | 
						fc0937b2ff | |
| 
							
							
								
								 | 
						c83997609f | |
| 
							
							
								
								 | 
						b65d4e391c | |
| 
							
							
								
								 | 
						56bab545ba | |
| 
							
							
								
								 | 
						2ae116c72b | |
| 
							
							
								
								 | 
						bd3e70b096 | |
| 
							
							
								
								 | 
						b1fe7f96b2 | |
| 
							
							
								
								 | 
						00366cc9d4 | |
| 
							
							
								
								 | 
						3bc25bc3a1 | |
| 
							
							
								
								 | 
						496eb670ab | |
| 
							
							
								
								 | 
						00f4bfdeae | |
| 
							
							
								
								 | 
						adeba633e5 | |
| 
							
							
								
								 | 
						300ef9f88e | |
| 
							
							
								
								 | 
						76684eaa33 | |
| 
							
							
								
								 | 
						c037b4cb69 | |
| 
							
							
								
								 | 
						5df08727c1 | |
| 
							
							
								
								 | 
						537517afb9 | |
| 
							
							
								
								 | 
						bd405dfbf0 | |
| 
							
							
								
								 | 
						2d7bb24b5c | |
| 
							
							
								
								 | 
						0ea60a376b | |
| 
							
							
								
								 | 
						e3c2f0e119 | |
| 
							
							
								
								 | 
						3584e670ee | |
| 
							
							
								
								 | 
						5a2f7eadb8 | |
| 
							
							
								
								 | 
						1e3f04235e | |
| 
							
							
								
								 | 
						dd06205399 | |
| 
							
							
								
								 | 
						1cf04a7279 | |
| 
							
							
								
								 | 
						3bd435746b | |
| 
							
							
								
								 | 
						8134761e44 | |
| 
							
							
								
								 | 
						8209051a3c | |
| 
							
							
								
								 | 
						80f3711f4c | |
| 
							
							
								
								 | 
						3a6b73e637 | |
| 
							
							
								
								 | 
						12e7e6b9c1 | |
| 
							
							
								
								 | 
						455f4048df | |
| 
							
							
								
								 | 
						1f0e3b5040 | |
| 
							
							
								
								 | 
						f8b14ea94f | |
| 
							
							
								
								 | 
						472da0edd2 | |
| 
							
							
								
								 | 
						bd825b6c91 | |
| 
							
							
								
								 | 
						515c2ebbca | |
| 
							
							
								
								 | 
						86322e9373 | |
| 
							
							
								
								 | 
						e9aacb44da | |
| 
							
							
								
								 | 
						5baa1d7b40 | |
| 
							
							
								
								 | 
						f673803c37 | |
| 
							
							
								
								 | 
						0f78cd1a0c | |
| 
							
							
								
								 | 
						36ce74b58a | |
| 
							
							
								
								 | 
						40d12357c0 | |
| 
							
							
								
								 | 
						e52fe60657 | |
| 
							
							
								
								 | 
						b39526ca90 | |
| 
							
							
								
								 | 
						88d9119830 | 
| 
						 | 
				
			
			@ -10,6 +10,6 @@
 | 
			
		|||
[submodule "lib/WellspringCS"]
 | 
			
		||||
	path = lib/WellspringCS
 | 
			
		||||
	url = https://gitea.moonside.games/MoonsideGames/WellspringCS.git
 | 
			
		||||
[submodule "lib/Theorafile"]
 | 
			
		||||
	path = lib/Theorafile
 | 
			
		||||
	url = https://github.com/FNA-XNA/Theorafile.git
 | 
			
		||||
[submodule "lib/dav1dfile"]
 | 
			
		||||
	path = lib/dav1dfile
 | 
			
		||||
	url = https://github.com/MoonsideGames/dav1dfile.git
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,8 +1,7 @@
 | 
			
		|||
<Project Sdk="Microsoft.NET.Sdk">
 | 
			
		||||
 | 
			
		||||
	<PropertyGroup>
 | 
			
		||||
		<TargetFrameworks>net7.0</TargetFrameworks>
 | 
			
		||||
		<Platforms>x64</Platforms>
 | 
			
		||||
		<TargetFramework>net8.0</TargetFramework>
 | 
			
		||||
		<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
 | 
			
		||||
		<LangVersion>11</LangVersion>
 | 
			
		||||
	</PropertyGroup>
 | 
			
		||||
| 
						 | 
				
			
			@ -15,13 +14,29 @@
 | 
			
		|||
		<Compile Include="lib\FAudio\csharp\FAudio.cs" />
 | 
			
		||||
		<Compile Include="lib\RefreshCS\src\Refresh.cs" />
 | 
			
		||||
		<Compile Include="lib\SDL2-CS\src\SDL2.cs" />
 | 
			
		||||
		<Compile Include="lib\Theorafile\csharp\Theorafile.cs" />
 | 
			
		||||
		<Compile Include="lib\WellspringCS\WellspringCS.cs" />
 | 
			
		||||
		<Compile Include="lib\dav1dfile\csharp\dav1dfile.cs" />
 | 
			
		||||
	</ItemGroup>
 | 
			
		||||
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
		<None Include="MoonWorks.dll.config">
 | 
			
		||||
			<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
 | 
			
		||||
			<CopyToPublishDirectory>Never</CopyToPublishDirectory>
 | 
			
		||||
		</None>
 | 
			
		||||
	</ItemGroup>
 | 
			
		||||
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
		<EmbeddedResource Include="src\Graphics\StockShaders\Binary\video_fullscreen.vert.refresh">
 | 
			
		||||
			<LogicalName>MoonWorks.Graphics.StockShaders.VideoFullscreen.vert.refresh</LogicalName>
 | 
			
		||||
		</EmbeddedResource>
 | 
			
		||||
		<EmbeddedResource Include="src\Graphics\StockShaders\Binary\video_yuv2rgba.frag.refresh">
 | 
			
		||||
			<LogicalName>MoonWorks.Graphics.StockShaders.VideoYUV2RGBA.frag.refresh</LogicalName>
 | 
			
		||||
		</EmbeddedResource>
 | 
			
		||||
		<EmbeddedResource Include="src\Graphics\StockShaders\Binary\text_transform.vert.refresh">
 | 
			
		||||
			<LogicalName>MoonWorks.Graphics.StockShaders.TextTransform.vert.refresh</LogicalName>
 | 
			
		||||
		</EmbeddedResource>
 | 
			
		||||
		<EmbeddedResource Include="src\Graphics\StockShaders\Binary\text_msdf.frag.refresh">
 | 
			
		||||
			<LogicalName>MoonWorks.Graphics.StockShaders.TextMSDF.frag.refresh</LogicalName>
 | 
			
		||||
		</EmbeddedResource>
 | 
			
		||||
	</ItemGroup>
 | 
			
		||||
</Project>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,18 +5,18 @@
 | 
			
		|||
	<dllmap dll="SDL2" os="linux,freebsd,netbsd" target="libSDL2-2.0.so.0"/>
 | 
			
		||||
 | 
			
		||||
	<dllmap dll="Refresh" os="windows" target="Refresh.dll"/>
 | 
			
		||||
	<dllmap dll="Refresh" os="osx" target="libRefresh.0.dylib"/>
 | 
			
		||||
	<dllmap dll="Refresh" os="linux,freebsd,netbsd" target="libRefresh.so.0"/>
 | 
			
		||||
	<dllmap dll="Refresh" os="osx" target="libRefresh.1.dylib"/>
 | 
			
		||||
	<dllmap dll="Refresh" os="linux,freebsd,netbsd" target="libRefresh.so.1"/>
 | 
			
		||||
 | 
			
		||||
	<dllmap dll="FAudio" os="windows" target="FAudio.dll"/>
 | 
			
		||||
	<dllmap dll="FAudio" os="osx" target="libFAudio.0.dylib"/>
 | 
			
		||||
	<dllmap dll="FAudio" os="linux,freebsd,netbsd" target="libFAudio.so.0"/>
 | 
			
		||||
 | 
			
		||||
	<dllmap dll="Wellspring" os="windows" target="Wellspring.dll"/>
 | 
			
		||||
	<dllmap dll="Wellspring" os="osx" target="libWellspring.0.dylib"/>
 | 
			
		||||
	<dllmap dll="Wellspring" os="linux,freebsd,netbsd" target="libWellspring.so.0"/>
 | 
			
		||||
	<dllmap dll="Wellspring" os="osx" target="libWellspring.1.dylib"/>
 | 
			
		||||
	<dllmap dll="Wellspring" os="linux,freebsd,netbsd" target="libWellspring.so.1"/>
 | 
			
		||||
 | 
			
		||||
	<dllmap dll="Theorafile" os="windows" target="libtheorafile.dll"/>
 | 
			
		||||
	<dllmap dll="Theorafile" os="osx" target="libtheorafile.dylib"/>
 | 
			
		||||
	<dllmap dll="Theorafile" os="linux,freebsd,netbsd" target="libtheorafile.so"/>
 | 
			
		||||
	<dllmap dll="dav1dfile" os="windows" target="dav1dfile.dll"/>
 | 
			
		||||
	<dllmap dll="dav1dfile" os="osx" target="libdav1dfile.1.dylib"/>
 | 
			
		||||
	<dllmap dll="dav1dfile" os="linux,freebsd,netbsd,openbsd" target="libdav1dfile.so.1"/>
 | 
			
		||||
</configuration>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,9 +12,13 @@ MoonWorks uses strictly Free Open Source Software. It will never have any kind o
 | 
			
		|||
 | 
			
		||||
## Documentation
 | 
			
		||||
 | 
			
		||||
API Reference: https://moonside.games/docs/moonworksapi/
 | 
			
		||||
 | 
			
		||||
High-level documentation is provided here: https://moonside.games/docs/moonworks/
 | 
			
		||||
 | 
			
		||||
For an actual API reference, the source is documented in doc comments that your preferred IDE can read.
 | 
			
		||||
The source is documented in doc comments that your preferred IDE can read.
 | 
			
		||||
 | 
			
		||||
Join our Discord! https://discord.gg/ujhwdkHmhN
 | 
			
		||||
 | 
			
		||||
## Dependencies
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -22,7 +26,7 @@ For an actual API reference, the source is documented in doc comments that your
 | 
			
		|||
* [Refresh](https://gitea.moonside.games/MoonsideGames/Refresh) - Graphics
 | 
			
		||||
* [FAudio](https://github.com/FNA-XNA/FAudio) - Audio
 | 
			
		||||
* [Wellspring](https://gitea.moonside.games/MoonsideGames/Wellspring) - Font Rendering
 | 
			
		||||
* [Theorafile](https://github.com/FNA-XNA/Theorafile) - Compressed Video
 | 
			
		||||
* [dav1dfile](https://github.com/MoonsideGames/dav1dfile) - Compressed Video
 | 
			
		||||
 | 
			
		||||
Prebuilt dependencies can be obtained here: https://moonside.games/files/moonlibs.tar.bz2
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1 +1 @@
 | 
			
		|||
Subproject commit 11ba6b37509a6c2fa2690f2643ee7bf5ce2ab4f2
 | 
			
		||||
Subproject commit 60480416bda930bf7544e6abe31b937f0daa0256
 | 
			
		||||
| 
						 | 
				
			
			@ -1 +1 @@
 | 
			
		|||
Subproject commit 1643061386177f62b516ccaad0ea04607cae2333
 | 
			
		||||
Subproject commit b5325e6d0329eeb35b074091a569a5f679852d28
 | 
			
		||||
| 
						 | 
				
			
			@ -1 +1 @@
 | 
			
		|||
Subproject commit f8c6fc407fbb22072fdafcda918aec52b2102519
 | 
			
		||||
Subproject commit e4afbb848586fca530b6538320f799f81a18b941
 | 
			
		||||
| 
						 | 
				
			
			@ -1 +0,0 @@
 | 
			
		|||
Subproject commit 8f9419ea856480e08294698e1d6be8752df3710b
 | 
			
		||||
| 
						 | 
				
			
			@ -1 +1 @@
 | 
			
		|||
Subproject commit f8872bae59e394b0f8a35224bb39ab8fd041af97
 | 
			
		||||
Subproject commit 074f2afc833b221906bb2468735041ce78f2cb89
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
Subproject commit 5065e2cd4662dbe023b77a45ef967f975170dfff
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,79 @@
 | 
			
		|||
using System;
 | 
			
		||||
using System.Runtime.InteropServices;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Audio
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// Contains raw audio data in a specified Format. <br/>
 | 
			
		||||
	/// Submit this to a SourceVoice to play audio.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public class AudioBuffer : AudioResource
 | 
			
		||||
	{
 | 
			
		||||
		IntPtr BufferDataPtr;
 | 
			
		||||
		uint BufferDataLength;
 | 
			
		||||
		private bool OwnsBufferData;
 | 
			
		||||
 | 
			
		||||
		public Format Format { get; }
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Create a new AudioBuffer.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="ownsBufferData">If true, the buffer data will be destroyed when this AudioBuffer is destroyed.</param>
 | 
			
		||||
		public AudioBuffer(
 | 
			
		||||
			AudioDevice device,
 | 
			
		||||
			Format format,
 | 
			
		||||
			IntPtr bufferPtr,
 | 
			
		||||
			uint bufferLengthInBytes,
 | 
			
		||||
			bool ownsBufferData) : base(device)
 | 
			
		||||
		{
 | 
			
		||||
			Format = format;
 | 
			
		||||
			BufferDataPtr = bufferPtr;
 | 
			
		||||
			BufferDataLength = bufferLengthInBytes;
 | 
			
		||||
			OwnsBufferData = ownsBufferData;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Create another AudioBuffer from this audio buffer.
 | 
			
		||||
		/// It will not own the buffer data.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="offset">Offset in bytes from the top of the original buffer.</param>
 | 
			
		||||
		/// <param name="length">Length in bytes of the new buffer.</param>
 | 
			
		||||
		/// <returns></returns>
 | 
			
		||||
		public AudioBuffer Slice(int offset, uint length)
 | 
			
		||||
		{
 | 
			
		||||
			return new AudioBuffer(Device, Format, BufferDataPtr + offset, length, false);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Create an FAudioBuffer struct from this AudioBuffer.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="loop">Whether we should set the FAudioBuffer to loop.</param>
 | 
			
		||||
		public FAudio.FAudioBuffer ToFAudioBuffer(bool loop = false)
 | 
			
		||||
		{
 | 
			
		||||
			return new FAudio.FAudioBuffer
 | 
			
		||||
			{
 | 
			
		||||
				Flags = FAudio.FAUDIO_END_OF_STREAM,
 | 
			
		||||
				pContext = IntPtr.Zero,
 | 
			
		||||
				pAudioData = BufferDataPtr,
 | 
			
		||||
				AudioBytes = BufferDataLength,
 | 
			
		||||
				PlayBegin = 0,
 | 
			
		||||
				PlayLength = 0,
 | 
			
		||||
				LoopBegin = 0,
 | 
			
		||||
				LoopLength = 0,
 | 
			
		||||
				LoopCount = loop ? FAudio.FAUDIO_LOOP_INFINITE : 0
 | 
			
		||||
			};
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		protected override unsafe void Dispose(bool disposing)
 | 
			
		||||
		{
 | 
			
		||||
			if (!IsDisposed)
 | 
			
		||||
			{
 | 
			
		||||
				if (OwnsBufferData)
 | 
			
		||||
				{
 | 
			
		||||
					NativeMemory.Free((void*) BufferDataPtr);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			base.Dispose(disposing);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,147 @@
 | 
			
		|||
using System;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Runtime.InteropServices;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Audio
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// Streamable audio in Ogg format.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public class AudioDataOgg : AudioDataStreamable
 | 
			
		||||
	{
 | 
			
		||||
		private IntPtr FileDataPtr = IntPtr.Zero;
 | 
			
		||||
		private IntPtr VorbisHandle = IntPtr.Zero;
 | 
			
		||||
 | 
			
		||||
		private string FilePath;
 | 
			
		||||
 | 
			
		||||
		public override bool Loaded => VorbisHandle != IntPtr.Zero;
 | 
			
		||||
		public override uint DecodeBufferSize => 32768;
 | 
			
		||||
 | 
			
		||||
		public AudioDataOgg(AudioDevice device, string filePath) : base(device)
 | 
			
		||||
		{
 | 
			
		||||
			FilePath = filePath;
 | 
			
		||||
 | 
			
		||||
			var handle = FAudio.stb_vorbis_open_filename(filePath, out var error, IntPtr.Zero);
 | 
			
		||||
 | 
			
		||||
			if (error != 0)
 | 
			
		||||
			{
 | 
			
		||||
				throw new InvalidOperationException("Error loading file!");
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			var info = FAudio.stb_vorbis_get_info(handle);
 | 
			
		||||
 | 
			
		||||
			Format = new Format
 | 
			
		||||
			{
 | 
			
		||||
				Tag = FormatTag.IEEE_FLOAT,
 | 
			
		||||
				BitsPerSample = 32,
 | 
			
		||||
				Channels = (ushort) info.channels,
 | 
			
		||||
				SampleRate = info.sample_rate
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			FAudio.stb_vorbis_close(handle);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public override unsafe void Decode(void* buffer, int bufferLengthInBytes, out int filledLengthInBytes, out bool reachedEnd)
 | 
			
		||||
		{
 | 
			
		||||
			var lengthInFloats = bufferLengthInBytes / sizeof(float);
 | 
			
		||||
 | 
			
		||||
			/* NOTE: this function returns samples per channel, not total samples */
 | 
			
		||||
			var samples = FAudio.stb_vorbis_get_samples_float_interleaved(
 | 
			
		||||
				VorbisHandle,
 | 
			
		||||
				Format.Channels,
 | 
			
		||||
				(IntPtr) buffer,
 | 
			
		||||
				lengthInFloats
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
			var sampleCount = samples * Format.Channels;
 | 
			
		||||
			reachedEnd = sampleCount < lengthInFloats;
 | 
			
		||||
			filledLengthInBytes = sampleCount * sizeof(float);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Prepares the Ogg data for streaming.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public override unsafe void Load()
 | 
			
		||||
		{
 | 
			
		||||
			if (!Loaded)
 | 
			
		||||
			{
 | 
			
		||||
				var fileStream = new FileStream(FilePath, FileMode.Open, FileAccess.Read);
 | 
			
		||||
				FileDataPtr = (nint) NativeMemory.Alloc((nuint) fileStream.Length);
 | 
			
		||||
				var fileDataSpan = new Span<byte>((void*) FileDataPtr, (int) fileStream.Length);
 | 
			
		||||
				fileStream.ReadExactly(fileDataSpan);
 | 
			
		||||
				fileStream.Close();
 | 
			
		||||
 | 
			
		||||
				VorbisHandle = FAudio.stb_vorbis_open_memory(FileDataPtr, fileDataSpan.Length, out int error, IntPtr.Zero);
 | 
			
		||||
				if (error != 0)
 | 
			
		||||
				{
 | 
			
		||||
					NativeMemory.Free((void*) FileDataPtr);
 | 
			
		||||
					Logger.LogError("Error opening OGG file!");
 | 
			
		||||
					Logger.LogError("Error: " + error);
 | 
			
		||||
					throw new InvalidOperationException("Error opening OGG file!");
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public override void Seek(uint sampleFrame)
 | 
			
		||||
		{
 | 
			
		||||
			FAudio.stb_vorbis_seek(VorbisHandle, sampleFrame);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Unloads the Ogg data, freeing resources.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public override unsafe void Unload()
 | 
			
		||||
		{
 | 
			
		||||
			if (Loaded)
 | 
			
		||||
			{
 | 
			
		||||
				FAudio.stb_vorbis_close(VorbisHandle);
 | 
			
		||||
				NativeMemory.Free((void*) FileDataPtr);
 | 
			
		||||
 | 
			
		||||
				VorbisHandle = IntPtr.Zero;
 | 
			
		||||
				FileDataPtr = IntPtr.Zero;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Loads an entire ogg file into an AudioBuffer. Useful for static audio.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public static unsafe AudioBuffer CreateBuffer(AudioDevice device, string filePath)
 | 
			
		||||
		{
 | 
			
		||||
			var filePointer = FAudio.stb_vorbis_open_filename(filePath, out var error, IntPtr.Zero);
 | 
			
		||||
 | 
			
		||||
			if (error != 0)
 | 
			
		||||
			{
 | 
			
		||||
				throw new InvalidOperationException("Error loading file!");
 | 
			
		||||
			}
 | 
			
		||||
			var info = FAudio.stb_vorbis_get_info(filePointer);
 | 
			
		||||
			var lengthInFloats =
 | 
			
		||||
				FAudio.stb_vorbis_stream_length_in_samples(filePointer) * info.channels;
 | 
			
		||||
			var lengthInBytes = lengthInFloats * Marshal.SizeOf<float>();
 | 
			
		||||
			var buffer = NativeMemory.Alloc((nuint) lengthInBytes);
 | 
			
		||||
 | 
			
		||||
			FAudio.stb_vorbis_get_samples_float_interleaved(
 | 
			
		||||
				filePointer,
 | 
			
		||||
				info.channels,
 | 
			
		||||
				(nint) buffer,
 | 
			
		||||
				(int) lengthInFloats
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
			FAudio.stb_vorbis_close(filePointer);
 | 
			
		||||
 | 
			
		||||
			var format = new Format
 | 
			
		||||
			{
 | 
			
		||||
				Tag = FormatTag.IEEE_FLOAT,
 | 
			
		||||
				BitsPerSample = 32,
 | 
			
		||||
				Channels = (ushort) info.channels,
 | 
			
		||||
				SampleRate = info.sample_rate
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			return new AudioBuffer(
 | 
			
		||||
				device,
 | 
			
		||||
				format,
 | 
			
		||||
				(nint) buffer,
 | 
			
		||||
				(uint) lengthInBytes,
 | 
			
		||||
				true);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,164 @@
 | 
			
		|||
using System;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Runtime.InteropServices;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Audio
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// Streamable audio in QOA format.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public class AudioDataQoa : AudioDataStreamable
 | 
			
		||||
	{
 | 
			
		||||
		private IntPtr QoaHandle = IntPtr.Zero;
 | 
			
		||||
		private IntPtr FileDataPtr = IntPtr.Zero;
 | 
			
		||||
 | 
			
		||||
		private string FilePath;
 | 
			
		||||
 | 
			
		||||
		private const uint QOA_MAGIC = 0x716f6166; /* 'qoaf' */
 | 
			
		||||
 | 
			
		||||
		public override bool Loaded => QoaHandle != IntPtr.Zero;
 | 
			
		||||
 | 
			
		||||
		private uint decodeBufferSize;
 | 
			
		||||
		public override uint DecodeBufferSize => decodeBufferSize;
 | 
			
		||||
 | 
			
		||||
		public AudioDataQoa(AudioDevice device, string filePath) : base(device)
 | 
			
		||||
		{
 | 
			
		||||
			FilePath = filePath;
 | 
			
		||||
 | 
			
		||||
			using var stream = new FileStream(FilePath, FileMode.Open, FileAccess.Read);
 | 
			
		||||
			using var reader = new BinaryReader(stream);
 | 
			
		||||
 | 
			
		||||
			UInt64 fileHeader = ReverseEndianness(reader.ReadUInt64());
 | 
			
		||||
			if ((fileHeader >> 32) != QOA_MAGIC)
 | 
			
		||||
			{
 | 
			
		||||
				throw new InvalidOperationException("Specified file is not a QOA file.");
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			uint totalSamplesPerChannel = (uint) (fileHeader & (0xFFFFFFFF));
 | 
			
		||||
			if (totalSamplesPerChannel == 0)
 | 
			
		||||
			{
 | 
			
		||||
				throw new InvalidOperationException("Specified file is not a valid QOA file.");
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			UInt64 frameHeader = ReverseEndianness(reader.ReadUInt64());
 | 
			
		||||
			uint channels = (uint) ((frameHeader >> 56) & 0x0000FF);
 | 
			
		||||
			uint samplerate = (uint) ((frameHeader >> 32) & 0xFFFFFF);
 | 
			
		||||
			uint samplesPerChannelPerFrame = (uint) ((frameHeader >> 16) & 0x00FFFF);
 | 
			
		||||
 | 
			
		||||
			Format = new Format
 | 
			
		||||
			{
 | 
			
		||||
				Tag = FormatTag.PCM,
 | 
			
		||||
				BitsPerSample = 16,
 | 
			
		||||
				Channels = (ushort) channels,
 | 
			
		||||
				SampleRate = samplerate
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			decodeBufferSize = channels * samplesPerChannelPerFrame * sizeof(short);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public override unsafe void Decode(void* buffer, int bufferLengthInBytes, out int filledLengthInBytes, out bool reachedEnd)
 | 
			
		||||
		{
 | 
			
		||||
			var lengthInShorts = bufferLengthInBytes / sizeof(short);
 | 
			
		||||
 | 
			
		||||
			// NOTE: this function returns samples per channel!
 | 
			
		||||
			var samples = FAudio.qoa_decode_next_frame(QoaHandle, (short*) buffer);
 | 
			
		||||
 | 
			
		||||
			var sampleCount = samples * Format.Channels;
 | 
			
		||||
			reachedEnd = sampleCount < lengthInShorts;
 | 
			
		||||
			filledLengthInBytes = (int) (sampleCount * sizeof(short));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Prepares qoa data for streaming.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public override unsafe void Load()
 | 
			
		||||
		{
 | 
			
		||||
			if (!Loaded)
 | 
			
		||||
			{
 | 
			
		||||
				var fileStream = new FileStream(FilePath, FileMode.Open, FileAccess.Read);
 | 
			
		||||
				FileDataPtr = (nint) NativeMemory.Alloc((nuint) fileStream.Length);
 | 
			
		||||
				var fileDataSpan = new Span<byte>((void*) FileDataPtr, (int) fileStream.Length);
 | 
			
		||||
				fileStream.ReadExactly(fileDataSpan);
 | 
			
		||||
				fileStream.Close();
 | 
			
		||||
 | 
			
		||||
				QoaHandle = FAudio.qoa_open_from_memory((char*) FileDataPtr, (uint) fileDataSpan.Length, 0);
 | 
			
		||||
				if (QoaHandle == IntPtr.Zero)
 | 
			
		||||
				{
 | 
			
		||||
					NativeMemory.Free((void*) FileDataPtr);
 | 
			
		||||
					Logger.LogError("Error opening QOA file!");
 | 
			
		||||
					throw new InvalidOperationException("Error opening QOA file!");
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public override void Seek(uint sampleFrame)
 | 
			
		||||
		{
 | 
			
		||||
			FAudio.qoa_seek_frame(QoaHandle, (int) sampleFrame);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Unloads the qoa data, freeing resources.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public override unsafe void Unload()
 | 
			
		||||
		{
 | 
			
		||||
			if (Loaded)
 | 
			
		||||
			{
 | 
			
		||||
				FAudio.qoa_close(QoaHandle);
 | 
			
		||||
				NativeMemory.Free((void*) FileDataPtr);
 | 
			
		||||
 | 
			
		||||
				QoaHandle = IntPtr.Zero;
 | 
			
		||||
				FileDataPtr = IntPtr.Zero;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Loads the entire qoa file into an AudioBuffer. Useful for static audio.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public unsafe static AudioBuffer CreateBuffer(AudioDevice device, string filePath)
 | 
			
		||||
		{
 | 
			
		||||
			using var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
 | 
			
		||||
			var fileDataPtr = NativeMemory.Alloc((nuint) fileStream.Length);
 | 
			
		||||
			var fileDataSpan = new Span<byte>(fileDataPtr, (int) fileStream.Length);
 | 
			
		||||
			fileStream.ReadExactly(fileDataSpan);
 | 
			
		||||
			fileStream.Close();
 | 
			
		||||
 | 
			
		||||
			var qoaHandle = FAudio.qoa_open_from_memory((char*) fileDataPtr, (uint) fileDataSpan.Length, 0);
 | 
			
		||||
			if (qoaHandle == 0)
 | 
			
		||||
			{
 | 
			
		||||
				NativeMemory.Free(fileDataPtr);
 | 
			
		||||
				Logger.LogError("Error opening QOA file!");
 | 
			
		||||
				throw new InvalidOperationException("Error opening QOA file!");
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			FAudio.qoa_attributes(qoaHandle, out var channels, out var samplerate, out var samples_per_channel_per_frame, out var total_samples_per_channel);
 | 
			
		||||
 | 
			
		||||
			var bufferLengthInBytes = total_samples_per_channel * channels * sizeof(short);
 | 
			
		||||
			var buffer = NativeMemory.Alloc(bufferLengthInBytes);
 | 
			
		||||
			FAudio.qoa_decode_entire(qoaHandle, (short*) buffer);
 | 
			
		||||
 | 
			
		||||
			FAudio.qoa_close(qoaHandle);
 | 
			
		||||
			NativeMemory.Free(fileDataPtr);
 | 
			
		||||
 | 
			
		||||
			var format = new Format
 | 
			
		||||
			{
 | 
			
		||||
				Tag = FormatTag.PCM,
 | 
			
		||||
				BitsPerSample = 16,
 | 
			
		||||
				Channels = (ushort) channels,
 | 
			
		||||
				SampleRate = samplerate
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			return new AudioBuffer(device, format, (nint) buffer, bufferLengthInBytes, true);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private static unsafe UInt64 ReverseEndianness(UInt64 value)
 | 
			
		||||
		{
 | 
			
		||||
			byte* bytes = (byte*) &value;
 | 
			
		||||
 | 
			
		||||
			return
 | 
			
		||||
				((UInt64)(bytes[0]) << 56) | ((UInt64)(bytes[1]) << 48) |
 | 
			
		||||
				((UInt64)(bytes[2]) << 40) | ((UInt64)(bytes[3]) << 32) |
 | 
			
		||||
				((UInt64)(bytes[4]) << 24) | ((UInt64)(bytes[5]) << 16) |
 | 
			
		||||
				((UInt64)(bytes[6]) <<  8) | ((UInt64)(bytes[7]) <<  0);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,49 @@
 | 
			
		|||
namespace MoonWorks.Audio
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// Use this in conjunction with a StreamingVoice to play back streaming audio data.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public abstract class AudioDataStreamable : AudioResource
 | 
			
		||||
	{
 | 
			
		||||
		public Format Format { get; protected set; }
 | 
			
		||||
		public abstract bool Loaded { get; }
 | 
			
		||||
		public abstract uint DecodeBufferSize { get; }
 | 
			
		||||
 | 
			
		||||
		protected AudioDataStreamable(AudioDevice device) : base(device)
 | 
			
		||||
		{
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Loads the raw audio data into memory to prepare it for stream decoding.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public abstract void Load();
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Unloads the raw audio data from memory.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public abstract void Unload();
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Seeks to the given sample frame.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public abstract void Seek(uint sampleFrame);
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Attempts to decodes data of length bufferLengthInBytes into the provided buffer.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="buffer">The buffer that decoded bytes will be placed into.</param>
 | 
			
		||||
		/// <param name="bufferLengthInBytes">Requested length of decoded audio data.</param>
 | 
			
		||||
		/// <param name="filledLengthInBytes">How much data was actually filled in by the decode.</param>
 | 
			
		||||
		/// <param name="reachedEnd">Whether the end of the data was reached on this decode.</param>
 | 
			
		||||
		public abstract unsafe void Decode(void* buffer, int bufferLengthInBytes, out int filledLengthInBytes, out bool reachedEnd);
 | 
			
		||||
 | 
			
		||||
		protected override void Dispose(bool disposing)
 | 
			
		||||
		{
 | 
			
		||||
			if (!IsDisposed)
 | 
			
		||||
			{
 | 
			
		||||
				Unload();
 | 
			
		||||
			}
 | 
			
		||||
			base.Dispose(disposing);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,100 @@
 | 
			
		|||
using System;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Runtime.InteropServices;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Audio
 | 
			
		||||
{
 | 
			
		||||
	public static class AudioDataWav
 | 
			
		||||
	{
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Create an AudioBuffer containing all the WAV audio data in a file.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <returns></returns>
 | 
			
		||||
		public unsafe static AudioBuffer CreateBuffer(AudioDevice device, string filePath)
 | 
			
		||||
		{
 | 
			
		||||
			// mostly borrowed from https://github.com/FNA-XNA/FNA/blob/b71b4a35ae59970ff0070dea6f8620856d8d4fec/src/Audio/SoundEffect.cs#L385
 | 
			
		||||
 | 
			
		||||
			// WaveFormatEx data
 | 
			
		||||
			ushort wFormatTag;
 | 
			
		||||
			ushort nChannels;
 | 
			
		||||
			uint nSamplesPerSec;
 | 
			
		||||
			uint nAvgBytesPerSec;
 | 
			
		||||
			ushort nBlockAlign;
 | 
			
		||||
			ushort wBitsPerSample;
 | 
			
		||||
 | 
			
		||||
			using var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
 | 
			
		||||
			using var reader = new BinaryReader(stream);
 | 
			
		||||
 | 
			
		||||
			// RIFF Signature
 | 
			
		||||
			string signature = new string(reader.ReadChars(4));
 | 
			
		||||
			if (signature != "RIFF")
 | 
			
		||||
			{
 | 
			
		||||
				throw new NotSupportedException("Specified stream is not a wave file.");
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			reader.ReadUInt32(); // Riff Chunk Size
 | 
			
		||||
 | 
			
		||||
			string wformat = new string(reader.ReadChars(4));
 | 
			
		||||
			if (wformat != "WAVE")
 | 
			
		||||
			{
 | 
			
		||||
				throw new NotSupportedException("Specified stream is not a wave file.");
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// WAVE Header
 | 
			
		||||
			string format_signature = new string(reader.ReadChars(4));
 | 
			
		||||
			while (format_signature != "fmt ")
 | 
			
		||||
			{
 | 
			
		||||
				reader.ReadBytes(reader.ReadInt32());
 | 
			
		||||
				format_signature = new string(reader.ReadChars(4));
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			int format_chunk_size = reader.ReadInt32();
 | 
			
		||||
 | 
			
		||||
			wFormatTag = reader.ReadUInt16();
 | 
			
		||||
			nChannels = reader.ReadUInt16();
 | 
			
		||||
			nSamplesPerSec = reader.ReadUInt32();
 | 
			
		||||
			nAvgBytesPerSec = reader.ReadUInt32();
 | 
			
		||||
			nBlockAlign = reader.ReadUInt16();
 | 
			
		||||
			wBitsPerSample = reader.ReadUInt16();
 | 
			
		||||
 | 
			
		||||
			// Reads residual bytes
 | 
			
		||||
			if (format_chunk_size > 16)
 | 
			
		||||
			{
 | 
			
		||||
				reader.ReadBytes(format_chunk_size - 16);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// data Signature
 | 
			
		||||
			string data_signature = new string(reader.ReadChars(4));
 | 
			
		||||
			while (data_signature.ToLowerInvariant() != "data")
 | 
			
		||||
			{
 | 
			
		||||
				reader.ReadBytes(reader.ReadInt32());
 | 
			
		||||
				data_signature = new string(reader.ReadChars(4));
 | 
			
		||||
			}
 | 
			
		||||
			if (data_signature != "data")
 | 
			
		||||
			{
 | 
			
		||||
				throw new NotSupportedException("Specified wave file is not supported.");
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			int waveDataLength = reader.ReadInt32();
 | 
			
		||||
			var waveDataBuffer = NativeMemory.Alloc((nuint) waveDataLength);
 | 
			
		||||
			var waveDataSpan = new Span<byte>(waveDataBuffer, waveDataLength);
 | 
			
		||||
			stream.ReadExactly(waveDataSpan);
 | 
			
		||||
 | 
			
		||||
			var format = new Format
 | 
			
		||||
			{
 | 
			
		||||
				Tag = (FormatTag) wFormatTag,
 | 
			
		||||
				BitsPerSample = wBitsPerSample,
 | 
			
		||||
				Channels = nChannels,
 | 
			
		||||
				SampleRate = nSamplesPerSec
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			return new AudioBuffer(
 | 
			
		||||
				device,
 | 
			
		||||
				format,
 | 
			
		||||
				(nint) waveDataBuffer,
 | 
			
		||||
				(uint) waveDataLength,
 | 
			
		||||
				true
 | 
			
		||||
			);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,41 +1,53 @@
 | 
			
		|||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Runtime.InteropServices;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Audio
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// AudioDevice manages all audio-related concerns.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public class AudioDevice : IDisposable
 | 
			
		||||
	{
 | 
			
		||||
		public IntPtr Handle { get; }
 | 
			
		||||
		public byte[] Handle3D { get; }
 | 
			
		||||
		public IntPtr MasteringVoice { get; }
 | 
			
		||||
		public FAudio.FAudioDeviceDetails DeviceDetails { get; }
 | 
			
		||||
		public IntPtr ReverbVoice { get; }
 | 
			
		||||
 | 
			
		||||
		private IntPtr trueMasteringVoice;
 | 
			
		||||
 | 
			
		||||
		// this is a fun little trick where we use a submix voice as a "faux" mastering voice
 | 
			
		||||
		// this lets us maintain API consistency for effects like panning and reverb
 | 
			
		||||
		private SubmixVoice fauxMasteringVoice;
 | 
			
		||||
		public SubmixVoice MasteringVoice => fauxMasteringVoice;
 | 
			
		||||
 | 
			
		||||
		public float CurveDistanceScalar = 1f;
 | 
			
		||||
		public float DopplerScale = 1f;
 | 
			
		||||
		public float SpeedOfSound = 343.5f;
 | 
			
		||||
 | 
			
		||||
		private float masteringVolume = 1f;
 | 
			
		||||
		public float MasteringVolume
 | 
			
		||||
		private readonly HashSet<GCHandle> resourceHandles = new HashSet<GCHandle>();
 | 
			
		||||
		private readonly HashSet<UpdatingSourceVoice> updatingSourceVoices = new HashSet<UpdatingSourceVoice>();
 | 
			
		||||
 | 
			
		||||
		private AudioTweenManager AudioTweenManager;
 | 
			
		||||
 | 
			
		||||
		private SourceVoicePool VoicePool;
 | 
			
		||||
		private List<SourceVoice> VoicesToReturn = new List<SourceVoice>();
 | 
			
		||||
 | 
			
		||||
		private const int Step = 200;
 | 
			
		||||
		private TimeSpan UpdateInterval;
 | 
			
		||||
		private System.Diagnostics.Stopwatch TickStopwatch = new System.Diagnostics.Stopwatch();
 | 
			
		||||
		private long previousTickTime;
 | 
			
		||||
		private Thread Thread;
 | 
			
		||||
		private AutoResetEvent WakeSignal;
 | 
			
		||||
		internal readonly object StateLock = new object();
 | 
			
		||||
 | 
			
		||||
		private bool Running;
 | 
			
		||||
		public bool IsDisposed { get; private set; }
 | 
			
		||||
 | 
			
		||||
		internal unsafe AudioDevice()
 | 
			
		||||
		{
 | 
			
		||||
			get => masteringVolume;
 | 
			
		||||
			set
 | 
			
		||||
			{
 | 
			
		||||
				masteringVolume = value;
 | 
			
		||||
				FAudio.FAudioVoice_SetVolume(MasteringVoice, masteringVolume, 0);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
			UpdateInterval = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / Step);
 | 
			
		||||
 | 
			
		||||
		internal FAudio.FAudioVoiceSends ReverbSends;
 | 
			
		||||
 | 
			
		||||
		private readonly List<WeakReference<AudioResource>> resources = new List<WeakReference<AudioResource>>();
 | 
			
		||||
		private readonly List<WeakReference<StreamingSound>> streamingSounds = new List<WeakReference<StreamingSound>>();
 | 
			
		||||
 | 
			
		||||
		private bool IsDisposed;
 | 
			
		||||
 | 
			
		||||
		public unsafe AudioDevice()
 | 
			
		||||
		{
 | 
			
		||||
			FAudio.FAudioCreate(out var handle, 0, FAudio.FAUDIO_DEFAULT_PROCESSOR);
 | 
			
		||||
			Handle = handle;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -80,25 +92,24 @@ namespace MoonWorks.Audio
 | 
			
		|||
			}
 | 
			
		||||
 | 
			
		||||
			/* Init Mastering Voice */
 | 
			
		||||
			IntPtr masteringVoice;
 | 
			
		||||
 | 
			
		||||
			if (FAudio.FAudio_CreateMasteringVoice(
 | 
			
		||||
			var result = FAudio.FAudio_CreateMasteringVoice(
 | 
			
		||||
				Handle,
 | 
			
		||||
				out masteringVoice,
 | 
			
		||||
				out trueMasteringVoice,
 | 
			
		||||
				FAudio.FAUDIO_DEFAULT_CHANNELS,
 | 
			
		||||
				FAudio.FAUDIO_DEFAULT_SAMPLERATE,
 | 
			
		||||
				0,
 | 
			
		||||
				i,
 | 
			
		||||
				IntPtr.Zero
 | 
			
		||||
			) != 0)
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
			if (result != 0)
 | 
			
		||||
			{
 | 
			
		||||
				Logger.LogError("No mastering voice found!");
 | 
			
		||||
				Handle = IntPtr.Zero;
 | 
			
		||||
				FAudio.FAudio_Release(Handle);
 | 
			
		||||
				Logger.LogError("Failed to create a mastering voice!");
 | 
			
		||||
				Logger.LogError("Audio device creation failed!");
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			MasteringVoice = masteringVoice;
 | 
			
		||||
			fauxMasteringVoice = SubmixVoice.CreateFauxMasteringVoice(this);
 | 
			
		||||
 | 
			
		||||
			/* Init 3D Audio */
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -109,139 +120,169 @@ namespace MoonWorks.Audio
 | 
			
		|||
				Handle3D
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
			/* Init reverb */
 | 
			
		||||
			AudioTweenManager = new AudioTweenManager();
 | 
			
		||||
			VoicePool = new SourceVoicePool(this);
 | 
			
		||||
 | 
			
		||||
			IntPtr reverbVoice;
 | 
			
		||||
			WakeSignal = new AutoResetEvent(true);
 | 
			
		||||
 | 
			
		||||
			IntPtr reverb;
 | 
			
		||||
			FAudio.FAudioCreateReverb(out reverb, 0);
 | 
			
		||||
			Thread = new Thread(ThreadMain);
 | 
			
		||||
			Thread.IsBackground = true;
 | 
			
		||||
			Thread.Start();
 | 
			
		||||
 | 
			
		||||
			IntPtr chainPtr;
 | 
			
		||||
			chainPtr = Marshal.AllocHGlobal(
 | 
			
		||||
				Marshal.SizeOf<FAudio.FAudioEffectChain>()
 | 
			
		||||
			);
 | 
			
		||||
			Running = true;
 | 
			
		||||
 | 
			
		||||
			FAudio.FAudioEffectChain* reverbChain = (FAudio.FAudioEffectChain*) chainPtr;
 | 
			
		||||
			reverbChain->EffectCount = 1;
 | 
			
		||||
			reverbChain->pEffectDescriptors = Marshal.AllocHGlobal(
 | 
			
		||||
				Marshal.SizeOf<FAudio.FAudioEffectDescriptor>()
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
			FAudio.FAudioEffectDescriptor* reverbDescriptor =
 | 
			
		||||
				(FAudio.FAudioEffectDescriptor*) reverbChain->pEffectDescriptors;
 | 
			
		||||
 | 
			
		||||
			reverbDescriptor->InitialState = 1;
 | 
			
		||||
			reverbDescriptor->OutputChannels = (uint) (
 | 
			
		||||
				(DeviceDetails.OutputFormat.Format.nChannels == 6) ? 6 : 1
 | 
			
		||||
			);
 | 
			
		||||
			reverbDescriptor->pEffect = reverb;
 | 
			
		||||
 | 
			
		||||
			FAudio.FAudio_CreateSubmixVoice(
 | 
			
		||||
				Handle,
 | 
			
		||||
				out reverbVoice,
 | 
			
		||||
				1, /* omnidirectional reverb */
 | 
			
		||||
				DeviceDetails.OutputFormat.Format.nSamplesPerSec,
 | 
			
		||||
				0,
 | 
			
		||||
				0,
 | 
			
		||||
				IntPtr.Zero,
 | 
			
		||||
				chainPtr
 | 
			
		||||
			);
 | 
			
		||||
			FAudio.FAPOBase_Release(reverb);
 | 
			
		||||
 | 
			
		||||
			Marshal.FreeHGlobal(reverbChain->pEffectDescriptors);
 | 
			
		||||
			Marshal.FreeHGlobal(chainPtr);
 | 
			
		||||
 | 
			
		||||
			ReverbVoice = reverbVoice;
 | 
			
		||||
 | 
			
		||||
			/* Init reverb params */
 | 
			
		||||
			// Defaults based on FAUDIOFX_I3DL2_PRESET_GENERIC
 | 
			
		||||
 | 
			
		||||
			IntPtr reverbParamsPtr = Marshal.AllocHGlobal(
 | 
			
		||||
				Marshal.SizeOf<FAudio.FAudioFXReverbParameters>()
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
			FAudio.FAudioFXReverbParameters* reverbParams = (FAudio.FAudioFXReverbParameters*) reverbParamsPtr;
 | 
			
		||||
			reverbParams->WetDryMix = 100.0f;
 | 
			
		||||
			reverbParams->ReflectionsDelay = 7;
 | 
			
		||||
			reverbParams->ReverbDelay = 11;
 | 
			
		||||
			reverbParams->RearDelay = FAudio.FAUDIOFX_REVERB_DEFAULT_REAR_DELAY;
 | 
			
		||||
			reverbParams->PositionLeft = FAudio.FAUDIOFX_REVERB_DEFAULT_POSITION;
 | 
			
		||||
			reverbParams->PositionRight = FAudio.FAUDIOFX_REVERB_DEFAULT_POSITION;
 | 
			
		||||
			reverbParams->PositionMatrixLeft = FAudio.FAUDIOFX_REVERB_DEFAULT_POSITION_MATRIX;
 | 
			
		||||
			reverbParams->PositionMatrixRight = FAudio.FAUDIOFX_REVERB_DEFAULT_POSITION_MATRIX;
 | 
			
		||||
			reverbParams->EarlyDiffusion = 15;
 | 
			
		||||
			reverbParams->LateDiffusion = 15;
 | 
			
		||||
			reverbParams->LowEQGain = 8;
 | 
			
		||||
			reverbParams->LowEQCutoff = 4;
 | 
			
		||||
			reverbParams->HighEQGain = 8;
 | 
			
		||||
			reverbParams->HighEQCutoff = 6;
 | 
			
		||||
			reverbParams->RoomFilterFreq = 5000f;
 | 
			
		||||
			reverbParams->RoomFilterMain = -10f;
 | 
			
		||||
			reverbParams->RoomFilterHF = -1f;
 | 
			
		||||
			reverbParams->ReflectionsGain = -26.0200005f;
 | 
			
		||||
			reverbParams->ReverbGain = 10.0f;
 | 
			
		||||
			reverbParams->DecayTime = 1.49000001f;
 | 
			
		||||
			reverbParams->Density = 100.0f;
 | 
			
		||||
			reverbParams->RoomSize = FAudio.FAUDIOFX_REVERB_DEFAULT_ROOM_SIZE;
 | 
			
		||||
			FAudio.FAudioVoice_SetEffectParameters(
 | 
			
		||||
				ReverbVoice,
 | 
			
		||||
				0,
 | 
			
		||||
				reverbParamsPtr,
 | 
			
		||||
				(uint) Marshal.SizeOf<FAudio.FAudioFXReverbParameters>(),
 | 
			
		||||
				0
 | 
			
		||||
			);
 | 
			
		||||
			Marshal.FreeHGlobal(reverbParamsPtr);
 | 
			
		||||
 | 
			
		||||
			/* Init reverb sends */
 | 
			
		||||
 | 
			
		||||
			ReverbSends = new FAudio.FAudioVoiceSends
 | 
			
		||||
			{
 | 
			
		||||
				SendCount = 2,
 | 
			
		||||
				pSends = Marshal.AllocHGlobal(
 | 
			
		||||
					2 * Marshal.SizeOf<FAudio.FAudioSendDescriptor>()
 | 
			
		||||
				)
 | 
			
		||||
			};
 | 
			
		||||
			FAudio.FAudioSendDescriptor* sendDesc = (FAudio.FAudioSendDescriptor*) ReverbSends.pSends;
 | 
			
		||||
			sendDesc[0].Flags = 0;
 | 
			
		||||
			sendDesc[0].pOutputVoice = MasteringVoice;
 | 
			
		||||
			sendDesc[1].Flags = 0;
 | 
			
		||||
			sendDesc[1].pOutputVoice = ReverbVoice;
 | 
			
		||||
			TickStopwatch.Start();
 | 
			
		||||
			previousTickTime = 0;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		internal void Update()
 | 
			
		||||
		private void ThreadMain()
 | 
			
		||||
		{
 | 
			
		||||
			for (var i = streamingSounds.Count - 1; i >= 0; i--)
 | 
			
		||||
			while (Running)
 | 
			
		||||
			{
 | 
			
		||||
				var weakReference = streamingSounds[i];
 | 
			
		||||
				if (weakReference.TryGetTarget(out var streamingSound))
 | 
			
		||||
				lock (StateLock)
 | 
			
		||||
				{
 | 
			
		||||
					streamingSound.Update();
 | 
			
		||||
					try
 | 
			
		||||
					{
 | 
			
		||||
						ThreadMainTick();
 | 
			
		||||
					}
 | 
			
		||||
					catch (Exception e)
 | 
			
		||||
					{
 | 
			
		||||
						Logger.LogError(e.ToString());
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				else
 | 
			
		||||
 | 
			
		||||
				WakeSignal.WaitOne(UpdateInterval);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private void ThreadMainTick()
 | 
			
		||||
		{
 | 
			
		||||
			long tickDelta = TickStopwatch.Elapsed.Ticks - previousTickTime;
 | 
			
		||||
			previousTickTime = TickStopwatch.Elapsed.Ticks;
 | 
			
		||||
			float elapsedSeconds = (float) tickDelta / System.TimeSpan.TicksPerSecond;
 | 
			
		||||
 | 
			
		||||
			AudioTweenManager.Update(elapsedSeconds);
 | 
			
		||||
 | 
			
		||||
			foreach (var voice in updatingSourceVoices)
 | 
			
		||||
			{
 | 
			
		||||
				voice.Update();
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			foreach (var voice in VoicesToReturn)
 | 
			
		||||
			{
 | 
			
		||||
				if (voice is UpdatingSourceVoice updatingSourceVoice)
 | 
			
		||||
				{
 | 
			
		||||
					streamingSounds.RemoveAt(i);
 | 
			
		||||
					updatingSourceVoices.Remove(updatingSourceVoice);
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				voice.Reset();
 | 
			
		||||
				VoicePool.Return(voice);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			VoicesToReturn.Clear();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Triggers all pending operations with the given syncGroup value.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public void TriggerSyncGroup(uint syncGroup)
 | 
			
		||||
		{
 | 
			
		||||
			FAudio.FAudio_CommitChanges(Handle, syncGroup);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Obtains an appropriate source voice from the voice pool.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="format">The format that the voice must match.</param>
 | 
			
		||||
		/// <returns>A source voice with the given format.</returns>
 | 
			
		||||
		public T Obtain<T>(Format format) where T : SourceVoice, IPoolable<T>
 | 
			
		||||
		{
 | 
			
		||||
			lock (StateLock)
 | 
			
		||||
			{
 | 
			
		||||
				var voice = VoicePool.Obtain<T>(format);
 | 
			
		||||
 | 
			
		||||
				if (voice is UpdatingSourceVoice updatingSourceVoice)
 | 
			
		||||
				{
 | 
			
		||||
					updatingSourceVoices.Add(updatingSourceVoice);
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				return voice;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Returns the source voice to the voice pool.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="voice"></param>
 | 
			
		||||
		internal void Return(SourceVoice voice)
 | 
			
		||||
		{
 | 
			
		||||
			lock (StateLock)
 | 
			
		||||
			{
 | 
			
		||||
				VoicesToReturn.Add(voice);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		internal void CreateTween(
 | 
			
		||||
			Voice voice,
 | 
			
		||||
			AudioTweenProperty property,
 | 
			
		||||
			System.Func<float, float> easingFunction,
 | 
			
		||||
			float start,
 | 
			
		||||
			float end,
 | 
			
		||||
			float duration,
 | 
			
		||||
			float delayTime
 | 
			
		||||
		) {
 | 
			
		||||
			lock (StateLock)
 | 
			
		||||
			{
 | 
			
		||||
				AudioTweenManager.CreateTween(
 | 
			
		||||
					voice,
 | 
			
		||||
					property,
 | 
			
		||||
					easingFunction,
 | 
			
		||||
					start,
 | 
			
		||||
					end,
 | 
			
		||||
					duration,
 | 
			
		||||
					delayTime
 | 
			
		||||
				);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		internal void ClearTweens(
 | 
			
		||||
			Voice voice,
 | 
			
		||||
			AudioTweenProperty property
 | 
			
		||||
		) {
 | 
			
		||||
			lock (StateLock)
 | 
			
		||||
			{
 | 
			
		||||
				AudioTweenManager.ClearTweens(voice, property);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		internal void WakeThread()
 | 
			
		||||
		{
 | 
			
		||||
			WakeSignal.Set();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		internal void AddResourceReference(GCHandle resourceReference)
 | 
			
		||||
		{
 | 
			
		||||
			lock (StateLock)
 | 
			
		||||
			{
 | 
			
		||||
				resourceHandles.Add(resourceReference);
 | 
			
		||||
 | 
			
		||||
				if (resourceReference.Target is UpdatingSourceVoice updatableVoice)
 | 
			
		||||
				{
 | 
			
		||||
					updatingSourceVoices.Add(updatableVoice);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		internal void AddDynamicSoundInstance(StreamingSound instance)
 | 
			
		||||
		internal void RemoveResourceReference(GCHandle resourceReference)
 | 
			
		||||
		{
 | 
			
		||||
			streamingSounds.Add(new WeakReference<StreamingSound>(instance));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		internal void AddResourceReference(WeakReference<AudioResource> resourceReference)
 | 
			
		||||
		{
 | 
			
		||||
			lock (resources)
 | 
			
		||||
			lock (StateLock)
 | 
			
		||||
			{
 | 
			
		||||
				resources.Add(resourceReference);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
				resourceHandles.Remove(resourceReference);
 | 
			
		||||
 | 
			
		||||
		internal void RemoveResourceReference(WeakReference<AudioResource> resourceReference)
 | 
			
		||||
		{
 | 
			
		||||
			lock (resources)
 | 
			
		||||
			{
 | 
			
		||||
				resources.Remove(resourceReference);
 | 
			
		||||
				if (resourceReference.Target is UpdatingSourceVoice updatableVoice)
 | 
			
		||||
				{
 | 
			
		||||
					updatingSourceVoices.Remove(updatableVoice);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -249,29 +290,54 @@ namespace MoonWorks.Audio
 | 
			
		|||
		{
 | 
			
		||||
			if (!IsDisposed)
 | 
			
		||||
			{
 | 
			
		||||
				Running = false;
 | 
			
		||||
 | 
			
		||||
				if (disposing)
 | 
			
		||||
				{
 | 
			
		||||
					for (var i = resources.Count - 1; i >= 0; i--)
 | 
			
		||||
					{
 | 
			
		||||
						var weakReference = resources[i];
 | 
			
		||||
					Thread.Join();
 | 
			
		||||
 | 
			
		||||
						if (weakReference.TryGetTarget(out var resource))
 | 
			
		||||
					// dispose all source voices first
 | 
			
		||||
					foreach (var handle in resourceHandles)
 | 
			
		||||
					{
 | 
			
		||||
						if (handle.Target is SourceVoice voice)
 | 
			
		||||
						{
 | 
			
		||||
							voice.Dispose();
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					// dispose all submix voices except the faux mastering voice
 | 
			
		||||
					foreach (var handle in resourceHandles)
 | 
			
		||||
					{
 | 
			
		||||
						if (handle.Target is SubmixVoice voice && voice != fauxMasteringVoice)
 | 
			
		||||
						{
 | 
			
		||||
							voice.Dispose();
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					// dispose the faux mastering voice
 | 
			
		||||
					fauxMasteringVoice.Dispose();
 | 
			
		||||
 | 
			
		||||
					// dispose the true mastering voice
 | 
			
		||||
					FAudio.FAudioVoice_DestroyVoice(trueMasteringVoice);
 | 
			
		||||
 | 
			
		||||
					// destroy all other audio resources
 | 
			
		||||
					foreach (var handle in resourceHandles)
 | 
			
		||||
					{
 | 
			
		||||
						if (handle.Target is AudioResource resource)
 | 
			
		||||
						{
 | 
			
		||||
							resource.Dispose();
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
					resources.Clear();
 | 
			
		||||
 | 
			
		||||
					resourceHandles.Clear();
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				FAudio.FAudioVoice_DestroyVoice(ReverbVoice);
 | 
			
		||||
				FAudio.FAudioVoice_DestroyVoice(MasteringVoice);
 | 
			
		||||
				FAudio.FAudio_Release(Handle);
 | 
			
		||||
 | 
			
		||||
				IsDisposed = true;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
 | 
			
		||||
		~AudioDevice()
 | 
			
		||||
		{
 | 
			
		||||
			// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,6 +4,9 @@ using MoonWorks.Math.Float;
 | 
			
		|||
 | 
			
		||||
namespace MoonWorks.Audio
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// An emitter for 3D spatial audio.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public class AudioEmitter : AudioResource
 | 
			
		||||
	{
 | 
			
		||||
		internal FAudio.F3DAUDIO_EMITTER emitterData;
 | 
			
		||||
| 
						 | 
				
			
			@ -129,7 +132,5 @@ namespace MoonWorks.Audio
 | 
			
		|||
			emitterData.pReverbCurve = IntPtr.Zero;
 | 
			
		||||
			emitterData.CurveDistanceScaler = 1.0f;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		protected override void Destroy() { }
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,6 +3,9 @@ using MoonWorks.Math.Float;
 | 
			
		|||
 | 
			
		||||
namespace MoonWorks.Audio
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// A listener for 3D spatial audio. Usually attached to a camera.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public class AudioListener : AudioResource
 | 
			
		||||
	{
 | 
			
		||||
		internal FAudio.F3DAUDIO_LISTENER listenerData;
 | 
			
		||||
| 
						 | 
				
			
			@ -91,7 +94,5 @@ namespace MoonWorks.Audio
 | 
			
		|||
			/* Unexposed variables, defaults based on XNA behavior */
 | 
			
		||||
			listenerData.pCone = IntPtr.Zero;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		protected override void Destroy() { }
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,5 @@
 | 
			
		|||
using System;
 | 
			
		||||
using System.Runtime.InteropServices;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Audio
 | 
			
		||||
{
 | 
			
		||||
| 
						 | 
				
			
			@ -8,28 +9,24 @@ namespace MoonWorks.Audio
 | 
			
		|||
 | 
			
		||||
		public bool IsDisposed { get; private set; }
 | 
			
		||||
 | 
			
		||||
		private WeakReference<AudioResource> selfReference;
 | 
			
		||||
		private GCHandle SelfReference;
 | 
			
		||||
 | 
			
		||||
		public AudioResource(AudioDevice device)
 | 
			
		||||
		protected AudioResource(AudioDevice device)
 | 
			
		||||
		{
 | 
			
		||||
			Device = device;
 | 
			
		||||
 | 
			
		||||
			selfReference = new WeakReference<AudioResource>(this);
 | 
			
		||||
			Device.AddResourceReference(selfReference);
 | 
			
		||||
			SelfReference = GCHandle.Alloc(this, GCHandleType.Weak);
 | 
			
		||||
			Device.AddResourceReference(SelfReference);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		protected abstract void Destroy();
 | 
			
		||||
 | 
			
		||||
		protected virtual void Dispose(bool disposing)
 | 
			
		||||
		{
 | 
			
		||||
			if (!IsDisposed)
 | 
			
		||||
			{
 | 
			
		||||
				Destroy();
 | 
			
		||||
 | 
			
		||||
				if (selfReference != null)
 | 
			
		||||
				if (disposing)
 | 
			
		||||
				{
 | 
			
		||||
					Device.RemoveResourceReference(selfReference);
 | 
			
		||||
					selfReference = null;
 | 
			
		||||
					Device.RemoveResourceReference(SelfReference);
 | 
			
		||||
					SelfReference.Free();
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				IsDisposed = true;
 | 
			
		||||
| 
						 | 
				
			
			@ -38,8 +35,12 @@ namespace MoonWorks.Audio
 | 
			
		|||
 | 
			
		||||
		~AudioResource()
 | 
			
		||||
		{
 | 
			
		||||
			// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
 | 
			
		||||
			Dispose(disposing: false);
 | 
			
		||||
			#if DEBUG
 | 
			
		||||
			// If you see this log message, you leaked an audio resource without disposing it!
 | 
			
		||||
			// We can't clean it up for you because this can cause catastrophic issues.
 | 
			
		||||
			// You should really fix this when it happens.
 | 
			
		||||
			Logger.LogWarn($"A resource of type {GetType().Name} was not Disposed.");
 | 
			
		||||
			#endif
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public void Dispose()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,58 @@
 | 
			
		|||
using System.Collections.Generic;
 | 
			
		||||
using EasingFunction = System.Func<float, float>;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Audio
 | 
			
		||||
{
 | 
			
		||||
	internal enum AudioTweenProperty
 | 
			
		||||
	{
 | 
			
		||||
		Pan,
 | 
			
		||||
		Pitch,
 | 
			
		||||
		Volume,
 | 
			
		||||
		FilterFrequency,
 | 
			
		||||
		Reverb
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	internal class AudioTween
 | 
			
		||||
	{
 | 
			
		||||
		public Voice Voice;
 | 
			
		||||
		public AudioTweenProperty Property;
 | 
			
		||||
		public EasingFunction EasingFunction;
 | 
			
		||||
		public float Time;
 | 
			
		||||
		public float StartValue;
 | 
			
		||||
		public float EndValue;
 | 
			
		||||
		public float DelayTime;
 | 
			
		||||
		public float Duration;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	internal class AudioTweenPool
 | 
			
		||||
	{
 | 
			
		||||
		private Queue<AudioTween> Tweens = new Queue<AudioTween>(16);
 | 
			
		||||
 | 
			
		||||
		public AudioTweenPool()
 | 
			
		||||
		{
 | 
			
		||||
			for (int i = 0; i < 16; i += 1)
 | 
			
		||||
			{
 | 
			
		||||
				Tweens.Enqueue(new AudioTween());
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public AudioTween Obtain()
 | 
			
		||||
		{
 | 
			
		||||
			if (Tweens.Count > 0)
 | 
			
		||||
			{
 | 
			
		||||
				var tween = Tweens.Dequeue();
 | 
			
		||||
				return tween;
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
				return new AudioTween();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public void Free(AudioTween tween)
 | 
			
		||||
		{
 | 
			
		||||
			tween.Voice = null;
 | 
			
		||||
			Tweens.Enqueue(tween);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,159 @@
 | 
			
		|||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Audio
 | 
			
		||||
{
 | 
			
		||||
	internal class AudioTweenManager
 | 
			
		||||
	{
 | 
			
		||||
		private AudioTweenPool AudioTweenPool = new AudioTweenPool();
 | 
			
		||||
		private readonly Dictionary<(Voice, AudioTweenProperty), AudioTween> AudioTweens = new Dictionary<(Voice, AudioTweenProperty), AudioTween>();
 | 
			
		||||
		private readonly List<AudioTween> DelayedAudioTweens = new List<AudioTween>();
 | 
			
		||||
 | 
			
		||||
		public void Update(float elapsedSeconds)
 | 
			
		||||
		{
 | 
			
		||||
			for (var i = DelayedAudioTweens.Count - 1; i >= 0; i--)
 | 
			
		||||
			{
 | 
			
		||||
				var audioTween = DelayedAudioTweens[i];
 | 
			
		||||
				var voice = audioTween.Voice;
 | 
			
		||||
 | 
			
		||||
				audioTween.Time += elapsedSeconds;
 | 
			
		||||
 | 
			
		||||
				if (audioTween.Time >= audioTween.DelayTime)
 | 
			
		||||
				{
 | 
			
		||||
					// set the tween start value to the current value of the property
 | 
			
		||||
					switch (audioTween.Property)
 | 
			
		||||
					{
 | 
			
		||||
						case AudioTweenProperty.Pan:
 | 
			
		||||
							audioTween.StartValue = voice.Pan;
 | 
			
		||||
							break;
 | 
			
		||||
 | 
			
		||||
						case AudioTweenProperty.Pitch:
 | 
			
		||||
							audioTween.StartValue = voice.Pitch;
 | 
			
		||||
							break;
 | 
			
		||||
 | 
			
		||||
						case AudioTweenProperty.Volume:
 | 
			
		||||
							audioTween.StartValue = voice.Volume;
 | 
			
		||||
							break;
 | 
			
		||||
 | 
			
		||||
						case AudioTweenProperty.FilterFrequency:
 | 
			
		||||
							audioTween.StartValue = voice.FilterFrequency;
 | 
			
		||||
							break;
 | 
			
		||||
 | 
			
		||||
						case AudioTweenProperty.Reverb:
 | 
			
		||||
							audioTween.StartValue = voice.Reverb;
 | 
			
		||||
							break;
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					audioTween.Time = 0;
 | 
			
		||||
					DelayedAudioTweens.RemoveAt(i);
 | 
			
		||||
 | 
			
		||||
					AddTween(audioTween);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			foreach (var (key, audioTween) in AudioTweens)
 | 
			
		||||
			{
 | 
			
		||||
				bool finished = UpdateAudioTween(audioTween, elapsedSeconds);
 | 
			
		||||
 | 
			
		||||
				if (finished)
 | 
			
		||||
				{
 | 
			
		||||
					AudioTweenPool.Free(audioTween);
 | 
			
		||||
					AudioTweens.Remove(key);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public void CreateTween(
 | 
			
		||||
			Voice voice,
 | 
			
		||||
			AudioTweenProperty property,
 | 
			
		||||
			System.Func<float, float> easingFunction,
 | 
			
		||||
			float start,
 | 
			
		||||
			float end,
 | 
			
		||||
			float duration,
 | 
			
		||||
			float delayTime
 | 
			
		||||
		) {
 | 
			
		||||
			var tween = AudioTweenPool.Obtain();
 | 
			
		||||
			tween.Voice = voice;
 | 
			
		||||
			tween.Property = property;
 | 
			
		||||
			tween.EasingFunction = easingFunction;
 | 
			
		||||
			tween.StartValue = start;
 | 
			
		||||
			tween.EndValue = end;
 | 
			
		||||
			tween.Duration = duration;
 | 
			
		||||
			tween.Time = 0;
 | 
			
		||||
			tween.DelayTime = delayTime;
 | 
			
		||||
 | 
			
		||||
			if (delayTime == 0)
 | 
			
		||||
			{
 | 
			
		||||
				AddTween(tween);
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
				DelayedAudioTweens.Add(tween);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public void ClearTweens(Voice voice, AudioTweenProperty property)
 | 
			
		||||
		{
 | 
			
		||||
			AudioTweens.Remove((voice, property));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private void AddTween(
 | 
			
		||||
			AudioTween audioTween
 | 
			
		||||
		) {
 | 
			
		||||
			// if a tween with the same sound and property already exists, get rid of it
 | 
			
		||||
			if (AudioTweens.TryGetValue((audioTween.Voice, audioTween.Property), out var currentTween))
 | 
			
		||||
			{
 | 
			
		||||
				AudioTweenPool.Free(currentTween);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			AudioTweens[(audioTween.Voice, audioTween.Property)] = audioTween;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private static bool UpdateAudioTween(AudioTween audioTween, float delta)
 | 
			
		||||
		{
 | 
			
		||||
			float value;
 | 
			
		||||
			audioTween.Time += delta;
 | 
			
		||||
 | 
			
		||||
			var finished = audioTween.Time >= audioTween.Duration;
 | 
			
		||||
			if (finished)
 | 
			
		||||
			{
 | 
			
		||||
				value = audioTween.EndValue;
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
				value = MoonWorks.Math.Easing.Interp(
 | 
			
		||||
					audioTween.StartValue,
 | 
			
		||||
					audioTween.EndValue,
 | 
			
		||||
					audioTween.Time,
 | 
			
		||||
					audioTween.Duration,
 | 
			
		||||
					audioTween.EasingFunction
 | 
			
		||||
				);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			switch (audioTween.Property)
 | 
			
		||||
			{
 | 
			
		||||
				case AudioTweenProperty.Pan:
 | 
			
		||||
					audioTween.Voice.Pan = value;
 | 
			
		||||
					break;
 | 
			
		||||
 | 
			
		||||
				case AudioTweenProperty.Pitch:
 | 
			
		||||
					audioTween.Voice.Pitch = value;
 | 
			
		||||
					break;
 | 
			
		||||
 | 
			
		||||
				case AudioTweenProperty.Volume:
 | 
			
		||||
					audioTween.Voice.Volume = value;
 | 
			
		||||
					break;
 | 
			
		||||
 | 
			
		||||
				case AudioTweenProperty.FilterFrequency:
 | 
			
		||||
					audioTween.Voice.FilterFrequency = value;
 | 
			
		||||
					break;
 | 
			
		||||
 | 
			
		||||
				case AudioTweenProperty.Reverb:
 | 
			
		||||
					audioTween.Voice.Reverb = value;
 | 
			
		||||
					break;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return finished;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,36 @@
 | 
			
		|||
namespace MoonWorks.Audio
 | 
			
		||||
{
 | 
			
		||||
	public enum FormatTag : ushort
 | 
			
		||||
	{
 | 
			
		||||
		Unknown = 0,
 | 
			
		||||
		PCM = 1,
 | 
			
		||||
		MSADPCM = 2,
 | 
			
		||||
		IEEE_FLOAT = 3
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// Describes the format of audio data. Usually specified in an audio file's header information.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public record struct Format
 | 
			
		||||
	{
 | 
			
		||||
		public FormatTag Tag;
 | 
			
		||||
		public ushort Channels;
 | 
			
		||||
		public uint SampleRate;
 | 
			
		||||
		public ushort BitsPerSample;
 | 
			
		||||
 | 
			
		||||
		internal FAudio.FAudioWaveFormatEx ToFAudioFormat()
 | 
			
		||||
		{
 | 
			
		||||
			var blockAlign = (ushort) ((BitsPerSample / 8) * Channels);
 | 
			
		||||
 | 
			
		||||
			return new FAudio.FAudioWaveFormatEx
 | 
			
		||||
			{
 | 
			
		||||
				wFormatTag = (ushort) Tag,
 | 
			
		||||
				nChannels = Channels,
 | 
			
		||||
				nSamplesPerSec = SampleRate,
 | 
			
		||||
				wBitsPerSample = BitsPerSample,
 | 
			
		||||
				nBlockAlign = blockAlign,
 | 
			
		||||
				nAvgBytesPerSec = blockAlign * SampleRate
 | 
			
		||||
			};
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,7 @@
 | 
			
		|||
namespace MoonWorks.Audio
 | 
			
		||||
{
 | 
			
		||||
	public interface IPoolable<T>
 | 
			
		||||
	{
 | 
			
		||||
		static abstract T Create(AudioDevice device, Format format);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,28 @@
 | 
			
		|||
namespace MoonWorks.Audio
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// PersistentVoice should be used when you need to maintain a long-term reference to a source voice.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public class PersistentVoice : SourceVoice, IPoolable<PersistentVoice>
 | 
			
		||||
	{
 | 
			
		||||
		public PersistentVoice(AudioDevice device, Format format) : base(device, format)
 | 
			
		||||
		{
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static PersistentVoice Create(AudioDevice device, Format format)
 | 
			
		||||
		{
 | 
			
		||||
			return new PersistentVoice(device, format);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Adds an AudioBuffer to the voice queue.
 | 
			
		||||
		/// The voice processes and plays back the buffers in its queue in the order that they were submitted.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="buffer">The buffer to submit to the voice.</param>
 | 
			
		||||
		/// <param name="loop">Whether the voice should loop this buffer.</param>
 | 
			
		||||
		public void Submit(AudioBuffer buffer, bool loop = false)
 | 
			
		||||
		{
 | 
			
		||||
			Submit(buffer.ToFAudioBuffer(loop));
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,83 @@
 | 
			
		|||
using System;
 | 
			
		||||
using System.Runtime.InteropServices;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Audio
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// Use this in conjunction with SourceVoice.SetReverbEffectChain to add reverb to a voice.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public unsafe class ReverbEffect : SubmixVoice
 | 
			
		||||
	{
 | 
			
		||||
		// Defaults based on FAUDIOFX_I3DL2_PRESET_GENERIC
 | 
			
		||||
		public static FAudio.FAudioFXReverbParameters DefaultParams = new FAudio.FAudioFXReverbParameters
 | 
			
		||||
		{
 | 
			
		||||
			WetDryMix = 100.0f,
 | 
			
		||||
			ReflectionsDelay = 7,
 | 
			
		||||
			ReverbDelay = 11,
 | 
			
		||||
			RearDelay = FAudio.FAUDIOFX_REVERB_DEFAULT_REAR_DELAY,
 | 
			
		||||
			PositionLeft = FAudio.FAUDIOFX_REVERB_DEFAULT_POSITION,
 | 
			
		||||
			PositionRight = FAudio.FAUDIOFX_REVERB_DEFAULT_POSITION,
 | 
			
		||||
			PositionMatrixLeft = FAudio.FAUDIOFX_REVERB_DEFAULT_POSITION_MATRIX,
 | 
			
		||||
			PositionMatrixRight = FAudio.FAUDIOFX_REVERB_DEFAULT_POSITION_MATRIX,
 | 
			
		||||
			EarlyDiffusion = 15,
 | 
			
		||||
			LateDiffusion = 15,
 | 
			
		||||
			LowEQGain = 8,
 | 
			
		||||
			LowEQCutoff = 4,
 | 
			
		||||
			HighEQGain = 8,
 | 
			
		||||
			HighEQCutoff = 6,
 | 
			
		||||
			RoomFilterFreq = 5000f,
 | 
			
		||||
			RoomFilterMain = -10f,
 | 
			
		||||
			RoomFilterHF = -1f,
 | 
			
		||||
			ReflectionsGain = -26.0200005f,
 | 
			
		||||
			ReverbGain = 10.0f,
 | 
			
		||||
			DecayTime = 1.49000001f,
 | 
			
		||||
			Density = 100.0f,
 | 
			
		||||
			RoomSize = FAudio.FAUDIOFX_REVERB_DEFAULT_ROOM_SIZE
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		public FAudio.FAudioFXReverbParameters Params { get; private set; }
 | 
			
		||||
 | 
			
		||||
		public ReverbEffect(AudioDevice audioDevice, uint processingStage) : base(audioDevice, 1, audioDevice.DeviceDetails.OutputFormat.Format.nSamplesPerSec, processingStage)
 | 
			
		||||
		{
 | 
			
		||||
			/* Init reverb */
 | 
			
		||||
			IntPtr reverb;
 | 
			
		||||
			FAudio.FAudioCreateReverb(out reverb, 0);
 | 
			
		||||
 | 
			
		||||
			var chain = new FAudio.FAudioEffectChain();
 | 
			
		||||
			var descriptor = new FAudio.FAudioEffectDescriptor
 | 
			
		||||
			{
 | 
			
		||||
				InitialState = 1,
 | 
			
		||||
				OutputChannels = 1,
 | 
			
		||||
				pEffect = reverb
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			chain.EffectCount = 1;
 | 
			
		||||
			chain.pEffectDescriptors = (nint) (&descriptor);
 | 
			
		||||
 | 
			
		||||
			FAudio.FAudioVoice_SetEffectChain(
 | 
			
		||||
				Handle,
 | 
			
		||||
				ref chain
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
			FAudio.FAPOBase_Release(reverb);
 | 
			
		||||
 | 
			
		||||
			SetParams(DefaultParams);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public void SetParams(in FAudio.FAudioFXReverbParameters reverbParams)
 | 
			
		||||
		{
 | 
			
		||||
			Params = reverbParams;
 | 
			
		||||
 | 
			
		||||
			fixed (FAudio.FAudioFXReverbParameters* reverbParamsPtr = &reverbParams)
 | 
			
		||||
			{
 | 
			
		||||
				FAudio.FAudioVoice_SetEffectParameters(
 | 
			
		||||
					Handle,
 | 
			
		||||
					0,
 | 
			
		||||
					(nint) reverbParamsPtr,
 | 
			
		||||
					(uint) Marshal.SizeOf<FAudio.FAudioFXReverbParameters>(),
 | 
			
		||||
					0
 | 
			
		||||
				);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,366 +0,0 @@
 | 
			
		|||
using System;
 | 
			
		||||
using System.Runtime.InteropServices;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Audio
 | 
			
		||||
{
 | 
			
		||||
	public abstract class SoundInstance : AudioResource
 | 
			
		||||
	{
 | 
			
		||||
		internal IntPtr Handle;
 | 
			
		||||
		internal FAudio.FAudioWaveFormatEx Format;
 | 
			
		||||
 | 
			
		||||
		protected FAudio.F3DAUDIO_DSP_SETTINGS dspSettings;
 | 
			
		||||
 | 
			
		||||
		public bool Is3D { get; protected set; }
 | 
			
		||||
 | 
			
		||||
		public virtual SoundState State { get; protected set; }
 | 
			
		||||
 | 
			
		||||
		private float pan = 0;
 | 
			
		||||
		public float Pan
 | 
			
		||||
		{
 | 
			
		||||
			get => pan;
 | 
			
		||||
			set
 | 
			
		||||
			{
 | 
			
		||||
				pan = value;
 | 
			
		||||
 | 
			
		||||
				if (pan < -1f)
 | 
			
		||||
				{
 | 
			
		||||
					pan = -1f;
 | 
			
		||||
				}
 | 
			
		||||
				if (pan > 1f)
 | 
			
		||||
				{
 | 
			
		||||
					pan = 1f;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if (Is3D) { return; }
 | 
			
		||||
 | 
			
		||||
				SetPanMatrixCoefficients();
 | 
			
		||||
				FAudio.FAudioVoice_SetOutputMatrix(
 | 
			
		||||
					Handle,
 | 
			
		||||
					Device.MasteringVoice,
 | 
			
		||||
					dspSettings.SrcChannelCount,
 | 
			
		||||
					dspSettings.DstChannelCount,
 | 
			
		||||
					dspSettings.pMatrixCoefficients,
 | 
			
		||||
					0
 | 
			
		||||
				);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private float pitch = 0;
 | 
			
		||||
		public float Pitch
 | 
			
		||||
		{
 | 
			
		||||
			get => pitch;
 | 
			
		||||
			set
 | 
			
		||||
			{
 | 
			
		||||
				pitch = Math.MathHelper.Clamp(value, -1f, 1f);
 | 
			
		||||
				UpdatePitch();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private float volume = 1;
 | 
			
		||||
		public float Volume
 | 
			
		||||
		{
 | 
			
		||||
			get => volume;
 | 
			
		||||
			set
 | 
			
		||||
			{
 | 
			
		||||
				volume = value;
 | 
			
		||||
				FAudio.FAudioVoice_SetVolume(Handle, volume, 0);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private float reverb;
 | 
			
		||||
		public unsafe float Reverb
 | 
			
		||||
		{
 | 
			
		||||
			get => reverb;
 | 
			
		||||
			set
 | 
			
		||||
			{
 | 
			
		||||
				reverb = value;
 | 
			
		||||
 | 
			
		||||
				float* outputMatrix = (float*) dspSettings.pMatrixCoefficients;
 | 
			
		||||
				outputMatrix[0] = reverb;
 | 
			
		||||
				if (dspSettings.SrcChannelCount == 2)
 | 
			
		||||
				{
 | 
			
		||||
					outputMatrix[1] = reverb;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				FAudio.FAudioVoice_SetOutputMatrix(
 | 
			
		||||
					Handle,
 | 
			
		||||
					Device.ReverbVoice,
 | 
			
		||||
					dspSettings.SrcChannelCount,
 | 
			
		||||
					1,
 | 
			
		||||
					dspSettings.pMatrixCoefficients,
 | 
			
		||||
					0
 | 
			
		||||
				);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private const float MAX_FILTER_FREQUENCY = 1f;
 | 
			
		||||
		private const float MAX_FILTER_ONEOVERQ = 1.5f;
 | 
			
		||||
 | 
			
		||||
		private FAudio.FAudioFilterParameters filterParameters = new FAudio.FAudioFilterParameters
 | 
			
		||||
		{
 | 
			
		||||
			Type = FAudio.FAudioFilterType.FAudioLowPassFilter,
 | 
			
		||||
			Frequency = 1f,
 | 
			
		||||
			OneOverQ = 1f
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		private float FilterFrequency
 | 
			
		||||
		{
 | 
			
		||||
			get => filterParameters.Frequency;
 | 
			
		||||
			set
 | 
			
		||||
			{
 | 
			
		||||
				value = System.Math.Clamp(value, 0.01f, MAX_FILTER_FREQUENCY);
 | 
			
		||||
				filterParameters.Frequency = value;
 | 
			
		||||
 | 
			
		||||
				FAudio.FAudioVoice_SetFilterParameters(
 | 
			
		||||
					Handle,
 | 
			
		||||
					ref filterParameters,
 | 
			
		||||
					0
 | 
			
		||||
				);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private float FilterOneOverQ
 | 
			
		||||
		{
 | 
			
		||||
			get => filterParameters.OneOverQ;
 | 
			
		||||
			set
 | 
			
		||||
			{
 | 
			
		||||
				value = System.Math.Clamp(value, 0.01f, MAX_FILTER_ONEOVERQ);
 | 
			
		||||
				filterParameters.OneOverQ = value;
 | 
			
		||||
 | 
			
		||||
				FAudio.FAudioVoice_SetFilterParameters(
 | 
			
		||||
					Handle,
 | 
			
		||||
					ref filterParameters,
 | 
			
		||||
					0
 | 
			
		||||
				);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private FilterType filterType;
 | 
			
		||||
		public FilterType FilterType
 | 
			
		||||
		{
 | 
			
		||||
			get => filterType;
 | 
			
		||||
			set
 | 
			
		||||
			{
 | 
			
		||||
				filterType = value;
 | 
			
		||||
 | 
			
		||||
				switch (filterType)
 | 
			
		||||
				{
 | 
			
		||||
					case FilterType.None:
 | 
			
		||||
						filterParameters = new FAudio.FAudioFilterParameters
 | 
			
		||||
						{
 | 
			
		||||
							Type = FAudio.FAudioFilterType.FAudioLowPassFilter,
 | 
			
		||||
							Frequency = 1f,
 | 
			
		||||
							OneOverQ = 1f
 | 
			
		||||
						};
 | 
			
		||||
						break;
 | 
			
		||||
 | 
			
		||||
					case FilterType.LowPass:
 | 
			
		||||
						filterParameters.Type = FAudio.FAudioFilterType.FAudioLowPassFilter;
 | 
			
		||||
						break;
 | 
			
		||||
 | 
			
		||||
					case FilterType.BandPass:
 | 
			
		||||
						filterParameters.Type = FAudio.FAudioFilterType.FAudioBandPassFilter;
 | 
			
		||||
						break;
 | 
			
		||||
 | 
			
		||||
					case FilterType.HighPass:
 | 
			
		||||
						filterParameters.Type = FAudio.FAudioFilterType.FAudioHighPassFilter;
 | 
			
		||||
						break;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				FAudio.FAudioVoice_SetFilterParameters(
 | 
			
		||||
					Handle,
 | 
			
		||||
					ref filterParameters,
 | 
			
		||||
					0
 | 
			
		||||
				);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public SoundInstance(
 | 
			
		||||
			AudioDevice device,
 | 
			
		||||
			ushort formatTag,
 | 
			
		||||
			ushort bitsPerSample,
 | 
			
		||||
			ushort blockAlign,
 | 
			
		||||
			ushort channels,
 | 
			
		||||
			uint samplesPerSecond
 | 
			
		||||
		) : base(device)
 | 
			
		||||
		{
 | 
			
		||||
			var format = new FAudio.FAudioWaveFormatEx
 | 
			
		||||
			{
 | 
			
		||||
				wFormatTag = formatTag,
 | 
			
		||||
				wBitsPerSample = bitsPerSample,
 | 
			
		||||
				nChannels = channels,
 | 
			
		||||
				nBlockAlign = blockAlign,
 | 
			
		||||
				nSamplesPerSec = samplesPerSecond,
 | 
			
		||||
				nAvgBytesPerSec = blockAlign * samplesPerSecond
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			Format = format;
 | 
			
		||||
 | 
			
		||||
			FAudio.FAudio_CreateSourceVoice(
 | 
			
		||||
				Device.Handle,
 | 
			
		||||
				out Handle,
 | 
			
		||||
				ref Format,
 | 
			
		||||
				FAudio.FAUDIO_VOICE_USEFILTER,
 | 
			
		||||
				FAudio.FAUDIO_DEFAULT_FREQ_RATIO,
 | 
			
		||||
				IntPtr.Zero,
 | 
			
		||||
				IntPtr.Zero,
 | 
			
		||||
				IntPtr.Zero
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
			if (Handle == IntPtr.Zero)
 | 
			
		||||
			{
 | 
			
		||||
				Logger.LogError("SoundInstance failed to initialize!");
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			InitDSPSettings(Format.nChannels);
 | 
			
		||||
 | 
			
		||||
			// FIXME: not everything should be running through reverb...
 | 
			
		||||
			/*
 | 
			
		||||
			FAudio.FAudioVoice_SetOutputVoices(
 | 
			
		||||
				Handle,
 | 
			
		||||
				ref Device.ReverbSends
 | 
			
		||||
			);
 | 
			
		||||
			*/
 | 
			
		||||
 | 
			
		||||
			State = SoundState.Stopped;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public void Apply3D(AudioListener listener, AudioEmitter emitter)
 | 
			
		||||
		{
 | 
			
		||||
			Is3D = true;
 | 
			
		||||
 | 
			
		||||
			emitter.emitterData.CurveDistanceScaler = Device.CurveDistanceScalar;
 | 
			
		||||
			emitter.emitterData.ChannelCount = dspSettings.SrcChannelCount;
 | 
			
		||||
 | 
			
		||||
			FAudio.F3DAudioCalculate(
 | 
			
		||||
				Device.Handle3D,
 | 
			
		||||
				ref listener.listenerData,
 | 
			
		||||
				ref emitter.emitterData,
 | 
			
		||||
				FAudio.F3DAUDIO_CALCULATE_MATRIX | FAudio.F3DAUDIO_CALCULATE_DOPPLER,
 | 
			
		||||
				ref dspSettings
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
			UpdatePitch();
 | 
			
		||||
			FAudio.FAudioVoice_SetOutputMatrix(
 | 
			
		||||
				Handle,
 | 
			
		||||
				Device.MasteringVoice,
 | 
			
		||||
				dspSettings.SrcChannelCount,
 | 
			
		||||
				dspSettings.DstChannelCount,
 | 
			
		||||
				dspSettings.pMatrixCoefficients,
 | 
			
		||||
				0
 | 
			
		||||
			);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public abstract void Play();
 | 
			
		||||
		public abstract void Pause();
 | 
			
		||||
		public abstract void Stop();
 | 
			
		||||
		public abstract void StopImmediate();
 | 
			
		||||
 | 
			
		||||
		private void InitDSPSettings(uint srcChannels)
 | 
			
		||||
		{
 | 
			
		||||
			dspSettings = new FAudio.F3DAUDIO_DSP_SETTINGS();
 | 
			
		||||
			dspSettings.DopplerFactor = 1f;
 | 
			
		||||
			dspSettings.SrcChannelCount = srcChannels;
 | 
			
		||||
			dspSettings.DstChannelCount = Device.DeviceDetails.OutputFormat.Format.nChannels;
 | 
			
		||||
 | 
			
		||||
			int memsize = (
 | 
			
		||||
				4 *
 | 
			
		||||
				(int) dspSettings.SrcChannelCount *
 | 
			
		||||
				(int) dspSettings.DstChannelCount
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
			dspSettings.pMatrixCoefficients = Marshal.AllocHGlobal(memsize);
 | 
			
		||||
			unsafe
 | 
			
		||||
			{
 | 
			
		||||
				byte* memPtr = (byte*) dspSettings.pMatrixCoefficients;
 | 
			
		||||
				for (int i = 0; i < memsize; i += 1)
 | 
			
		||||
				{
 | 
			
		||||
					memPtr[i] = 0;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			SetPanMatrixCoefficients();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private void UpdatePitch()
 | 
			
		||||
		{
 | 
			
		||||
			float doppler;
 | 
			
		||||
			float dopplerScale = Device.DopplerScale;
 | 
			
		||||
			if (!Is3D || dopplerScale == 0.0f)
 | 
			
		||||
			{
 | 
			
		||||
				doppler = 1.0f;
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
				doppler = dspSettings.DopplerFactor * dopplerScale;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			FAudio.FAudioSourceVoice_SetFrequencyRatio(
 | 
			
		||||
				Handle,
 | 
			
		||||
				(float) System.Math.Pow(2.0, pitch) * doppler,
 | 
			
		||||
				0
 | 
			
		||||
			);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Taken from https://github.com/FNA-XNA/FNA/blob/master/src/Audio/SoundEffectInstance.cs
 | 
			
		||||
		private unsafe void SetPanMatrixCoefficients()
 | 
			
		||||
		{
 | 
			
		||||
			/* Two major things to notice:
 | 
			
		||||
			 * 1. The spec assumes any speaker count >= 2 has Front Left/Right.
 | 
			
		||||
			 * 2. Stereo panning is WAY more complicated than you think.
 | 
			
		||||
			 *    The main thing is that hard panning does NOT eliminate an
 | 
			
		||||
			 *    entire channel; the two channels are blended on each side.
 | 
			
		||||
			 * -flibit
 | 
			
		||||
			 */
 | 
			
		||||
			float* outputMatrix = (float*) dspSettings.pMatrixCoefficients;
 | 
			
		||||
			if (dspSettings.SrcChannelCount == 1)
 | 
			
		||||
			{
 | 
			
		||||
				if (dspSettings.DstChannelCount == 1)
 | 
			
		||||
				{
 | 
			
		||||
					outputMatrix[0] = 1.0f;
 | 
			
		||||
				}
 | 
			
		||||
				else
 | 
			
		||||
				{
 | 
			
		||||
					outputMatrix[0] = (pan > 0.0f) ? (1.0f - pan) : 1.0f;
 | 
			
		||||
					outputMatrix[1] = (pan < 0.0f) ? (1.0f + pan) : 1.0f;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
				if (dspSettings.DstChannelCount == 1)
 | 
			
		||||
				{
 | 
			
		||||
					outputMatrix[0] = 1.0f;
 | 
			
		||||
					outputMatrix[1] = 1.0f;
 | 
			
		||||
				}
 | 
			
		||||
				else
 | 
			
		||||
				{
 | 
			
		||||
					if (pan <= 0.0f)
 | 
			
		||||
					{
 | 
			
		||||
						// Left speaker blends left/right channels
 | 
			
		||||
						outputMatrix[0] = 0.5f * pan + 1.0f;
 | 
			
		||||
						outputMatrix[1] = 0.5f * -pan;
 | 
			
		||||
						// Right speaker gets less of the right channel
 | 
			
		||||
						outputMatrix[2] = 0.0f;
 | 
			
		||||
						outputMatrix[3] = pan + 1.0f;
 | 
			
		||||
					}
 | 
			
		||||
					else
 | 
			
		||||
					{
 | 
			
		||||
						// Left speaker gets less of the left channel
 | 
			
		||||
						outputMatrix[0] = -pan + 1.0f;
 | 
			
		||||
						outputMatrix[1] = 0.0f;
 | 
			
		||||
						// Right speaker blends right/left channels
 | 
			
		||||
						outputMatrix[2] = 0.5f * pan;
 | 
			
		||||
						outputMatrix[3] = 0.5f * -pan + 1.0f;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		protected override void Destroy()
 | 
			
		||||
		{
 | 
			
		||||
			StopImmediate();
 | 
			
		||||
			FAudio.FAudioVoice_DestroyVoice(Handle);
 | 
			
		||||
			Marshal.FreeHGlobal(dspSettings.pMatrixCoefficients);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,64 @@
 | 
			
		|||
namespace MoonWorks.Audio
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// Plays back a series of AudioBuffers in sequence. Set the OnSoundNeeded callback to add AudioBuffers dynamically.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public class SoundSequence : UpdatingSourceVoice, IPoolable<SoundSequence>
 | 
			
		||||
	{
 | 
			
		||||
		public int NeedSoundThreshold = 0;
 | 
			
		||||
		public delegate void OnSoundNeededFunc();
 | 
			
		||||
		public OnSoundNeededFunc OnSoundNeeded;
 | 
			
		||||
 | 
			
		||||
		public SoundSequence(AudioDevice device, Format format) : base(device, format)
 | 
			
		||||
		{
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public SoundSequence(AudioDevice device, AudioBuffer templateSound) : base(device, templateSound.Format)
 | 
			
		||||
		{
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static SoundSequence Create(AudioDevice device, Format format)
 | 
			
		||||
		{
 | 
			
		||||
			return new SoundSequence(device, format);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public override void Update()
 | 
			
		||||
		{
 | 
			
		||||
			lock (StateLock)
 | 
			
		||||
			{
 | 
			
		||||
				if (State != SoundState.Playing) { return; }
 | 
			
		||||
 | 
			
		||||
				if (NeedSoundThreshold > 0)
 | 
			
		||||
				{
 | 
			
		||||
					var buffersNeeded = NeedSoundThreshold - (int) BuffersQueued;
 | 
			
		||||
 | 
			
		||||
					for (int i = 0; i < buffersNeeded; i += 1)
 | 
			
		||||
					{
 | 
			
		||||
						if (OnSoundNeeded != null)
 | 
			
		||||
						{
 | 
			
		||||
							OnSoundNeeded();
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public void EnqueueSound(AudioBuffer buffer)
 | 
			
		||||
		{
 | 
			
		||||
#if DEBUG
 | 
			
		||||
			if (!(buffer.Format == Format))
 | 
			
		||||
			{
 | 
			
		||||
				Logger.LogWarn("Sound sequence audio format mismatch!");
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
			lock (StateLock)
 | 
			
		||||
			{
 | 
			
		||||
				Submit(buffer.ToFAudioBuffer());
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,218 @@
 | 
			
		|||
using System;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Audio
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// Emits audio from submitted audio buffers.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public abstract class SourceVoice : Voice
 | 
			
		||||
	{
 | 
			
		||||
		private Format format;
 | 
			
		||||
		public Format Format => format;
 | 
			
		||||
 | 
			
		||||
		protected bool PlaybackInitiated;
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// The number of buffers queued in the voice.
 | 
			
		||||
		/// This includes the currently playing voice!
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public uint BuffersQueued
 | 
			
		||||
		{
 | 
			
		||||
			get
 | 
			
		||||
			{
 | 
			
		||||
				FAudio.FAudioSourceVoice_GetState(
 | 
			
		||||
					Handle,
 | 
			
		||||
					out var state,
 | 
			
		||||
					FAudio.FAUDIO_VOICE_NOSAMPLESPLAYED
 | 
			
		||||
				);
 | 
			
		||||
 | 
			
		||||
				return state.BuffersQueued;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private SoundState state = SoundState.Stopped;
 | 
			
		||||
		public SoundState State
 | 
			
		||||
		{
 | 
			
		||||
			get
 | 
			
		||||
			{
 | 
			
		||||
				if (BuffersQueued == 0)
 | 
			
		||||
				{
 | 
			
		||||
					Stop();
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				return state;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			internal set
 | 
			
		||||
			{
 | 
			
		||||
				state = value;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		protected object StateLock = new object();
 | 
			
		||||
 | 
			
		||||
		public SourceVoice(
 | 
			
		||||
			AudioDevice device,
 | 
			
		||||
			Format format
 | 
			
		||||
		) : base(device, format.Channels, device.DeviceDetails.OutputFormat.Format.nChannels)
 | 
			
		||||
		{
 | 
			
		||||
			this.format = format;
 | 
			
		||||
			var fAudioFormat = format.ToFAudioFormat();
 | 
			
		||||
 | 
			
		||||
			 FAudio.FAudio_CreateSourceVoice(
 | 
			
		||||
				device.Handle,
 | 
			
		||||
				out handle,
 | 
			
		||||
				ref fAudioFormat,
 | 
			
		||||
				FAudio.FAUDIO_VOICE_USEFILTER,
 | 
			
		||||
				FAudio.FAUDIO_DEFAULT_FREQ_RATIO,
 | 
			
		||||
				IntPtr.Zero,
 | 
			
		||||
				IntPtr.Zero, // default sends to mastering voice!
 | 
			
		||||
				IntPtr.Zero
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
			SetOutputVoice(device.MasteringVoice);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Starts consumption and processing of audio by the voice.
 | 
			
		||||
		/// Delivers the result to any connected submix or mastering voice.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="syncGroup">Optional. Denotes that the operation will be pending until AudioDevice.TriggerSyncGroup is called.</param>
 | 
			
		||||
		public void Play(uint syncGroup = FAudio.FAUDIO_COMMIT_NOW)
 | 
			
		||||
		{
 | 
			
		||||
			lock (StateLock)
 | 
			
		||||
			{
 | 
			
		||||
				FAudio.FAudioSourceVoice_Start(Handle, 0, syncGroup);
 | 
			
		||||
 | 
			
		||||
				State = SoundState.Playing;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Pauses playback.
 | 
			
		||||
		/// All source buffers that are queued on the voice and the current cursor position are preserved.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="syncGroup">Optional. Denotes that the operation will be pending until AudioDevice.TriggerSyncGroup is called.</param>
 | 
			
		||||
		public void Pause(uint syncGroup = FAudio.FAUDIO_COMMIT_NOW)
 | 
			
		||||
		{
 | 
			
		||||
			lock (StateLock)
 | 
			
		||||
			{
 | 
			
		||||
				FAudio.FAudioSourceVoice_Stop(Handle, 0, syncGroup);
 | 
			
		||||
 | 
			
		||||
				State = SoundState.Paused;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Stops looping the voice when it reaches the end of the current loop region.
 | 
			
		||||
		/// If the cursor for the voice is not in a loop region, ExitLoop does nothing.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="syncGroup">Optional. Denotes that the operation will be pending until AudioDevice.TriggerSyncGroup is called.</param>
 | 
			
		||||
		public void ExitLoop(uint syncGroup = FAudio.FAUDIO_COMMIT_NOW)
 | 
			
		||||
		{
 | 
			
		||||
			lock (StateLock)
 | 
			
		||||
			{
 | 
			
		||||
				FAudio.FAudioSourceVoice_ExitLoop(Handle, syncGroup);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Stops playback and removes all pending audio buffers from the voice queue.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="syncGroup">Optional. Denotes that the operation will be pending until AudioDevice.TriggerSyncGroup is called.</param>
 | 
			
		||||
		public void Stop(uint syncGroup = FAudio.FAUDIO_COMMIT_NOW)
 | 
			
		||||
		{
 | 
			
		||||
			lock (StateLock)
 | 
			
		||||
			{
 | 
			
		||||
				FAudio.FAudioSourceVoice_Stop(Handle, 0, syncGroup);
 | 
			
		||||
				FAudio.FAudioSourceVoice_FlushSourceBuffers(Handle);
 | 
			
		||||
 | 
			
		||||
				State = SoundState.Stopped;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Adds an AudioBuffer to the voice queue.
 | 
			
		||||
		/// The voice processes and plays back the buffers in its queue in the order that they were submitted.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="buffer">The buffer to submit to the voice.</param>
 | 
			
		||||
		public void Submit(AudioBuffer buffer)
 | 
			
		||||
		{
 | 
			
		||||
			Submit(buffer.ToFAudioBuffer());
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Calculates positional sound. This must be called continuously to update positional sound.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="listener"></param>
 | 
			
		||||
		/// <param name="emitter"></param>
 | 
			
		||||
		public unsafe void Apply3D(AudioListener listener, AudioEmitter emitter)
 | 
			
		||||
		{
 | 
			
		||||
			Is3D = true;
 | 
			
		||||
 | 
			
		||||
			emitter.emitterData.CurveDistanceScaler = Device.CurveDistanceScalar;
 | 
			
		||||
			emitter.emitterData.ChannelCount = SourceChannelCount;
 | 
			
		||||
 | 
			
		||||
			var dspSettings = new FAudio.F3DAUDIO_DSP_SETTINGS
 | 
			
		||||
			{
 | 
			
		||||
				DopplerFactor = DopplerFactor,
 | 
			
		||||
				SrcChannelCount = SourceChannelCount,
 | 
			
		||||
				DstChannelCount = DestinationChannelCount,
 | 
			
		||||
				pMatrixCoefficients = (nint) pMatrixCoefficients
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			FAudio.F3DAudioCalculate(
 | 
			
		||||
				Device.Handle3D,
 | 
			
		||||
				ref listener.listenerData,
 | 
			
		||||
				ref emitter.emitterData,
 | 
			
		||||
				FAudio.F3DAUDIO_CALCULATE_MATRIX | FAudio.F3DAUDIO_CALCULATE_DOPPLER,
 | 
			
		||||
				ref dspSettings
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
			UpdatePitch();
 | 
			
		||||
 | 
			
		||||
			FAudio.FAudioVoice_SetOutputMatrix(
 | 
			
		||||
				Handle,
 | 
			
		||||
				OutputVoice.Handle,
 | 
			
		||||
				SourceChannelCount,
 | 
			
		||||
				DestinationChannelCount,
 | 
			
		||||
				(nint) pMatrixCoefficients,
 | 
			
		||||
				0
 | 
			
		||||
			);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Specifies that this source voice can be returned to the voice pool.
 | 
			
		||||
		/// Holding on to the reference after calling this will cause problems!
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public void Return()
 | 
			
		||||
		{
 | 
			
		||||
			Stop();
 | 
			
		||||
			Device.Return(this);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Adds an FAudio buffer to the voice queue.
 | 
			
		||||
		/// The voice processes and plays back the buffers in its queue in the order that they were submitted.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="buffer">The buffer to submit to the voice.</param>
 | 
			
		||||
		protected void Submit(FAudio.FAudioBuffer buffer)
 | 
			
		||||
		{
 | 
			
		||||
			lock (StateLock)
 | 
			
		||||
			{
 | 
			
		||||
				FAudio.FAudioSourceVoice_SubmitSourceBuffer(
 | 
			
		||||
					Handle,
 | 
			
		||||
					ref buffer,
 | 
			
		||||
					IntPtr.Zero
 | 
			
		||||
				);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public override void Reset()
 | 
			
		||||
		{
 | 
			
		||||
			Stop();
 | 
			
		||||
			PlaybackInitiated = false;
 | 
			
		||||
			base.Reset();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,39 @@
 | 
			
		|||
using System.Collections.Generic;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Audio
 | 
			
		||||
{
 | 
			
		||||
	internal class SourceVoicePool
 | 
			
		||||
	{
 | 
			
		||||
		private AudioDevice Device;
 | 
			
		||||
 | 
			
		||||
		Dictionary<(System.Type, Format), Queue<SourceVoice>> VoiceLists = new Dictionary<(System.Type, Format), Queue<SourceVoice>>();
 | 
			
		||||
 | 
			
		||||
		public SourceVoicePool(AudioDevice device)
 | 
			
		||||
		{
 | 
			
		||||
			Device = device;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public T Obtain<T>(Format format) where T : SourceVoice, IPoolable<T>
 | 
			
		||||
		{
 | 
			
		||||
			if (!VoiceLists.ContainsKey((typeof(T), format)))
 | 
			
		||||
			{
 | 
			
		||||
				VoiceLists.Add((typeof(T), format), new Queue<SourceVoice>());
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			var list = VoiceLists[(typeof(T), format)];
 | 
			
		||||
 | 
			
		||||
			if (list.Count == 0)
 | 
			
		||||
			{
 | 
			
		||||
				list.Enqueue(T.Create(Device, format));
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return (T) list.Dequeue();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public void Return(SourceVoice voice)
 | 
			
		||||
		{
 | 
			
		||||
			var list = VoiceLists[(voice.GetType(), voice.Format)];
 | 
			
		||||
			list.Enqueue(voice);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,295 +0,0 @@
 | 
			
		|||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Runtime.InteropServices;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Audio
 | 
			
		||||
{
 | 
			
		||||
	public class StaticSound : AudioResource
 | 
			
		||||
	{
 | 
			
		||||
		internal FAudio.FAudioBuffer Handle;
 | 
			
		||||
		public ushort FormatTag { get; }
 | 
			
		||||
		public ushort BitsPerSample { get; }
 | 
			
		||||
		public ushort Channels { get; }
 | 
			
		||||
		public uint SamplesPerSecond { get; }
 | 
			
		||||
		public ushort BlockAlign { get; }
 | 
			
		||||
 | 
			
		||||
		public uint LoopStart { get; set; } = 0;
 | 
			
		||||
		public uint LoopLength { get; set; } = 0;
 | 
			
		||||
 | 
			
		||||
		private Stack<StaticSoundInstance> Instances = new Stack<StaticSoundInstance>();
 | 
			
		||||
 | 
			
		||||
		public static StaticSound LoadOgg(AudioDevice device, string filePath)
 | 
			
		||||
		{
 | 
			
		||||
			var filePointer = FAudio.stb_vorbis_open_filename(filePath, out var error, IntPtr.Zero);
 | 
			
		||||
 | 
			
		||||
			if (error != 0)
 | 
			
		||||
			{
 | 
			
		||||
				throw new AudioLoadException("Error loading file!");
 | 
			
		||||
			}
 | 
			
		||||
			var info = FAudio.stb_vorbis_get_info(filePointer);
 | 
			
		||||
			var bufferSize = FAudio.stb_vorbis_stream_length_in_samples(filePointer) * info.channels;
 | 
			
		||||
			var buffer = new float[bufferSize];
 | 
			
		||||
 | 
			
		||||
			FAudio.stb_vorbis_get_samples_float_interleaved(
 | 
			
		||||
				filePointer,
 | 
			
		||||
				info.channels,
 | 
			
		||||
				buffer,
 | 
			
		||||
				(int) bufferSize
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
			FAudio.stb_vorbis_close(filePointer);
 | 
			
		||||
 | 
			
		||||
			return new StaticSound(
 | 
			
		||||
				device,
 | 
			
		||||
				(ushort) info.channels,
 | 
			
		||||
				info.sample_rate,
 | 
			
		||||
				buffer,
 | 
			
		||||
				0,
 | 
			
		||||
				(uint) buffer.Length
 | 
			
		||||
			);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// mostly borrowed from https://github.com/FNA-XNA/FNA/blob/b71b4a35ae59970ff0070dea6f8620856d8d4fec/src/Audio/SoundEffect.cs#L385
 | 
			
		||||
		public static StaticSound LoadWav(AudioDevice device, string filePath)
 | 
			
		||||
		{
 | 
			
		||||
			// Sample data
 | 
			
		||||
			byte[] data;
 | 
			
		||||
 | 
			
		||||
			// WaveFormatEx data
 | 
			
		||||
			ushort wFormatTag;
 | 
			
		||||
			ushort nChannels;
 | 
			
		||||
			uint nSamplesPerSec;
 | 
			
		||||
			uint nAvgBytesPerSec;
 | 
			
		||||
			ushort nBlockAlign;
 | 
			
		||||
			ushort wBitsPerSample;
 | 
			
		||||
			int samplerLoopStart = 0;
 | 
			
		||||
			int samplerLoopEnd = 0;
 | 
			
		||||
 | 
			
		||||
			using (BinaryReader reader = new BinaryReader(File.OpenRead(filePath)))
 | 
			
		||||
			{
 | 
			
		||||
				// RIFF Signature
 | 
			
		||||
				string signature = new string(reader.ReadChars(4));
 | 
			
		||||
				if (signature != "RIFF")
 | 
			
		||||
				{
 | 
			
		||||
					throw new NotSupportedException("Specified stream is not a wave file.");
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				reader.ReadUInt32(); // Riff Chunk Size
 | 
			
		||||
 | 
			
		||||
				string wformat = new string(reader.ReadChars(4));
 | 
			
		||||
				if (wformat != "WAVE")
 | 
			
		||||
				{
 | 
			
		||||
					throw new NotSupportedException("Specified stream is not a wave file.");
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// WAVE Header
 | 
			
		||||
				string format_signature = new string(reader.ReadChars(4));
 | 
			
		||||
				while (format_signature != "fmt ")
 | 
			
		||||
				{
 | 
			
		||||
					reader.ReadBytes(reader.ReadInt32());
 | 
			
		||||
					format_signature = new string(reader.ReadChars(4));
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				int format_chunk_size = reader.ReadInt32();
 | 
			
		||||
 | 
			
		||||
				wFormatTag = reader.ReadUInt16();
 | 
			
		||||
				nChannels = reader.ReadUInt16();
 | 
			
		||||
				nSamplesPerSec = reader.ReadUInt32();
 | 
			
		||||
				nAvgBytesPerSec = reader.ReadUInt32();
 | 
			
		||||
				nBlockAlign = reader.ReadUInt16();
 | 
			
		||||
				wBitsPerSample = reader.ReadUInt16();
 | 
			
		||||
 | 
			
		||||
				// Reads residual bytes
 | 
			
		||||
				if (format_chunk_size > 16)
 | 
			
		||||
				{
 | 
			
		||||
					reader.ReadBytes(format_chunk_size - 16);
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// data Signature
 | 
			
		||||
				string data_signature = new string(reader.ReadChars(4));
 | 
			
		||||
				while (data_signature.ToLowerInvariant() != "data")
 | 
			
		||||
				{
 | 
			
		||||
					reader.ReadBytes(reader.ReadInt32());
 | 
			
		||||
					data_signature = new string(reader.ReadChars(4));
 | 
			
		||||
				}
 | 
			
		||||
				if (data_signature != "data")
 | 
			
		||||
				{
 | 
			
		||||
					throw new NotSupportedException("Specified wave file is not supported.");
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				int waveDataLength = reader.ReadInt32();
 | 
			
		||||
				data = reader.ReadBytes(waveDataLength);
 | 
			
		||||
 | 
			
		||||
				// Scan for other chunks
 | 
			
		||||
				while (reader.PeekChar() != -1)
 | 
			
		||||
				{
 | 
			
		||||
					char[] chunkIDChars = reader.ReadChars(4);
 | 
			
		||||
					if (chunkIDChars.Length < 4)
 | 
			
		||||
					{
 | 
			
		||||
						break; // EOL!
 | 
			
		||||
					}
 | 
			
		||||
					byte[] chunkSizeBytes = reader.ReadBytes(4);
 | 
			
		||||
					if (chunkSizeBytes.Length < 4)
 | 
			
		||||
					{
 | 
			
		||||
						break; // EOL!
 | 
			
		||||
					}
 | 
			
		||||
					string chunk_signature = new string(chunkIDChars);
 | 
			
		||||
					int chunkDataSize = BitConverter.ToInt32(chunkSizeBytes, 0);
 | 
			
		||||
					if (chunk_signature == "smpl") // "smpl", Sampler Chunk Found
 | 
			
		||||
					{
 | 
			
		||||
						reader.ReadUInt32(); // Manufacturer
 | 
			
		||||
						reader.ReadUInt32(); // Product
 | 
			
		||||
						reader.ReadUInt32(); // Sample Period
 | 
			
		||||
						reader.ReadUInt32(); // MIDI Unity Note
 | 
			
		||||
						reader.ReadUInt32(); // MIDI Pitch Fraction
 | 
			
		||||
						reader.ReadUInt32(); // SMPTE Format
 | 
			
		||||
						reader.ReadUInt32(); // SMPTE Offset
 | 
			
		||||
						uint numSampleLoops = reader.ReadUInt32();
 | 
			
		||||
						int samplerData = reader.ReadInt32();
 | 
			
		||||
 | 
			
		||||
						for (int i = 0; i < numSampleLoops; i += 1)
 | 
			
		||||
						{
 | 
			
		||||
							reader.ReadUInt32(); // Cue Point ID
 | 
			
		||||
							reader.ReadUInt32(); // Type
 | 
			
		||||
							int start = reader.ReadInt32();
 | 
			
		||||
							int end = reader.ReadInt32();
 | 
			
		||||
							reader.ReadUInt32(); // Fraction
 | 
			
		||||
							reader.ReadUInt32(); // Play Count
 | 
			
		||||
 | 
			
		||||
							if (i == 0) // Grab loopStart and loopEnd from first sample loop
 | 
			
		||||
							{
 | 
			
		||||
								samplerLoopStart = start;
 | 
			
		||||
								samplerLoopEnd = end;
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
 | 
			
		||||
						if (samplerData != 0) // Read Sampler Data if it exists
 | 
			
		||||
						{
 | 
			
		||||
							reader.ReadBytes(samplerData);
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
					else // Read unwanted chunk data and try again
 | 
			
		||||
					{
 | 
			
		||||
						reader.ReadBytes(chunkDataSize);
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				// End scan
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return new StaticSound(
 | 
			
		||||
				device,
 | 
			
		||||
				wFormatTag,
 | 
			
		||||
				wBitsPerSample,
 | 
			
		||||
				nBlockAlign,
 | 
			
		||||
				nChannels,
 | 
			
		||||
				nSamplesPerSec,
 | 
			
		||||
				data,
 | 
			
		||||
				0,
 | 
			
		||||
				(uint) data.Length
 | 
			
		||||
			);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public StaticSound(
 | 
			
		||||
			AudioDevice device,
 | 
			
		||||
			ushort formatTag,
 | 
			
		||||
			ushort bitsPerSample,
 | 
			
		||||
			ushort blockAlign,
 | 
			
		||||
			ushort channels,
 | 
			
		||||
			uint samplesPerSecond,
 | 
			
		||||
			byte[] buffer,
 | 
			
		||||
			uint bufferOffset, /* number of bytes */
 | 
			
		||||
			uint bufferLength /* number of bytes */
 | 
			
		||||
		) : base(device)
 | 
			
		||||
		{
 | 
			
		||||
			FormatTag = formatTag;
 | 
			
		||||
			BitsPerSample = bitsPerSample;
 | 
			
		||||
			BlockAlign = blockAlign;
 | 
			
		||||
			Channels = channels;
 | 
			
		||||
			SamplesPerSecond = samplesPerSecond;
 | 
			
		||||
 | 
			
		||||
			Handle = new FAudio.FAudioBuffer();
 | 
			
		||||
			Handle.Flags = FAudio.FAUDIO_END_OF_STREAM;
 | 
			
		||||
			Handle.pContext = IntPtr.Zero;
 | 
			
		||||
			Handle.AudioBytes = bufferLength;
 | 
			
		||||
			Handle.pAudioData = Marshal.AllocHGlobal((int) bufferLength);
 | 
			
		||||
			Marshal.Copy(buffer, (int) bufferOffset, Handle.pAudioData, (int) bufferLength);
 | 
			
		||||
			Handle.PlayBegin = 0;
 | 
			
		||||
			Handle.PlayLength = 0;
 | 
			
		||||
 | 
			
		||||
			if (formatTag == 1)
 | 
			
		||||
			{
 | 
			
		||||
				Handle.PlayLength = (uint) (
 | 
			
		||||
					bufferLength /
 | 
			
		||||
					channels /
 | 
			
		||||
					(bitsPerSample / 8)
 | 
			
		||||
				);
 | 
			
		||||
			}
 | 
			
		||||
			else if (formatTag == 2)
 | 
			
		||||
			{
 | 
			
		||||
				Handle.PlayLength = (uint) (
 | 
			
		||||
					bufferLength /
 | 
			
		||||
					blockAlign *
 | 
			
		||||
					(((blockAlign / channels) - 6) * 2)
 | 
			
		||||
				);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			LoopStart = 0;
 | 
			
		||||
			LoopLength = 0;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public StaticSound(
 | 
			
		||||
			AudioDevice device,
 | 
			
		||||
			ushort channels,
 | 
			
		||||
			uint samplesPerSecond,
 | 
			
		||||
			float[] buffer,
 | 
			
		||||
			uint bufferOffset, /* in floats */
 | 
			
		||||
			uint bufferLength  /* in floats */
 | 
			
		||||
		) : base(device)
 | 
			
		||||
		{
 | 
			
		||||
			FormatTag = 3;
 | 
			
		||||
			BitsPerSample = 32;
 | 
			
		||||
			BlockAlign = (ushort) (4 * channels);
 | 
			
		||||
			Channels = channels;
 | 
			
		||||
			SamplesPerSecond = samplesPerSecond;
 | 
			
		||||
 | 
			
		||||
			var bufferLengthInBytes = (int) (bufferLength * sizeof(float));
 | 
			
		||||
			Handle = new FAudio.FAudioBuffer();
 | 
			
		||||
			Handle.Flags = FAudio.FAUDIO_END_OF_STREAM;
 | 
			
		||||
			Handle.pContext = IntPtr.Zero;
 | 
			
		||||
			Handle.AudioBytes = (uint) bufferLengthInBytes;
 | 
			
		||||
			Handle.pAudioData = Marshal.AllocHGlobal(bufferLengthInBytes);
 | 
			
		||||
			Marshal.Copy(buffer, (int) bufferOffset, Handle.pAudioData, (int) bufferLength);
 | 
			
		||||
			Handle.PlayBegin = 0;
 | 
			
		||||
			Handle.PlayLength = 0;
 | 
			
		||||
 | 
			
		||||
			LoopStart = 0;
 | 
			
		||||
			LoopLength = 0;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Gets a sound instance from the pool.
 | 
			
		||||
		/// NOTE: If you lose track of instances, you will create garbage collection pressure!
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public StaticSoundInstance GetInstance()
 | 
			
		||||
		{
 | 
			
		||||
			if (Instances.Count == 0)
 | 
			
		||||
			{
 | 
			
		||||
				Instances.Push(new StaticSoundInstance(Device, this));
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return Instances.Pop();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		internal void FreeInstance(StaticSoundInstance instance)
 | 
			
		||||
		{
 | 
			
		||||
			instance.Reset();
 | 
			
		||||
			Instances.Push(instance);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		protected override void Destroy()
 | 
			
		||||
		{
 | 
			
		||||
			Marshal.FreeHGlobal(Handle.pAudioData);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,123 +0,0 @@
 | 
			
		|||
using System;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Audio
 | 
			
		||||
{
 | 
			
		||||
	public class StaticSoundInstance : SoundInstance
 | 
			
		||||
	{
 | 
			
		||||
		public StaticSound Parent { get; }
 | 
			
		||||
 | 
			
		||||
		public bool Loop { get; set; }
 | 
			
		||||
 | 
			
		||||
		private SoundState _state = SoundState.Stopped;
 | 
			
		||||
		public override SoundState State
 | 
			
		||||
		{
 | 
			
		||||
			get
 | 
			
		||||
			{
 | 
			
		||||
				FAudio.FAudioSourceVoice_GetState(
 | 
			
		||||
					Handle,
 | 
			
		||||
					out var state,
 | 
			
		||||
					FAudio.FAUDIO_VOICE_NOSAMPLESPLAYED
 | 
			
		||||
				);
 | 
			
		||||
				if (state.BuffersQueued == 0)
 | 
			
		||||
				{
 | 
			
		||||
					StopImmediate();
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				return _state;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			protected set
 | 
			
		||||
			{
 | 
			
		||||
				_state = value;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		internal StaticSoundInstance(
 | 
			
		||||
			AudioDevice device,
 | 
			
		||||
			StaticSound parent
 | 
			
		||||
		) : base(device, parent.FormatTag, parent.BitsPerSample, parent.BlockAlign, parent.Channels, parent.SamplesPerSecond)
 | 
			
		||||
		{
 | 
			
		||||
			Parent = parent;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public override void Play()
 | 
			
		||||
		{
 | 
			
		||||
			if (State == SoundState.Playing)
 | 
			
		||||
			{
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (Loop)
 | 
			
		||||
			{
 | 
			
		||||
				Parent.Handle.LoopCount = 255;
 | 
			
		||||
				Parent.Handle.LoopBegin = Parent.LoopStart;
 | 
			
		||||
				Parent.Handle.LoopLength = Parent.LoopLength;
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
				Parent.Handle.LoopCount = 0;
 | 
			
		||||
				Parent.Handle.LoopBegin = 0;
 | 
			
		||||
				Parent.Handle.LoopLength = 0;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			FAudio.FAudioSourceVoice_SubmitSourceBuffer(
 | 
			
		||||
				Handle,
 | 
			
		||||
				ref Parent.Handle,
 | 
			
		||||
				IntPtr.Zero
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
			FAudio.FAudioSourceVoice_Start(Handle, 0, 0);
 | 
			
		||||
			State = SoundState.Playing;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public override void Pause()
 | 
			
		||||
		{
 | 
			
		||||
			if (State == SoundState.Paused)
 | 
			
		||||
			{
 | 
			
		||||
				FAudio.FAudioSourceVoice_Stop(Handle, 0, 0);
 | 
			
		||||
				State = SoundState.Paused;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public override void Stop()
 | 
			
		||||
		{
 | 
			
		||||
			FAudio.FAudioSourceVoice_ExitLoop(Handle, 0);
 | 
			
		||||
			State = SoundState.Stopped;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public override void StopImmediate()
 | 
			
		||||
		{
 | 
			
		||||
			FAudio.FAudioSourceVoice_Stop(Handle, 0, 0);
 | 
			
		||||
			FAudio.FAudioSourceVoice_FlushSourceBuffers(Handle);
 | 
			
		||||
			State = SoundState.Stopped;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public void Seek(uint sampleFrame)
 | 
			
		||||
		{
 | 
			
		||||
			if (State == SoundState.Playing)
 | 
			
		||||
			{
 | 
			
		||||
				FAudio.FAudioSourceVoice_Stop(Handle, 0, 0);
 | 
			
		||||
				FAudio.FAudioSourceVoice_FlushSourceBuffers(Handle);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			Parent.Handle.PlayBegin = sampleFrame;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public void Free()
 | 
			
		||||
		{
 | 
			
		||||
			Parent.FreeInstance(this);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		internal void Reset()
 | 
			
		||||
		{
 | 
			
		||||
			Pan = 0;
 | 
			
		||||
			Pitch = 0;
 | 
			
		||||
			Volume = 1;
 | 
			
		||||
			Reverb = 0;
 | 
			
		||||
			Loop = false;
 | 
			
		||||
			Is3D = false;
 | 
			
		||||
			FilterType = FilterType.None;
 | 
			
		||||
			Reverb = 0;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,165 +0,0 @@
 | 
			
		|||
using System;
 | 
			
		||||
using System.Runtime.InteropServices;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Audio
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// For streaming long playback.
 | 
			
		||||
	/// Must be extended with a decoder routine called by FillBuffer.
 | 
			
		||||
	/// See StreamingSoundOgg for an example.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public abstract class StreamingSound : SoundInstance
 | 
			
		||||
	{
 | 
			
		||||
		private const int BUFFER_COUNT = 3;
 | 
			
		||||
		private readonly IntPtr[] buffers;
 | 
			
		||||
		private int nextBufferIndex = 0;
 | 
			
		||||
		private uint queuedBufferCount = 0;
 | 
			
		||||
		protected abstract int BUFFER_SIZE { get; }
 | 
			
		||||
 | 
			
		||||
		public unsafe StreamingSound(
 | 
			
		||||
			AudioDevice device,
 | 
			
		||||
			ushort formatTag,
 | 
			
		||||
			ushort bitsPerSample,
 | 
			
		||||
			ushort blockAlign,
 | 
			
		||||
			ushort channels,
 | 
			
		||||
			uint samplesPerSecond
 | 
			
		||||
		) : base(device, formatTag, bitsPerSample, blockAlign, channels, samplesPerSecond)
 | 
			
		||||
		{
 | 
			
		||||
			device.AddDynamicSoundInstance(this);
 | 
			
		||||
 | 
			
		||||
			buffers = new IntPtr[BUFFER_COUNT];
 | 
			
		||||
			for (int i = 0; i < BUFFER_COUNT; i += 1)
 | 
			
		||||
			{
 | 
			
		||||
				buffers[i] = (IntPtr) NativeMemory.Alloc((nuint) BUFFER_SIZE);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public override void Play()
 | 
			
		||||
		{
 | 
			
		||||
			if (State == SoundState.Playing)
 | 
			
		||||
			{
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			State = SoundState.Playing;
 | 
			
		||||
 | 
			
		||||
			Update();
 | 
			
		||||
			FAudio.FAudioSourceVoice_Start(Handle, 0, 0);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public override void Pause()
 | 
			
		||||
		{
 | 
			
		||||
			if (State == SoundState.Playing)
 | 
			
		||||
			{
 | 
			
		||||
				FAudio.FAudioSourceVoice_Stop(Handle, 0, 0);
 | 
			
		||||
				State = SoundState.Paused;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public override void Stop()
 | 
			
		||||
		{
 | 
			
		||||
			State = SoundState.Stopped;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public override void StopImmediate()
 | 
			
		||||
		{
 | 
			
		||||
			FAudio.FAudioSourceVoice_Stop(Handle, 0, 0);
 | 
			
		||||
			FAudio.FAudioSourceVoice_FlushSourceBuffers(Handle);
 | 
			
		||||
			ClearBuffers();
 | 
			
		||||
 | 
			
		||||
			State = SoundState.Stopped;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		internal unsafe void Update()
 | 
			
		||||
		{
 | 
			
		||||
			if (State != SoundState.Playing)
 | 
			
		||||
			{
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			FAudio.FAudioSourceVoice_GetState(
 | 
			
		||||
				Handle,
 | 
			
		||||
				out var state,
 | 
			
		||||
				FAudio.FAUDIO_VOICE_NOSAMPLESPLAYED
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
			queuedBufferCount = state.BuffersQueued;
 | 
			
		||||
 | 
			
		||||
			QueueBuffers();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		protected void QueueBuffers()
 | 
			
		||||
		{
 | 
			
		||||
			for (int i = 0; i < BUFFER_COUNT - queuedBufferCount; i += 1)
 | 
			
		||||
			{
 | 
			
		||||
				AddBuffer();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		protected unsafe void ClearBuffers()
 | 
			
		||||
		{
 | 
			
		||||
			nextBufferIndex = 0;
 | 
			
		||||
			queuedBufferCount = 0;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		protected unsafe void AddBuffer()
 | 
			
		||||
		{
 | 
			
		||||
			var buffer = buffers[nextBufferIndex];
 | 
			
		||||
			nextBufferIndex = (nextBufferIndex + 1) % BUFFER_COUNT;
 | 
			
		||||
 | 
			
		||||
			FillBuffer(
 | 
			
		||||
				(void*) buffer,
 | 
			
		||||
				BUFFER_SIZE,
 | 
			
		||||
				out int filledLengthInBytes,
 | 
			
		||||
				out bool reachedEnd
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
			FAudio.FAudioBuffer buf = new FAudio.FAudioBuffer
 | 
			
		||||
			{
 | 
			
		||||
				AudioBytes = (uint) filledLengthInBytes,
 | 
			
		||||
				pAudioData = (IntPtr) buffer,
 | 
			
		||||
				PlayLength = (
 | 
			
		||||
					(uint) (filledLengthInBytes /
 | 
			
		||||
					Format.nChannels /
 | 
			
		||||
					(uint) (Format.wBitsPerSample / 8))
 | 
			
		||||
				)
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			FAudio.FAudioSourceVoice_SubmitSourceBuffer(
 | 
			
		||||
				Handle,
 | 
			
		||||
				ref buf,
 | 
			
		||||
				IntPtr.Zero
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
			queuedBufferCount += 1;
 | 
			
		||||
 | 
			
		||||
			/* We have reached the end of the file, what do we do? */
 | 
			
		||||
			if (reachedEnd)
 | 
			
		||||
			{
 | 
			
		||||
				OnReachedEnd();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		protected virtual void OnReachedEnd()
 | 
			
		||||
		{
 | 
			
		||||
			Stop();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		protected unsafe abstract void FillBuffer(
 | 
			
		||||
			void* buffer,
 | 
			
		||||
			int bufferLengthInBytes, /* in bytes */
 | 
			
		||||
			out int filledLengthInBytes, /* in bytes */
 | 
			
		||||
			out bool reachedEnd
 | 
			
		||||
		);
 | 
			
		||||
 | 
			
		||||
		protected unsafe override void Destroy()
 | 
			
		||||
		{
 | 
			
		||||
			StopImmediate();
 | 
			
		||||
 | 
			
		||||
			for (int i = 0; i < BUFFER_COUNT; i += 1)
 | 
			
		||||
			{
 | 
			
		||||
				NativeMemory.Free((void*) buffers[i]);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,90 +0,0 @@
 | 
			
		|||
using System;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Runtime.InteropServices;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Audio
 | 
			
		||||
{
 | 
			
		||||
	public class StreamingSoundOgg : StreamingSoundSeekable
 | 
			
		||||
	{
 | 
			
		||||
		private IntPtr VorbisHandle;
 | 
			
		||||
		private IntPtr FileDataPtr;
 | 
			
		||||
		private FAudio.stb_vorbis_info Info;
 | 
			
		||||
 | 
			
		||||
		protected override int BUFFER_SIZE => 32768;
 | 
			
		||||
 | 
			
		||||
		public unsafe static StreamingSoundOgg Load(AudioDevice device, string filePath)
 | 
			
		||||
		{
 | 
			
		||||
			var fileData = File.ReadAllBytes(filePath);
 | 
			
		||||
			var fileDataPtr = NativeMemory.Alloc((nuint) fileData.Length);
 | 
			
		||||
			Marshal.Copy(fileData, 0, (IntPtr) fileDataPtr, fileData.Length);
 | 
			
		||||
			var vorbisHandle = FAudio.stb_vorbis_open_memory((IntPtr) fileDataPtr, fileData.Length, out int error, IntPtr.Zero);
 | 
			
		||||
			if (error != 0)
 | 
			
		||||
			{
 | 
			
		||||
				NativeMemory.Free(fileDataPtr);
 | 
			
		||||
				Logger.LogError("Error opening OGG file!");
 | 
			
		||||
				Logger.LogError("Error: " + error);
 | 
			
		||||
				throw new AudioLoadException("Error opening OGG file!");
 | 
			
		||||
			}
 | 
			
		||||
			var info = FAudio.stb_vorbis_get_info(vorbisHandle);
 | 
			
		||||
 | 
			
		||||
			return new StreamingSoundOgg(
 | 
			
		||||
				device,
 | 
			
		||||
				(IntPtr) fileDataPtr,
 | 
			
		||||
				vorbisHandle,
 | 
			
		||||
				info
 | 
			
		||||
			);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		internal StreamingSoundOgg(
 | 
			
		||||
			AudioDevice device,
 | 
			
		||||
			IntPtr fileDataPtr, // MUST BE A NATIVE MEMORY HANDLE!!
 | 
			
		||||
			IntPtr vorbisHandle,
 | 
			
		||||
			FAudio.stb_vorbis_info info
 | 
			
		||||
		) : base(
 | 
			
		||||
			device,
 | 
			
		||||
			3, /* float type */
 | 
			
		||||
			32, /* size of float */
 | 
			
		||||
			(ushort) (4 * info.channels),
 | 
			
		||||
			(ushort) info.channels,
 | 
			
		||||
			info.sample_rate
 | 
			
		||||
		)
 | 
			
		||||
		{
 | 
			
		||||
			FileDataPtr = fileDataPtr;
 | 
			
		||||
			VorbisHandle = vorbisHandle;
 | 
			
		||||
			Info = info;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public override void Seek(uint sampleFrame)
 | 
			
		||||
		{
 | 
			
		||||
			FAudio.stb_vorbis_seek(VorbisHandle, sampleFrame);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		protected unsafe override void FillBuffer(
 | 
			
		||||
			void* buffer,
 | 
			
		||||
			int bufferLengthInBytes,
 | 
			
		||||
			out int filledLengthInBytes,
 | 
			
		||||
			out bool reachedEnd
 | 
			
		||||
		)
 | 
			
		||||
		{
 | 
			
		||||
			var lengthInFloats = bufferLengthInBytes / sizeof(float);
 | 
			
		||||
 | 
			
		||||
			/* NOTE: this function returns samples per channel, not total samples */
 | 
			
		||||
			var samples = FAudio.stb_vorbis_get_samples_float_interleaved(
 | 
			
		||||
				VorbisHandle,
 | 
			
		||||
				Info.channels,
 | 
			
		||||
				(IntPtr) buffer,
 | 
			
		||||
				lengthInFloats
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
			var sampleCount = samples * Info.channels;
 | 
			
		||||
			reachedEnd = sampleCount < lengthInFloats;
 | 
			
		||||
			filledLengthInBytes = sampleCount * sizeof(float);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		protected unsafe override void Destroy()
 | 
			
		||||
		{
 | 
			
		||||
			FAudio.stb_vorbis_close(VorbisHandle);
 | 
			
		||||
			NativeMemory.Free((void*) FileDataPtr);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,25 +0,0 @@
 | 
			
		|||
namespace MoonWorks.Audio
 | 
			
		||||
{
 | 
			
		||||
	public abstract class StreamingSoundSeekable : StreamingSound
 | 
			
		||||
	{
 | 
			
		||||
		public bool Loop { get; set; }
 | 
			
		||||
 | 
			
		||||
		protected StreamingSoundSeekable(AudioDevice device, ushort formatTag, ushort bitsPerSample, ushort blockAlign, ushort channels, uint samplesPerSecond) : base(device, formatTag, bitsPerSample, blockAlign, channels, samplesPerSecond)
 | 
			
		||||
		{
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public abstract void Seek(uint sampleFrame);
 | 
			
		||||
 | 
			
		||||
		protected override void OnReachedEnd()
 | 
			
		||||
		{
 | 
			
		||||
			if (Loop)
 | 
			
		||||
			{
 | 
			
		||||
				Seek(0);
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
				Stop();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,169 @@
 | 
			
		|||
using System;
 | 
			
		||||
using System.Runtime.InteropServices;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Audio
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// Use in conjunction with an AudioDataStreamable object to play back streaming audio data.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public class StreamingVoice : UpdatingSourceVoice, IPoolable<StreamingVoice>
 | 
			
		||||
	{
 | 
			
		||||
		private const int BUFFER_COUNT = 3;
 | 
			
		||||
		private readonly IntPtr[] buffers;
 | 
			
		||||
		private int nextBufferIndex = 0;
 | 
			
		||||
		private uint BufferSize;
 | 
			
		||||
 | 
			
		||||
		public bool Loop { get; set; }
 | 
			
		||||
 | 
			
		||||
		public AudioDataStreamable AudioData { get; protected set; }
 | 
			
		||||
 | 
			
		||||
		public unsafe StreamingVoice(AudioDevice device, Format format) : base(device, format)
 | 
			
		||||
		{
 | 
			
		||||
			buffers = new IntPtr[BUFFER_COUNT];
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static StreamingVoice Create(AudioDevice device, Format format)
 | 
			
		||||
		{
 | 
			
		||||
			return new StreamingVoice(device, format);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Loads and prepares an AudioDataStreamable for streaming playback.
 | 
			
		||||
		/// This automatically calls Load on the given AudioDataStreamable.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public void Load(AudioDataStreamable data)
 | 
			
		||||
		{
 | 
			
		||||
			lock (StateLock)
 | 
			
		||||
			{
 | 
			
		||||
				if (AudioData != null)
 | 
			
		||||
				{
 | 
			
		||||
					AudioData.Unload();
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				data.Load();
 | 
			
		||||
				AudioData = data;
 | 
			
		||||
 | 
			
		||||
				InitializeBuffers();
 | 
			
		||||
				QueueBuffers();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Unloads AudioDataStreamable from this voice.
 | 
			
		||||
		/// This automatically calls Unload on the given AudioDataStreamable.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public void Unload()
 | 
			
		||||
		{
 | 
			
		||||
			lock (StateLock)
 | 
			
		||||
			{
 | 
			
		||||
				if (AudioData != null)
 | 
			
		||||
				{
 | 
			
		||||
					Stop();
 | 
			
		||||
					AudioData.Unload();
 | 
			
		||||
					AudioData = null;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public override void Reset()
 | 
			
		||||
		{
 | 
			
		||||
			Unload();
 | 
			
		||||
			base.Reset();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public override void Update()
 | 
			
		||||
		{
 | 
			
		||||
			lock (StateLock)
 | 
			
		||||
			{
 | 
			
		||||
				if (AudioData == null || State != SoundState.Playing)
 | 
			
		||||
				{
 | 
			
		||||
					return;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				QueueBuffers();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private void QueueBuffers()
 | 
			
		||||
		{
 | 
			
		||||
			int buffersNeeded = BUFFER_COUNT - (int) BuffersQueued; // don't get got by uint underflow!
 | 
			
		||||
			for (int i = 0; i < buffersNeeded; i += 1)
 | 
			
		||||
			{
 | 
			
		||||
				AddBuffer();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private unsafe void AddBuffer()
 | 
			
		||||
		{
 | 
			
		||||
			var buffer = buffers[nextBufferIndex];
 | 
			
		||||
			nextBufferIndex = (nextBufferIndex + 1) % BUFFER_COUNT;
 | 
			
		||||
 | 
			
		||||
			AudioData.Decode(
 | 
			
		||||
				(void*) buffer,
 | 
			
		||||
				(int) BufferSize,
 | 
			
		||||
				out int filledLengthInBytes,
 | 
			
		||||
				out bool reachedEnd
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
			if (filledLengthInBytes > 0)
 | 
			
		||||
			{
 | 
			
		||||
				var buf = new FAudio.FAudioBuffer
 | 
			
		||||
				{
 | 
			
		||||
					AudioBytes = (uint) filledLengthInBytes,
 | 
			
		||||
					pAudioData = buffer,
 | 
			
		||||
					PlayLength = (
 | 
			
		||||
						(uint) (filledLengthInBytes /
 | 
			
		||||
						Format.Channels /
 | 
			
		||||
						(uint) (Format.BitsPerSample / 8))
 | 
			
		||||
					)
 | 
			
		||||
				};
 | 
			
		||||
 | 
			
		||||
				Submit(buf);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (reachedEnd)
 | 
			
		||||
			{
 | 
			
		||||
				/* We have reached the end of the data, what do we do? */
 | 
			
		||||
				if (Loop)
 | 
			
		||||
				{
 | 
			
		||||
					AudioData.Seek(0);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private unsafe void InitializeBuffers()
 | 
			
		||||
		{
 | 
			
		||||
			BufferSize = AudioData.DecodeBufferSize;
 | 
			
		||||
 | 
			
		||||
			for (int i = 0; i < BUFFER_COUNT; i += 1)
 | 
			
		||||
			{
 | 
			
		||||
				if (buffers[i] != IntPtr.Zero)
 | 
			
		||||
				{
 | 
			
		||||
					NativeMemory.Free((void*) buffers[i]);
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				buffers[i] = (IntPtr) NativeMemory.Alloc(BufferSize);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		protected override unsafe void Dispose(bool disposing)
 | 
			
		||||
		{
 | 
			
		||||
			if (!IsDisposed)
 | 
			
		||||
			{
 | 
			
		||||
				lock (StateLock)
 | 
			
		||||
				{
 | 
			
		||||
					Stop();
 | 
			
		||||
 | 
			
		||||
					for (int i = 0; i < BUFFER_COUNT; i += 1)
 | 
			
		||||
					{
 | 
			
		||||
						if (buffers[i] != IntPtr.Zero)
 | 
			
		||||
						{
 | 
			
		||||
							NativeMemory.Free((void*) buffers[i]);
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			base.Dispose(disposing);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,56 @@
 | 
			
		|||
using System;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Audio
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// SourceVoices can send audio to a SubmixVoice for convenient effects processing.
 | 
			
		||||
	/// Submixes process in order of processingStage, from lowest to highest.
 | 
			
		||||
	/// Therefore submixes early in a chain should have a low processingStage, and later in the chain they should have a higher one.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public class SubmixVoice : Voice
 | 
			
		||||
	{
 | 
			
		||||
		public SubmixVoice(
 | 
			
		||||
			AudioDevice device,
 | 
			
		||||
			uint sourceChannelCount,
 | 
			
		||||
			uint sampleRate,
 | 
			
		||||
			uint processingStage
 | 
			
		||||
		) : base(device, sourceChannelCount, device.DeviceDetails.OutputFormat.Format.nChannels)
 | 
			
		||||
		{
 | 
			
		||||
			FAudio.FAudio_CreateSubmixVoice(
 | 
			
		||||
				device.Handle,
 | 
			
		||||
				out handle,
 | 
			
		||||
				sourceChannelCount,
 | 
			
		||||
				sampleRate,
 | 
			
		||||
				FAudio.FAUDIO_VOICE_USEFILTER,
 | 
			
		||||
				processingStage,
 | 
			
		||||
				IntPtr.Zero,
 | 
			
		||||
				IntPtr.Zero
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
			SetOutputVoice(device.MasteringVoice);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private SubmixVoice(
 | 
			
		||||
			AudioDevice device
 | 
			
		||||
		) : base(device, device.DeviceDetails.OutputFormat.Format.nChannels, device.DeviceDetails.OutputFormat.Format.nChannels)
 | 
			
		||||
		{
 | 
			
		||||
			FAudio.FAudio_CreateSubmixVoice(
 | 
			
		||||
				device.Handle,
 | 
			
		||||
				out handle,
 | 
			
		||||
				device.DeviceDetails.OutputFormat.Format.nChannels,
 | 
			
		||||
				device.DeviceDetails.OutputFormat.Format.nSamplesPerSec,
 | 
			
		||||
				FAudio.FAUDIO_VOICE_USEFILTER,
 | 
			
		||||
				int.MaxValue,
 | 
			
		||||
				IntPtr.Zero, // default sends to mastering voice
 | 
			
		||||
				IntPtr.Zero
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
			OutputVoice = null;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		internal static SubmixVoice CreateFauxMasteringVoice(AudioDevice device)
 | 
			
		||||
		{
 | 
			
		||||
			return new SubmixVoice(device);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,29 @@
 | 
			
		|||
namespace MoonWorks.Audio
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// TransientVoice is intended for playing one-off sound effects that don't have a long term reference. <br/>
 | 
			
		||||
	/// It will be automatically returned to the AudioDevice SourceVoice pool once it is done playing back.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public class TransientVoice : UpdatingSourceVoice, IPoolable<TransientVoice>
 | 
			
		||||
	{
 | 
			
		||||
		static TransientVoice IPoolable<TransientVoice>.Create(AudioDevice device, Format format)
 | 
			
		||||
		{
 | 
			
		||||
			return new TransientVoice(device, format);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public TransientVoice(AudioDevice device, Format format) : base(device, format)
 | 
			
		||||
		{
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public override void Update()
 | 
			
		||||
		{
 | 
			
		||||
			lock (StateLock)
 | 
			
		||||
			{
 | 
			
		||||
				if (PlaybackInitiated && BuffersQueued == 0)
 | 
			
		||||
				{
 | 
			
		||||
					Return();
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,11 @@
 | 
			
		|||
namespace MoonWorks.Audio
 | 
			
		||||
{
 | 
			
		||||
	public abstract class UpdatingSourceVoice : SourceVoice
 | 
			
		||||
	{
 | 
			
		||||
		protected UpdatingSourceVoice(AudioDevice device, Format format) : base(device, format)
 | 
			
		||||
		{
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public abstract void Update();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,578 @@
 | 
			
		|||
using System;
 | 
			
		||||
using System.Runtime.InteropServices;
 | 
			
		||||
using EasingFunction = System.Func<float, float>;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Audio
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// Handles audio playback from audio buffer data. Can be configured with a variety of parameters.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public abstract unsafe class Voice : AudioResource
 | 
			
		||||
	{
 | 
			
		||||
		protected IntPtr handle;
 | 
			
		||||
		public IntPtr Handle => handle;
 | 
			
		||||
 | 
			
		||||
		public uint SourceChannelCount { get; }
 | 
			
		||||
		public uint DestinationChannelCount { get; }
 | 
			
		||||
 | 
			
		||||
		protected SubmixVoice OutputVoice;
 | 
			
		||||
		private ReverbEffect ReverbEffect;
 | 
			
		||||
 | 
			
		||||
		protected byte* pMatrixCoefficients;
 | 
			
		||||
 | 
			
		||||
		public bool Is3D { get; protected set; }
 | 
			
		||||
 | 
			
		||||
		private float dopplerFactor;
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// The strength of the doppler effect on this voice.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public float DopplerFactor
 | 
			
		||||
		{
 | 
			
		||||
			get => dopplerFactor;
 | 
			
		||||
			set
 | 
			
		||||
			{
 | 
			
		||||
				if (dopplerFactor != value)
 | 
			
		||||
				{
 | 
			
		||||
					dopplerFactor = value;
 | 
			
		||||
					UpdatePitch();
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private float volume = 1;
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// The overall volume level for the voice.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public float Volume
 | 
			
		||||
		{
 | 
			
		||||
			get => volume;
 | 
			
		||||
			internal set
 | 
			
		||||
			{
 | 
			
		||||
				value = Math.MathHelper.Max(0, value);
 | 
			
		||||
				if (volume != value)
 | 
			
		||||
				{
 | 
			
		||||
					volume = value;
 | 
			
		||||
					FAudio.FAudioVoice_SetVolume(Handle, volume, 0);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private float pitch = 0;
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// The pitch of the voice.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public float Pitch
 | 
			
		||||
		{
 | 
			
		||||
			get => pitch;
 | 
			
		||||
			internal set
 | 
			
		||||
			{
 | 
			
		||||
				value = Math.MathHelper.Clamp(value, -1f, 1f);
 | 
			
		||||
				if (pitch != value)
 | 
			
		||||
				{
 | 
			
		||||
					pitch = value;
 | 
			
		||||
					UpdatePitch();
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private const float MAX_FILTER_FREQUENCY = 1f;
 | 
			
		||||
		private const float MAX_FILTER_ONEOVERQ = 1.5f;
 | 
			
		||||
 | 
			
		||||
		private FAudio.FAudioFilterParameters filterParameters = new FAudio.FAudioFilterParameters
 | 
			
		||||
		{
 | 
			
		||||
			Type = FAudio.FAudioFilterType.FAudioLowPassFilter,
 | 
			
		||||
			Frequency = 1f,
 | 
			
		||||
			OneOverQ = 1f
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// The frequency cutoff on the voice filter.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public float FilterFrequency
 | 
			
		||||
		{
 | 
			
		||||
			get => filterParameters.Frequency;
 | 
			
		||||
			internal set
 | 
			
		||||
			{
 | 
			
		||||
				value = System.Math.Clamp(value, 0.01f, MAX_FILTER_FREQUENCY);
 | 
			
		||||
				if (filterParameters.Frequency != value)
 | 
			
		||||
				{
 | 
			
		||||
					filterParameters.Frequency = value;
 | 
			
		||||
 | 
			
		||||
					FAudio.FAudioVoice_SetFilterParameters(
 | 
			
		||||
						Handle,
 | 
			
		||||
						ref filterParameters,
 | 
			
		||||
						0
 | 
			
		||||
					);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Reciprocal of Q factor.
 | 
			
		||||
		/// Controls how quickly frequencies beyond the filter frequency are dampened.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public float FilterOneOverQ
 | 
			
		||||
		{
 | 
			
		||||
			get => filterParameters.OneOverQ;
 | 
			
		||||
			internal set
 | 
			
		||||
			{
 | 
			
		||||
				value = System.Math.Clamp(value, 0.01f, MAX_FILTER_ONEOVERQ);
 | 
			
		||||
				if (filterParameters.OneOverQ != value)
 | 
			
		||||
				{
 | 
			
		||||
					filterParameters.OneOverQ = value;
 | 
			
		||||
 | 
			
		||||
					FAudio.FAudioVoice_SetFilterParameters(
 | 
			
		||||
						Handle,
 | 
			
		||||
						ref filterParameters,
 | 
			
		||||
						0
 | 
			
		||||
					);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private FilterType filterType;
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// The frequency filter that is applied to the voice.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public FilterType FilterType
 | 
			
		||||
		{
 | 
			
		||||
			get => filterType;
 | 
			
		||||
			set
 | 
			
		||||
			{
 | 
			
		||||
				if (filterType != value)
 | 
			
		||||
				{
 | 
			
		||||
					filterType = value;
 | 
			
		||||
 | 
			
		||||
					switch (filterType)
 | 
			
		||||
					{
 | 
			
		||||
						case FilterType.None:
 | 
			
		||||
							filterParameters = new FAudio.FAudioFilterParameters
 | 
			
		||||
							{
 | 
			
		||||
								Type = FAudio.FAudioFilterType.FAudioLowPassFilter,
 | 
			
		||||
								Frequency = 1f,
 | 
			
		||||
								OneOverQ = 1f
 | 
			
		||||
							};
 | 
			
		||||
							break;
 | 
			
		||||
 | 
			
		||||
						case FilterType.LowPass:
 | 
			
		||||
							filterParameters.Type = FAudio.FAudioFilterType.FAudioLowPassFilter;
 | 
			
		||||
							filterParameters.Frequency = 1f;
 | 
			
		||||
							break;
 | 
			
		||||
 | 
			
		||||
						case FilterType.BandPass:
 | 
			
		||||
							filterParameters.Type = FAudio.FAudioFilterType.FAudioBandPassFilter;
 | 
			
		||||
							break;
 | 
			
		||||
 | 
			
		||||
						case FilterType.HighPass:
 | 
			
		||||
							filterParameters.Type = FAudio.FAudioFilterType.FAudioHighPassFilter;
 | 
			
		||||
							filterParameters.Frequency = 0f;
 | 
			
		||||
							break;
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					FAudio.FAudioVoice_SetFilterParameters(
 | 
			
		||||
						Handle,
 | 
			
		||||
						ref filterParameters,
 | 
			
		||||
						0
 | 
			
		||||
					);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		protected float pan = 0;
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Left-right panning. -1 is hard left pan, 1 is hard right pan.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public float Pan
 | 
			
		||||
		{
 | 
			
		||||
			get => pan;
 | 
			
		||||
			internal set
 | 
			
		||||
			{
 | 
			
		||||
				value = Math.MathHelper.Clamp(value, -1f, 1f);
 | 
			
		||||
				if (pan != value)
 | 
			
		||||
				{
 | 
			
		||||
					pan = value;
 | 
			
		||||
 | 
			
		||||
					if (pan < -1f)
 | 
			
		||||
					{
 | 
			
		||||
						pan = -1f;
 | 
			
		||||
					}
 | 
			
		||||
					if (pan > 1f)
 | 
			
		||||
					{
 | 
			
		||||
						pan = 1f;
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					if (Is3D) { return; }
 | 
			
		||||
 | 
			
		||||
					SetPanMatrixCoefficients();
 | 
			
		||||
					FAudio.FAudioVoice_SetOutputMatrix(
 | 
			
		||||
						Handle,
 | 
			
		||||
						OutputVoice.Handle,
 | 
			
		||||
						SourceChannelCount,
 | 
			
		||||
						DestinationChannelCount,
 | 
			
		||||
						(nint) pMatrixCoefficients,
 | 
			
		||||
						0
 | 
			
		||||
					);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private float reverb;
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// The wet-dry mix of the reverb effect.
 | 
			
		||||
		/// Has no effect if SetReverbEffectChain has not been called.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public unsafe float Reverb
 | 
			
		||||
		{
 | 
			
		||||
			get => reverb;
 | 
			
		||||
			internal set
 | 
			
		||||
			{
 | 
			
		||||
				if (ReverbEffect != null)
 | 
			
		||||
				{
 | 
			
		||||
					value = MathF.Max(0, value);
 | 
			
		||||
					if (reverb != value)
 | 
			
		||||
					{
 | 
			
		||||
						reverb = value;
 | 
			
		||||
 | 
			
		||||
						float* outputMatrix = (float*) pMatrixCoefficients;
 | 
			
		||||
						outputMatrix[0] = reverb;
 | 
			
		||||
						if (SourceChannelCount == 2)
 | 
			
		||||
						{
 | 
			
		||||
							outputMatrix[1] = reverb;
 | 
			
		||||
						}
 | 
			
		||||
 | 
			
		||||
						FAudio.FAudioVoice_SetOutputMatrix(
 | 
			
		||||
							Handle,
 | 
			
		||||
							ReverbEffect.Handle,
 | 
			
		||||
							SourceChannelCount,
 | 
			
		||||
							1,
 | 
			
		||||
							(nint) pMatrixCoefficients,
 | 
			
		||||
							0
 | 
			
		||||
						);
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				#if DEBUG
 | 
			
		||||
				if (ReverbEffect == null)
 | 
			
		||||
				{
 | 
			
		||||
					Logger.LogWarn("Tried to set reverb value before applying a reverb effect");
 | 
			
		||||
				}
 | 
			
		||||
				#endif
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public Voice(AudioDevice device, uint sourceChannelCount, uint destinationChannelCount) : base(device)
 | 
			
		||||
		{
 | 
			
		||||
			SourceChannelCount = sourceChannelCount;
 | 
			
		||||
			DestinationChannelCount = destinationChannelCount;
 | 
			
		||||
			nuint memsize = 4 * sourceChannelCount * destinationChannelCount;
 | 
			
		||||
			pMatrixCoefficients = (byte*) NativeMemory.AllocZeroed(memsize);
 | 
			
		||||
			SetPanMatrixCoefficients();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Sets the pitch of the voice. Valid input range is -1f to 1f.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public void SetPitch(float targetValue)
 | 
			
		||||
		{
 | 
			
		||||
			Pitch = targetValue;
 | 
			
		||||
			Device.ClearTweens(this, AudioTweenProperty.Pitch);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Sets the pitch of the voice over a time duration in seconds.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="easingFunction">An easing function. See MoonWorks.Math.Easing.Function.Float</param>
 | 
			
		||||
		public void SetPitch(float targetValue, float duration, EasingFunction easingFunction)
 | 
			
		||||
		{
 | 
			
		||||
			Device.CreateTween(this, AudioTweenProperty.Pitch, easingFunction, Pitch, targetValue, duration, 0);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Sets the pitch of the voice over a time duration in seconds after a delay in seconds.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="easingFunction">An easing function. See MoonWorks.Math.Easing.Function.Float</param>
 | 
			
		||||
		public void SetPitch(float targetValue, float delayTime, float duration, EasingFunction easingFunction)
 | 
			
		||||
		{
 | 
			
		||||
			Device.CreateTween(this, AudioTweenProperty.Pitch, easingFunction, Pitch, targetValue, duration, delayTime);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Sets the volume of the voice. Minimum value is 0f.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public void SetVolume(float targetValue)
 | 
			
		||||
		{
 | 
			
		||||
			Volume = targetValue;
 | 
			
		||||
			Device.ClearTweens(this, AudioTweenProperty.Volume);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Sets the volume of the voice over a time duration in seconds.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="easingFunction">An easing function. See MoonWorks.Math.Easing.Function.Float</param>
 | 
			
		||||
		public void SetVolume(float targetValue, float duration, EasingFunction easingFunction)
 | 
			
		||||
		{
 | 
			
		||||
			Device.CreateTween(this, AudioTweenProperty.Volume, easingFunction, Volume, targetValue, duration, 0);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Sets the volume of the voice over a time duration in seconds after a delay in seconds.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="easingFunction">An easing function. See MoonWorks.Math.Easing.Function.Float</param>
 | 
			
		||||
		public void SetVolume(float targetValue, float delayTime, float duration, EasingFunction easingFunction)
 | 
			
		||||
		{
 | 
			
		||||
			Device.CreateTween(this, AudioTweenProperty.Volume, easingFunction, Volume, targetValue, duration, delayTime);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Sets the frequency cutoff on the voice filter. Valid range is 0.01f to 1f.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public void SetFilterFrequency(float targetValue)
 | 
			
		||||
		{
 | 
			
		||||
			FilterFrequency = targetValue;
 | 
			
		||||
			Device.ClearTweens(this, AudioTweenProperty.FilterFrequency);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Sets the frequency cutoff on the voice filter over a time duration in seconds.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="easingFunction">An easing function. See MoonWorks.Math.Easing.Function.Float</param>
 | 
			
		||||
		public void SetFilterFrequency(float targetValue, float duration, EasingFunction easingFunction)
 | 
			
		||||
		{
 | 
			
		||||
			Device.CreateTween(this, AudioTweenProperty.FilterFrequency, easingFunction, FilterFrequency, targetValue, duration, 0);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Sets the frequency cutoff on the voice filter over a time duration in seconds after a delay in seconds.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="easingFunction">An easing function. See MoonWorks.Math.Easing.Function.Float</param>
 | 
			
		||||
		public void SetFilterFrequency(float targetValue, float delayTime, float duration, EasingFunction easingFunction)
 | 
			
		||||
		{
 | 
			
		||||
			Device.CreateTween(this, AudioTweenProperty.FilterFrequency, easingFunction, FilterFrequency, targetValue, duration, delayTime);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Sets reciprocal of Q factor on the frequency filter.
 | 
			
		||||
		/// Controls how quickly frequencies beyond the filter frequency are dampened.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public void SetFilterOneOverQ(float targetValue)
 | 
			
		||||
		{
 | 
			
		||||
			FilterOneOverQ = targetValue;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Sets a left-right panning value. -1f is hard left pan, 1f is hard right pan.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public virtual void SetPan(float targetValue)
 | 
			
		||||
		{
 | 
			
		||||
			Pan = targetValue;
 | 
			
		||||
			Device.ClearTweens(this, AudioTweenProperty.Pan);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Sets a left-right panning value over a time duration in seconds.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="easingFunction">An easing function. See MoonWorks.Math.Easing.Function.Float</param>
 | 
			
		||||
		public virtual void SetPan(float targetValue, float duration, EasingFunction easingFunction)
 | 
			
		||||
		{
 | 
			
		||||
			Device.CreateTween(this, AudioTweenProperty.Pan, easingFunction, Pan, targetValue, duration, 0);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Sets a left-right panning value over a time duration in seconds after a delay in seconds.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="easingFunction">An easing function. See MoonWorks.Math.Easing.Function.Float</param>
 | 
			
		||||
		public virtual void SetPan(float targetValue, float delayTime, float duration, EasingFunction easingFunction)
 | 
			
		||||
		{
 | 
			
		||||
			Device.CreateTween(this, AudioTweenProperty.Pan, easingFunction, Pan, targetValue, duration, delayTime);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Sets the wet-dry mix value of the reverb effect. Minimum value is 0f.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public virtual void SetReverb(float targetValue)
 | 
			
		||||
		{
 | 
			
		||||
			Reverb = targetValue;
 | 
			
		||||
			Device.ClearTweens(this, AudioTweenProperty.Reverb);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Sets the wet-dry mix value of the reverb effect over a time duration in seconds.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="easingFunction">An easing function. See MoonWorks.Math.Easing.Function.Float</param>
 | 
			
		||||
		public virtual void SetReverb(float targetValue, float duration, EasingFunction easingFunction)
 | 
			
		||||
		{
 | 
			
		||||
			Device.CreateTween(this, AudioTweenProperty.Reverb, easingFunction, Volume, targetValue, duration, 0);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Sets the wet-dry mix value of the reverb effect over a time duration in seconds after a delay in seconds.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="easingFunction">An easing function. See MoonWorks.Math.Easing.Function.Float</param>
 | 
			
		||||
		public virtual void SetReverb(float targetValue, float delayTime, float duration, EasingFunction easingFunction)
 | 
			
		||||
		{
 | 
			
		||||
			Device.CreateTween(this, AudioTweenProperty.Reverb, easingFunction, Volume, targetValue, duration, delayTime);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Sets the output voice for this voice.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="send">Where the output should be sent.</param>
 | 
			
		||||
		public unsafe void SetOutputVoice(SubmixVoice send)
 | 
			
		||||
		{
 | 
			
		||||
			OutputVoice = send;
 | 
			
		||||
 | 
			
		||||
			if (ReverbEffect != null)
 | 
			
		||||
			{
 | 
			
		||||
				SetReverbEffectChain(ReverbEffect);
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
				FAudio.FAudioSendDescriptor* sendDesc = stackalloc FAudio.FAudioSendDescriptor[1];
 | 
			
		||||
				sendDesc[0].Flags = 0;
 | 
			
		||||
				sendDesc[0].pOutputVoice = send.Handle;
 | 
			
		||||
 | 
			
		||||
				var sends = new FAudio.FAudioVoiceSends();
 | 
			
		||||
				sends.SendCount = 1;
 | 
			
		||||
				sends.pSends = (nint) sendDesc;
 | 
			
		||||
 | 
			
		||||
				FAudio.FAudioVoice_SetOutputVoices(
 | 
			
		||||
					Handle,
 | 
			
		||||
					ref sends
 | 
			
		||||
				);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Applies a reverb effect chain to this voice.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public unsafe void SetReverbEffectChain(ReverbEffect reverbEffect)
 | 
			
		||||
		{
 | 
			
		||||
			var sendDesc = stackalloc FAudio.FAudioSendDescriptor[2];
 | 
			
		||||
			sendDesc[0].Flags = 0;
 | 
			
		||||
			sendDesc[0].pOutputVoice = OutputVoice.Handle;
 | 
			
		||||
			sendDesc[1].Flags = 0;
 | 
			
		||||
			sendDesc[1].pOutputVoice = reverbEffect.Handle;
 | 
			
		||||
 | 
			
		||||
			var sends = new FAudio.FAudioVoiceSends();
 | 
			
		||||
			sends.SendCount = 2;
 | 
			
		||||
			sends.pSends = (nint) sendDesc;
 | 
			
		||||
 | 
			
		||||
			FAudio.FAudioVoice_SetOutputVoices(
 | 
			
		||||
				Handle,
 | 
			
		||||
				ref sends
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
			ReverbEffect = reverbEffect;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Removes the reverb effect chain from this voice.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public void RemoveReverbEffectChain()
 | 
			
		||||
		{
 | 
			
		||||
			if (ReverbEffect != null)
 | 
			
		||||
			{
 | 
			
		||||
				ReverbEffect = null;
 | 
			
		||||
				reverb = 0;
 | 
			
		||||
				SetOutputVoice(OutputVoice);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Resets all voice parameters to defaults.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public virtual void Reset()
 | 
			
		||||
		{
 | 
			
		||||
			RemoveReverbEffectChain();
 | 
			
		||||
			Volume = 1;
 | 
			
		||||
			Pan = 0;
 | 
			
		||||
			Pitch = 0;
 | 
			
		||||
			FilterType = FilterType.None;
 | 
			
		||||
			SetOutputVoice(Device.MasteringVoice);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Taken from https://github.com/FNA-XNA/FNA/blob/master/src/Audio/SoundEffectInstance.cs
 | 
			
		||||
		private unsafe void SetPanMatrixCoefficients()
 | 
			
		||||
		{
 | 
			
		||||
			/* Two major things to notice:
 | 
			
		||||
			 * 1. The spec assumes any speaker count >= 2 has Front Left/Right.
 | 
			
		||||
			 * 2. Stereo panning is WAY more complicated than you think.
 | 
			
		||||
			 *    The main thing is that hard panning does NOT eliminate an
 | 
			
		||||
			 *    entire channel; the two channels are blended on each side.
 | 
			
		||||
			 * -flibit
 | 
			
		||||
			 */
 | 
			
		||||
			float* outputMatrix = (float*) pMatrixCoefficients;
 | 
			
		||||
			if (SourceChannelCount == 1)
 | 
			
		||||
			{
 | 
			
		||||
				if (DestinationChannelCount == 1)
 | 
			
		||||
				{
 | 
			
		||||
					outputMatrix[0] = 1.0f;
 | 
			
		||||
				}
 | 
			
		||||
				else
 | 
			
		||||
				{
 | 
			
		||||
					outputMatrix[0] = (pan > 0.0f) ? (1.0f - pan) : 1.0f;
 | 
			
		||||
					outputMatrix[1] = (pan < 0.0f) ? (1.0f + pan) : 1.0f;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
				if (DestinationChannelCount == 1)
 | 
			
		||||
				{
 | 
			
		||||
					outputMatrix[0] = 1.0f;
 | 
			
		||||
					outputMatrix[1] = 1.0f;
 | 
			
		||||
				}
 | 
			
		||||
				else
 | 
			
		||||
				{
 | 
			
		||||
					if (pan <= 0.0f)
 | 
			
		||||
					{
 | 
			
		||||
						// Left speaker blends left/right channels
 | 
			
		||||
						outputMatrix[0] = 0.5f * pan + 1.0f;
 | 
			
		||||
						outputMatrix[1] = 0.5f * -pan;
 | 
			
		||||
						// Right speaker gets less of the right channel
 | 
			
		||||
						outputMatrix[2] = 0.0f;
 | 
			
		||||
						outputMatrix[3] = pan + 1.0f;
 | 
			
		||||
					}
 | 
			
		||||
					else
 | 
			
		||||
					{
 | 
			
		||||
						// Left speaker gets less of the left channel
 | 
			
		||||
						outputMatrix[0] = -pan + 1.0f;
 | 
			
		||||
						outputMatrix[1] = 0.0f;
 | 
			
		||||
						// Right speaker blends right/left channels
 | 
			
		||||
						outputMatrix[2] = 0.5f * pan;
 | 
			
		||||
						outputMatrix[3] = 0.5f * -pan + 1.0f;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		protected void UpdatePitch()
 | 
			
		||||
		{
 | 
			
		||||
			float doppler;
 | 
			
		||||
			float dopplerScale = Device.DopplerScale;
 | 
			
		||||
			if (!Is3D || dopplerScale == 0.0f)
 | 
			
		||||
			{
 | 
			
		||||
				doppler = 1.0f;
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
				doppler = DopplerFactor * dopplerScale;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			FAudio.FAudioSourceVoice_SetFrequencyRatio(
 | 
			
		||||
				Handle,
 | 
			
		||||
				(float) System.Math.Pow(2.0, pitch) * doppler,
 | 
			
		||||
				0
 | 
			
		||||
			);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		protected override unsafe void Dispose(bool disposing)
 | 
			
		||||
		{
 | 
			
		||||
			if (!IsDisposed)
 | 
			
		||||
			{
 | 
			
		||||
				NativeMemory.Free(pMatrixCoefficients);
 | 
			
		||||
				FAudio.FAudioVoice_DestroyVoice(Handle);
 | 
			
		||||
			}
 | 
			
		||||
			base.Dispose(disposing);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,181 +0,0 @@
 | 
			
		|||
using System.Collections.Generic;
 | 
			
		||||
using MoonWorks.Math.Fixed;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Collision.Fixed
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// Axis-aligned bounding box.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public struct AABB2D : System.IEquatable<AABB2D>
 | 
			
		||||
	{
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// The top-left position of the AABB.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <value></value>
 | 
			
		||||
		public Vector2 Min { get; private set; }
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// The bottom-right position of the AABB.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <value></value>
 | 
			
		||||
		public Vector2 Max { get; private set; }
 | 
			
		||||
 | 
			
		||||
		public Fix64 Width { get { return Max.X - Min.X; } }
 | 
			
		||||
		public Fix64 Height { get { return Max.Y - Min.Y; } }
 | 
			
		||||
 | 
			
		||||
		public Fix64 Right { get { return Max.X; } }
 | 
			
		||||
		public Fix64 Left { get { return Min.X; } }
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// The top of the AABB. Assumes a downward-aligned Y axis, so this value will be smaller than Bottom.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <value></value>
 | 
			
		||||
		public Fix64 Top { get { return Min.Y; } }
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// The bottom of the AABB. Assumes a downward-aligned Y axis, so this value will be larger than Top.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <value></value>
 | 
			
		||||
		public Fix64 Bottom { get { return Max.Y; } }
 | 
			
		||||
 | 
			
		||||
		public AABB2D(Fix64 minX, Fix64 minY, Fix64 maxX, Fix64 maxY)
 | 
			
		||||
		{
 | 
			
		||||
			Min = new Vector2(minX, minY);
 | 
			
		||||
			Max = new Vector2(maxX, maxY);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public AABB2D(int minX, int minY, int maxX, int maxY)
 | 
			
		||||
		{
 | 
			
		||||
			Min = new Vector2(minX, minY);
 | 
			
		||||
			Max = new Vector2(maxX, maxY);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public AABB2D(Vector2 min, Vector2 max)
 | 
			
		||||
		{
 | 
			
		||||
			Min = min;
 | 
			
		||||
			Max = max;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private static Matrix3x2 AbsoluteMatrix(Matrix3x2 matrix)
 | 
			
		||||
		{
 | 
			
		||||
			return new Matrix3x2
 | 
			
		||||
			(
 | 
			
		||||
				Fix64.Abs(matrix.M11), Fix64.Abs(matrix.M12),
 | 
			
		||||
				Fix64.Abs(matrix.M21), Fix64.Abs(matrix.M22),
 | 
			
		||||
				Fix64.Abs(matrix.M31), Fix64.Abs(matrix.M32)
 | 
			
		||||
			);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Efficiently transforms the AABB by a Transform2D.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="aabb"></param>
 | 
			
		||||
		/// <param name="transform"></param>
 | 
			
		||||
		/// <returns></returns>
 | 
			
		||||
		public static AABB2D Transformed(AABB2D aabb, Transform2D transform)
 | 
			
		||||
		{
 | 
			
		||||
			var two = new Fix64(2);
 | 
			
		||||
			var center = (aabb.Min + aabb.Max) / two;
 | 
			
		||||
            var extent = (aabb.Max - aabb.Min) / two;
 | 
			
		||||
 | 
			
		||||
            var newCenter = Vector2.Transform(center, transform.TransformMatrix);
 | 
			
		||||
            var newExtent = Vector2.TransformNormal(extent, AbsoluteMatrix(transform.TransformMatrix));
 | 
			
		||||
 | 
			
		||||
            return new AABB2D(newCenter - newExtent, newCenter + newExtent);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public AABB2D Compose(AABB2D aabb)
 | 
			
		||||
		{
 | 
			
		||||
			Fix64 left = Left;
 | 
			
		||||
			Fix64 top = Top;
 | 
			
		||||
			Fix64 right = Right;
 | 
			
		||||
			Fix64 bottom = Bottom;
 | 
			
		||||
 | 
			
		||||
			if (aabb.Left < left)
 | 
			
		||||
			{
 | 
			
		||||
				left = aabb.Left;
 | 
			
		||||
			}
 | 
			
		||||
			if (aabb.Right > right)
 | 
			
		||||
			{
 | 
			
		||||
				right = aabb.Right;
 | 
			
		||||
			}
 | 
			
		||||
			if (aabb.Top < top)
 | 
			
		||||
			{
 | 
			
		||||
				top = aabb.Top;
 | 
			
		||||
			}
 | 
			
		||||
			if (aabb.Bottom > bottom)
 | 
			
		||||
			{
 | 
			
		||||
				bottom = aabb.Bottom;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return new AABB2D(left, top, right, bottom);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Creates an AABB for an arbitrary collection of positions.
 | 
			
		||||
		/// This is less efficient than defining a custom AABB method for most shapes, so avoid using this if possible.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="vertices"></param>
 | 
			
		||||
		/// <returns></returns>
 | 
			
		||||
		public static AABB2D FromVertices(IEnumerable<Vector2> vertices)
 | 
			
		||||
		{
 | 
			
		||||
			var minX = Fix64.MaxValue;
 | 
			
		||||
			var minY = Fix64.MaxValue;
 | 
			
		||||
			var maxX = Fix64.MinValue;
 | 
			
		||||
			var maxY = Fix64.MinValue;
 | 
			
		||||
 | 
			
		||||
			foreach (var vertex in vertices)
 | 
			
		||||
			{
 | 
			
		||||
				if (vertex.X < minX)
 | 
			
		||||
				{
 | 
			
		||||
					minX = vertex.X;
 | 
			
		||||
				}
 | 
			
		||||
				if (vertex.Y < minY)
 | 
			
		||||
				{
 | 
			
		||||
					minY = vertex.Y;
 | 
			
		||||
				}
 | 
			
		||||
				if (vertex.X > maxX)
 | 
			
		||||
				{
 | 
			
		||||
					maxX = vertex.X;
 | 
			
		||||
				}
 | 
			
		||||
				if (vertex.Y > maxY)
 | 
			
		||||
				{
 | 
			
		||||
					maxY = vertex.Y;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return new AABB2D(minX, minY, maxX, maxY);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static bool TestOverlap(AABB2D a, AABB2D b)
 | 
			
		||||
		{
 | 
			
		||||
			return a.Left < b.Right && a.Right > b.Left && a.Top < b.Bottom && a.Bottom > b.Top;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public override bool Equals(object obj)
 | 
			
		||||
		{
 | 
			
		||||
			return obj is AABB2D aabb && Equals(aabb);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public bool Equals(AABB2D other)
 | 
			
		||||
		{
 | 
			
		||||
			return Min == other.Min &&
 | 
			
		||||
				   Max == other.Max;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public override int GetHashCode()
 | 
			
		||||
		{
 | 
			
		||||
			return System.HashCode.Combine(Min, Max);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static bool operator ==(AABB2D left, AABB2D right)
 | 
			
		||||
		{
 | 
			
		||||
			return left.Equals(right);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static bool operator !=(AABB2D left, AABB2D right)
 | 
			
		||||
		{
 | 
			
		||||
			return !(left == right);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,12 +0,0 @@
 | 
			
		|||
using System.Collections.Generic;
 | 
			
		||||
using MoonWorks.Math.Fixed;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Collision.Fixed
 | 
			
		||||
{
 | 
			
		||||
	public interface ICollidable
 | 
			
		||||
	{
 | 
			
		||||
		IEnumerable<IShape2D> Shapes { get; }
 | 
			
		||||
		AABB2D AABB { get; }
 | 
			
		||||
		AABB2D TransformedAABB(Transform2D transform);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,15 +0,0 @@
 | 
			
		|||
using MoonWorks.Math.Fixed;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Collision.Fixed
 | 
			
		||||
{
 | 
			
		||||
	public interface IShape2D : ICollidable, System.IEquatable<IShape2D>
 | 
			
		||||
	{
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// A Minkowski support function. Gives the farthest point on the edge of a shape along the given direction.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="direction">A normalized Vector2.</param>
 | 
			
		||||
		/// <param name="transform">A Transform for transforming the shape vertices.</param>
 | 
			
		||||
		/// <returns>The farthest point on the edge of the shape along the given direction.</returns>
 | 
			
		||||
		Vector2 Support(Vector2 direction, Transform2D transform);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,57 +0,0 @@
 | 
			
		|||
using MoonWorks.Math.Fixed;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Collision.Fixed
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// A Minkowski difference between two shapes.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public struct MinkowskiDifference : System.IEquatable<MinkowskiDifference>
 | 
			
		||||
	{
 | 
			
		||||
		private IShape2D ShapeA { get; }
 | 
			
		||||
		private Transform2D TransformA { get; }
 | 
			
		||||
		private IShape2D ShapeB { get; }
 | 
			
		||||
		private Transform2D TransformB { get; }
 | 
			
		||||
 | 
			
		||||
		public MinkowskiDifference(IShape2D shapeA, Transform2D transformA, IShape2D shapeB, Transform2D transformB)
 | 
			
		||||
		{
 | 
			
		||||
			ShapeA = shapeA;
 | 
			
		||||
			TransformA = transformA;
 | 
			
		||||
			ShapeB = shapeB;
 | 
			
		||||
			TransformB = transformB;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public Vector2 Support(Vector2 direction)
 | 
			
		||||
		{
 | 
			
		||||
			return ShapeA.Support(direction, TransformA) - ShapeB.Support(-direction, TransformB);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public override bool Equals(object other)
 | 
			
		||||
		{
 | 
			
		||||
			return other is MinkowskiDifference minkowskiDifference && Equals(minkowskiDifference);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public bool Equals(MinkowskiDifference other)
 | 
			
		||||
		{
 | 
			
		||||
			return
 | 
			
		||||
				ShapeA == other.ShapeA &&
 | 
			
		||||
				TransformA == other.TransformA &&
 | 
			
		||||
				ShapeB == other.ShapeB &&
 | 
			
		||||
				TransformB == other.TransformB;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public override int GetHashCode()
 | 
			
		||||
		{
 | 
			
		||||
			return System.HashCode.Combine(ShapeA, TransformA, ShapeB, TransformB);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static bool operator ==(MinkowskiDifference a, MinkowskiDifference b)
 | 
			
		||||
		{
 | 
			
		||||
			return a.Equals(b);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static bool operator !=(MinkowskiDifference a, MinkowskiDifference b)
 | 
			
		||||
		{
 | 
			
		||||
			return !(a == b);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,333 +0,0 @@
 | 
			
		|||
using MoonWorks.Math.Fixed;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Collision.Fixed
 | 
			
		||||
{
 | 
			
		||||
	public static class NarrowPhase
 | 
			
		||||
	{
 | 
			
		||||
		private struct Edge
 | 
			
		||||
		{
 | 
			
		||||
			public Fix64 Distance;
 | 
			
		||||
			public Vector2 Normal;
 | 
			
		||||
			public int Index;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
        public static bool TestCollision(ICollidable collidableA, Transform2D transformA, ICollidable collidableB, Transform2D transformB)
 | 
			
		||||
        {
 | 
			
		||||
            foreach (var shapeA in collidableA.Shapes)
 | 
			
		||||
            {
 | 
			
		||||
                foreach (var shapeB in collidableB.Shapes)
 | 
			
		||||
                {
 | 
			
		||||
					if (TestCollision(shapeA, transformA, shapeB, transformB))
 | 
			
		||||
                    {
 | 
			
		||||
						return true;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static bool TestCollision(IShape2D shapeA, Transform2D transformA, IShape2D shapeB, Transform2D transformB)
 | 
			
		||||
		{
 | 
			
		||||
			// If we can use a fast path check, let's do that!
 | 
			
		||||
			if (shapeA is Rectangle rectangleA && shapeB is Rectangle rectangleB && transformA.IsAxisAligned && transformB.IsAxisAligned)
 | 
			
		||||
			{
 | 
			
		||||
				return TestRectangleOverlap(rectangleA, transformA, rectangleB, transformB);
 | 
			
		||||
			}
 | 
			
		||||
			else if (shapeA is Point && shapeB is Rectangle && transformB.IsAxisAligned)
 | 
			
		||||
			{
 | 
			
		||||
				return TestPointRectangleOverlap((Point) shapeA, transformA, (Rectangle) shapeB, transformB);
 | 
			
		||||
			}
 | 
			
		||||
			else if (shapeA is Rectangle && shapeB is Point && transformA.IsAxisAligned)
 | 
			
		||||
			{
 | 
			
		||||
				return TestPointRectangleOverlap((Point) shapeB, transformB, (Rectangle) shapeA, transformA);
 | 
			
		||||
			}
 | 
			
		||||
			else if (shapeA is Rectangle && shapeB is Circle && transformA.IsAxisAligned && transformB.IsUniformScale)
 | 
			
		||||
			{
 | 
			
		||||
				return TestCircleRectangleOverlap((Circle) shapeB, transformB, (Rectangle) shapeA, transformA);
 | 
			
		||||
			}
 | 
			
		||||
			else if (shapeA is Circle && shapeB is Rectangle && transformA.IsUniformScale && transformB.IsAxisAligned)
 | 
			
		||||
			{
 | 
			
		||||
				return TestCircleRectangleOverlap((Circle) shapeA, transformA, (Rectangle) shapeB, transformB);
 | 
			
		||||
			}
 | 
			
		||||
			else if (shapeA is Circle && shapeB is Point && transformA.IsUniformScale)
 | 
			
		||||
			{
 | 
			
		||||
				return TestCirclePointOverlap((Circle) shapeA, transformA, (Point) shapeB, transformB);
 | 
			
		||||
			}
 | 
			
		||||
			else if (shapeA is Point && shapeB is Circle && transformB.IsUniformScale)
 | 
			
		||||
			{
 | 
			
		||||
				return TestCirclePointOverlap((Circle) shapeB, transformB, (Point) shapeA, transformA);
 | 
			
		||||
			}
 | 
			
		||||
			else if (shapeA is Circle circleA && shapeB is Circle circleB && transformA.IsUniformScale && transformB.IsUniformScale)
 | 
			
		||||
			{
 | 
			
		||||
				return TestCircleOverlap(circleA, transformA, circleB, transformB);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Sad, we can't do a fast path optimization. Time for a simplex reduction.
 | 
			
		||||
			return FindCollisionSimplex(shapeA, transformA, shapeB, transformB).Item1;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static bool TestRectangleOverlap(Rectangle rectangleA, Transform2D transformA, Rectangle rectangleB, Transform2D transformB)
 | 
			
		||||
		{
 | 
			
		||||
			var firstAABB = rectangleA.TransformedAABB(transformA);
 | 
			
		||||
			var secondAABB = rectangleB.TransformedAABB(transformB);
 | 
			
		||||
 | 
			
		||||
			return firstAABB.Left < secondAABB.Right && firstAABB.Right > secondAABB.Left && firstAABB.Top < secondAABB.Bottom && firstAABB.Bottom > secondAABB.Top;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static bool TestPointRectangleOverlap(Point point, Transform2D pointTransform, Rectangle rectangle, Transform2D rectangleTransform)
 | 
			
		||||
		{
 | 
			
		||||
			var transformedPoint = pointTransform.Position;
 | 
			
		||||
			var AABB = rectangle.TransformedAABB(rectangleTransform);
 | 
			
		||||
 | 
			
		||||
			return transformedPoint.X > AABB.Left && transformedPoint.X < AABB.Right && transformedPoint.Y < AABB.Bottom && transformedPoint.Y > AABB.Top;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static bool TestCirclePointOverlap(Circle circle, Transform2D circleTransform, Point point, Transform2D pointTransform)
 | 
			
		||||
		{
 | 
			
		||||
			var circleCenter = circleTransform.Position;
 | 
			
		||||
			var circleRadius = circle.Radius * circleTransform.Scale.X;
 | 
			
		||||
 | 
			
		||||
			var distanceX = circleCenter.X - pointTransform.Position.X;
 | 
			
		||||
			var distanceY = circleCenter.Y - pointTransform.Position.Y;
 | 
			
		||||
 | 
			
		||||
			return (distanceX * distanceX) + (distanceY * distanceY) < (circleRadius * circleRadius);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// NOTE: The rectangle must be axis aligned, and the scaling of the circle must be uniform.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public static bool TestCircleRectangleOverlap(Circle circle, Transform2D circleTransform, Rectangle rectangle, Transform2D rectangleTransform)
 | 
			
		||||
		{
 | 
			
		||||
			var circleCenter = circleTransform.Position;
 | 
			
		||||
			var circleRadius = circle.Radius * circleTransform.Scale.X;
 | 
			
		||||
			var AABB = rectangle.TransformedAABB(rectangleTransform);
 | 
			
		||||
 | 
			
		||||
			var closestX = Fix64.Clamp(circleCenter.X, AABB.Left, AABB.Right);
 | 
			
		||||
			var closestY = Fix64.Clamp(circleCenter.Y, AABB.Top, AABB.Bottom);
 | 
			
		||||
 | 
			
		||||
			var distanceX = circleCenter.X - closestX;
 | 
			
		||||
			var distanceY = circleCenter.Y - closestY;
 | 
			
		||||
 | 
			
		||||
			var distanceSquared = (distanceX * distanceX) + (distanceY * distanceY);
 | 
			
		||||
			return distanceSquared < (circleRadius * circleRadius);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static bool TestCircleOverlap(Circle circleA, Transform2D transformA, Circle circleB, Transform2D transformB)
 | 
			
		||||
		{
 | 
			
		||||
			var radiusA = circleA.Radius * transformA.Scale.X;
 | 
			
		||||
			var radiusB = circleB.Radius * transformB.Scale.Y;
 | 
			
		||||
 | 
			
		||||
			var centerA = transformA.Position;
 | 
			
		||||
			var centerB = transformB.Position;
 | 
			
		||||
 | 
			
		||||
			var distanceSquared = (centerA - centerB).LengthSquared();
 | 
			
		||||
			var radiusSumSquared = (radiusA + radiusB) * (radiusA + radiusB);
 | 
			
		||||
 | 
			
		||||
			return distanceSquared < radiusSumSquared;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static (bool, Simplex2D) FindCollisionSimplex(IShape2D shapeA, Transform2D transformA, IShape2D shapeB, Transform2D transformB)
 | 
			
		||||
		{
 | 
			
		||||
			var minkowskiDifference = new MinkowskiDifference(shapeA, transformA, shapeB, transformB);
 | 
			
		||||
			var c = minkowskiDifference.Support(Vector2.UnitX);
 | 
			
		||||
			var b = minkowskiDifference.Support(-Vector2.UnitX);
 | 
			
		||||
			return Check(minkowskiDifference, c, b);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
        public unsafe static Vector2 Intersect(IShape2D shapeA, Transform2D Transform2DA, IShape2D shapeB, Transform2D Transform2DB, Simplex2D simplex)
 | 
			
		||||
        {
 | 
			
		||||
            if (shapeA == null) { throw new System.ArgumentNullException(nameof(shapeA)); }
 | 
			
		||||
            if (shapeB == null) { throw new System.ArgumentNullException(nameof(shapeB)); }
 | 
			
		||||
            if (!simplex.TwoSimplex) { throw new System.ArgumentException("Simplex must be a 2-Simplex.", nameof(simplex)); }
 | 
			
		||||
 | 
			
		||||
			var epsilon = Fix64.FromFraction(1, 10000);
 | 
			
		||||
 | 
			
		||||
			var a = simplex.A;
 | 
			
		||||
            var b = simplex.B.Value;
 | 
			
		||||
            var c = simplex.C.Value;
 | 
			
		||||
 | 
			
		||||
            Vector2 intersection = default;
 | 
			
		||||
 | 
			
		||||
            for (var i = 0; i < 32; i++)
 | 
			
		||||
            {
 | 
			
		||||
                var edge = FindClosestEdge(simplex);
 | 
			
		||||
                var support = CalculateSupport(shapeA, Transform2DA, shapeB, Transform2DB, edge.Normal);
 | 
			
		||||
                var distance = Vector2.Dot(support, edge.Normal);
 | 
			
		||||
 | 
			
		||||
                intersection = edge.Normal;
 | 
			
		||||
                intersection *= distance;
 | 
			
		||||
 | 
			
		||||
                if (Fix64.Abs(distance - edge.Distance) <= epsilon)
 | 
			
		||||
                {
 | 
			
		||||
                    return intersection;
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
					simplex.Insert(support, edge.Index);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return intersection; // close enough
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static unsafe Edge FindClosestEdge(Simplex2D simplex)
 | 
			
		||||
        {
 | 
			
		||||
			var closestDistance = Fix64.MaxValue;
 | 
			
		||||
            var closestNormal = Vector2.Zero;
 | 
			
		||||
            var closestIndex = 0;
 | 
			
		||||
 | 
			
		||||
			for (var i = 0; i < 4; i += 1)
 | 
			
		||||
			{
 | 
			
		||||
				var j = (i + 1 == 3) ? 0 : i + 1;
 | 
			
		||||
 | 
			
		||||
				var a = simplex[i];
 | 
			
		||||
				var b = simplex[j];
 | 
			
		||||
 | 
			
		||||
				var e = b - a;
 | 
			
		||||
 | 
			
		||||
				var oa = a;
 | 
			
		||||
 | 
			
		||||
				var n = Vector2.Normalize(TripleProduct(e, oa, e));
 | 
			
		||||
 | 
			
		||||
				var d = Vector2.Dot(n, a);
 | 
			
		||||
 | 
			
		||||
				if (d < closestDistance)
 | 
			
		||||
				{
 | 
			
		||||
					closestDistance = d;
 | 
			
		||||
					closestNormal = n;
 | 
			
		||||
					closestIndex = j;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
            return new Edge
 | 
			
		||||
			{
 | 
			
		||||
				Distance = closestDistance,
 | 
			
		||||
				Normal = closestNormal,
 | 
			
		||||
				Index = closestIndex
 | 
			
		||||
			};
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static Vector2 CalculateSupport(IShape2D shapeA, Transform2D Transform2DA, IShape2D shapeB, Transform2D Transform2DB, Vector2 direction)
 | 
			
		||||
        {
 | 
			
		||||
            return shapeA.Support(direction, Transform2DA) - shapeB.Support(-direction, Transform2DB);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
		private static (bool, Simplex2D) Check(MinkowskiDifference minkowskiDifference, Vector2 c, Vector2 b)
 | 
			
		||||
        {
 | 
			
		||||
            var cb = c - b;
 | 
			
		||||
            var c0 = -c;
 | 
			
		||||
            var d = Direction(cb, c0);
 | 
			
		||||
            return DoSimplex(minkowskiDifference, new Simplex2D(b, c), d);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static (bool, Simplex2D) DoSimplex(MinkowskiDifference minkowskiDifference, Simplex2D simplex, Vector2 direction)
 | 
			
		||||
        {
 | 
			
		||||
            var a = minkowskiDifference.Support(direction);
 | 
			
		||||
            var notPastOrigin = Vector2.Dot(a, direction) < Fix64.Zero;
 | 
			
		||||
            var (intersects, newSimplex, newDirection) = EnclosesOrigin(a, simplex);
 | 
			
		||||
 | 
			
		||||
            if (notPastOrigin)
 | 
			
		||||
            {
 | 
			
		||||
                return (false, default(Simplex2D));
 | 
			
		||||
            }
 | 
			
		||||
            else if (intersects)
 | 
			
		||||
            {
 | 
			
		||||
                return (true, new Simplex2D(simplex.A, simplex.B.Value, a));
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                return DoSimplex(minkowskiDifference, newSimplex, newDirection);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static (bool, Simplex2D, Vector2) EnclosesOrigin(Vector2 a, Simplex2D simplex)
 | 
			
		||||
        {
 | 
			
		||||
            if (simplex.ZeroSimplex)
 | 
			
		||||
            {
 | 
			
		||||
                return HandleZeroSimplex(a, simplex.A);
 | 
			
		||||
            }
 | 
			
		||||
            else if (simplex.OneSimplex)
 | 
			
		||||
            {
 | 
			
		||||
                return HandleOneSimplex(a, simplex.A, simplex.B.Value);
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                return (false, simplex, Vector2.Zero);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static (bool, Simplex2D, Vector2) HandleZeroSimplex(Vector2 a, Vector2 b)
 | 
			
		||||
        {
 | 
			
		||||
            var ab = b - a;
 | 
			
		||||
            var a0 = -a;
 | 
			
		||||
            var (newSimplex, newDirection) = SameDirection(ab, a0) ? (new Simplex2D(a, b), Perpendicular(ab, a0)) : (new Simplex2D(a), a0);
 | 
			
		||||
            return (false, newSimplex, newDirection);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static (bool, Simplex2D, Vector2) HandleOneSimplex(Vector2 a, Vector2 b, Vector2 c)
 | 
			
		||||
        {
 | 
			
		||||
            var a0 = -a;
 | 
			
		||||
            var ab = b - a;
 | 
			
		||||
            var ac = c - a;
 | 
			
		||||
            var abp = Perpendicular(ab, -ac);
 | 
			
		||||
            var acp = Perpendicular(ac, -ab);
 | 
			
		||||
 | 
			
		||||
            if (SameDirection(abp, a0))
 | 
			
		||||
            {
 | 
			
		||||
                if (SameDirection(ab, a0))
 | 
			
		||||
                {
 | 
			
		||||
                    return (false, new Simplex2D(a, b), abp);
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    return (false, new Simplex2D(a), a0);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else if (SameDirection(acp, a0))
 | 
			
		||||
            {
 | 
			
		||||
                if (SameDirection(ac, a0))
 | 
			
		||||
                {
 | 
			
		||||
                    return (false, new Simplex2D(a, c), acp);
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    return (false, new Simplex2D(a), a0);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                return (true, new Simplex2D(b, c), a0);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static Vector2 TripleProduct(Vector2 a, Vector2 b, Vector2 c)
 | 
			
		||||
        {
 | 
			
		||||
            var A = new Vector3(a.X, a.Y, Fix64.Zero);
 | 
			
		||||
            var B = new Vector3(b.X, b.Y, Fix64.Zero);
 | 
			
		||||
            var C = new Vector3(c.X, c.Y, Fix64.Zero);
 | 
			
		||||
 | 
			
		||||
            var first = Vector3.Cross(A, B);
 | 
			
		||||
            var second = Vector3.Cross(first, C);
 | 
			
		||||
 | 
			
		||||
            return new Vector2(second.X, second.Y);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static Vector2 Direction(Vector2 a, Vector2 b)
 | 
			
		||||
        {
 | 
			
		||||
            var d = TripleProduct(a, b, a);
 | 
			
		||||
            var collinear = d == Vector2.Zero;
 | 
			
		||||
            return collinear ? new Vector2(a.Y, -a.X) : d;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static bool SameDirection(Vector2 a, Vector2 b)
 | 
			
		||||
        {
 | 
			
		||||
            return Vector2.Dot(a, b) > Fix64.Zero;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static Vector2 Perpendicular(Vector2 a, Vector2 b)
 | 
			
		||||
        {
 | 
			
		||||
            return TripleProduct(a, b, a);
 | 
			
		||||
        }
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,73 +0,0 @@
 | 
			
		|||
using System.Collections.Generic;
 | 
			
		||||
using MoonWorks.Math.Fixed;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Collision.Fixed
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// A Circle is a shape defined by a radius.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public struct Circle : IShape2D, System.IEquatable<Circle>
 | 
			
		||||
	{
 | 
			
		||||
		public Fix64 Radius { get; }
 | 
			
		||||
		public AABB2D AABB { get; }
 | 
			
		||||
		public IEnumerable<IShape2D> Shapes
 | 
			
		||||
		{
 | 
			
		||||
			get
 | 
			
		||||
			{
 | 
			
		||||
				yield return this;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public Circle(Fix64 radius)
 | 
			
		||||
		{
 | 
			
		||||
			Radius = radius;
 | 
			
		||||
			AABB = new AABB2D(-Radius, -Radius, Radius, Radius);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public Circle(int radius)
 | 
			
		||||
		{
 | 
			
		||||
			Radius = (Fix64) radius;
 | 
			
		||||
			AABB = new AABB2D(-Radius, -Radius, Radius, Radius);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public Vector2 Support(Vector2 direction, Transform2D transform)
 | 
			
		||||
		{
 | 
			
		||||
			return Vector2.Transform(Vector2.Normalize(direction) * Radius, transform.TransformMatrix);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public AABB2D TransformedAABB(Transform2D transform2D)
 | 
			
		||||
		{
 | 
			
		||||
			return AABB2D.Transformed(AABB, transform2D);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public override bool Equals(object obj)
 | 
			
		||||
		{
 | 
			
		||||
			return obj is IShape2D other && Equals(other);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public bool Equals(IShape2D other)
 | 
			
		||||
		{
 | 
			
		||||
			return other is Circle circle && Equals(circle);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public bool Equals(Circle other)
 | 
			
		||||
		{
 | 
			
		||||
			return Radius == other.Radius;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public override int GetHashCode()
 | 
			
		||||
		{
 | 
			
		||||
			return System.HashCode.Combine(Radius);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static bool operator ==(Circle a, Circle b)
 | 
			
		||||
		{
 | 
			
		||||
			return a.Equals(b);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static bool operator !=(Circle a, Circle b)
 | 
			
		||||
		{
 | 
			
		||||
			return !(a == b);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,83 +0,0 @@
 | 
			
		|||
using System.Collections.Generic;
 | 
			
		||||
using MoonWorks.Math.Fixed;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Collision.Fixed
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
    /// A line is a shape defined by exactly two points in space.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public struct Line : IShape2D, System.IEquatable<Line>
 | 
			
		||||
    {
 | 
			
		||||
        public Vector2 Start { get; }
 | 
			
		||||
        public Vector2 End { get; }
 | 
			
		||||
 | 
			
		||||
        public AABB2D AABB { get; }
 | 
			
		||||
 | 
			
		||||
		public IEnumerable<IShape2D> Shapes
 | 
			
		||||
        {
 | 
			
		||||
            get
 | 
			
		||||
            {
 | 
			
		||||
				yield return this;
 | 
			
		||||
			}
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
		public Line(Vector2 start, Vector2 end)
 | 
			
		||||
        {
 | 
			
		||||
            Start = start;
 | 
			
		||||
            End = end;
 | 
			
		||||
 | 
			
		||||
            AABB = new AABB2D(
 | 
			
		||||
				Fix64.Min(Start.X, End.X),
 | 
			
		||||
				Fix64.Min(Start.Y, End.Y),
 | 
			
		||||
				Fix64.Max(Start.X, End.X),
 | 
			
		||||
				Fix64.Max(Start.Y, End.Y)
 | 
			
		||||
			);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Vector2 Support(Vector2 direction, Transform2D transform)
 | 
			
		||||
        {
 | 
			
		||||
            var transformedStart = Vector2.Transform(Start, transform.TransformMatrix);
 | 
			
		||||
            var transformedEnd = Vector2.Transform(End, transform.TransformMatrix);
 | 
			
		||||
            return Vector2.Dot(transformedStart, direction) > Vector2.Dot(transformedEnd, direction) ?
 | 
			
		||||
                transformedStart :
 | 
			
		||||
                transformedEnd;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public AABB2D TransformedAABB(Transform2D transform)
 | 
			
		||||
        {
 | 
			
		||||
            return AABB2D.Transformed(AABB, transform);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override bool Equals(object obj)
 | 
			
		||||
        {
 | 
			
		||||
            return obj is IShape2D other && Equals(other);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public bool Equals(IShape2D other)
 | 
			
		||||
        {
 | 
			
		||||
            return other is Line otherLine && Equals(otherLine);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public bool Equals(Line other)
 | 
			
		||||
        {
 | 
			
		||||
            return
 | 
			
		||||
				(Start == other.Start && End == other.End) ||
 | 
			
		||||
				(End == other.Start && Start == other.End);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override int GetHashCode()
 | 
			
		||||
        {
 | 
			
		||||
            return System.HashCode.Combine(Start, End);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static bool operator ==(Line a, Line b)
 | 
			
		||||
        {
 | 
			
		||||
            return a.Equals(b);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static bool operator !=(Line a, Line b)
 | 
			
		||||
        {
 | 
			
		||||
            return !(a == b);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,61 +0,0 @@
 | 
			
		|||
using System.Collections.Generic;
 | 
			
		||||
using MoonWorks.Math.Fixed;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Collision.Fixed
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// A Point is "that which has no part".
 | 
			
		||||
	/// All points by themselves are identical.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public struct Point : IShape2D, System.IEquatable<Point>
 | 
			
		||||
	{
 | 
			
		||||
		public AABB2D AABB { get; }
 | 
			
		||||
		public IEnumerable<IShape2D> Shapes
 | 
			
		||||
        {
 | 
			
		||||
            get
 | 
			
		||||
            {
 | 
			
		||||
				yield return this;
 | 
			
		||||
			}
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
		public AABB2D TransformedAABB(Transform2D transform)
 | 
			
		||||
		{
 | 
			
		||||
			return AABB2D.Transformed(AABB, transform);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public Vector2 Support(Vector2 direction, Transform2D transform)
 | 
			
		||||
		{
 | 
			
		||||
			return Vector2.Transform(Vector2.Zero, transform.TransformMatrix);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public override bool Equals(object obj)
 | 
			
		||||
		{
 | 
			
		||||
			return obj is IShape2D other && Equals(other);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public bool Equals(IShape2D other)
 | 
			
		||||
		{
 | 
			
		||||
			return other is Point otherPoint && Equals(otherPoint);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public bool Equals(Point other)
 | 
			
		||||
		{
 | 
			
		||||
			return true;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public override int GetHashCode()
 | 
			
		||||
		{
 | 
			
		||||
			return 0;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static bool operator ==(Point a, Point b)
 | 
			
		||||
		{
 | 
			
		||||
			return true;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static bool operator !=(Point a, Point b)
 | 
			
		||||
		{
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,130 +0,0 @@
 | 
			
		|||
using System.Collections.Generic;
 | 
			
		||||
using MoonWorks.Math.Fixed;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Collision.Fixed
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// A rectangle is a shape defined by a width and height. The origin is the center of the rectangle.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public struct Rectangle : IShape2D, System.IEquatable<Rectangle>
 | 
			
		||||
	{
 | 
			
		||||
		public AABB2D AABB { get; }
 | 
			
		||||
		public Fix64 Width { get; }
 | 
			
		||||
		public Fix64 Height { get; }
 | 
			
		||||
 | 
			
		||||
		public Fix64 Right { get; }
 | 
			
		||||
		public Fix64 Left { get; }
 | 
			
		||||
		public Fix64 Top { get; }
 | 
			
		||||
		public Fix64 Bottom { get; }
 | 
			
		||||
		public Vector2 TopLeft { get; }
 | 
			
		||||
		public Vector2 BottomRight { get; }
 | 
			
		||||
 | 
			
		||||
		public Vector2 Min { get; }
 | 
			
		||||
		public Vector2 Max { get; }
 | 
			
		||||
 | 
			
		||||
		public IEnumerable<IShape2D> Shapes
 | 
			
		||||
        {
 | 
			
		||||
            get
 | 
			
		||||
            {
 | 
			
		||||
				yield return this;
 | 
			
		||||
			}
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
		public Rectangle(Fix64 left, Fix64 top, Fix64 width, Fix64 height)
 | 
			
		||||
		{
 | 
			
		||||
			Width = width;
 | 
			
		||||
			Height = height;
 | 
			
		||||
			Left = left;
 | 
			
		||||
			Right = left + width;
 | 
			
		||||
			Top = top;
 | 
			
		||||
			Bottom = top + height;
 | 
			
		||||
			AABB = new AABB2D(left, top, Right, Bottom);
 | 
			
		||||
			TopLeft = new Vector2(Left, Top);
 | 
			
		||||
			BottomRight = new Vector2(Right, Bottom);
 | 
			
		||||
			Min = AABB.Min;
 | 
			
		||||
			Max = AABB.Max;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public Rectangle(int left, int top, int width, int height)
 | 
			
		||||
		{
 | 
			
		||||
			Width = (Fix64) width;
 | 
			
		||||
			Height = (Fix64) height;
 | 
			
		||||
			Left = (Fix64) left;
 | 
			
		||||
			Right = (Fix64) (left + width);
 | 
			
		||||
			Top = (Fix64) top;
 | 
			
		||||
			Bottom = (Fix64) (top + height);
 | 
			
		||||
			AABB = new AABB2D(Left, Top, Right, Bottom);
 | 
			
		||||
			TopLeft = new Vector2(Left, Top);
 | 
			
		||||
			BottomRight = new Vector2(Right, Bottom);
 | 
			
		||||
			Min = AABB.Min;
 | 
			
		||||
			Max = AABB.Max;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private Vector2 Support(Vector2 direction)
 | 
			
		||||
		{
 | 
			
		||||
			if (direction.X >= Fix64.Zero && direction.Y >= Fix64.Zero)
 | 
			
		||||
			{
 | 
			
		||||
				return Max;
 | 
			
		||||
			}
 | 
			
		||||
			else if (direction.X >= Fix64.Zero && direction.Y < Fix64.Zero)
 | 
			
		||||
			{
 | 
			
		||||
				return new Vector2(Max.X, Min.Y);
 | 
			
		||||
			}
 | 
			
		||||
			else if (direction.X < Fix64.Zero && direction.Y >= Fix64.Zero)
 | 
			
		||||
			{
 | 
			
		||||
				return new Vector2(Min.X, Max.Y);
 | 
			
		||||
			}
 | 
			
		||||
			else if (direction.X < Fix64.Zero && direction.Y < Fix64.Zero)
 | 
			
		||||
			{
 | 
			
		||||
				return new Vector2(Min.X, Min.Y);
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
				throw new System.ArgumentException("Support vector direction cannot be zero.");
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public Vector2 Support(Vector2 direction, Transform2D transform)
 | 
			
		||||
		{
 | 
			
		||||
			Matrix3x2 inverseTransform;
 | 
			
		||||
			Matrix3x2.Invert(transform.TransformMatrix, out inverseTransform);
 | 
			
		||||
			var inverseDirection = Vector2.TransformNormal(direction, inverseTransform);
 | 
			
		||||
			return Vector2.Transform(Support(inverseDirection), transform.TransformMatrix);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public AABB2D TransformedAABB(Transform2D transform)
 | 
			
		||||
		{
 | 
			
		||||
			return AABB2D.Transformed(AABB, transform);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public override bool Equals(object obj)
 | 
			
		||||
		{
 | 
			
		||||
			return obj is IShape2D other && Equals(other);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public bool Equals(IShape2D other)
 | 
			
		||||
		{
 | 
			
		||||
			return (other is Rectangle rectangle && Equals(rectangle));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public bool Equals(Rectangle other)
 | 
			
		||||
		{
 | 
			
		||||
			return Min == other.Min && Max == other.Max;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public override int GetHashCode()
 | 
			
		||||
		{
 | 
			
		||||
			return System.HashCode.Combine(Min, Max);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static bool operator ==(Rectangle a, Rectangle b)
 | 
			
		||||
		{
 | 
			
		||||
			return a.Equals(b);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static bool operator !=(Rectangle a, Rectangle b)
 | 
			
		||||
		{
 | 
			
		||||
			return !(a == b);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,136 +0,0 @@
 | 
			
		|||
using System.Collections.Generic;
 | 
			
		||||
using MoonWorks.Math.Fixed;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Collision.Fixed
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// A simplex is a shape with up to n - 2 vertices in the nth dimension.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public struct Simplex2D : System.IEquatable<Simplex2D>
 | 
			
		||||
	{
 | 
			
		||||
		private Vector2 a;
 | 
			
		||||
		private Vector2? b;
 | 
			
		||||
		private Vector2? c;
 | 
			
		||||
 | 
			
		||||
		public Vector2 A => a;
 | 
			
		||||
		public Vector2? B => b;
 | 
			
		||||
		public Vector2? C => c;
 | 
			
		||||
 | 
			
		||||
		public bool ZeroSimplex { get { return !b.HasValue && !c.HasValue; } }
 | 
			
		||||
		public bool OneSimplex { get { return b.HasValue && !c.HasValue; } }
 | 
			
		||||
		public bool TwoSimplex { get { return b.HasValue && c.HasValue; } }
 | 
			
		||||
 | 
			
		||||
		public int Count => TwoSimplex ? 3 : (OneSimplex ? 2 : 1);
 | 
			
		||||
 | 
			
		||||
		public Simplex2D(Vector2 a)
 | 
			
		||||
		{
 | 
			
		||||
			this.a = a;
 | 
			
		||||
			b = null;
 | 
			
		||||
			c = null;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public Simplex2D(Vector2 a, Vector2 b)
 | 
			
		||||
		{
 | 
			
		||||
			this.a = a;
 | 
			
		||||
			this.b = b;
 | 
			
		||||
			c = null;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public Simplex2D(Vector2 a, Vector2 b, Vector2 c)
 | 
			
		||||
		{
 | 
			
		||||
			this.a = a;
 | 
			
		||||
			this.b = b;
 | 
			
		||||
			this.c = c;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public Vector2 this[int index]
 | 
			
		||||
		{
 | 
			
		||||
			get
 | 
			
		||||
			{
 | 
			
		||||
				if (index == 0) { return a; }
 | 
			
		||||
				if (index == 1) { return b.Value; }
 | 
			
		||||
				if (index == 2) { return c.Value; }
 | 
			
		||||
				throw new System.IndexOutOfRangeException();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public IEnumerable<Vector2> Vertices
 | 
			
		||||
		{
 | 
			
		||||
			get
 | 
			
		||||
			{
 | 
			
		||||
				yield return (Vector2) a;
 | 
			
		||||
				if (b.HasValue) { yield return (Vector2) b; }
 | 
			
		||||
				if (c.HasValue) { yield return (Vector2) c; }
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public Vector2 Support(Vector2 direction, Transform2D transform)
 | 
			
		||||
		{
 | 
			
		||||
			var maxDotProduct = Fix64.MinValue;
 | 
			
		||||
			var maxVertex = a;
 | 
			
		||||
			foreach (var vertex in Vertices)
 | 
			
		||||
			{
 | 
			
		||||
				var transformed = Vector2.Transform(vertex, transform.TransformMatrix);
 | 
			
		||||
				var dot = Vector2.Dot(transformed, direction);
 | 
			
		||||
				if (dot > maxDotProduct)
 | 
			
		||||
				{
 | 
			
		||||
					maxVertex = transformed;
 | 
			
		||||
					maxDotProduct = dot;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			return maxVertex;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public void Insert(Vector2 point, int index)
 | 
			
		||||
		{
 | 
			
		||||
			if (index == 0)
 | 
			
		||||
			{
 | 
			
		||||
				c = b;
 | 
			
		||||
				b = a;
 | 
			
		||||
				a = point;
 | 
			
		||||
			}
 | 
			
		||||
			else if (index == 1)
 | 
			
		||||
			{
 | 
			
		||||
				c = b;
 | 
			
		||||
				b = point;
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
				c = point;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public override bool Equals(object obj)
 | 
			
		||||
		{
 | 
			
		||||
			return obj is Simplex2D other && Equals(other);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public bool Equals(Simplex2D other)
 | 
			
		||||
		{
 | 
			
		||||
			if (Count != other.Count) { return false; }
 | 
			
		||||
 | 
			
		||||
			return
 | 
			
		||||
				(A == other.A && B == other.B && C == other.C) ||
 | 
			
		||||
				(A == other.A && B == other.C && C == other.B) ||
 | 
			
		||||
				(A == other.B && B == other.A && C == other.C) ||
 | 
			
		||||
				(A == other.B && B == other.C && C == other.A) ||
 | 
			
		||||
				(A == other.C && B == other.A && C == other.B) ||
 | 
			
		||||
				(A == other.C && B == other.B && C == other.A);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public override int GetHashCode()
 | 
			
		||||
		{
 | 
			
		||||
			return System.HashCode.Combine(Vertices);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static bool operator ==(Simplex2D a, Simplex2D b)
 | 
			
		||||
		{
 | 
			
		||||
			return a.Equals(b);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static bool operator !=(Simplex2D a, Simplex2D b)
 | 
			
		||||
		{
 | 
			
		||||
			return !(a == b);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,253 +0,0 @@
 | 
			
		|||
using System.Collections.Generic;
 | 
			
		||||
using MoonWorks.Math.Fixed;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Collision.Fixed
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// Used to quickly check if two shapes are potentially overlapping.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	/// <typeparam name="T">The type that will be used to uniquely identify shape-transform pairs.</typeparam>
 | 
			
		||||
	public class SpatialHash2D<T> where T : System.IEquatable<T>
 | 
			
		||||
	{
 | 
			
		||||
		private readonly Fix64 cellSize;
 | 
			
		||||
 | 
			
		||||
		private readonly Dictionary<long, HashSet<T>> hashDictionary = new Dictionary<long, HashSet<T>>();
 | 
			
		||||
		// FIXME: this ICollidable causes boxing which triggers garbage collection
 | 
			
		||||
		private readonly Dictionary<T, (ICollidable, Transform2D, uint)> IDLookup = new Dictionary<T, (ICollidable, Transform2D, uint)>();
 | 
			
		||||
 | 
			
		||||
		public int MinX { get; private set; } = 0;
 | 
			
		||||
		public int MaxX { get; private set; } = 0;
 | 
			
		||||
		public int MinY { get; private set; } = 0;
 | 
			
		||||
		public int MaxY { get; private set; } = 0;
 | 
			
		||||
 | 
			
		||||
		private Queue<HashSet<T>> hashSetPool = new Queue<HashSet<T>>();
 | 
			
		||||
 | 
			
		||||
		public SpatialHash2D(int cellSize)
 | 
			
		||||
		{
 | 
			
		||||
			this.cellSize = new Fix64(cellSize);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private (int, int) Hash(Vector2 position)
 | 
			
		||||
		{
 | 
			
		||||
			return ((int) (position.X / cellSize), (int) (position.Y / cellSize));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Inserts an element into the SpatialHash.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="id">A unique ID for the shape-transform pair.</param>
 | 
			
		||||
		/// <param name="shape"></param>
 | 
			
		||||
		/// <param name="transform2D"></param>
 | 
			
		||||
		/// <param name="collisionGroups">A bitmask value specifying the groups this object belongs to.</param>
 | 
			
		||||
		public void Insert(T id, ICollidable shape, Transform2D transform2D, uint collisionGroups = uint.MaxValue)
 | 
			
		||||
		{
 | 
			
		||||
			var box = shape.TransformedAABB(transform2D);
 | 
			
		||||
			var minHash = Hash(box.Min);
 | 
			
		||||
			var maxHash = Hash(box.Max);
 | 
			
		||||
 | 
			
		||||
			foreach (var key in Keys(minHash.Item1, minHash.Item2, maxHash.Item1, maxHash.Item2))
 | 
			
		||||
			{
 | 
			
		||||
				if (!hashDictionary.ContainsKey(key))
 | 
			
		||||
				{
 | 
			
		||||
					hashDictionary.Add(key, new HashSet<T>());
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				hashDictionary[key].Add(id);
 | 
			
		||||
				IDLookup[id] = (shape, transform2D, collisionGroups);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			MinX = System.Math.Min(MinX, minHash.Item1);
 | 
			
		||||
			MinY = System.Math.Min(MinY, minHash.Item2);
 | 
			
		||||
			MaxX = System.Math.Max(MaxX, maxHash.Item1);
 | 
			
		||||
			MaxY = System.Math.Max(MaxY, maxHash.Item2);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Retrieves all the potential collisions of a shape-transform pair. Excludes any shape-transforms with the given ID.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public IEnumerable<(T, ICollidable, Transform2D, uint)> Retrieve(T id, ICollidable shape, Transform2D transform2D, uint collisionMask = uint.MaxValue)
 | 
			
		||||
		{
 | 
			
		||||
			var returned = AcquireHashSet();
 | 
			
		||||
 | 
			
		||||
			var box = shape.TransformedAABB(transform2D);
 | 
			
		||||
			var (minX, minY) = Hash(box.Min);
 | 
			
		||||
			var (maxX, maxY) = Hash(box.Max);
 | 
			
		||||
 | 
			
		||||
			if (minX < MinX) { minX = MinX; }
 | 
			
		||||
			if (maxX > MaxX) { maxX = MaxX; }
 | 
			
		||||
			if (minY < MinY) { minY = MinY; }
 | 
			
		||||
			if (maxY > MaxY) { maxY = MaxY; }
 | 
			
		||||
 | 
			
		||||
			foreach (var key in Keys(minX, minY, maxX, maxY))
 | 
			
		||||
			{
 | 
			
		||||
				if (hashDictionary.ContainsKey(key))
 | 
			
		||||
				{
 | 
			
		||||
					foreach (var t in hashDictionary[key])
 | 
			
		||||
					{
 | 
			
		||||
						if (!returned.Contains(t))
 | 
			
		||||
						{
 | 
			
		||||
							var (otherShape, otherTransform, collisionGroups) = IDLookup[t];
 | 
			
		||||
							if (!id.Equals(t) && ((collisionGroups & collisionMask) > 0) && AABB2D.TestOverlap(box, otherShape.TransformedAABB(otherTransform)))
 | 
			
		||||
							{
 | 
			
		||||
								returned.Add(t);
 | 
			
		||||
								yield return (t, otherShape, otherTransform, collisionGroups);
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			FreeHashSet(returned);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Retrieves all the potential collisions of a shape-transform pair.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public IEnumerable<(T, ICollidable, Transform2D, uint)> Retrieve(ICollidable shape, Transform2D transform2D, uint collisionMask = uint.MaxValue)
 | 
			
		||||
		{
 | 
			
		||||
			var returned = AcquireHashSet();
 | 
			
		||||
 | 
			
		||||
			var box = shape.TransformedAABB(transform2D);
 | 
			
		||||
			var (minX, minY) = Hash(box.Min);
 | 
			
		||||
			var (maxX, maxY) = Hash(box.Max);
 | 
			
		||||
 | 
			
		||||
			if (minX < MinX) { minX = MinX; }
 | 
			
		||||
			if (maxX > MaxX) { maxX = MaxX; }
 | 
			
		||||
			if (minY < MinY) { minY = MinY; }
 | 
			
		||||
			if (maxY > MaxY) { maxY = MaxY; }
 | 
			
		||||
 | 
			
		||||
			foreach (var key in Keys(minX, minY, maxX, maxY))
 | 
			
		||||
			{
 | 
			
		||||
				if (hashDictionary.ContainsKey(key))
 | 
			
		||||
				{
 | 
			
		||||
					foreach (var t in hashDictionary[key])
 | 
			
		||||
					{
 | 
			
		||||
						if (!returned.Contains(t))
 | 
			
		||||
						{
 | 
			
		||||
							var (otherShape, otherTransform, collisionGroups) = IDLookup[t];
 | 
			
		||||
							if (((collisionGroups & collisionMask) > 0) && AABB2D.TestOverlap(box, otherShape.TransformedAABB(otherTransform)))
 | 
			
		||||
							{
 | 
			
		||||
								returned.Add(t);
 | 
			
		||||
								yield return (t, otherShape, otherTransform, collisionGroups);
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			FreeHashSet(returned);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Retrieves objects based on a pre-transformed AABB.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="aabb">A transformed AABB.</param>
 | 
			
		||||
		/// <returns></returns>
 | 
			
		||||
		public IEnumerable<(T, ICollidable, Transform2D, uint)> Retrieve(AABB2D aabb, uint collisionMask = uint.MaxValue)
 | 
			
		||||
		{
 | 
			
		||||
			var returned = AcquireHashSet();
 | 
			
		||||
 | 
			
		||||
			var (minX, minY) = Hash(aabb.Min);
 | 
			
		||||
			var (maxX, maxY) = Hash(aabb.Max);
 | 
			
		||||
 | 
			
		||||
			if (minX < MinX) { minX = MinX; }
 | 
			
		||||
			if (maxX > MaxX) { maxX = MaxX; }
 | 
			
		||||
			if (minY < MinY) { minY = MinY; }
 | 
			
		||||
			if (maxY > MaxY) { maxY = MaxY; }
 | 
			
		||||
 | 
			
		||||
			foreach (var key in Keys(minX, minY, maxX, maxY))
 | 
			
		||||
			{
 | 
			
		||||
				if (hashDictionary.ContainsKey(key))
 | 
			
		||||
				{
 | 
			
		||||
					foreach (var t in hashDictionary[key])
 | 
			
		||||
					{
 | 
			
		||||
						if (!returned.Contains(t))
 | 
			
		||||
						{
 | 
			
		||||
							var (otherShape, otherTransform, collisionGroups) = IDLookup[t];
 | 
			
		||||
							if (((collisionGroups & collisionMask) > 0) && AABB2D.TestOverlap(aabb, otherShape.TransformedAABB(otherTransform)))
 | 
			
		||||
							{
 | 
			
		||||
								yield return (t, otherShape, otherTransform, collisionGroups);
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			FreeHashSet(returned);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public void Update(T id, ICollidable shape, Transform2D transform2D, uint collisionGroups = uint.MaxValue)
 | 
			
		||||
		{
 | 
			
		||||
			Remove(id);
 | 
			
		||||
			Insert(id, shape, transform2D, collisionGroups);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Removes a specific ID from the SpatialHash.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public void Remove(T id)
 | 
			
		||||
		{
 | 
			
		||||
			var (shape, transform, collisionGroups) = IDLookup[id];
 | 
			
		||||
 | 
			
		||||
			var box = shape.TransformedAABB(transform);
 | 
			
		||||
			var minHash = Hash(box.Min);
 | 
			
		||||
			var maxHash = Hash(box.Max);
 | 
			
		||||
 | 
			
		||||
			foreach (var key in Keys(minHash.Item1, minHash.Item2, maxHash.Item1, maxHash.Item2))
 | 
			
		||||
			{
 | 
			
		||||
				if (hashDictionary.ContainsKey(key))
 | 
			
		||||
				{
 | 
			
		||||
					hashDictionary[key].Remove(id);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			IDLookup.Remove(id);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Removes everything that has been inserted into the SpatialHash.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public void Clear()
 | 
			
		||||
		{
 | 
			
		||||
			foreach (var hash in hashDictionary.Values)
 | 
			
		||||
			{
 | 
			
		||||
				hash.Clear();
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			IDLookup.Clear();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private static long MakeLong(int left, int right)
 | 
			
		||||
		{
 | 
			
		||||
			return ((long) left << 32) | ((uint) right);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private IEnumerable<long> Keys(int minX, int minY, int maxX, int maxY)
 | 
			
		||||
		{
 | 
			
		||||
			for (var i = minX; i <= maxX; i++)
 | 
			
		||||
			{
 | 
			
		||||
				for (var j = minY; j <= maxY; j++)
 | 
			
		||||
				{
 | 
			
		||||
					yield return MakeLong(i, j);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private HashSet<T> AcquireHashSet()
 | 
			
		||||
		{
 | 
			
		||||
			if (hashSetPool.Count == 0)
 | 
			
		||||
			{
 | 
			
		||||
				hashSetPool.Enqueue(new HashSet<T>());
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			var hashSet = hashSetPool.Dequeue();
 | 
			
		||||
			hashSet.Clear();
 | 
			
		||||
			return hashSet;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private void FreeHashSet(HashSet<T> hashSet)
 | 
			
		||||
		{
 | 
			
		||||
			hashSetPool.Enqueue(hashSet);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,174 +0,0 @@
 | 
			
		|||
using System.Collections.Generic;
 | 
			
		||||
using MoonWorks.Math.Float;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Collision.Float
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// Axis-aligned bounding box.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public struct AABB2D : System.IEquatable<AABB2D>
 | 
			
		||||
	{
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// The top-left position of the AABB.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <value></value>
 | 
			
		||||
		public Vector2 Min { get; private set; }
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// The bottom-right position of the AABB.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <value></value>
 | 
			
		||||
		public Vector2 Max { get; private set; }
 | 
			
		||||
 | 
			
		||||
		public float Width { get { return Max.X - Min.X; } }
 | 
			
		||||
		public float Height { get { return Max.Y - Min.Y; } }
 | 
			
		||||
 | 
			
		||||
		public float Right { get { return Max.X; } }
 | 
			
		||||
		public float Left { get { return Min.X; } }
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// The top of the AABB. Assumes a downward-aligned Y axis, so this value will be smaller than Bottom.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <value></value>
 | 
			
		||||
		public float Top { get { return Min.Y; } }
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// The bottom of the AABB. Assumes a downward-aligned Y axis, so this value will be larger than Top.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <value></value>
 | 
			
		||||
		public float Bottom { get { return Max.Y; } }
 | 
			
		||||
 | 
			
		||||
		public AABB2D(float minX, float minY, float maxX, float maxY)
 | 
			
		||||
		{
 | 
			
		||||
			Min = new Vector2(minX, minY);
 | 
			
		||||
			Max = new Vector2(maxX, maxY);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public AABB2D(Vector2 min, Vector2 max)
 | 
			
		||||
		{
 | 
			
		||||
			Min = min;
 | 
			
		||||
			Max = max;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private static Matrix3x2 AbsoluteMatrix(Matrix3x2 matrix)
 | 
			
		||||
		{
 | 
			
		||||
			return new Matrix3x2
 | 
			
		||||
			(
 | 
			
		||||
				System.Math.Abs(matrix.M11), System.Math.Abs(matrix.M12),
 | 
			
		||||
				System.Math.Abs(matrix.M21), System.Math.Abs(matrix.M22),
 | 
			
		||||
				System.Math.Abs(matrix.M31), System.Math.Abs(matrix.M32)
 | 
			
		||||
			);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Efficiently transforms the AABB by a Transform2D.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="aabb"></param>
 | 
			
		||||
		/// <param name="transform"></param>
 | 
			
		||||
		/// <returns></returns>
 | 
			
		||||
		public static AABB2D Transformed(AABB2D aabb, Transform2D transform)
 | 
			
		||||
		{
 | 
			
		||||
            var center = (aabb.Min + aabb.Max) / 2f;
 | 
			
		||||
            var extent = (aabb.Max - aabb.Min) / 2f;
 | 
			
		||||
 | 
			
		||||
            var newCenter = Vector2.Transform(center, transform.TransformMatrix);
 | 
			
		||||
            var newExtent = Vector2.TransformNormal(extent, AbsoluteMatrix(transform.TransformMatrix));
 | 
			
		||||
 | 
			
		||||
            return new AABB2D(newCenter - newExtent, newCenter + newExtent);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public AABB2D Compose(AABB2D aabb)
 | 
			
		||||
		{
 | 
			
		||||
			float left = Left;
 | 
			
		||||
			float top = Top;
 | 
			
		||||
			float right = Right;
 | 
			
		||||
			float bottom = Bottom;
 | 
			
		||||
 | 
			
		||||
			if (aabb.Left < left)
 | 
			
		||||
			{
 | 
			
		||||
				left = aabb.Left;
 | 
			
		||||
			}
 | 
			
		||||
			if (aabb.Right > right)
 | 
			
		||||
			{
 | 
			
		||||
				right = aabb.Right;
 | 
			
		||||
			}
 | 
			
		||||
			if (aabb.Top < top)
 | 
			
		||||
			{
 | 
			
		||||
				top = aabb.Top;
 | 
			
		||||
			}
 | 
			
		||||
			if (aabb.Bottom > bottom)
 | 
			
		||||
			{
 | 
			
		||||
				bottom = aabb.Bottom;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return new AABB2D(left, top, right, bottom);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Creates an AABB for an arbitrary collection of positions.
 | 
			
		||||
		/// This is less efficient than defining a custom AABB method for most shapes, so avoid using this if possible.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="vertices"></param>
 | 
			
		||||
		/// <returns></returns>
 | 
			
		||||
		public static AABB2D FromVertices(IEnumerable<Vector2> vertices)
 | 
			
		||||
		{
 | 
			
		||||
			var minX = float.MaxValue;
 | 
			
		||||
			var minY = float.MaxValue;
 | 
			
		||||
			var maxX = float.MinValue;
 | 
			
		||||
			var maxY = float.MinValue;
 | 
			
		||||
 | 
			
		||||
			foreach (var vertex in vertices)
 | 
			
		||||
			{
 | 
			
		||||
				if (vertex.X < minX)
 | 
			
		||||
				{
 | 
			
		||||
					minX = vertex.X;
 | 
			
		||||
				}
 | 
			
		||||
				if (vertex.Y < minY)
 | 
			
		||||
				{
 | 
			
		||||
					minY = vertex.Y;
 | 
			
		||||
				}
 | 
			
		||||
				if (vertex.X > maxX)
 | 
			
		||||
				{
 | 
			
		||||
					maxX = vertex.X;
 | 
			
		||||
				}
 | 
			
		||||
				if (vertex.Y > maxY)
 | 
			
		||||
				{
 | 
			
		||||
					maxY = vertex.Y;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return new AABB2D(minX, minY, maxX, maxY);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static bool TestOverlap(AABB2D a, AABB2D b)
 | 
			
		||||
		{
 | 
			
		||||
			return a.Left < b.Right && a.Right > b.Left && a.Top < b.Bottom && a.Bottom > b.Top;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public override bool Equals(object obj)
 | 
			
		||||
		{
 | 
			
		||||
			return obj is AABB2D aabb && Equals(aabb);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public bool Equals(AABB2D other)
 | 
			
		||||
		{
 | 
			
		||||
			return Min == other.Min &&
 | 
			
		||||
				   Max == other.Max;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public override int GetHashCode()
 | 
			
		||||
		{
 | 
			
		||||
			return System.HashCode.Combine(Min, Max);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static bool operator ==(AABB2D left, AABB2D right)
 | 
			
		||||
		{
 | 
			
		||||
			return left.Equals(right);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static bool operator !=(AABB2D left, AABB2D right)
 | 
			
		||||
		{
 | 
			
		||||
			return !(left == right);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,12 +0,0 @@
 | 
			
		|||
using System.Collections.Generic;
 | 
			
		||||
using MoonWorks.Math.Float;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Collision.Float
 | 
			
		||||
{
 | 
			
		||||
	public interface ICollidable
 | 
			
		||||
	{
 | 
			
		||||
		IEnumerable<IShape2D> Shapes { get; }
 | 
			
		||||
		AABB2D AABB { get; }
 | 
			
		||||
		AABB2D TransformedAABB(Transform2D transform);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,15 +0,0 @@
 | 
			
		|||
using MoonWorks.Math.Float;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Collision.Float
 | 
			
		||||
{
 | 
			
		||||
	public interface IShape2D : ICollidable, System.IEquatable<IShape2D>
 | 
			
		||||
	{
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// A Minkowski support function. Gives the farthest point on the edge of a shape along the given direction.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="direction">A normalized Vector2.</param>
 | 
			
		||||
		/// <param name="transform">A Transform for transforming the shape vertices.</param>
 | 
			
		||||
		/// <returns>The farthest point on the edge of the shape along the given direction.</returns>
 | 
			
		||||
		Vector2 Support(Vector2 direction, Transform2D transform);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,57 +0,0 @@
 | 
			
		|||
using MoonWorks.Math.Float;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Collision.Float
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// A Minkowski difference between two shapes.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public struct MinkowskiDifference : System.IEquatable<MinkowskiDifference>
 | 
			
		||||
	{
 | 
			
		||||
		private IShape2D ShapeA { get; }
 | 
			
		||||
		private Transform2D TransformA { get; }
 | 
			
		||||
		private IShape2D ShapeB { get; }
 | 
			
		||||
		private Transform2D TransformB { get; }
 | 
			
		||||
 | 
			
		||||
		public MinkowskiDifference(IShape2D shapeA, Transform2D transformA, IShape2D shapeB, Transform2D transformB)
 | 
			
		||||
		{
 | 
			
		||||
			ShapeA = shapeA;
 | 
			
		||||
			TransformA = transformA;
 | 
			
		||||
			ShapeB = shapeB;
 | 
			
		||||
			TransformB = transformB;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public Vector2 Support(Vector2 direction)
 | 
			
		||||
		{
 | 
			
		||||
			return ShapeA.Support(direction, TransformA) - ShapeB.Support(-direction, TransformB);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public override bool Equals(object other)
 | 
			
		||||
		{
 | 
			
		||||
			return other is MinkowskiDifference minkowskiDifference && Equals(minkowskiDifference);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public bool Equals(MinkowskiDifference other)
 | 
			
		||||
		{
 | 
			
		||||
			return
 | 
			
		||||
				ShapeA == other.ShapeA &&
 | 
			
		||||
				TransformA == other.TransformA &&
 | 
			
		||||
				ShapeB == other.ShapeB &&
 | 
			
		||||
				TransformB == other.TransformB;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public override int GetHashCode()
 | 
			
		||||
		{
 | 
			
		||||
			return System.HashCode.Combine(ShapeA, TransformA, ShapeB, TransformB);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static bool operator ==(MinkowskiDifference a, MinkowskiDifference b)
 | 
			
		||||
		{
 | 
			
		||||
			return a.Equals(b);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static bool operator !=(MinkowskiDifference a, MinkowskiDifference b)
 | 
			
		||||
		{
 | 
			
		||||
			return !(a == b);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,331 +0,0 @@
 | 
			
		|||
using MoonWorks.Math.Float;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Collision.Float
 | 
			
		||||
{
 | 
			
		||||
	public static class NarrowPhase
 | 
			
		||||
	{
 | 
			
		||||
		private struct Edge
 | 
			
		||||
		{
 | 
			
		||||
			public float Distance;
 | 
			
		||||
			public Vector2 Normal;
 | 
			
		||||
			public int Index;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
        public static bool TestCollision(ICollidable collidableA, Transform2D transformA, ICollidable collidableB, Transform2D transformB)
 | 
			
		||||
        {
 | 
			
		||||
            foreach (var shapeA in collidableA.Shapes)
 | 
			
		||||
            {
 | 
			
		||||
                foreach (var shapeB in collidableB.Shapes)
 | 
			
		||||
                {
 | 
			
		||||
					if (TestCollision(shapeA, transformA, shapeB, transformB))
 | 
			
		||||
                    {
 | 
			
		||||
						return true;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static bool TestCollision(IShape2D shapeA, Transform2D transformA, IShape2D shapeB, Transform2D transformB)
 | 
			
		||||
		{
 | 
			
		||||
			// If we can use a fast path check, let's do that!
 | 
			
		||||
			if (shapeA is Rectangle rectangleA && shapeB is Rectangle rectangleB && transformA.IsAxisAligned && transformB.IsAxisAligned)
 | 
			
		||||
			{
 | 
			
		||||
				return TestRectangleOverlap(rectangleA, transformA, rectangleB, transformB);
 | 
			
		||||
			}
 | 
			
		||||
			else if (shapeA is Point && shapeB is Rectangle && transformB.IsAxisAligned)
 | 
			
		||||
			{
 | 
			
		||||
				return TestPointRectangleOverlap((Point) shapeA, transformA, (Rectangle) shapeB, transformB);
 | 
			
		||||
			}
 | 
			
		||||
			else if (shapeA is Rectangle && shapeB is Point && transformA.IsAxisAligned)
 | 
			
		||||
			{
 | 
			
		||||
				return TestPointRectangleOverlap((Point) shapeB, transformB, (Rectangle) shapeA, transformA);
 | 
			
		||||
			}
 | 
			
		||||
			else if (shapeA is Rectangle && shapeB is Circle && transformA.IsAxisAligned && transformB.IsUniformScale)
 | 
			
		||||
			{
 | 
			
		||||
				return TestCircleRectangleOverlap((Circle) shapeB, transformB, (Rectangle) shapeA, transformA);
 | 
			
		||||
			}
 | 
			
		||||
			else if (shapeA is Circle && shapeB is Rectangle && transformA.IsUniformScale && transformB.IsAxisAligned)
 | 
			
		||||
			{
 | 
			
		||||
				return TestCircleRectangleOverlap((Circle) shapeA, transformA, (Rectangle) shapeB, transformB);
 | 
			
		||||
			}
 | 
			
		||||
			else if (shapeA is Circle && shapeB is Point && transformA.IsUniformScale)
 | 
			
		||||
			{
 | 
			
		||||
				return TestCirclePointOverlap((Circle) shapeA, transformA, (Point) shapeB, transformB);
 | 
			
		||||
			}
 | 
			
		||||
			else if (shapeA is Point && shapeB is Circle && transformB.IsUniformScale)
 | 
			
		||||
			{
 | 
			
		||||
				return TestCirclePointOverlap((Circle) shapeB, transformB, (Point) shapeA, transformA);
 | 
			
		||||
			}
 | 
			
		||||
			else if (shapeA is Circle circleA && shapeB is Circle circleB && transformA.IsUniformScale && transformB.IsUniformScale)
 | 
			
		||||
			{
 | 
			
		||||
				return TestCircleOverlap(circleA, transformA, circleB, transformB);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Sad, we can't do a fast path optimization. Time for a simplex reduction.
 | 
			
		||||
			return FindCollisionSimplex(shapeA, transformA, shapeB, transformB).Item1;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static bool TestRectangleOverlap(Rectangle rectangleA, Transform2D transformA, Rectangle rectangleB, Transform2D transformB)
 | 
			
		||||
		{
 | 
			
		||||
			var firstAABB = rectangleA.TransformedAABB(transformA);
 | 
			
		||||
			var secondAABB = rectangleB.TransformedAABB(transformB);
 | 
			
		||||
 | 
			
		||||
			return firstAABB.Left < secondAABB.Right && firstAABB.Right > secondAABB.Left && firstAABB.Top < secondAABB.Bottom && firstAABB.Bottom > secondAABB.Top;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static bool TestPointRectangleOverlap(Point point, Transform2D pointTransform, Rectangle rectangle, Transform2D rectangleTransform)
 | 
			
		||||
		{
 | 
			
		||||
			var transformedPoint = pointTransform.Position;
 | 
			
		||||
			var AABB = rectangle.TransformedAABB(rectangleTransform);
 | 
			
		||||
 | 
			
		||||
			return transformedPoint.X > AABB.Left && transformedPoint.X < AABB.Right && transformedPoint.Y < AABB.Bottom && transformedPoint.Y > AABB.Top;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static bool TestCirclePointOverlap(Circle circle, Transform2D circleTransform, Point point, Transform2D pointTransform)
 | 
			
		||||
		{
 | 
			
		||||
			var circleCenter = circleTransform.Position;
 | 
			
		||||
			var circleRadius = circle.Radius * circleTransform.Scale.X;
 | 
			
		||||
 | 
			
		||||
			var distanceX = circleCenter.X - pointTransform.Position.X;
 | 
			
		||||
			var distanceY = circleCenter.Y - pointTransform.Position.Y;
 | 
			
		||||
 | 
			
		||||
			return (distanceX * distanceX) + (distanceY * distanceY) < (circleRadius * circleRadius);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// NOTE: The rectangle must be axis aligned, and the scaling of the circle must be uniform.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public static bool TestCircleRectangleOverlap(Circle circle, Transform2D circleTransform, Rectangle rectangle, Transform2D rectangleTransform)
 | 
			
		||||
		{
 | 
			
		||||
			var circleCenter = circleTransform.Position;
 | 
			
		||||
			var circleRadius = circle.Radius * circleTransform.Scale.X;
 | 
			
		||||
			var AABB = rectangle.TransformedAABB(rectangleTransform);
 | 
			
		||||
 | 
			
		||||
			var closestX = Math.MathHelper.Clamp(circleCenter.X, AABB.Left, AABB.Right);
 | 
			
		||||
			var closestY = Math.MathHelper.Clamp(circleCenter.Y, AABB.Top, AABB.Bottom);
 | 
			
		||||
 | 
			
		||||
			var distanceX = circleCenter.X - closestX;
 | 
			
		||||
			var distanceY = circleCenter.Y - closestY;
 | 
			
		||||
 | 
			
		||||
			var distanceSquared = (distanceX * distanceX) + (distanceY * distanceY);
 | 
			
		||||
			return distanceSquared < (circleRadius * circleRadius);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static bool TestCircleOverlap(Circle circleA, Transform2D transformA, Circle circleB, Transform2D transformB)
 | 
			
		||||
		{
 | 
			
		||||
			var radiusA = circleA.Radius * transformA.Scale.X;
 | 
			
		||||
			var radiusB = circleB.Radius * transformB.Scale.Y;
 | 
			
		||||
 | 
			
		||||
			var centerA = transformA.Position;
 | 
			
		||||
			var centerB = transformB.Position;
 | 
			
		||||
 | 
			
		||||
			var distanceSquared = (centerA - centerB).LengthSquared();
 | 
			
		||||
			var radiusSumSquared = (radiusA + radiusB) * (radiusA + radiusB);
 | 
			
		||||
 | 
			
		||||
			return distanceSquared < radiusSumSquared;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static (bool, Simplex2D) FindCollisionSimplex(IShape2D shapeA, Transform2D transformA, IShape2D shapeB, Transform2D transformB)
 | 
			
		||||
		{
 | 
			
		||||
			var minkowskiDifference = new MinkowskiDifference(shapeA, transformA, shapeB, transformB);
 | 
			
		||||
			var c = minkowskiDifference.Support(Vector2.UnitX);
 | 
			
		||||
			var b = minkowskiDifference.Support(-Vector2.UnitX);
 | 
			
		||||
			return Check(minkowskiDifference, c, b);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
        public unsafe static Vector2 Intersect(IShape2D shapeA, Transform2D Transform2DA, IShape2D shapeB, Transform2D Transform2DB, Simplex2D simplex)
 | 
			
		||||
        {
 | 
			
		||||
            if (shapeA == null) { throw new System.ArgumentNullException(nameof(shapeA)); }
 | 
			
		||||
            if (shapeB == null) { throw new System.ArgumentNullException(nameof(shapeB)); }
 | 
			
		||||
            if (!simplex.TwoSimplex) { throw new System.ArgumentException("Simplex must be a 2-Simplex.", nameof(simplex)); }
 | 
			
		||||
 | 
			
		||||
            var a = simplex.A;
 | 
			
		||||
            var b = simplex.B.Value;
 | 
			
		||||
            var c = simplex.C.Value;
 | 
			
		||||
 | 
			
		||||
            Vector2 intersection = default;
 | 
			
		||||
 | 
			
		||||
            for (var i = 0; i < 32; i++)
 | 
			
		||||
            {
 | 
			
		||||
                var edge = FindClosestEdge(simplex);
 | 
			
		||||
                var support = CalculateSupport(shapeA, Transform2DA, shapeB, Transform2DB, edge.Normal);
 | 
			
		||||
                var distance = Vector2.Dot(support, edge.Normal);
 | 
			
		||||
 | 
			
		||||
                intersection = edge.Normal;
 | 
			
		||||
                intersection *= distance;
 | 
			
		||||
 | 
			
		||||
                if (System.Math.Abs(distance - edge.Distance) <= 0.00001f)
 | 
			
		||||
                {
 | 
			
		||||
                    return intersection;
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
					simplex.Insert(support, edge.Index);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return intersection; // close enough
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static unsafe Edge FindClosestEdge(Simplex2D simplex)
 | 
			
		||||
        {
 | 
			
		||||
            var closestDistance = float.PositiveInfinity;
 | 
			
		||||
            var closestNormal = Vector2.Zero;
 | 
			
		||||
            var closestIndex = 0;
 | 
			
		||||
 | 
			
		||||
			for (var i = 0; i < 4; i += 1)
 | 
			
		||||
			{
 | 
			
		||||
				var j = (i + 1 == 3) ? 0 : i + 1;
 | 
			
		||||
 | 
			
		||||
				var a = simplex[i];
 | 
			
		||||
				var b = simplex[j];
 | 
			
		||||
 | 
			
		||||
				var e = b - a;
 | 
			
		||||
 | 
			
		||||
				var oa = a;
 | 
			
		||||
 | 
			
		||||
				var n = Vector2.Normalize(TripleProduct(e, oa, e));
 | 
			
		||||
 | 
			
		||||
				var d = Vector2.Dot(n, a);
 | 
			
		||||
 | 
			
		||||
				if (d < closestDistance)
 | 
			
		||||
				{
 | 
			
		||||
					closestDistance = d;
 | 
			
		||||
					closestNormal = n;
 | 
			
		||||
					closestIndex = j;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
            return new Edge
 | 
			
		||||
			{
 | 
			
		||||
				Distance = closestDistance,
 | 
			
		||||
				Normal = closestNormal,
 | 
			
		||||
				Index = closestIndex
 | 
			
		||||
			};
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static Vector2 CalculateSupport(IShape2D shapeA, Transform2D Transform2DA, IShape2D shapeB, Transform2D Transform2DB, Vector2 direction)
 | 
			
		||||
        {
 | 
			
		||||
            return shapeA.Support(direction, Transform2DA) - shapeB.Support(-direction, Transform2DB);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
		private static (bool, Simplex2D) Check(MinkowskiDifference minkowskiDifference, Vector2 c, Vector2 b)
 | 
			
		||||
        {
 | 
			
		||||
            var cb = c - b;
 | 
			
		||||
            var c0 = -c;
 | 
			
		||||
            var d = Direction(cb, c0);
 | 
			
		||||
            return DoSimplex(minkowskiDifference, new Simplex2D(b, c), d);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static (bool, Simplex2D) DoSimplex(MinkowskiDifference minkowskiDifference, Simplex2D simplex, Vector2 direction)
 | 
			
		||||
        {
 | 
			
		||||
            var a = minkowskiDifference.Support(direction);
 | 
			
		||||
            var notPastOrigin = Vector2.Dot(a, direction) < 0;
 | 
			
		||||
            var (intersects, newSimplex, newDirection) = EnclosesOrigin(a, simplex);
 | 
			
		||||
 | 
			
		||||
            if (notPastOrigin)
 | 
			
		||||
            {
 | 
			
		||||
                return (false, default(Simplex2D));
 | 
			
		||||
            }
 | 
			
		||||
            else if (intersects)
 | 
			
		||||
            {
 | 
			
		||||
                return (true, new Simplex2D(simplex.A, simplex.B.Value, a));
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                return DoSimplex(minkowskiDifference, newSimplex, newDirection);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static (bool, Simplex2D, Vector2) EnclosesOrigin(Vector2 a, Simplex2D simplex)
 | 
			
		||||
        {
 | 
			
		||||
            if (simplex.ZeroSimplex)
 | 
			
		||||
            {
 | 
			
		||||
                return HandleZeroSimplex(a, simplex.A);
 | 
			
		||||
            }
 | 
			
		||||
            else if (simplex.OneSimplex)
 | 
			
		||||
            {
 | 
			
		||||
                return HandleOneSimplex(a, simplex.A, simplex.B.Value);
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                return (false, simplex, Vector2.Zero);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static (bool, Simplex2D, Vector2) HandleZeroSimplex(Vector2 a, Vector2 b)
 | 
			
		||||
        {
 | 
			
		||||
            var ab = b - a;
 | 
			
		||||
            var a0 = -a;
 | 
			
		||||
            var (newSimplex, newDirection) = SameDirection(ab, a0) ? (new Simplex2D(a, b), Perpendicular(ab, a0)) : (new Simplex2D(a), a0);
 | 
			
		||||
            return (false, newSimplex, newDirection);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static (bool, Simplex2D, Vector2) HandleOneSimplex(Vector2 a, Vector2 b, Vector2 c)
 | 
			
		||||
        {
 | 
			
		||||
            var a0 = -a;
 | 
			
		||||
            var ab = b - a;
 | 
			
		||||
            var ac = c - a;
 | 
			
		||||
            var abp = Perpendicular(ab, -ac);
 | 
			
		||||
            var acp = Perpendicular(ac, -ab);
 | 
			
		||||
 | 
			
		||||
            if (SameDirection(abp, a0))
 | 
			
		||||
            {
 | 
			
		||||
                if (SameDirection(ab, a0))
 | 
			
		||||
                {
 | 
			
		||||
                    return (false, new Simplex2D(a, b), abp);
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    return (false, new Simplex2D(a), a0);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else if (SameDirection(acp, a0))
 | 
			
		||||
            {
 | 
			
		||||
                if (SameDirection(ac, a0))
 | 
			
		||||
                {
 | 
			
		||||
                    return (false, new Simplex2D(a, c), acp);
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    return (false, new Simplex2D(a), a0);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                return (true, new Simplex2D(b, c), a0);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static Vector2 TripleProduct(Vector2 a, Vector2 b, Vector2 c)
 | 
			
		||||
        {
 | 
			
		||||
            var A = new Vector3(a.X, a.Y, 0);
 | 
			
		||||
            var B = new Vector3(b.X, b.Y, 0);
 | 
			
		||||
            var C = new Vector3(c.X, c.Y, 0);
 | 
			
		||||
 | 
			
		||||
            var first = Vector3.Cross(A, B);
 | 
			
		||||
            var second = Vector3.Cross(first, C);
 | 
			
		||||
 | 
			
		||||
            return new Vector2(second.X, second.Y);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static Vector2 Direction(Vector2 a, Vector2 b)
 | 
			
		||||
        {
 | 
			
		||||
            var d = TripleProduct(a, b, a);
 | 
			
		||||
            var collinear = d == Vector2.Zero;
 | 
			
		||||
            return collinear ? new Vector2(a.Y, -a.X) : d;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static bool SameDirection(Vector2 a, Vector2 b)
 | 
			
		||||
        {
 | 
			
		||||
            return Vector2.Dot(a, b) > 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static Vector2 Perpendicular(Vector2 a, Vector2 b)
 | 
			
		||||
        {
 | 
			
		||||
            return TripleProduct(a, b, a);
 | 
			
		||||
        }
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,67 +0,0 @@
 | 
			
		|||
using System.Collections.Generic;
 | 
			
		||||
using MoonWorks.Math.Float;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Collision.Float
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// A Circle is a shape defined by a radius.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public struct Circle : IShape2D, System.IEquatable<Circle>
 | 
			
		||||
	{
 | 
			
		||||
		public float Radius { get; }
 | 
			
		||||
		public AABB2D AABB { get; }
 | 
			
		||||
		public IEnumerable<IShape2D> Shapes
 | 
			
		||||
		{
 | 
			
		||||
			get
 | 
			
		||||
			{
 | 
			
		||||
				yield return this;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public Circle(float radius)
 | 
			
		||||
		{
 | 
			
		||||
			Radius = radius;
 | 
			
		||||
			AABB = new AABB2D(-Radius, -Radius, Radius, Radius);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public Vector2 Support(Vector2 direction, Transform2D transform)
 | 
			
		||||
		{
 | 
			
		||||
			return Vector2.Transform(Vector2.Normalize(direction) * Radius, transform.TransformMatrix);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public AABB2D TransformedAABB(Transform2D transform2D)
 | 
			
		||||
		{
 | 
			
		||||
			return AABB2D.Transformed(AABB, transform2D);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public override bool Equals(object obj)
 | 
			
		||||
		{
 | 
			
		||||
			return obj is IShape2D other && Equals(other);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public bool Equals(IShape2D other)
 | 
			
		||||
		{
 | 
			
		||||
			return other is Circle circle && Equals(circle);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public bool Equals(Circle other)
 | 
			
		||||
		{
 | 
			
		||||
			return Radius == other.Radius;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public override int GetHashCode()
 | 
			
		||||
		{
 | 
			
		||||
			return System.HashCode.Combine(Radius);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static bool operator ==(Circle a, Circle b)
 | 
			
		||||
		{
 | 
			
		||||
			return a.Equals(b);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static bool operator !=(Circle a, Circle b)
 | 
			
		||||
		{
 | 
			
		||||
			return !(a == b);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,83 +0,0 @@
 | 
			
		|||
using System.Collections.Generic;
 | 
			
		||||
using MoonWorks.Math.Float;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Collision.Float
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
    /// A line is a shape defined by exactly two points in space.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public struct Line : IShape2D, System.IEquatable<Line>
 | 
			
		||||
    {
 | 
			
		||||
        public Vector2 Start { get; }
 | 
			
		||||
        public Vector2 End { get; }
 | 
			
		||||
 | 
			
		||||
        public AABB2D AABB { get; }
 | 
			
		||||
 | 
			
		||||
		public IEnumerable<IShape2D> Shapes
 | 
			
		||||
        {
 | 
			
		||||
            get
 | 
			
		||||
            {
 | 
			
		||||
				yield return this;
 | 
			
		||||
			}
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
		public Line(Vector2 start, Vector2 end)
 | 
			
		||||
        {
 | 
			
		||||
            Start = start;
 | 
			
		||||
            End = end;
 | 
			
		||||
 | 
			
		||||
            AABB = new AABB2D(
 | 
			
		||||
				System.Math.Min(Start.X, End.X),
 | 
			
		||||
				System.Math.Min(Start.Y, End.Y),
 | 
			
		||||
				System.Math.Max(Start.X, End.X),
 | 
			
		||||
				System.Math.Max(Start.Y, End.Y)
 | 
			
		||||
			);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Vector2 Support(Vector2 direction, Transform2D transform)
 | 
			
		||||
        {
 | 
			
		||||
            var transformedStart = Vector2.Transform(Start, transform.TransformMatrix);
 | 
			
		||||
            var transformedEnd = Vector2.Transform(End, transform.TransformMatrix);
 | 
			
		||||
            return Vector2.Dot(transformedStart, direction) > Vector2.Dot(transformedEnd, direction) ?
 | 
			
		||||
                transformedStart :
 | 
			
		||||
                transformedEnd;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public AABB2D TransformedAABB(Transform2D transform)
 | 
			
		||||
        {
 | 
			
		||||
            return AABB2D.Transformed(AABB, transform);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override bool Equals(object obj)
 | 
			
		||||
        {
 | 
			
		||||
            return obj is IShape2D other && Equals(other);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public bool Equals(IShape2D other)
 | 
			
		||||
        {
 | 
			
		||||
            return other is Line otherLine && Equals(otherLine);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public bool Equals(Line other)
 | 
			
		||||
        {
 | 
			
		||||
            return
 | 
			
		||||
				(Start == other.Start && End == other.End) ||
 | 
			
		||||
				(End == other.Start && Start == other.End);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override int GetHashCode()
 | 
			
		||||
        {
 | 
			
		||||
            return System.HashCode.Combine(Start, End);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static bool operator ==(Line a, Line b)
 | 
			
		||||
        {
 | 
			
		||||
            return a.Equals(b);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static bool operator !=(Line a, Line b)
 | 
			
		||||
        {
 | 
			
		||||
            return !(a == b);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,61 +0,0 @@
 | 
			
		|||
using System.Collections.Generic;
 | 
			
		||||
using MoonWorks.Math.Float;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Collision.Float
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// A Point is "that which has no part".
 | 
			
		||||
	/// All points by themselves are identical.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public struct Point : IShape2D, System.IEquatable<Point>
 | 
			
		||||
	{
 | 
			
		||||
		public AABB2D AABB { get; }
 | 
			
		||||
		public IEnumerable<IShape2D> Shapes
 | 
			
		||||
        {
 | 
			
		||||
            get
 | 
			
		||||
            {
 | 
			
		||||
				yield return this;
 | 
			
		||||
			}
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
		public AABB2D TransformedAABB(Transform2D transform)
 | 
			
		||||
		{
 | 
			
		||||
			return AABB2D.Transformed(AABB, transform);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public Vector2 Support(Vector2 direction, Transform2D transform)
 | 
			
		||||
		{
 | 
			
		||||
			return Vector2.Transform(Vector2.Zero, transform.TransformMatrix);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public override bool Equals(object obj)
 | 
			
		||||
		{
 | 
			
		||||
			return obj is IShape2D other && Equals(other);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public bool Equals(IShape2D other)
 | 
			
		||||
		{
 | 
			
		||||
			return other is Point otherPoint && Equals(otherPoint);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public bool Equals(Point other)
 | 
			
		||||
		{
 | 
			
		||||
			return true;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public override int GetHashCode()
 | 
			
		||||
		{
 | 
			
		||||
			return 0;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static bool operator ==(Point a, Point b)
 | 
			
		||||
		{
 | 
			
		||||
			return true;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static bool operator !=(Point a, Point b)
 | 
			
		||||
		{
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,115 +0,0 @@
 | 
			
		|||
using System.Collections.Generic;
 | 
			
		||||
using MoonWorks.Math.Float;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Collision.Float
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// A rectangle is a shape defined by a width and height. The origin is the center of the rectangle.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public struct Rectangle : IShape2D, System.IEquatable<Rectangle>
 | 
			
		||||
	{
 | 
			
		||||
		public AABB2D AABB { get; }
 | 
			
		||||
		public float Width { get; }
 | 
			
		||||
		public float Height { get; }
 | 
			
		||||
 | 
			
		||||
		public float Right { get; }
 | 
			
		||||
		public float Left { get; }
 | 
			
		||||
		public float Top { get; }
 | 
			
		||||
		public float Bottom { get; }
 | 
			
		||||
		public Vector2 TopLeft { get; }
 | 
			
		||||
		public Vector2 BottomRight { get; }
 | 
			
		||||
 | 
			
		||||
		public Vector2 Min { get; }
 | 
			
		||||
		public Vector2 Max { get; }
 | 
			
		||||
 | 
			
		||||
		public IEnumerable<IShape2D> Shapes
 | 
			
		||||
        {
 | 
			
		||||
            get
 | 
			
		||||
            {
 | 
			
		||||
				yield return this;
 | 
			
		||||
			}
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
		public Rectangle(float left, float top, float width, float height)
 | 
			
		||||
		{
 | 
			
		||||
			Width = width;
 | 
			
		||||
			Height = height;
 | 
			
		||||
			Left = left;
 | 
			
		||||
			Right = left + width;
 | 
			
		||||
			Top = top;
 | 
			
		||||
			Bottom = top + height;
 | 
			
		||||
			AABB = new AABB2D(left, top, Right, Bottom);
 | 
			
		||||
			TopLeft = new Vector2(Left, Top);
 | 
			
		||||
			BottomRight = new Vector2(Right, Bottom);
 | 
			
		||||
			Min = AABB.Min;
 | 
			
		||||
			Max = AABB.Max;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private Vector2 Support(Vector2 direction)
 | 
			
		||||
		{
 | 
			
		||||
			if (direction.X >= 0 && direction.Y >= 0)
 | 
			
		||||
			{
 | 
			
		||||
				return Max;
 | 
			
		||||
			}
 | 
			
		||||
			else if (direction.X >= 0 && direction.Y < 0)
 | 
			
		||||
			{
 | 
			
		||||
				return new Vector2(Max.X, Min.Y);
 | 
			
		||||
			}
 | 
			
		||||
			else if (direction.X < 0 && direction.Y >= 0)
 | 
			
		||||
			{
 | 
			
		||||
				return new Vector2(Min.X, Max.Y);
 | 
			
		||||
			}
 | 
			
		||||
			else if (direction.X < 0 && direction.Y < 0)
 | 
			
		||||
			{
 | 
			
		||||
				return new Vector2(Min.X, Min.Y);
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
				throw new System.ArgumentException("Support vector direction cannot be zero.");
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public Vector2 Support(Vector2 direction, Transform2D transform)
 | 
			
		||||
		{
 | 
			
		||||
			Matrix3x2 inverseTransform;
 | 
			
		||||
			Matrix3x2.Invert(transform.TransformMatrix, out inverseTransform);
 | 
			
		||||
			var inverseDirection = Vector2.TransformNormal(direction, inverseTransform);
 | 
			
		||||
			return Vector2.Transform(Support(inverseDirection), transform.TransformMatrix);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public AABB2D TransformedAABB(Transform2D transform)
 | 
			
		||||
		{
 | 
			
		||||
			return AABB2D.Transformed(AABB, transform);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public override bool Equals(object obj)
 | 
			
		||||
		{
 | 
			
		||||
			return obj is IShape2D other && Equals(other);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public bool Equals(IShape2D other)
 | 
			
		||||
		{
 | 
			
		||||
			return (other is Rectangle rectangle && Equals(rectangle));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public bool Equals(Rectangle other)
 | 
			
		||||
		{
 | 
			
		||||
			return Min == other.Min && Max == other.Max;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public override int GetHashCode()
 | 
			
		||||
		{
 | 
			
		||||
			return System.HashCode.Combine(Min, Max);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static bool operator ==(Rectangle a, Rectangle b)
 | 
			
		||||
		{
 | 
			
		||||
			return a.Equals(b);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static bool operator !=(Rectangle a, Rectangle b)
 | 
			
		||||
		{
 | 
			
		||||
			return !(a == b);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,136 +0,0 @@
 | 
			
		|||
using System.Collections.Generic;
 | 
			
		||||
using MoonWorks.Math.Float;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Collision.Float
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// A simplex is a shape with up to n - 2 vertices in the nth dimension.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public struct Simplex2D : System.IEquatable<Simplex2D>
 | 
			
		||||
	{
 | 
			
		||||
		private Vector2 a;
 | 
			
		||||
		private Vector2? b;
 | 
			
		||||
		private Vector2? c;
 | 
			
		||||
 | 
			
		||||
		public Vector2 A => a;
 | 
			
		||||
		public Vector2? B => b;
 | 
			
		||||
		public Vector2? C => c;
 | 
			
		||||
 | 
			
		||||
		public bool ZeroSimplex { get { return !b.HasValue && !c.HasValue; } }
 | 
			
		||||
		public bool OneSimplex { get { return b.HasValue && !c.HasValue; } }
 | 
			
		||||
		public bool TwoSimplex { get { return b.HasValue && c.HasValue; } }
 | 
			
		||||
 | 
			
		||||
		public int Count => TwoSimplex ? 3 : (OneSimplex ? 2 : 1);
 | 
			
		||||
 | 
			
		||||
		public Simplex2D(Vector2 a)
 | 
			
		||||
		{
 | 
			
		||||
			this.a = a;
 | 
			
		||||
			b = null;
 | 
			
		||||
			c = null;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public Simplex2D(Vector2 a, Vector2 b)
 | 
			
		||||
		{
 | 
			
		||||
			this.a = a;
 | 
			
		||||
			this.b = b;
 | 
			
		||||
			c = null;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public Simplex2D(Vector2 a, Vector2 b, Vector2 c)
 | 
			
		||||
		{
 | 
			
		||||
			this.a = a;
 | 
			
		||||
			this.b = b;
 | 
			
		||||
			this.c = c;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public Vector2 this[int index]
 | 
			
		||||
		{
 | 
			
		||||
			get
 | 
			
		||||
			{
 | 
			
		||||
				if (index == 0) { return a; }
 | 
			
		||||
				if (index == 1) { return b.Value; }
 | 
			
		||||
				if (index == 2) { return c.Value; }
 | 
			
		||||
				throw new System.IndexOutOfRangeException();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public IEnumerable<Vector2> Vertices
 | 
			
		||||
		{
 | 
			
		||||
			get
 | 
			
		||||
			{
 | 
			
		||||
				yield return (Vector2) a;
 | 
			
		||||
				if (b.HasValue) { yield return (Vector2) b; }
 | 
			
		||||
				if (c.HasValue) { yield return (Vector2) c; }
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public Vector2 Support(Vector2 direction, Transform2D transform)
 | 
			
		||||
		{
 | 
			
		||||
			var maxDotProduct = float.NegativeInfinity;
 | 
			
		||||
			var maxVertex = a;
 | 
			
		||||
			foreach (var vertex in Vertices)
 | 
			
		||||
			{
 | 
			
		||||
				var transformed = Vector2.Transform(vertex, transform.TransformMatrix);
 | 
			
		||||
				var dot = Vector2.Dot(transformed, direction);
 | 
			
		||||
				if (dot > maxDotProduct)
 | 
			
		||||
				{
 | 
			
		||||
					maxVertex = transformed;
 | 
			
		||||
					maxDotProduct = dot;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			return maxVertex;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public void Insert(Vector2 point, int index)
 | 
			
		||||
		{
 | 
			
		||||
			if (index == 0)
 | 
			
		||||
			{
 | 
			
		||||
				c = b;
 | 
			
		||||
				b = a;
 | 
			
		||||
				a = point;
 | 
			
		||||
			}
 | 
			
		||||
			else if (index == 1)
 | 
			
		||||
			{
 | 
			
		||||
				c = b;
 | 
			
		||||
				b = point;
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
				c = point;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public override bool Equals(object obj)
 | 
			
		||||
		{
 | 
			
		||||
			return obj is Simplex2D other && Equals(other);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public bool Equals(Simplex2D other)
 | 
			
		||||
		{
 | 
			
		||||
			if (Count != other.Count) { return false; }
 | 
			
		||||
 | 
			
		||||
			return
 | 
			
		||||
				(A == other.A && B == other.B && C == other.C) ||
 | 
			
		||||
				(A == other.A && B == other.C && C == other.B) ||
 | 
			
		||||
				(A == other.B && B == other.A && C == other.C) ||
 | 
			
		||||
				(A == other.B && B == other.C && C == other.A) ||
 | 
			
		||||
				(A == other.C && B == other.A && C == other.B) ||
 | 
			
		||||
				(A == other.C && B == other.B && C == other.A);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public override int GetHashCode()
 | 
			
		||||
		{
 | 
			
		||||
			return System.HashCode.Combine(Vertices);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static bool operator ==(Simplex2D a, Simplex2D b)
 | 
			
		||||
		{
 | 
			
		||||
			return a.Equals(b);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static bool operator !=(Simplex2D a, Simplex2D b)
 | 
			
		||||
		{
 | 
			
		||||
			return !(a == b);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,253 +0,0 @@
 | 
			
		|||
using System.Collections.Generic;
 | 
			
		||||
using MoonWorks.Math.Float;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Collision.Float
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// Used to quickly check if two shapes are potentially overlapping.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	/// <typeparam name="T">The type that will be used to uniquely identify shape-transform pairs.</typeparam>
 | 
			
		||||
	public class SpatialHash2D<T> where T : System.IEquatable<T>
 | 
			
		||||
	{
 | 
			
		||||
		private readonly int cellSize;
 | 
			
		||||
 | 
			
		||||
		private readonly Dictionary<long, HashSet<T>> hashDictionary = new Dictionary<long, HashSet<T>>();
 | 
			
		||||
		// FIXME: this ICollidable causes boxing which triggers garbage collection
 | 
			
		||||
		private readonly Dictionary<T, (ICollidable, Transform2D, uint)> IDLookup = new Dictionary<T, (ICollidable, Transform2D, uint)>();
 | 
			
		||||
 | 
			
		||||
		public int MinX { get; private set; } = 0;
 | 
			
		||||
		public int MaxX { get; private set; } = 0;
 | 
			
		||||
		public int MinY { get; private set; } = 0;
 | 
			
		||||
		public int MaxY { get; private set; } = 0;
 | 
			
		||||
 | 
			
		||||
		private Queue<HashSet<T>> hashSetPool = new Queue<HashSet<T>>();
 | 
			
		||||
 | 
			
		||||
		public SpatialHash2D(int cellSize)
 | 
			
		||||
		{
 | 
			
		||||
			this.cellSize = cellSize;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private (int, int) Hash(Vector2 position)
 | 
			
		||||
		{
 | 
			
		||||
			return ((int) System.Math.Floor(position.X / cellSize), (int) System.Math.Floor(position.Y / cellSize));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Inserts an element into the SpatialHash.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="id">A unique ID for the shape-transform pair.</param>
 | 
			
		||||
		/// <param name="shape"></param>
 | 
			
		||||
		/// <param name="transform2D"></param>
 | 
			
		||||
		/// <param name="collisionGroups">A bitmask value specifying the groups this object belongs to.</param>
 | 
			
		||||
		public void Insert(T id, ICollidable shape, Transform2D transform2D, uint collisionGroups = uint.MaxValue)
 | 
			
		||||
		{
 | 
			
		||||
			var box = shape.TransformedAABB(transform2D);
 | 
			
		||||
			var minHash = Hash(box.Min);
 | 
			
		||||
			var maxHash = Hash(box.Max);
 | 
			
		||||
 | 
			
		||||
			foreach (var key in Keys(minHash.Item1, minHash.Item2, maxHash.Item1, maxHash.Item2))
 | 
			
		||||
			{
 | 
			
		||||
				if (!hashDictionary.ContainsKey(key))
 | 
			
		||||
				{
 | 
			
		||||
					hashDictionary.Add(key, new HashSet<T>());
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				hashDictionary[key].Add(id);
 | 
			
		||||
				IDLookup[id] = (shape, transform2D, collisionGroups);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			MinX = System.Math.Min(MinX, minHash.Item1);
 | 
			
		||||
			MinY = System.Math.Min(MinY, minHash.Item2);
 | 
			
		||||
			MaxX = System.Math.Max(MaxX, maxHash.Item1);
 | 
			
		||||
			MaxY = System.Math.Max(MaxY, maxHash.Item2);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Retrieves all the potential collisions of a shape-transform pair. Excludes any shape-transforms with the given ID.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public IEnumerable<(T, ICollidable, Transform2D, uint)> Retrieve(T id, ICollidable shape, Transform2D transform2D, uint collisionMask = uint.MaxValue)
 | 
			
		||||
		{
 | 
			
		||||
			var returned = AcquireHashSet();
 | 
			
		||||
 | 
			
		||||
			var box = shape.TransformedAABB(transform2D);
 | 
			
		||||
			var (minX, minY) = Hash(box.Min);
 | 
			
		||||
			var (maxX, maxY) = Hash(box.Max);
 | 
			
		||||
 | 
			
		||||
			if (minX < MinX) { minX = MinX; }
 | 
			
		||||
			if (maxX > MaxX) { maxX = MaxX; }
 | 
			
		||||
			if (minY < MinY) { minY = MinY; }
 | 
			
		||||
			if (maxY > MaxY) { maxY = MaxY; }
 | 
			
		||||
 | 
			
		||||
			foreach (var key in Keys(minX, minY, maxX, maxY))
 | 
			
		||||
			{
 | 
			
		||||
				if (hashDictionary.ContainsKey(key))
 | 
			
		||||
				{
 | 
			
		||||
					foreach (var t in hashDictionary[key])
 | 
			
		||||
					{
 | 
			
		||||
						if (!returned.Contains(t))
 | 
			
		||||
						{
 | 
			
		||||
							var (otherShape, otherTransform, collisionGroups) = IDLookup[t];
 | 
			
		||||
							if (!id.Equals(t) && ((collisionGroups & collisionMask) > 0) && AABB2D.TestOverlap(box, otherShape.TransformedAABB(otherTransform)))
 | 
			
		||||
							{
 | 
			
		||||
								returned.Add(t);
 | 
			
		||||
								yield return (t, otherShape, otherTransform, collisionGroups);
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			FreeHashSet(returned);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Retrieves all the potential collisions of a shape-transform pair.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public IEnumerable<(T, ICollidable, Transform2D, uint)> Retrieve(ICollidable shape, Transform2D transform2D, uint collisionMask = uint.MaxValue)
 | 
			
		||||
		{
 | 
			
		||||
			var returned = AcquireHashSet();
 | 
			
		||||
 | 
			
		||||
			var box = shape.TransformedAABB(transform2D);
 | 
			
		||||
			var (minX, minY) = Hash(box.Min);
 | 
			
		||||
			var (maxX, maxY) = Hash(box.Max);
 | 
			
		||||
 | 
			
		||||
			if (minX < MinX) { minX = MinX; }
 | 
			
		||||
			if (maxX > MaxX) { maxX = MaxX; }
 | 
			
		||||
			if (minY < MinY) { minY = MinY; }
 | 
			
		||||
			if (maxY > MaxY) { maxY = MaxY; }
 | 
			
		||||
 | 
			
		||||
			foreach (var key in Keys(minX, minY, maxX, maxY))
 | 
			
		||||
			{
 | 
			
		||||
				if (hashDictionary.ContainsKey(key))
 | 
			
		||||
				{
 | 
			
		||||
					foreach (var t in hashDictionary[key])
 | 
			
		||||
					{
 | 
			
		||||
						if (!returned.Contains(t))
 | 
			
		||||
						{
 | 
			
		||||
							var (otherShape, otherTransform, collisionGroups) = IDLookup[t];
 | 
			
		||||
							if (((collisionGroups & collisionMask) > 0) && AABB2D.TestOverlap(box, otherShape.TransformedAABB(otherTransform)))
 | 
			
		||||
							{
 | 
			
		||||
								returned.Add(t);
 | 
			
		||||
								yield return (t, otherShape, otherTransform, collisionGroups);
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			FreeHashSet(returned);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Retrieves objects based on a pre-transformed AABB.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="aabb">A transformed AABB.</param>
 | 
			
		||||
		/// <returns></returns>
 | 
			
		||||
		public IEnumerable<(T, ICollidable, Transform2D, uint)> Retrieve(AABB2D aabb, uint collisionMask = uint.MaxValue)
 | 
			
		||||
		{
 | 
			
		||||
			var returned = AcquireHashSet();
 | 
			
		||||
 | 
			
		||||
			var (minX, minY) = Hash(aabb.Min);
 | 
			
		||||
			var (maxX, maxY) = Hash(aabb.Max);
 | 
			
		||||
 | 
			
		||||
			if (minX < MinX) { minX = MinX; }
 | 
			
		||||
			if (maxX > MaxX) { maxX = MaxX; }
 | 
			
		||||
			if (minY < MinY) { minY = MinY; }
 | 
			
		||||
			if (maxY > MaxY) { maxY = MaxY; }
 | 
			
		||||
 | 
			
		||||
			foreach (var key in Keys(minX, minY, maxX, maxY))
 | 
			
		||||
			{
 | 
			
		||||
				if (hashDictionary.ContainsKey(key))
 | 
			
		||||
				{
 | 
			
		||||
					foreach (var t in hashDictionary[key])
 | 
			
		||||
					{
 | 
			
		||||
						if (!returned.Contains(t))
 | 
			
		||||
						{
 | 
			
		||||
							var (otherShape, otherTransform, collisionGroups) = IDLookup[t];
 | 
			
		||||
							if (((collisionGroups & collisionMask) > 0) && AABB2D.TestOverlap(aabb, otherShape.TransformedAABB(otherTransform)))
 | 
			
		||||
							{
 | 
			
		||||
								yield return (t, otherShape, otherTransform, collisionGroups);
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			FreeHashSet(returned);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public void Update(T id, ICollidable shape, Transform2D transform2D, uint collisionGroups = uint.MaxValue)
 | 
			
		||||
		{
 | 
			
		||||
			Remove(id);
 | 
			
		||||
			Insert(id, shape, transform2D, collisionGroups);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Removes a specific ID from the SpatialHash.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public void Remove(T id)
 | 
			
		||||
		{
 | 
			
		||||
			var (shape, transform, collisionGroups) = IDLookup[id];
 | 
			
		||||
 | 
			
		||||
			var box = shape.TransformedAABB(transform);
 | 
			
		||||
			var minHash = Hash(box.Min);
 | 
			
		||||
			var maxHash = Hash(box.Max);
 | 
			
		||||
 | 
			
		||||
			foreach (var key in Keys(minHash.Item1, minHash.Item2, maxHash.Item1, maxHash.Item2))
 | 
			
		||||
			{
 | 
			
		||||
				if (hashDictionary.ContainsKey(key))
 | 
			
		||||
				{
 | 
			
		||||
					hashDictionary[key].Remove(id);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			IDLookup.Remove(id);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Removes everything that has been inserted into the SpatialHash.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public void Clear()
 | 
			
		||||
		{
 | 
			
		||||
			foreach (var hash in hashDictionary.Values)
 | 
			
		||||
			{
 | 
			
		||||
				hash.Clear();
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			IDLookup.Clear();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private static long MakeLong(int left, int right)
 | 
			
		||||
		{
 | 
			
		||||
			return ((long) left << 32) | ((uint) right);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private IEnumerable<long> Keys(int minX, int minY, int maxX, int maxY)
 | 
			
		||||
		{
 | 
			
		||||
			for (var i = minX; i <= maxX; i++)
 | 
			
		||||
			{
 | 
			
		||||
				for (var j = minY; j <= maxY; j++)
 | 
			
		||||
				{
 | 
			
		||||
					yield return MakeLong(i, j);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private HashSet<T> AcquireHashSet()
 | 
			
		||||
		{
 | 
			
		||||
			if (hashSetPool.Count == 0)
 | 
			
		||||
			{
 | 
			
		||||
				hashSetPool.Enqueue(new HashSet<T>());
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			var hashSet = hashSetPool.Dequeue();
 | 
			
		||||
			hashSet.Clear();
 | 
			
		||||
			return hashSet;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private void FreeHashSet(HashSet<T> hashSet)
 | 
			
		||||
		{
 | 
			
		||||
			hashSetPool.Enqueue(hashSet);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,9 +1,13 @@
 | 
			
		|||
using System.Collections.Generic;
 | 
			
		||||
using System.Runtime.InteropServices;
 | 
			
		||||
using MoonWorks.Graphics;
 | 
			
		||||
using MoonWorks.Graphics.PackedVector;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// Conversion utilities for interop.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public static class Conversions
 | 
			
		||||
	{
 | 
			
		||||
		private readonly static Dictionary<VertexElementFormat, uint> Sizes = new Dictionary<VertexElementFormat, uint>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,12 +0,0 @@
 | 
			
		|||
using System;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks
 | 
			
		||||
{
 | 
			
		||||
	public class AudioLoadException : Exception
 | 
			
		||||
	{
 | 
			
		||||
		public AudioLoadException(string message) : base(message)
 | 
			
		||||
		{
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -2,13 +2,27 @@ namespace MoonWorks
 | 
			
		|||
{
 | 
			
		||||
	public enum FrameLimiterMode
 | 
			
		||||
	{
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// The game will render at the maximum possible framerate that the computing resources allow. <br/>
 | 
			
		||||
		/// Note that this may lead to overheating, resource starvation, etc.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		Uncapped,
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// The game will render no more than the specified frames per second.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		Capped
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// The Game's frame limiter setting. Specifies uncapped framerate or a maximum rendering frames per second value. <br/>
 | 
			
		||||
	/// Note that this is separate from the Game's Update timestep and can be a different value.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public struct FrameLimiterSettings
 | 
			
		||||
	{
 | 
			
		||||
		public FrameLimiterMode Mode;
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// If Mode is set to Capped, this is the maximum frames per second that will be rendered.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public int Cap;
 | 
			
		||||
 | 
			
		||||
		public FrameLimiterSettings(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										99
									
								
								src/Game.cs
								
								
								
								
							
							
						
						
									
										99
									
								
								src/Game.cs
								
								
								
								
							| 
						 | 
				
			
			@ -1,5 +1,4 @@
 | 
			
		|||
using System.Collections.Generic;
 | 
			
		||||
using SDL2;
 | 
			
		||||
using SDL2;
 | 
			
		||||
using MoonWorks.Audio;
 | 
			
		||||
using MoonWorks.Graphics;
 | 
			
		||||
using MoonWorks.Input;
 | 
			
		||||
| 
						 | 
				
			
			@ -9,6 +8,12 @@ using System.Diagnostics;
 | 
			
		|||
 | 
			
		||||
namespace MoonWorks
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// This class is your entry point into controlling your game. <br/>
 | 
			
		||||
	/// It manages the main game loop and subsystems. <br/>
 | 
			
		||||
	/// You should inherit this class and implement Update and Draw methods. <br/>
 | 
			
		||||
	/// Then instantiate your Game subclass from your Program.Main method and call the Run method.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public abstract class Game
 | 
			
		||||
	{
 | 
			
		||||
		public TimeSpan MAX_DELTA_TIME = TimeSpan.FromMilliseconds(100);
 | 
			
		||||
| 
						 | 
				
			
			@ -33,8 +38,18 @@ namespace MoonWorks
 | 
			
		|||
		public AudioDevice AudioDevice { get; }
 | 
			
		||||
		public Inputs Inputs { get; }
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// This Window is automatically created when your Game is instantiated.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public Window MainWindow { get; }
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Instantiates your Game.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="windowCreateInfo">The parameters that will be used to create the MainWindow.</param>
 | 
			
		||||
		/// <param name="frameLimiterSettings">The frame limiter settings.</param>
 | 
			
		||||
		/// <param name="targetTimestep">How often Game.Update will run in terms of ticks per second.</param>
 | 
			
		||||
		/// <param name="debugMode">If true, enables extra debug checks. Should be turned off for release builds.</param>
 | 
			
		||||
		public Game(
 | 
			
		||||
			WindowCreateInfo windowCreateInfo,
 | 
			
		||||
			FrameLimiterSettings frameLimiterSettings,
 | 
			
		||||
| 
						 | 
				
			
			@ -42,6 +57,7 @@ namespace MoonWorks
 | 
			
		|||
			bool debugMode = false
 | 
			
		||||
		)
 | 
			
		||||
		{
 | 
			
		||||
			Logger.LogInfo("Initializing frame limiter...");
 | 
			
		||||
			Timestep = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / targetTimestep);
 | 
			
		||||
			gameTimer = Stopwatch.StartNew();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -52,21 +68,25 @@ namespace MoonWorks
 | 
			
		|||
				previousSleepTimes[i] = TimeSpan.FromMilliseconds(1);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			Logger.LogInfo("Initializing SDL...");
 | 
			
		||||
			if (SDL.SDL_Init(SDL.SDL_INIT_VIDEO | SDL.SDL_INIT_TIMER | SDL.SDL_INIT_GAMECONTROLLER) < 0)
 | 
			
		||||
			{
 | 
			
		||||
				System.Console.WriteLine("Failed to initialize SDL!");
 | 
			
		||||
				Logger.LogError("Failed to initialize SDL!");
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			Logger.Initialize();
 | 
			
		||||
 | 
			
		||||
			Logger.LogInfo("Initializing input...");
 | 
			
		||||
			Inputs = new Inputs();
 | 
			
		||||
 | 
			
		||||
			Logger.LogInfo("Initializing graphics device...");
 | 
			
		||||
			GraphicsDevice = new GraphicsDevice(
 | 
			
		||||
				Backend.Vulkan,
 | 
			
		||||
				debugMode
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
			Logger.LogInfo("Initializing main window...");
 | 
			
		||||
			MainWindow = new Window(windowCreateInfo, GraphicsDevice.WindowFlags | SDL.SDL_WindowFlags.SDL_WINDOW_HIDDEN);
 | 
			
		||||
 | 
			
		||||
			if (!GraphicsDevice.ClaimWindow(MainWindow, windowCreateInfo.PresentMode))
 | 
			
		||||
| 
						 | 
				
			
			@ -74,9 +94,13 @@ namespace MoonWorks
 | 
			
		|||
				throw new System.SystemException("Could not claim window!");
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			Logger.LogInfo("Initializing audio thread...");
 | 
			
		||||
			AudioDevice = new AudioDevice();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Initiates the main game loop. Call this once from your Program.Main method.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public void Run()
 | 
			
		||||
		{
 | 
			
		||||
			MainWindow.Show();
 | 
			
		||||
| 
						 | 
				
			
			@ -86,15 +110,29 @@ namespace MoonWorks
 | 
			
		|||
				Tick();
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			Logger.LogInfo("Starting shutdown sequence...");
 | 
			
		||||
 | 
			
		||||
			Logger.LogInfo("Cleaning up game...");
 | 
			
		||||
			Destroy();
 | 
			
		||||
 | 
			
		||||
			AudioDevice.Dispose();
 | 
			
		||||
			Logger.LogInfo("Unclaiming window...");
 | 
			
		||||
			GraphicsDevice.UnclaimWindow(MainWindow);
 | 
			
		||||
 | 
			
		||||
			Logger.LogInfo("Disposing window...");
 | 
			
		||||
			MainWindow.Dispose();
 | 
			
		||||
 | 
			
		||||
			Logger.LogInfo("Disposing graphics device...");
 | 
			
		||||
			GraphicsDevice.Dispose();
 | 
			
		||||
 | 
			
		||||
			Logger.LogInfo("Closing audio thread...");
 | 
			
		||||
			AudioDevice.Dispose();
 | 
			
		||||
 | 
			
		||||
			SDL.SDL_Quit();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Updates the frame limiter settings.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public void SetFrameLimiter(FrameLimiterSettings settings)
 | 
			
		||||
		{
 | 
			
		||||
			FramerateCapped = settings.Mode == FrameLimiterMode.Capped;
 | 
			
		||||
| 
						 | 
				
			
			@ -109,18 +147,42 @@ namespace MoonWorks
 | 
			
		|||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Starts the game shutdown process.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public void Quit()
 | 
			
		||||
		{
 | 
			
		||||
			quit = true;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Will execute at the specified targetTimestep you provided when instantiating your Game class.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="delta"></param>
 | 
			
		||||
		protected abstract void Update(TimeSpan delta);
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// If the frame limiter mode is Capped, this will run at most Cap times per second. <br />
 | 
			
		||||
		/// Otherwise it will run as many times as possible.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="alpha">A value from 0-1 describing how "in-between" update ticks it is called. Useful for interpolation.</param>
 | 
			
		||||
		protected abstract void Draw(double alpha);
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// You can optionally override this to perform cleanup tasks before the game quits.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		protected virtual void Destroy() {}
 | 
			
		||||
 | 
			
		||||
		// Called when a file is dropped on the game window.
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Called when a file is dropped on the game window.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		protected virtual void DropFile(string filePath) {}
 | 
			
		||||
 | 
			
		||||
		/* Required to distinguish between multiple files dropped at once
 | 
			
		||||
		 * vs multiple files dropped one at a time.
 | 
			
		||||
		 *
 | 
			
		||||
		 * Called once for every multi-file drop.
 | 
			
		||||
		 */
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Required to distinguish between multiple files dropped at once
 | 
			
		||||
		/// vs multiple files dropped one at a time.
 | 
			
		||||
		/// Called once for every multi-file drop.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		protected virtual void DropBegin() {}
 | 
			
		||||
		protected virtual void DropComplete() {}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -168,9 +230,8 @@ namespace MoonWorks
 | 
			
		|||
				while (accumulatedUpdateTime >= Timestep)
 | 
			
		||||
				{
 | 
			
		||||
					Inputs.Update();
 | 
			
		||||
					AudioDevice.Update();
 | 
			
		||||
 | 
			
		||||
					Update(Timestep);
 | 
			
		||||
					AudioDevice.WakeThread();
 | 
			
		||||
 | 
			
		||||
					accumulatedUpdateTime -= Timestep;
 | 
			
		||||
				}
 | 
			
		||||
| 
						 | 
				
			
			@ -282,17 +343,27 @@ namespace MoonWorks
 | 
			
		|||
			var index = evt.cdevice.which;
 | 
			
		||||
			if (SDL.SDL_IsGameController(index) == SDL.SDL_bool.SDL_TRUE)
 | 
			
		||||
			{
 | 
			
		||||
				System.Console.WriteLine($"New controller detected!");
 | 
			
		||||
				Logger.LogInfo("New controller detected!");
 | 
			
		||||
				Inputs.AddGamepad(index);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private void HandleControllerRemoved(SDL.SDL_Event evt)
 | 
			
		||||
		{
 | 
			
		||||
			System.Console.WriteLine($"Controller removal detected!");
 | 
			
		||||
			Logger.LogInfo("Controller removal detected!");
 | 
			
		||||
			Inputs.RemoveGamepad(evt.cdevice.which);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static void ShowRuntimeError(string title, string message)
 | 
			
		||||
		{
 | 
			
		||||
			SDL.SDL_ShowSimpleMessageBox(
 | 
			
		||||
				SDL.SDL_MessageBoxFlags.SDL_MESSAGEBOX_ERROR,
 | 
			
		||||
				title ?? "",
 | 
			
		||||
				message ?? "",
 | 
			
		||||
				IntPtr.Zero
 | 
			
		||||
			);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private TimeSpan AdvanceElapsedTime()
 | 
			
		||||
		{
 | 
			
		||||
			long currentTicks = gameTimer.Elapsed.Ticks;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,8 @@
 | 
			
		|||
namespace MoonWorks.Graphics
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// A buffer-offset pair to be used when binding vertex buffers.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public struct BufferBinding
 | 
			
		||||
	{
 | 
			
		||||
		public Buffer Buffer;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,8 @@
 | 
			
		|||
using System;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Graphics
 | 
			
		||||
namespace MoonWorks.Graphics
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// A texture-sampler pair to be used when binding samplers.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public struct TextureSamplerBinding
 | 
			
		||||
	{
 | 
			
		||||
		public Texture Texture;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,6 +20,7 @@ using System.Diagnostics;
 | 
			
		|||
using System.Text;
 | 
			
		||||
using MoonWorks.Math;
 | 
			
		||||
using MoonWorks.Math.Float;
 | 
			
		||||
using MoonWorks.Graphics.PackedVector;
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Graphics
 | 
			
		||||
| 
						 | 
				
			
			@ -1758,6 +1759,67 @@ namespace MoonWorks.Graphics
 | 
			
		|||
			);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Modified from one of the responses here:
 | 
			
		||||
		// https://stackoverflow.com/questions/3018313/algorithm-to-convert-rgb-to-hsv-and-hsv-to-rgb-in-range-0-255-for-both/6930407#6930407
 | 
			
		||||
		public static Color FromHSV(float r, float g, float b)
 | 
			
		||||
		{
 | 
			
		||||
			r = (100 + r) % 1f;
 | 
			
		||||
 | 
			
		||||
			float hueSlice = 6 * r; // [0, 6)
 | 
			
		||||
			float hueSliceInteger = MathF.Floor(hueSlice);
 | 
			
		||||
 | 
			
		||||
			// In [0,1) for each hue slice
 | 
			
		||||
			float hueSliceInterpolant = hueSlice - hueSliceInteger;
 | 
			
		||||
 | 
			
		||||
			Vector3 tempRGB = new Vector3(
 | 
			
		||||
				b * (1f - g),
 | 
			
		||||
				b * (1f - g * hueSliceInterpolant),
 | 
			
		||||
				b * (1f - g * (1f - hueSliceInterpolant))
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
			// The idea here to avoid conditions is to notice that the conversion code can be rewritten:
 | 
			
		||||
			//    if      ( var_i == 0 ) { R = V         ; G = TempRGB.z ; B = TempRGB.x }
 | 
			
		||||
			//    else if ( var_i == 2 ) { R = TempRGB.x ; G = V         ; B = TempRGB.z }
 | 
			
		||||
			//    else if ( var_i == 4 ) { R = TempRGB.z ; G = TempRGB.x ; B = V     }
 | 
			
		||||
			//
 | 
			
		||||
			//    else if ( var_i == 1 ) { R = TempRGB.y ; G = V         ; B = TempRGB.x }
 | 
			
		||||
			//    else if ( var_i == 3 ) { R = TempRGB.x ; G = TempRGB.y ; B = V     }
 | 
			
		||||
			//    else if ( var_i == 5 ) { R = V         ; G = TempRGB.x ; B = TempRGB.y }
 | 
			
		||||
			//
 | 
			
		||||
			// This shows several things:
 | 
			
		||||
			//  . A separation between even and odd slices
 | 
			
		||||
			//  . If slices (0,2,4) and (1,3,5) can be rewritten as basically being slices (0,1,2) then
 | 
			
		||||
			//      the operation simply amounts to performing a "rotate right" on the RGB components
 | 
			
		||||
			//  . The base value to rotate is either (V, B, R) for even slices or (G, V, R) for odd slices
 | 
			
		||||
			//
 | 
			
		||||
			float isOddSlice =  hueSliceInteger % 2f;                          // 0 if even (slices 0, 2, 4), 1 if odd (slices 1, 3, 5)
 | 
			
		||||
			float threeSliceSelector = 0.5f * (hueSliceInteger - isOddSlice);  // (0, 1, 2) corresponding to slices (0, 2, 4) and (1, 3, 5)
 | 
			
		||||
 | 
			
		||||
			Vector3 scrollingRGBForEvenSlices = new Vector3(b, tempRGB.Z, tempRGB.X); // (V, Temp Blue, Temp Red) for even slices (0, 2, 4)
 | 
			
		||||
			Vector3 scrollingRGBForOddSlices = new Vector3(tempRGB.Y, b, tempRGB.X);  // (Temp Green, V, Temp Red) for odd slices (1, 3, 5)
 | 
			
		||||
			Vector3 scrollingRGB = Vector3.Lerp(scrollingRGBForEvenSlices, scrollingRGBForOddSlices, isOddSlice);
 | 
			
		||||
 | 
			
		||||
			float IsNotFirstSlice = MathHelper.Clamp(threeSliceSelector, 0f, 1f);        // 1 if NOT the first slice (true for slices 1 and 2)
 | 
			
		||||
			float IsNotSecondSlice = MathHelper.Clamp(threeSliceSelector - 1f, 0f, 1f);  // 1 if NOT the first or second slice (true only for slice 2)
 | 
			
		||||
 | 
			
		||||
			Vector3 color = Vector3.Lerp(
 | 
			
		||||
				scrollingRGB,
 | 
			
		||||
				Vector3.Lerp(
 | 
			
		||||
					new Vector3(scrollingRGB.Z, scrollingRGB.X, scrollingRGB.Y),
 | 
			
		||||
					new Vector3(scrollingRGB.Y, scrollingRGB.Z, scrollingRGB.X),
 | 
			
		||||
					IsNotSecondSlice
 | 
			
		||||
				),
 | 
			
		||||
				IsNotFirstSlice
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
			return new Color(color);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static Color FromHSV(int r, int g, int b)
 | 
			
		||||
		{
 | 
			
		||||
			return Color.FromHSV(r / 255f, g / 255f, b / 255f);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		#endregion
 | 
			
		||||
 | 
			
		||||
		#region Public Static Operators and Override Methods
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
				
			
			@ -0,0 +1,34 @@
 | 
			
		|||
using System;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Graphics
 | 
			
		||||
{
 | 
			
		||||
	internal class CommandBufferPool
 | 
			
		||||
	{
 | 
			
		||||
		private GraphicsDevice GraphicsDevice;
 | 
			
		||||
		private ConcurrentQueue<CommandBuffer> CommandBuffers = new ConcurrentQueue<CommandBuffer>();
 | 
			
		||||
 | 
			
		||||
		public CommandBufferPool(GraphicsDevice graphicsDevice)
 | 
			
		||||
		{
 | 
			
		||||
			GraphicsDevice = graphicsDevice;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public CommandBuffer Obtain()
 | 
			
		||||
		{
 | 
			
		||||
			if (CommandBuffers.TryDequeue(out var commandBuffer))
 | 
			
		||||
			{
 | 
			
		||||
				return commandBuffer;
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
				return new CommandBuffer(GraphicsDevice);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public void Return(CommandBuffer commandBuffer)
 | 
			
		||||
		{
 | 
			
		||||
			commandBuffer.Handle = IntPtr.Zero;
 | 
			
		||||
			CommandBuffers.Enqueue(commandBuffer);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,32 @@
 | 
			
		|||
using System.Collections.Concurrent;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Graphics
 | 
			
		||||
{
 | 
			
		||||
	internal class FencePool
 | 
			
		||||
	{
 | 
			
		||||
		private GraphicsDevice GraphicsDevice;
 | 
			
		||||
		private ConcurrentQueue<Fence> Fences = new ConcurrentQueue<Fence>();
 | 
			
		||||
 | 
			
		||||
		public FencePool(GraphicsDevice graphicsDevice)
 | 
			
		||||
		{
 | 
			
		||||
			GraphicsDevice = graphicsDevice;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public Fence Obtain()
 | 
			
		||||
		{
 | 
			
		||||
			if (Fences.TryDequeue(out var fence))
 | 
			
		||||
			{
 | 
			
		||||
				return fence;
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
				return new Fence(GraphicsDevice);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public void Return(Fence fence)
 | 
			
		||||
		{
 | 
			
		||||
			Fences.Enqueue(fence);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,44 +1,121 @@
 | 
			
		|||
using System;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Runtime.InteropServices;
 | 
			
		||||
using WellspringCS;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Graphics.Font
 | 
			
		||||
{
 | 
			
		||||
    public class Font : IDisposable
 | 
			
		||||
    {
 | 
			
		||||
		public IntPtr Handle { get; }
 | 
			
		||||
	public unsafe class Font : GraphicsResource
 | 
			
		||||
	{
 | 
			
		||||
		public Texture Texture { get; }
 | 
			
		||||
		public float PixelsPerEm { get; }
 | 
			
		||||
		public float DistanceRange { get; }
 | 
			
		||||
 | 
			
		||||
		private bool IsDisposed;
 | 
			
		||||
		internal IntPtr Handle { get; }
 | 
			
		||||
 | 
			
		||||
        public unsafe Font(string path)
 | 
			
		||||
        {
 | 
			
		||||
            var bytes = File.ReadAllBytes(path);
 | 
			
		||||
			fixed (byte* pByte = &bytes[0])
 | 
			
		||||
		private byte* StringBytes;
 | 
			
		||||
		private int StringBytesLength;
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Loads a TTF or OTF font from a path for use in MSDF rendering.
 | 
			
		||||
		/// Note that there must be an msdf-atlas-gen JSON and image file alongside.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <returns></returns>
 | 
			
		||||
		public unsafe static Font Load(
 | 
			
		||||
			GraphicsDevice graphicsDevice,
 | 
			
		||||
			CommandBuffer commandBuffer,
 | 
			
		||||
			string fontPath
 | 
			
		||||
		) {
 | 
			
		||||
			var fontFileStream = new FileStream(fontPath, FileMode.Open, FileAccess.Read);
 | 
			
		||||
			var fontFileByteBuffer = NativeMemory.Alloc((nuint) fontFileStream.Length);
 | 
			
		||||
			var fontFileByteSpan = new Span<byte>(fontFileByteBuffer, (int) fontFileStream.Length);
 | 
			
		||||
			fontFileStream.ReadExactly(fontFileByteSpan);
 | 
			
		||||
			fontFileStream.Close();
 | 
			
		||||
 | 
			
		||||
			var atlasFileStream = new FileStream(Path.ChangeExtension(fontPath, ".json"), FileMode.Open, FileAccess.Read);
 | 
			
		||||
			var atlasFileByteBuffer = NativeMemory.Alloc((nuint) atlasFileStream.Length);
 | 
			
		||||
			var atlasFileByteSpan = new Span<byte>(atlasFileByteBuffer, (int) atlasFileStream.Length);
 | 
			
		||||
			atlasFileStream.ReadExactly(atlasFileByteSpan);
 | 
			
		||||
			atlasFileStream.Close();
 | 
			
		||||
 | 
			
		||||
			var handle = Wellspring.Wellspring_CreateFont(
 | 
			
		||||
				(IntPtr) fontFileByteBuffer,
 | 
			
		||||
				(uint) fontFileByteSpan.Length,
 | 
			
		||||
				(IntPtr) atlasFileByteBuffer,
 | 
			
		||||
				(uint) atlasFileByteSpan.Length,
 | 
			
		||||
				out float pixelsPerEm,
 | 
			
		||||
				out float distanceRange
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
			var texture = Texture.FromImageFile(graphicsDevice, commandBuffer, Path.ChangeExtension(fontPath, ".png"));
 | 
			
		||||
 | 
			
		||||
			NativeMemory.Free(fontFileByteBuffer);
 | 
			
		||||
			NativeMemory.Free(atlasFileByteBuffer);
 | 
			
		||||
 | 
			
		||||
			return new Font(graphicsDevice, handle, texture, pixelsPerEm, distanceRange);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private Font(GraphicsDevice device, IntPtr handle, Texture texture, float pixelsPerEm, float distanceRange) : base(device)
 | 
			
		||||
		{
 | 
			
		||||
			Handle = handle;
 | 
			
		||||
			Texture = texture;
 | 
			
		||||
			PixelsPerEm = pixelsPerEm;
 | 
			
		||||
			DistanceRange = distanceRange;
 | 
			
		||||
 | 
			
		||||
			StringBytesLength = 32;
 | 
			
		||||
			StringBytes = (byte*) NativeMemory.Alloc((nuint) StringBytesLength);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public unsafe bool TextBounds(
 | 
			
		||||
			string text,
 | 
			
		||||
			int pixelSize,
 | 
			
		||||
			HorizontalAlignment horizontalAlignment,
 | 
			
		||||
			VerticalAlignment verticalAlignment,
 | 
			
		||||
			out Wellspring.Rectangle rectangle
 | 
			
		||||
		) {
 | 
			
		||||
			var byteCount = System.Text.Encoding.UTF8.GetByteCount(text);
 | 
			
		||||
 | 
			
		||||
			if (StringBytesLength < byteCount)
 | 
			
		||||
			{
 | 
			
		||||
				Handle = Wellspring.Wellspring_CreateFont((IntPtr) pByte, (uint) bytes.Length);
 | 
			
		||||
				StringBytes = (byte*) NativeMemory.Realloc(StringBytes, (nuint) byteCount);
 | 
			
		||||
			}
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
		protected virtual void Dispose(bool disposing)
 | 
			
		||||
			fixed (char* chars = text)
 | 
			
		||||
			{
 | 
			
		||||
				System.Text.Encoding.UTF8.GetBytes(chars, text.Length, StringBytes, byteCount);
 | 
			
		||||
 | 
			
		||||
				var result = Wellspring.Wellspring_TextBounds(
 | 
			
		||||
					Handle,
 | 
			
		||||
					pixelSize,
 | 
			
		||||
					(Wellspring.HorizontalAlignment) horizontalAlignment,
 | 
			
		||||
					(Wellspring.VerticalAlignment) verticalAlignment,
 | 
			
		||||
					(IntPtr) StringBytes,
 | 
			
		||||
					(uint) byteCount,
 | 
			
		||||
					out rectangle
 | 
			
		||||
				);
 | 
			
		||||
 | 
			
		||||
				if (result == 0)
 | 
			
		||||
				{
 | 
			
		||||
					Logger.LogWarn("Could not decode string: " + text);
 | 
			
		||||
					return false;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return true;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		protected override void Dispose(bool disposing)
 | 
			
		||||
		{
 | 
			
		||||
			if (!IsDisposed)
 | 
			
		||||
			{
 | 
			
		||||
				if (disposing)
 | 
			
		||||
				{
 | 
			
		||||
					Texture.Dispose();
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				Wellspring.Wellspring_DestroyFont(Handle);
 | 
			
		||||
				IsDisposed = true;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		~Font()
 | 
			
		||||
		{
 | 
			
		||||
		    // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
 | 
			
		||||
		    Dispose(disposing: false);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public void Dispose()
 | 
			
		||||
		{
 | 
			
		||||
			// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
 | 
			
		||||
			Dispose(disposing: true);
 | 
			
		||||
			GC.SuppressFinalize(this);
 | 
			
		||||
			base.Dispose(disposing);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,109 +0,0 @@
 | 
			
		|||
using System;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Runtime.InteropServices;
 | 
			
		||||
using WellspringCS;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Graphics.Font
 | 
			
		||||
{
 | 
			
		||||
	public class Packer : IDisposable
 | 
			
		||||
	{
 | 
			
		||||
		public IntPtr Handle { get; }
 | 
			
		||||
		public Texture Texture { get; }
 | 
			
		||||
 | 
			
		||||
		public Font Font { get; }
 | 
			
		||||
 | 
			
		||||
		private byte[] StringBytes;
 | 
			
		||||
 | 
			
		||||
		private bool IsDisposed;
 | 
			
		||||
 | 
			
		||||
		public unsafe Packer(GraphicsDevice graphicsDevice, Font font, float fontSize, uint textureWidth, uint textureHeight, uint padding = 1)
 | 
			
		||||
		{
 | 
			
		||||
			Font = font;
 | 
			
		||||
			Handle = Wellspring.Wellspring_CreatePacker(Font.Handle, fontSize, textureWidth, textureHeight, 0, padding);
 | 
			
		||||
			Texture = Texture.CreateTexture2D(graphicsDevice, textureWidth, textureHeight, TextureFormat.R8, TextureUsageFlags.Sampler);
 | 
			
		||||
			StringBytes = new byte[128];
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public unsafe bool PackFontRanges(params FontRange[] fontRanges)
 | 
			
		||||
		{
 | 
			
		||||
			fixed (FontRange *pFontRanges = &fontRanges[0])
 | 
			
		||||
			{
 | 
			
		||||
				var nativeSize = fontRanges.Length * Marshal.SizeOf<Wellspring.FontRange>();
 | 
			
		||||
				void* fontRangeMemory = NativeMemory.Alloc((nuint) fontRanges.Length, (nuint) Marshal.SizeOf<Wellspring.FontRange>());
 | 
			
		||||
				System.Buffer.MemoryCopy(pFontRanges, fontRangeMemory, nativeSize, nativeSize);
 | 
			
		||||
 | 
			
		||||
				var result = Wellspring.Wellspring_PackFontRanges(Handle, (IntPtr) fontRangeMemory, (uint) fontRanges.Length);
 | 
			
		||||
 | 
			
		||||
				NativeMemory.Free(fontRangeMemory);
 | 
			
		||||
 | 
			
		||||
				return result > 0;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public unsafe void SetTextureData(CommandBuffer commandBuffer)
 | 
			
		||||
		{
 | 
			
		||||
			var pixelDataPointer = Wellspring.Wellspring_GetPixelDataPointer(Handle);
 | 
			
		||||
			commandBuffer.SetTextureData(Texture, pixelDataPointer, Texture.Width * Texture.Height);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public unsafe void TextBounds(
 | 
			
		||||
			string text,
 | 
			
		||||
			float x,
 | 
			
		||||
			float y,
 | 
			
		||||
			HorizontalAlignment horizontalAlignment,
 | 
			
		||||
			VerticalAlignment verticalAlignment,
 | 
			
		||||
			out Wellspring.Rectangle rectangle
 | 
			
		||||
		) {
 | 
			
		||||
			var byteCount = System.Text.Encoding.UTF8.GetByteCount(text);
 | 
			
		||||
 | 
			
		||||
			if (StringBytes.Length < byteCount)
 | 
			
		||||
			{
 | 
			
		||||
				System.Array.Resize(ref StringBytes, byteCount);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			fixed (char* chars = text)
 | 
			
		||||
			fixed (byte* bytes = StringBytes)
 | 
			
		||||
			{
 | 
			
		||||
				System.Text.Encoding.UTF8.GetBytes(chars, text.Length, bytes, byteCount);
 | 
			
		||||
				Wellspring.Wellspring_TextBounds(
 | 
			
		||||
					Handle,
 | 
			
		||||
					x,
 | 
			
		||||
					y,
 | 
			
		||||
					(Wellspring.HorizontalAlignment) horizontalAlignment,
 | 
			
		||||
					(Wellspring.VerticalAlignment) verticalAlignment,
 | 
			
		||||
					(IntPtr) bytes,
 | 
			
		||||
					(uint) byteCount,
 | 
			
		||||
					out rectangle
 | 
			
		||||
				);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		protected virtual void Dispose(bool disposing)
 | 
			
		||||
		{
 | 
			
		||||
			if (!IsDisposed)
 | 
			
		||||
			{
 | 
			
		||||
				if (disposing)
 | 
			
		||||
				{
 | 
			
		||||
					Texture.Dispose();
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				Wellspring.Wellspring_DestroyPacker(Handle);
 | 
			
		||||
 | 
			
		||||
				IsDisposed = true;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		~Packer()
 | 
			
		||||
		{
 | 
			
		||||
		    // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
 | 
			
		||||
		    Dispose(disposing: false);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public void Dispose()
 | 
			
		||||
		{
 | 
			
		||||
			// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
 | 
			
		||||
			Dispose(disposing: true);
 | 
			
		||||
			GC.SuppressFinalize(this);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -4,19 +4,17 @@ using MoonWorks.Math.Float;
 | 
			
		|||
namespace MoonWorks.Graphics.Font
 | 
			
		||||
{
 | 
			
		||||
	[StructLayout(LayoutKind.Sequential)]
 | 
			
		||||
	public struct FontRange
 | 
			
		||||
	{
 | 
			
		||||
		public uint FirstCodepoint;
 | 
			
		||||
		public uint NumChars;
 | 
			
		||||
		public byte OversampleH;
 | 
			
		||||
		public byte OversampleV;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	[StructLayout(LayoutKind.Sequential)]
 | 
			
		||||
	public struct Vertex
 | 
			
		||||
	public struct Vertex : IVertexType
 | 
			
		||||
	{
 | 
			
		||||
		public Vector3 Position;
 | 
			
		||||
		public Vector2 TexCoord;
 | 
			
		||||
		public Color Color;
 | 
			
		||||
 | 
			
		||||
		public static VertexElementFormat[] Formats { get; } = new VertexElementFormat[]
 | 
			
		||||
		{
 | 
			
		||||
			VertexElementFormat.Vector3,
 | 
			
		||||
			VertexElementFormat.Vector2,
 | 
			
		||||
			VertexElementFormat.Color
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,75 +1,87 @@
 | 
			
		|||
using System;
 | 
			
		||||
using System.Runtime.InteropServices;
 | 
			
		||||
using WellspringCS;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Graphics.Font
 | 
			
		||||
{
 | 
			
		||||
	public class TextBatch
 | 
			
		||||
	public unsafe class TextBatch : GraphicsResource
 | 
			
		||||
	{
 | 
			
		||||
		public const int INITIAL_CHAR_COUNT = 64;
 | 
			
		||||
		public const int INITIAL_VERTEX_COUNT = INITIAL_CHAR_COUNT * 4;
 | 
			
		||||
		public const int INITIAL_INDEX_COUNT = INITIAL_CHAR_COUNT * 6;
 | 
			
		||||
 | 
			
		||||
		private GraphicsDevice GraphicsDevice { get; }
 | 
			
		||||
		public IntPtr Handle { get; }
 | 
			
		||||
 | 
			
		||||
		public Buffer VertexBuffer { get; protected set; } = null;
 | 
			
		||||
		public Buffer IndexBuffer { get; protected set; } = null;
 | 
			
		||||
		public Texture Texture { get; protected set; }
 | 
			
		||||
		public uint PrimitiveCount { get; protected set; }
 | 
			
		||||
 | 
			
		||||
		private byte[] StringBytes;
 | 
			
		||||
		public Font CurrentFont { get; private set; }
 | 
			
		||||
 | 
			
		||||
		public TextBatch(GraphicsDevice graphicsDevice)
 | 
			
		||||
		private byte* StringBytes;
 | 
			
		||||
		private int StringBytesLength;
 | 
			
		||||
 | 
			
		||||
		public TextBatch(GraphicsDevice device) : base(device)
 | 
			
		||||
		{
 | 
			
		||||
			GraphicsDevice = graphicsDevice;
 | 
			
		||||
			GraphicsDevice = device;
 | 
			
		||||
			Handle = Wellspring.Wellspring_CreateTextBatch();
 | 
			
		||||
			StringBytes = new byte[128];
 | 
			
		||||
 | 
			
		||||
			StringBytesLength = 128;
 | 
			
		||||
			StringBytes = (byte*) NativeMemory.Alloc((nuint) StringBytesLength);
 | 
			
		||||
 | 
			
		||||
			VertexBuffer = Buffer.Create<Vertex>(GraphicsDevice, BufferUsageFlags.Vertex, INITIAL_VERTEX_COUNT);
 | 
			
		||||
			IndexBuffer = Buffer.Create<uint>(GraphicsDevice, BufferUsageFlags.Index, INITIAL_INDEX_COUNT);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public void Start(Packer packer)
 | 
			
		||||
		// Call this to initialize or reset the batch.
 | 
			
		||||
		public void Start(Font font)
 | 
			
		||||
		{
 | 
			
		||||
			Wellspring.Wellspring_StartTextBatch(Handle, packer.Handle);
 | 
			
		||||
			Texture = packer.Texture;
 | 
			
		||||
			Wellspring.Wellspring_StartTextBatch(Handle, font.Handle);
 | 
			
		||||
			CurrentFont = font;
 | 
			
		||||
			PrimitiveCount = 0;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public unsafe void Draw(
 | 
			
		||||
		// Add text with size and color to the batch
 | 
			
		||||
		public unsafe bool Add(
 | 
			
		||||
			string text,
 | 
			
		||||
			float x,
 | 
			
		||||
			float y,
 | 
			
		||||
			float depth,
 | 
			
		||||
			int pixelSize,
 | 
			
		||||
			Color color,
 | 
			
		||||
			HorizontalAlignment horizontalAlignment = HorizontalAlignment.Left,
 | 
			
		||||
			VerticalAlignment verticalAlignment = VerticalAlignment.Baseline
 | 
			
		||||
		) {
 | 
			
		||||
			var byteCount = System.Text.Encoding.UTF8.GetByteCount(text);
 | 
			
		||||
 | 
			
		||||
			if (StringBytes.Length < byteCount)
 | 
			
		||||
			if (StringBytesLength < byteCount)
 | 
			
		||||
			{
 | 
			
		||||
				System.Array.Resize(ref StringBytes, byteCount);
 | 
			
		||||
				StringBytes = (byte*) NativeMemory.Realloc(StringBytes, (nuint) byteCount);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			fixed (char* chars = text)
 | 
			
		||||
			fixed (byte* bytes = StringBytes)
 | 
			
		||||
			{
 | 
			
		||||
				System.Text.Encoding.UTF8.GetBytes(chars, text.Length, bytes, byteCount);
 | 
			
		||||
				System.Text.Encoding.UTF8.GetBytes(chars, text.Length, StringBytes, byteCount);
 | 
			
		||||
 | 
			
		||||
				var result = Wellspring.Wellspring_Draw(
 | 
			
		||||
				var result = Wellspring.Wellspring_AddToTextBatch(
 | 
			
		||||
					Handle,
 | 
			
		||||
					x,
 | 
			
		||||
					y,
 | 
			
		||||
					depth,
 | 
			
		||||
					pixelSize,
 | 
			
		||||
					new Wellspring.Color { R = color.R, G = color.G, B = color.B, A = color.A },
 | 
			
		||||
					(Wellspring.HorizontalAlignment) horizontalAlignment,
 | 
			
		||||
					(Wellspring.VerticalAlignment) verticalAlignment,
 | 
			
		||||
					(IntPtr) bytes,
 | 
			
		||||
					(IntPtr) StringBytes,
 | 
			
		||||
					(uint) byteCount
 | 
			
		||||
				);
 | 
			
		||||
 | 
			
		||||
				if (result == 0)
 | 
			
		||||
				{
 | 
			
		||||
					throw new System.ArgumentException("Could not decode string!");
 | 
			
		||||
					Logger.LogWarn("Could not decode string: " + text);
 | 
			
		||||
					return false;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return true;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Call this after you have made all the Draw calls you want.
 | 
			
		||||
		// Call this after you have made all the Add calls you want, but before beginning a render pass.
 | 
			
		||||
		public unsafe void UploadBufferData(CommandBuffer commandBuffer)
 | 
			
		||||
		{
 | 
			
		||||
			Wellspring.Wellspring_GetBufferData(
 | 
			
		||||
| 
						 | 
				
			
			@ -81,24 +93,16 @@ namespace MoonWorks.Graphics.Font
 | 
			
		|||
				out uint indexDataLengthInBytes
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
			if (VertexBuffer == null)
 | 
			
		||||
			{
 | 
			
		||||
				VertexBuffer = new Buffer(GraphicsDevice, BufferUsageFlags.Vertex, vertexDataLengthInBytes);
 | 
			
		||||
			}
 | 
			
		||||
			else if (VertexBuffer.Size < vertexDataLengthInBytes)
 | 
			
		||||
			if (VertexBuffer.Size < vertexDataLengthInBytes)
 | 
			
		||||
			{
 | 
			
		||||
				VertexBuffer.Dispose();
 | 
			
		||||
				VertexBuffer = new Buffer(GraphicsDevice, BufferUsageFlags.Vertex, vertexDataLengthInBytes);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (IndexBuffer == null)
 | 
			
		||||
			{
 | 
			
		||||
				IndexBuffer = new Buffer(GraphicsDevice, BufferUsageFlags.Index, indexDataLengthInBytes);
 | 
			
		||||
			}
 | 
			
		||||
			else if (IndexBuffer.Size < indexDataLengthInBytes)
 | 
			
		||||
			if (IndexBuffer.Size < indexDataLengthInBytes)
 | 
			
		||||
			{
 | 
			
		||||
				IndexBuffer.Dispose();
 | 
			
		||||
				IndexBuffer = new Buffer(GraphicsDevice, BufferUsageFlags.Index, indexDataLengthInBytes);
 | 
			
		||||
				IndexBuffer = new Buffer(GraphicsDevice, BufferUsageFlags.Index, vertexDataLengthInBytes);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (vertexDataLengthInBytes > 0 && indexDataLengthInBytes > 0)
 | 
			
		||||
| 
						 | 
				
			
			@ -107,7 +111,41 @@ namespace MoonWorks.Graphics.Font
 | 
			
		|||
				commandBuffer.SetBufferData(IndexBuffer, indexDataPointer, 0, indexDataLengthInBytes);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			PrimitiveCount = vertexCount / 2; // FIXME: is this jank?
 | 
			
		||||
			PrimitiveCount = vertexCount / 2;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Call this AFTER binding your text pipeline!
 | 
			
		||||
		public void Render(CommandBuffer commandBuffer, Math.Float.Matrix4x4 transformMatrix)
 | 
			
		||||
		{
 | 
			
		||||
			commandBuffer.BindFragmentSamplers(new TextureSamplerBinding(
 | 
			
		||||
				CurrentFont.Texture,
 | 
			
		||||
				GraphicsDevice.LinearSampler
 | 
			
		||||
			));
 | 
			
		||||
			commandBuffer.BindVertexBuffers(VertexBuffer);
 | 
			
		||||
			commandBuffer.BindIndexBuffer(IndexBuffer, IndexElementSize.ThirtyTwo);
 | 
			
		||||
			commandBuffer.DrawIndexedPrimitives(
 | 
			
		||||
				0,
 | 
			
		||||
				0,
 | 
			
		||||
				PrimitiveCount,
 | 
			
		||||
				commandBuffer.PushVertexShaderUniforms(transformMatrix),
 | 
			
		||||
				commandBuffer.PushFragmentShaderUniforms(CurrentFont.DistanceRange)
 | 
			
		||||
			);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		protected override void Dispose(bool disposing)
 | 
			
		||||
		{
 | 
			
		||||
			if (!IsDisposed)
 | 
			
		||||
			{
 | 
			
		||||
				if (disposing)
 | 
			
		||||
				{
 | 
			
		||||
					VertexBuffer.Dispose();
 | 
			
		||||
					IndexBuffer.Dispose();
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				NativeMemory.Free(StringBytes);
 | 
			
		||||
				Wellspring.Wellspring_DestroyTextBatch(Handle);
 | 
			
		||||
			}
 | 
			
		||||
			base.Dispose(disposing);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,10 +1,16 @@
 | 
			
		|||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Runtime.InteropServices;
 | 
			
		||||
using MoonWorks.Video;
 | 
			
		||||
using RefreshCS;
 | 
			
		||||
using WellspringCS;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Graphics
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// GraphicsDevice manages all graphics-related concerns.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public class GraphicsDevice : IDisposable
 | 
			
		||||
	{
 | 
			
		||||
		public IntPtr Handle { get; }
 | 
			
		||||
| 
						 | 
				
			
			@ -16,11 +22,22 @@ namespace MoonWorks.Graphics
 | 
			
		|||
		// Built-in video pipeline
 | 
			
		||||
		internal GraphicsPipeline VideoPipeline { get; }
 | 
			
		||||
 | 
			
		||||
		// Built-in text shader info
 | 
			
		||||
		public GraphicsShaderInfo TextVertexShaderInfo { get; }
 | 
			
		||||
		public GraphicsShaderInfo TextFragmentShaderInfo { get; }
 | 
			
		||||
		public VertexInputState TextVertexInputState { get; }
 | 
			
		||||
 | 
			
		||||
		// Built-in samplers
 | 
			
		||||
		public Sampler PointSampler { get; }
 | 
			
		||||
		public Sampler LinearSampler { get; }
 | 
			
		||||
 | 
			
		||||
		public bool IsDisposed { get; private set; }
 | 
			
		||||
 | 
			
		||||
		private readonly List<WeakReference<GraphicsResource>> resources = new List<WeakReference<GraphicsResource>>();
 | 
			
		||||
		private readonly HashSet<GCHandle> resources = new HashSet<GCHandle>();
 | 
			
		||||
		private FencePool FencePool;
 | 
			
		||||
		private CommandBufferPool CommandBufferPool;
 | 
			
		||||
 | 
			
		||||
		public GraphicsDevice(
 | 
			
		||||
		internal GraphicsDevice(
 | 
			
		||||
			Backend preferredBackend,
 | 
			
		||||
			bool debugMode
 | 
			
		||||
		) {
 | 
			
		||||
| 
						 | 
				
			
			@ -35,47 +52,109 @@ namespace MoonWorks.Graphics
 | 
			
		|||
				Conversions.BoolToByte(debugMode)
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
			// Check for optional video shaders
 | 
			
		||||
			string basePath = SDL2.SDL.SDL_GetBasePath();
 | 
			
		||||
			string videoVertPath = Path.Combine(basePath, "video_fullscreen.refresh");
 | 
			
		||||
			string videoFragPath = Path.Combine(basePath, "video_yuv2rgba.refresh");
 | 
			
		||||
			// TODO: check for CreateDevice fail
 | 
			
		||||
 | 
			
		||||
			// Check for replacement stock shaders
 | 
			
		||||
			string basePath = System.AppContext.BaseDirectory;
 | 
			
		||||
 | 
			
		||||
			string videoVertPath = Path.Combine(basePath, "video_fullscreen.vert.refresh");
 | 
			
		||||
			string videoFragPath = Path.Combine(basePath, "video_yuv2rgba.frag.refresh");
 | 
			
		||||
 | 
			
		||||
			string textVertPath = Path.Combine(basePath, "text_transform.vert.refresh");
 | 
			
		||||
			string textFragPath = Path.Combine(basePath, "text_msdf.frag.refresh");
 | 
			
		||||
 | 
			
		||||
			ShaderModule videoVertShader;
 | 
			
		||||
			ShaderModule videoFragShader;
 | 
			
		||||
 | 
			
		||||
			ShaderModule textVertShader;
 | 
			
		||||
			ShaderModule textFragShader;
 | 
			
		||||
 | 
			
		||||
			if (File.Exists(videoVertPath) && File.Exists(videoFragPath))
 | 
			
		||||
			{
 | 
			
		||||
				ShaderModule videoVertShader = new ShaderModule(this, videoVertPath);
 | 
			
		||||
				ShaderModule videoFragShader = new ShaderModule(this, videoFragPath);
 | 
			
		||||
 | 
			
		||||
				VideoPipeline = new GraphicsPipeline(
 | 
			
		||||
					this,
 | 
			
		||||
					new GraphicsPipelineCreateInfo
 | 
			
		||||
					{
 | 
			
		||||
						AttachmentInfo = new GraphicsPipelineAttachmentInfo(
 | 
			
		||||
							new ColorAttachmentDescription(
 | 
			
		||||
								TextureFormat.R8G8B8A8,
 | 
			
		||||
								ColorAttachmentBlendState.None
 | 
			
		||||
							)
 | 
			
		||||
						),
 | 
			
		||||
						DepthStencilState = DepthStencilState.Disable,
 | 
			
		||||
						VertexShaderInfo = GraphicsShaderInfo.Create(
 | 
			
		||||
							videoVertShader,
 | 
			
		||||
							"main",
 | 
			
		||||
							0
 | 
			
		||||
						),
 | 
			
		||||
						FragmentShaderInfo = GraphicsShaderInfo.Create(
 | 
			
		||||
							videoFragShader,
 | 
			
		||||
							"main",
 | 
			
		||||
							3
 | 
			
		||||
						),
 | 
			
		||||
						VertexInputState = VertexInputState.Empty,
 | 
			
		||||
						RasterizerState = RasterizerState.CCW_CullNone,
 | 
			
		||||
						PrimitiveType = PrimitiveType.TriangleList,
 | 
			
		||||
						MultisampleState = MultisampleState.None
 | 
			
		||||
					}
 | 
			
		||||
				);
 | 
			
		||||
				videoVertShader = new ShaderModule(this, videoVertPath);
 | 
			
		||||
				videoFragShader = new ShaderModule(this, videoFragPath);
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
				// use defaults
 | 
			
		||||
				var assembly = typeof(GraphicsDevice).Assembly;
 | 
			
		||||
 | 
			
		||||
				using var vertStream = assembly.GetManifestResourceStream("MoonWorks.Graphics.StockShaders.VideoFullscreen.vert.refresh");
 | 
			
		||||
				using var fragStream = assembly.GetManifestResourceStream("MoonWorks.Graphics.StockShaders.VideoYUV2RGBA.frag.refresh");
 | 
			
		||||
 | 
			
		||||
				videoVertShader = new ShaderModule(this, vertStream);
 | 
			
		||||
				videoFragShader = new ShaderModule(this, fragStream);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (File.Exists(textVertPath) && File.Exists(textFragPath))
 | 
			
		||||
			{
 | 
			
		||||
				textVertShader = new ShaderModule(this, textVertPath);
 | 
			
		||||
				textFragShader = new ShaderModule(this, textFragPath);
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
				// use defaults
 | 
			
		||||
				var assembly = typeof(GraphicsDevice).Assembly;
 | 
			
		||||
 | 
			
		||||
				using var vertStream = assembly.GetManifestResourceStream("MoonWorks.Graphics.StockShaders.TextTransform.vert.refresh");
 | 
			
		||||
				using var fragStream = assembly.GetManifestResourceStream("MoonWorks.Graphics.StockShaders.TextMSDF.frag.refresh");
 | 
			
		||||
 | 
			
		||||
				textVertShader = new ShaderModule(this, vertStream);
 | 
			
		||||
				textFragShader = new ShaderModule(this, fragStream);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			VideoPipeline = new GraphicsPipeline(
 | 
			
		||||
				this,
 | 
			
		||||
				new GraphicsPipelineCreateInfo
 | 
			
		||||
				{
 | 
			
		||||
					AttachmentInfo = new GraphicsPipelineAttachmentInfo(
 | 
			
		||||
						new ColorAttachmentDescription(
 | 
			
		||||
							TextureFormat.R8G8B8A8,
 | 
			
		||||
							ColorAttachmentBlendState.None
 | 
			
		||||
						)
 | 
			
		||||
					),
 | 
			
		||||
					DepthStencilState = DepthStencilState.Disable,
 | 
			
		||||
					VertexShaderInfo = GraphicsShaderInfo.Create(
 | 
			
		||||
						videoVertShader,
 | 
			
		||||
						"main",
 | 
			
		||||
						0
 | 
			
		||||
					),
 | 
			
		||||
					FragmentShaderInfo = GraphicsShaderInfo.Create(
 | 
			
		||||
						videoFragShader,
 | 
			
		||||
						"main",
 | 
			
		||||
						3
 | 
			
		||||
					),
 | 
			
		||||
					VertexInputState = VertexInputState.Empty,
 | 
			
		||||
					RasterizerState = RasterizerState.CCW_CullNone,
 | 
			
		||||
					PrimitiveType = PrimitiveType.TriangleList,
 | 
			
		||||
					MultisampleState = MultisampleState.None
 | 
			
		||||
				}
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
			TextVertexShaderInfo = GraphicsShaderInfo.Create<Math.Float.Matrix4x4>(textVertShader, "main", 0);
 | 
			
		||||
			TextFragmentShaderInfo = GraphicsShaderInfo.Create<float>(textFragShader, "main", 1);
 | 
			
		||||
			TextVertexInputState = VertexInputState.CreateSingleBinding<Font.Vertex>();
 | 
			
		||||
 | 
			
		||||
			PointSampler = new Sampler(this, SamplerCreateInfo.PointClamp);
 | 
			
		||||
			LinearSampler = new Sampler(this, SamplerCreateInfo.LinearClamp);
 | 
			
		||||
 | 
			
		||||
			FencePool = new FencePool(this);
 | 
			
		||||
			CommandBufferPool = new CommandBufferPool(this);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Prepares a window so that frames can be presented to it.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="presentMode">The desired presentation mode for the window. Roughly equivalent to V-Sync.</param>
 | 
			
		||||
		/// <returns>True if successfully claimed.</returns>
 | 
			
		||||
		public bool ClaimWindow(Window window, PresentMode presentMode)
 | 
			
		||||
		{
 | 
			
		||||
			if (window.Claimed)
 | 
			
		||||
			{
 | 
			
		||||
				Logger.LogError("Window already claimed!");
 | 
			
		||||
				return false;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			var success = Conversions.ByteToBool(
 | 
			
		||||
				Refresh.Refresh_ClaimWindow(
 | 
			
		||||
					Handle,
 | 
			
		||||
| 
						 | 
				
			
			@ -90,24 +169,46 @@ namespace MoonWorks.Graphics
 | 
			
		|||
				window.SwapchainFormat = GetSwapchainFormat(window);
 | 
			
		||||
				if (window.SwapchainTexture == null)
 | 
			
		||||
				{
 | 
			
		||||
					window.SwapchainTexture = new Texture(this, IntPtr.Zero, window.SwapchainFormat, 0, 0);
 | 
			
		||||
					window.SwapchainTexture = new Texture(this, window.SwapchainFormat);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return success;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Unclaims a window, making it unavailable for presenting and freeing associated resources.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public void UnclaimWindow(Window window)
 | 
			
		||||
		{
 | 
			
		||||
			Refresh.Refresh_UnclaimWindow(
 | 
			
		||||
				Handle,
 | 
			
		||||
				window.Handle
 | 
			
		||||
			);
 | 
			
		||||
			window.Claimed = false;
 | 
			
		||||
			if (window.Claimed)
 | 
			
		||||
			{
 | 
			
		||||
				Refresh.Refresh_UnclaimWindow(
 | 
			
		||||
					Handle,
 | 
			
		||||
					window.Handle
 | 
			
		||||
				);
 | 
			
		||||
				window.Claimed = false;
 | 
			
		||||
 | 
			
		||||
				// The swapchain texture doesn't actually have a permanent texture reference, so we zero the handle before disposing.
 | 
			
		||||
				window.SwapchainTexture.Handle = IntPtr.Zero;
 | 
			
		||||
				window.SwapchainTexture.Dispose();
 | 
			
		||||
				window.SwapchainTexture = null;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Changes the present mode of a claimed window. Does nothing if the window is not claimed.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="window"></param>
 | 
			
		||||
		/// <param name="presentMode"></param>
 | 
			
		||||
		public void SetPresentMode(Window window, PresentMode presentMode)
 | 
			
		||||
		{
 | 
			
		||||
			if (!window.Claimed)
 | 
			
		||||
			{
 | 
			
		||||
				Logger.LogError("Cannot set present mode on unclaimed window!");
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			Refresh.Refresh_SetSwapchainPresentMode(
 | 
			
		||||
				Handle,
 | 
			
		||||
				window.Handle,
 | 
			
		||||
| 
						 | 
				
			
			@ -115,105 +216,208 @@ namespace MoonWorks.Graphics
 | 
			
		|||
			);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Acquires a command buffer.
 | 
			
		||||
		/// This is the start of your rendering process.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <returns></returns>
 | 
			
		||||
		public CommandBuffer AcquireCommandBuffer()
 | 
			
		||||
		{
 | 
			
		||||
			return new CommandBuffer(this, Refresh.Refresh_AcquireCommandBuffer(Handle));
 | 
			
		||||
			var commandBuffer = CommandBufferPool.Obtain();
 | 
			
		||||
			commandBuffer.SetHandle(Refresh.Refresh_AcquireCommandBuffer(Handle));
 | 
			
		||||
#if DEBUG
 | 
			
		||||
			commandBuffer.ResetStateTracking();
 | 
			
		||||
#endif
 | 
			
		||||
			return commandBuffer;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public unsafe void Submit(CommandBuffer commandBuffer)
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Submits a command buffer to the GPU for processing.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public void Submit(CommandBuffer commandBuffer)
 | 
			
		||||
		{
 | 
			
		||||
			var commandBufferPtrs = stackalloc IntPtr[1];
 | 
			
		||||
 | 
			
		||||
			commandBufferPtrs[0] = commandBuffer.Handle;
 | 
			
		||||
 | 
			
		||||
			Refresh.Refresh_Submit(
 | 
			
		||||
				Handle,
 | 
			
		||||
				1,
 | 
			
		||||
				(IntPtr) commandBufferPtrs
 | 
			
		||||
			);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public unsafe void Submit(
 | 
			
		||||
			CommandBuffer commandBufferOne,
 | 
			
		||||
			CommandBuffer commandBufferTwo
 | 
			
		||||
		) {
 | 
			
		||||
			var commandBufferPtrs = stackalloc IntPtr[2];
 | 
			
		||||
 | 
			
		||||
			commandBufferPtrs[0] = commandBufferOne.Handle;
 | 
			
		||||
			commandBufferPtrs[1] = commandBufferTwo.Handle;
 | 
			
		||||
 | 
			
		||||
			Refresh.Refresh_Submit(
 | 
			
		||||
				Handle,
 | 
			
		||||
				2,
 | 
			
		||||
				(IntPtr) commandBufferPtrs
 | 
			
		||||
			);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public unsafe void Submit(
 | 
			
		||||
			CommandBuffer commandBufferOne,
 | 
			
		||||
			CommandBuffer commandBufferTwo,
 | 
			
		||||
			CommandBuffer commandBufferThree
 | 
			
		||||
		) {
 | 
			
		||||
			var commandBufferPtrs = stackalloc IntPtr[3];
 | 
			
		||||
 | 
			
		||||
			commandBufferPtrs[0] = commandBufferOne.Handle;
 | 
			
		||||
			commandBufferPtrs[1] = commandBufferTwo.Handle;
 | 
			
		||||
			commandBufferPtrs[2] = commandBufferThree.Handle;
 | 
			
		||||
 | 
			
		||||
			Refresh.Refresh_Submit(
 | 
			
		||||
				Handle,
 | 
			
		||||
				3,
 | 
			
		||||
				(IntPtr) commandBufferPtrs
 | 
			
		||||
			);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public unsafe void Submit(
 | 
			
		||||
			CommandBuffer commandBufferOne,
 | 
			
		||||
			CommandBuffer commandBufferTwo,
 | 
			
		||||
			CommandBuffer commandBufferThree,
 | 
			
		||||
			CommandBuffer commandBufferFour
 | 
			
		||||
		) {
 | 
			
		||||
			var commandBufferPtrs = stackalloc IntPtr[4];
 | 
			
		||||
 | 
			
		||||
			commandBufferPtrs[0] = commandBufferOne.Handle;
 | 
			
		||||
			commandBufferPtrs[1] = commandBufferTwo.Handle;
 | 
			
		||||
			commandBufferPtrs[2] = commandBufferThree.Handle;
 | 
			
		||||
			commandBufferPtrs[3] = commandBufferFour.Handle;
 | 
			
		||||
 | 
			
		||||
			Refresh.Refresh_Submit(
 | 
			
		||||
				Handle,
 | 
			
		||||
				4,
 | 
			
		||||
				(IntPtr) commandBufferPtrs
 | 
			
		||||
			);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public unsafe void Submit(params CommandBuffer[] commandBuffers)
 | 
			
		||||
		{
 | 
			
		||||
			var commandBufferPtrs = stackalloc IntPtr[commandBuffers.Length];
 | 
			
		||||
 | 
			
		||||
			for (var i = 0; i < commandBuffers.Length; i += 1)
 | 
			
		||||
#if DEBUG
 | 
			
		||||
			if (commandBuffer.Submitted)
 | 
			
		||||
			{
 | 
			
		||||
				commandBufferPtrs[i] = commandBuffers[i].Handle;
 | 
			
		||||
				throw new System.InvalidOperationException("Command buffer already submitted!");
 | 
			
		||||
			}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
			Refresh.Refresh_Submit(
 | 
			
		||||
				Handle,
 | 
			
		||||
				(uint) commandBuffers.Length,
 | 
			
		||||
				(IntPtr) commandBufferPtrs
 | 
			
		||||
				commandBuffer.Handle
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
			CommandBufferPool.Return(commandBuffer);
 | 
			
		||||
 | 
			
		||||
#if DEBUG
 | 
			
		||||
			commandBuffer.Submitted = true;
 | 
			
		||||
#endif
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Submits a command buffer to the GPU for processing and acquires a fence associated with the submission.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <returns></returns>
 | 
			
		||||
		public Fence SubmitAndAcquireFence(CommandBuffer commandBuffer)
 | 
			
		||||
		{
 | 
			
		||||
			var fenceHandle = Refresh.Refresh_SubmitAndAcquireFence(
 | 
			
		||||
				Handle,
 | 
			
		||||
				commandBuffer.Handle
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
			var fence = FencePool.Obtain();
 | 
			
		||||
			fence.SetHandle(fenceHandle);
 | 
			
		||||
 | 
			
		||||
			return fence;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Wait for the graphics device to become idle.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public void Wait()
 | 
			
		||||
		{
 | 
			
		||||
			Refresh.Refresh_Wait(Handle);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Waits for the given fence to become signaled.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public unsafe void WaitForFences(Fence fence)
 | 
			
		||||
		{
 | 
			
		||||
			var handlePtr = stackalloc nint[1];
 | 
			
		||||
			handlePtr[0] = fence.Handle;
 | 
			
		||||
 | 
			
		||||
			Refresh.Refresh_WaitForFences(
 | 
			
		||||
				Handle,
 | 
			
		||||
				1,
 | 
			
		||||
				1,
 | 
			
		||||
				(nint) handlePtr
 | 
			
		||||
			);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Wait for one or more fences to become signaled.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="waitAll">If true, will wait for all given fences to be signaled.</param>
 | 
			
		||||
		public unsafe void WaitForFences(
 | 
			
		||||
			Fence fenceOne,
 | 
			
		||||
			Fence fenceTwo,
 | 
			
		||||
			bool waitAll
 | 
			
		||||
		) {
 | 
			
		||||
			var handlePtr = stackalloc nint[2];
 | 
			
		||||
			handlePtr[0] = fenceOne.Handle;
 | 
			
		||||
			handlePtr[1] = fenceTwo.Handle;
 | 
			
		||||
 | 
			
		||||
			Refresh.Refresh_WaitForFences(
 | 
			
		||||
				Handle,
 | 
			
		||||
				Conversions.BoolToByte(waitAll),
 | 
			
		||||
				2,
 | 
			
		||||
				(nint) handlePtr
 | 
			
		||||
			);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Wait for one or more fences to become signaled.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="waitAll">If true, will wait for all given fences to be signaled.</param>
 | 
			
		||||
		public unsafe void WaitForFences(
 | 
			
		||||
			Fence fenceOne,
 | 
			
		||||
			Fence fenceTwo,
 | 
			
		||||
			Fence fenceThree,
 | 
			
		||||
			bool waitAll
 | 
			
		||||
		) {
 | 
			
		||||
			var handlePtr = stackalloc nint[3];
 | 
			
		||||
			handlePtr[0] = fenceOne.Handle;
 | 
			
		||||
			handlePtr[1] = fenceTwo.Handle;
 | 
			
		||||
			handlePtr[2] = fenceThree.Handle;
 | 
			
		||||
 | 
			
		||||
			Refresh.Refresh_WaitForFences(
 | 
			
		||||
				Handle,
 | 
			
		||||
				Conversions.BoolToByte(waitAll),
 | 
			
		||||
				3,
 | 
			
		||||
				(nint) handlePtr
 | 
			
		||||
			);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Wait for one or more fences to become signaled.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="waitAll">If true, will wait for all given fences to be signaled.</param>
 | 
			
		||||
		public unsafe void WaitForFences(
 | 
			
		||||
			Fence fenceOne,
 | 
			
		||||
			Fence fenceTwo,
 | 
			
		||||
			Fence fenceThree,
 | 
			
		||||
			Fence fenceFour,
 | 
			
		||||
			bool waitAll
 | 
			
		||||
		) {
 | 
			
		||||
			var handlePtr = stackalloc nint[4];
 | 
			
		||||
			handlePtr[0] = fenceOne.Handle;
 | 
			
		||||
			handlePtr[1] = fenceTwo.Handle;
 | 
			
		||||
			handlePtr[2] = fenceThree.Handle;
 | 
			
		||||
			handlePtr[3] = fenceFour.Handle;
 | 
			
		||||
 | 
			
		||||
			Refresh.Refresh_WaitForFences(
 | 
			
		||||
				Handle,
 | 
			
		||||
				Conversions.BoolToByte(waitAll),
 | 
			
		||||
				4,
 | 
			
		||||
				(nint) handlePtr
 | 
			
		||||
			);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Wait for one or more fences to become signaled.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="waitAll">If true, will wait for all given fences to be signaled.</param>
 | 
			
		||||
		public unsafe void WaitForFences(Fence[] fences, bool waitAll)
 | 
			
		||||
		{
 | 
			
		||||
			var handlePtr = stackalloc nint[fences.Length];
 | 
			
		||||
 | 
			
		||||
			for (var i = 0; i < fences.Length; i += 1)
 | 
			
		||||
			{
 | 
			
		||||
				handlePtr[i] = fences[i].Handle;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			Refresh.Refresh_WaitForFences(
 | 
			
		||||
				Handle,
 | 
			
		||||
				Conversions.BoolToByte(waitAll),
 | 
			
		||||
				4,
 | 
			
		||||
				(nint) handlePtr
 | 
			
		||||
			);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Returns true if the fence is signaled, indicating that the associated command buffer has finished processing.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <exception cref="InvalidOperationException">Throws if the fence query indicates that the graphics device has been lost.</exception>
 | 
			
		||||
		public bool QueryFence(Fence fence)
 | 
			
		||||
		{
 | 
			
		||||
			var result = Refresh.Refresh_QueryFence(Handle, fence.Handle);
 | 
			
		||||
 | 
			
		||||
			if (result < 0)
 | 
			
		||||
			{
 | 
			
		||||
				throw new InvalidOperationException("The graphics device has been lost.");
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return result != 0;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Release reference to an acquired fence, enabling it to be reused.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public void ReleaseFence(Fence fence)
 | 
			
		||||
		{
 | 
			
		||||
			Refresh.Refresh_ReleaseFence(Handle, fence.Handle);
 | 
			
		||||
			fence.Handle = IntPtr.Zero;
 | 
			
		||||
			FencePool.Return(fence);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private TextureFormat GetSwapchainFormat(Window window)
 | 
			
		||||
		{
 | 
			
		||||
			return (TextureFormat) Refresh.Refresh_GetSwapchainFormat(Handle, window.Handle);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		internal void AddResourceReference(WeakReference<GraphicsResource> resourceReference)
 | 
			
		||||
		internal void AddResourceReference(GCHandle resourceReference)
 | 
			
		||||
		{
 | 
			
		||||
			lock (resources)
 | 
			
		||||
			{
 | 
			
		||||
| 
						 | 
				
			
			@ -221,7 +425,7 @@ namespace MoonWorks.Graphics
 | 
			
		|||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		internal void RemoveResourceReference(WeakReference<GraphicsResource> resourceReference)
 | 
			
		||||
		internal void RemoveResourceReference(GCHandle resourceReference)
 | 
			
		||||
		{
 | 
			
		||||
			lock (resources)
 | 
			
		||||
			{
 | 
			
		||||
| 
						 | 
				
			
			@ -237,20 +441,29 @@ namespace MoonWorks.Graphics
 | 
			
		|||
				{
 | 
			
		||||
					lock (resources)
 | 
			
		||||
					{
 | 
			
		||||
						for (var i = resources.Count - 1; i >= 0; i--)
 | 
			
		||||
						// Dispose video players first to avoid race condition on threaded decoding
 | 
			
		||||
						foreach (var resource in resources)
 | 
			
		||||
						{
 | 
			
		||||
							var resource = resources[i];
 | 
			
		||||
							if (resource.TryGetTarget(out var target))
 | 
			
		||||
							if (resource.Target is VideoPlayer player)
 | 
			
		||||
							{
 | 
			
		||||
								target.Dispose();
 | 
			
		||||
								player.Dispose();
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
 | 
			
		||||
						// Dispose everything else
 | 
			
		||||
						foreach (var resource in resources)
 | 
			
		||||
						{
 | 
			
		||||
							if (resource.Target is IDisposable disposable)
 | 
			
		||||
							{
 | 
			
		||||
								disposable.Dispose();
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
						resources.Clear();
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					Refresh.Refresh_DestroyDevice(Handle);
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				Refresh.Refresh_DestroyDevice(Handle);
 | 
			
		||||
 | 
			
		||||
				IsDisposed = true;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,37 +1,33 @@
 | 
			
		|||
using System;
 | 
			
		||||
using System.Runtime.InteropServices;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Graphics
 | 
			
		||||
{
 | 
			
		||||
	// TODO: give this a Name property for debugging use
 | 
			
		||||
	public abstract class GraphicsResource : IDisposable
 | 
			
		||||
	{
 | 
			
		||||
		public GraphicsDevice Device { get; }
 | 
			
		||||
		public IntPtr Handle { get; internal set; }
 | 
			
		||||
 | 
			
		||||
		private GCHandle SelfReference;
 | 
			
		||||
 | 
			
		||||
		public bool IsDisposed { get; private set; }
 | 
			
		||||
		protected abstract Action<IntPtr, IntPtr> QueueDestroyFunction { get; }
 | 
			
		||||
 | 
			
		||||
		private WeakReference<GraphicsResource> selfReference;
 | 
			
		||||
 | 
			
		||||
		public GraphicsResource(GraphicsDevice device, bool trackResource = true)
 | 
			
		||||
		protected GraphicsResource(GraphicsDevice device)
 | 
			
		||||
		{
 | 
			
		||||
			Device = device;
 | 
			
		||||
 | 
			
		||||
			if (trackResource)
 | 
			
		||||
			{
 | 
			
		||||
				selfReference = new WeakReference<GraphicsResource>(this);
 | 
			
		||||
				Device.AddResourceReference(selfReference);
 | 
			
		||||
			}
 | 
			
		||||
			SelfReference = GCHandle.Alloc(this, GCHandleType.Weak);
 | 
			
		||||
			Device.AddResourceReference(SelfReference);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		protected virtual void Dispose(bool disposing)
 | 
			
		||||
		{
 | 
			
		||||
			if (!IsDisposed)
 | 
			
		||||
			{
 | 
			
		||||
				if (selfReference != null)
 | 
			
		||||
				if (disposing)
 | 
			
		||||
				{
 | 
			
		||||
					QueueDestroyFunction(Device.Handle, Handle);
 | 
			
		||||
					Device.RemoveResourceReference(selfReference);
 | 
			
		||||
					selfReference = null;
 | 
			
		||||
					Device.RemoveResourceReference(SelfReference);
 | 
			
		||||
					SelfReference.Free();
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				IsDisposed = true;
 | 
			
		||||
| 
						 | 
				
			
			@ -40,8 +36,13 @@ namespace MoonWorks.Graphics
 | 
			
		|||
 | 
			
		||||
		~GraphicsResource()
 | 
			
		||||
		{
 | 
			
		||||
			// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
 | 
			
		||||
			Dispose(disposing: false);
 | 
			
		||||
			#if DEBUG
 | 
			
		||||
			// If you see this log message, you leaked a graphics resource without disposing it!
 | 
			
		||||
			// We'll try to clean it up for you but you really should fix this.
 | 
			
		||||
			Logger.LogWarn($"A resource of type {GetType().Name} was not Disposed.");
 | 
			
		||||
			#endif
 | 
			
		||||
 | 
			
		||||
			Dispose(false);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public void Dispose()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,13 @@
 | 
			
		|||
namespace MoonWorks.Graphics
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// Can be defined on your struct type to enable simplified vertex input state definition.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public interface IVertexType
 | 
			
		||||
	{
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// An ordered list of the types in your vertex struct.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		static abstract VertexElementFormat[] Formats { get; }
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,7 +20,7 @@ using MoonWorks.Math;
 | 
			
		|||
using MoonWorks.Math.Float;
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Graphics
 | 
			
		||||
namespace MoonWorks.Graphics.PackedVector
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// Packed vector type containing unsigned normalized values ranging from 0 to 1.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,7 +20,7 @@ using MoonWorks.Math;
 | 
			
		|||
using MoonWorks.Math.Float;
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Graphics
 | 
			
		||||
namespace MoonWorks.Graphics.PackedVector
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// Packed vector type containing unsigned normalized values ranging from 0 to 1.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,7 +20,7 @@ using MoonWorks.Math;
 | 
			
		|||
using MoonWorks.Math.Float;
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Graphics
 | 
			
		||||
namespace MoonWorks.Graphics.PackedVector
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// Packed vector type containing unsigned normalized values, ranging from 0 to 1, using
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,7 +20,7 @@ using MoonWorks.Math;
 | 
			
		|||
using MoonWorks.Math.Float;
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Graphics
 | 
			
		||||
namespace MoonWorks.Graphics.PackedVector
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// Packed vector type containing unsigned normalized values ranging from 0 to 1.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,7 +20,7 @@ using MoonWorks.Math;
 | 
			
		|||
using MoonWorks.Math.Float;
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Graphics
 | 
			
		||||
namespace MoonWorks.Graphics.PackedVector
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// Packed vector type containing four 8-bit unsigned integer values, ranging from 0 to 255.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,7 +19,7 @@ using System;
 | 
			
		|||
using MoonWorks.Math.Float;
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Graphics
 | 
			
		||||
namespace MoonWorks.Graphics.PackedVector
 | 
			
		||||
{
 | 
			
		||||
	public struct HalfSingle : IPackedVector<ushort>, IEquatable<HalfSingle>, IPackedVector
 | 
			
		||||
	{
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,7 +19,7 @@ using System;
 | 
			
		|||
using System.Runtime.InteropServices;
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Graphics
 | 
			
		||||
namespace MoonWorks.Graphics.PackedVector
 | 
			
		||||
{
 | 
			
		||||
	internal static class HalfTypeHelper
 | 
			
		||||
	{
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,7 +19,7 @@ using System;
 | 
			
		|||
using MoonWorks.Math.Float;
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Graphics
 | 
			
		||||
namespace MoonWorks.Graphics.PackedVector
 | 
			
		||||
{
 | 
			
		||||
	public struct HalfVector2 : IPackedVector<uint>, IPackedVector, IEquatable<HalfVector2>
 | 
			
		||||
	{
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,7 +19,7 @@ using System;
 | 
			
		|||
using MoonWorks.Math.Float;
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Graphics
 | 
			
		||||
namespace MoonWorks.Graphics.PackedVector
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// Packed vector type containing four 16-bit floating-point values.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,7 +16,7 @@
 | 
			
		|||
 | 
			
		||||
using MoonWorks.Math.Float;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Graphics
 | 
			
		||||
namespace MoonWorks.Graphics.PackedVector
 | 
			
		||||
{
 | 
			
		||||
	// http://msdn.microsoft.com/en-us/library/microsoft.xna.framework.graphics.packedvector.ipackedvector.aspx
 | 
			
		||||
	public interface IPackedVector
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,7 +20,7 @@ using MoonWorks.Math;
 | 
			
		|||
using MoonWorks.Math.Float;
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Graphics
 | 
			
		||||
namespace MoonWorks.Graphics.PackedVector
 | 
			
		||||
{
 | 
			
		||||
	public struct NormalizedByte2 : IPackedVector<ushort>, IEquatable<NormalizedByte2>
 | 
			
		||||
	{
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,7 +20,7 @@ using MoonWorks.Math;
 | 
			
		|||
using MoonWorks.Math.Float;
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Graphics
 | 
			
		||||
namespace MoonWorks.Graphics.PackedVector
 | 
			
		||||
{
 | 
			
		||||
	public struct NormalizedByte4 : IPackedVector<uint>, IEquatable<NormalizedByte4>
 | 
			
		||||
	{
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,7 +20,7 @@ using MoonWorks.Math;
 | 
			
		|||
using MoonWorks.Math.Float;
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Graphics
 | 
			
		||||
namespace MoonWorks.Graphics.PackedVector
 | 
			
		||||
{
 | 
			
		||||
	public struct NormalizedShort2 : IPackedVector<uint>, IEquatable<NormalizedShort2>
 | 
			
		||||
	{
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,7 +20,7 @@ using MoonWorks.Math;
 | 
			
		|||
using MoonWorks.Math.Float;
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Graphics
 | 
			
		||||
namespace MoonWorks.Graphics.PackedVector
 | 
			
		||||
{
 | 
			
		||||
	public struct NormalizedShort4 : IPackedVector<ulong>, IEquatable<NormalizedShort4>
 | 
			
		||||
	{
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,7 +20,7 @@ using MoonWorks.Math;
 | 
			
		|||
using MoonWorks.Math.Float;
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Graphics
 | 
			
		||||
namespace MoonWorks.Graphics.PackedVector
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// Packed vector type containing unsigned normalized values ranging from 0 to 1.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,7 +20,7 @@ using MoonWorks.Math;
 | 
			
		|||
using MoonWorks.Math.Float;
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Graphics
 | 
			
		||||
namespace MoonWorks.Graphics.PackedVector
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// Packed vector type containing unsigned normalized values ranging from 0 to 1.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,7 +20,7 @@ using MoonWorks.Math;
 | 
			
		|||
using MoonWorks.Math.Float;
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Graphics
 | 
			
		||||
namespace MoonWorks.Graphics.PackedVector
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// Packed vector type containing unsigned normalized values ranging from 0 to 1.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,7 +20,7 @@ using MoonWorks.Math;
 | 
			
		|||
using MoonWorks.Math.Float;
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Graphics
 | 
			
		||||
namespace MoonWorks.Graphics.PackedVector
 | 
			
		||||
{
 | 
			
		||||
	public struct Short2 : IPackedVector<uint>, IEquatable<Short2>
 | 
			
		||||
	{
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,7 +20,7 @@ using MoonWorks.Math;
 | 
			
		|||
using MoonWorks.Math.Float;
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Graphics
 | 
			
		||||
namespace MoonWorks.Graphics.PackedVector
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// Packed vector type containing four 16-bit signed integer values.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,11 +2,31 @@
 | 
			
		|||
 | 
			
		||||
namespace MoonWorks
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// Presentation mode for a window.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public enum PresentMode
 | 
			
		||||
	{
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Does not wait for v-blank to update the window. Can cause visible tearing.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		Immediate,
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Waits for v-blank and uses a queue to hold present requests.
 | 
			
		||||
		/// Allows for low latency while preventing tearing.
 | 
			
		||||
		/// May not be supported on non-Vulkan non-Linux systems or older hardware.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		Mailbox,
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Waits for v-blank and adds present requests to a queue.
 | 
			
		||||
		/// Will probably cause latency.
 | 
			
		||||
		/// Required to be supported by all compliant hardware.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		FIFO,
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Usually waits for v-blank, but if v-blank has passed since last update will update immediately.
 | 
			
		||||
		/// May cause visible tearing.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		FIFORelaxed
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,31 @@
 | 
			
		|||
using System;
 | 
			
		||||
using System.Runtime.InteropServices;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Graphics;
 | 
			
		||||
 | 
			
		||||
public abstract class RefreshResource : GraphicsResource
 | 
			
		||||
{
 | 
			
		||||
	public IntPtr Handle { get => handle; internal set => handle = value; }
 | 
			
		||||
	private IntPtr handle;
 | 
			
		||||
 | 
			
		||||
	protected abstract Action<IntPtr, IntPtr> QueueDestroyFunction { get; }
 | 
			
		||||
 | 
			
		||||
	protected RefreshResource(GraphicsDevice device) : base(device)
 | 
			
		||||
	{
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	protected override void Dispose(bool disposing)
 | 
			
		||||
	{
 | 
			
		||||
		if (!IsDisposed)
 | 
			
		||||
		{
 | 
			
		||||
			// Atomically call destroy function in case this is called from the finalizer thread
 | 
			
		||||
			var toDispose = Interlocked.Exchange(ref handle, IntPtr.Zero);
 | 
			
		||||
			if (toDispose != IntPtr.Zero)
 | 
			
		||||
			{
 | 
			
		||||
				QueueDestroyFunction(Device.Handle, toDispose);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		base.Dispose(disposing);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -127,12 +127,12 @@ namespace MoonWorks.Graphics
 | 
			
		|||
		public uint Stride;
 | 
			
		||||
		public VertexInputRate InputRate;
 | 
			
		||||
 | 
			
		||||
		public static VertexBinding Create<T>(uint binding = 0) where T : unmanaged
 | 
			
		||||
		public static VertexBinding Create<T>(uint binding = 0, VertexInputRate inputRate = VertexInputRate.Vertex) where T : unmanaged
 | 
			
		||||
		{
 | 
			
		||||
			return new VertexBinding
 | 
			
		||||
			{
 | 
			
		||||
				Binding = binding,
 | 
			
		||||
				InputRate = VertexInputRate.Vertex,
 | 
			
		||||
				InputRate = inputRate,
 | 
			
		||||
				Stride = (uint) Marshal.SizeOf<T>()
 | 
			
		||||
			};
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -181,7 +181,6 @@ namespace MoonWorks.Graphics
 | 
			
		|||
		public uint Depth;
 | 
			
		||||
		public uint Layer;
 | 
			
		||||
		public uint Level;
 | 
			
		||||
		public SampleCount SampleCount;
 | 
			
		||||
		public Color ClearColor;
 | 
			
		||||
		public LoadOp LoadOp;
 | 
			
		||||
		public StoreOp StoreOp;
 | 
			
		||||
| 
						 | 
				
			
			@ -189,15 +188,12 @@ namespace MoonWorks.Graphics
 | 
			
		|||
		public ColorAttachmentInfo(
 | 
			
		||||
			Texture texture,
 | 
			
		||||
			Color clearColor,
 | 
			
		||||
			SampleCount sampleCount = SampleCount.One,
 | 
			
		||||
			StoreOp storeOp = StoreOp.Store
 | 
			
		||||
		)
 | 
			
		||||
		{
 | 
			
		||||
		) {
 | 
			
		||||
			Texture = texture;
 | 
			
		||||
			Depth = 0;
 | 
			
		||||
			Layer = 0;
 | 
			
		||||
			Level = 0;
 | 
			
		||||
			SampleCount = sampleCount;
 | 
			
		||||
			ClearColor = clearColor;
 | 
			
		||||
			LoadOp = LoadOp.Clear;
 | 
			
		||||
			StoreOp = storeOp;
 | 
			
		||||
| 
						 | 
				
			
			@ -206,15 +202,12 @@ namespace MoonWorks.Graphics
 | 
			
		|||
		public ColorAttachmentInfo(
 | 
			
		||||
			Texture texture,
 | 
			
		||||
			LoadOp loadOp = LoadOp.DontCare,
 | 
			
		||||
			SampleCount sampleCount = SampleCount.One,
 | 
			
		||||
			StoreOp storeOp = StoreOp.Store
 | 
			
		||||
		)
 | 
			
		||||
		{
 | 
			
		||||
		) {
 | 
			
		||||
			Texture = texture;
 | 
			
		||||
			Depth = 0;
 | 
			
		||||
			Layer = 0;
 | 
			
		||||
			Level = 0;
 | 
			
		||||
			SampleCount = sampleCount;
 | 
			
		||||
			ClearColor = Color.White;
 | 
			
		||||
			LoadOp = loadOp;
 | 
			
		||||
			StoreOp = storeOp;
 | 
			
		||||
| 
						 | 
				
			
			@ -228,7 +221,6 @@ namespace MoonWorks.Graphics
 | 
			
		|||
				depth = Depth,
 | 
			
		||||
				layer = Layer,
 | 
			
		||||
				level = Level,
 | 
			
		||||
				sampleCount = (Refresh.SampleCount) SampleCount,
 | 
			
		||||
				clearColor = new Refresh.Vec4
 | 
			
		||||
				{
 | 
			
		||||
					x = ClearColor.R / 255f,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,7 +7,7 @@ namespace MoonWorks.Graphics
 | 
			
		|||
	/// <summary>
 | 
			
		||||
	/// Buffers are generic data containers that can be used by the GPU.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public class Buffer : GraphicsResource
 | 
			
		||||
	public class Buffer : RefreshResource
 | 
			
		||||
	{
 | 
			
		||||
		protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyBuffer;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -58,17 +58,29 @@ namespace MoonWorks.Graphics
 | 
			
		|||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Reads data out of a buffer and into an array.
 | 
			
		||||
		/// This operation is only guaranteed to read up-to-date data if GraphicsDevice.Wait is called first.
 | 
			
		||||
		/// Reads data out of a buffer and into a span.
 | 
			
		||||
		/// This operation is only guaranteed to read up-to-date data if GraphicsDevice.Wait or GraphicsDevice.WaitForFences is called first.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="data">The array that data will be copied to.</param>
 | 
			
		||||
		/// <param name="data">The span that data will be copied to.</param>
 | 
			
		||||
		/// <param name="dataLengthInBytes">The length of the data to read.</param>
 | 
			
		||||
		public unsafe void GetData<T>(
 | 
			
		||||
			T[] data,
 | 
			
		||||
			Span<T> data,
 | 
			
		||||
			uint dataLengthInBytes
 | 
			
		||||
		) where T : unmanaged
 | 
			
		||||
		{
 | 
			
		||||
			fixed (T* ptr = &data[0])
 | 
			
		||||
#if DEBUG
 | 
			
		||||
			if (dataLengthInBytes > Size)
 | 
			
		||||
			{
 | 
			
		||||
				Logger.LogWarn("Requested too many bytes from buffer!");
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (dataLengthInBytes > data.Length * Marshal.SizeOf<T>())
 | 
			
		||||
			{
 | 
			
		||||
				Logger.LogWarn("Data length is larger than the provided Span!");
 | 
			
		||||
			}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
			fixed (T* ptr = data)
 | 
			
		||||
			{
 | 
			
		||||
				Refresh.Refresh_GetBufferData(
 | 
			
		||||
					Device.Handle,
 | 
			
		||||
| 
						 | 
				
			
			@ -79,6 +91,48 @@ namespace MoonWorks.Graphics
 | 
			
		|||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Reads data out of a buffer and into an array.
 | 
			
		||||
		/// This operation is only guaranteed to read up-to-date data if GraphicsDevice.Wait or GraphicsDevice.WaitForFences is called first.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="data">The span that data will be copied to.</param>
 | 
			
		||||
		/// <param name="dataLengthInBytes">The length of the data to read.</param>
 | 
			
		||||
		public unsafe void GetData<T>(
 | 
			
		||||
			T[] data,
 | 
			
		||||
			uint dataLengthInBytes
 | 
			
		||||
		) where T : unmanaged
 | 
			
		||||
		{
 | 
			
		||||
			GetData(new Span<T>(data), dataLengthInBytes);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Reads data out of a buffer and into a span.
 | 
			
		||||
		/// This operation is only guaranteed to read up-to-date data if GraphicsDevice.Wait or GraphicsDevice.WaitForFences is called first.
 | 
			
		||||
		/// Fills the span with as much data from the buffer as it can.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="data">The span that data will be copied to.</param>
 | 
			
		||||
		public unsafe void GetData<T>(
 | 
			
		||||
			Span<T> data
 | 
			
		||||
		) where T : unmanaged
 | 
			
		||||
		{
 | 
			
		||||
			var lengthInBytes = System.Math.Min(data.Length * Marshal.SizeOf<T>(), Size);
 | 
			
		||||
			GetData(data, (uint) lengthInBytes);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Reads data out of a buffer and into an array.
 | 
			
		||||
		/// This operation is only guaranteed to read up-to-date data if GraphicsDevice.Wait or GraphicsDevice.WaitForFences is called first.
 | 
			
		||||
		/// Fills the array with as much data from the buffer as it can.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="data">The span that data will be copied to.</param>
 | 
			
		||||
		public unsafe void GetData<T>(
 | 
			
		||||
			T[] data
 | 
			
		||||
		) where T : unmanaged
 | 
			
		||||
		{
 | 
			
		||||
			var lengthInBytes = System.Math.Min(data.Length * Marshal.SizeOf<T>(), Size);
 | 
			
		||||
			GetData(new Span<T>(data), (uint) lengthInBytes);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static implicit operator BufferBinding(Buffer b)
 | 
			
		||||
		{
 | 
			
		||||
			return new BufferBinding(b, 0);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,10 +1,12 @@
 | 
			
		|||
using RefreshCS;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Runtime.InteropServices;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Graphics
 | 
			
		||||
{
 | 
			
		||||
	public class ComputePipeline : GraphicsResource
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// Compute pipelines perform arbitrary parallel processing on input data.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public class ComputePipeline : RefreshResource
 | 
			
		||||
	{
 | 
			
		||||
		protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyComputePipeline;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,26 @@
 | 
			
		|||
using System;
 | 
			
		||||
using RefreshCS;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Graphics
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// Fences allow you to track the status of a submitted command buffer. <br/>
 | 
			
		||||
	/// You should only acquire a Fence if you will need to track the command buffer. <br/>
 | 
			
		||||
	/// You should make sure to call GraphicsDevice.ReleaseFence when done with a Fence to avoid memory growth. <br/>
 | 
			
		||||
	/// The Fence object itself is basically just a wrapper for the Refresh_Fence. <br/>
 | 
			
		||||
	/// The internal handle is replaced so that we can pool Fence objects to manage garbage.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public class Fence : RefreshResource
 | 
			
		||||
	{
 | 
			
		||||
		protected override Action<nint, nint> QueueDestroyFunction => Refresh.Refresh_ReleaseFence;
 | 
			
		||||
 | 
			
		||||
		internal Fence(GraphicsDevice device) : base(device)
 | 
			
		||||
		{
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		internal void SetHandle(nint handle)
 | 
			
		||||
		{
 | 
			
		||||
			Handle = handle;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -5,10 +5,10 @@ using RefreshCS;
 | 
			
		|||
namespace MoonWorks.Graphics
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// Graphics pipelines encapsulate all of the render state in a single object.
 | 
			
		||||
	/// Graphics pipelines encapsulate all of the render state in a single object. <br/>
 | 
			
		||||
	/// These pipelines are bound before draw calls are issued.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public class GraphicsPipeline : GraphicsResource
 | 
			
		||||
	public class GraphicsPipeline : RefreshResource
 | 
			
		||||
	{
 | 
			
		||||
		protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyGraphicsPipeline;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,7 +6,7 @@ namespace MoonWorks.Graphics
 | 
			
		|||
	/// <summary>
 | 
			
		||||
	/// A sampler specifies how a texture will be sampled in a shader.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public class Sampler : GraphicsResource
 | 
			
		||||
	public class Sampler : RefreshResource
 | 
			
		||||
	{
 | 
			
		||||
		protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroySampler;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,22 +1,21 @@
 | 
			
		|||
using RefreshCS;
 | 
			
		||||
using System;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Runtime.InteropServices;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Graphics
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// Shader modules expect input in Refresh bytecode format.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public class ShaderModule : GraphicsResource
 | 
			
		||||
	public class ShaderModule : RefreshResource
 | 
			
		||||
	{
 | 
			
		||||
		protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyShaderModule;
 | 
			
		||||
 | 
			
		||||
		public unsafe ShaderModule(GraphicsDevice device, string filePath) : base(device)
 | 
			
		||||
		{
 | 
			
		||||
			using (FileStream stream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
 | 
			
		||||
			{
 | 
			
		||||
				Handle = CreateFromStream(device, stream);
 | 
			
		||||
			}
 | 
			
		||||
			using var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
 | 
			
		||||
			Handle = CreateFromStream(device, stream);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public unsafe ShaderModule(GraphicsDevice device, Stream stream) : base(device)
 | 
			
		||||
| 
						 | 
				
			
			@ -24,19 +23,20 @@ namespace MoonWorks.Graphics
 | 
			
		|||
			Handle = CreateFromStream(device, stream);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private unsafe static IntPtr CreateFromStream(GraphicsDevice device, Stream stream)
 | 
			
		||||
		private static unsafe IntPtr CreateFromStream(GraphicsDevice device, Stream stream)
 | 
			
		||||
		{
 | 
			
		||||
			var bytecode = new byte[stream.Length];
 | 
			
		||||
			stream.Read(bytecode, 0, (int) stream.Length);
 | 
			
		||||
			var bytecodeBuffer = NativeMemory.Alloc((nuint) stream.Length);
 | 
			
		||||
			var bytecodeSpan = new Span<byte>(bytecodeBuffer, (int) stream.Length);
 | 
			
		||||
			stream.ReadExactly(bytecodeSpan);
 | 
			
		||||
 | 
			
		||||
			fixed (byte* ptr = bytecode)
 | 
			
		||||
			{
 | 
			
		||||
				Refresh.ShaderModuleCreateInfo shaderModuleCreateInfo;
 | 
			
		||||
				shaderModuleCreateInfo.codeSize = (UIntPtr) bytecode.Length;
 | 
			
		||||
				shaderModuleCreateInfo.byteCode = (IntPtr) ptr;
 | 
			
		||||
			Refresh.ShaderModuleCreateInfo shaderModuleCreateInfo;
 | 
			
		||||
			shaderModuleCreateInfo.codeSize = (nuint) stream.Length;
 | 
			
		||||
			shaderModuleCreateInfo.byteCode = (nint) bytecodeBuffer;
 | 
			
		||||
 | 
			
		||||
				return Refresh.Refresh_CreateShaderModule(device.Handle, shaderModuleCreateInfo);
 | 
			
		||||
			}
 | 
			
		||||
			var shaderModule = Refresh.Refresh_CreateShaderModule(device.Handle, shaderModuleCreateInfo);
 | 
			
		||||
 | 
			
		||||
			NativeMemory.Free(bytecodeBuffer);
 | 
			
		||||
			return shaderModule;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,6 @@
 | 
			
		|||
using System;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Runtime.InteropServices;
 | 
			
		||||
using RefreshCS;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Graphics
 | 
			
		||||
| 
						 | 
				
			
			@ -7,7 +8,7 @@ namespace MoonWorks.Graphics
 | 
			
		|||
	/// <summary>
 | 
			
		||||
	/// A container for pixel data.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public class Texture : GraphicsResource
 | 
			
		||||
	public class Texture : RefreshResource
 | 
			
		||||
	{
 | 
			
		||||
		public uint Width { get; internal set; }
 | 
			
		||||
		public uint Height { get; internal set; }
 | 
			
		||||
| 
						 | 
				
			
			@ -15,104 +16,167 @@ namespace MoonWorks.Graphics
 | 
			
		|||
		public TextureFormat Format { get; internal set; }
 | 
			
		||||
		public bool IsCube { get; }
 | 
			
		||||
		public uint LevelCount { get; }
 | 
			
		||||
		public SampleCount SampleCount { get; }
 | 
			
		||||
		public TextureUsageFlags UsageFlags { get; }
 | 
			
		||||
		public uint Size { get; }
 | 
			
		||||
 | 
			
		||||
		// FIXME: this allocates a delegate instance
 | 
			
		||||
		protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyTexture;
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Loads a PNG from a file path.
 | 
			
		||||
		/// NOTE: You can queue as many of these as you want on to a command buffer but it MUST be submitted!
 | 
			
		||||
		/// Creates a 2D Texture using PNG or QOI data from raw byte data.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="device"></param>
 | 
			
		||||
		/// <param name="commandBuffer"></param>
 | 
			
		||||
		/// <param name="filePath"></param>
 | 
			
		||||
		/// <returns></returns>
 | 
			
		||||
		public static Texture LoadPNG(GraphicsDevice device, CommandBuffer commandBuffer, string filePath)
 | 
			
		||||
		{
 | 
			
		||||
			var pixels = Refresh.Refresh_Image_Load(
 | 
			
		||||
				filePath,
 | 
			
		||||
				out var width,
 | 
			
		||||
				out var height,
 | 
			
		||||
				out var channels
 | 
			
		||||
			);
 | 
			
		||||
		public static unsafe Texture FromImageBytes(
 | 
			
		||||
			GraphicsDevice device,
 | 
			
		||||
			CommandBuffer commandBuffer,
 | 
			
		||||
			Span<byte> data
 | 
			
		||||
		) {
 | 
			
		||||
			Texture texture;
 | 
			
		||||
 | 
			
		||||
			var byteCount = (uint) (width * height * channels);
 | 
			
		||||
			fixed (byte *dataPtr = data)
 | 
			
		||||
			{
 | 
			
		||||
				var pixels = Refresh.Refresh_Image_Load((nint) dataPtr, data.Length, out var width, out var height, out var len);
 | 
			
		||||
 | 
			
		||||
			TextureCreateInfo textureCreateInfo;
 | 
			
		||||
			textureCreateInfo.Width = (uint) width;
 | 
			
		||||
			textureCreateInfo.Height = (uint) height;
 | 
			
		||||
			textureCreateInfo.Depth = 1;
 | 
			
		||||
			textureCreateInfo.Format = TextureFormat.R8G8B8A8;
 | 
			
		||||
			textureCreateInfo.IsCube = false;
 | 
			
		||||
			textureCreateInfo.LevelCount = 1;
 | 
			
		||||
			textureCreateInfo.UsageFlags = TextureUsageFlags.Sampler;
 | 
			
		||||
				TextureCreateInfo textureCreateInfo = new TextureCreateInfo();
 | 
			
		||||
				textureCreateInfo.Width = (uint) width;
 | 
			
		||||
				textureCreateInfo.Height = (uint) height;
 | 
			
		||||
				textureCreateInfo.Depth = 1;
 | 
			
		||||
				textureCreateInfo.Format = TextureFormat.R8G8B8A8;
 | 
			
		||||
				textureCreateInfo.IsCube = false;
 | 
			
		||||
				textureCreateInfo.LevelCount = 1;
 | 
			
		||||
				textureCreateInfo.SampleCount = SampleCount.One;
 | 
			
		||||
				textureCreateInfo.UsageFlags = TextureUsageFlags.Sampler;
 | 
			
		||||
 | 
			
		||||
			var texture = new Texture(device, textureCreateInfo);
 | 
			
		||||
			commandBuffer.SetTextureData(texture, pixels, byteCount);
 | 
			
		||||
				texture = new Texture(device, textureCreateInfo);
 | 
			
		||||
				commandBuffer.SetTextureData(texture, pixels, (uint) len);
 | 
			
		||||
 | 
			
		||||
			Refresh.Refresh_Image_Free(pixels);
 | 
			
		||||
				Refresh.Refresh_Image_Free(pixels);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return texture;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Saves RGBA or BGRA pixel data to a file in PNG format.
 | 
			
		||||
		/// Creates a 2D Texture using PNG or QOI data from a stream.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public unsafe static void SavePNG(string path, int width, int height, TextureFormat format, byte[] pixels)
 | 
			
		||||
		{
 | 
			
		||||
			if (format != TextureFormat.R8G8B8A8 && format != TextureFormat.B8G8R8A8)
 | 
			
		||||
			{
 | 
			
		||||
				throw new ArgumentException("Texture format must be RGBA8 or BGRA8!", "format");
 | 
			
		||||
			}
 | 
			
		||||
		public static unsafe Texture FromImageStream(
 | 
			
		||||
			GraphicsDevice device,
 | 
			
		||||
			CommandBuffer commandBuffer,
 | 
			
		||||
			Stream stream
 | 
			
		||||
		) {
 | 
			
		||||
			var length = stream.Length;
 | 
			
		||||
			var buffer = NativeMemory.Alloc((nuint) length);
 | 
			
		||||
			var span = new Span<byte>(buffer, (int) length);
 | 
			
		||||
			stream.ReadExactly(span);
 | 
			
		||||
 | 
			
		||||
			fixed (byte* ptr = &pixels[0])
 | 
			
		||||
			var texture = FromImageBytes(device, commandBuffer, span);
 | 
			
		||||
 | 
			
		||||
			NativeMemory.Free((void*) buffer);
 | 
			
		||||
 | 
			
		||||
			return texture;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Creates a 2D Texture using PNG or QOI data from a file.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public static Texture FromImageFile(
 | 
			
		||||
			GraphicsDevice device,
 | 
			
		||||
			CommandBuffer commandBuffer,
 | 
			
		||||
			string path
 | 
			
		||||
		) {
 | 
			
		||||
			var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read);
 | 
			
		||||
			return FromImageStream(device, commandBuffer, fileStream);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static unsafe void SetDataFromImageBytes(
 | 
			
		||||
			CommandBuffer commandBuffer,
 | 
			
		||||
			TextureSlice textureSlice,
 | 
			
		||||
			Span<byte> data
 | 
			
		||||
		) {
 | 
			
		||||
			fixed (byte* ptr = data)
 | 
			
		||||
			{
 | 
			
		||||
				Refresh.Refresh_Image_SavePNG(path, width, height, Conversions.BoolToByte(format == TextureFormat.B8G8R8A8), (IntPtr) ptr);
 | 
			
		||||
				var pixels = Refresh.Refresh_Image_Load(
 | 
			
		||||
					(nint) ptr,
 | 
			
		||||
					(int) data.Length,
 | 
			
		||||
					out var w,
 | 
			
		||||
					out var h,
 | 
			
		||||
					out var len
 | 
			
		||||
				);
 | 
			
		||||
 | 
			
		||||
				commandBuffer.SetTextureData(textureSlice, pixels, (uint) len);
 | 
			
		||||
 | 
			
		||||
				Refresh.Refresh_Image_Free(pixels);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static Texture LoadDDS(GraphicsDevice graphicsDevice, CommandBuffer commandBuffer, System.IO.Stream stream)
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Sets data for a texture slice using PNG or QOI data from a stream.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public static unsafe void SetDataFromImageStream(
 | 
			
		||||
			CommandBuffer commandBuffer,
 | 
			
		||||
			TextureSlice textureSlice,
 | 
			
		||||
			Stream stream
 | 
			
		||||
		) {
 | 
			
		||||
			var length = stream.Length;
 | 
			
		||||
			var buffer = NativeMemory.Alloc((nuint) length);
 | 
			
		||||
			var span = new Span<byte>(buffer, (int) length);
 | 
			
		||||
			stream.ReadExactly(span);
 | 
			
		||||
 | 
			
		||||
			SetDataFromImageBytes(commandBuffer, textureSlice, span);
 | 
			
		||||
 | 
			
		||||
			NativeMemory.Free((void*) buffer);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Sets data for a texture slice using PNG or QOI data from a file.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public static void SetDataFromImageFile(
 | 
			
		||||
			CommandBuffer commandBuffer,
 | 
			
		||||
			TextureSlice textureSlice,
 | 
			
		||||
			string path
 | 
			
		||||
		) {
 | 
			
		||||
			var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read);
 | 
			
		||||
			SetDataFromImageStream(commandBuffer, textureSlice, fileStream);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public unsafe static Texture LoadDDS(GraphicsDevice graphicsDevice, CommandBuffer commandBuffer, System.IO.Stream stream)
 | 
			
		||||
		{
 | 
			
		||||
			using (var reader = new BinaryReader(stream))
 | 
			
		||||
			using var reader = new BinaryReader(stream);
 | 
			
		||||
			Texture texture;
 | 
			
		||||
			int faces;
 | 
			
		||||
			ParseDDS(reader, out var format, out var width, out var height, out var levels, out var isCube);
 | 
			
		||||
 | 
			
		||||
			if (isCube)
 | 
			
		||||
			{
 | 
			
		||||
				Texture texture;
 | 
			
		||||
				int faces;
 | 
			
		||||
				ParseDDS(reader, out var format, out var width, out var height, out var levels, out var isCube);
 | 
			
		||||
 | 
			
		||||
				if (isCube)
 | 
			
		||||
				{
 | 
			
		||||
					texture = CreateTextureCube(graphicsDevice, (uint) width, format, TextureUsageFlags.Sampler, (uint) levels);
 | 
			
		||||
					faces = 6;
 | 
			
		||||
				}
 | 
			
		||||
				else
 | 
			
		||||
				{
 | 
			
		||||
					texture = CreateTexture2D(graphicsDevice, (uint) width, (uint) height, format, TextureUsageFlags.Sampler, (uint) levels);
 | 
			
		||||
					faces = 1;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				for (int i = 0; i < faces; i += 1)
 | 
			
		||||
				{
 | 
			
		||||
					for (int j = 0; j < levels; j += 1)
 | 
			
		||||
					{
 | 
			
		||||
						var levelWidth = width >> j;
 | 
			
		||||
						var levelHeight = height >> j;
 | 
			
		||||
 | 
			
		||||
						var pixels = reader.ReadBytes(
 | 
			
		||||
							Texture.CalculateDDSLevelSize(
 | 
			
		||||
								levelWidth,
 | 
			
		||||
								levelHeight,
 | 
			
		||||
								format
 | 
			
		||||
							)
 | 
			
		||||
						);
 | 
			
		||||
 | 
			
		||||
						var textureSlice = new TextureSlice(texture, new Rect(0, 0, levelWidth, levelHeight), 0, (uint) i, (uint) j);
 | 
			
		||||
						commandBuffer.SetTextureData(textureSlice, pixels);
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				return texture;
 | 
			
		||||
				texture = CreateTextureCube(graphicsDevice, (uint) width, format, TextureUsageFlags.Sampler, (uint) levels);
 | 
			
		||||
				faces = 6;
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
				texture = CreateTexture2D(graphicsDevice, (uint) width, (uint) height, format, TextureUsageFlags.Sampler, (uint) levels);
 | 
			
		||||
				faces = 1;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			for (int i = 0; i < faces; i += 1)
 | 
			
		||||
			{
 | 
			
		||||
				for (int j = 0; j < levels; j += 1)
 | 
			
		||||
				{
 | 
			
		||||
					var levelWidth = width >> j;
 | 
			
		||||
					var levelHeight = height >> j;
 | 
			
		||||
 | 
			
		||||
					var levelSize = CalculateDDSLevelSize(levelWidth, levelHeight, format);
 | 
			
		||||
					var byteBuffer = NativeMemory.Alloc((nuint) levelSize);
 | 
			
		||||
					var byteSpan = new Span<byte>(byteBuffer, levelSize);
 | 
			
		||||
					stream.ReadExactly(byteSpan);
 | 
			
		||||
 | 
			
		||||
					var textureSlice = new TextureSlice(texture, new Rect(0, 0, levelWidth, levelHeight), 0, (uint) i, (uint) j);
 | 
			
		||||
					commandBuffer.SetTextureData(textureSlice, (nint) byteBuffer, (uint) levelSize);
 | 
			
		||||
 | 
			
		||||
					NativeMemory.Free(byteBuffer);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return texture;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
| 
						 | 
				
			
			@ -130,9 +194,9 @@ namespace MoonWorks.Graphics
 | 
			
		|||
			uint height,
 | 
			
		||||
			TextureFormat format,
 | 
			
		||||
			TextureUsageFlags usageFlags,
 | 
			
		||||
			uint levelCount = 1
 | 
			
		||||
		)
 | 
			
		||||
		{
 | 
			
		||||
			uint levelCount = 1,
 | 
			
		||||
			SampleCount sampleCount = SampleCount.One
 | 
			
		||||
		) {
 | 
			
		||||
			var textureCreateInfo = new TextureCreateInfo
 | 
			
		||||
			{
 | 
			
		||||
				Width = width,
 | 
			
		||||
| 
						 | 
				
			
			@ -140,6 +204,7 @@ namespace MoonWorks.Graphics
 | 
			
		|||
				Depth = 1,
 | 
			
		||||
				IsCube = false,
 | 
			
		||||
				LevelCount = levelCount,
 | 
			
		||||
				SampleCount = sampleCount,
 | 
			
		||||
				Format = format,
 | 
			
		||||
				UsageFlags = usageFlags
 | 
			
		||||
			};
 | 
			
		||||
| 
						 | 
				
			
			@ -165,8 +230,7 @@ namespace MoonWorks.Graphics
 | 
			
		|||
			TextureFormat format,
 | 
			
		||||
			TextureUsageFlags usageFlags,
 | 
			
		||||
			uint levelCount = 1
 | 
			
		||||
		)
 | 
			
		||||
		{
 | 
			
		||||
		) {
 | 
			
		||||
			var textureCreateInfo = new TextureCreateInfo
 | 
			
		||||
			{
 | 
			
		||||
				Width = width,
 | 
			
		||||
| 
						 | 
				
			
			@ -195,8 +259,7 @@ namespace MoonWorks.Graphics
 | 
			
		|||
			TextureFormat format,
 | 
			
		||||
			TextureUsageFlags usageFlags,
 | 
			
		||||
			uint levelCount = 1
 | 
			
		||||
		)
 | 
			
		||||
		{
 | 
			
		||||
		) {
 | 
			
		||||
			var textureCreateInfo = new TextureCreateInfo
 | 
			
		||||
			{
 | 
			
		||||
				Width = size,
 | 
			
		||||
| 
						 | 
				
			
			@ -232,7 +295,9 @@ namespace MoonWorks.Graphics
 | 
			
		|||
			Depth = textureCreateInfo.Depth;
 | 
			
		||||
			IsCube = textureCreateInfo.IsCube;
 | 
			
		||||
			LevelCount = textureCreateInfo.LevelCount;
 | 
			
		||||
			SampleCount = textureCreateInfo.SampleCount;
 | 
			
		||||
			UsageFlags = textureCreateInfo.UsageFlags;
 | 
			
		||||
			Size = Width * Height * BytesPerPixel(Format) / BlockSizeSquared(Format);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static implicit operator TextureSlice(Texture t) => new TextureSlice(t);
 | 
			
		||||
| 
						 | 
				
			
			@ -241,21 +306,20 @@ namespace MoonWorks.Graphics
 | 
			
		|||
		// Should not be tracked, because swapchain textures are managed by Vulkan.
 | 
			
		||||
		internal Texture(
 | 
			
		||||
			GraphicsDevice device,
 | 
			
		||||
			IntPtr handle,
 | 
			
		||||
			TextureFormat format,
 | 
			
		||||
			uint width,
 | 
			
		||||
			uint height
 | 
			
		||||
		) : base(device, false)
 | 
			
		||||
			TextureFormat format
 | 
			
		||||
		) : base(device)
 | 
			
		||||
		{
 | 
			
		||||
			Handle = handle;
 | 
			
		||||
			Handle = IntPtr.Zero;
 | 
			
		||||
 | 
			
		||||
			Format = format;
 | 
			
		||||
			Width = width;
 | 
			
		||||
			Height = height;
 | 
			
		||||
			Width = 0;
 | 
			
		||||
			Height = 0;
 | 
			
		||||
			Depth = 1;
 | 
			
		||||
			IsCube = false;
 | 
			
		||||
			LevelCount = 1;
 | 
			
		||||
			SampleCount = SampleCount.One;
 | 
			
		||||
			UsageFlags = TextureUsageFlags.ColorTarget;
 | 
			
		||||
			Size = Width * Height * BytesPerPixel(Format) / BlockSizeSquared(Format);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// DDS loading extension, based on MojoDDS
 | 
			
		||||
| 
						 | 
				
			
			@ -525,5 +589,155 @@ namespace MoonWorks.Graphics
 | 
			
		|||
				);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Asynchronously saves RGBA or BGRA pixel data to a file in PNG format. <br/>
 | 
			
		||||
		/// Warning: this is expensive and will block to wait for data download from GPU! <br/>
 | 
			
		||||
		/// You can avoid blocking by calling this method from a thread.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public unsafe void SavePNG(string path)
 | 
			
		||||
		{
 | 
			
		||||
#if DEBUG
 | 
			
		||||
			if (Format != TextureFormat.R8G8B8A8 && Format != TextureFormat.B8G8R8A8)
 | 
			
		||||
			{
 | 
			
		||||
				throw new ArgumentException("Texture format must be RGBA or BGRA!", "format");
 | 
			
		||||
			}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
			var buffer = new Buffer(Device, 0, Width * Height * 4); // this creates garbage... oh well
 | 
			
		||||
 | 
			
		||||
			// immediately request the data copy
 | 
			
		||||
			var commandBuffer = Device.AcquireCommandBuffer();
 | 
			
		||||
			commandBuffer.CopyTextureToBuffer(this, buffer);
 | 
			
		||||
			var fence = Device.SubmitAndAcquireFence(commandBuffer);
 | 
			
		||||
 | 
			
		||||
			var byteCount = buffer.Size;
 | 
			
		||||
 | 
			
		||||
			var pixelsPtr = NativeMemory.Alloc((nuint) byteCount);
 | 
			
		||||
			var pixelsSpan = new Span<byte>(pixelsPtr, (int) byteCount);
 | 
			
		||||
 | 
			
		||||
			Device.WaitForFences(fence); // make sure the data transfer is done...
 | 
			
		||||
			Device.ReleaseFence(fence); // and then release the fence
 | 
			
		||||
 | 
			
		||||
			buffer.GetData(pixelsSpan);
 | 
			
		||||
 | 
			
		||||
			if (Format == TextureFormat.B8G8R8A8)
 | 
			
		||||
			{
 | 
			
		||||
				var rgbaPtr = NativeMemory.Alloc((nuint) byteCount);
 | 
			
		||||
				var rgbaSpan = new Span<byte>(rgbaPtr, (int) byteCount);
 | 
			
		||||
 | 
			
		||||
				for (var i = 0; i < byteCount; i += 4)
 | 
			
		||||
				{
 | 
			
		||||
					rgbaSpan[i] = pixelsSpan[i + 2];
 | 
			
		||||
					rgbaSpan[i + 1] = pixelsSpan[i + 1];
 | 
			
		||||
					rgbaSpan[i + 2] = pixelsSpan[i];
 | 
			
		||||
					rgbaSpan[i + 3] = pixelsSpan[i + 3];
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				Refresh.Refresh_Image_SavePNG(path, (nint) rgbaPtr, (int) Width, (int) Height);
 | 
			
		||||
 | 
			
		||||
				NativeMemory.Free((void*) rgbaPtr);
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
				fixed (byte* ptr = pixelsSpan)
 | 
			
		||||
				{
 | 
			
		||||
					Refresh.Refresh_Image_SavePNG(path, (nint) ptr, (int) Width, (int) Height);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			NativeMemory.Free(pixelsPtr);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static uint BytesPerPixel(TextureFormat format)
 | 
			
		||||
		{
 | 
			
		||||
			switch (format)
 | 
			
		||||
			{
 | 
			
		||||
				case TextureFormat.R8:
 | 
			
		||||
				case TextureFormat.R8_UINT:
 | 
			
		||||
					return 1;
 | 
			
		||||
				case TextureFormat.R5G6B5:
 | 
			
		||||
				case TextureFormat.B4G4R4A4:
 | 
			
		||||
				case TextureFormat.A1R5G5B5:
 | 
			
		||||
				case TextureFormat.R16_SFLOAT:
 | 
			
		||||
				case TextureFormat.R8G8_SNORM:
 | 
			
		||||
				case TextureFormat.R8G8_UINT:
 | 
			
		||||
				case TextureFormat.R16_UINT:
 | 
			
		||||
				case TextureFormat.D16:
 | 
			
		||||
					return 2;
 | 
			
		||||
				case TextureFormat.D16S8:
 | 
			
		||||
					return 3;
 | 
			
		||||
				case TextureFormat.R8G8B8A8:
 | 
			
		||||
				case TextureFormat.B8G8R8A8:
 | 
			
		||||
				case TextureFormat.R32_SFLOAT:
 | 
			
		||||
				case TextureFormat.R16G16:
 | 
			
		||||
				case TextureFormat.R16G16_SFLOAT:
 | 
			
		||||
				case TextureFormat.R8G8B8A8_SNORM:
 | 
			
		||||
				case TextureFormat.A2R10G10B10:
 | 
			
		||||
				case TextureFormat.R8G8B8A8_UINT:
 | 
			
		||||
				case TextureFormat.R16G16_UINT:
 | 
			
		||||
				case TextureFormat.D32:
 | 
			
		||||
					return 4;
 | 
			
		||||
				case TextureFormat.D32S8:
 | 
			
		||||
					return 5;
 | 
			
		||||
				case TextureFormat.R16G16B16A16_SFLOAT:
 | 
			
		||||
				case TextureFormat.R16G16B16A16:
 | 
			
		||||
				case TextureFormat.R32G32_SFLOAT:
 | 
			
		||||
				case TextureFormat.R16G16B16A16_UINT:
 | 
			
		||||
				case TextureFormat.BC1:
 | 
			
		||||
					return 8;
 | 
			
		||||
				case TextureFormat.R32G32B32A32_SFLOAT:
 | 
			
		||||
				case TextureFormat.BC2:
 | 
			
		||||
				case TextureFormat.BC3:
 | 
			
		||||
				case TextureFormat.BC7:
 | 
			
		||||
					return 16;
 | 
			
		||||
				default:
 | 
			
		||||
					Logger.LogError("Texture format not recognized!");
 | 
			
		||||
					return 0;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static uint BlockSizeSquared(TextureFormat format)
 | 
			
		||||
		{
 | 
			
		||||
			switch (format)
 | 
			
		||||
			{
 | 
			
		||||
				case TextureFormat.BC1:
 | 
			
		||||
				case TextureFormat.BC2:
 | 
			
		||||
				case TextureFormat.BC3:
 | 
			
		||||
				case TextureFormat.BC7:
 | 
			
		||||
					return 16;
 | 
			
		||||
				case TextureFormat.R8G8B8A8:
 | 
			
		||||
				case TextureFormat.B8G8R8A8:
 | 
			
		||||
				case TextureFormat.R5G6B5:
 | 
			
		||||
				case TextureFormat.A1R5G5B5:
 | 
			
		||||
				case TextureFormat.B4G4R4A4:
 | 
			
		||||
				case TextureFormat.A2R10G10B10:
 | 
			
		||||
				case TextureFormat.R16G16:
 | 
			
		||||
				case TextureFormat.R16G16B16A16:
 | 
			
		||||
				case TextureFormat.R8:
 | 
			
		||||
				case TextureFormat.R8G8_SNORM:
 | 
			
		||||
				case TextureFormat.R8G8B8A8_SNORM:
 | 
			
		||||
				case TextureFormat.R16_SFLOAT:
 | 
			
		||||
				case TextureFormat.R16G16_SFLOAT:
 | 
			
		||||
				case TextureFormat.R16G16B16A16_SFLOAT:
 | 
			
		||||
				case TextureFormat.R32_SFLOAT:
 | 
			
		||||
				case TextureFormat.R32G32_SFLOAT:
 | 
			
		||||
				case TextureFormat.R32G32B32A32_SFLOAT:
 | 
			
		||||
				case TextureFormat.R8_UINT:
 | 
			
		||||
				case TextureFormat.R8G8_UINT:
 | 
			
		||||
				case TextureFormat.R8G8B8A8_UINT:
 | 
			
		||||
				case TextureFormat.R16_UINT:
 | 
			
		||||
				case TextureFormat.R16G16_UINT:
 | 
			
		||||
				case TextureFormat.R16G16B16A16_UINT:
 | 
			
		||||
				case TextureFormat.D16:
 | 
			
		||||
				case TextureFormat.D32:
 | 
			
		||||
				case TextureFormat.D16S8:
 | 
			
		||||
				case TextureFormat.D32S8:
 | 
			
		||||
					return 1;
 | 
			
		||||
				default:
 | 
			
		||||
					Logger.LogError("Texture format not recognized!");
 | 
			
		||||
					return 0;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,6 +2,9 @@
 | 
			
		|||
 | 
			
		||||
namespace MoonWorks.Graphics
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// Defines how color blending will be performed in a GraphicsPipeline.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public struct ColorAttachmentBlendState
 | 
			
		||||
	{
 | 
			
		||||
		/// <summary>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,7 +3,7 @@ using System.Runtime.InteropServices;
 | 
			
		|||
namespace MoonWorks.Graphics
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// Information that the pipeline needs about a shader.
 | 
			
		||||
	/// Information that the compute pipeline needs about a compute shader.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public struct ComputeShaderInfo
 | 
			
		||||
	{
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,8 @@
 | 
			
		|||
namespace MoonWorks.Graphics
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// All of the information that is used to create a GraphicsPipeline.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public struct GraphicsPipelineCreateInfo
 | 
			
		||||
	{
 | 
			
		||||
		public DepthStencilState DepthStencilState;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,7 +3,7 @@
 | 
			
		|||
namespace MoonWorks.Graphics
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// Information that the pipeline needs about a shader.
 | 
			
		||||
	/// Information that the pipeline needs about a graphics shader.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public struct GraphicsShaderInfo
 | 
			
		||||
	{
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,21 +2,60 @@
 | 
			
		|||
 | 
			
		||||
namespace MoonWorks.Graphics
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// All of the information that is used to create a sampler.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public struct SamplerCreateInfo
 | 
			
		||||
	{
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Minification filter mode. Used when the image is downscaled.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public Filter MinFilter;
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Magnification filter mode. Used when the image is upscaled.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public Filter MagFilter;
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Filter mode applied to mipmap lookups.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public SamplerMipmapMode MipmapMode;
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Horizontal addressing mode.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public SamplerAddressMode AddressModeU;
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Vertical addressing mode.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public SamplerAddressMode AddressModeV;
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Depth addressing mode.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public SamplerAddressMode AddressModeW;
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Bias value added to mipmap level of detail calculation.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public float MipLodBias;
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Enables anisotropic filtering.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public bool AnisotropyEnable;
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Maximum anisotropy value.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public float MaxAnisotropy;
 | 
			
		||||
		public bool CompareEnable;
 | 
			
		||||
		public CompareOp CompareOp;
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Clamps the LOD value to a specified minimum.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public float MinLod;
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Clamps the LOD value to a specified maximum.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public float MaxLod;
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// If an address mode is set to ClampToBorder, will replace color with this color when samples are outside the [0, 1) range.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public BorderColor BorderColor;
 | 
			
		||||
 | 
			
		||||
		public static readonly SamplerCreateInfo AnisotropicClamp = new SamplerCreateInfo
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,6 +2,9 @@
 | 
			
		|||
 | 
			
		||||
namespace MoonWorks.Graphics
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// All of the information that is used to create a texture.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public struct TextureCreateInfo
 | 
			
		||||
	{
 | 
			
		||||
		public uint Width;
 | 
			
		||||
| 
						 | 
				
			
			@ -9,6 +12,7 @@ namespace MoonWorks.Graphics
 | 
			
		|||
		public uint Depth;
 | 
			
		||||
		public bool IsCube;
 | 
			
		||||
		public uint LevelCount;
 | 
			
		||||
		public SampleCount SampleCount;
 | 
			
		||||
		public TextureFormat Format;
 | 
			
		||||
		public TextureUsageFlags UsageFlags;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -21,6 +25,7 @@ namespace MoonWorks.Graphics
 | 
			
		|||
				depth = Depth,
 | 
			
		||||
				isCube = Conversions.BoolToByte(IsCube),
 | 
			
		||||
				levelCount = LevelCount,
 | 
			
		||||
				sampleCount = (Refresh.SampleCount) SampleCount,
 | 
			
		||||
				format = (Refresh.TextureFormat) Format,
 | 
			
		||||
				usageFlags = (Refresh.TextureUsageFlags) UsageFlags
 | 
			
		||||
			};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,7 @@
 | 
			
		|||
namespace MoonWorks.Graphics
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// Specifies how to interpet vertex data in a buffer to be passed to the vertex shader.
 | 
			
		||||
	/// Specifies how the vertex shader will interpet vertex data in a buffer.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public struct VertexInputState
 | 
			
		||||
	{
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							| 
						 | 
				
			
			@ -0,0 +1,34 @@
 | 
			
		|||
#version 450
 | 
			
		||||
 | 
			
		||||
layout(set = 1, binding = 0) uniform sampler2D msdf;
 | 
			
		||||
 | 
			
		||||
layout(location = 0) in vec2 inTexCoord;
 | 
			
		||||
layout(location = 1) in vec4 inColor;
 | 
			
		||||
 | 
			
		||||
layout(location = 0) out vec4 outColor;
 | 
			
		||||
 | 
			
		||||
layout(binding = 0, set = 3) uniform UBO
 | 
			
		||||
{
 | 
			
		||||
	float pxRange;
 | 
			
		||||
} ubo;
 | 
			
		||||
 | 
			
		||||
float median(float r, float g, float b)
 | 
			
		||||
{
 | 
			
		||||
	return max(min(r, g), min(max(r, g), b));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float screenPxRange()
 | 
			
		||||
{
 | 
			
		||||
    vec2 unitRange = vec2(ubo.pxRange)/vec2(textureSize(msdf, 0));
 | 
			
		||||
    vec2 screenTexSize = vec2(1.0)/fwidth(inTexCoord);
 | 
			
		||||
    return max(0.5*dot(unitRange, screenTexSize), 1.0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void main()
 | 
			
		||||
{
 | 
			
		||||
	vec3 msd = texture(msdf, inTexCoord).rgb;
 | 
			
		||||
	float sd = median(msd.r, msd.g, msd.b);
 | 
			
		||||
    float screenPxDistance = screenPxRange() * (sd - 0.5);
 | 
			
		||||
    float opacity = clamp(screenPxDistance + 0.5, 0.0, 1.0);
 | 
			
		||||
    outColor = mix(vec4(0.0, 0.0, 0.0, 0.0), inColor, opacity);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,20 @@
 | 
			
		|||
#version 450
 | 
			
		||||
 | 
			
		||||
layout(location = 0) in vec3 inPos;
 | 
			
		||||
layout(location = 1) in vec2 inTexCoord;
 | 
			
		||||
layout(location = 2) in vec4 inColor;
 | 
			
		||||
 | 
			
		||||
layout(location = 0) out vec2 outTexCoord;
 | 
			
		||||
layout(location = 1) out vec4 outColor;
 | 
			
		||||
 | 
			
		||||
layout(binding = 0, set = 2) uniform UBO
 | 
			
		||||
{
 | 
			
		||||
	mat4 ViewProjection;
 | 
			
		||||
} ubo;
 | 
			
		||||
 | 
			
		||||
void main()
 | 
			
		||||
{
 | 
			
		||||
	gl_Position = ubo.ViewProjection * vec4(inPos, 1.0);
 | 
			
		||||
	outTexCoord = inTexCoord;
 | 
			
		||||
	outColor = inColor;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -14,6 +14,8 @@ namespace MoonWorks.Graphics
 | 
			
		|||
		public uint Layer { get; }
 | 
			
		||||
		public uint Level { get; }
 | 
			
		||||
 | 
			
		||||
		public uint Size => (uint) (Rectangle.W * Rectangle.H * Texture.BytesPerPixel(Texture.Format) / Texture.BlockSizeSquared(Texture.Format));
 | 
			
		||||
 | 
			
		||||
		public TextureSlice(Texture texture)
 | 
			
		||||
		{
 | 
			
		||||
			Texture = texture;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,8 @@
 | 
			
		|||
namespace MoonWorks.Graphics
 | 
			
		||||
{
 | 
			
		||||
	// This is a convenience structure for pairing a vertex binding with its associated attributes.
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// A convenience structure for pairing a vertex binding with its associated attributes.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public struct VertexBindingAndAttributes
 | 
			
		||||
	{
 | 
			
		||||
		public VertexBinding VertexBinding { get; }
 | 
			
		||||
| 
						 | 
				
			
			@ -12,9 +14,9 @@ namespace MoonWorks.Graphics
 | 
			
		|||
			VertexAttributes = attributes;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static VertexBindingAndAttributes Create<T>(uint bindingIndex) where T : unmanaged, IVertexType
 | 
			
		||||
		public static VertexBindingAndAttributes Create<T>(uint bindingIndex, uint locationOffset = 0, VertexInputRate inputRate = VertexInputRate.Vertex) where T : unmanaged, IVertexType
 | 
			
		||||
		{
 | 
			
		||||
			VertexBinding binding = VertexBinding.Create<T>(bindingIndex);
 | 
			
		||||
			VertexBinding binding = VertexBinding.Create<T>(bindingIndex, inputRate);
 | 
			
		||||
			VertexAttribute[] attributes = new VertexAttribute[T.Formats.Length];
 | 
			
		||||
			uint offset = 0;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -25,7 +27,7 @@ namespace MoonWorks.Graphics
 | 
			
		|||
				attributes[i] = new VertexAttribute
 | 
			
		||||
				{
 | 
			
		||||
					Binding = bindingIndex,
 | 
			
		||||
					Location = i,
 | 
			
		||||
					Location = locationOffset + i,
 | 
			
		||||
					Format = format,
 | 
			
		||||
					Offset = offset
 | 
			
		||||
				};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,6 +3,9 @@ using SDL2;
 | 
			
		|||
 | 
			
		||||
namespace MoonWorks.Input
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// Represents a specific joystick direction on a gamepad.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public class Axis
 | 
			
		||||
	{
 | 
			
		||||
		public Gamepad Parent { get; }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,8 @@
 | 
			
		|||
namespace MoonWorks.Input
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// Can be used to access a gamepad axis virtual button without a direct reference to the button object.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public enum AxisButtonCode
 | 
			
		||||
	{
 | 
			
		||||
		LeftX_Left,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,9 @@
 | 
			
		|||
namespace MoonWorks.Input
 | 
			
		||||
{
 | 
			
		||||
	// Enum values are equivalent to SDL GameControllerAxis
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// Can be used to access a gamepad axis without a direct reference to the axis object.
 | 
			
		||||
	/// Enum values are equivalent to SDL_GameControllerAxis.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public enum AxisCode
 | 
			
		||||
	{
 | 
			
		||||
		LeftX,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,9 @@
 | 
			
		|||
namespace MoonWorks.Input
 | 
			
		||||
{
 | 
			
		||||
	// Enum values are equivalent to the SDL GameControllerButton value.
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// Can be used to access a gamepad button without a direct reference to the button object.
 | 
			
		||||
 	/// Enum values are equivalent to the SDL GameControllerButton value.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public enum GamepadButtonCode
 | 
			
		||||
	{
 | 
			
		||||
		A,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,14 +1,40 @@
 | 
			
		|||
namespace MoonWorks.Input
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// Container for the current state of a binary input.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public struct ButtonState
 | 
			
		||||
	{
 | 
			
		||||
		public ButtonStatus ButtonStatus { get; }
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// True if the button was pressed this frame.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public bool IsPressed => ButtonStatus == ButtonStatus.Pressed;
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// True if the button was pressed this frame and the previous frame.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public bool IsHeld => ButtonStatus == ButtonStatus.Held;
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// True if the button was either pressed or continued to be held this frame.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public bool IsDown => ButtonStatus == ButtonStatus.Pressed || ButtonStatus == ButtonStatus.Held;
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// True if the button was let go this frame.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public bool IsReleased => ButtonStatus == ButtonStatus.Released;
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// True if the button was not pressed this frame or the previous frame.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public bool IsIdle => ButtonStatus == ButtonStatus.Idle;
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// True if the button was either idle or released this frame.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public bool IsUp => ButtonStatus == ButtonStatus.Idle || ButtonStatus == ButtonStatus.Released;
 | 
			
		||||
 | 
			
		||||
		public ButtonState(ButtonStatus buttonStatus)
 | 
			
		||||
| 
						 | 
				
			
			@ -43,7 +69,7 @@
 | 
			
		|||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Combines two button states. Useful for alt controls or input buffering.
 | 
			
		||||
		/// Combines two button states. Useful for alt control sets or input buffering.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public static ButtonState operator |(ButtonState a, ButtonState b)
 | 
			
		||||
		{
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,8 @@
 | 
			
		|||
namespace MoonWorks.Input
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// Represents the current status of a binary input.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public enum ButtonStatus
 | 
			
		||||
	{
 | 
			
		||||
		/// <summary>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,10 +0,0 @@
 | 
			
		|||
namespace MoonWorks.Input
 | 
			
		||||
{
 | 
			
		||||
    public enum DeviceKind
 | 
			
		||||
    {
 | 
			
		||||
		None,
 | 
			
		||||
        Keyboard,
 | 
			
		||||
        Mouse,
 | 
			
		||||
        Gamepad,
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -5,6 +5,12 @@ using SDL2;
 | 
			
		|||
 | 
			
		||||
namespace MoonWorks.Input
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// A Gamepad input abstraction that represents input coming from a console controller or other such devices.
 | 
			
		||||
	/// The button names map to a standard Xbox 360 controller.
 | 
			
		||||
	/// For different controllers the relative position of the face buttons will determine the button mapping.
 | 
			
		||||
	/// For example on a DualShock controller the Cross button will map to the A button.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public class Gamepad
 | 
			
		||||
	{
 | 
			
		||||
		internal IntPtr Handle;
 | 
			
		||||
| 
						 | 
				
			
			@ -51,7 +57,14 @@ namespace MoonWorks.Input
 | 
			
		|||
 | 
			
		||||
		public bool IsDummy => Handle == IntPtr.Zero;
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// True if any input on the gamepad is active. Useful for input remapping.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public bool AnyPressed { get; private set; }
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Contains a reference to an arbitrary VirtualButton that was pressed on the gamepad this frame. Useful for input remapping.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public VirtualButton AnyPressedButton { get; private set; }
 | 
			
		||||
 | 
			
		||||
		private Dictionary<SDL.SDL_GameControllerButton, GamepadButton> EnumToButton;
 | 
			
		||||
| 
						 | 
				
			
			@ -100,13 +113,13 @@ namespace MoonWorks.Input
 | 
			
		|||
 | 
			
		||||
			LeftXLeft = new AxisButton(LeftX, false);
 | 
			
		||||
			LeftXRight = new AxisButton(LeftX, true);
 | 
			
		||||
			LeftYUp = new AxisButton(LeftY, false);
 | 
			
		||||
			LeftYDown = new AxisButton(LeftY, true);
 | 
			
		||||
			LeftYUp = new AxisButton(LeftY, true);
 | 
			
		||||
			LeftYDown = new AxisButton(LeftY, false);
 | 
			
		||||
 | 
			
		||||
			RightXLeft = new AxisButton(RightX, false);
 | 
			
		||||
			RightXRight = new AxisButton(RightX, true);
 | 
			
		||||
			RightYUp = new AxisButton(RightY, false);
 | 
			
		||||
			RightYDown = new AxisButton(RightY, true);
 | 
			
		||||
			RightYUp = new AxisButton(RightY, true);
 | 
			
		||||
			RightYDown = new AxisButton(RightY, false);
 | 
			
		||||
 | 
			
		||||
			TriggerLeft = new Trigger(this, TriggerCode.Left, SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERLEFT);
 | 
			
		||||
			TriggerRight = new Trigger(this, TriggerCode.Right, SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERRIGHT);
 | 
			
		||||
| 
						 | 
				
			
			@ -195,6 +208,20 @@ namespace MoonWorks.Input
 | 
			
		|||
			};
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		internal void Register(IntPtr handle)
 | 
			
		||||
		{
 | 
			
		||||
			Handle = handle;
 | 
			
		||||
 | 
			
		||||
			IntPtr joystickHandle = SDL.SDL_GameControllerGetJoystick(Handle);
 | 
			
		||||
			JoystickInstanceID = SDL.SDL_JoystickInstanceID(joystickHandle);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		internal void Unregister()
 | 
			
		||||
		{
 | 
			
		||||
			Handle = IntPtr.Zero;
 | 
			
		||||
			JoystickInstanceID = -1;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		internal void Update()
 | 
			
		||||
		{
 | 
			
		||||
			AnyPressed = false;
 | 
			
		||||
| 
						 | 
				
			
			@ -253,16 +280,25 @@ namespace MoonWorks.Input
 | 
			
		|||
			) == 0;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Obtains a gamepad button object given a button code.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public GamepadButton Button(GamepadButtonCode buttonCode)
 | 
			
		||||
		{
 | 
			
		||||
			return EnumToButton[(SDL.SDL_GameControllerButton) buttonCode];
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Obtains an axis button object given a button code.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public AxisButton Button(AxisButtonCode axisButtonCode)
 | 
			
		||||
		{
 | 
			
		||||
			return AxisButtonCodeToAxisButton[axisButtonCode];
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Obtains a trigger button object given a button code.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public TriggerButton Button(TriggerCode triggerCode)
 | 
			
		||||
		{
 | 
			
		||||
			return TriggerCodeToTriggerButton[triggerCode];
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,31 +3,65 @@ using System;
 | 
			
		|||
 | 
			
		||||
namespace MoonWorks.Input
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// The main container class for all input tracking.
 | 
			
		||||
	/// Your Game class will automatically have a reference to this class.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public class Inputs
 | 
			
		||||
	{
 | 
			
		||||
		public const int MAX_GAMEPADS = 4;
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// The reference to the Keyboard input abstraction.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public Keyboard Keyboard { get; }
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// The reference to the Mouse input abstraction.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public Mouse Mouse { get; }
 | 
			
		||||
 | 
			
		||||
		Gamepad[] gamepads;
 | 
			
		||||
		Gamepad[] Gamepads;
 | 
			
		||||
 | 
			
		||||
		public static event Action<char> TextInput;
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// True if any input on any input device is active. Useful for input remapping.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public bool AnyPressed { get; private set; }
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Contains a reference to an arbitrary VirtualButton that was pressed this frame. Useful for input remapping.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public VirtualButton AnyPressedButton { get; private set; }
 | 
			
		||||
 | 
			
		||||
		public delegate void OnGamepadConnectedFunc(int slot);
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Called when a gamepad has been connected.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="slot">The slot where the connection occurred.</param>
 | 
			
		||||
		public OnGamepadConnectedFunc OnGamepadConnected = delegate { };
 | 
			
		||||
 | 
			
		||||
		public delegate void OnGamepadDisconnectedFunc(int slot);
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Called when a gamepad has been disconnected.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="slot">The slot where the disconnection occurred.</param>
 | 
			
		||||
		public OnGamepadDisconnectedFunc OnGamepadDisconnected = delegate { };
 | 
			
		||||
 | 
			
		||||
		internal Inputs()
 | 
			
		||||
		{
 | 
			
		||||
			Keyboard = new Keyboard();
 | 
			
		||||
			Mouse = new Mouse();
 | 
			
		||||
 | 
			
		||||
			gamepads = new Gamepad[MAX_GAMEPADS];
 | 
			
		||||
			Gamepads = new Gamepad[MAX_GAMEPADS];
 | 
			
		||||
 | 
			
		||||
			// initialize dummy controllers
 | 
			
		||||
			for (var slot = 0; slot < MAX_GAMEPADS; slot += 1)
 | 
			
		||||
			{
 | 
			
		||||
				gamepads[slot] = new Gamepad(IntPtr.Zero, slot);
 | 
			
		||||
				Gamepads[slot] = new Gamepad(IntPtr.Zero, slot);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -53,7 +87,7 @@ namespace MoonWorks.Input
 | 
			
		|||
				AnyPressedButton = Mouse.AnyPressedButton;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			foreach (var gamepad in gamepads)
 | 
			
		||||
			foreach (var gamepad in Gamepads)
 | 
			
		||||
			{
 | 
			
		||||
				gamepad.Update();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -65,6 +99,11 @@ namespace MoonWorks.Input
 | 
			
		|||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Returns true if a gamepad is currently connected in the given slot.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="slot">Range: 0-3</param>
 | 
			
		||||
		/// <returns></returns>
 | 
			
		||||
		public bool GamepadExists(int slot)
 | 
			
		||||
		{
 | 
			
		||||
			if (slot < 0 || slot >= MAX_GAMEPADS)
 | 
			
		||||
| 
						 | 
				
			
			@ -72,13 +111,19 @@ namespace MoonWorks.Input
 | 
			
		|||
				return false;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return !gamepads[slot].IsDummy;
 | 
			
		||||
			return !Gamepads[slot].IsDummy;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// From 0-4
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Gets a gamepad associated with the given slot.
 | 
			
		||||
		/// The first n slots are guaranteed to occupied with gamepads if they are connected.
 | 
			
		||||
		/// If a gamepad does not exist for the given slot, a dummy object with all inputs in default state will be returned.
 | 
			
		||||
		/// You can check if a gamepad is connected in a slot with the GamepadExists function.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="slot">Range: 0-3</param>
 | 
			
		||||
		public Gamepad GetGamepad(int slot)
 | 
			
		||||
		{
 | 
			
		||||
			return gamepads[slot];
 | 
			
		||||
			return Gamepads[slot];
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		internal void AddGamepad(int index)
 | 
			
		||||
| 
						 | 
				
			
			@ -87,24 +132,40 @@ namespace MoonWorks.Input
 | 
			
		|||
			{
 | 
			
		||||
				if (!GamepadExists(slot))
 | 
			
		||||
				{
 | 
			
		||||
					gamepads[slot].Handle = SDL.SDL_GameControllerOpen(index);
 | 
			
		||||
					System.Console.WriteLine($"Gamepad added to slot {slot}!");
 | 
			
		||||
					var openResult = SDL.SDL_GameControllerOpen(index);
 | 
			
		||||
					if (openResult == 0)
 | 
			
		||||
					{
 | 
			
		||||
						Logger.LogError("Error opening gamepad!");
 | 
			
		||||
						Logger.LogError(SDL.SDL_GetError());
 | 
			
		||||
					}
 | 
			
		||||
					else
 | 
			
		||||
					{
 | 
			
		||||
						Gamepads[slot].Register(openResult);
 | 
			
		||||
						Logger.LogInfo($"Gamepad added to slot {slot}!");
 | 
			
		||||
 | 
			
		||||
						if (OnGamepadConnected != null)
 | 
			
		||||
						{
 | 
			
		||||
							OnGamepadConnected(slot);
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					return;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			System.Console.WriteLine("Too many gamepads already!");
 | 
			
		||||
			Logger.LogInfo("Too many gamepads already!");
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		internal void RemoveGamepad(int joystickInstanceID)
 | 
			
		||||
		{
 | 
			
		||||
			for (int slot = 0; slot < MAX_GAMEPADS; slot += 1)
 | 
			
		||||
			{
 | 
			
		||||
				if (joystickInstanceID == gamepads[slot].JoystickInstanceID)
 | 
			
		||||
				if (joystickInstanceID == Gamepads[slot].JoystickInstanceID)
 | 
			
		||||
				{
 | 
			
		||||
					SDL.SDL_GameControllerClose(gamepads[slot].Handle);
 | 
			
		||||
					gamepads[slot].Handle = IntPtr.Zero;
 | 
			
		||||
					System.Console.WriteLine($"Removing gamepad from slot {slot}!");
 | 
			
		||||
					SDL.SDL_GameControllerClose(Gamepads[slot].Handle);
 | 
			
		||||
					Gamepads[slot].Unregister();
 | 
			
		||||
					Logger.LogInfo($"Removing gamepad from slot {slot}!");
 | 
			
		||||
					OnGamepadDisconnected(slot);
 | 
			
		||||
					return;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,9 @@
 | 
			
		|||
namespace MoonWorks.Input
 | 
			
		||||
{
 | 
			
		||||
	// Enum values are equivalent to the SDL Scancode value.
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// Can be used to determine key state without a direct reference to the virtual button object.
 | 
			
		||||
	/// Enum values are equivalent to the SDL Scancode value.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public enum KeyCode : int
 | 
			
		||||
	{
 | 
			
		||||
		Unknown = 0,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,12 +4,22 @@ using SDL2;
 | 
			
		|||
 | 
			
		||||
namespace MoonWorks.Input
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// The keyboard input device abstraction.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public class Keyboard
 | 
			
		||||
	{
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// True if any button on the keyboard is active. Useful for input remapping.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public bool AnyPressed { get; private set; }
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Contains a reference to an arbitrary KeyboardButton that was pressed this frame. Useful for input remapping.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public KeyboardButton AnyPressedButton { get; private set; }
 | 
			
		||||
 | 
			
		||||
		public IntPtr State { get; private set; }
 | 
			
		||||
		internal IntPtr State { get; private set; }
 | 
			
		||||
 | 
			
		||||
		private KeyCode[] KeyCodes;
 | 
			
		||||
		private KeyboardButton[] Keys { get; }
 | 
			
		||||
| 
						 | 
				
			
			@ -78,41 +88,65 @@ namespace MoonWorks.Input
 | 
			
		|||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// True if the button was pressed this frame.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public bool IsPressed(KeyCode keycode)
 | 
			
		||||
		{
 | 
			
		||||
			return Keys[(int) keycode].IsPressed;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// True if the button was pressed this frame and the previous frame.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public bool IsHeld(KeyCode keycode)
 | 
			
		||||
		{
 | 
			
		||||
			return Keys[(int) keycode].IsHeld;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// True if the button was either pressed or continued to be held this frame.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public bool IsDown(KeyCode keycode)
 | 
			
		||||
		{
 | 
			
		||||
			return Keys[(int) keycode].IsDown;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// True if the button was let go this frame.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public bool IsReleased(KeyCode keycode)
 | 
			
		||||
		{
 | 
			
		||||
			return Keys[(int) keycode].IsReleased;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// True if the button was not pressed this frame or the previous frame.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public bool IsIdle(KeyCode keycode)
 | 
			
		||||
		{
 | 
			
		||||
			return Keys[(int) keycode].IsIdle;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// True if the button was either idle or released this frame.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public bool IsUp(KeyCode keycode)
 | 
			
		||||
		{
 | 
			
		||||
			return Keys[(int) keycode].IsUp;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Gets a reference to a keyboard button object using a key code.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public KeyboardButton Button(KeyCode keycode)
 | 
			
		||||
		{
 | 
			
		||||
			return Keys[(int) keycode];
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Gets the state of a keyboard button from a key code.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public ButtonState ButtonState(KeyCode keycode)
 | 
			
		||||
		{
 | 
			
		||||
			return Keys[(int) keycode].State;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,6 +3,9 @@ using SDL2;
 | 
			
		|||
 | 
			
		||||
namespace MoonWorks.Input
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// The mouse input device abstraction.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public class Mouse
 | 
			
		||||
	{
 | 
			
		||||
		public MouseButton LeftButton { get; }
 | 
			
		||||
| 
						 | 
				
			
			@ -21,12 +24,23 @@ namespace MoonWorks.Input
 | 
			
		|||
		internal int WheelRaw;
 | 
			
		||||
		private int previousWheelRaw = 0;
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// True if any button on the keyboard is active. Useful for input remapping.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public bool AnyPressed { get; private set; }
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Contains a reference to an arbitrary MouseButton that was pressed this frame. Useful for input remapping.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public MouseButton AnyPressedButton { get; private set; }
 | 
			
		||||
 | 
			
		||||
		public uint ButtonMask { get; private set; }
 | 
			
		||||
		internal uint ButtonMask { get; private set; }
 | 
			
		||||
 | 
			
		||||
		private bool relativeMode;
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// If set to true, the cursor is hidden, the mouse position is constrained to the window,
 | 
			
		||||
		/// and relative mouse motion will be reported even if the mouse is at the edge of the window.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public bool RelativeMode
 | 
			
		||||
		{
 | 
			
		||||
			get => relativeMode;
 | 
			
		||||
| 
						 | 
				
			
			@ -41,9 +55,23 @@ namespace MoonWorks.Input
 | 
			
		|||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private bool hidden;
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// If set to true, the OS cursor will not be shown in your application window.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public bool Hidden
 | 
			
		||||
		{
 | 
			
		||||
			get => hidden;
 | 
			
		||||
			set
 | 
			
		||||
			{
 | 
			
		||||
				hidden = value;
 | 
			
		||||
				SDL.SDL_ShowCursor(hidden ? SDL.SDL_DISABLE : SDL.SDL_ENABLE);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private readonly Dictionary<MouseButtonCode, MouseButton> CodeToButton;
 | 
			
		||||
 | 
			
		||||
		public Mouse()
 | 
			
		||||
		internal Mouse()
 | 
			
		||||
		{
 | 
			
		||||
			LeftButton = new MouseButton(this, MouseButtonCode.Left, SDL.SDL_BUTTON_LMASK);
 | 
			
		||||
			MiddleButton = new MouseButton(this, MouseButtonCode.Middle, SDL.SDL_BUTTON_MMASK);
 | 
			
		||||
| 
						 | 
				
			
			@ -88,6 +116,17 @@ namespace MoonWorks.Input
 | 
			
		|||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Gets a button from the mouse given a MouseButtonCode.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public MouseButton Button(MouseButtonCode buttonCode)
 | 
			
		||||
		{
 | 
			
		||||
			return CodeToButton[buttonCode];
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Gets a button state from a mouse button corresponding to the given MouseButtonCode.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public ButtonState ButtonState(MouseButtonCode buttonCode)
 | 
			
		||||
		{
 | 
			
		||||
			return CodeToButton[buttonCode].State;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,8 @@
 | 
			
		|||
namespace MoonWorks.Input
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// Can be used to determine virtual mouse button state without a direct reference to the button object.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public enum MouseButtonCode
 | 
			
		||||
	{
 | 
			
		||||
		Left,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,6 +3,9 @@ using SDL2;
 | 
			
		|||
 | 
			
		||||
namespace MoonWorks.Input
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// Represents a trigger input on a gamepad.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public class Trigger
 | 
			
		||||
	{
 | 
			
		||||
		public Gamepad Parent { get; }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,9 @@
 | 
			
		|||
namespace MoonWorks.Input
 | 
			
		||||
{
 | 
			
		||||
	// Enum values correspond to SDL GameControllerAxis
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// Can be used to determine trigger state or trigger virtual button state without direct reference to the trigger object or virtual button object.
 | 
			
		||||
	/// Enum values correspond to SDL_GameControllerAxis.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public enum TriggerCode
 | 
			
		||||
	{
 | 
			
		||||
		Left = 4,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,8 @@
 | 
			
		|||
namespace MoonWorks.Input
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// VirtualButtons map inputs to binary inputs, like a trigger threshold or joystick axis threshold.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public abstract class VirtualButton
 | 
			
		||||
	{
 | 
			
		||||
		public ButtonState State { get; protected set; }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,11 +1,15 @@
 | 
			
		|||
namespace MoonWorks.Input
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// A virtual button corresponding to a direction on a joystick.
 | 
			
		||||
	/// If the axis value exceeds the threshold, it will be treated as a press.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public class AxisButton : VirtualButton
 | 
			
		||||
	{
 | 
			
		||||
		public Axis Parent { get; }
 | 
			
		||||
		public AxisButtonCode Code { get; }
 | 
			
		||||
 | 
			
		||||
		private float threshold = 0.9f;
 | 
			
		||||
		private float threshold = 0.5f;
 | 
			
		||||
		public float Threshold
 | 
			
		||||
		{
 | 
			
		||||
			get => threshold;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,8 @@
 | 
			
		|||
namespace MoonWorks.Input
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// A dummy button that can never be pressed. Used for the dummy gamepad.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public class EmptyButton : VirtualButton
 | 
			
		||||
	{
 | 
			
		||||
		internal override bool CheckPressed()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,6 +2,9 @@ using SDL2;
 | 
			
		|||
 | 
			
		||||
namespace MoonWorks.Input
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// A virtual button corresponding to a gamepad button.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public class GamepadButton : VirtualButton
 | 
			
		||||
	{
 | 
			
		||||
		public Gamepad Parent { get; }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,11 +1,12 @@
 | 
			
		|||
using System.Runtime.InteropServices;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Input
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// A virtual button corresponding to a keyboard button.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public class KeyboardButton : VirtualButton
 | 
			
		||||
	{
 | 
			
		||||
		Keyboard Parent;
 | 
			
		||||
		KeyCode KeyCode;
 | 
			
		||||
		public KeyCode KeyCode { get; }
 | 
			
		||||
 | 
			
		||||
		internal KeyboardButton(Keyboard parent, KeyCode keyCode)
 | 
			
		||||
		{
 | 
			
		||||
| 
						 | 
				
			
			@ -13,9 +14,9 @@ namespace MoonWorks.Input
 | 
			
		|||
			KeyCode = keyCode;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		internal override bool CheckPressed()
 | 
			
		||||
		internal unsafe override bool CheckPressed()
 | 
			
		||||
		{
 | 
			
		||||
			return Conversions.ByteToBool(Marshal.ReadByte(Parent.State, (int) KeyCode));
 | 
			
		||||
			return Conversions.ByteToBool(((byte*) Parent.State)[(int) KeyCode]);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,8 @@
 | 
			
		|||
namespace MoonWorks.Input
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// A virtual button corresponding to a mouse button.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public class MouseButton : VirtualButton
 | 
			
		||||
	{
 | 
			
		||||
		Mouse Parent;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,9 @@
 | 
			
		|||
namespace MoonWorks.Input
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// A virtual button corresponding to a trigger on a gamepad.
 | 
			
		||||
	/// If the trigger value exceeds the threshold, it will be treated as a press.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public class TriggerButton : VirtualButton
 | 
			
		||||
	{
 | 
			
		||||
		public Trigger Parent { get; }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,9 +5,9 @@ namespace MoonWorks
 | 
			
		|||
{
 | 
			
		||||
	public static class Logger
 | 
			
		||||
	{
 | 
			
		||||
		public static Action<string> LogInfo;
 | 
			
		||||
		public static Action<string> LogWarn;
 | 
			
		||||
		public static Action<string> LogError;
 | 
			
		||||
		public static Action<string> LogInfo = LogInfoDefault;
 | 
			
		||||
		public static Action<string> LogWarn = LogWarnDefault;
 | 
			
		||||
		public static Action<string> LogError = LogErrorDefault;
 | 
			
		||||
 | 
			
		||||
		private static RefreshCS.Refresh.Refresh_LogFunc LogInfoFunc = RefreshLogInfo;
 | 
			
		||||
		private static RefreshCS.Refresh.Refresh_LogFunc LogWarnFunc = RefreshLogWarn;
 | 
			
		||||
| 
						 | 
				
			
			@ -15,19 +15,6 @@ namespace MoonWorks
 | 
			
		|||
 | 
			
		||||
		internal static void Initialize()
 | 
			
		||||
		{
 | 
			
		||||
			if (Logger.LogInfo == null)
 | 
			
		||||
			{
 | 
			
		||||
				Logger.LogInfo = Console.WriteLine;
 | 
			
		||||
			}
 | 
			
		||||
			if (Logger.LogWarn == null)
 | 
			
		||||
			{
 | 
			
		||||
				Logger.LogWarn = Console.WriteLine;
 | 
			
		||||
			}
 | 
			
		||||
			if (Logger.LogError == null)
 | 
			
		||||
			{
 | 
			
		||||
				Logger.LogError = Console.WriteLine;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			Refresh.Refresh_HookLogFunctions(
 | 
			
		||||
				LogInfoFunc,
 | 
			
		||||
				LogWarnFunc,
 | 
			
		||||
| 
						 | 
				
			
			@ -35,6 +22,30 @@ namespace MoonWorks
 | 
			
		|||
			);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private static void LogInfoDefault(string str)
 | 
			
		||||
		{
 | 
			
		||||
			Console.ForegroundColor = ConsoleColor.Green;
 | 
			
		||||
			Console.Write("INFO: ");
 | 
			
		||||
			Console.ForegroundColor = ConsoleColor.White;
 | 
			
		||||
			Console.WriteLine(str);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private static void LogWarnDefault(string str)
 | 
			
		||||
		{
 | 
			
		||||
			Console.ForegroundColor = ConsoleColor.Yellow;
 | 
			
		||||
			Console.Write("WARN: ");
 | 
			
		||||
			Console.ForegroundColor = ConsoleColor.White;
 | 
			
		||||
			Console.WriteLine(str);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private static void LogErrorDefault(string str)
 | 
			
		||||
		{
 | 
			
		||||
			Console.ForegroundColor = ConsoleColor.Red;
 | 
			
		||||
			Console.Write("ERROR: ");
 | 
			
		||||
			Console.ForegroundColor = ConsoleColor.White;
 | 
			
		||||
			Console.WriteLine(str);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private static void RefreshLogInfo(IntPtr msg)
 | 
			
		||||
		{
 | 
			
		||||
			LogInfo(UTF8_ToManaged(msg));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
				
			
			@ -7,7 +7,7 @@ namespace MoonWorks.Math.Fixed
 | 
			
		|||
{
 | 
			
		||||
	public struct Fix64 : IEquatable<Fix64>, IComparable<Fix64>
 | 
			
		||||
	{
 | 
			
		||||
		private readonly long RawValue;
 | 
			
		||||
		public long RawValue { get; }
 | 
			
		||||
 | 
			
		||||
		const long MAX_VALUE = long.MaxValue;
 | 
			
		||||
		const long MIN_VALUE = long.MinValue;
 | 
			
		||||
| 
						 | 
				
			
			@ -17,6 +17,9 @@ namespace MoonWorks.Math.Fixed
 | 
			
		|||
		const long PI_TIMES_2 = 0x6487ED511;
 | 
			
		||||
		const long PI = 0x3243F6A88;
 | 
			
		||||
		const long PI_OVER_2 = 0x1921FB544;
 | 
			
		||||
		const long LN2 = 0xB17217F7;
 | 
			
		||||
		const long LOG2MAX = 0x1F00000000;
 | 
			
		||||
		const long LOG2MIN = -0x2000000000;
 | 
			
		||||
 | 
			
		||||
		public static readonly Fix64 MaxValue = new Fix64(MAX_VALUE);
 | 
			
		||||
		public static readonly Fix64 MinValue = new Fix64(MIN_VALUE);
 | 
			
		||||
| 
						 | 
				
			
			@ -28,6 +31,10 @@ namespace MoonWorks.Math.Fixed
 | 
			
		|||
		public static readonly Fix64 PiOver4 = PiOver2 / new Fix64(2);
 | 
			
		||||
		public static readonly Fix64 PiTimes2 = new Fix64(PI_TIMES_2);
 | 
			
		||||
 | 
			
		||||
		static readonly Fix64 Ln2 = new Fix64(LN2);
 | 
			
		||||
		static readonly Fix64 Log2Max = new Fix64(LOG2MAX);
 | 
			
		||||
		static readonly Fix64 Log2Min = new Fix64(LOG2MIN);
 | 
			
		||||
 | 
			
		||||
		const int LUT_SIZE = (int)(PI_OVER_2 >> 15);
 | 
			
		||||
		static readonly Fix64 LutInterval = (Fix64)(LUT_SIZE - 1) / PiOver2;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -52,6 +59,11 @@ namespace MoonWorks.Math.Fixed
 | 
			
		|||
			return new Fix64(numerator) / new Fix64(denominator);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static Fix64 FromRawValue(long value)
 | 
			
		||||
		{
 | 
			
		||||
			return new Fix64(value);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Gets the fractional component of this Fix64 value.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
| 
						 | 
				
			
			@ -60,30 +72,6 @@ namespace MoonWorks.Math.Fixed
 | 
			
		|||
			return new Fix64(number.RawValue & 0x00000000FFFFFFFF);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static Fix64 Random(System.Random random, int max)
 | 
			
		||||
		{
 | 
			
		||||
			return new Fix64(random.NextInt64(new Fix64(max).RawValue));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static Fix64 Random(System.Random random, Fix64 max)
 | 
			
		||||
		{
 | 
			
		||||
			return new Fix64(random.NextInt64(max.RawValue));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static Fix64 Random(System.Random random, Fix64 min, Fix64 max)
 | 
			
		||||
		{
 | 
			
		||||
			return new Fix64(random.NextInt64(min.RawValue, max.RawValue));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Max should be between 0.0 and 1.0.
 | 
			
		||||
		public static Fix64 RandomFraction(System.Random random, Fix64 max)
 | 
			
		||||
		{
 | 
			
		||||
			long fractionalPart = (max.RawValue & 0x00000000FFFFFFFF);
 | 
			
		||||
			long fractional = random.NextInt64(fractionalPart);
 | 
			
		||||
 | 
			
		||||
			return new Fix64(fractional);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Returns an int indicating the sign of a Fix64 number.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
| 
						 | 
				
			
			@ -117,27 +105,27 @@ namespace MoonWorks.Math.Fixed
 | 
			
		|||
			return new Fix64((value.RawValue + mask) ^ mask);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Returns the largest integral value less than or equal to the specified number.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Returns the largest integral value less than or equal to the specified number.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public static Fix64 Floor(Fix64 value)
 | 
			
		||||
		{
 | 
			
		||||
			// Zero out the fractional part.
 | 
			
		||||
			return new Fix64((long)((ulong)value.RawValue & 0xFFFFFFFF00000000));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Returns the smallest integral value that is greater than or equal to the specified number.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Returns the smallest integral value that is greater than or equal to the specified number.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public static Fix64 Ceiling(Fix64 value)
 | 
			
		||||
		{
 | 
			
		||||
			return value.IsFractional ? Floor(value) + One : value;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Rounds to the nearest integral value.
 | 
			
		||||
        /// If the value is halfway between an even and an uneven value, returns the even value.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Rounds to the nearest integral value.
 | 
			
		||||
		/// If the value is halfway between an even and an uneven value, returns the even value.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public static Fix64 Round(Fix64 value)
 | 
			
		||||
		{
 | 
			
		||||
			var fractionalPart = value.RawValue & 0x00000000FFFFFFFF;
 | 
			
		||||
| 
						 | 
				
			
			@ -212,7 +200,145 @@ namespace MoonWorks.Math.Fixed
 | 
			
		|||
			return Fix64.Floor(value / step) * step;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Trigonometry functions
 | 
			
		||||
		// Exponentiation functions
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Returns 2 raised to the specified power.
 | 
			
		||||
		/// Provides at least 6 decimals of accuracy.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		internal static Fix64 Pow2(Fix64 x)
 | 
			
		||||
		{
 | 
			
		||||
			if (x.RawValue == 0)
 | 
			
		||||
			{
 | 
			
		||||
				return One;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Avoid negative arguments by exploiting that exp(-x) = 1/exp(x).
 | 
			
		||||
			bool neg = x.RawValue < 0;
 | 
			
		||||
			if (neg)
 | 
			
		||||
			{
 | 
			
		||||
				x = -x;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (x == One)
 | 
			
		||||
			{
 | 
			
		||||
				return neg ? One / (Fix64)2 : (Fix64)2;
 | 
			
		||||
			}
 | 
			
		||||
			if (x >= Log2Max)
 | 
			
		||||
			{
 | 
			
		||||
				return neg ? One / MaxValue : MaxValue;
 | 
			
		||||
			}
 | 
			
		||||
			if (x <= Log2Min)
 | 
			
		||||
			{
 | 
			
		||||
				return neg ? MaxValue : Zero;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			/* The algorithm is based on the power series for exp(x):
 | 
			
		||||
			 * http://en.wikipedia.org/wiki/Exponential_function#Formal_definition
 | 
			
		||||
			 *
 | 
			
		||||
			 * From term n, we get term n+1 by multiplying with x/n.
 | 
			
		||||
			 * When the sum term drops to zero, we can stop summing.
 | 
			
		||||
			 */
 | 
			
		||||
 | 
			
		||||
			int integerPart = (int)Floor(x);
 | 
			
		||||
			// Take fractional part of exponent
 | 
			
		||||
			x = new Fix64(x.RawValue & 0x00000000FFFFFFFF);
 | 
			
		||||
 | 
			
		||||
			var result = One;
 | 
			
		||||
			var term = One;
 | 
			
		||||
			int i = 1;
 | 
			
		||||
			while (term.RawValue != 0)
 | 
			
		||||
			{
 | 
			
		||||
				term = FastMul(FastMul(x, term), Ln2) / (Fix64)i;
 | 
			
		||||
				result += term;
 | 
			
		||||
				i++;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			result = new Fix64(result.RawValue << integerPart);
 | 
			
		||||
			if (neg)
 | 
			
		||||
			{
 | 
			
		||||
				result = One / result;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return result;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Returns the base-2 logarithm of a specified number.
 | 
			
		||||
		/// Provides at least 9 decimals of accuracy.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <exception cref="ArgumentOutOfRangeException">
 | 
			
		||||
		/// The argument was non-positive
 | 
			
		||||
		/// </exception>
 | 
			
		||||
		internal static Fix64 Log2(Fix64 x)
 | 
			
		||||
		{
 | 
			
		||||
			if (x.RawValue <= 0)
 | 
			
		||||
			{
 | 
			
		||||
				throw new ArgumentOutOfRangeException("Non-positive value passed to Ln", "x");
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// This implementation is based on Clay. S. Turner's fast binary logarithm
 | 
			
		||||
			// algorithm (C. S. Turner,  "A Fast Binary Logarithm Algorithm", IEEE Signal
 | 
			
		||||
			//     Processing Mag., pp. 124,140, Sep. 2010.)
 | 
			
		||||
 | 
			
		||||
			long b = 1U << (FRACTIONAL_PLACES - 1);
 | 
			
		||||
			long y = 0;
 | 
			
		||||
 | 
			
		||||
			long rawX = x.RawValue;
 | 
			
		||||
			while (rawX < ONE)
 | 
			
		||||
			{
 | 
			
		||||
				rawX <<= 1;
 | 
			
		||||
				y -= ONE;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			while (rawX >= (ONE << 1))
 | 
			
		||||
			{
 | 
			
		||||
				rawX >>= 1;
 | 
			
		||||
				y += ONE;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			var z = new Fix64(rawX);
 | 
			
		||||
 | 
			
		||||
			for (int i = 0; i < FRACTIONAL_PLACES; i++)
 | 
			
		||||
			{
 | 
			
		||||
				z = FastMul(z, z);
 | 
			
		||||
				if (z.RawValue >= (ONE << 1))
 | 
			
		||||
				{
 | 
			
		||||
					z = new Fix64(z.RawValue >> 1);
 | 
			
		||||
					y += b;
 | 
			
		||||
				}
 | 
			
		||||
				b >>= 1;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return new Fix64(y);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static Fix64 Pow(Fix64 b, Fix64 exp)
 | 
			
		||||
		{
 | 
			
		||||
			if (b == One)
 | 
			
		||||
			{
 | 
			
		||||
				return One;
 | 
			
		||||
			}
 | 
			
		||||
			if (exp.RawValue == 0)
 | 
			
		||||
			{
 | 
			
		||||
				return One;
 | 
			
		||||
			}
 | 
			
		||||
			if (exp.RawValue == ONE)
 | 
			
		||||
			{
 | 
			
		||||
				return b;
 | 
			
		||||
			}
 | 
			
		||||
			if (b.RawValue == 0)
 | 
			
		||||
			{
 | 
			
		||||
				if (exp.RawValue < 0)
 | 
			
		||||
				{
 | 
			
		||||
					throw new DivideByZeroException();
 | 
			
		||||
				}
 | 
			
		||||
				return Zero;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			Fix64 log2 = Log2(b);
 | 
			
		||||
			return Pow2(exp * log2);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Returns the square root of the given Fix64 value.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -996,8 +996,8 @@ namespace MoonWorks.Math.Fixed
 | 
			
		|||
			z = Vector3.Normalize(forward);
 | 
			
		||||
			Vector3.Cross(ref forward, ref up, out x);
 | 
			
		||||
			Vector3.Cross(ref x, ref forward, out y);
 | 
			
		||||
			x.Normalize();
 | 
			
		||||
			y.Normalize();
 | 
			
		||||
			x = Vector3.Normalize(x);
 | 
			
		||||
			y = Vector3.Normalize(y);
 | 
			
		||||
 | 
			
		||||
			result = new Matrix4x4();
 | 
			
		||||
			result.Right = x;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -214,23 +214,6 @@ namespace MoonWorks.Math.Fixed
 | 
			
		|||
			);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Scales the quaternion magnitude to unit length.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public void Normalize()
 | 
			
		||||
		{
 | 
			
		||||
			Fix64 num = Fix64.One / (Fix64.Sqrt(
 | 
			
		||||
				(X * X) +
 | 
			
		||||
				(Y * Y) +
 | 
			
		||||
				(Z * Z) +
 | 
			
		||||
				(W * W)
 | 
			
		||||
			));
 | 
			
		||||
			this.X *= num;
 | 
			
		||||
			this.Y *= num;
 | 
			
		||||
			this.Z *= num;
 | 
			
		||||
			this.W *= num;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Returns a <see cref="String"/> representation of this <see cref="Quaternion"/> in the format:
 | 
			
		||||
		/// {X:[<see cref="X"/>] Y:[<see cref="Y"/>] Z:[<see cref="Z"/>] W:[<see cref="W"/>]}
 | 
			
		||||
| 
						 | 
				
			
			@ -759,12 +742,16 @@ namespace MoonWorks.Math.Fixed
 | 
			
		|||
		/// <param name="result">The unit length quaternion an output parameter.</param>
 | 
			
		||||
		public static void Normalize(ref Quaternion quaternion, out Quaternion result)
 | 
			
		||||
		{
 | 
			
		||||
			Fix64 num = Fix64.One / (Fix64.Sqrt(
 | 
			
		||||
				(quaternion.X * quaternion.X) +
 | 
			
		||||
				(quaternion.Y * quaternion.Y) +
 | 
			
		||||
				(quaternion.Z * quaternion.Z) +
 | 
			
		||||
				(quaternion.W * quaternion.W)
 | 
			
		||||
			));
 | 
			
		||||
			Fix64 lengthSquared = (quaternion.X * quaternion.X) + (quaternion.Y * quaternion.Y) +
 | 
			
		||||
			                      (quaternion.Z * quaternion.Z) + (quaternion.W * quaternion.W);
 | 
			
		||||
 | 
			
		||||
			if (lengthSquared == Fix64.Zero)
 | 
			
		||||
			{
 | 
			
		||||
				result = Identity;
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			Fix64 num = Fix64.One / Fix64.Sqrt(lengthSquared);
 | 
			
		||||
			result.X = quaternion.X * num;
 | 
			
		||||
			result.Y = quaternion.Y * num;
 | 
			
		||||
			result.Z = quaternion.Z * num;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,105 +0,0 @@
 | 
			
		|||
namespace MoonWorks.Math.Fixed
 | 
			
		||||
{
 | 
			
		||||
	public struct Transform2D : System.IEquatable<Transform2D>
 | 
			
		||||
	{
 | 
			
		||||
		public Vector2 Position { get; }
 | 
			
		||||
		public Fix64 Rotation { get; }
 | 
			
		||||
		public Vector2 Scale { get; }
 | 
			
		||||
 | 
			
		||||
		private bool transformMatrixCalculated;
 | 
			
		||||
		private Matrix3x2 transformMatrix;
 | 
			
		||||
 | 
			
		||||
		public Matrix3x2 TransformMatrix
 | 
			
		||||
		{
 | 
			
		||||
			get
 | 
			
		||||
			{
 | 
			
		||||
				if (!transformMatrixCalculated)
 | 
			
		||||
				{
 | 
			
		||||
					transformMatrix = CreateTransformMatrix(Position, Rotation, Scale);
 | 
			
		||||
					transformMatrixCalculated = true;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				return transformMatrix;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public bool IsAxisAligned => Rotation % Fix64.PiOver2 == Fix64.Zero;
 | 
			
		||||
		public bool IsUniformScale => Scale.X == Scale.Y;
 | 
			
		||||
 | 
			
		||||
		public static readonly Transform2D Identity = new Transform2D(Vector2.Zero, Fix64.Zero, Vector2.One);
 | 
			
		||||
 | 
			
		||||
		public Transform2D(Vector2 position)
 | 
			
		||||
		{
 | 
			
		||||
			Position = position;
 | 
			
		||||
			Rotation = Fix64.Zero;
 | 
			
		||||
			Scale = Vector2.One;
 | 
			
		||||
			transformMatrixCalculated = false;
 | 
			
		||||
			transformMatrix = Matrix3x2.Identity;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public Transform2D(Vector2 position, Fix64 rotation)
 | 
			
		||||
		{
 | 
			
		||||
			Position = position;
 | 
			
		||||
			Rotation = rotation;
 | 
			
		||||
			Scale = Vector2.One;
 | 
			
		||||
			transformMatrixCalculated = false;
 | 
			
		||||
			transformMatrix = Matrix3x2.Identity;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public Transform2D(Vector2 position, Fix64 rotation, Vector2 scale)
 | 
			
		||||
		{
 | 
			
		||||
			Position = position;
 | 
			
		||||
			Rotation = rotation;
 | 
			
		||||
			Scale = scale;
 | 
			
		||||
			transformMatrixCalculated = false;
 | 
			
		||||
			transformMatrix = Matrix3x2.Identity;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public Transform2D Compose(Transform2D other)
 | 
			
		||||
		{
 | 
			
		||||
			return new Transform2D(Position + other.Position, Rotation + other.Rotation, Scale * other.Scale);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private static Matrix3x2 CreateTransformMatrix(Vector2 position, Fix64 rotation, Vector2 scale)
 | 
			
		||||
		{
 | 
			
		||||
			return
 | 
			
		||||
				Matrix3x2.CreateScale(scale) *
 | 
			
		||||
				Matrix3x2.CreateRotation(rotation) *
 | 
			
		||||
				Matrix3x2.CreateTranslation(position);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public bool Equals(Transform2D other)
 | 
			
		||||
		{
 | 
			
		||||
			return
 | 
			
		||||
				Position == other.Position &&
 | 
			
		||||
				Rotation == other.Rotation &&
 | 
			
		||||
				Scale == other.Scale;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        public override bool Equals(System.Object other)
 | 
			
		||||
        {
 | 
			
		||||
            if (other is Transform2D otherTransform)
 | 
			
		||||
            {
 | 
			
		||||
                return Equals(otherTransform);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
		public override int GetHashCode()
 | 
			
		||||
		{
 | 
			
		||||
			return System.HashCode.Combine(Position, Rotation, Scale);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static bool operator ==(Transform2D a, Transform2D b)
 | 
			
		||||
		{
 | 
			
		||||
			return a.Equals(b);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static bool operator !=(Transform2D a, Transform2D b)
 | 
			
		||||
		{
 | 
			
		||||
			return !a.Equals(b);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -200,16 +200,6 @@ namespace MoonWorks.Math.Fixed
 | 
			
		|||
			return (X * X) + (Y * Y);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Turns this <see cref="Vector2"/> to a unit vector with the same direction.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public void Normalize()
 | 
			
		||||
		{
 | 
			
		||||
			Fix64 val = Fix64.One / Fix64.Sqrt((X * X) + (Y * Y));
 | 
			
		||||
			X *= val;
 | 
			
		||||
			Y *= val;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Turns this <see cref="Vector2"/> to an angle in radians.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
| 
						 | 
				
			
			@ -423,7 +413,14 @@ namespace MoonWorks.Math.Fixed
 | 
			
		|||
		/// <returns>Unit vector.</returns>
 | 
			
		||||
		public static Vector2 Normalize(Vector2 value)
 | 
			
		||||
		{
 | 
			
		||||
			Fix64 val = Fix64.One / Fix64.Sqrt((value.X * value.X) + (value.Y * value.Y));
 | 
			
		||||
			Fix64 lengthSquared = (value.X * value.X) + (value.Y * value.Y);
 | 
			
		||||
 | 
			
		||||
			if (lengthSquared == Fix64.Zero)
 | 
			
		||||
			{
 | 
			
		||||
				return Zero;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			Fix64 val = Fix64.One / Fix64.Sqrt(lengthSquared);
 | 
			
		||||
			value.X *= val;
 | 
			
		||||
			value.Y *= val;
 | 
			
		||||
			return value;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -309,21 +309,6 @@ namespace MoonWorks.Math.Fixed
 | 
			
		|||
			return (X * X) + (Y * Y) + (Z * Z);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Turns this <see cref="Vector3"/> to a unit vector with the same direction.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public void Normalize()
 | 
			
		||||
		{
 | 
			
		||||
			Fix64 factor = Fix64.One / Fix64.Sqrt(
 | 
			
		||||
				(X * X) +
 | 
			
		||||
				(Y * Y) +
 | 
			
		||||
				(Z * Z)
 | 
			
		||||
			);
 | 
			
		||||
			X *= factor;
 | 
			
		||||
			Y *= factor;
 | 
			
		||||
			Z *= factor;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Returns a <see cref="String"/> representation of this <see cref="Vector3"/> in the format:
 | 
			
		||||
		/// {X:[<see cref="X"/>] Y:[<see cref="Y"/>] Z:[<see cref="Z"/>]}
 | 
			
		||||
| 
						 | 
				
			
			@ -733,11 +718,14 @@ namespace MoonWorks.Math.Fixed
 | 
			
		|||
		/// <returns>Unit vector.</returns>
 | 
			
		||||
		public static Vector3 Normalize(Vector3 value)
 | 
			
		||||
		{
 | 
			
		||||
			Fix64 factor = Fix64.One / Fix64.Sqrt(
 | 
			
		||||
				(value.X * value.X) +
 | 
			
		||||
				(value.Y * value.Y) +
 | 
			
		||||
				(value.Z * value.Z)
 | 
			
		||||
			);
 | 
			
		||||
			Fix64 lengthSquared = (value.X * value.X) + (value.Y * value.Y) + (value.Z * value.Z);
 | 
			
		||||
 | 
			
		||||
			if (lengthSquared == Fix64.Zero)
 | 
			
		||||
			{
 | 
			
		||||
				return Zero;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			Fix64 factor = Fix64.One / Fix64.Sqrt(lengthSquared);
 | 
			
		||||
			return new Vector3(
 | 
			
		||||
				value.X * factor,
 | 
			
		||||
				value.Y * factor,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -611,7 +611,7 @@ namespace MoonWorks.Math.Float
 | 
			
		|||
				);
 | 
			
		||||
			}
 | 
			
		||||
			Vector3.Cross(ref cameraUpVector, ref vector, out vector3);
 | 
			
		||||
			vector3.Normalize();
 | 
			
		||||
			vector3 = Vector3.Normalize(vector3);
 | 
			
		||||
			Vector3.Cross(ref vector, ref vector3, out vector2);
 | 
			
		||||
			result.M11 = vector3.X;
 | 
			
		||||
			result.M12 = vector3.Y;
 | 
			
		||||
| 
						 | 
				
			
			@ -730,16 +730,16 @@ namespace MoonWorks.Math.Float
 | 
			
		|||
						Vector3.Forward;
 | 
			
		||||
				}
 | 
			
		||||
				Vector3.Cross(ref rotateAxis, ref vector, out vector3);
 | 
			
		||||
				vector3.Normalize();
 | 
			
		||||
				vector3 = Vector3.Normalize(vector3);
 | 
			
		||||
				Vector3.Cross(ref vector3, ref rotateAxis, out vector);
 | 
			
		||||
				vector.Normalize();
 | 
			
		||||
				vector = Vector3.Normalize(vector);
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
				Vector3.Cross(ref rotateAxis, ref vector2, out vector3);
 | 
			
		||||
				vector3.Normalize();
 | 
			
		||||
				vector3 = Vector3.Normalize(vector3);
 | 
			
		||||
				Vector3.Cross(ref vector3, ref vector4, out vector);
 | 
			
		||||
				vector.Normalize();
 | 
			
		||||
				vector = Vector3.Normalize(vector);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			result.M11 = vector3.X;
 | 
			
		||||
| 
						 | 
				
			
			@ -1701,8 +1701,8 @@ namespace MoonWorks.Math.Float
 | 
			
		|||
			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();
 | 
			
		||||
			x = Vector3.Normalize(x);
 | 
			
		||||
			y = Vector3.Normalize(y);
 | 
			
		||||
 | 
			
		||||
			result = new Matrix4x4();
 | 
			
		||||
			result.Right = x;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,105 +0,0 @@
 | 
			
		|||
namespace MoonWorks.Math.Float
 | 
			
		||||
{
 | 
			
		||||
	public struct Transform2D : System.IEquatable<Transform2D>
 | 
			
		||||
	{
 | 
			
		||||
		public Vector2 Position { get; }
 | 
			
		||||
		public float Rotation { get; }
 | 
			
		||||
		public Vector2 Scale { get; }
 | 
			
		||||
 | 
			
		||||
		private bool transformMatrixCalculated;
 | 
			
		||||
		private Matrix3x2 transformMatrix;
 | 
			
		||||
 | 
			
		||||
		public Matrix3x2 TransformMatrix
 | 
			
		||||
		{
 | 
			
		||||
			get
 | 
			
		||||
			{
 | 
			
		||||
				if (!transformMatrixCalculated)
 | 
			
		||||
				{
 | 
			
		||||
					transformMatrix = CreateTransformMatrix(Position, Rotation, Scale);
 | 
			
		||||
					transformMatrixCalculated = true;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				return transformMatrix;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public bool IsAxisAligned => Rotation % MathHelper.PiOver2 == 0;
 | 
			
		||||
		public bool IsUniformScale => Scale.X == Scale.Y;
 | 
			
		||||
 | 
			
		||||
		public static readonly Transform2D Identity = new Transform2D(Vector2.Zero, 0, Vector2.One);
 | 
			
		||||
 | 
			
		||||
		public Transform2D(Vector2 position)
 | 
			
		||||
		{
 | 
			
		||||
			Position = position;
 | 
			
		||||
			Rotation = 0;
 | 
			
		||||
			Scale = Vector2.One;
 | 
			
		||||
			transformMatrixCalculated = false;
 | 
			
		||||
			transformMatrix = Matrix3x2.Identity;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public Transform2D(Vector2 position, float rotation)
 | 
			
		||||
		{
 | 
			
		||||
			Position = position;
 | 
			
		||||
			Rotation = rotation;
 | 
			
		||||
			Scale = Vector2.One;
 | 
			
		||||
			transformMatrixCalculated = false;
 | 
			
		||||
			transformMatrix = Matrix3x2.Identity;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public Transform2D(Vector2 position, float rotation, Vector2 scale)
 | 
			
		||||
		{
 | 
			
		||||
			Position = position;
 | 
			
		||||
			Rotation = rotation;
 | 
			
		||||
			Scale = scale;
 | 
			
		||||
			transformMatrixCalculated = false;
 | 
			
		||||
			transformMatrix = Matrix3x2.Identity;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public Transform2D Compose(Transform2D other)
 | 
			
		||||
		{
 | 
			
		||||
			return new Transform2D(Position + other.Position, Rotation + other.Rotation, Scale * other.Scale);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private static Matrix3x2 CreateTransformMatrix(Vector2 position, float rotation, Vector2 scale)
 | 
			
		||||
		{
 | 
			
		||||
			return
 | 
			
		||||
				Matrix3x2.CreateScale(scale) *
 | 
			
		||||
				Matrix3x2.CreateRotation(rotation) *
 | 
			
		||||
				Matrix3x2.CreateTranslation(position);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public bool Equals(Transform2D other)
 | 
			
		||||
		{
 | 
			
		||||
			return
 | 
			
		||||
				Position == other.Position &&
 | 
			
		||||
				Rotation == other.Rotation &&
 | 
			
		||||
				Scale == other.Scale;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        public override bool Equals(System.Object other)
 | 
			
		||||
        {
 | 
			
		||||
            if (other is Transform2D otherTransform)
 | 
			
		||||
            {
 | 
			
		||||
                return Equals(otherTransform);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
		public override int GetHashCode()
 | 
			
		||||
		{
 | 
			
		||||
			return System.HashCode.Combine(Position, Rotation, Scale);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static bool operator ==(Transform2D a, Transform2D b)
 | 
			
		||||
		{
 | 
			
		||||
			return a.Equals(b);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static bool operator !=(Transform2D a, Transform2D b)
 | 
			
		||||
		{
 | 
			
		||||
			return !a.Equals(b);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -194,16 +194,6 @@ namespace MoonWorks.Math.Float
 | 
			
		|||
			return (X * X) + (Y * Y);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Turns this <see cref="Vector2"/> to a unit vector with the same direction.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public void Normalize()
 | 
			
		||||
		{
 | 
			
		||||
			float val = 1.0f / (float) System.Math.Sqrt((X * X) + (Y * Y));
 | 
			
		||||
			X *= val;
 | 
			
		||||
			Y *= val;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Turns this <see cref="Vector2"/> to an angle in radians.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
| 
						 | 
				
			
			@ -717,7 +707,14 @@ namespace MoonWorks.Math.Float
 | 
			
		|||
		/// <returns>Unit vector.</returns>
 | 
			
		||||
		public static Vector2 Normalize(Vector2 value)
 | 
			
		||||
		{
 | 
			
		||||
			float val = 1.0f / (float) System.Math.Sqrt((value.X * value.X) + (value.Y * value.Y));
 | 
			
		||||
			float lengthSquared = (value.X * value.X) + (value.Y * value.Y);
 | 
			
		||||
 | 
			
		||||
			if (lengthSquared == 0)
 | 
			
		||||
			{
 | 
			
		||||
				return Zero;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			float val = 1.0f / System.MathF.Sqrt(lengthSquared);
 | 
			
		||||
			value.X *= val;
 | 
			
		||||
			value.Y *= val;
 | 
			
		||||
			return value;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -302,21 +302,6 @@ namespace MoonWorks.Math.Float
 | 
			
		|||
			return (X * X) + (Y * Y) + (Z * Z);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Turns this <see cref="Vector3"/> to a unit vector with the same direction.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public void Normalize()
 | 
			
		||||
		{
 | 
			
		||||
			float factor = 1.0f / (float) System.Math.Sqrt(
 | 
			
		||||
				(X * X) +
 | 
			
		||||
				(Y * Y) +
 | 
			
		||||
				(Z * Z)
 | 
			
		||||
			);
 | 
			
		||||
			X *= factor;
 | 
			
		||||
			Y *= factor;
 | 
			
		||||
			Z *= factor;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Returns a <see cref="String"/> representation of this <see cref="Vector3"/> in the format:
 | 
			
		||||
		/// {X:[<see cref="X"/>] Y:[<see cref="Y"/>] Z:[<see cref="Z"/>]}
 | 
			
		||||
| 
						 | 
				
			
			@ -900,11 +885,14 @@ namespace MoonWorks.Math.Float
 | 
			
		|||
		/// <returns>Unit vector.</returns>
 | 
			
		||||
		public static Vector3 Normalize(Vector3 value)
 | 
			
		||||
		{
 | 
			
		||||
			float factor = 1.0f / (float) System.Math.Sqrt(
 | 
			
		||||
				(value.X * value.X) +
 | 
			
		||||
				(value.Y * value.Y) +
 | 
			
		||||
				(value.Z * value.Z)
 | 
			
		||||
			);
 | 
			
		||||
			float lengthSquared = (value.X * value.X) + (value.Y * value.Y) + (value.Z * value.Z);
 | 
			
		||||
 | 
			
		||||
			if (lengthSquared == 0f)
 | 
			
		||||
			{
 | 
			
		||||
				return Zero;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			float factor = 1.0f / System.MathF.Sqrt(lengthSquared);
 | 
			
		||||
			return new Vector3(
 | 
			
		||||
				value.X * factor,
 | 
			
		||||
				value.Y * factor,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -267,23 +267,6 @@ namespace MoonWorks.Math.Float
 | 
			
		|||
			return (X * X) + (Y * Y) + (Z * Z) + (W * W);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Turns this <see cref="Vector4"/> to a unit vector with the same direction.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public void Normalize()
 | 
			
		||||
		{
 | 
			
		||||
			float factor = 1.0f / (float) System.Math.Sqrt(
 | 
			
		||||
				(X * X) +
 | 
			
		||||
				(Y * Y) +
 | 
			
		||||
				(Z * Z) +
 | 
			
		||||
				(W * W)
 | 
			
		||||
			);
 | 
			
		||||
			X *= factor;
 | 
			
		||||
			Y *= factor;
 | 
			
		||||
			Z *= factor;
 | 
			
		||||
			W *= factor;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public override string ToString()
 | 
			
		||||
		{
 | 
			
		||||
			return (
 | 
			
		||||
| 
						 | 
				
			
			@ -853,12 +836,15 @@ namespace MoonWorks.Math.Float
 | 
			
		|||
		/// <returns>Unit vector.</returns>
 | 
			
		||||
		public static Vector4 Normalize(Vector4 vector)
 | 
			
		||||
		{
 | 
			
		||||
			float factor = 1.0f / (float) System.Math.Sqrt(
 | 
			
		||||
				(vector.X * vector.X) +
 | 
			
		||||
				(vector.Y * vector.Y) +
 | 
			
		||||
				(vector.Z * vector.Z) +
 | 
			
		||||
				(vector.W * vector.W)
 | 
			
		||||
			);
 | 
			
		||||
			var lengthSquared = (vector.X * vector.X) + (vector.Y * vector.Y) +
 | 
			
		||||
			                        (vector.Z * vector.Z) + (vector.W * vector.W);
 | 
			
		||||
 | 
			
		||||
			if (lengthSquared == 0)
 | 
			
		||||
			{
 | 
			
		||||
				return Zero;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			float factor = 1.0f / System.MathF.Sqrt(lengthSquared);
 | 
			
		||||
			return new Vector4(
 | 
			
		||||
				vector.X * factor,
 | 
			
		||||
				vector.Y * factor,
 | 
			
		||||
| 
						 | 
				
			
			@ -870,16 +856,20 @@ namespace MoonWorks.Math.Float
 | 
			
		|||
		/// <summary>
 | 
			
		||||
		/// Creates a new <see cref="Vector4"/> that contains a normalized values from another vector.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="value">Source <see cref="Vector4"/>.</param>
 | 
			
		||||
		/// <param name="vector">Source <see cref="Vector4"/>.</param>
 | 
			
		||||
		/// <param name="result">Unit vector as an output parameter.</param>
 | 
			
		||||
		public static void Normalize(ref Vector4 vector, out Vector4 result)
 | 
			
		||||
		{
 | 
			
		||||
			float factor = 1.0f / (float) System.Math.Sqrt(
 | 
			
		||||
				(vector.X * vector.X) +
 | 
			
		||||
				(vector.Y * vector.Y) +
 | 
			
		||||
				(vector.Z * vector.Z) +
 | 
			
		||||
				(vector.W * vector.W)
 | 
			
		||||
			);
 | 
			
		||||
			float lengthSquared = (vector.X * vector.X) + (vector.Y * vector.Y) +
 | 
			
		||||
			                      (vector.Z * vector.Z) + (vector.W * vector.W);
 | 
			
		||||
 | 
			
		||||
			if (lengthSquared == 0)
 | 
			
		||||
			{
 | 
			
		||||
				result = Zero;
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			float factor = 1.0f / System.MathF.Sqrt(lengthSquared);
 | 
			
		||||
			result.X = vector.X * factor;
 | 
			
		||||
			result.Y = vector.Y * factor;
 | 
			
		||||
			result.Z = vector.Z * factor;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -160,6 +160,26 @@ namespace MoonWorks.Math
 | 
			
		|||
			return value;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Restricts a value to be within a specified range.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="value">The value to clamp.</param>
 | 
			
		||||
		/// <param name="min">
 | 
			
		||||
		/// The minimum value. If <c>value</c> is less than <c>min</c>, <c>min</c>
 | 
			
		||||
		/// will be returned.
 | 
			
		||||
		/// </param>
 | 
			
		||||
		/// <param name="max">
 | 
			
		||||
		/// The maximum value. If <c>value</c> is greater than <c>max</c>, <c>max</c>
 | 
			
		||||
		/// will be returned.
 | 
			
		||||
		/// </param>
 | 
			
		||||
		/// <returns>The clamped value.</returns>
 | 
			
		||||
		public static int Clamp(int value, int min, int max)
 | 
			
		||||
		{
 | 
			
		||||
			value = (value > max) ? max : value;
 | 
			
		||||
			value = (value < min) ? min : value;
 | 
			
		||||
			return value;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Calculates the absolute value of the difference of two values.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
| 
						 | 
				
			
			@ -282,7 +302,12 @@ namespace MoonWorks.Math
 | 
			
		|||
 | 
			
		||||
		public static float Quantize(float value, float step)
 | 
			
		||||
		{
 | 
			
		||||
			return (float) System.Math.Floor(value / step) * step;
 | 
			
		||||
			return System.MathF.Round(value / step) * step;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static Fixed.Fix64 Quantize(Fixed.Fix64 value, Fixed.Fix64 step)
 | 
			
		||||
		{
 | 
			
		||||
			return Fixed.Fix64.Round(value / step) * step;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
| 
						 | 
				
			
			@ -395,27 +420,6 @@ namespace MoonWorks.Math
 | 
			
		|||
 | 
			
		||||
		#region Internal Static Methods
 | 
			
		||||
 | 
			
		||||
		// FIXME: This could be an extension! ClampIntEXT? -flibit
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Restricts a value to be within a specified range.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="value">The value to clamp.</param>
 | 
			
		||||
		/// <param name="min">
 | 
			
		||||
		/// The minimum value. If <c>value</c> is less than <c>min</c>, <c>min</c>
 | 
			
		||||
		/// will be returned.
 | 
			
		||||
		/// </param>
 | 
			
		||||
		/// <param name="max">
 | 
			
		||||
		/// The maximum value. If <c>value</c> is greater than <c>max</c>, <c>max</c>
 | 
			
		||||
		/// will be returned.
 | 
			
		||||
		/// </param>
 | 
			
		||||
		/// <returns>The clamped value.</returns>
 | 
			
		||||
		internal static int Clamp(int value, int min, int max)
 | 
			
		||||
		{
 | 
			
		||||
			value = (value > max) ? max : value;
 | 
			
		||||
			value = (value < min) ? min : value;
 | 
			
		||||
			return value;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		internal static bool WithinEpsilon(float floatA, float floatB)
 | 
			
		||||
		{
 | 
			
		||||
			return System.Math.Abs(floatA - floatB) < MachineEpsilonFloat;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -97,16 +97,12 @@ namespace MoonWorks
 | 
			
		|||
 | 
			
		||||
			// Get the path to the assembly
 | 
			
		||||
			Assembly assembly = Assembly.GetExecutingAssembly();
 | 
			
		||||
			string assemblyPath = "";
 | 
			
		||||
			if (assembly.Location != null)
 | 
			
		||||
			{
 | 
			
		||||
				assemblyPath = Path.GetDirectoryName(assembly.Location);
 | 
			
		||||
			}
 | 
			
		||||
			string assemblyPath = System.AppContext.BaseDirectory;
 | 
			
		||||
 | 
			
		||||
			// Locate the config file
 | 
			
		||||
			string xmlPath = Path.Combine(
 | 
			
		||||
				assemblyPath,
 | 
			
		||||
				assembly.GetName().Name + ".dll.config"
 | 
			
		||||
				"MoonWorks.dll.config"
 | 
			
		||||
			);
 | 
			
		||||
			if (!File.Exists(xmlPath))
 | 
			
		||||
			{
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,45 +0,0 @@
 | 
			
		|||
using System;
 | 
			
		||||
using MoonWorks.Audio;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Video
 | 
			
		||||
{
 | 
			
		||||
	public unsafe class StreamingSoundTheora : StreamingSound
 | 
			
		||||
	{
 | 
			
		||||
		private IntPtr VideoHandle;
 | 
			
		||||
		protected override int BUFFER_SIZE => 8192;
 | 
			
		||||
 | 
			
		||||
		internal StreamingSoundTheora(
 | 
			
		||||
			AudioDevice device,
 | 
			
		||||
			IntPtr videoHandle,
 | 
			
		||||
			int channels,
 | 
			
		||||
			uint sampleRate
 | 
			
		||||
		) : base(
 | 
			
		||||
			device,
 | 
			
		||||
			3, /* float type */
 | 
			
		||||
			32, /* size of float */
 | 
			
		||||
			(ushort) (4 * channels),
 | 
			
		||||
			(ushort) channels,
 | 
			
		||||
			sampleRate
 | 
			
		||||
		) {
 | 
			
		||||
			VideoHandle = videoHandle;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		protected override unsafe void FillBuffer(
 | 
			
		||||
			void* buffer,
 | 
			
		||||
			int bufferLengthInBytes,
 | 
			
		||||
			out int filledLengthInBytes,
 | 
			
		||||
			out bool reachedEnd
 | 
			
		||||
		) {
 | 
			
		||||
			var lengthInFloats = bufferLengthInBytes / sizeof(float);
 | 
			
		||||
 | 
			
		||||
			int samples = Theorafile.tf_readaudio(
 | 
			
		||||
				VideoHandle,
 | 
			
		||||
				(IntPtr) buffer,
 | 
			
		||||
				lengthInFloats
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
			filledLengthInBytes = samples * sizeof(float);
 | 
			
		||||
			reachedEnd = Theorafile.tf_eos(VideoHandle) == 1;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,120 +0,0 @@
 | 
			
		|||
/* Heavily based on https://github.com/FNA-XNA/FNA/blob/master/src/Media/Xiph/VideoPlayer.cs */
 | 
			
		||||
using System;
 | 
			
		||||
using System.Runtime.InteropServices;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Video
 | 
			
		||||
{
 | 
			
		||||
	public enum VideoState
 | 
			
		||||
	{
 | 
			
		||||
		Playing,
 | 
			
		||||
		Paused,
 | 
			
		||||
		Stopped
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public unsafe class Video : IDisposable
 | 
			
		||||
	{
 | 
			
		||||
		internal IntPtr Handle;
 | 
			
		||||
		private IntPtr rwData;
 | 
			
		||||
		private void* videoData;
 | 
			
		||||
 | 
			
		||||
		public double FramesPerSecond => fps;
 | 
			
		||||
		public int Width => yWidth;
 | 
			
		||||
		public int Height => yHeight;
 | 
			
		||||
		public int UVWidth { get; }
 | 
			
		||||
		public int UVHeight { get; }
 | 
			
		||||
 | 
			
		||||
		private double fps;
 | 
			
		||||
		private int yWidth;
 | 
			
		||||
		private int yHeight;
 | 
			
		||||
 | 
			
		||||
		private bool disposed;
 | 
			
		||||
 | 
			
		||||
		public Video(string filename)
 | 
			
		||||
		{
 | 
			
		||||
			if (!System.IO.File.Exists(filename))
 | 
			
		||||
			{
 | 
			
		||||
				throw new ArgumentException("Video file not found!");
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			var bytes = System.IO.File.ReadAllBytes(filename);
 | 
			
		||||
			videoData = NativeMemory.Alloc((nuint) bytes.Length);
 | 
			
		||||
			Marshal.Copy(bytes, 0, (IntPtr) videoData, bytes.Length);
 | 
			
		||||
			rwData = SDL2.SDL.SDL_RWFromMem((IntPtr) videoData, bytes.Length);
 | 
			
		||||
 | 
			
		||||
			if (Theorafile.tf_open_callbacks(rwData, out Handle, callbacks) < 0)
 | 
			
		||||
			{
 | 
			
		||||
				throw new ArgumentException("Invalid video file!");
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			Theorafile.th_pixel_fmt format;
 | 
			
		||||
			Theorafile.tf_videoinfo(
 | 
			
		||||
				Handle,
 | 
			
		||||
				out yWidth,
 | 
			
		||||
				out yHeight,
 | 
			
		||||
				out fps,
 | 
			
		||||
				out format
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
			if (format == Theorafile.th_pixel_fmt.TH_PF_420)
 | 
			
		||||
			{
 | 
			
		||||
				UVWidth = Width / 2;
 | 
			
		||||
				UVHeight = Height / 2;
 | 
			
		||||
			}
 | 
			
		||||
			else if (format == Theorafile.th_pixel_fmt.TH_PF_422)
 | 
			
		||||
			{
 | 
			
		||||
				UVWidth = Width / 2;
 | 
			
		||||
				UVHeight = Height;
 | 
			
		||||
			}
 | 
			
		||||
			else if (format == Theorafile.th_pixel_fmt.TH_PF_444)
 | 
			
		||||
			{
 | 
			
		||||
				UVWidth = Width;
 | 
			
		||||
				UVHeight = Height;
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
				throw new NotSupportedException("Unrecognized YUV format!");
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private static IntPtr Read(IntPtr ptr, IntPtr size, IntPtr nmemb, IntPtr datasource) => (IntPtr) SDL2.SDL.SDL_RWread(datasource, ptr, size, nmemb);
 | 
			
		||||
		private static int Seek(IntPtr datasource, long offset, Theorafile.SeekWhence whence) => (int) SDL2.SDL.SDL_RWseek(datasource, offset, (int) whence);
 | 
			
		||||
		private static int Close(IntPtr datasource) => (int) SDL2.SDL.SDL_RWclose(datasource);
 | 
			
		||||
 | 
			
		||||
		private static Theorafile.tf_callbacks callbacks = new Theorafile.tf_callbacks
 | 
			
		||||
		{
 | 
			
		||||
			read_func = Read,
 | 
			
		||||
			seek_func = Seek,
 | 
			
		||||
			close_func = Close
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		protected virtual void Dispose(bool disposing)
 | 
			
		||||
		{
 | 
			
		||||
			if (!disposed)
 | 
			
		||||
			{
 | 
			
		||||
				if (disposing)
 | 
			
		||||
				{
 | 
			
		||||
					// dispose managed state (managed objects)
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// free unmanaged resources (unmanaged objects)
 | 
			
		||||
				Theorafile.tf_close(ref Handle);
 | 
			
		||||
				NativeMemory.Free(videoData);
 | 
			
		||||
 | 
			
		||||
				disposed = true;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		~Video()
 | 
			
		||||
		{
 | 
			
		||||
		    // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
 | 
			
		||||
		    Dispose(disposing: false);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public void Dispose()
 | 
			
		||||
		{
 | 
			
		||||
			// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
 | 
			
		||||
			Dispose(disposing: true);
 | 
			
		||||
			GC.SuppressFinalize(this);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,89 @@
 | 
			
		|||
using System;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using MoonWorks.Graphics;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Video
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// This class takes in a filename for AV1 data in .obu (open bitstream unit) format
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public unsafe class VideoAV1 : GraphicsResource
 | 
			
		||||
	{
 | 
			
		||||
		public string Filename { get; }
 | 
			
		||||
 | 
			
		||||
		// "double buffering" so we can loop without a stutter
 | 
			
		||||
		internal VideoAV1Stream StreamA { get; }
 | 
			
		||||
		internal VideoAV1Stream StreamB { get; }
 | 
			
		||||
 | 
			
		||||
		public int Width => width;
 | 
			
		||||
		public int Height => height;
 | 
			
		||||
		public double FramesPerSecond { get; set; }
 | 
			
		||||
		public Dav1dfile.PixelLayout PixelLayout => pixelLayout;
 | 
			
		||||
		public int UVWidth { get; }
 | 
			
		||||
		public int UVHeight { get; }
 | 
			
		||||
 | 
			
		||||
		private int width;
 | 
			
		||||
		private int height;
 | 
			
		||||
		private Dav1dfile.PixelLayout pixelLayout;
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Opens an AV1 file so it can be loaded by VideoPlayer. You must also provide a playback framerate.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public VideoAV1(GraphicsDevice device, string filename, double framesPerSecond) : base(device)
 | 
			
		||||
		{
 | 
			
		||||
			if (!File.Exists(filename))
 | 
			
		||||
			{
 | 
			
		||||
				throw new ArgumentException("Video file not found!");
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (Dav1dfile.df_fopen(filename, out var handle) == 0)
 | 
			
		||||
			{
 | 
			
		||||
				throw new Exception("Failed to open video file!");
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			Dav1dfile.df_videoinfo(handle, out width, out height, out pixelLayout);
 | 
			
		||||
			Dav1dfile.df_close(handle);
 | 
			
		||||
 | 
			
		||||
			if (pixelLayout == Dav1dfile.PixelLayout.I420)
 | 
			
		||||
			{
 | 
			
		||||
				UVWidth = Width / 2;
 | 
			
		||||
				UVHeight = Height / 2;
 | 
			
		||||
			}
 | 
			
		||||
			else if (pixelLayout == Dav1dfile.PixelLayout.I422)
 | 
			
		||||
			{
 | 
			
		||||
				UVWidth = Width / 2;
 | 
			
		||||
				UVHeight = Height;
 | 
			
		||||
			}
 | 
			
		||||
			else if (pixelLayout == Dav1dfile.PixelLayout.I444)
 | 
			
		||||
			{
 | 
			
		||||
				UVWidth = width;
 | 
			
		||||
				UVHeight = height;
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
				throw new NotSupportedException("Unrecognized YUV format!");
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			FramesPerSecond = framesPerSecond;
 | 
			
		||||
 | 
			
		||||
			Filename = filename;
 | 
			
		||||
 | 
			
		||||
			StreamA = new VideoAV1Stream(device, this);
 | 
			
		||||
			StreamB = new VideoAV1Stream(device, this);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// NOTE: if you call this while a VideoPlayer is playing the stream, your program will explode
 | 
			
		||||
		protected override void Dispose(bool disposing)
 | 
			
		||||
		{
 | 
			
		||||
			if (!IsDisposed)
 | 
			
		||||
			{
 | 
			
		||||
				if (disposing)
 | 
			
		||||
				{
 | 
			
		||||
					StreamA.Dispose();
 | 
			
		||||
					StreamB.Dispose();
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			base.Dispose(disposing);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,82 @@
 | 
			
		|||
using System;
 | 
			
		||||
using MoonWorks.Graphics;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Video
 | 
			
		||||
{
 | 
			
		||||
	internal class VideoAV1Stream : GraphicsResource
 | 
			
		||||
	{
 | 
			
		||||
		public IntPtr Handle => handle;
 | 
			
		||||
		IntPtr handle;
 | 
			
		||||
 | 
			
		||||
		public bool Ended => Dav1dfile.df_eos(Handle) == 1;
 | 
			
		||||
 | 
			
		||||
		public IntPtr yDataHandle;
 | 
			
		||||
		public IntPtr uDataHandle;
 | 
			
		||||
		public IntPtr vDataHandle;
 | 
			
		||||
		public uint yDataLength;
 | 
			
		||||
		public uint uvDataLength;
 | 
			
		||||
		public uint yStride;
 | 
			
		||||
		public uint uvStride;
 | 
			
		||||
 | 
			
		||||
		public bool FrameDataUpdated { get; set; }
 | 
			
		||||
 | 
			
		||||
		public VideoAV1Stream(GraphicsDevice device, VideoAV1 video) : base(device)
 | 
			
		||||
		{
 | 
			
		||||
			if (Dav1dfile.df_fopen(video.Filename, out handle) == 0)
 | 
			
		||||
			{
 | 
			
		||||
				throw new Exception("Failed to open video file!");
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			Reset();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public void Reset()
 | 
			
		||||
		{
 | 
			
		||||
			lock (this)
 | 
			
		||||
			{
 | 
			
		||||
				Dav1dfile.df_reset(Handle);
 | 
			
		||||
				ReadNextFrame();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public void ReadNextFrame()
 | 
			
		||||
		{
 | 
			
		||||
			lock (this)
 | 
			
		||||
			{
 | 
			
		||||
				if (!Ended)
 | 
			
		||||
				{
 | 
			
		||||
					if (Dav1dfile.df_readvideo(
 | 
			
		||||
						Handle,
 | 
			
		||||
						1,
 | 
			
		||||
						out var yDataHandle,
 | 
			
		||||
						out var uDataHandle,
 | 
			
		||||
						out var vDataHandle,
 | 
			
		||||
						out var yDataLength,
 | 
			
		||||
						out var uvDataLength,
 | 
			
		||||
						out var yStride,
 | 
			
		||||
						out var uvStride) == 1
 | 
			
		||||
					) {
 | 
			
		||||
						this.yDataHandle = yDataHandle;
 | 
			
		||||
						this.uDataHandle = uDataHandle;
 | 
			
		||||
						this.vDataHandle = vDataHandle;
 | 
			
		||||
						this.yDataLength = yDataLength;
 | 
			
		||||
						this.uvDataLength = uvDataLength;
 | 
			
		||||
						this.yStride = yStride;
 | 
			
		||||
						this.uvStride = uvStride;
 | 
			
		||||
 | 
			
		||||
						FrameDataUpdated = true;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		protected override void Dispose(bool disposing)
 | 
			
		||||
		{
 | 
			
		||||
			if (!IsDisposed)
 | 
			
		||||
			{
 | 
			
		||||
				Dav1dfile.df_close(Handle);
 | 
			
		||||
			}
 | 
			
		||||
			base.Dispose(disposing);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,30 +1,26 @@
 | 
			
		|||
using System;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Diagnostics;
 | 
			
		||||
using System.Runtime.InteropServices;
 | 
			
		||||
using MoonWorks.Audio;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using MoonWorks.Graphics;
 | 
			
		||||
 | 
			
		||||
namespace MoonWorks.Video
 | 
			
		||||
{
 | 
			
		||||
	public unsafe class VideoPlayer : IDisposable
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// A structure for continuous decoding of AV1 videos and rendering them into a texture.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public unsafe class VideoPlayer : GraphicsResource
 | 
			
		||||
	{
 | 
			
		||||
		public Texture RenderTexture { get; private set; } = null;
 | 
			
		||||
		public VideoState State { get; private set; } = VideoState.Stopped;
 | 
			
		||||
		public bool Loop { get; set; }
 | 
			
		||||
		public float Volume {
 | 
			
		||||
			get => volume;
 | 
			
		||||
			set
 | 
			
		||||
			{
 | 
			
		||||
				volume = value;
 | 
			
		||||
				if (audioStream != null)
 | 
			
		||||
				{
 | 
			
		||||
					audioStream.Volume = value;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		public float PlaybackSpeed { get; set; } = 1;
 | 
			
		||||
 | 
			
		||||
		private Video Video = null;
 | 
			
		||||
		private VideoAV1 Video = null;
 | 
			
		||||
		private VideoAV1Stream CurrentStream = null;
 | 
			
		||||
 | 
			
		||||
		private Task ReadNextFrameTask;
 | 
			
		||||
		private Task ResetStreamATask;
 | 
			
		||||
		private Task ResetStreamBTask;
 | 
			
		||||
 | 
			
		||||
		private GraphicsDevice GraphicsDevice;
 | 
			
		||||
		private Texture yTexture = null;
 | 
			
		||||
| 
						 | 
				
			
			@ -32,36 +28,26 @@ namespace MoonWorks.Video
 | 
			
		|||
		private Texture vTexture = null;
 | 
			
		||||
		private Sampler LinearSampler;
 | 
			
		||||
 | 
			
		||||
		private void* yuvData = null;
 | 
			
		||||
		private int yuvDataLength = 0;
 | 
			
		||||
 | 
			
		||||
		private int currentFrame;
 | 
			
		||||
 | 
			
		||||
		private AudioDevice AudioDevice;
 | 
			
		||||
		private StreamingSoundTheora audioStream = null;
 | 
			
		||||
		private float volume = 1.0f;
 | 
			
		||||
 | 
			
		||||
		private Stopwatch timer;
 | 
			
		||||
		private double lastTimestamp;
 | 
			
		||||
		private double timeElapsed;
 | 
			
		||||
 | 
			
		||||
		private bool disposed;
 | 
			
		||||
 | 
			
		||||
		public VideoPlayer(GraphicsDevice graphicsDevice, AudioDevice audioDevice)
 | 
			
		||||
		public VideoPlayer(GraphicsDevice device) : base(device)
 | 
			
		||||
		{
 | 
			
		||||
			GraphicsDevice = graphicsDevice;
 | 
			
		||||
			if (GraphicsDevice.VideoPipeline == null)
 | 
			
		||||
			{
 | 
			
		||||
				throw new InvalidOperationException("Missing video shaders!");
 | 
			
		||||
			}
 | 
			
		||||
			GraphicsDevice = device;
 | 
			
		||||
 | 
			
		||||
			AudioDevice = audioDevice;
 | 
			
		||||
			LinearSampler = new Sampler(graphicsDevice, SamplerCreateInfo.LinearClamp);
 | 
			
		||||
			LinearSampler = new Sampler(device, SamplerCreateInfo.LinearClamp);
 | 
			
		||||
 | 
			
		||||
			timer = new Stopwatch();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public void Load(Video video)
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Prepares a VideoAV1 for decoding and rendering.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="video"></param>
 | 
			
		||||
		public void Load(VideoAV1 video)
 | 
			
		||||
		{
 | 
			
		||||
			if (Video != video)
 | 
			
		||||
			{
 | 
			
		||||
| 
						 | 
				
			
			@ -111,25 +97,19 @@ namespace MoonWorks.Video
 | 
			
		|||
					vTexture = CreateSubTexture(GraphicsDevice, video.UVWidth, video.UVHeight);
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				var newDataLength = (
 | 
			
		||||
					(video.Width * video.Height) +
 | 
			
		||||
					(video.UVWidth * video.UVHeight * 2)
 | 
			
		||||
				);
 | 
			
		||||
 | 
			
		||||
				if (newDataLength != yuvDataLength)
 | 
			
		||||
				{
 | 
			
		||||
					yuvData = NativeMemory.Realloc(yuvData, (nuint) newDataLength);
 | 
			
		||||
					yuvDataLength = newDataLength;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				Video = video;
 | 
			
		||||
 | 
			
		||||
				InitializeTheoraStream();
 | 
			
		||||
				InitializeDav1dStream();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Starts playing back and decoding the loaded video.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public void Play()
 | 
			
		||||
		{
 | 
			
		||||
			if (Video == null) { return; }
 | 
			
		||||
 | 
			
		||||
			if (State == VideoState.Playing)
 | 
			
		||||
			{
 | 
			
		||||
				return;
 | 
			
		||||
| 
						 | 
				
			
			@ -137,16 +117,16 @@ namespace MoonWorks.Video
 | 
			
		|||
 | 
			
		||||
			timer.Start();
 | 
			
		||||
 | 
			
		||||
			if (audioStream != null)
 | 
			
		||||
			{
 | 
			
		||||
				audioStream.Play();
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			State = VideoState.Playing;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Pauses playback and decoding of the currently playing video.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public void Pause()
 | 
			
		||||
		{
 | 
			
		||||
			if (Video == null) { return; }
 | 
			
		||||
 | 
			
		||||
			if (State != VideoState.Playing)
 | 
			
		||||
			{
 | 
			
		||||
				return;
 | 
			
		||||
| 
						 | 
				
			
			@ -154,16 +134,16 @@ namespace MoonWorks.Video
 | 
			
		|||
 | 
			
		||||
			timer.Stop();
 | 
			
		||||
 | 
			
		||||
			if (audioStream != null)
 | 
			
		||||
			{
 | 
			
		||||
				audioStream.Pause();
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			State = VideoState.Paused;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Stops and resets decoding of the currently playing video.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public void Stop()
 | 
			
		||||
		{
 | 
			
		||||
			if (Video == null) { return; }
 | 
			
		||||
 | 
			
		||||
			if (State == VideoState.Stopped)
 | 
			
		||||
			{
 | 
			
		||||
				return;
 | 
			
		||||
| 
						 | 
				
			
			@ -172,20 +152,28 @@ namespace MoonWorks.Video
 | 
			
		|||
			timer.Stop();
 | 
			
		||||
			timer.Reset();
 | 
			
		||||
 | 
			
		||||
			Theorafile.tf_reset(Video.Handle);
 | 
			
		||||
			lastTimestamp = 0;
 | 
			
		||||
			timeElapsed = 0;
 | 
			
		||||
 | 
			
		||||
			if (audioStream != null)
 | 
			
		||||
			{
 | 
			
		||||
				audioStream.StopImmediate();
 | 
			
		||||
				audioStream.Dispose();
 | 
			
		||||
				audioStream = null;
 | 
			
		||||
			}
 | 
			
		||||
			InitializeDav1dStream();
 | 
			
		||||
 | 
			
		||||
			State = VideoState.Stopped;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Unloads the currently playing video.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public void Unload()
 | 
			
		||||
		{
 | 
			
		||||
			Stop();
 | 
			
		||||
			ResetStreamATask?.Wait();
 | 
			
		||||
			ResetStreamBTask?.Wait();
 | 
			
		||||
			Video = null;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Renders the video data into RenderTexture.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public void Render()
 | 
			
		||||
		{
 | 
			
		||||
			if (Video == null || State == VideoState.Stopped)
 | 
			
		||||
| 
						 | 
				
			
			@ -199,37 +187,39 @@ namespace MoonWorks.Video
 | 
			
		|||
			int thisFrame = ((int) (timeElapsed / (1000.0 / Video.FramesPerSecond)));
 | 
			
		||||
			if (thisFrame > currentFrame)
 | 
			
		||||
			{
 | 
			
		||||
				if (Theorafile.tf_readvideo(
 | 
			
		||||
					Video.Handle,
 | 
			
		||||
					(IntPtr) yuvData,
 | 
			
		||||
					thisFrame - currentFrame
 | 
			
		||||
				) == 1 || currentFrame == -1) {
 | 
			
		||||
				if (CurrentStream.FrameDataUpdated)
 | 
			
		||||
				{
 | 
			
		||||
					UpdateRenderTexture();
 | 
			
		||||
					CurrentStream.FrameDataUpdated = false;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				currentFrame = thisFrame;
 | 
			
		||||
				ReadNextFrameTask = Task.Run(CurrentStream.ReadNextFrame);
 | 
			
		||||
				ReadNextFrameTask.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			bool ended = Theorafile.tf_eos(Video.Handle) == 1;
 | 
			
		||||
			if (ended)
 | 
			
		||||
			if (CurrentStream.Ended)
 | 
			
		||||
			{
 | 
			
		||||
				timer.Stop();
 | 
			
		||||
				timer.Reset();
 | 
			
		||||
 | 
			
		||||
				if (audioStream != null)
 | 
			
		||||
				{
 | 
			
		||||
					audioStream.Stop();
 | 
			
		||||
					audioStream.Dispose();
 | 
			
		||||
					audioStream = null;
 | 
			
		||||
				}
 | 
			
		||||
				var task = Task.Run(CurrentStream.Reset);
 | 
			
		||||
				task.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
 | 
			
		||||
 | 
			
		||||
				Theorafile.tf_reset(Video.Handle);
 | 
			
		||||
				if (CurrentStream == Video.StreamA)
 | 
			
		||||
				{
 | 
			
		||||
					ResetStreamATask = task;
 | 
			
		||||
				}
 | 
			
		||||
				else
 | 
			
		||||
				{
 | 
			
		||||
					ResetStreamBTask = task;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if (Loop)
 | 
			
		||||
				{
 | 
			
		||||
					// Start over!
 | 
			
		||||
					InitializeTheoraStream();
 | 
			
		||||
 | 
			
		||||
					// Start over on the next stream!
 | 
			
		||||
					CurrentStream = (CurrentStream == Video.StreamA) ? Video.StreamB : Video.StreamA;
 | 
			
		||||
					currentFrame = -1;
 | 
			
		||||
					timer.Start();
 | 
			
		||||
				}
 | 
			
		||||
				else
 | 
			
		||||
| 
						 | 
				
			
			@ -241,32 +231,40 @@ namespace MoonWorks.Video
 | 
			
		|||
 | 
			
		||||
		private void UpdateRenderTexture()
 | 
			
		||||
		{
 | 
			
		||||
			var commandBuffer = GraphicsDevice.AcquireCommandBuffer();
 | 
			
		||||
			lock (CurrentStream)
 | 
			
		||||
			{
 | 
			
		||||
				var commandBuffer = GraphicsDevice.AcquireCommandBuffer();
 | 
			
		||||
 | 
			
		||||
			commandBuffer.SetTextureDataYUV(
 | 
			
		||||
				yTexture,
 | 
			
		||||
				uTexture,
 | 
			
		||||
				vTexture,
 | 
			
		||||
				(IntPtr) yuvData,
 | 
			
		||||
				(uint) yuvDataLength
 | 
			
		||||
			);
 | 
			
		||||
				commandBuffer.SetTextureDataYUV(
 | 
			
		||||
					yTexture,
 | 
			
		||||
					uTexture,
 | 
			
		||||
					vTexture,
 | 
			
		||||
					CurrentStream.yDataHandle,
 | 
			
		||||
					CurrentStream.uDataHandle,
 | 
			
		||||
					CurrentStream.vDataHandle,
 | 
			
		||||
					CurrentStream.yDataLength,
 | 
			
		||||
					CurrentStream.uvDataLength,
 | 
			
		||||
					CurrentStream.yStride,
 | 
			
		||||
					CurrentStream.uvStride
 | 
			
		||||
				);
 | 
			
		||||
 | 
			
		||||
			commandBuffer.BeginRenderPass(
 | 
			
		||||
				new ColorAttachmentInfo(RenderTexture, Color.Black)
 | 
			
		||||
			);
 | 
			
		||||
				commandBuffer.BeginRenderPass(
 | 
			
		||||
					new ColorAttachmentInfo(RenderTexture, Color.Black)
 | 
			
		||||
				);
 | 
			
		||||
 | 
			
		||||
			commandBuffer.BindGraphicsPipeline(GraphicsDevice.VideoPipeline);
 | 
			
		||||
			commandBuffer.BindFragmentSamplers(
 | 
			
		||||
				new TextureSamplerBinding(yTexture, LinearSampler),
 | 
			
		||||
				new TextureSamplerBinding(uTexture, LinearSampler),
 | 
			
		||||
				new TextureSamplerBinding(vTexture, LinearSampler)
 | 
			
		||||
			);
 | 
			
		||||
				commandBuffer.BindGraphicsPipeline(GraphicsDevice.VideoPipeline);
 | 
			
		||||
				commandBuffer.BindFragmentSamplers(
 | 
			
		||||
					new TextureSamplerBinding(yTexture, LinearSampler),
 | 
			
		||||
					new TextureSamplerBinding(uTexture, LinearSampler),
 | 
			
		||||
					new TextureSamplerBinding(vTexture, LinearSampler)
 | 
			
		||||
				);
 | 
			
		||||
 | 
			
		||||
			commandBuffer.DrawPrimitives(0, 1, 0, 0);
 | 
			
		||||
				commandBuffer.DrawPrimitives(0, 1, 0, 0);
 | 
			
		||||
 | 
			
		||||
			commandBuffer.EndRenderPass();
 | 
			
		||||
				commandBuffer.EndRenderPass();
 | 
			
		||||
 | 
			
		||||
			GraphicsDevice.Submit(commandBuffer);
 | 
			
		||||
				GraphicsDevice.Submit(commandBuffer);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private static Texture CreateRenderTexture(GraphicsDevice graphicsDevice, int width, int height)
 | 
			
		||||
| 
						 | 
				
			
			@ -291,53 +289,42 @@ namespace MoonWorks.Video
 | 
			
		|||
			);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private void InitializeTheoraStream()
 | 
			
		||||
		private void InitializeDav1dStream()
 | 
			
		||||
		{
 | 
			
		||||
			// Grab the first video frame ASAP.
 | 
			
		||||
			while (Theorafile.tf_readvideo(Video.Handle, (IntPtr) yuvData, 1) == 0);
 | 
			
		||||
			ReadNextFrameTask?.Wait();
 | 
			
		||||
 | 
			
		||||
			// Grab the first bit of audio. We're trying to start the decoding ASAP.
 | 
			
		||||
			if (AudioDevice != null && Theorafile.tf_hasaudio(Video.Handle) == 1)
 | 
			
		||||
			{
 | 
			
		||||
				int channels, sampleRate;
 | 
			
		||||
				Theorafile.tf_audioinfo(Video.Handle, out channels, out sampleRate);
 | 
			
		||||
				audioStream = new StreamingSoundTheora(AudioDevice, Video.Handle, channels, (uint) sampleRate);
 | 
			
		||||
			}
 | 
			
		||||
			ResetStreamATask = Task.Run(Video.StreamA.Reset);
 | 
			
		||||
			ResetStreamATask.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
 | 
			
		||||
			ResetStreamBTask = Task.Run(Video.StreamB.Reset);
 | 
			
		||||
			ResetStreamBTask.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
 | 
			
		||||
 | 
			
		||||
			CurrentStream = Video.StreamA;
 | 
			
		||||
			currentFrame = -1;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		protected virtual void Dispose(bool disposing)
 | 
			
		||||
		private static void HandleTaskException(Task task)
 | 
			
		||||
		{
 | 
			
		||||
			if (!disposed)
 | 
			
		||||
			if (task.Exception.InnerException is not TaskCanceledException)
 | 
			
		||||
			{
 | 
			
		||||
				if (disposing)
 | 
			
		||||
				{
 | 
			
		||||
					// dispose managed state (managed objects)
 | 
			
		||||
					RenderTexture.Dispose();
 | 
			
		||||
					yTexture.Dispose();
 | 
			
		||||
					uTexture.Dispose();
 | 
			
		||||
					vTexture.Dispose();
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// free unmanaged resources (unmanaged objects) and override finalizer
 | 
			
		||||
				NativeMemory.Free(yuvData);
 | 
			
		||||
 | 
			
		||||
				disposed = true;
 | 
			
		||||
				throw task.Exception;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		~VideoPlayer()
 | 
			
		||||
		protected override void Dispose(bool disposing)
 | 
			
		||||
		{
 | 
			
		||||
		    // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
 | 
			
		||||
		    Dispose(disposing: false);
 | 
			
		||||
		}
 | 
			
		||||
			if (!IsDisposed)
 | 
			
		||||
			{
 | 
			
		||||
				if (disposing)
 | 
			
		||||
				{
 | 
			
		||||
					Unload();
 | 
			
		||||
 | 
			
		||||
		public void Dispose()
 | 
			
		||||
		{
 | 
			
		||||
			// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
 | 
			
		||||
			Dispose(disposing: true);
 | 
			
		||||
			GC.SuppressFinalize(this);
 | 
			
		||||
					RenderTexture?.Dispose();
 | 
			
		||||
					yTexture?.Dispose();
 | 
			
		||||
					uTexture?.Dispose();
 | 
			
		||||
					vTexture?.Dispose();
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			base.Dispose(disposing);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,9 @@
 | 
			
		|||
namespace MoonWorks.Video
 | 
			
		||||
{
 | 
			
		||||
	public enum VideoState
 | 
			
		||||
	{
 | 
			
		||||
		Playing,
 | 
			
		||||
		Paused,
 | 
			
		||||
		Stopped
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -5,13 +5,18 @@ using SDL2;
 | 
			
		|||
 | 
			
		||||
namespace MoonWorks
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// Represents a window in the client operating system. <br/>
 | 
			
		||||
	/// Every Game has a MainWindow automatically. <br/>
 | 
			
		||||
	/// You can create additional Windows if you desire. They must be Claimed by the GraphicsDevice to be rendered to.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public class Window : IDisposable
 | 
			
		||||
	{
 | 
			
		||||
		internal IntPtr Handle { get; }
 | 
			
		||||
		public ScreenMode ScreenMode { get; private set; }
 | 
			
		||||
		public uint Width { get; private set; }
 | 
			
		||||
		public uint Height { get; private set; }
 | 
			
		||||
		internal Texture SwapchainTexture { get; set; } = null;
 | 
			
		||||
		internal Texture SwapchainTexture { get; set; }
 | 
			
		||||
 | 
			
		||||
		public bool Claimed { get; internal set; }
 | 
			
		||||
		public MoonWorks.Graphics.TextureFormat SwapchainFormat { get; internal set; }
 | 
			
		||||
| 
						 | 
				
			
			@ -45,21 +50,28 @@ namespace MoonWorks
 | 
			
		|||
 | 
			
		||||
			ScreenMode = windowCreateInfo.ScreenMode;
 | 
			
		||||
 | 
			
		||||
			SDL.SDL_GetDesktopDisplayMode(0, out var displayMode);
 | 
			
		||||
 | 
			
		||||
			Handle = SDL.SDL_CreateWindow(
 | 
			
		||||
				windowCreateInfo.WindowTitle,
 | 
			
		||||
				SDL.SDL_WINDOWPOS_UNDEFINED,
 | 
			
		||||
				SDL.SDL_WINDOWPOS_UNDEFINED,
 | 
			
		||||
				(int) windowCreateInfo.WindowWidth,
 | 
			
		||||
				(int) windowCreateInfo.WindowHeight,
 | 
			
		||||
				SDL.SDL_WINDOWPOS_CENTERED,
 | 
			
		||||
				SDL.SDL_WINDOWPOS_CENTERED,
 | 
			
		||||
				windowCreateInfo.ScreenMode == ScreenMode.Windowed ? (int) windowCreateInfo.WindowWidth : displayMode.w,
 | 
			
		||||
				windowCreateInfo.ScreenMode == ScreenMode.Windowed ? (int) windowCreateInfo.WindowHeight : displayMode.h,
 | 
			
		||||
				flags
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
			Width = windowCreateInfo.WindowWidth;
 | 
			
		||||
			Height = windowCreateInfo.WindowHeight;
 | 
			
		||||
			/* Requested size might be different in fullscreen, so let's just get the area */
 | 
			
		||||
			SDL.SDL_GetWindowSize(Handle, out var width, out var height);
 | 
			
		||||
			Width = (uint) width;
 | 
			
		||||
			Height = (uint) height;
 | 
			
		||||
 | 
			
		||||
			idLookup.Add(SDL.SDL_GetWindowID(Handle), this);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Changes the ScreenMode of this window.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public void SetScreenMode(ScreenMode screenMode)
 | 
			
		||||
		{
 | 
			
		||||
			SDL.SDL_WindowFlags windowFlag = 0;
 | 
			
		||||
| 
						 | 
				
			
			@ -73,13 +85,18 @@ namespace MoonWorks
 | 
			
		|||
				windowFlag = SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			ScreenMode = screenMode;
 | 
			
		||||
 | 
			
		||||
			SDL.SDL_SetWindowFullscreen(Handle, (uint) windowFlag);
 | 
			
		||||
 | 
			
		||||
			if (screenMode == ScreenMode.Windowed)
 | 
			
		||||
			{
 | 
			
		||||
				SDL.SDL_SetWindowPosition(Handle, SDL.SDL_WINDOWPOS_CENTERED, SDL.SDL_WINDOWPOS_CENTERED);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			ScreenMode = screenMode;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Resizes the window.
 | 
			
		||||
		/// Resizes the window. <br/>
 | 
			
		||||
		/// Note that you are responsible for recreating any graphics resources that need to change as a result of the size change.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="width"></param>
 | 
			
		||||
| 
						 | 
				
			
			@ -89,6 +106,11 @@ namespace MoonWorks
 | 
			
		|||
			SDL.SDL_SetWindowSize(Handle, (int) width, (int) height);
 | 
			
		||||
			Width = width;
 | 
			
		||||
			Height = height;
 | 
			
		||||
 | 
			
		||||
			if (ScreenMode == ScreenMode.Windowed)
 | 
			
		||||
			{
 | 
			
		||||
				SDL.SDL_SetWindowPosition(Handle, SDL.SDL_WINDOWPOS_CENTERED, SDL.SDL_WINDOWPOS_CENTERED);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		internal static Window Lookup(uint windowID)
 | 
			
		||||
| 
						 | 
				
			
			@ -112,6 +134,9 @@ namespace MoonWorks
 | 
			
		|||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// You can specify a method to run when the window size changes.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public void RegisterSizeChangeCallback(System.Action<uint, uint> sizeChangeCallback)
 | 
			
		||||
		{
 | 
			
		||||
			SizeChangeCallback = sizeChangeCallback;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,13 +1,37 @@
 | 
			
		|||
namespace MoonWorks
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// All the information required for window creation.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public struct WindowCreateInfo
 | 
			
		||||
	{
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// The name of the window that will be displayed in the operating system.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public string WindowTitle;
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// The width of the window.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public uint WindowWidth;
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// The height of the window.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public uint WindowHeight;
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Specifies if the window will be created in windowed mode or a fullscreen mode.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public ScreenMode ScreenMode;
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Specifies the presentation mode for the window. Roughly equivalent to V-Sync.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public PresentMode PresentMode;
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Whether the window can be resized using the operating system's window dragging feature.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public bool SystemResizable;
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Specifies if the window will open at the maximum desktop resolution.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public bool StartMaximized;
 | 
			
		||||
 | 
			
		||||
		public WindowCreateInfo(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue