Compare commits
1 Commits
Author | SHA1 | Date |
---|---|---|
cosmonaut | 9833c608d8 |
|
@ -10,6 +10,6 @@
|
||||||
[submodule "lib/WellspringCS"]
|
[submodule "lib/WellspringCS"]
|
||||||
path = lib/WellspringCS
|
path = lib/WellspringCS
|
||||||
url = https://gitea.moonside.games/MoonsideGames/WellspringCS.git
|
url = https://gitea.moonside.games/MoonsideGames/WellspringCS.git
|
||||||
[submodule "lib/dav1dfile"]
|
[submodule "lib/Theorafile"]
|
||||||
path = lib/dav1dfile
|
path = lib/Theorafile
|
||||||
url = https://github.com/MoonsideGames/dav1dfile.git
|
url = https://github.com/FNA-XNA/Theorafile.git
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<Platforms>x64</Platforms>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<LangVersion>11</LangVersion>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
@ -14,29 +14,22 @@
|
||||||
<Compile Include="lib\FAudio\csharp\FAudio.cs" />
|
<Compile Include="lib\FAudio\csharp\FAudio.cs" />
|
||||||
<Compile Include="lib\RefreshCS\src\Refresh.cs" />
|
<Compile Include="lib\RefreshCS\src\Refresh.cs" />
|
||||||
<Compile Include="lib\SDL2-CS\src\SDL2.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\WellspringCS\WellspringCS.cs" />
|
||||||
<Compile Include="lib\dav1dfile\csharp\dav1dfile.cs" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="MoonWorks.dll.config">
|
<None Include="MoonWorks.dll.config">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
|
|
||||||
</None>
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource Include="src\Graphics\StockShaders\Binary\video_fullscreen.vert.refresh">
|
<EmbeddedResource Include="src\Video\Shaders\Compiled\FullscreenVert.spv">
|
||||||
<LogicalName>MoonWorks.Graphics.StockShaders.VideoFullscreen.vert.refresh</LogicalName>
|
<LogicalName>MoonWorks.Shaders.FullscreenVert.spv</LogicalName>
|
||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
<EmbeddedResource Include="src\Graphics\StockShaders\Binary\video_yuv2rgba.frag.refresh">
|
<EmbeddedResource Include="src\Video\Shaders\Compiled\YUV2RGBAFrag.spv">
|
||||||
<LogicalName>MoonWorks.Graphics.StockShaders.VideoYUV2RGBA.frag.refresh</LogicalName>
|
<LogicalName>MoonWorks.Shaders.YUV2RGBAFrag.spv</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>
|
</EmbeddedResource>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -5,18 +5,18 @@
|
||||||
<dllmap dll="SDL2" os="linux,freebsd,netbsd" target="libSDL2-2.0.so.0"/>
|
<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="windows" target="Refresh.dll"/>
|
||||||
<dllmap dll="Refresh" os="osx" target="libRefresh.1.dylib"/>
|
<dllmap dll="Refresh" os="osx" target="libRefresh.0.dylib"/>
|
||||||
<dllmap dll="Refresh" os="linux,freebsd,netbsd" target="libRefresh.so.1"/>
|
<dllmap dll="Refresh" os="linux,freebsd,netbsd" target="libRefresh.so.0"/>
|
||||||
|
|
||||||
<dllmap dll="FAudio" os="windows" target="FAudio.dll"/>
|
<dllmap dll="FAudio" os="windows" target="FAudio.dll"/>
|
||||||
<dllmap dll="FAudio" os="osx" target="libFAudio.0.dylib"/>
|
<dllmap dll="FAudio" os="osx" target="libFAudio.0.dylib"/>
|
||||||
<dllmap dll="FAudio" os="linux,freebsd,netbsd" target="libFAudio.so.0"/>
|
<dllmap dll="FAudio" os="linux,freebsd,netbsd" target="libFAudio.so.0"/>
|
||||||
|
|
||||||
<dllmap dll="Wellspring" os="windows" target="Wellspring.dll"/>
|
<dllmap dll="Wellspring" os="windows" target="Wellspring.dll"/>
|
||||||
<dllmap dll="Wellspring" os="osx" target="libWellspring.1.dylib"/>
|
<dllmap dll="Wellspring" os="osx" target="libWellspring.0.dylib"/>
|
||||||
<dllmap dll="Wellspring" os="linux,freebsd,netbsd" target="libWellspring.so.1"/>
|
<dllmap dll="Wellspring" os="linux,freebsd,netbsd" target="libWellspring.so.0"/>
|
||||||
|
|
||||||
<dllmap dll="dav1dfile" os="windows" target="dav1dfile.dll"/>
|
<dllmap dll="Theorafile" os="windows" target="libtheorafile.dll"/>
|
||||||
<dllmap dll="dav1dfile" os="osx" target="libdav1dfile.1.dylib"/>
|
<dllmap dll="Theorafile" os="osx" target="libtheorafile.dylib"/>
|
||||||
<dllmap dll="dav1dfile" os="linux,freebsd,netbsd,openbsd" target="libdav1dfile.so.1"/>
|
<dllmap dll="Theorafile" os="linux,freebsd,netbsd" target="libtheorafile.so"/>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|
|
@ -12,13 +12,9 @@ MoonWorks uses strictly Free Open Source Software. It will never have any kind o
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
API Reference: https://moonside.games/docs/moonworksapi/
|
|
||||||
|
|
||||||
High-level documentation is provided here: https://moonside.games/docs/moonworks/
|
High-level documentation is provided here: https://moonside.games/docs/moonworks/
|
||||||
|
|
||||||
The source is documented in doc comments that your preferred IDE can read.
|
For an actual API reference, the source is documented in doc comments that your preferred IDE can read.
|
||||||
|
|
||||||
Join our Discord! https://discord.gg/ujhwdkHmhN
|
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
|
|
||||||
|
@ -26,7 +22,7 @@ Join our Discord! https://discord.gg/ujhwdkHmhN
|
||||||
* [Refresh](https://gitea.moonside.games/MoonsideGames/Refresh) - Graphics
|
* [Refresh](https://gitea.moonside.games/MoonsideGames/Refresh) - Graphics
|
||||||
* [FAudio](https://github.com/FNA-XNA/FAudio) - Audio
|
* [FAudio](https://github.com/FNA-XNA/FAudio) - Audio
|
||||||
* [Wellspring](https://gitea.moonside.games/MoonsideGames/Wellspring) - Font Rendering
|
* [Wellspring](https://gitea.moonside.games/MoonsideGames/Wellspring) - Font Rendering
|
||||||
* [dav1dfile](https://github.com/MoonsideGames/dav1dfile) - Compressed Video
|
* [Theorafile](https://github.com/FNA-XNA/Theorafile) - Compressed Video
|
||||||
|
|
||||||
Prebuilt dependencies can be obtained here: https://moonside.games/files/moonlibs.tar.bz2
|
Prebuilt dependencies can be obtained here: https://moonside.games/files/moonlibs.tar.bz2
|
||||||
|
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 60480416bda930bf7544e6abe31b937f0daa0256
|
Subproject commit f16c2b6168e525391d349265a26ea4018590056f
|
|
@ -1 +1 @@
|
||||||
Subproject commit b5325e6d0329eeb35b074091a569a5f679852d28
|
Subproject commit 356f8e9ec2a6118b75e32d2a2ed7dbf4297aba78
|
|
@ -1 +1 @@
|
||||||
Subproject commit e4afbb848586fca530b6538320f799f81a18b941
|
Subproject commit b72c0c57128fafb6fc5aa4f258a41d928f98a438
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 6f5166c9006910a1c4c2b516b8c03bb6efa82d53
|
|
@ -1 +1 @@
|
||||||
Subproject commit 074f2afc833b221906bb2468735041ce78f2cb89
|
Subproject commit f8872bae59e394b0f8a35224bb39ab8fd041af97
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit 5065e2cd4662dbe023b77a45ef967f975170dfff
|
|
|
@ -1,79 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,147 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,164 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,49 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,100 +0,0 @@
|
||||||
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,53 +1,30 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
namespace MoonWorks.Audio
|
namespace MoonWorks.Audio
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// AudioDevice manages all audio-related concerns.
|
|
||||||
/// </summary>
|
|
||||||
public class AudioDevice : IDisposable
|
public class AudioDevice : IDisposable
|
||||||
{
|
{
|
||||||
public IntPtr Handle { get; }
|
public IntPtr Handle { get; }
|
||||||
public byte[] Handle3D { get; }
|
public byte[] Handle3D { get; }
|
||||||
|
public IntPtr MasteringVoice { get; }
|
||||||
public FAudio.FAudioDeviceDetails DeviceDetails { 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 CurveDistanceScalar = 1f;
|
||||||
public float DopplerScale = 1f;
|
public float DopplerScale = 1f;
|
||||||
public float SpeedOfSound = 343.5f;
|
public float SpeedOfSound = 343.5f;
|
||||||
|
|
||||||
private readonly HashSet<GCHandle> resourceHandles = new HashSet<GCHandle>();
|
internal FAudio.FAudioVoiceSends ReverbSends;
|
||||||
private readonly HashSet<UpdatingSourceVoice> updatingSourceVoices = new HashSet<UpdatingSourceVoice>();
|
|
||||||
|
|
||||||
private AudioTweenManager AudioTweenManager;
|
private readonly List<WeakReference<AudioResource>> resources = new List<WeakReference<AudioResource>>();
|
||||||
|
private readonly List<WeakReference<StreamingSound>> streamingSounds = new List<WeakReference<StreamingSound>>();
|
||||||
|
|
||||||
private SourceVoicePool VoicePool;
|
private bool IsDisposed;
|
||||||
private List<SourceVoice> VoicesToReturn = new List<SourceVoice>();
|
|
||||||
|
|
||||||
private const int Step = 200;
|
public unsafe AudioDevice()
|
||||||
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()
|
|
||||||
{
|
{
|
||||||
UpdateInterval = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / Step);
|
|
||||||
|
|
||||||
FAudio.FAudioCreate(out var handle, 0, FAudio.FAUDIO_DEFAULT_PROCESSOR);
|
FAudio.FAudioCreate(out var handle, 0, FAudio.FAUDIO_DEFAULT_PROCESSOR);
|
||||||
Handle = handle;
|
Handle = handle;
|
||||||
|
|
||||||
|
@ -58,8 +35,8 @@ namespace MoonWorks.Audio
|
||||||
if (devices == 0)
|
if (devices == 0)
|
||||||
{
|
{
|
||||||
Logger.LogError("No audio devices found!");
|
Logger.LogError("No audio devices found!");
|
||||||
FAudio.FAudio_Release(Handle);
|
|
||||||
Handle = IntPtr.Zero;
|
Handle = IntPtr.Zero;
|
||||||
|
FAudio.FAudio_Release(Handle);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,24 +69,25 @@ namespace MoonWorks.Audio
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Init Mastering Voice */
|
/* Init Mastering Voice */
|
||||||
var result = FAudio.FAudio_CreateMasteringVoice(
|
IntPtr masteringVoice;
|
||||||
|
|
||||||
|
if (FAudio.FAudio_CreateMasteringVoice(
|
||||||
Handle,
|
Handle,
|
||||||
out trueMasteringVoice,
|
out masteringVoice,
|
||||||
FAudio.FAUDIO_DEFAULT_CHANNELS,
|
FAudio.FAUDIO_DEFAULT_CHANNELS,
|
||||||
FAudio.FAUDIO_DEFAULT_SAMPLERATE,
|
FAudio.FAUDIO_DEFAULT_SAMPLERATE,
|
||||||
0,
|
0,
|
||||||
i,
|
i,
|
||||||
IntPtr.Zero
|
IntPtr.Zero
|
||||||
);
|
) != 0)
|
||||||
|
|
||||||
if (result != 0)
|
|
||||||
{
|
{
|
||||||
Logger.LogError("Failed to create a mastering voice!");
|
Logger.LogError("No mastering voice found!");
|
||||||
Logger.LogError("Audio device creation failed!");
|
Handle = IntPtr.Zero;
|
||||||
|
FAudio.FAudio_Release(Handle);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
fauxMasteringVoice = SubmixVoice.CreateFauxMasteringVoice(this);
|
MasteringVoice = masteringVoice;
|
||||||
|
|
||||||
/* Init 3D Audio */
|
/* Init 3D Audio */
|
||||||
|
|
||||||
|
@ -120,169 +98,144 @@ namespace MoonWorks.Audio
|
||||||
Handle3D
|
Handle3D
|
||||||
);
|
);
|
||||||
|
|
||||||
AudioTweenManager = new AudioTweenManager();
|
/* Init reverb */
|
||||||
VoicePool = new SourceVoicePool(this);
|
|
||||||
|
|
||||||
WakeSignal = new AutoResetEvent(true);
|
IntPtr reverbVoice;
|
||||||
|
|
||||||
Thread = new Thread(ThreadMain);
|
IntPtr reverb;
|
||||||
Thread.IsBackground = true;
|
FAudio.FAudioCreateReverb(out reverb, 0);
|
||||||
Thread.Start();
|
|
||||||
|
|
||||||
Running = true;
|
IntPtr chainPtr;
|
||||||
|
chainPtr = Marshal.AllocHGlobal(
|
||||||
|
sizeof(FAudio.FAudioEffectChain)
|
||||||
|
);
|
||||||
|
|
||||||
TickStopwatch.Start();
|
FAudio.FAudioEffectChain* reverbChain = (FAudio.FAudioEffectChain*) chainPtr;
|
||||||
previousTickTime = 0;
|
reverbChain->EffectCount = 1;
|
||||||
|
reverbChain->pEffectDescriptors = Marshal.AllocHGlobal(
|
||||||
|
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(
|
||||||
|
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) sizeof(FAudio.FAudioFXReverbParameters),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
Marshal.FreeHGlobal(reverbParamsPtr);
|
||||||
|
|
||||||
|
/* Init reverb sends */
|
||||||
|
|
||||||
|
ReverbSends = new FAudio.FAudioVoiceSends
|
||||||
|
{
|
||||||
|
SendCount = 2,
|
||||||
|
pSends = Marshal.AllocHGlobal(
|
||||||
|
2 * 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ThreadMain()
|
public void SetMasteringVolume(float volume)
|
||||||
{
|
{
|
||||||
while (Running)
|
FAudio.FAudioVoice_SetVolume(MasteringVoice, volume, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Update()
|
||||||
|
{
|
||||||
|
for (var i = streamingSounds.Count - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
lock (StateLock)
|
var weakReference = streamingSounds[i];
|
||||||
|
if (weakReference.TryGetTarget(out var streamingSound))
|
||||||
{
|
{
|
||||||
try
|
streamingSound.Update();
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
updatingSourceVoices.Remove(updatingSourceVoice);
|
streamingSounds.RemoveAt(i);
|
||||||
}
|
|
||||||
|
|
||||||
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 RemoveResourceReference(GCHandle resourceReference)
|
internal void AddDynamicSoundInstance(StreamingSound instance)
|
||||||
{
|
{
|
||||||
lock (StateLock)
|
streamingSounds.Add(new WeakReference<StreamingSound>(instance));
|
||||||
{
|
}
|
||||||
resourceHandles.Remove(resourceReference);
|
|
||||||
|
|
||||||
if (resourceReference.Target is UpdatingSourceVoice updatableVoice)
|
internal void AddResourceReference(WeakReference<AudioResource> resourceReference)
|
||||||
{
|
{
|
||||||
updatingSourceVoices.Remove(updatableVoice);
|
lock (resources)
|
||||||
}
|
{
|
||||||
|
resources.Add(resourceReference);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void RemoveResourceReference(WeakReference<AudioResource> resourceReference)
|
||||||
|
{
|
||||||
|
lock (resources)
|
||||||
|
{
|
||||||
|
resources.Remove(resourceReference);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -290,54 +243,29 @@ namespace MoonWorks.Audio
|
||||||
{
|
{
|
||||||
if (!IsDisposed)
|
if (!IsDisposed)
|
||||||
{
|
{
|
||||||
Running = false;
|
|
||||||
|
|
||||||
if (disposing)
|
if (disposing)
|
||||||
{
|
{
|
||||||
Thread.Join();
|
for (var i = resources.Count - 1; i >= 0; i--)
|
||||||
|
|
||||||
// dispose all source voices first
|
|
||||||
foreach (var handle in resourceHandles)
|
|
||||||
{
|
{
|
||||||
if (handle.Target is SourceVoice voice)
|
var weakReference = resources[i];
|
||||||
{
|
|
||||||
voice.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// dispose all submix voices except the faux mastering voice
|
if (weakReference.TryGetTarget(out var resource))
|
||||||
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();
|
resource.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
resources.Clear();
|
||||||
resourceHandles.Clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FAudio.FAudioVoice_DestroyVoice(ReverbVoice);
|
||||||
|
FAudio.FAudioVoice_DestroyVoice(MasteringVoice);
|
||||||
FAudio.FAudio_Release(Handle);
|
FAudio.FAudio_Release(Handle);
|
||||||
|
|
||||||
IsDisposed = true;
|
IsDisposed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
|
||||||
~AudioDevice()
|
~AudioDevice()
|
||||||
{
|
{
|
||||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||||
|
|
|
@ -4,9 +4,6 @@ using MoonWorks.Math.Float;
|
||||||
|
|
||||||
namespace MoonWorks.Audio
|
namespace MoonWorks.Audio
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// An emitter for 3D spatial audio.
|
|
||||||
/// </summary>
|
|
||||||
public class AudioEmitter : AudioResource
|
public class AudioEmitter : AudioResource
|
||||||
{
|
{
|
||||||
internal FAudio.F3DAUDIO_EMITTER emitterData;
|
internal FAudio.F3DAUDIO_EMITTER emitterData;
|
||||||
|
@ -132,5 +129,7 @@ namespace MoonWorks.Audio
|
||||||
emitterData.pReverbCurve = IntPtr.Zero;
|
emitterData.pReverbCurve = IntPtr.Zero;
|
||||||
emitterData.CurveDistanceScaler = 1.0f;
|
emitterData.CurveDistanceScaler = 1.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void Destroy() { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,6 @@ using MoonWorks.Math.Float;
|
||||||
|
|
||||||
namespace MoonWorks.Audio
|
namespace MoonWorks.Audio
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// A listener for 3D spatial audio. Usually attached to a camera.
|
|
||||||
/// </summary>
|
|
||||||
public class AudioListener : AudioResource
|
public class AudioListener : AudioResource
|
||||||
{
|
{
|
||||||
internal FAudio.F3DAUDIO_LISTENER listenerData;
|
internal FAudio.F3DAUDIO_LISTENER listenerData;
|
||||||
|
@ -94,5 +91,7 @@ namespace MoonWorks.Audio
|
||||||
/* Unexposed variables, defaults based on XNA behavior */
|
/* Unexposed variables, defaults based on XNA behavior */
|
||||||
listenerData.pCone = IntPtr.Zero;
|
listenerData.pCone = IntPtr.Zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void Destroy() { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace MoonWorks.Audio
|
namespace MoonWorks.Audio
|
||||||
{
|
{
|
||||||
|
@ -9,24 +8,28 @@ namespace MoonWorks.Audio
|
||||||
|
|
||||||
public bool IsDisposed { get; private set; }
|
public bool IsDisposed { get; private set; }
|
||||||
|
|
||||||
private GCHandle SelfReference;
|
private WeakReference<AudioResource> selfReference;
|
||||||
|
|
||||||
protected AudioResource(AudioDevice device)
|
public AudioResource(AudioDevice device)
|
||||||
{
|
{
|
||||||
Device = device;
|
Device = device;
|
||||||
|
|
||||||
SelfReference = GCHandle.Alloc(this, GCHandleType.Weak);
|
selfReference = new WeakReference<AudioResource>(this);
|
||||||
Device.AddResourceReference(SelfReference);
|
Device.AddResourceReference(selfReference);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected abstract void Destroy();
|
||||||
|
|
||||||
protected virtual void Dispose(bool disposing)
|
protected virtual void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
if (!IsDisposed)
|
if (!IsDisposed)
|
||||||
{
|
{
|
||||||
if (disposing)
|
Destroy();
|
||||||
|
|
||||||
|
if (selfReference != null)
|
||||||
{
|
{
|
||||||
Device.RemoveResourceReference(SelfReference);
|
Device.RemoveResourceReference(selfReference);
|
||||||
SelfReference.Free();
|
selfReference = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
IsDisposed = true;
|
IsDisposed = true;
|
||||||
|
@ -35,12 +38,8 @@ namespace MoonWorks.Audio
|
||||||
|
|
||||||
~AudioResource()
|
~AudioResource()
|
||||||
{
|
{
|
||||||
#if DEBUG
|
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||||
// If you see this log message, you leaked an audio resource without disposing it!
|
Dispose(disposing: false);
|
||||||
// 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()
|
public void Dispose()
|
||||||
|
|
|
@ -1,58 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,159 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
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
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
namespace MoonWorks.Audio
|
|
||||||
{
|
|
||||||
public interface IPoolable<T>
|
|
||||||
{
|
|
||||||
static abstract T Create(AudioDevice device, Format format);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,83 +0,0 @@
|
||||||
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
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,366 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,64 +0,0 @@
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,218 +0,0 @@
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,295 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,123 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,165 @@
|
||||||
|
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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,169 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,56 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
namespace MoonWorks.Audio
|
|
||||||
{
|
|
||||||
public abstract class UpdatingSourceVoice : SourceVoice
|
|
||||||
{
|
|
||||||
protected UpdatingSourceVoice(AudioDevice device, Format format) : base(device, format)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract void Update();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,578 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,181 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,333 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,130 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,136 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,252 @@
|
||||||
|
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>>();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,174 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,331 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,115 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,136 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,252 @@
|
||||||
|
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>>();
|
||||||
|
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,45 +0,0 @@
|
||||||
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>
|
|
||||||
{
|
|
||||||
{ VertexElementFormat.Byte4, (uint) Marshal.SizeOf<Byte4>() },
|
|
||||||
{ VertexElementFormat.Color, (uint) Marshal.SizeOf<Color>() },
|
|
||||||
{ VertexElementFormat.Float, (uint) Marshal.SizeOf<float>() },
|
|
||||||
{ VertexElementFormat.HalfVector2, (uint) Marshal.SizeOf<HalfVector2>() },
|
|
||||||
{ VertexElementFormat.HalfVector4, (uint) Marshal.SizeOf<HalfVector4>() },
|
|
||||||
{ VertexElementFormat.NormalizedShort2, (uint) Marshal.SizeOf<NormalizedShort2>() },
|
|
||||||
{ VertexElementFormat.NormalizedShort4, (uint) Marshal.SizeOf<NormalizedShort4>() },
|
|
||||||
{ VertexElementFormat.Short2, (uint) Marshal.SizeOf<Short2>() },
|
|
||||||
{ VertexElementFormat.Short4, (uint) Marshal.SizeOf<Short4>() },
|
|
||||||
{ VertexElementFormat.UInt, (uint) Marshal.SizeOf<uint>() },
|
|
||||||
{ VertexElementFormat.Vector2, (uint) Marshal.SizeOf<Math.Float.Vector2>() },
|
|
||||||
{ VertexElementFormat.Vector3, (uint) Marshal.SizeOf<Math.Float.Vector3>() },
|
|
||||||
{ VertexElementFormat.Vector4, (uint) Marshal.SizeOf<Math.Float.Vector4>() }
|
|
||||||
};
|
|
||||||
|
|
||||||
public static byte BoolToByte(bool b)
|
|
||||||
{
|
|
||||||
return (byte) (b ? 1 : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool ByteToBool(byte b)
|
|
||||||
{
|
|
||||||
return b != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static uint VertexElementFormatSize(VertexElementFormat format)
|
|
||||||
{
|
|
||||||
return Sizes[format];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace MoonWorks
|
||||||
|
{
|
||||||
|
public class AudioLoadException : Exception
|
||||||
|
{
|
||||||
|
public AudioLoadException(string message) : base(message)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,36 +0,0 @@
|
||||||
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(
|
|
||||||
FrameLimiterMode mode,
|
|
||||||
int cap
|
|
||||||
) {
|
|
||||||
Mode = mode;
|
|
||||||
Cap = cap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
namespace MoonWorks
|
||||||
|
{
|
||||||
|
public enum FramerateMode
|
||||||
|
{
|
||||||
|
Uncapped,
|
||||||
|
Capped
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct FramerateSettings
|
||||||
|
{
|
||||||
|
public FramerateMode Mode;
|
||||||
|
public int Cap;
|
||||||
|
}
|
||||||
|
}
|
159
src/Game.cs
159
src/Game.cs
|
@ -1,4 +1,5 @@
|
||||||
using SDL2;
|
using System.Collections.Generic;
|
||||||
|
using SDL2;
|
||||||
using MoonWorks.Audio;
|
using MoonWorks.Audio;
|
||||||
using MoonWorks.Graphics;
|
using MoonWorks.Graphics;
|
||||||
using MoonWorks.Input;
|
using MoonWorks.Input;
|
||||||
|
@ -8,12 +9,6 @@ using System.Diagnostics;
|
||||||
|
|
||||||
namespace MoonWorks
|
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 abstract class Game
|
||||||
{
|
{
|
||||||
public TimeSpan MAX_DELTA_TIME = TimeSpan.FromMilliseconds(100);
|
public TimeSpan MAX_DELTA_TIME = TimeSpan.FromMilliseconds(100);
|
||||||
|
@ -34,155 +29,91 @@ namespace MoonWorks
|
||||||
private bool FramerateCapped = false;
|
private bool FramerateCapped = false;
|
||||||
private TimeSpan FramerateCapTimeSpan = TimeSpan.Zero;
|
private TimeSpan FramerateCapTimeSpan = TimeSpan.Zero;
|
||||||
|
|
||||||
|
public Window Window { get; }
|
||||||
public GraphicsDevice GraphicsDevice { get; }
|
public GraphicsDevice GraphicsDevice { get; }
|
||||||
public AudioDevice AudioDevice { get; }
|
public AudioDevice AudioDevice { get; }
|
||||||
public Inputs Inputs { get; }
|
public Inputs Inputs { get; }
|
||||||
|
|
||||||
/// <summary>
|
private Dictionary<PresentMode, RefreshCS.Refresh.PresentMode> moonWorksToRefreshPresentMode = new Dictionary<PresentMode, RefreshCS.Refresh.PresentMode>
|
||||||
/// This Window is automatically created when your Game is instantiated.
|
{
|
||||||
/// </summary>
|
{ PresentMode.Immediate, RefreshCS.Refresh.PresentMode.Immediate },
|
||||||
public Window MainWindow { get; }
|
{ PresentMode.Mailbox, RefreshCS.Refresh.PresentMode.Mailbox },
|
||||||
|
{ PresentMode.FIFO, RefreshCS.Refresh.PresentMode.FIFO },
|
||||||
|
{ PresentMode.FIFORelaxed, RefreshCS.Refresh.PresentMode.FIFORelaxed }
|
||||||
|
};
|
||||||
|
|
||||||
/// <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(
|
public Game(
|
||||||
WindowCreateInfo windowCreateInfo,
|
WindowCreateInfo windowCreateInfo,
|
||||||
FrameLimiterSettings frameLimiterSettings,
|
PresentMode presentMode,
|
||||||
|
FramerateSettings framerateSettings,
|
||||||
int targetTimestep = 60,
|
int targetTimestep = 60,
|
||||||
bool debugMode = false
|
bool debugMode = false
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
Logger.LogInfo("Initializing frame limiter...");
|
|
||||||
Timestep = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / targetTimestep);
|
Timestep = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / targetTimestep);
|
||||||
gameTimer = Stopwatch.StartNew();
|
gameTimer = Stopwatch.StartNew();
|
||||||
|
|
||||||
SetFrameLimiter(frameLimiterSettings);
|
FramerateCapped = framerateSettings.Mode == FramerateMode.Capped;
|
||||||
|
|
||||||
|
if (FramerateCapped)
|
||||||
|
{
|
||||||
|
FramerateCapTimeSpan = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / framerateSettings.Cap);
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < previousSleepTimes.Length; i += 1)
|
for (int i = 0; i < previousSleepTimes.Length; i += 1)
|
||||||
{
|
{
|
||||||
previousSleepTimes[i] = TimeSpan.FromMilliseconds(1);
|
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)
|
if (SDL.SDL_Init(SDL.SDL_INIT_VIDEO | SDL.SDL_INIT_TIMER | SDL.SDL_INIT_GAMECONTROLLER) < 0)
|
||||||
{
|
{
|
||||||
Logger.LogError("Failed to initialize SDL!");
|
System.Console.WriteLine("Failed to initialize SDL!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.Initialize();
|
Logger.Initialize();
|
||||||
|
|
||||||
Logger.LogInfo("Initializing input...");
|
|
||||||
Inputs = new Inputs();
|
Inputs = new Inputs();
|
||||||
|
|
||||||
Logger.LogInfo("Initializing graphics device...");
|
Window = new Window(windowCreateInfo);
|
||||||
|
|
||||||
GraphicsDevice = new GraphicsDevice(
|
GraphicsDevice = new GraphicsDevice(
|
||||||
Backend.Vulkan,
|
Window.Handle,
|
||||||
|
moonWorksToRefreshPresentMode[presentMode],
|
||||||
debugMode
|
debugMode
|
||||||
);
|
);
|
||||||
|
|
||||||
Logger.LogInfo("Initializing main window...");
|
|
||||||
MainWindow = new Window(windowCreateInfo, GraphicsDevice.WindowFlags | SDL.SDL_WindowFlags.SDL_WINDOW_HIDDEN);
|
|
||||||
|
|
||||||
if (!GraphicsDevice.ClaimWindow(MainWindow, windowCreateInfo.PresentMode))
|
|
||||||
{
|
|
||||||
throw new System.SystemException("Could not claim window!");
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.LogInfo("Initializing audio thread...");
|
|
||||||
AudioDevice = new AudioDevice();
|
AudioDevice = new AudioDevice();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initiates the main game loop. Call this once from your Program.Main method.
|
|
||||||
/// </summary>
|
|
||||||
public void Run()
|
public void Run()
|
||||||
{
|
{
|
||||||
MainWindow.Show();
|
|
||||||
|
|
||||||
while (!quit)
|
while (!quit)
|
||||||
{
|
{
|
||||||
Tick();
|
Tick();
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.LogInfo("Starting shutdown sequence...");
|
|
||||||
|
|
||||||
Logger.LogInfo("Cleaning up game...");
|
|
||||||
Destroy();
|
Destroy();
|
||||||
|
|
||||||
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();
|
AudioDevice.Dispose();
|
||||||
|
GraphicsDevice.Dispose();
|
||||||
|
Window.Dispose();
|
||||||
|
|
||||||
SDL.SDL_Quit();
|
SDL.SDL_Quit();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Updates the frame limiter settings.
|
|
||||||
/// </summary>
|
|
||||||
public void SetFrameLimiter(FrameLimiterSettings settings)
|
|
||||||
{
|
|
||||||
FramerateCapped = settings.Mode == FrameLimiterMode.Capped;
|
|
||||||
|
|
||||||
if (FramerateCapped)
|
|
||||||
{
|
|
||||||
FramerateCapTimeSpan = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / settings.Cap);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
FramerateCapTimeSpan = TimeSpan.Zero;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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);
|
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);
|
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() {}
|
protected virtual void Destroy() {}
|
||||||
|
|
||||||
/// <summary>
|
// Called when a file is dropped on the game window.
|
||||||
/// Called when a file is dropped on the game window.
|
|
||||||
/// </summary>
|
|
||||||
protected virtual void DropFile(string filePath) {}
|
protected virtual void DropFile(string filePath) {}
|
||||||
|
|
||||||
/// <summary>
|
/* Required to distinguish between multiple files dropped at once
|
||||||
/// Required to distinguish between multiple files dropped at once
|
* vs multiple files dropped one at a time.
|
||||||
/// vs multiple files dropped one at a time.
|
*
|
||||||
/// Called once for every multi-file drop.
|
* Called once for every multi-file drop.
|
||||||
/// </summary>
|
*/
|
||||||
protected virtual void DropBegin() {}
|
protected virtual void DropBegin() {}
|
||||||
protected virtual void DropComplete() {}
|
protected virtual void DropComplete() {}
|
||||||
|
|
||||||
|
@ -230,8 +161,9 @@ namespace MoonWorks
|
||||||
while (accumulatedUpdateTime >= Timestep)
|
while (accumulatedUpdateTime >= Timestep)
|
||||||
{
|
{
|
||||||
Inputs.Update();
|
Inputs.Update();
|
||||||
|
AudioDevice.Update();
|
||||||
|
|
||||||
Update(Timestep);
|
Update(Timestep);
|
||||||
AudioDevice.WakeThread();
|
|
||||||
|
|
||||||
accumulatedUpdateTime -= Timestep;
|
accumulatedUpdateTime -= Timestep;
|
||||||
}
|
}
|
||||||
|
@ -258,7 +190,7 @@ namespace MoonWorks
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SDL.SDL_EventType.SDL_MOUSEWHEEL:
|
case SDL.SDL_EventType.SDL_MOUSEWHEEL:
|
||||||
Inputs.Mouse.WheelRaw += _event.wheel.y;
|
Inputs.Mouse.Wheel += _event.wheel.y;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SDL.SDL_EventType.SDL_DROPBEGIN:
|
case SDL.SDL_EventType.SDL_DROPBEGIN:
|
||||||
|
@ -292,14 +224,7 @@ namespace MoonWorks
|
||||||
{
|
{
|
||||||
if (evt.window.windowEvent == SDL.SDL_WindowEventID.SDL_WINDOWEVENT_SIZE_CHANGED)
|
if (evt.window.windowEvent == SDL.SDL_WindowEventID.SDL_WINDOWEVENT_SIZE_CHANGED)
|
||||||
{
|
{
|
||||||
var window = Window.Lookup(evt.window.windowID);
|
Window.SizeChanged((uint) evt.window.data1, (uint) evt.window.data2);
|
||||||
window.HandleSizeChange((uint) evt.window.data1, (uint) evt.window.data2);
|
|
||||||
}
|
|
||||||
else if (evt.window.windowEvent == SDL.SDL_WindowEventID.SDL_WINDOWEVENT_CLOSE)
|
|
||||||
{
|
|
||||||
var window = Window.Lookup(evt.window.windowID);
|
|
||||||
GraphicsDevice.UnclaimWindow(window);
|
|
||||||
window.Dispose();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -343,27 +268,17 @@ namespace MoonWorks
|
||||||
var index = evt.cdevice.which;
|
var index = evt.cdevice.which;
|
||||||
if (SDL.SDL_IsGameController(index) == SDL.SDL_bool.SDL_TRUE)
|
if (SDL.SDL_IsGameController(index) == SDL.SDL_bool.SDL_TRUE)
|
||||||
{
|
{
|
||||||
Logger.LogInfo("New controller detected!");
|
System.Console.WriteLine($"New controller detected!");
|
||||||
Inputs.AddGamepad(index);
|
Inputs.AddGamepad(index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleControllerRemoved(SDL.SDL_Event evt)
|
private void HandleControllerRemoved(SDL.SDL_Event evt)
|
||||||
{
|
{
|
||||||
Logger.LogInfo("Controller removal detected!");
|
System.Console.WriteLine($"Controller removal detected!");
|
||||||
Inputs.RemoveGamepad(evt.cdevice.which);
|
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()
|
private TimeSpan AdvanceElapsedTime()
|
||||||
{
|
{
|
||||||
long currentTicks = gameTimer.Elapsed.Ticks;
|
long currentTicks = gameTimer.Elapsed.Ticks;
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
namespace MoonWorks.Graphics
|
namespace MoonWorks.Graphics
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// A buffer-offset pair to be used when binding vertex buffers.
|
|
||||||
/// </summary>
|
|
||||||
public struct BufferBinding
|
public struct BufferBinding
|
||||||
{
|
{
|
||||||
public Buffer Buffer;
|
public Buffer Buffer;
|
||||||
|
|
|
@ -1,17 +1,22 @@
|
||||||
namespace MoonWorks.Graphics
|
using System;
|
||||||
|
|
||||||
|
namespace MoonWorks.Graphics
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// A texture-sampler pair to be used when binding samplers.
|
|
||||||
/// </summary>
|
|
||||||
public struct TextureSamplerBinding
|
public struct TextureSamplerBinding
|
||||||
{
|
{
|
||||||
public Texture Texture;
|
public IntPtr TextureHandle;
|
||||||
public Sampler Sampler;
|
public IntPtr SamplerHandle;
|
||||||
|
|
||||||
public TextureSamplerBinding(Texture texture, Sampler sampler)
|
public TextureSamplerBinding(Texture texture, Sampler sampler)
|
||||||
{
|
{
|
||||||
Texture = texture;
|
TextureHandle = texture.Handle;
|
||||||
Sampler = sampler;
|
SamplerHandle = sampler.Handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextureSamplerBinding(IntPtr textureHandle, IntPtr samplerHandle)
|
||||||
|
{
|
||||||
|
TextureHandle = textureHandle;
|
||||||
|
SamplerHandle = samplerHandle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,6 @@ using System.Diagnostics;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using MoonWorks.Math;
|
using MoonWorks.Math;
|
||||||
using MoonWorks.Math.Float;
|
using MoonWorks.Math.Float;
|
||||||
using MoonWorks.Graphics.PackedVector;
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
namespace MoonWorks.Graphics
|
namespace MoonWorks.Graphics
|
||||||
|
@ -1759,67 +1758,6 @@ 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
|
#endregion
|
||||||
|
|
||||||
#region Public Static Operators and Override Methods
|
#region Public Static Operators and Override Methods
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,34 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,32 +0,0 @@
|
||||||
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,121 +1,44 @@
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using WellspringCS;
|
using WellspringCS;
|
||||||
|
|
||||||
namespace MoonWorks.Graphics.Font
|
namespace MoonWorks.Graphics.Font
|
||||||
{
|
{
|
||||||
public unsafe class Font : GraphicsResource
|
public class Font : IDisposable
|
||||||
{
|
{
|
||||||
public Texture Texture { get; }
|
public IntPtr Handle { get; }
|
||||||
public float PixelsPerEm { get; }
|
|
||||||
public float DistanceRange { get; }
|
|
||||||
|
|
||||||
internal IntPtr Handle { get; }
|
private bool IsDisposed;
|
||||||
|
|
||||||
private byte* StringBytes;
|
public unsafe Font(string path)
|
||||||
private int StringBytesLength;
|
{
|
||||||
|
var bytes = File.ReadAllBytes(path);
|
||||||
/// <summary>
|
fixed (byte* pByte = &bytes[0])
|
||||||
/// 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)
|
|
||||||
{
|
{
|
||||||
StringBytes = (byte*) NativeMemory.Realloc(StringBytes, (nuint) byteCount);
|
Handle = Wellspring.Wellspring_CreateFont((IntPtr) pByte, (uint) bytes.Length);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fixed (char* chars = text)
|
protected virtual void Dispose(bool disposing)
|
||||||
{
|
|
||||||
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 (!IsDisposed)
|
||||||
{
|
{
|
||||||
if (disposing)
|
|
||||||
{
|
|
||||||
Texture.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
Wellspring.Wellspring_DestroyFont(Handle);
|
Wellspring.Wellspring_DestroyFont(Handle);
|
||||||
|
IsDisposed = true;
|
||||||
}
|
}
|
||||||
base.Dispose(disposing);
|
}
|
||||||
|
|
||||||
|
~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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,109 @@
|
||||||
|
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 * sizeof(Wellspring.FontRange);
|
||||||
|
void* fontRangeMemory = NativeMemory.Alloc((nuint) fontRanges.Length, (nuint) 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,17 +4,19 @@ using MoonWorks.Math.Float;
|
||||||
namespace MoonWorks.Graphics.Font
|
namespace MoonWorks.Graphics.Font
|
||||||
{
|
{
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
public struct Vertex : IVertexType
|
public struct FontRange
|
||||||
|
{
|
||||||
|
public uint FirstCodepoint;
|
||||||
|
public uint NumChars;
|
||||||
|
public byte OversampleH;
|
||||||
|
public byte OversampleV;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct Vertex
|
||||||
{
|
{
|
||||||
public Vector3 Position;
|
public Vector3 Position;
|
||||||
public Vector2 TexCoord;
|
public Vector2 TexCoord;
|
||||||
public Color Color;
|
public Color Color;
|
||||||
|
|
||||||
public static VertexElementFormat[] Formats { get; } = new VertexElementFormat[]
|
|
||||||
{
|
|
||||||
VertexElementFormat.Vector3,
|
|
||||||
VertexElementFormat.Vector2,
|
|
||||||
VertexElementFormat.Color
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,87 +1,75 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using WellspringCS;
|
using WellspringCS;
|
||||||
|
|
||||||
namespace MoonWorks.Graphics.Font
|
namespace MoonWorks.Graphics.Font
|
||||||
{
|
{
|
||||||
public unsafe class TextBatch : GraphicsResource
|
public class TextBatch
|
||||||
{
|
{
|
||||||
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; }
|
private GraphicsDevice GraphicsDevice { get; }
|
||||||
public IntPtr Handle { get; }
|
public IntPtr Handle { get; }
|
||||||
|
|
||||||
public Buffer VertexBuffer { get; protected set; } = null;
|
public Buffer VertexBuffer { get; protected set; } = null;
|
||||||
public Buffer IndexBuffer { get; protected set; } = null;
|
public Buffer IndexBuffer { get; protected set; } = null;
|
||||||
|
public Texture Texture { get; protected set; }
|
||||||
public uint PrimitiveCount { get; protected set; }
|
public uint PrimitiveCount { get; protected set; }
|
||||||
|
|
||||||
public Font CurrentFont { get; private set; }
|
private byte[] StringBytes;
|
||||||
|
|
||||||
private byte* StringBytes;
|
public TextBatch(GraphicsDevice graphicsDevice)
|
||||||
private int StringBytesLength;
|
|
||||||
|
|
||||||
public TextBatch(GraphicsDevice device) : base(device)
|
|
||||||
{
|
{
|
||||||
GraphicsDevice = device;
|
GraphicsDevice = graphicsDevice;
|
||||||
Handle = Wellspring.Wellspring_CreateTextBatch();
|
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call this to initialize or reset the batch.
|
public void Start(Packer packer)
|
||||||
public void Start(Font font)
|
|
||||||
{
|
{
|
||||||
Wellspring.Wellspring_StartTextBatch(Handle, font.Handle);
|
Wellspring.Wellspring_StartTextBatch(Handle, packer.Handle);
|
||||||
CurrentFont = font;
|
Texture = packer.Texture;
|
||||||
PrimitiveCount = 0;
|
PrimitiveCount = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add text with size and color to the batch
|
public unsafe void Draw(
|
||||||
public unsafe bool Add(
|
|
||||||
string text,
|
string text,
|
||||||
int pixelSize,
|
float x,
|
||||||
|
float y,
|
||||||
|
float depth,
|
||||||
Color color,
|
Color color,
|
||||||
HorizontalAlignment horizontalAlignment = HorizontalAlignment.Left,
|
HorizontalAlignment horizontalAlignment = HorizontalAlignment.Left,
|
||||||
VerticalAlignment verticalAlignment = VerticalAlignment.Baseline
|
VerticalAlignment verticalAlignment = VerticalAlignment.Baseline
|
||||||
) {
|
) {
|
||||||
var byteCount = System.Text.Encoding.UTF8.GetByteCount(text);
|
var byteCount = System.Text.Encoding.UTF8.GetByteCount(text);
|
||||||
|
|
||||||
if (StringBytesLength < byteCount)
|
if (StringBytes.Length < byteCount)
|
||||||
{
|
{
|
||||||
StringBytes = (byte*) NativeMemory.Realloc(StringBytes, (nuint) byteCount);
|
System.Array.Resize(ref StringBytes, byteCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
fixed (char* chars = text)
|
fixed (char* chars = text)
|
||||||
|
fixed (byte* bytes = StringBytes)
|
||||||
{
|
{
|
||||||
System.Text.Encoding.UTF8.GetBytes(chars, text.Length, StringBytes, byteCount);
|
System.Text.Encoding.UTF8.GetBytes(chars, text.Length, bytes, byteCount);
|
||||||
|
|
||||||
var result = Wellspring.Wellspring_AddToTextBatch(
|
var result = Wellspring.Wellspring_Draw(
|
||||||
Handle,
|
Handle,
|
||||||
pixelSize,
|
x,
|
||||||
|
y,
|
||||||
|
depth,
|
||||||
new Wellspring.Color { R = color.R, G = color.G, B = color.B, A = color.A },
|
new Wellspring.Color { R = color.R, G = color.G, B = color.B, A = color.A },
|
||||||
(Wellspring.HorizontalAlignment) horizontalAlignment,
|
(Wellspring.HorizontalAlignment) horizontalAlignment,
|
||||||
(Wellspring.VerticalAlignment) verticalAlignment,
|
(Wellspring.VerticalAlignment) verticalAlignment,
|
||||||
(IntPtr) StringBytes,
|
(IntPtr) bytes,
|
||||||
(uint) byteCount
|
(uint) byteCount
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result == 0)
|
if (result == 0)
|
||||||
{
|
{
|
||||||
Logger.LogWarn("Could not decode string: " + text);
|
throw new System.ArgumentException("Could not decode string!");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call this after you have made all the Add calls you want, but before beginning a render pass.
|
// Call this after you have made all the Draw calls you want.
|
||||||
public unsafe void UploadBufferData(CommandBuffer commandBuffer)
|
public unsafe void UploadBufferData(CommandBuffer commandBuffer)
|
||||||
{
|
{
|
||||||
Wellspring.Wellspring_GetBufferData(
|
Wellspring.Wellspring_GetBufferData(
|
||||||
|
@ -93,59 +81,30 @@ namespace MoonWorks.Graphics.Font
|
||||||
out uint indexDataLengthInBytes
|
out uint indexDataLengthInBytes
|
||||||
);
|
);
|
||||||
|
|
||||||
if (VertexBuffer.Size < vertexDataLengthInBytes)
|
if (VertexBuffer == null)
|
||||||
|
{
|
||||||
|
VertexBuffer = new Buffer(GraphicsDevice, BufferUsageFlags.Vertex, vertexDataLengthInBytes);
|
||||||
|
}
|
||||||
|
else if (VertexBuffer.Size < vertexDataLengthInBytes)
|
||||||
{
|
{
|
||||||
VertexBuffer.Dispose();
|
VertexBuffer.Dispose();
|
||||||
VertexBuffer = new Buffer(GraphicsDevice, BufferUsageFlags.Vertex, vertexDataLengthInBytes);
|
VertexBuffer = new Buffer(GraphicsDevice, BufferUsageFlags.Vertex, vertexDataLengthInBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IndexBuffer.Size < indexDataLengthInBytes)
|
if (IndexBuffer == null)
|
||||||
|
{
|
||||||
|
IndexBuffer = new Buffer(GraphicsDevice, BufferUsageFlags.Index, indexDataLengthInBytes);
|
||||||
|
}
|
||||||
|
else if (IndexBuffer.Size < indexDataLengthInBytes)
|
||||||
{
|
{
|
||||||
IndexBuffer.Dispose();
|
IndexBuffer.Dispose();
|
||||||
IndexBuffer = new Buffer(GraphicsDevice, BufferUsageFlags.Index, vertexDataLengthInBytes);
|
IndexBuffer = new Buffer(GraphicsDevice, BufferUsageFlags.Index, indexDataLengthInBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (vertexDataLengthInBytes > 0 && indexDataLengthInBytes > 0)
|
commandBuffer.SetBufferData(VertexBuffer, vertexDataPointer, 0, vertexDataLengthInBytes);
|
||||||
{
|
commandBuffer.SetBufferData(IndexBuffer, indexDataPointer, 0, indexDataLengthInBytes);
|
||||||
commandBuffer.SetBufferData(VertexBuffer, vertexDataPointer, 0, vertexDataLengthInBytes);
|
|
||||||
commandBuffer.SetBufferData(IndexBuffer, indexDataPointer, 0, indexDataLengthInBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
PrimitiveCount = vertexCount / 2;
|
PrimitiveCount = vertexCount / 2; // FIXME: is this jank?
|
||||||
}
|
|
||||||
|
|
||||||
// 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,423 +1,93 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using MoonWorks.Video;
|
|
||||||
using RefreshCS;
|
using RefreshCS;
|
||||||
using WellspringCS;
|
|
||||||
|
|
||||||
namespace MoonWorks.Graphics
|
namespace MoonWorks.Graphics
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// GraphicsDevice manages all graphics-related concerns.
|
|
||||||
/// </summary>
|
|
||||||
public class GraphicsDevice : IDisposable
|
public class GraphicsDevice : IDisposable
|
||||||
{
|
{
|
||||||
public IntPtr Handle { get; }
|
public IntPtr Handle { get; }
|
||||||
public Backend Backend { get; }
|
|
||||||
|
|
||||||
private uint windowFlags;
|
|
||||||
public SDL2.SDL.SDL_WindowFlags WindowFlags => (SDL2.SDL.SDL_WindowFlags) windowFlags;
|
|
||||||
|
|
||||||
// Built-in video pipeline
|
// Built-in video pipeline
|
||||||
|
private ShaderModule VideoVertexShader { get; }
|
||||||
|
private ShaderModule VideoFragmentShader { get; }
|
||||||
internal GraphicsPipeline VideoPipeline { get; }
|
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; }
|
public bool IsDisposed { get; private set; }
|
||||||
|
|
||||||
private readonly HashSet<GCHandle> resources = new HashSet<GCHandle>();
|
private readonly List<WeakReference<GraphicsResource>> resources = new List<WeakReference<GraphicsResource>>();
|
||||||
private FencePool FencePool;
|
|
||||||
private CommandBufferPool CommandBufferPool;
|
|
||||||
|
|
||||||
internal GraphicsDevice(
|
public GraphicsDevice(
|
||||||
Backend preferredBackend,
|
IntPtr deviceWindowHandle,
|
||||||
|
Refresh.PresentMode presentMode,
|
||||||
bool debugMode
|
bool debugMode
|
||||||
) {
|
)
|
||||||
Backend = (Backend) Refresh.Refresh_SelectBackend((Refresh.Backend) preferredBackend, out windowFlags);
|
{
|
||||||
|
var presentationParameters = new Refresh.PresentationParameters
|
||||||
if (Backend == Backend.Invalid)
|
|
||||||
{
|
{
|
||||||
throw new System.Exception("Could not set graphics backend!");
|
deviceWindowHandle = deviceWindowHandle,
|
||||||
}
|
presentMode = presentMode
|
||||||
|
};
|
||||||
|
|
||||||
Handle = Refresh.Refresh_CreateDevice(
|
Handle = Refresh.Refresh_CreateDevice(
|
||||||
|
presentationParameters,
|
||||||
Conversions.BoolToByte(debugMode)
|
Conversions.BoolToByte(debugMode)
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO: check for CreateDevice fail
|
VideoVertexShader = new ShaderModule(this, GetEmbeddedResource("MoonWorks.Shaders.FullscreenVert.spv"));
|
||||||
|
VideoFragmentShader = new ShaderModule(this, GetEmbeddedResource("MoonWorks.Shaders.YUV2RGBAFrag.spv"));
|
||||||
// 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))
|
|
||||||
{
|
|
||||||
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(
|
VideoPipeline = new GraphicsPipeline(
|
||||||
this,
|
this,
|
||||||
new GraphicsPipelineCreateInfo
|
new GraphicsPipelineCreateInfo
|
||||||
{
|
{
|
||||||
AttachmentInfo = new GraphicsPipelineAttachmentInfo(
|
AttachmentInfo = new GraphicsPipelineAttachmentInfo(
|
||||||
new ColorAttachmentDescription(
|
new ColorAttachmentDescription(TextureFormat.R8G8B8A8, ColorAttachmentBlendState.None)
|
||||||
TextureFormat.R8G8B8A8,
|
|
||||||
ColorAttachmentBlendState.None
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
DepthStencilState = DepthStencilState.Disable,
|
DepthStencilState = DepthStencilState.Disable,
|
||||||
VertexShaderInfo = GraphicsShaderInfo.Create(
|
VertexShaderInfo = GraphicsShaderInfo.Create(VideoVertexShader, "main", 0),
|
||||||
videoVertShader,
|
FragmentShaderInfo = GraphicsShaderInfo.Create(VideoFragmentShader, "main", 3),
|
||||||
"main",
|
|
||||||
0
|
|
||||||
),
|
|
||||||
FragmentShaderInfo = GraphicsShaderInfo.Create(
|
|
||||||
videoFragShader,
|
|
||||||
"main",
|
|
||||||
3
|
|
||||||
),
|
|
||||||
VertexInputState = VertexInputState.Empty,
|
VertexInputState = VertexInputState.Empty,
|
||||||
RasterizerState = RasterizerState.CCW_CullNone,
|
RasterizerState = RasterizerState.CCW_CullNone,
|
||||||
PrimitiveType = PrimitiveType.TriangleList,
|
PrimitiveType = PrimitiveType.TriangleList,
|
||||||
MultisampleState = MultisampleState.None
|
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,
|
|
||||||
window.Handle,
|
|
||||||
(Refresh.PresentMode) presentMode
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (success)
|
|
||||||
{
|
|
||||||
window.Claimed = true;
|
|
||||||
window.SwapchainFormat = GetSwapchainFormat(window);
|
|
||||||
if (window.SwapchainTexture == null)
|
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
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,
|
|
||||||
(Refresh.PresentMode) presentMode
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Acquires a command buffer.
|
|
||||||
/// This is the start of your rendering process.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
public CommandBuffer AcquireCommandBuffer()
|
public CommandBuffer AcquireCommandBuffer()
|
||||||
{
|
{
|
||||||
var commandBuffer = CommandBufferPool.Obtain();
|
return new CommandBuffer(this, Refresh.Refresh_AcquireCommandBuffer(Handle, 0));
|
||||||
commandBuffer.SetHandle(Refresh.Refresh_AcquireCommandBuffer(Handle));
|
|
||||||
#if DEBUG
|
|
||||||
commandBuffer.ResetStateTracking();
|
|
||||||
#endif
|
|
||||||
return commandBuffer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public unsafe void Submit(params CommandBuffer[] commandBuffers)
|
||||||
/// Submits a command buffer to the GPU for processing.
|
|
||||||
/// </summary>
|
|
||||||
public void Submit(CommandBuffer commandBuffer)
|
|
||||||
{
|
{
|
||||||
#if DEBUG
|
var commandBufferPtrs = stackalloc IntPtr[commandBuffers.Length];
|
||||||
if (commandBuffer.Submitted)
|
|
||||||
|
for (var i = 0; i < commandBuffers.Length; i += 1)
|
||||||
{
|
{
|
||||||
throw new System.InvalidOperationException("Command buffer already submitted!");
|
commandBufferPtrs[i] = commandBuffers[i].Handle;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
Refresh.Refresh_Submit(
|
Refresh.Refresh_Submit(
|
||||||
Handle,
|
Handle,
|
||||||
commandBuffer.Handle
|
(uint) commandBuffers.Length,
|
||||||
|
(IntPtr) commandBufferPtrs
|
||||||
);
|
);
|
||||||
|
|
||||||
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()
|
public void Wait()
|
||||||
{
|
{
|
||||||
Refresh.Refresh_Wait(Handle);
|
Refresh.Refresh_Wait(Handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public TextureFormat GetSwapchainFormat(Window window)
|
||||||
/// 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);
|
return (TextureFormat) Refresh.Refresh_GetSwapchainFormat(Handle, window.Handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void AddResourceReference(GCHandle resourceReference)
|
internal void AddResourceReference(WeakReference<GraphicsResource> resourceReference)
|
||||||
{
|
{
|
||||||
lock (resources)
|
lock (resources)
|
||||||
{
|
{
|
||||||
|
@ -425,7 +95,7 @@ namespace MoonWorks.Graphics
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void RemoveResourceReference(GCHandle resourceReference)
|
internal void RemoveResourceReference(WeakReference<GraphicsResource> resourceReference)
|
||||||
{
|
{
|
||||||
lock (resources)
|
lock (resources)
|
||||||
{
|
{
|
||||||
|
@ -433,6 +103,11 @@ namespace MoonWorks.Graphics
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Stream GetEmbeddedResource(string name)
|
||||||
|
{
|
||||||
|
return typeof(GraphicsDevice).Assembly.GetManifestResourceStream(name);
|
||||||
|
}
|
||||||
|
|
||||||
protected virtual void Dispose(bool disposing)
|
protected virtual void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
if (!IsDisposed)
|
if (!IsDisposed)
|
||||||
|
@ -441,28 +116,19 @@ namespace MoonWorks.Graphics
|
||||||
{
|
{
|
||||||
lock (resources)
|
lock (resources)
|
||||||
{
|
{
|
||||||
// Dispose video players first to avoid race condition on threaded decoding
|
for (var i = resources.Count - 1; i >= 0; i--)
|
||||||
foreach (var resource in resources)
|
|
||||||
{
|
{
|
||||||
if (resource.Target is VideoPlayer player)
|
var resource = resources[i];
|
||||||
|
if (resource.TryGetTarget(out var target))
|
||||||
{
|
{
|
||||||
player.Dispose();
|
target.Dispose();
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dispose everything else
|
|
||||||
foreach (var resource in resources)
|
|
||||||
{
|
|
||||||
if (resource.Target is IDisposable disposable)
|
|
||||||
{
|
|
||||||
disposable.Dispose();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resources.Clear();
|
resources.Clear();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Refresh.Refresh_DestroyDevice(Handle);
|
Refresh.Refresh_DestroyDevice(Handle);
|
||||||
|
}
|
||||||
|
|
||||||
IsDisposed = true;
|
IsDisposed = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,33 +1,37 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace MoonWorks.Graphics
|
namespace MoonWorks.Graphics
|
||||||
{
|
{
|
||||||
// TODO: give this a Name property for debugging use
|
|
||||||
public abstract class GraphicsResource : IDisposable
|
public abstract class GraphicsResource : IDisposable
|
||||||
{
|
{
|
||||||
public GraphicsDevice Device { get; }
|
public GraphicsDevice Device { get; }
|
||||||
|
public IntPtr Handle { get; protected set; }
|
||||||
private GCHandle SelfReference;
|
|
||||||
|
|
||||||
public bool IsDisposed { get; private set; }
|
public bool IsDisposed { get; private set; }
|
||||||
|
protected abstract Action<IntPtr, IntPtr> QueueDestroyFunction { get; }
|
||||||
|
|
||||||
protected GraphicsResource(GraphicsDevice device)
|
private WeakReference<GraphicsResource> selfReference;
|
||||||
|
|
||||||
|
public GraphicsResource(GraphicsDevice device, bool trackResource = true)
|
||||||
{
|
{
|
||||||
Device = device;
|
Device = device;
|
||||||
|
|
||||||
SelfReference = GCHandle.Alloc(this, GCHandleType.Weak);
|
if (trackResource)
|
||||||
Device.AddResourceReference(SelfReference);
|
{
|
||||||
|
selfReference = new WeakReference<GraphicsResource>(this);
|
||||||
|
Device.AddResourceReference(selfReference);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void Dispose(bool disposing)
|
protected virtual void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
if (!IsDisposed)
|
if (!IsDisposed)
|
||||||
{
|
{
|
||||||
if (disposing)
|
if (selfReference != null)
|
||||||
{
|
{
|
||||||
Device.RemoveResourceReference(SelfReference);
|
QueueDestroyFunction(Device.Handle, Handle);
|
||||||
SelfReference.Free();
|
Device.RemoveResourceReference(selfReference);
|
||||||
|
selfReference = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
IsDisposed = true;
|
IsDisposed = true;
|
||||||
|
@ -36,13 +40,8 @@ namespace MoonWorks.Graphics
|
||||||
|
|
||||||
~GraphicsResource()
|
~GraphicsResource()
|
||||||
{
|
{
|
||||||
#if DEBUG
|
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||||
// If you see this log message, you leaked a graphics resource without disposing it!
|
Dispose(disposing: false);
|
||||||
// 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()
|
public void Dispose()
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
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;
|
using MoonWorks.Math.Float;
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
namespace MoonWorks.Graphics.PackedVector
|
namespace MoonWorks.Graphics
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Packed vector type containing unsigned normalized values ranging from 0 to 1.
|
/// Packed vector type containing unsigned normalized values ranging from 0 to 1.
|
||||||
|
|
|
@ -20,7 +20,7 @@ using MoonWorks.Math;
|
||||||
using MoonWorks.Math.Float;
|
using MoonWorks.Math.Float;
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
namespace MoonWorks.Graphics.PackedVector
|
namespace MoonWorks.Graphics
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Packed vector type containing unsigned normalized values ranging from 0 to 1.
|
/// Packed vector type containing unsigned normalized values ranging from 0 to 1.
|
||||||
|
|
|
@ -20,7 +20,7 @@ using MoonWorks.Math;
|
||||||
using MoonWorks.Math.Float;
|
using MoonWorks.Math.Float;
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
namespace MoonWorks.Graphics.PackedVector
|
namespace MoonWorks.Graphics
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Packed vector type containing unsigned normalized values, ranging from 0 to 1, using
|
/// Packed vector type containing unsigned normalized values, ranging from 0 to 1, using
|
||||||
|
|
|
@ -20,7 +20,7 @@ using MoonWorks.Math;
|
||||||
using MoonWorks.Math.Float;
|
using MoonWorks.Math.Float;
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
namespace MoonWorks.Graphics.PackedVector
|
namespace MoonWorks.Graphics
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Packed vector type containing unsigned normalized values ranging from 0 to 1.
|
/// Packed vector type containing unsigned normalized values ranging from 0 to 1.
|
||||||
|
|
|
@ -20,7 +20,7 @@ using MoonWorks.Math;
|
||||||
using MoonWorks.Math.Float;
|
using MoonWorks.Math.Float;
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
namespace MoonWorks.Graphics.PackedVector
|
namespace MoonWorks.Graphics
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Packed vector type containing four 8-bit unsigned integer values, ranging from 0 to 255.
|
/// 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;
|
using MoonWorks.Math.Float;
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
namespace MoonWorks.Graphics.PackedVector
|
namespace MoonWorks.Graphics
|
||||||
{
|
{
|
||||||
public struct HalfSingle : IPackedVector<ushort>, IEquatable<HalfSingle>, IPackedVector
|
public struct HalfSingle : IPackedVector<ushort>, IEquatable<HalfSingle>, IPackedVector
|
||||||
{
|
{
|
||||||
|
|
|
@ -19,7 +19,7 @@ using System;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
namespace MoonWorks.Graphics.PackedVector
|
namespace MoonWorks.Graphics
|
||||||
{
|
{
|
||||||
internal static class HalfTypeHelper
|
internal static class HalfTypeHelper
|
||||||
{
|
{
|
||||||
|
|
|
@ -19,7 +19,7 @@ using System;
|
||||||
using MoonWorks.Math.Float;
|
using MoonWorks.Math.Float;
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
namespace MoonWorks.Graphics.PackedVector
|
namespace MoonWorks.Graphics
|
||||||
{
|
{
|
||||||
public struct HalfVector2 : IPackedVector<uint>, IPackedVector, IEquatable<HalfVector2>
|
public struct HalfVector2 : IPackedVector<uint>, IPackedVector, IEquatable<HalfVector2>
|
||||||
{
|
{
|
||||||
|
|
|
@ -19,7 +19,7 @@ using System;
|
||||||
using MoonWorks.Math.Float;
|
using MoonWorks.Math.Float;
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
namespace MoonWorks.Graphics.PackedVector
|
namespace MoonWorks.Graphics
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Packed vector type containing four 16-bit floating-point values.
|
/// Packed vector type containing four 16-bit floating-point values.
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
using MoonWorks.Math.Float;
|
using MoonWorks.Math.Float;
|
||||||
|
|
||||||
namespace MoonWorks.Graphics.PackedVector
|
namespace MoonWorks.Graphics
|
||||||
{
|
{
|
||||||
// http://msdn.microsoft.com/en-us/library/microsoft.xna.framework.graphics.packedvector.ipackedvector.aspx
|
// http://msdn.microsoft.com/en-us/library/microsoft.xna.framework.graphics.packedvector.ipackedvector.aspx
|
||||||
public interface IPackedVector
|
public interface IPackedVector
|
||||||
|
|
|
@ -20,7 +20,7 @@ using MoonWorks.Math;
|
||||||
using MoonWorks.Math.Float;
|
using MoonWorks.Math.Float;
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
namespace MoonWorks.Graphics.PackedVector
|
namespace MoonWorks.Graphics
|
||||||
{
|
{
|
||||||
public struct NormalizedByte2 : IPackedVector<ushort>, IEquatable<NormalizedByte2>
|
public struct NormalizedByte2 : IPackedVector<ushort>, IEquatable<NormalizedByte2>
|
||||||
{
|
{
|
||||||
|
|
|
@ -20,7 +20,7 @@ using MoonWorks.Math;
|
||||||
using MoonWorks.Math.Float;
|
using MoonWorks.Math.Float;
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
namespace MoonWorks.Graphics.PackedVector
|
namespace MoonWorks.Graphics
|
||||||
{
|
{
|
||||||
public struct NormalizedByte4 : IPackedVector<uint>, IEquatable<NormalizedByte4>
|
public struct NormalizedByte4 : IPackedVector<uint>, IEquatable<NormalizedByte4>
|
||||||
{
|
{
|
||||||
|
|
|
@ -20,7 +20,7 @@ using MoonWorks.Math;
|
||||||
using MoonWorks.Math.Float;
|
using MoonWorks.Math.Float;
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
namespace MoonWorks.Graphics.PackedVector
|
namespace MoonWorks.Graphics
|
||||||
{
|
{
|
||||||
public struct NormalizedShort2 : IPackedVector<uint>, IEquatable<NormalizedShort2>
|
public struct NormalizedShort2 : IPackedVector<uint>, IEquatable<NormalizedShort2>
|
||||||
{
|
{
|
||||||
|
|
|
@ -20,7 +20,7 @@ using MoonWorks.Math;
|
||||||
using MoonWorks.Math.Float;
|
using MoonWorks.Math.Float;
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
namespace MoonWorks.Graphics.PackedVector
|
namespace MoonWorks.Graphics
|
||||||
{
|
{
|
||||||
public struct NormalizedShort4 : IPackedVector<ulong>, IEquatable<NormalizedShort4>
|
public struct NormalizedShort4 : IPackedVector<ulong>, IEquatable<NormalizedShort4>
|
||||||
{
|
{
|
||||||
|
|
|
@ -20,7 +20,7 @@ using MoonWorks.Math;
|
||||||
using MoonWorks.Math.Float;
|
using MoonWorks.Math.Float;
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
namespace MoonWorks.Graphics.PackedVector
|
namespace MoonWorks.Graphics
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Packed vector type containing unsigned normalized values ranging from 0 to 1.
|
/// Packed vector type containing unsigned normalized values ranging from 0 to 1.
|
||||||
|
|
|
@ -20,7 +20,7 @@ using MoonWorks.Math;
|
||||||
using MoonWorks.Math.Float;
|
using MoonWorks.Math.Float;
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
namespace MoonWorks.Graphics.PackedVector
|
namespace MoonWorks.Graphics
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Packed vector type containing unsigned normalized values ranging from 0 to 1.
|
/// Packed vector type containing unsigned normalized values ranging from 0 to 1.
|
||||||
|
|
|
@ -20,7 +20,7 @@ using MoonWorks.Math;
|
||||||
using MoonWorks.Math.Float;
|
using MoonWorks.Math.Float;
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
namespace MoonWorks.Graphics.PackedVector
|
namespace MoonWorks.Graphics
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Packed vector type containing unsigned normalized values ranging from 0 to 1.
|
/// Packed vector type containing unsigned normalized values ranging from 0 to 1.
|
||||||
|
|
|
@ -20,7 +20,7 @@ using MoonWorks.Math;
|
||||||
using MoonWorks.Math.Float;
|
using MoonWorks.Math.Float;
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
namespace MoonWorks.Graphics.PackedVector
|
namespace MoonWorks.Graphics
|
||||||
{
|
{
|
||||||
public struct Short2 : IPackedVector<uint>, IEquatable<Short2>
|
public struct Short2 : IPackedVector<uint>, IEquatable<Short2>
|
||||||
{
|
{
|
||||||
|
|
|
@ -20,7 +20,7 @@ using MoonWorks.Math;
|
||||||
using MoonWorks.Math.Float;
|
using MoonWorks.Math.Float;
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
namespace MoonWorks.Graphics.PackedVector
|
namespace MoonWorks.Graphics
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Packed vector type containing four 16-bit signed integer values.
|
/// Packed vector type containing four 16-bit signed integer values.
|
||||||
|
|
|
@ -1,41 +1,18 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Recreate all the enums in here so we don't need to explicitly
|
/* Recreate all the enums in here so we don't need to explicitly
|
||||||
* reference the RefreshCS namespace when using MoonWorks.Graphics
|
* reference the RefreshCS namespace when using MoonWorks.Graphics
|
||||||
*/
|
*/
|
||||||
namespace MoonWorks.Graphics
|
namespace MoonWorks.Graphics
|
||||||
{
|
{
|
||||||
|
public enum PresentMode
|
||||||
|
{
|
||||||
|
Immediate,
|
||||||
|
Mailbox,
|
||||||
|
FIFO,
|
||||||
|
FIFORelaxed
|
||||||
|
}
|
||||||
|
|
||||||
public enum PrimitiveType
|
public enum PrimitiveType
|
||||||
{
|
{
|
||||||
PointList,
|
PointList,
|
||||||
|
@ -130,7 +107,10 @@ namespace MoonWorks.Graphics
|
||||||
One,
|
One,
|
||||||
Two,
|
Two,
|
||||||
Four,
|
Four,
|
||||||
Eight
|
Eight,
|
||||||
|
Sixteen,
|
||||||
|
ThirtyTwo,
|
||||||
|
SixtyFour
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum CubeMapFace : uint
|
public enum CubeMapFace : uint
|
||||||
|
@ -241,7 +221,11 @@ namespace MoonWorks.Graphics
|
||||||
OneMinusDestinationAlpha,
|
OneMinusDestinationAlpha,
|
||||||
ConstantColor,
|
ConstantColor,
|
||||||
OneMinusConstantColor,
|
OneMinusConstantColor,
|
||||||
SourceAlphaSaturate
|
SourceAlphaSaturate,
|
||||||
|
SourceOneColor,
|
||||||
|
OneMinusSourceOneColor,
|
||||||
|
SourceOneAlpha,
|
||||||
|
OneMinusSourceOneAlpha
|
||||||
}
|
}
|
||||||
|
|
||||||
[Flags]
|
[Flags]
|
||||||
|
@ -296,12 +280,4 @@ namespace MoonWorks.Graphics
|
||||||
FloatOpaqueWhite,
|
FloatOpaqueWhite,
|
||||||
IntOpaqueWhite
|
IntOpaqueWhite
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Backend
|
|
||||||
{
|
|
||||||
DontCare,
|
|
||||||
Vulkan,
|
|
||||||
PS5,
|
|
||||||
Invalid
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
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,13 +127,14 @@ namespace MoonWorks.Graphics
|
||||||
public uint Stride;
|
public uint Stride;
|
||||||
public VertexInputRate InputRate;
|
public VertexInputRate InputRate;
|
||||||
|
|
||||||
public static VertexBinding Create<T>(uint binding = 0, VertexInputRate inputRate = VertexInputRate.Vertex) where T : unmanaged
|
// Shortcut for the common case of having a single vertex binding.
|
||||||
|
public unsafe static VertexBinding Create<T>() where T : unmanaged
|
||||||
{
|
{
|
||||||
return new VertexBinding
|
return new VertexBinding
|
||||||
{
|
{
|
||||||
Binding = binding,
|
Binding = 0,
|
||||||
InputRate = inputRate,
|
InputRate = VertexInputRate.Vertex,
|
||||||
Stride = (uint) Marshal.SizeOf<T>()
|
Stride = (uint) sizeof(T)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -145,6 +146,28 @@ namespace MoonWorks.Graphics
|
||||||
public uint Binding;
|
public uint Binding;
|
||||||
public VertexElementFormat Format;
|
public VertexElementFormat Format;
|
||||||
public uint Offset;
|
public uint Offset;
|
||||||
|
|
||||||
|
public static VertexAttribute Create<T>(
|
||||||
|
string fieldName,
|
||||||
|
uint location,
|
||||||
|
uint binding = 0
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var fieldInfo = typeof(T).GetField(fieldName);
|
||||||
|
|
||||||
|
if (fieldInfo == null)
|
||||||
|
{
|
||||||
|
throw new System.ArgumentException("Field not recognized!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new VertexAttribute
|
||||||
|
{
|
||||||
|
Binding = binding,
|
||||||
|
Location = location,
|
||||||
|
Format = Conversions.TypeToVertexElementFormat(fieldInfo.FieldType),
|
||||||
|
Offset = (uint) Marshal.OffsetOf<T>(fieldName)
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
@ -181,33 +204,30 @@ namespace MoonWorks.Graphics
|
||||||
public uint Depth;
|
public uint Depth;
|
||||||
public uint Layer;
|
public uint Layer;
|
||||||
public uint Level;
|
public uint Level;
|
||||||
|
public SampleCount SampleCount;
|
||||||
public Color ClearColor;
|
public Color ClearColor;
|
||||||
public LoadOp LoadOp;
|
public LoadOp LoadOp;
|
||||||
public StoreOp StoreOp;
|
public StoreOp StoreOp;
|
||||||
|
|
||||||
public ColorAttachmentInfo(
|
public ColorAttachmentInfo(Texture texture, Color clearColor, StoreOp storeOp = StoreOp.Store)
|
||||||
Texture texture,
|
{
|
||||||
Color clearColor,
|
|
||||||
StoreOp storeOp = StoreOp.Store
|
|
||||||
) {
|
|
||||||
Texture = texture;
|
Texture = texture;
|
||||||
Depth = 0;
|
Depth = 0;
|
||||||
Layer = 0;
|
Layer = 0;
|
||||||
Level = 0;
|
Level = 0;
|
||||||
|
SampleCount = SampleCount.One;
|
||||||
ClearColor = clearColor;
|
ClearColor = clearColor;
|
||||||
LoadOp = LoadOp.Clear;
|
LoadOp = LoadOp.Clear;
|
||||||
StoreOp = storeOp;
|
StoreOp = storeOp;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ColorAttachmentInfo(
|
public ColorAttachmentInfo(Texture texture, LoadOp loadOp = LoadOp.DontCare, StoreOp storeOp = StoreOp.Store)
|
||||||
Texture texture,
|
{
|
||||||
LoadOp loadOp = LoadOp.DontCare,
|
|
||||||
StoreOp storeOp = StoreOp.Store
|
|
||||||
) {
|
|
||||||
Texture = texture;
|
Texture = texture;
|
||||||
Depth = 0;
|
Depth = 0;
|
||||||
Layer = 0;
|
Layer = 0;
|
||||||
Level = 0;
|
Level = 0;
|
||||||
|
SampleCount = SampleCount.One;
|
||||||
ClearColor = Color.White;
|
ClearColor = Color.White;
|
||||||
LoadOp = loadOp;
|
LoadOp = loadOp;
|
||||||
StoreOp = storeOp;
|
StoreOp = storeOp;
|
||||||
|
@ -221,6 +241,7 @@ namespace MoonWorks.Graphics
|
||||||
depth = Depth,
|
depth = Depth,
|
||||||
layer = Layer,
|
layer = Layer,
|
||||||
level = Level,
|
level = Level,
|
||||||
|
sampleCount = (Refresh.SampleCount) SampleCount,
|
||||||
clearColor = new Refresh.Vec4
|
clearColor = new Refresh.Vec4
|
||||||
{
|
{
|
||||||
x = ClearColor.R / 255f,
|
x = ClearColor.R / 255f,
|
||||||
|
@ -323,35 +344,17 @@ namespace MoonWorks.Graphics
|
||||||
public struct ColorAttachmentDescription
|
public struct ColorAttachmentDescription
|
||||||
{
|
{
|
||||||
public TextureFormat Format;
|
public TextureFormat Format;
|
||||||
|
public SampleCount SampleCount;
|
||||||
public ColorAttachmentBlendState BlendState;
|
public ColorAttachmentBlendState BlendState;
|
||||||
|
|
||||||
public ColorAttachmentDescription(
|
public ColorAttachmentDescription(
|
||||||
TextureFormat format,
|
TextureFormat format,
|
||||||
ColorAttachmentBlendState blendState
|
ColorAttachmentBlendState blendState,
|
||||||
|
SampleCount sampleCount = SampleCount.One
|
||||||
) {
|
) {
|
||||||
Format = format;
|
Format = format;
|
||||||
|
SampleCount = sampleCount;
|
||||||
BlendState = blendState;
|
BlendState = blendState;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
|
||||||
public struct IndirectDrawCommand
|
|
||||||
{
|
|
||||||
public uint VertexCount;
|
|
||||||
public uint InstanceCount;
|
|
||||||
public uint FirstVertex;
|
|
||||||
public uint FirstInstance;
|
|
||||||
|
|
||||||
public IndirectDrawCommand(
|
|
||||||
uint vertexCount,
|
|
||||||
uint instanceCount,
|
|
||||||
uint firstVertex,
|
|
||||||
uint firstInstance
|
|
||||||
) {
|
|
||||||
VertexCount = vertexCount;
|
|
||||||
InstanceCount = instanceCount;
|
|
||||||
FirstVertex = firstVertex;
|
|
||||||
FirstInstance = firstInstance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ namespace MoonWorks.Graphics
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Buffers are generic data containers that can be used by the GPU.
|
/// Buffers are generic data containers that can be used by the GPU.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Buffer : RefreshResource
|
public class Buffer : GraphicsResource
|
||||||
{
|
{
|
||||||
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyBuffer;
|
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyBuffer;
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ namespace MoonWorks.Graphics
|
||||||
return new Buffer(
|
return new Buffer(
|
||||||
device,
|
device,
|
||||||
usageFlags,
|
usageFlags,
|
||||||
(uint) Marshal.SizeOf<T>() * elementCount
|
(uint) sizeof(T) * elementCount
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,29 +58,17 @@ namespace MoonWorks.Graphics
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reads data out of a buffer and into a span.
|
/// 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.
|
/// This operation is only guaranteed to read up-to-date data if GraphicsDevice.Wait is called first.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="data">The span that data will be copied to.</param>
|
/// <param name="data">The array that data will be copied to.</param>
|
||||||
/// <param name="dataLengthInBytes">The length of the data to read.</param>
|
/// <param name="dataLengthInBytes">The length of the data to read.</param>
|
||||||
public unsafe void GetData<T>(
|
public unsafe void GetData<T>(
|
||||||
Span<T> data,
|
T[] data,
|
||||||
uint dataLengthInBytes
|
uint dataLengthInBytes
|
||||||
) where T : unmanaged
|
) where T : unmanaged
|
||||||
{
|
{
|
||||||
#if DEBUG
|
fixed (T* ptr = &data[0])
|
||||||
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(
|
Refresh.Refresh_GetBufferData(
|
||||||
Device.Handle,
|
Device.Handle,
|
||||||
|
@ -90,52 +78,5 @@ 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,12 +1,10 @@
|
||||||
using RefreshCS;
|
using RefreshCS;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace MoonWorks.Graphics
|
namespace MoonWorks.Graphics
|
||||||
{
|
{
|
||||||
/// <summary>
|
public class ComputePipeline : GraphicsResource
|
||||||
/// Compute pipelines perform arbitrary parallel processing on input data.
|
|
||||||
/// </summary>
|
|
||||||
public class ComputePipeline : RefreshResource
|
|
||||||
{
|
{
|
||||||
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyComputePipeline;
|
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyComputePipeline;
|
||||||
|
|
||||||
|
@ -30,10 +28,6 @@ namespace MoonWorks.Graphics
|
||||||
device.Handle,
|
device.Handle,
|
||||||
refreshComputeShaderInfo
|
refreshComputeShaderInfo
|
||||||
);
|
);
|
||||||
if (Handle == IntPtr.Zero)
|
|
||||||
{
|
|
||||||
throw new Exception("Could not create compute pipeline!");
|
|
||||||
}
|
|
||||||
|
|
||||||
ComputeShaderInfo = computeShaderInfo;
|
ComputeShaderInfo = computeShaderInfo;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
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,20 +5,15 @@ using RefreshCS;
|
||||||
namespace MoonWorks.Graphics
|
namespace MoonWorks.Graphics
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Graphics pipelines encapsulate all of the render state in a single object. <br/>
|
/// Graphics pipelines encapsulate all of the render state in a single object.
|
||||||
/// These pipelines are bound before draw calls are issued.
|
/// These pipelines are bound before draw calls are issued.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class GraphicsPipeline : RefreshResource
|
public class GraphicsPipeline : GraphicsResource
|
||||||
{
|
{
|
||||||
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyGraphicsPipeline;
|
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyGraphicsPipeline;
|
||||||
|
|
||||||
public GraphicsShaderInfo VertexShaderInfo { get; }
|
public GraphicsShaderInfo VertexShaderInfo { get; }
|
||||||
public GraphicsShaderInfo FragmentShaderInfo { get; }
|
public GraphicsShaderInfo FragmentShaderInfo { get; }
|
||||||
public SampleCount SampleCount { get; }
|
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
internal GraphicsPipelineAttachmentInfo AttachmentInfo { get; }
|
|
||||||
#endif
|
|
||||||
|
|
||||||
public unsafe GraphicsPipeline(
|
public unsafe GraphicsPipeline(
|
||||||
GraphicsDevice device,
|
GraphicsDevice device,
|
||||||
|
@ -51,6 +46,7 @@ namespace MoonWorks.Graphics
|
||||||
for (var i = 0; i < attachmentInfo.ColorAttachmentDescriptions.Length; i += 1)
|
for (var i = 0; i < attachmentInfo.ColorAttachmentDescriptions.Length; i += 1)
|
||||||
{
|
{
|
||||||
colorAttachmentDescriptions[i].format = (Refresh.TextureFormat) attachmentInfo.ColorAttachmentDescriptions[i].Format;
|
colorAttachmentDescriptions[i].format = (Refresh.TextureFormat) attachmentInfo.ColorAttachmentDescriptions[i].Format;
|
||||||
|
colorAttachmentDescriptions[i].sampleCount = (Refresh.SampleCount) attachmentInfo.ColorAttachmentDescriptions[i].SampleCount;
|
||||||
colorAttachmentDescriptions[i].blendState = attachmentInfo.ColorAttachmentDescriptions[i].BlendState.ToRefresh();
|
colorAttachmentDescriptions[i].blendState = attachmentInfo.ColorAttachmentDescriptions[i].BlendState.ToRefresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,6 +85,7 @@ namespace MoonWorks.Graphics
|
||||||
refreshGraphicsPipelineCreateInfo.rasterizerState.depthBiasConstantFactor = rasterizerState.DepthBiasConstantFactor;
|
refreshGraphicsPipelineCreateInfo.rasterizerState.depthBiasConstantFactor = rasterizerState.DepthBiasConstantFactor;
|
||||||
refreshGraphicsPipelineCreateInfo.rasterizerState.depthBiasEnable = Conversions.BoolToByte(rasterizerState.DepthBiasEnable);
|
refreshGraphicsPipelineCreateInfo.rasterizerState.depthBiasEnable = Conversions.BoolToByte(rasterizerState.DepthBiasEnable);
|
||||||
refreshGraphicsPipelineCreateInfo.rasterizerState.depthBiasSlopeFactor = rasterizerState.DepthBiasSlopeFactor;
|
refreshGraphicsPipelineCreateInfo.rasterizerState.depthBiasSlopeFactor = rasterizerState.DepthBiasSlopeFactor;
|
||||||
|
refreshGraphicsPipelineCreateInfo.rasterizerState.depthClampEnable = Conversions.BoolToByte(rasterizerState.DepthClampEnable);
|
||||||
refreshGraphicsPipelineCreateInfo.rasterizerState.fillMode = (Refresh.FillMode) rasterizerState.FillMode;
|
refreshGraphicsPipelineCreateInfo.rasterizerState.fillMode = (Refresh.FillMode) rasterizerState.FillMode;
|
||||||
refreshGraphicsPipelineCreateInfo.rasterizerState.frontFace = (Refresh.FrontFace) rasterizerState.FrontFace;
|
refreshGraphicsPipelineCreateInfo.rasterizerState.frontFace = (Refresh.FrontFace) rasterizerState.FrontFace;
|
||||||
|
|
||||||
|
@ -105,21 +102,12 @@ namespace MoonWorks.Graphics
|
||||||
refreshGraphicsPipelineCreateInfo.attachmentInfo.hasDepthStencilAttachment = Conversions.BoolToByte(attachmentInfo.HasDepthStencilAttachment);
|
refreshGraphicsPipelineCreateInfo.attachmentInfo.hasDepthStencilAttachment = Conversions.BoolToByte(attachmentInfo.HasDepthStencilAttachment);
|
||||||
|
|
||||||
Handle = Refresh.Refresh_CreateGraphicsPipeline(device.Handle, refreshGraphicsPipelineCreateInfo);
|
Handle = Refresh.Refresh_CreateGraphicsPipeline(device.Handle, refreshGraphicsPipelineCreateInfo);
|
||||||
if (Handle == IntPtr.Zero)
|
|
||||||
{
|
|
||||||
throw new Exception("Could not create graphics pipeline!");
|
|
||||||
}
|
|
||||||
|
|
||||||
vertexAttributesHandle.Free();
|
vertexAttributesHandle.Free();
|
||||||
vertexBindingsHandle.Free();
|
vertexBindingsHandle.Free();
|
||||||
|
|
||||||
VertexShaderInfo = vertexShaderInfo;
|
VertexShaderInfo = vertexShaderInfo;
|
||||||
FragmentShaderInfo = fragmentShaderInfo;
|
FragmentShaderInfo = fragmentShaderInfo;
|
||||||
SampleCount = multisampleState.MultisampleCount;
|
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
AttachmentInfo = attachmentInfo;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ namespace MoonWorks.Graphics
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A sampler specifies how a texture will be sampled in a shader.
|
/// A sampler specifies how a texture will be sampled in a shader.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Sampler : RefreshResource
|
public class Sampler : GraphicsResource
|
||||||
{
|
{
|
||||||
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroySampler;
|
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroySampler;
|
||||||
|
|
||||||
|
|
|
@ -1,21 +1,22 @@
|
||||||
using RefreshCS;
|
using RefreshCS;
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace MoonWorks.Graphics
|
namespace MoonWorks.Graphics
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Shader modules expect input in Refresh bytecode format.
|
/// Shader modules expect input in SPIR-V bytecode format.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ShaderModule : RefreshResource
|
public class ShaderModule : GraphicsResource
|
||||||
{
|
{
|
||||||
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyShaderModule;
|
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyShaderModule;
|
||||||
|
|
||||||
public unsafe ShaderModule(GraphicsDevice device, string filePath) : base(device)
|
public unsafe ShaderModule(GraphicsDevice device, string filePath) : base(device)
|
||||||
{
|
{
|
||||||
using var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
|
using (FileStream stream = new FileStream(filePath, FileMode.Open))
|
||||||
Handle = CreateFromStream(device, stream);
|
{
|
||||||
|
Handle = CreateFromStream(device, stream);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe ShaderModule(GraphicsDevice device, Stream stream) : base(device)
|
public unsafe ShaderModule(GraphicsDevice device, Stream stream) : base(device)
|
||||||
|
@ -23,20 +24,19 @@ namespace MoonWorks.Graphics
|
||||||
Handle = CreateFromStream(device, stream);
|
Handle = CreateFromStream(device, stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static unsafe IntPtr CreateFromStream(GraphicsDevice device, Stream stream)
|
private unsafe static IntPtr CreateFromStream(GraphicsDevice device, Stream stream)
|
||||||
{
|
{
|
||||||
var bytecodeBuffer = NativeMemory.Alloc((nuint) stream.Length);
|
var bytecode = new byte[stream.Length];
|
||||||
var bytecodeSpan = new Span<byte>(bytecodeBuffer, (int) stream.Length);
|
stream.Read(bytecode, 0, (int) stream.Length);
|
||||||
stream.ReadExactly(bytecodeSpan);
|
|
||||||
|
|
||||||
Refresh.ShaderModuleCreateInfo shaderModuleCreateInfo;
|
fixed (byte* ptr = bytecode)
|
||||||
shaderModuleCreateInfo.codeSize = (nuint) stream.Length;
|
{
|
||||||
shaderModuleCreateInfo.byteCode = (nint) bytecodeBuffer;
|
Refresh.ShaderModuleCreateInfo shaderModuleCreateInfo;
|
||||||
|
shaderModuleCreateInfo.codeSize = (UIntPtr) bytecode.Length;
|
||||||
|
shaderModuleCreateInfo.byteCode = (IntPtr) ptr;
|
||||||
|
|
||||||
var shaderModule = Refresh.Refresh_CreateShaderModule(device.Handle, shaderModuleCreateInfo);
|
return Refresh.Refresh_CreateShaderModule(device.Handle, shaderModuleCreateInfo);
|
||||||
|
}
|
||||||
NativeMemory.Free(bytecodeBuffer);
|
|
||||||
return shaderModule;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using RefreshCS;
|
using RefreshCS;
|
||||||
|
|
||||||
namespace MoonWorks.Graphics
|
namespace MoonWorks.Graphics
|
||||||
|
@ -8,177 +7,115 @@ namespace MoonWorks.Graphics
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A container for pixel data.
|
/// A container for pixel data.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Texture : RefreshResource
|
public class Texture : GraphicsResource
|
||||||
{
|
{
|
||||||
public uint Width { get; internal set; }
|
public uint Width { get; }
|
||||||
public uint Height { get; internal set; }
|
public uint Height { get; }
|
||||||
public uint Depth { get; }
|
public uint Depth { get; }
|
||||||
public TextureFormat Format { get; internal set; }
|
public TextureFormat Format { get; }
|
||||||
public bool IsCube { get; }
|
public bool IsCube { get; }
|
||||||
public uint LevelCount { get; }
|
public uint LevelCount { get; }
|
||||||
public SampleCount SampleCount { get; }
|
public SampleCount SampleCount { get; }
|
||||||
public TextureUsageFlags UsageFlags { get; }
|
public TextureUsageFlags UsageFlags { get; }
|
||||||
public uint Size { get; }
|
|
||||||
|
|
||||||
// FIXME: this allocates a delegate instance
|
|
||||||
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyTexture;
|
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyTexture;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a 2D Texture using PNG or QOI data from raw byte data.
|
/// 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!
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static unsafe Texture FromImageBytes(
|
/// <param name="device"></param>
|
||||||
GraphicsDevice device,
|
/// <param name="commandBuffer"></param>
|
||||||
CommandBuffer commandBuffer,
|
/// <param name="filePath"></param>
|
||||||
Span<byte> data
|
/// <returns></returns>
|
||||||
) {
|
public static Texture LoadPNG(GraphicsDevice device, CommandBuffer commandBuffer, string filePath)
|
||||||
Texture texture;
|
|
||||||
|
|
||||||
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 = 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;
|
|
||||||
|
|
||||||
texture = new Texture(device, textureCreateInfo);
|
|
||||||
commandBuffer.SetTextureData(texture, pixels, (uint) len);
|
|
||||||
|
|
||||||
Refresh.Refresh_Image_Free(pixels);
|
|
||||||
}
|
|
||||||
|
|
||||||
return texture;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a 2D Texture using PNG or QOI data from a stream.
|
|
||||||
/// </summary>
|
|
||||||
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);
|
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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);
|
var pixels = Refresh.Refresh_Image_Load(
|
||||||
Texture texture;
|
filePath,
|
||||||
int faces;
|
out var width,
|
||||||
ParseDDS(reader, out var format, out var width, out var height, out var levels, out var isCube);
|
out var height,
|
||||||
|
out var channels
|
||||||
|
);
|
||||||
|
|
||||||
if (isCube)
|
var byteCount = (uint) (width * height * channels);
|
||||||
{
|
|
||||||
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)
|
TextureCreateInfo textureCreateInfo;
|
||||||
{
|
textureCreateInfo.Width = (uint) width;
|
||||||
for (int j = 0; j < levels; j += 1)
|
textureCreateInfo.Height = (uint) height;
|
||||||
{
|
textureCreateInfo.Depth = 1;
|
||||||
var levelWidth = width >> j;
|
textureCreateInfo.Format = TextureFormat.R8G8B8A8;
|
||||||
var levelHeight = height >> j;
|
textureCreateInfo.IsCube = false;
|
||||||
|
textureCreateInfo.LevelCount = 1;
|
||||||
|
textureCreateInfo.SampleCount = SampleCount.One;
|
||||||
|
textureCreateInfo.UsageFlags = TextureUsageFlags.Sampler;
|
||||||
|
|
||||||
var levelSize = CalculateDDSLevelSize(levelWidth, levelHeight, format);
|
var texture = new Texture(device, textureCreateInfo);
|
||||||
var byteBuffer = NativeMemory.Alloc((nuint) levelSize);
|
commandBuffer.SetTextureData(texture, pixels, byteCount);
|
||||||
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);
|
Refresh.Refresh_Image_Free(pixels);
|
||||||
commandBuffer.SetTextureData(textureSlice, (nint) byteBuffer, (uint) levelSize);
|
|
||||||
|
|
||||||
NativeMemory.Free(byteBuffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return texture;
|
return texture;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Saves RGBA or BGRA pixel data to a file in PNG format.
|
||||||
|
/// </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");
|
||||||
|
}
|
||||||
|
|
||||||
|
fixed (byte* ptr = &pixels[0])
|
||||||
|
{
|
||||||
|
Refresh.Refresh_Image_SavePNG(path, width, height, Conversions.BoolToByte(format == TextureFormat.B8G8R8A8), (IntPtr) ptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Texture LoadDDS(GraphicsDevice graphicsDevice, CommandBuffer commandBuffer, System.IO.Stream 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 = CreateTextureCube(graphicsDevice, (uint) width, format, TextureUsageFlags.Sampler, SampleCount.One, (uint) levels);
|
||||||
|
faces = 6;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
texture = CreateTexture2D(graphicsDevice, (uint) width, (uint) height, format, TextureUsageFlags.Sampler, SampleCount.One, (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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a 2D texture.
|
/// Creates a 2D texture.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -187,6 +124,7 @@ namespace MoonWorks.Graphics
|
||||||
/// <param name="height">The height of the texture.</param>
|
/// <param name="height">The height of the texture.</param>
|
||||||
/// <param name="format">The format of the texture.</param>
|
/// <param name="format">The format of the texture.</param>
|
||||||
/// <param name="usageFlags">Specifies how the texture will be used.</param>
|
/// <param name="usageFlags">Specifies how the texture will be used.</param>
|
||||||
|
/// <param name="sampleCount">Specifies the multisample count.</param>
|
||||||
/// <param name="levelCount">Specifies the number of mip levels.</param>
|
/// <param name="levelCount">Specifies the number of mip levels.</param>
|
||||||
public static Texture CreateTexture2D(
|
public static Texture CreateTexture2D(
|
||||||
GraphicsDevice device,
|
GraphicsDevice device,
|
||||||
|
@ -194,17 +132,18 @@ namespace MoonWorks.Graphics
|
||||||
uint height,
|
uint height,
|
||||||
TextureFormat format,
|
TextureFormat format,
|
||||||
TextureUsageFlags usageFlags,
|
TextureUsageFlags usageFlags,
|
||||||
uint levelCount = 1,
|
SampleCount sampleCount = SampleCount.One,
|
||||||
SampleCount sampleCount = SampleCount.One
|
uint levelCount = 1
|
||||||
) {
|
)
|
||||||
|
{
|
||||||
var textureCreateInfo = new TextureCreateInfo
|
var textureCreateInfo = new TextureCreateInfo
|
||||||
{
|
{
|
||||||
Width = width,
|
Width = width,
|
||||||
Height = height,
|
Height = height,
|
||||||
Depth = 1,
|
Depth = 1,
|
||||||
IsCube = false,
|
IsCube = false,
|
||||||
LevelCount = levelCount,
|
|
||||||
SampleCount = sampleCount,
|
SampleCount = sampleCount,
|
||||||
|
LevelCount = levelCount,
|
||||||
Format = format,
|
Format = format,
|
||||||
UsageFlags = usageFlags
|
UsageFlags = usageFlags
|
||||||
};
|
};
|
||||||
|
@ -221,6 +160,7 @@ namespace MoonWorks.Graphics
|
||||||
/// <param name="depth">The depth of the texture.</param>
|
/// <param name="depth">The depth of the texture.</param>
|
||||||
/// <param name="format">The format of the texture.</param>
|
/// <param name="format">The format of the texture.</param>
|
||||||
/// <param name="usageFlags">Specifies how the texture will be used.</param>
|
/// <param name="usageFlags">Specifies how the texture will be used.</param>
|
||||||
|
/// <param name="sampleCount">Specifies the multisample count.</param>
|
||||||
/// <param name="levelCount">Specifies the number of mip levels.</param>
|
/// <param name="levelCount">Specifies the number of mip levels.</param>
|
||||||
public static Texture CreateTexture3D(
|
public static Texture CreateTexture3D(
|
||||||
GraphicsDevice device,
|
GraphicsDevice device,
|
||||||
|
@ -229,14 +169,17 @@ namespace MoonWorks.Graphics
|
||||||
uint depth,
|
uint depth,
|
||||||
TextureFormat format,
|
TextureFormat format,
|
||||||
TextureUsageFlags usageFlags,
|
TextureUsageFlags usageFlags,
|
||||||
|
SampleCount sampleCount = SampleCount.One,
|
||||||
uint levelCount = 1
|
uint levelCount = 1
|
||||||
) {
|
)
|
||||||
|
{
|
||||||
var textureCreateInfo = new TextureCreateInfo
|
var textureCreateInfo = new TextureCreateInfo
|
||||||
{
|
{
|
||||||
Width = width,
|
Width = width,
|
||||||
Height = height,
|
Height = height,
|
||||||
Depth = depth,
|
Depth = depth,
|
||||||
IsCube = false,
|
IsCube = false,
|
||||||
|
SampleCount = sampleCount,
|
||||||
LevelCount = levelCount,
|
LevelCount = levelCount,
|
||||||
Format = format,
|
Format = format,
|
||||||
UsageFlags = usageFlags
|
UsageFlags = usageFlags
|
||||||
|
@ -252,20 +195,24 @@ namespace MoonWorks.Graphics
|
||||||
/// <param name="size">The length of one side of the cube.</param>
|
/// <param name="size">The length of one side of the cube.</param>
|
||||||
/// <param name="format">The format of the texture.</param>
|
/// <param name="format">The format of the texture.</param>
|
||||||
/// <param name="usageFlags">Specifies how the texture will be used.</param>
|
/// <param name="usageFlags">Specifies how the texture will be used.</param>
|
||||||
|
/// <param name="sampleCount">Specifies the multisample count.</param>
|
||||||
/// <param name="levelCount">Specifies the number of mip levels.</param>
|
/// <param name="levelCount">Specifies the number of mip levels.</param>
|
||||||
public static Texture CreateTextureCube(
|
public static Texture CreateTextureCube(
|
||||||
GraphicsDevice device,
|
GraphicsDevice device,
|
||||||
uint size,
|
uint size,
|
||||||
TextureFormat format,
|
TextureFormat format,
|
||||||
TextureUsageFlags usageFlags,
|
TextureUsageFlags usageFlags,
|
||||||
|
SampleCount sampleCount = SampleCount.One,
|
||||||
uint levelCount = 1
|
uint levelCount = 1
|
||||||
) {
|
)
|
||||||
|
{
|
||||||
var textureCreateInfo = new TextureCreateInfo
|
var textureCreateInfo = new TextureCreateInfo
|
||||||
{
|
{
|
||||||
Width = size,
|
Width = size,
|
||||||
Height = size,
|
Height = size,
|
||||||
Depth = 1,
|
Depth = 1,
|
||||||
IsCube = true,
|
IsCube = true,
|
||||||
|
SampleCount = sampleCount,
|
||||||
LevelCount = levelCount,
|
LevelCount = levelCount,
|
||||||
Format = format,
|
Format = format,
|
||||||
UsageFlags = usageFlags
|
UsageFlags = usageFlags
|
||||||
|
@ -294,10 +241,9 @@ namespace MoonWorks.Graphics
|
||||||
Height = textureCreateInfo.Height;
|
Height = textureCreateInfo.Height;
|
||||||
Depth = textureCreateInfo.Depth;
|
Depth = textureCreateInfo.Depth;
|
||||||
IsCube = textureCreateInfo.IsCube;
|
IsCube = textureCreateInfo.IsCube;
|
||||||
LevelCount = textureCreateInfo.LevelCount;
|
|
||||||
SampleCount = textureCreateInfo.SampleCount;
|
SampleCount = textureCreateInfo.SampleCount;
|
||||||
|
LevelCount = textureCreateInfo.LevelCount;
|
||||||
UsageFlags = textureCreateInfo.UsageFlags;
|
UsageFlags = textureCreateInfo.UsageFlags;
|
||||||
Size = Width * Height * BytesPerPixel(Format) / BlockSizeSquared(Format);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static implicit operator TextureSlice(Texture t) => new TextureSlice(t);
|
public static implicit operator TextureSlice(Texture t) => new TextureSlice(t);
|
||||||
|
@ -306,20 +252,22 @@ namespace MoonWorks.Graphics
|
||||||
// Should not be tracked, because swapchain textures are managed by Vulkan.
|
// Should not be tracked, because swapchain textures are managed by Vulkan.
|
||||||
internal Texture(
|
internal Texture(
|
||||||
GraphicsDevice device,
|
GraphicsDevice device,
|
||||||
TextureFormat format
|
IntPtr handle,
|
||||||
) : base(device)
|
TextureFormat format,
|
||||||
|
uint width,
|
||||||
|
uint height
|
||||||
|
) : base(device, false)
|
||||||
{
|
{
|
||||||
Handle = IntPtr.Zero;
|
Handle = handle;
|
||||||
|
|
||||||
Format = format;
|
Format = format;
|
||||||
Width = 0;
|
Width = width;
|
||||||
Height = 0;
|
Height = height;
|
||||||
Depth = 1;
|
Depth = 1;
|
||||||
IsCube = false;
|
IsCube = false;
|
||||||
LevelCount = 1;
|
|
||||||
SampleCount = SampleCount.One;
|
SampleCount = SampleCount.One;
|
||||||
|
LevelCount = 1;
|
||||||
UsageFlags = TextureUsageFlags.ColorTarget;
|
UsageFlags = TextureUsageFlags.ColorTarget;
|
||||||
Size = Width * Height * BytesPerPixel(Format) / BlockSizeSquared(Format);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DDS loading extension, based on MojoDDS
|
// DDS loading extension, based on MojoDDS
|
||||||
|
@ -336,12 +284,14 @@ namespace MoonWorks.Graphics
|
||||||
const uint DDS_MAGIC = 0x20534444;
|
const uint DDS_MAGIC = 0x20534444;
|
||||||
const uint DDS_HEADERSIZE = 124;
|
const uint DDS_HEADERSIZE = 124;
|
||||||
const uint DDS_PIXFMTSIZE = 32;
|
const uint DDS_PIXFMTSIZE = 32;
|
||||||
|
const uint DDSD_CAPS = 0x1;
|
||||||
const uint DDSD_HEIGHT = 0x2;
|
const uint DDSD_HEIGHT = 0x2;
|
||||||
const uint DDSD_WIDTH = 0x4;
|
const uint DDSD_WIDTH = 0x4;
|
||||||
const uint DDSD_PITCH = 0x8;
|
const uint DDSD_PITCH = 0x8;
|
||||||
|
const uint DDSD_FMT = 0x1000;
|
||||||
const uint DDSD_LINEARSIZE = 0x80000;
|
const uint DDSD_LINEARSIZE = 0x80000;
|
||||||
const uint DDSD_REQ = (
|
const uint DDSD_REQ = (
|
||||||
DDSD_HEIGHT | DDSD_WIDTH
|
DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_FMT
|
||||||
);
|
);
|
||||||
const uint DDSCAPS_MIPMAP = 0x400000;
|
const uint DDSCAPS_MIPMAP = 0x400000;
|
||||||
const uint DDSCAPS_TEXTURE = 0x1000;
|
const uint DDSCAPS_TEXTURE = 0x1000;
|
||||||
|
@ -589,155 +539,5 @@ 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,9 +2,6 @@
|
||||||
|
|
||||||
namespace MoonWorks.Graphics
|
namespace MoonWorks.Graphics
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Defines how color blending will be performed in a GraphicsPipeline.
|
|
||||||
/// </summary>
|
|
||||||
public struct ColorAttachmentBlendState
|
public struct ColorAttachmentBlendState
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -3,7 +3,7 @@ using System.Runtime.InteropServices;
|
||||||
namespace MoonWorks.Graphics
|
namespace MoonWorks.Graphics
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Information that the compute pipeline needs about a compute shader.
|
/// Information that the pipeline needs about a shader.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public struct ComputeShaderInfo
|
public struct ComputeShaderInfo
|
||||||
{
|
{
|
||||||
|
@ -24,7 +24,7 @@ namespace MoonWorks.Graphics
|
||||||
{
|
{
|
||||||
ShaderModule = shaderModule,
|
ShaderModule = shaderModule,
|
||||||
EntryPointName = entryPointName,
|
EntryPointName = entryPointName,
|
||||||
UniformBufferSize = (uint) Marshal.SizeOf<T>(),
|
UniformBufferSize = (uint) sizeof(T),
|
||||||
BufferBindingCount = bufferBindingCount,
|
BufferBindingCount = bufferBindingCount,
|
||||||
ImageBindingCount = imageBindingCount
|
ImageBindingCount = imageBindingCount
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
namespace MoonWorks.Graphics
|
namespace MoonWorks.Graphics
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// All of the information that is used to create a GraphicsPipeline.
|
|
||||||
/// </summary>
|
|
||||||
public struct GraphicsPipelineCreateInfo
|
public struct GraphicsPipelineCreateInfo
|
||||||
{
|
{
|
||||||
public DepthStencilState DepthStencilState;
|
public DepthStencilState DepthStencilState;
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
namespace MoonWorks.Graphics
|
namespace MoonWorks.Graphics
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Information that the pipeline needs about a graphics shader.
|
/// Information that the pipeline needs about a shader.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public struct GraphicsShaderInfo
|
public struct GraphicsShaderInfo
|
||||||
{
|
{
|
||||||
|
@ -22,7 +22,7 @@ namespace MoonWorks.Graphics
|
||||||
{
|
{
|
||||||
ShaderModule = shaderModule,
|
ShaderModule = shaderModule,
|
||||||
EntryPointName = entryPointName,
|
EntryPointName = entryPointName,
|
||||||
UniformBufferSize = (uint) Marshal.SizeOf<T>(),
|
UniformBufferSize = (uint) sizeof(T),
|
||||||
SamplerBindingCount = samplerBindingCount
|
SamplerBindingCount = samplerBindingCount
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,13 +13,5 @@
|
||||||
MultisampleCount = SampleCount.One,
|
MultisampleCount = SampleCount.One,
|
||||||
SampleMask = uint.MaxValue
|
SampleMask = uint.MaxValue
|
||||||
};
|
};
|
||||||
|
|
||||||
public MultisampleState(
|
|
||||||
SampleCount sampleCount,
|
|
||||||
uint sampleMask = uint.MaxValue
|
|
||||||
) {
|
|
||||||
MultisampleCount = sampleCount;
|
|
||||||
SampleMask = sampleMask;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
/// Factor applied to a fragment's slope in depth bias calculations. Only applies if depth biasing is enabled.
|
/// Factor applied to a fragment's slope in depth bias calculations. Only applies if depth biasing is enabled.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public float DepthBiasSlopeFactor;
|
public float DepthBiasSlopeFactor;
|
||||||
|
public bool DepthClampEnable;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Specifies how triangles should be drawn.
|
/// Specifies how triangles should be drawn.
|
||||||
|
|
|
@ -2,60 +2,21 @@
|
||||||
|
|
||||||
namespace MoonWorks.Graphics
|
namespace MoonWorks.Graphics
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// All of the information that is used to create a sampler.
|
|
||||||
/// </summary>
|
|
||||||
public struct SamplerCreateInfo
|
public struct SamplerCreateInfo
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Minification filter mode. Used when the image is downscaled.
|
|
||||||
/// </summary>
|
|
||||||
public Filter MinFilter;
|
public Filter MinFilter;
|
||||||
/// <summary>
|
|
||||||
/// Magnification filter mode. Used when the image is upscaled.
|
|
||||||
/// </summary>
|
|
||||||
public Filter MagFilter;
|
public Filter MagFilter;
|
||||||
/// <summary>
|
|
||||||
/// Filter mode applied to mipmap lookups.
|
|
||||||
/// </summary>
|
|
||||||
public SamplerMipmapMode MipmapMode;
|
public SamplerMipmapMode MipmapMode;
|
||||||
/// <summary>
|
|
||||||
/// Horizontal addressing mode.
|
|
||||||
/// </summary>
|
|
||||||
public SamplerAddressMode AddressModeU;
|
public SamplerAddressMode AddressModeU;
|
||||||
/// <summary>
|
|
||||||
/// Vertical addressing mode.
|
|
||||||
/// </summary>
|
|
||||||
public SamplerAddressMode AddressModeV;
|
public SamplerAddressMode AddressModeV;
|
||||||
/// <summary>
|
|
||||||
/// Depth addressing mode.
|
|
||||||
/// </summary>
|
|
||||||
public SamplerAddressMode AddressModeW;
|
public SamplerAddressMode AddressModeW;
|
||||||
/// <summary>
|
|
||||||
/// Bias value added to mipmap level of detail calculation.
|
|
||||||
/// </summary>
|
|
||||||
public float MipLodBias;
|
public float MipLodBias;
|
||||||
/// <summary>
|
|
||||||
/// Enables anisotropic filtering.
|
|
||||||
/// </summary>
|
|
||||||
public bool AnisotropyEnable;
|
public bool AnisotropyEnable;
|
||||||
/// <summary>
|
|
||||||
/// Maximum anisotropy value.
|
|
||||||
/// </summary>
|
|
||||||
public float MaxAnisotropy;
|
public float MaxAnisotropy;
|
||||||
public bool CompareEnable;
|
public bool CompareEnable;
|
||||||
public CompareOp CompareOp;
|
public CompareOp CompareOp;
|
||||||
/// <summary>
|
|
||||||
/// Clamps the LOD value to a specified minimum.
|
|
||||||
/// </summary>
|
|
||||||
public float MinLod;
|
public float MinLod;
|
||||||
/// <summary>
|
|
||||||
/// Clamps the LOD value to a specified maximum.
|
|
||||||
/// </summary>
|
|
||||||
public float MaxLod;
|
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 BorderColor BorderColor;
|
||||||
|
|
||||||
public static readonly SamplerCreateInfo AnisotropicClamp = new SamplerCreateInfo
|
public static readonly SamplerCreateInfo AnisotropicClamp = new SamplerCreateInfo
|
||||||
|
|
|
@ -2,17 +2,14 @@
|
||||||
|
|
||||||
namespace MoonWorks.Graphics
|
namespace MoonWorks.Graphics
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// All of the information that is used to create a texture.
|
|
||||||
/// </summary>
|
|
||||||
public struct TextureCreateInfo
|
public struct TextureCreateInfo
|
||||||
{
|
{
|
||||||
public uint Width;
|
public uint Width;
|
||||||
public uint Height;
|
public uint Height;
|
||||||
public uint Depth;
|
public uint Depth;
|
||||||
public bool IsCube;
|
public bool IsCube;
|
||||||
public uint LevelCount;
|
|
||||||
public SampleCount SampleCount;
|
public SampleCount SampleCount;
|
||||||
|
public uint LevelCount;
|
||||||
public TextureFormat Format;
|
public TextureFormat Format;
|
||||||
public TextureUsageFlags UsageFlags;
|
public TextureUsageFlags UsageFlags;
|
||||||
|
|
||||||
|
@ -24,8 +21,8 @@ namespace MoonWorks.Graphics
|
||||||
height = Height,
|
height = Height,
|
||||||
depth = Depth,
|
depth = Depth,
|
||||||
isCube = Conversions.BoolToByte(IsCube),
|
isCube = Conversions.BoolToByte(IsCube),
|
||||||
levelCount = LevelCount,
|
|
||||||
sampleCount = (Refresh.SampleCount) SampleCount,
|
sampleCount = (Refresh.SampleCount) SampleCount,
|
||||||
|
levelCount = LevelCount,
|
||||||
format = (Refresh.TextureFormat) Format,
|
format = (Refresh.TextureFormat) Format,
|
||||||
usageFlags = (Refresh.TextureUsageFlags) UsageFlags
|
usageFlags = (Refresh.TextureUsageFlags) UsageFlags
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
namespace MoonWorks.Graphics
|
namespace MoonWorks.Graphics
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Specifies how the vertex shader will interpet vertex data in a buffer.
|
/// Specifies how to interpet vertex data in a buffer to be passed to the vertex shader.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public struct VertexInputState
|
public struct VertexInputState
|
||||||
{
|
{
|
||||||
|
@ -10,61 +10,16 @@
|
||||||
|
|
||||||
public static readonly VertexInputState Empty = new VertexInputState
|
public static readonly VertexInputState Empty = new VertexInputState
|
||||||
{
|
{
|
||||||
VertexBindings = System.Array.Empty<VertexBinding>(),
|
VertexBindings = new VertexBinding[0],
|
||||||
VertexAttributes = System.Array.Empty<VertexAttribute>()
|
VertexAttributes = new VertexAttribute[0]
|
||||||
};
|
};
|
||||||
|
|
||||||
public VertexInputState(
|
public VertexInputState(
|
||||||
VertexBinding vertexBinding,
|
VertexBinding vertexBinding,
|
||||||
VertexAttribute[] vertexAttributes
|
params VertexAttribute[] vertexAttributes
|
||||||
) {
|
) {
|
||||||
VertexBindings = new VertexBinding[] { vertexBinding };
|
VertexBindings = new VertexBinding[] { vertexBinding };
|
||||||
VertexAttributes = vertexAttributes;
|
VertexAttributes = vertexAttributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public VertexInputState(
|
|
||||||
VertexBinding[] vertexBindings,
|
|
||||||
VertexAttribute[] vertexAttributes
|
|
||||||
) {
|
|
||||||
VertexBindings = vertexBindings;
|
|
||||||
VertexAttributes = vertexAttributes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public VertexInputState(
|
|
||||||
VertexBindingAndAttributes bindingAndAttributes
|
|
||||||
) {
|
|
||||||
VertexBindings = new VertexBinding[] { bindingAndAttributes.VertexBinding };
|
|
||||||
VertexAttributes = bindingAndAttributes.VertexAttributes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public VertexInputState(
|
|
||||||
VertexBindingAndAttributes[] bindingAndAttributesArray
|
|
||||||
) {
|
|
||||||
VertexBindings = new VertexBinding[bindingAndAttributesArray.Length];
|
|
||||||
var attributesLength = 0;
|
|
||||||
|
|
||||||
for (var i = 0; i < bindingAndAttributesArray.Length; i += 1)
|
|
||||||
{
|
|
||||||
VertexBindings[i] = bindingAndAttributesArray[i].VertexBinding;
|
|
||||||
attributesLength += bindingAndAttributesArray[i].VertexAttributes.Length;
|
|
||||||
}
|
|
||||||
|
|
||||||
VertexAttributes = new VertexAttribute[attributesLength];
|
|
||||||
|
|
||||||
var attributeIndex = 0;
|
|
||||||
for (var i = 0; i < bindingAndAttributesArray.Length; i += 1)
|
|
||||||
{
|
|
||||||
for (var j = 0; j < bindingAndAttributesArray[i].VertexAttributes.Length; j += 1)
|
|
||||||
{
|
|
||||||
VertexAttributes[attributeIndex] = bindingAndAttributesArray[i].VertexAttributes[j];
|
|
||||||
attributeIndex += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static VertexInputState CreateSingleBinding<T>() where T : unmanaged, IVertexType
|
|
||||||
{
|
|
||||||
return new VertexInputState(VertexBindingAndAttributes.Create<T>(0));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,34 +0,0 @@
|
||||||
#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);
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
#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,8 +14,6 @@ namespace MoonWorks.Graphics
|
||||||
public uint Layer { get; }
|
public uint Layer { get; }
|
||||||
public uint Level { 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)
|
public TextureSlice(Texture texture)
|
||||||
{
|
{
|
||||||
Texture = texture;
|
Texture = texture;
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
namespace MoonWorks
|
||||||
|
{
|
||||||
|
public static class Conversions
|
||||||
|
{
|
||||||
|
public static byte BoolToByte(bool b)
|
||||||
|
{
|
||||||
|
return (byte) (b ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool ByteToBool(byte b)
|
||||||
|
{
|
||||||
|
return b != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Graphics.VertexElementFormat TypeToVertexElementFormat(System.Type type)
|
||||||
|
{
|
||||||
|
if (type == typeof(uint))
|
||||||
|
{
|
||||||
|
return Graphics.VertexElementFormat.UInt;
|
||||||
|
}
|
||||||
|
if (type == typeof(float))
|
||||||
|
{
|
||||||
|
return Graphics.VertexElementFormat.Float;
|
||||||
|
}
|
||||||
|
else if (type == typeof(Math.Float.Vector2))
|
||||||
|
{
|
||||||
|
return Graphics.VertexElementFormat.Vector2;
|
||||||
|
}
|
||||||
|
else if (type == typeof(Math.Float.Vector3))
|
||||||
|
{
|
||||||
|
return Graphics.VertexElementFormat.Vector3;
|
||||||
|
}
|
||||||
|
else if (type == typeof(Math.Float.Vector4))
|
||||||
|
{
|
||||||
|
return Graphics.VertexElementFormat.Vector4;
|
||||||
|
}
|
||||||
|
else if (type == typeof(Graphics.Color))
|
||||||
|
{
|
||||||
|
return Graphics.VertexElementFormat.Color;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new System.ArgumentException(
|
||||||
|
"Cannot automatically convert this type to a VertexElementFormat!"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,41 +0,0 @@
|
||||||
namespace MoonWorks.Graphics
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A convenience structure for pairing a vertex binding with its associated attributes.
|
|
||||||
/// </summary>
|
|
||||||
public struct VertexBindingAndAttributes
|
|
||||||
{
|
|
||||||
public VertexBinding VertexBinding { get; }
|
|
||||||
public VertexAttribute[] VertexAttributes { get; }
|
|
||||||
|
|
||||||
public VertexBindingAndAttributes(VertexBinding binding, VertexAttribute[] attributes)
|
|
||||||
{
|
|
||||||
VertexBinding = binding;
|
|
||||||
VertexAttributes = attributes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static VertexBindingAndAttributes Create<T>(uint bindingIndex, uint locationOffset = 0, VertexInputRate inputRate = VertexInputRate.Vertex) where T : unmanaged, IVertexType
|
|
||||||
{
|
|
||||||
VertexBinding binding = VertexBinding.Create<T>(bindingIndex, inputRate);
|
|
||||||
VertexAttribute[] attributes = new VertexAttribute[T.Formats.Length];
|
|
||||||
uint offset = 0;
|
|
||||||
|
|
||||||
for (uint i = 0; i < T.Formats.Length; i += 1)
|
|
||||||
{
|
|
||||||
var format = T.Formats[i];
|
|
||||||
|
|
||||||
attributes[i] = new VertexAttribute
|
|
||||||
{
|
|
||||||
Binding = bindingIndex,
|
|
||||||
Location = locationOffset + i,
|
|
||||||
Format = format,
|
|
||||||
Offset = offset
|
|
||||||
};
|
|
||||||
|
|
||||||
offset += Conversions.VertexElementFormatSize(format);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new VertexBindingAndAttributes(binding, attributes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,9 +3,6 @@ using SDL2;
|
||||||
|
|
||||||
namespace MoonWorks.Input
|
namespace MoonWorks.Input
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Represents a specific joystick direction on a gamepad.
|
|
||||||
/// </summary>
|
|
||||||
public class Axis
|
public class Axis
|
||||||
{
|
{
|
||||||
public Gamepad Parent { get; }
|
public Gamepad Parent { get; }
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
namespace MoonWorks.Input
|
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
|
public enum AxisButtonCode
|
||||||
{
|
{
|
||||||
LeftX_Left,
|
LeftX_Left,
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
namespace MoonWorks.Input
|
namespace MoonWorks.Input
|
||||||
{
|
{
|
||||||
/// <summary>
|
// Enum values are equivalent to SDL GameControllerAxis
|
||||||
/// 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
|
public enum AxisCode
|
||||||
{
|
{
|
||||||
LeftX,
|
LeftX,
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
namespace MoonWorks.Input
|
namespace MoonWorks.Input
|
||||||
{
|
{
|
||||||
/// <summary>
|
// Enum values are equivalent to the SDL GameControllerButton value.
|
||||||
/// 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
|
public enum GamepadButtonCode
|
||||||
{
|
{
|
||||||
A,
|
A,
|
||||||
|
|
|
@ -1,42 +1,14 @@
|
||||||
namespace MoonWorks.Input
|
namespace MoonWorks.Input
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Container for the current state of a binary input.
|
|
||||||
/// </summary>
|
|
||||||
public struct ButtonState
|
public struct ButtonState
|
||||||
{
|
{
|
||||||
public ButtonStatus ButtonStatus { get; }
|
public ButtonStatus ButtonStatus { get; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// True if the button was pressed this frame.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsPressed => ButtonStatus == ButtonStatus.Pressed;
|
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;
|
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;
|
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;
|
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)
|
public ButtonState(ButtonStatus buttonStatus)
|
||||||
{
|
{
|
||||||
ButtonStatus = buttonStatus;
|
ButtonStatus = buttonStatus;
|
||||||
|
@ -46,34 +18,26 @@
|
||||||
{
|
{
|
||||||
if (isPressed)
|
if (isPressed)
|
||||||
{
|
{
|
||||||
if (IsUp)
|
if (ButtonStatus == ButtonStatus.Pressed)
|
||||||
|
{
|
||||||
|
return new ButtonState(ButtonStatus.Held);
|
||||||
|
}
|
||||||
|
else if (ButtonStatus == ButtonStatus.Released)
|
||||||
{
|
{
|
||||||
return new ButtonState(ButtonStatus.Pressed);
|
return new ButtonState(ButtonStatus.Pressed);
|
||||||
}
|
}
|
||||||
else
|
else if (ButtonStatus == ButtonStatus.Held)
|
||||||
{
|
{
|
||||||
return new ButtonState(ButtonStatus.Held);
|
return new ButtonState(ButtonStatus.Held);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
return new ButtonState(ButtonStatus.Released);
|
||||||
if (IsDown)
|
|
||||||
{
|
|
||||||
return new ButtonState(ButtonStatus.Released);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return new ButtonState(ButtonStatus.Idle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Combines two button states. Useful for alt control sets or input buffering.
|
|
||||||
/// </summary>
|
|
||||||
public static ButtonState operator |(ButtonState a, ButtonState b)
|
public static ButtonState operator |(ButtonState a, ButtonState b)
|
||||||
{
|
{
|
||||||
if (a.ButtonStatus == ButtonStatus.Idle || a.ButtonStatus == ButtonStatus.Released)
|
if (a.ButtonStatus == ButtonStatus.Released)
|
||||||
{
|
{
|
||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,17 @@
|
||||||
namespace MoonWorks.Input
|
namespace MoonWorks.Input
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Represents the current status of a binary input.
|
|
||||||
/// </summary>
|
|
||||||
public enum ButtonStatus
|
public enum ButtonStatus
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates that the button was not pressed last frame and is still not pressed.
|
/// Indicates that the input is not pressed.
|
||||||
/// </summary>
|
|
||||||
Idle,
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates that the button was released this frame.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Released,
|
Released,
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates that the button was pressed this frame.
|
/// Indicates that the input was pressed this frame.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Pressed,
|
Pressed,
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates that the button has been held for multiple frames.
|
/// Indicates that the input has been held for multiple frames.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Held
|
Held
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
namespace MoonWorks.Input
|
||||||
|
{
|
||||||
|
public enum DeviceKind
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Keyboard,
|
||||||
|
Mouse,
|
||||||
|
Gamepad,
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,12 +5,6 @@ using SDL2;
|
||||||
|
|
||||||
namespace MoonWorks.Input
|
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
|
public class Gamepad
|
||||||
{
|
{
|
||||||
internal IntPtr Handle;
|
internal IntPtr Handle;
|
||||||
|
@ -57,14 +51,7 @@ namespace MoonWorks.Input
|
||||||
|
|
||||||
public bool IsDummy => Handle == IntPtr.Zero;
|
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; }
|
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; }
|
public VirtualButton AnyPressedButton { get; private set; }
|
||||||
|
|
||||||
private Dictionary<SDL.SDL_GameControllerButton, GamepadButton> EnumToButton;
|
private Dictionary<SDL.SDL_GameControllerButton, GamepadButton> EnumToButton;
|
||||||
|
@ -113,13 +100,13 @@ namespace MoonWorks.Input
|
||||||
|
|
||||||
LeftXLeft = new AxisButton(LeftX, false);
|
LeftXLeft = new AxisButton(LeftX, false);
|
||||||
LeftXRight = new AxisButton(LeftX, true);
|
LeftXRight = new AxisButton(LeftX, true);
|
||||||
LeftYUp = new AxisButton(LeftY, true);
|
LeftYUp = new AxisButton(LeftY, false);
|
||||||
LeftYDown = new AxisButton(LeftY, false);
|
LeftYDown = new AxisButton(LeftY, true);
|
||||||
|
|
||||||
RightXLeft = new AxisButton(RightX, false);
|
RightXLeft = new AxisButton(RightX, false);
|
||||||
RightXRight = new AxisButton(RightX, true);
|
RightXRight = new AxisButton(RightX, true);
|
||||||
RightYUp = new AxisButton(RightY, true);
|
RightYUp = new AxisButton(RightY, false);
|
||||||
RightYDown = new AxisButton(RightY, false);
|
RightYDown = new AxisButton(RightY, true);
|
||||||
|
|
||||||
TriggerLeft = new Trigger(this, TriggerCode.Left, SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERLEFT);
|
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);
|
TriggerRight = new Trigger(this, TriggerCode.Right, SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERRIGHT);
|
||||||
|
@ -208,20 +195,6 @@ 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()
|
internal void Update()
|
||||||
{
|
{
|
||||||
AnyPressed = false;
|
AnyPressed = false;
|
||||||
|
@ -280,25 +253,16 @@ namespace MoonWorks.Input
|
||||||
) == 0;
|
) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Obtains a gamepad button object given a button code.
|
|
||||||
/// </summary>
|
|
||||||
public GamepadButton Button(GamepadButtonCode buttonCode)
|
public GamepadButton Button(GamepadButtonCode buttonCode)
|
||||||
{
|
{
|
||||||
return EnumToButton[(SDL.SDL_GameControllerButton) buttonCode];
|
return EnumToButton[(SDL.SDL_GameControllerButton) buttonCode];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Obtains an axis button object given a button code.
|
|
||||||
/// </summary>
|
|
||||||
public AxisButton Button(AxisButtonCode axisButtonCode)
|
public AxisButton Button(AxisButtonCode axisButtonCode)
|
||||||
{
|
{
|
||||||
return AxisButtonCodeToAxisButton[axisButtonCode];
|
return AxisButtonCodeToAxisButton[axisButtonCode];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Obtains a trigger button object given a button code.
|
|
||||||
/// </summary>
|
|
||||||
public TriggerButton Button(TriggerCode triggerCode)
|
public TriggerButton Button(TriggerCode triggerCode)
|
||||||
{
|
{
|
||||||
return TriggerCodeToTriggerButton[triggerCode];
|
return TriggerCodeToTriggerButton[triggerCode];
|
||||||
|
|
|
@ -3,65 +3,31 @@ using System;
|
||||||
|
|
||||||
namespace MoonWorks.Input
|
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 class Inputs
|
||||||
{
|
{
|
||||||
public const int MAX_GAMEPADS = 4;
|
public const int MAX_GAMEPADS = 4;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The reference to the Keyboard input abstraction.
|
|
||||||
/// </summary>
|
|
||||||
public Keyboard Keyboard { get; }
|
public Keyboard Keyboard { get; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The reference to the Mouse input abstraction.
|
|
||||||
/// </summary>
|
|
||||||
public Mouse Mouse { get; }
|
public Mouse Mouse { get; }
|
||||||
|
|
||||||
Gamepad[] Gamepads;
|
Gamepad[] gamepads;
|
||||||
|
|
||||||
public static event Action<char> TextInput;
|
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; }
|
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 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()
|
internal Inputs()
|
||||||
{
|
{
|
||||||
Keyboard = new Keyboard();
|
Keyboard = new Keyboard();
|
||||||
Mouse = new Mouse();
|
Mouse = new Mouse();
|
||||||
|
|
||||||
Gamepads = new Gamepad[MAX_GAMEPADS];
|
gamepads = new Gamepad[MAX_GAMEPADS];
|
||||||
|
|
||||||
// initialize dummy controllers
|
// initialize dummy controllers
|
||||||
for (var slot = 0; slot < MAX_GAMEPADS; slot += 1)
|
for (var slot = 0; slot < MAX_GAMEPADS; slot += 1)
|
||||||
{
|
{
|
||||||
Gamepads[slot] = new Gamepad(IntPtr.Zero, slot);
|
gamepads[slot] = new Gamepad(IntPtr.Zero, slot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,6 +37,7 @@ namespace MoonWorks.Input
|
||||||
AnyPressed = false;
|
AnyPressed = false;
|
||||||
AnyPressedButton = default; // DeviceKind.None
|
AnyPressedButton = default; // DeviceKind.None
|
||||||
|
|
||||||
|
Mouse.Wheel = 0;
|
||||||
Keyboard.Update();
|
Keyboard.Update();
|
||||||
|
|
||||||
if (Keyboard.AnyPressed)
|
if (Keyboard.AnyPressed)
|
||||||
|
@ -87,7 +54,7 @@ namespace MoonWorks.Input
|
||||||
AnyPressedButton = Mouse.AnyPressedButton;
|
AnyPressedButton = Mouse.AnyPressedButton;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var gamepad in Gamepads)
|
foreach (var gamepad in gamepads)
|
||||||
{
|
{
|
||||||
gamepad.Update();
|
gamepad.Update();
|
||||||
|
|
||||||
|
@ -99,11 +66,6 @@ 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)
|
public bool GamepadExists(int slot)
|
||||||
{
|
{
|
||||||
if (slot < 0 || slot >= MAX_GAMEPADS)
|
if (slot < 0 || slot >= MAX_GAMEPADS)
|
||||||
|
@ -111,19 +73,13 @@ namespace MoonWorks.Input
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return !Gamepads[slot].IsDummy;
|
return !gamepads[slot].IsDummy;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
// From 0-4
|
||||||
/// 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)
|
public Gamepad GetGamepad(int slot)
|
||||||
{
|
{
|
||||||
return Gamepads[slot];
|
return gamepads[slot];
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void AddGamepad(int index)
|
internal void AddGamepad(int index)
|
||||||
|
@ -132,40 +88,24 @@ namespace MoonWorks.Input
|
||||||
{
|
{
|
||||||
if (!GamepadExists(slot))
|
if (!GamepadExists(slot))
|
||||||
{
|
{
|
||||||
var openResult = SDL.SDL_GameControllerOpen(index);
|
gamepads[slot].Handle = SDL.SDL_GameControllerOpen(index);
|
||||||
if (openResult == 0)
|
System.Console.WriteLine($"Gamepad added to slot {slot}!");
|
||||||
{
|
|
||||||
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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.LogInfo("Too many gamepads already!");
|
System.Console.WriteLine("Too many gamepads already!");
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void RemoveGamepad(int joystickInstanceID)
|
internal void RemoveGamepad(int joystickInstanceID)
|
||||||
{
|
{
|
||||||
for (int slot = 0; slot < MAX_GAMEPADS; slot += 1)
|
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);
|
SDL.SDL_GameControllerClose(gamepads[slot].Handle);
|
||||||
Gamepads[slot].Unregister();
|
gamepads[slot].Handle = IntPtr.Zero;
|
||||||
Logger.LogInfo($"Removing gamepad from slot {slot}!");
|
System.Console.WriteLine($"Removing gamepad from slot {slot}!");
|
||||||
OnGamepadDisconnected(slot);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
namespace MoonWorks.Input
|
namespace MoonWorks.Input
|
||||||
{
|
{
|
||||||
/// <summary>
|
// Enum values are equivalent to the SDL Scancode value.
|
||||||
/// 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
|
public enum KeyCode : int
|
||||||
{
|
{
|
||||||
Unknown = 0,
|
Unknown = 0,
|
||||||
|
|
|
@ -1,27 +1,17 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using SDL2;
|
using SDL2;
|
||||||
|
|
||||||
namespace MoonWorks.Input
|
namespace MoonWorks.Input
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// The keyboard input device abstraction.
|
|
||||||
/// </summary>
|
|
||||||
public class Keyboard
|
public class Keyboard
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// True if any button on the keyboard is active. Useful for input remapping.
|
|
||||||
/// </summary>
|
|
||||||
public bool AnyPressed { get; private set; }
|
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 KeyboardButton AnyPressedButton { get; private set; }
|
||||||
|
|
||||||
internal IntPtr State { get; private set; }
|
public IntPtr State { get; private set; }
|
||||||
|
|
||||||
private KeyCode[] KeyCodes;
|
|
||||||
private KeyboardButton[] Keys { get; }
|
private KeyboardButton[] Keys { get; }
|
||||||
private int numKeys;
|
private int numKeys;
|
||||||
|
|
||||||
|
@ -51,10 +41,8 @@ namespace MoonWorks.Input
|
||||||
{
|
{
|
||||||
SDL.SDL_GetKeyboardState(out numKeys);
|
SDL.SDL_GetKeyboardState(out numKeys);
|
||||||
|
|
||||||
KeyCodes = Enum.GetValues<KeyCode>();
|
|
||||||
Keys = new KeyboardButton[numKeys];
|
Keys = new KeyboardButton[numKeys];
|
||||||
|
foreach (KeyCode keycode in Enum.GetValues(typeof(KeyCode)))
|
||||||
foreach (KeyCode keycode in KeyCodes)
|
|
||||||
{
|
{
|
||||||
Keys[(int) keycode] = new KeyboardButton(this, keycode);
|
Keys[(int) keycode] = new KeyboardButton(this, keycode);
|
||||||
}
|
}
|
||||||
|
@ -66,18 +54,18 @@ namespace MoonWorks.Input
|
||||||
|
|
||||||
State = SDL.SDL_GetKeyboardState(out _);
|
State = SDL.SDL_GetKeyboardState(out _);
|
||||||
|
|
||||||
foreach (KeyCode keycode in KeyCodes)
|
foreach (int keycode in Enum.GetValues(typeof(KeyCode)))
|
||||||
{
|
{
|
||||||
var button = Keys[(int) keycode];
|
var button = Keys[keycode];
|
||||||
button.Update();
|
button.Update();
|
||||||
|
|
||||||
if (button.IsPressed)
|
if (button.IsPressed)
|
||||||
{
|
{
|
||||||
if (TextInputBindings.TryGetValue(keycode, out var textIndex))
|
if (TextInputBindings.TryGetValue((KeyCode) keycode, out var textIndex))
|
||||||
{
|
{
|
||||||
Inputs.OnTextInput(TextInputCharacters[(textIndex)]);
|
Inputs.OnTextInput(TextInputCharacters[(textIndex)]);
|
||||||
}
|
}
|
||||||
else if (IsDown(KeyCode.LeftControl) && keycode == KeyCode.V)
|
else if (IsDown(KeyCode.LeftControl) && (KeyCode) keycode == KeyCode.V)
|
||||||
{
|
{
|
||||||
Inputs.OnTextInput(TextInputCharacters[6]);
|
Inputs.OnTextInput(TextInputCharacters[6]);
|
||||||
}
|
}
|
||||||
|
@ -88,65 +76,31 @@ 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)
|
public bool IsDown(KeyCode keycode)
|
||||||
{
|
{
|
||||||
return Keys[(int) keycode].IsDown;
|
return Keys[(int) keycode].IsDown;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public bool IsPressed(KeyCode keycode)
|
||||||
/// True if the button was let go this frame.
|
{
|
||||||
/// </summary>
|
return Keys[(int) keycode].IsPressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsHeld(KeyCode keycode)
|
||||||
|
{
|
||||||
|
return Keys[(int) keycode].IsHeld;
|
||||||
|
}
|
||||||
|
|
||||||
public bool IsReleased(KeyCode keycode)
|
public bool IsReleased(KeyCode keycode)
|
||||||
{
|
{
|
||||||
return Keys[(int) keycode].IsReleased;
|
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)
|
public KeyboardButton Button(KeyCode keycode)
|
||||||
{
|
{
|
||||||
return Keys[(int) keycode];
|
return Keys[(int) keycode];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the state of a keyboard button from a key code.
|
|
||||||
/// </summary>
|
|
||||||
public ButtonState ButtonState(KeyCode keycode)
|
public ButtonState ButtonState(KeyCode keycode)
|
||||||
{
|
{
|
||||||
return Keys[(int) keycode].State;
|
return Keys[(int) keycode].State;
|
||||||
|
|
|
@ -3,44 +3,25 @@ using SDL2;
|
||||||
|
|
||||||
namespace MoonWorks.Input
|
namespace MoonWorks.Input
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// The mouse input device abstraction.
|
|
||||||
/// </summary>
|
|
||||||
public class Mouse
|
public class Mouse
|
||||||
{
|
{
|
||||||
public MouseButton LeftButton { get; }
|
public MouseButton LeftButton { get; }
|
||||||
public MouseButton MiddleButton { get; }
|
public MouseButton MiddleButton { get; }
|
||||||
public MouseButton RightButton { get; }
|
public MouseButton RightButton { get; }
|
||||||
public MouseButton X1Button { get; }
|
|
||||||
public MouseButton X2Button { get; }
|
|
||||||
|
|
||||||
public int X { get; private set; }
|
public int X { get; private set; }
|
||||||
public int Y { get; private set; }
|
public int Y { get; private set; }
|
||||||
public int DeltaX { get; private set; }
|
public int DeltaX { get; private set; }
|
||||||
public int DeltaY { get; private set; }
|
public int DeltaY { get; private set; }
|
||||||
|
|
||||||
// note that this is a delta value
|
public int Wheel { get; internal set; }
|
||||||
public int Wheel { get; private set; }
|
|
||||||
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; }
|
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 MouseButton AnyPressedButton { get; private set; }
|
||||||
|
|
||||||
internal uint ButtonMask { get; private set; }
|
public uint ButtonMask { get; private set; }
|
||||||
|
|
||||||
private bool relativeMode;
|
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
|
public bool RelativeMode
|
||||||
{
|
{
|
||||||
get => relativeMode;
|
get => relativeMode;
|
||||||
|
@ -55,37 +36,29 @@ namespace MoonWorks.Input
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool hidden;
|
private readonly Dictionary<MouseButtonCode, MouseButton> CodeToButton;
|
||||||
/// <summary>
|
|
||||||
/// If set to true, the OS cursor will not be shown in your application window.
|
private IEnumerable<MouseButton> Buttons
|
||||||
/// </summary>
|
|
||||||
public bool Hidden
|
|
||||||
{
|
{
|
||||||
get => hidden;
|
get
|
||||||
set
|
|
||||||
{
|
{
|
||||||
hidden = value;
|
yield return LeftButton;
|
||||||
SDL.SDL_ShowCursor(hidden ? SDL.SDL_DISABLE : SDL.SDL_ENABLE);
|
yield return MiddleButton;
|
||||||
|
yield return RightButton;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly Dictionary<MouseButtonCode, MouseButton> CodeToButton;
|
public Mouse()
|
||||||
|
|
||||||
internal Mouse()
|
|
||||||
{
|
{
|
||||||
LeftButton = new MouseButton(this, MouseButtonCode.Left, SDL.SDL_BUTTON_LMASK);
|
LeftButton = new MouseButton(this, MouseButtonCode.Left, SDL.SDL_BUTTON_LMASK);
|
||||||
MiddleButton = new MouseButton(this, MouseButtonCode.Middle, SDL.SDL_BUTTON_MMASK);
|
MiddleButton = new MouseButton(this, MouseButtonCode.Middle, SDL.SDL_BUTTON_MMASK);
|
||||||
RightButton = new MouseButton(this, MouseButtonCode.Right, SDL.SDL_BUTTON_RMASK);
|
RightButton = new MouseButton(this, MouseButtonCode.Right, SDL.SDL_BUTTON_RMASK);
|
||||||
X1Button = new MouseButton(this, MouseButtonCode.X1, SDL.SDL_BUTTON_X1MASK);
|
|
||||||
X2Button = new MouseButton(this, MouseButtonCode.X2, SDL.SDL_BUTTON_X2MASK);
|
|
||||||
|
|
||||||
CodeToButton = new Dictionary<MouseButtonCode, MouseButton>
|
CodeToButton = new Dictionary<MouseButtonCode, MouseButton>
|
||||||
{
|
{
|
||||||
{ MouseButtonCode.Left, LeftButton },
|
{ MouseButtonCode.Left, LeftButton },
|
||||||
{ MouseButtonCode.Right, RightButton },
|
{ MouseButtonCode.Right, RightButton },
|
||||||
{ MouseButtonCode.Middle, MiddleButton },
|
{ MouseButtonCode.Middle, MiddleButton }
|
||||||
{ MouseButtonCode.X1, X1Button },
|
|
||||||
{ MouseButtonCode.X2, X2Button }
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,10 +74,11 @@ namespace MoonWorks.Input
|
||||||
DeltaX = deltaX;
|
DeltaX = deltaX;
|
||||||
DeltaY = deltaY;
|
DeltaY = deltaY;
|
||||||
|
|
||||||
Wheel = WheelRaw - previousWheelRaw;
|
LeftButton.Update();
|
||||||
previousWheelRaw = WheelRaw;
|
MiddleButton.Update();
|
||||||
|
RightButton.Update();
|
||||||
|
|
||||||
foreach (var button in CodeToButton.Values)
|
foreach (var button in Buttons)
|
||||||
{
|
{
|
||||||
button.Update();
|
button.Update();
|
||||||
|
|
||||||
|
@ -116,17 +90,6 @@ 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)
|
public ButtonState ButtonState(MouseButtonCode buttonCode)
|
||||||
{
|
{
|
||||||
return CodeToButton[buttonCode].State;
|
return CodeToButton[buttonCode].State;
|
||||||
|
|
|
@ -1,14 +1,9 @@
|
||||||
namespace MoonWorks.Input
|
namespace MoonWorks.Input
|
||||||
{
|
{
|
||||||
/// <summary>
|
public enum MouseButtonCode
|
||||||
/// Can be used to determine virtual mouse button state without a direct reference to the button object.
|
{
|
||||||
/// </summary>
|
Left,
|
||||||
public enum MouseButtonCode
|
Right,
|
||||||
{
|
Middle
|
||||||
Left,
|
}
|
||||||
Right,
|
|
||||||
Middle,
|
|
||||||
X1,
|
|
||||||
X2
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,6 @@ using SDL2;
|
||||||
|
|
||||||
namespace MoonWorks.Input
|
namespace MoonWorks.Input
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Represents a trigger input on a gamepad.
|
|
||||||
/// </summary>
|
|
||||||
public class Trigger
|
public class Trigger
|
||||||
{
|
{
|
||||||
public Gamepad Parent { get; }
|
public Gamepad Parent { get; }
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
namespace MoonWorks.Input
|
namespace MoonWorks.Input
|
||||||
{
|
{
|
||||||
/// <summary>
|
// Enum values correspond to SDL GameControllerAxis
|
||||||
/// 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
|
public enum TriggerCode
|
||||||
{
|
{
|
||||||
Left = 4,
|
Left = 4,
|
||||||
|
|
|
@ -1,42 +1,29 @@
|
||||||
namespace MoonWorks.Input
|
namespace MoonWorks.Input
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// VirtualButtons map inputs to binary inputs, like a trigger threshold or joystick axis threshold.
|
|
||||||
/// </summary>
|
|
||||||
public abstract class VirtualButton
|
public abstract class VirtualButton
|
||||||
{
|
{
|
||||||
public ButtonState State { get; protected set; }
|
public ButtonState State { get; protected set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// True if the button was pressed this exact frame.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsPressed => State.IsPressed;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// True if the button has been continuously held for more than one frame.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsHeld => State.IsHeld;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// True if the button is pressed or held.
|
/// True if the button is pressed or held.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsDown => State.IsDown;
|
public bool IsDown => State.IsDown;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// True if the button was released this frame.
|
/// True if the button has been continuously held for more than one frame.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsHeld => State.IsHeld;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// True if the button was pressed this exact frame.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsPressed => State.IsPressed;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// True if the button is not pressed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsReleased => State.IsReleased;
|
public bool IsReleased => State.IsReleased;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// True if the button was not pressed the previous or current frame.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsIdle => State.IsIdle;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// True if the button is idle or released.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsUp => State.IsUp;
|
|
||||||
|
|
||||||
internal virtual void Update()
|
internal virtual void Update()
|
||||||
{
|
{
|
||||||
State = State.Update(CheckPressed());
|
State = State.Update(CheckPressed());
|
||||||
|
|
|
@ -1,15 +1,11 @@
|
||||||
namespace MoonWorks.Input
|
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 class AxisButton : VirtualButton
|
||||||
{
|
{
|
||||||
public Axis Parent { get; }
|
public Axis Parent { get; }
|
||||||
public AxisButtonCode Code { get; }
|
public AxisButtonCode Code { get; }
|
||||||
|
|
||||||
private float threshold = 0.5f;
|
private float threshold = 0.9f;
|
||||||
public float Threshold
|
public float Threshold
|
||||||
{
|
{
|
||||||
get => threshold;
|
get => threshold;
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
namespace MoonWorks.Input
|
namespace MoonWorks.Input
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// A dummy button that can never be pressed. Used for the dummy gamepad.
|
|
||||||
/// </summary>
|
|
||||||
public class EmptyButton : VirtualButton
|
public class EmptyButton : VirtualButton
|
||||||
{
|
{
|
||||||
internal override bool CheckPressed()
|
internal override bool CheckPressed()
|
||||||
|
|
|
@ -2,9 +2,6 @@ using SDL2;
|
||||||
|
|
||||||
namespace MoonWorks.Input
|
namespace MoonWorks.Input
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// A virtual button corresponding to a gamepad button.
|
|
||||||
/// </summary>
|
|
||||||
public class GamepadButton : VirtualButton
|
public class GamepadButton : VirtualButton
|
||||||
{
|
{
|
||||||
public Gamepad Parent { get; }
|
public Gamepad Parent { get; }
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace MoonWorks.Input
|
namespace MoonWorks.Input
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// A virtual button corresponding to a keyboard button.
|
|
||||||
/// </summary>
|
|
||||||
public class KeyboardButton : VirtualButton
|
public class KeyboardButton : VirtualButton
|
||||||
{
|
{
|
||||||
Keyboard Parent;
|
Keyboard Parent;
|
||||||
public KeyCode KeyCode { get; }
|
KeyCode KeyCode;
|
||||||
|
|
||||||
internal KeyboardButton(Keyboard parent, KeyCode keyCode)
|
internal KeyboardButton(Keyboard parent, KeyCode keyCode)
|
||||||
{
|
{
|
||||||
|
@ -14,9 +13,9 @@ namespace MoonWorks.Input
|
||||||
KeyCode = keyCode;
|
KeyCode = keyCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal unsafe override bool CheckPressed()
|
internal override bool CheckPressed()
|
||||||
{
|
{
|
||||||
return Conversions.ByteToBool(((byte*) Parent.State)[(int) KeyCode]);
|
return Conversions.ByteToBool(Marshal.ReadByte(Parent.State, (int) KeyCode));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
namespace MoonWorks.Input
|
namespace MoonWorks.Input
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// A virtual button corresponding to a mouse button.
|
|
||||||
/// </summary>
|
|
||||||
public class MouseButton : VirtualButton
|
public class MouseButton : VirtualButton
|
||||||
{
|
{
|
||||||
Mouse Parent;
|
Mouse Parent;
|
||||||
|
|
|
@ -1,9 +1,5 @@
|
||||||
namespace MoonWorks.Input
|
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 class TriggerButton : VirtualButton
|
||||||
{
|
{
|
||||||
public Trigger Parent { get; }
|
public Trigger Parent { get; }
|
||||||
|
|
|
@ -5,9 +5,9 @@ namespace MoonWorks
|
||||||
{
|
{
|
||||||
public static class Logger
|
public static class Logger
|
||||||
{
|
{
|
||||||
public static Action<string> LogInfo = LogInfoDefault;
|
public static Action<string> LogInfo;
|
||||||
public static Action<string> LogWarn = LogWarnDefault;
|
public static Action<string> LogWarn;
|
||||||
public static Action<string> LogError = LogErrorDefault;
|
public static Action<string> LogError;
|
||||||
|
|
||||||
private static RefreshCS.Refresh.Refresh_LogFunc LogInfoFunc = RefreshLogInfo;
|
private static RefreshCS.Refresh.Refresh_LogFunc LogInfoFunc = RefreshLogInfo;
|
||||||
private static RefreshCS.Refresh.Refresh_LogFunc LogWarnFunc = RefreshLogWarn;
|
private static RefreshCS.Refresh.Refresh_LogFunc LogWarnFunc = RefreshLogWarn;
|
||||||
|
@ -15,6 +15,19 @@ namespace MoonWorks
|
||||||
|
|
||||||
internal static void Initialize()
|
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(
|
Refresh.Refresh_HookLogFunctions(
|
||||||
LogInfoFunc,
|
LogInfoFunc,
|
||||||
LogWarnFunc,
|
LogWarnFunc,
|
||||||
|
@ -22,30 +35,6 @@ 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)
|
private static void RefreshLogInfo(IntPtr msg)
|
||||||
{
|
{
|
||||||
LogInfo(UTF8_ToManaged(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>
|
public struct Fix64 : IEquatable<Fix64>, IComparable<Fix64>
|
||||||
{
|
{
|
||||||
public long RawValue { get; }
|
private readonly long RawValue;
|
||||||
|
|
||||||
const long MAX_VALUE = long.MaxValue;
|
const long MAX_VALUE = long.MaxValue;
|
||||||
const long MIN_VALUE = long.MinValue;
|
const long MIN_VALUE = long.MinValue;
|
||||||
|
@ -17,9 +17,6 @@ namespace MoonWorks.Math.Fixed
|
||||||
const long PI_TIMES_2 = 0x6487ED511;
|
const long PI_TIMES_2 = 0x6487ED511;
|
||||||
const long PI = 0x3243F6A88;
|
const long PI = 0x3243F6A88;
|
||||||
const long PI_OVER_2 = 0x1921FB544;
|
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 MaxValue = new Fix64(MAX_VALUE);
|
||||||
public static readonly Fix64 MinValue = new Fix64(MIN_VALUE);
|
public static readonly Fix64 MinValue = new Fix64(MIN_VALUE);
|
||||||
|
@ -31,10 +28,6 @@ namespace MoonWorks.Math.Fixed
|
||||||
public static readonly Fix64 PiOver4 = PiOver2 / new Fix64(2);
|
public static readonly Fix64 PiOver4 = PiOver2 / new Fix64(2);
|
||||||
public static readonly Fix64 PiTimes2 = new Fix64(PI_TIMES_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);
|
const int LUT_SIZE = (int)(PI_OVER_2 >> 15);
|
||||||
static readonly Fix64 LutInterval = (Fix64)(LUT_SIZE - 1) / PiOver2;
|
static readonly Fix64 LutInterval = (Fix64)(LUT_SIZE - 1) / PiOver2;
|
||||||
|
|
||||||
|
@ -59,11 +52,6 @@ namespace MoonWorks.Math.Fixed
|
||||||
return new Fix64(numerator) / new Fix64(denominator);
|
return new Fix64(numerator) / new Fix64(denominator);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Fix64 FromRawValue(long value)
|
|
||||||
{
|
|
||||||
return new Fix64(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the fractional component of this Fix64 value.
|
/// Gets the fractional component of this Fix64 value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -72,6 +60,30 @@ namespace MoonWorks.Math.Fixed
|
||||||
return new Fix64(number.RawValue & 0x00000000FFFFFFFF);
|
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>
|
/// <summary>
|
||||||
/// Returns an int indicating the sign of a Fix64 number.
|
/// Returns an int indicating the sign of a Fix64 number.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -105,27 +117,27 @@ namespace MoonWorks.Math.Fixed
|
||||||
return new Fix64((value.RawValue + mask) ^ mask);
|
return new Fix64((value.RawValue + mask) ^ mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the largest integral value less than or equal to the specified number.
|
/// Returns the largest integral value less than or equal to the specified number.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static Fix64 Floor(Fix64 value)
|
public static Fix64 Floor(Fix64 value)
|
||||||
{
|
{
|
||||||
// Zero out the fractional part.
|
// Zero out the fractional part.
|
||||||
return new Fix64((long)((ulong)value.RawValue & 0xFFFFFFFF00000000));
|
return new Fix64((long)((ulong)value.RawValue & 0xFFFFFFFF00000000));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the smallest integral value that is greater than or equal to the specified number.
|
/// Returns the smallest integral value that is greater than or equal to the specified number.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static Fix64 Ceiling(Fix64 value)
|
public static Fix64 Ceiling(Fix64 value)
|
||||||
{
|
{
|
||||||
return value.IsFractional ? Floor(value) + One : value;
|
return value.IsFractional ? Floor(value) + One : value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Rounds to the nearest integral value.
|
/// Rounds to the nearest integral value.
|
||||||
/// If the value is halfway between an even and an uneven value, returns the even value.
|
/// If the value is halfway between an even and an uneven value, returns the even value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static Fix64 Round(Fix64 value)
|
public static Fix64 Round(Fix64 value)
|
||||||
{
|
{
|
||||||
var fractionalPart = value.RawValue & 0x00000000FFFFFFFF;
|
var fractionalPart = value.RawValue & 0x00000000FFFFFFFF;
|
||||||
|
@ -200,145 +212,7 @@ namespace MoonWorks.Math.Fixed
|
||||||
return Fix64.Floor(value / step) * step;
|
return Fix64.Floor(value / step) * step;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exponentiation functions
|
// Trigonometry 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>
|
/// <summary>
|
||||||
/// Returns the square root of the given Fix64 value.
|
/// Returns the square root of the given Fix64 value.
|
||||||
|
|
|
@ -996,8 +996,8 @@ namespace MoonWorks.Math.Fixed
|
||||||
z = Vector3.Normalize(forward);
|
z = Vector3.Normalize(forward);
|
||||||
Vector3.Cross(ref forward, ref up, out x);
|
Vector3.Cross(ref forward, ref up, out x);
|
||||||
Vector3.Cross(ref x, ref forward, out y);
|
Vector3.Cross(ref x, ref forward, out y);
|
||||||
x = Vector3.Normalize(x);
|
x.Normalize();
|
||||||
y = Vector3.Normalize(y);
|
y.Normalize();
|
||||||
|
|
||||||
result = new Matrix4x4();
|
result = new Matrix4x4();
|
||||||
result.Right = x;
|
result.Right = x;
|
||||||
|
|
|
@ -214,6 +214,23 @@ 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>
|
/// <summary>
|
||||||
/// Returns a <see cref="String"/> representation of this <see cref="Quaternion"/> in the format:
|
/// 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"/>]}
|
/// {X:[<see cref="X"/>] Y:[<see cref="Y"/>] Z:[<see cref="Z"/>] W:[<see cref="W"/>]}
|
||||||
|
@ -742,16 +759,12 @@ namespace MoonWorks.Math.Fixed
|
||||||
/// <param name="result">The unit length quaternion an output parameter.</param>
|
/// <param name="result">The unit length quaternion an output parameter.</param>
|
||||||
public static void Normalize(ref Quaternion quaternion, out Quaternion result)
|
public static void Normalize(ref Quaternion quaternion, out Quaternion result)
|
||||||
{
|
{
|
||||||
Fix64 lengthSquared = (quaternion.X * quaternion.X) + (quaternion.Y * quaternion.Y) +
|
Fix64 num = Fix64.One / (Fix64.Sqrt(
|
||||||
(quaternion.Z * quaternion.Z) + (quaternion.W * quaternion.W);
|
(quaternion.X * quaternion.X) +
|
||||||
|
(quaternion.Y * quaternion.Y) +
|
||||||
if (lengthSquared == Fix64.Zero)
|
(quaternion.Z * quaternion.Z) +
|
||||||
{
|
(quaternion.W * quaternion.W)
|
||||||
result = Identity;
|
));
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Fix64 num = Fix64.One / Fix64.Sqrt(lengthSquared);
|
|
||||||
result.X = quaternion.X * num;
|
result.X = quaternion.X * num;
|
||||||
result.Y = quaternion.Y * num;
|
result.Y = quaternion.Y * num;
|
||||||
result.Z = quaternion.Z * num;
|
result.Z = quaternion.Z * num;
|
||||||
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
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,6 +200,16 @@ namespace MoonWorks.Math.Fixed
|
||||||
return (X * X) + (Y * Y);
|
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>
|
/// <summary>
|
||||||
/// Turns this <see cref="Vector2"/> to an angle in radians.
|
/// Turns this <see cref="Vector2"/> to an angle in radians.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -413,14 +423,7 @@ namespace MoonWorks.Math.Fixed
|
||||||
/// <returns>Unit vector.</returns>
|
/// <returns>Unit vector.</returns>
|
||||||
public static Vector2 Normalize(Vector2 value)
|
public static Vector2 Normalize(Vector2 value)
|
||||||
{
|
{
|
||||||
Fix64 lengthSquared = (value.X * value.X) + (value.Y * value.Y);
|
Fix64 val = Fix64.One / Fix64.Sqrt((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.X *= val;
|
||||||
value.Y *= val;
|
value.Y *= val;
|
||||||
return value;
|
return value;
|
||||||
|
|
|
@ -309,6 +309,21 @@ namespace MoonWorks.Math.Fixed
|
||||||
return (X * X) + (Y * Y) + (Z * Z);
|
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>
|
/// <summary>
|
||||||
/// Returns a <see cref="String"/> representation of this <see cref="Vector3"/> in the format:
|
/// 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"/>]}
|
/// {X:[<see cref="X"/>] Y:[<see cref="Y"/>] Z:[<see cref="Z"/>]}
|
||||||
|
@ -718,14 +733,11 @@ namespace MoonWorks.Math.Fixed
|
||||||
/// <returns>Unit vector.</returns>
|
/// <returns>Unit vector.</returns>
|
||||||
public static Vector3 Normalize(Vector3 value)
|
public static Vector3 Normalize(Vector3 value)
|
||||||
{
|
{
|
||||||
Fix64 lengthSquared = (value.X * value.X) + (value.Y * value.Y) + (value.Z * value.Z);
|
Fix64 factor = Fix64.One / Fix64.Sqrt(
|
||||||
|
(value.X * value.X) +
|
||||||
if (lengthSquared == Fix64.Zero)
|
(value.Y * value.Y) +
|
||||||
{
|
(value.Z * value.Z)
|
||||||
return Zero;
|
);
|
||||||
}
|
|
||||||
|
|
||||||
Fix64 factor = Fix64.One / Fix64.Sqrt(lengthSquared);
|
|
||||||
return new Vector3(
|
return new Vector3(
|
||||||
value.X * factor,
|
value.X * factor,
|
||||||
value.Y * factor,
|
value.Y * factor,
|
||||||
|
|
|
@ -611,7 +611,7 @@ namespace MoonWorks.Math.Float
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Vector3.Cross(ref cameraUpVector, ref vector, out vector3);
|
Vector3.Cross(ref cameraUpVector, ref vector, out vector3);
|
||||||
vector3 = Vector3.Normalize(vector3);
|
vector3.Normalize();
|
||||||
Vector3.Cross(ref vector, ref vector3, out vector2);
|
Vector3.Cross(ref vector, ref vector3, out vector2);
|
||||||
result.M11 = vector3.X;
|
result.M11 = vector3.X;
|
||||||
result.M12 = vector3.Y;
|
result.M12 = vector3.Y;
|
||||||
|
@ -730,16 +730,16 @@ namespace MoonWorks.Math.Float
|
||||||
Vector3.Forward;
|
Vector3.Forward;
|
||||||
}
|
}
|
||||||
Vector3.Cross(ref rotateAxis, ref vector, out vector3);
|
Vector3.Cross(ref rotateAxis, ref vector, out vector3);
|
||||||
vector3 = Vector3.Normalize(vector3);
|
vector3.Normalize();
|
||||||
Vector3.Cross(ref vector3, ref rotateAxis, out vector);
|
Vector3.Cross(ref vector3, ref rotateAxis, out vector);
|
||||||
vector = Vector3.Normalize(vector);
|
vector.Normalize();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Vector3.Cross(ref rotateAxis, ref vector2, out vector3);
|
Vector3.Cross(ref rotateAxis, ref vector2, out vector3);
|
||||||
vector3 = Vector3.Normalize(vector3);
|
vector3.Normalize();
|
||||||
Vector3.Cross(ref vector3, ref vector4, out vector);
|
Vector3.Cross(ref vector3, ref vector4, out vector);
|
||||||
vector = Vector3.Normalize(vector);
|
vector.Normalize();
|
||||||
}
|
}
|
||||||
|
|
||||||
result.M11 = vector3.X;
|
result.M11 = vector3.X;
|
||||||
|
@ -1701,8 +1701,8 @@ namespace MoonWorks.Math.Float
|
||||||
Vector3.Normalize(ref forward, out z);
|
Vector3.Normalize(ref forward, out z);
|
||||||
Vector3.Cross(ref forward, ref up, out x);
|
Vector3.Cross(ref forward, ref up, out x);
|
||||||
Vector3.Cross(ref x, ref forward, out y);
|
Vector3.Cross(ref x, ref forward, out y);
|
||||||
x = Vector3.Normalize(x);
|
x.Normalize();
|
||||||
y = Vector3.Normalize(y);
|
y.Normalize();
|
||||||
|
|
||||||
result = new Matrix4x4();
|
result = new Matrix4x4();
|
||||||
result.Right = x;
|
result.Right = x;
|
||||||
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
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,6 +194,16 @@ namespace MoonWorks.Math.Float
|
||||||
return (X * X) + (Y * Y);
|
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>
|
/// <summary>
|
||||||
/// Turns this <see cref="Vector2"/> to an angle in radians.
|
/// Turns this <see cref="Vector2"/> to an angle in radians.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -707,14 +717,7 @@ namespace MoonWorks.Math.Float
|
||||||
/// <returns>Unit vector.</returns>
|
/// <returns>Unit vector.</returns>
|
||||||
public static Vector2 Normalize(Vector2 value)
|
public static Vector2 Normalize(Vector2 value)
|
||||||
{
|
{
|
||||||
float lengthSquared = (value.X * value.X) + (value.Y * value.Y);
|
float val = 1.0f / (float) System.Math.Sqrt((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.X *= val;
|
||||||
value.Y *= val;
|
value.Y *= val;
|
||||||
return value;
|
return value;
|
||||||
|
|
|
@ -302,6 +302,21 @@ namespace MoonWorks.Math.Float
|
||||||
return (X * X) + (Y * Y) + (Z * Z);
|
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>
|
/// <summary>
|
||||||
/// Returns a <see cref="String"/> representation of this <see cref="Vector3"/> in the format:
|
/// 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"/>]}
|
/// {X:[<see cref="X"/>] Y:[<see cref="Y"/>] Z:[<see cref="Z"/>]}
|
||||||
|
@ -885,14 +900,11 @@ namespace MoonWorks.Math.Float
|
||||||
/// <returns>Unit vector.</returns>
|
/// <returns>Unit vector.</returns>
|
||||||
public static Vector3 Normalize(Vector3 value)
|
public static Vector3 Normalize(Vector3 value)
|
||||||
{
|
{
|
||||||
float lengthSquared = (value.X * value.X) + (value.Y * value.Y) + (value.Z * value.Z);
|
float factor = 1.0f / (float) System.Math.Sqrt(
|
||||||
|
(value.X * value.X) +
|
||||||
if (lengthSquared == 0f)
|
(value.Y * value.Y) +
|
||||||
{
|
(value.Z * value.Z)
|
||||||
return Zero;
|
);
|
||||||
}
|
|
||||||
|
|
||||||
float factor = 1.0f / System.MathF.Sqrt(lengthSquared);
|
|
||||||
return new Vector3(
|
return new Vector3(
|
||||||
value.X * factor,
|
value.X * factor,
|
||||||
value.Y * factor,
|
value.Y * factor,
|
||||||
|
|
|
@ -267,6 +267,23 @@ namespace MoonWorks.Math.Float
|
||||||
return (X * X) + (Y * Y) + (Z * Z) + (W * W);
|
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()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return (
|
return (
|
||||||
|
@ -836,15 +853,12 @@ namespace MoonWorks.Math.Float
|
||||||
/// <returns>Unit vector.</returns>
|
/// <returns>Unit vector.</returns>
|
||||||
public static Vector4 Normalize(Vector4 vector)
|
public static Vector4 Normalize(Vector4 vector)
|
||||||
{
|
{
|
||||||
var lengthSquared = (vector.X * vector.X) + (vector.Y * vector.Y) +
|
float factor = 1.0f / (float) System.Math.Sqrt(
|
||||||
(vector.Z * vector.Z) + (vector.W * vector.W);
|
(vector.X * vector.X) +
|
||||||
|
(vector.Y * vector.Y) +
|
||||||
if (lengthSquared == 0)
|
(vector.Z * vector.Z) +
|
||||||
{
|
(vector.W * vector.W)
|
||||||
return Zero;
|
);
|
||||||
}
|
|
||||||
|
|
||||||
float factor = 1.0f / System.MathF.Sqrt(lengthSquared);
|
|
||||||
return new Vector4(
|
return new Vector4(
|
||||||
vector.X * factor,
|
vector.X * factor,
|
||||||
vector.Y * factor,
|
vector.Y * factor,
|
||||||
|
@ -856,20 +870,16 @@ namespace MoonWorks.Math.Float
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new <see cref="Vector4"/> that contains a normalized values from another vector.
|
/// Creates a new <see cref="Vector4"/> that contains a normalized values from another vector.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="vector">Source <see cref="Vector4"/>.</param>
|
/// <param name="value">Source <see cref="Vector4"/>.</param>
|
||||||
/// <param name="result">Unit vector as an output parameter.</param>
|
/// <param name="result">Unit vector as an output parameter.</param>
|
||||||
public static void Normalize(ref Vector4 vector, out Vector4 result)
|
public static void Normalize(ref Vector4 vector, out Vector4 result)
|
||||||
{
|
{
|
||||||
float lengthSquared = (vector.X * vector.X) + (vector.Y * vector.Y) +
|
float factor = 1.0f / (float) System.Math.Sqrt(
|
||||||
(vector.Z * vector.Z) + (vector.W * vector.W);
|
(vector.X * vector.X) +
|
||||||
|
(vector.Y * vector.Y) +
|
||||||
if (lengthSquared == 0)
|
(vector.Z * vector.Z) +
|
||||||
{
|
(vector.W * vector.W)
|
||||||
result = Zero;
|
);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
float factor = 1.0f / System.MathF.Sqrt(lengthSquared);
|
|
||||||
result.X = vector.X * factor;
|
result.X = vector.X * factor;
|
||||||
result.Y = vector.Y * factor;
|
result.Y = vector.Y * factor;
|
||||||
result.Z = vector.Z * factor;
|
result.Z = vector.Z * factor;
|
||||||
|
|
|
@ -160,26 +160,6 @@ namespace MoonWorks.Math
|
||||||
return value;
|
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>
|
/// <summary>
|
||||||
/// Calculates the absolute value of the difference of two values.
|
/// Calculates the absolute value of the difference of two values.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -302,12 +282,7 @@ namespace MoonWorks.Math
|
||||||
|
|
||||||
public static float Quantize(float value, float step)
|
public static float Quantize(float value, float step)
|
||||||
{
|
{
|
||||||
return System.MathF.Round(value / step) * step;
|
return (float) System.Math.Floor(value / step) * step;
|
||||||
}
|
|
||||||
|
|
||||||
public static Fixed.Fix64 Quantize(Fixed.Fix64 value, Fixed.Fix64 step)
|
|
||||||
{
|
|
||||||
return Fixed.Fix64.Round(value / step) * step;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -420,6 +395,27 @@ namespace MoonWorks.Math
|
||||||
|
|
||||||
#region Internal Static Methods
|
#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)
|
internal static bool WithinEpsilon(float floatA, float floatB)
|
||||||
{
|
{
|
||||||
return System.Math.Abs(floatA - floatB) < MachineEpsilonFloat;
|
return System.Math.Abs(floatA - floatB) < MachineEpsilonFloat;
|
||||||
|
|
|
@ -97,12 +97,16 @@ namespace MoonWorks
|
||||||
|
|
||||||
// Get the path to the assembly
|
// Get the path to the assembly
|
||||||
Assembly assembly = Assembly.GetExecutingAssembly();
|
Assembly assembly = Assembly.GetExecutingAssembly();
|
||||||
string assemblyPath = System.AppContext.BaseDirectory;
|
string assemblyPath = "";
|
||||||
|
if (assembly.Location != null)
|
||||||
|
{
|
||||||
|
assemblyPath = Path.GetDirectoryName(assembly.Location);
|
||||||
|
}
|
||||||
|
|
||||||
// Locate the config file
|
// Locate the config file
|
||||||
string xmlPath = Path.Combine(
|
string xmlPath = Path.Combine(
|
||||||
assemblyPath,
|
assemblyPath,
|
||||||
"MoonWorks.dll.config"
|
assembly.GetName().Name + ".dll.config"
|
||||||
);
|
);
|
||||||
if (!File.Exists(xmlPath))
|
if (!File.Exists(xmlPath))
|
||||||
{
|
{
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
public enum ScreenMode
|
public enum ScreenMode
|
||||||
{
|
{
|
||||||
Fullscreen,
|
Fullscreen,
|
||||||
BorderlessFullscreen,
|
BorderlessWindow,
|
||||||
Windowed
|
Windowed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,45 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,120 @@
|
||||||
|
/* 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,89 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,82 +0,0 @@
|
||||||
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,26 +1,30 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Threading.Tasks;
|
using System.Runtime.InteropServices;
|
||||||
|
using MoonWorks.Audio;
|
||||||
using MoonWorks.Graphics;
|
using MoonWorks.Graphics;
|
||||||
|
|
||||||
namespace MoonWorks.Video
|
namespace MoonWorks.Video
|
||||||
{
|
{
|
||||||
/// <summary>
|
public unsafe class VideoPlayer : IDisposable
|
||||||
/// 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 Texture RenderTexture { get; private set; } = null;
|
||||||
public VideoState State { get; private set; } = VideoState.Stopped;
|
public VideoState State { get; private set; } = VideoState.Stopped;
|
||||||
public bool Loop { get; set; }
|
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;
|
public float PlaybackSpeed { get; set; } = 1;
|
||||||
|
|
||||||
private VideoAV1 Video = null;
|
private Video Video = null;
|
||||||
private VideoAV1Stream CurrentStream = null;
|
|
||||||
|
|
||||||
private Task ReadNextFrameTask;
|
|
||||||
private Task ResetStreamATask;
|
|
||||||
private Task ResetStreamBTask;
|
|
||||||
|
|
||||||
private GraphicsDevice GraphicsDevice;
|
private GraphicsDevice GraphicsDevice;
|
||||||
private Texture yTexture = null;
|
private Texture yTexture = null;
|
||||||
|
@ -28,26 +32,31 @@ namespace MoonWorks.Video
|
||||||
private Texture vTexture = null;
|
private Texture vTexture = null;
|
||||||
private Sampler LinearSampler;
|
private Sampler LinearSampler;
|
||||||
|
|
||||||
|
private void* yuvData = null;
|
||||||
|
private int yuvDataLength = 0;
|
||||||
|
|
||||||
private int currentFrame;
|
private int currentFrame;
|
||||||
|
|
||||||
|
private AudioDevice AudioDevice;
|
||||||
|
private StreamingSoundTheora audioStream = null;
|
||||||
|
private float volume = 1.0f;
|
||||||
|
|
||||||
private Stopwatch timer;
|
private Stopwatch timer;
|
||||||
private double lastTimestamp;
|
private double lastTimestamp;
|
||||||
private double timeElapsed;
|
private double timeElapsed;
|
||||||
|
|
||||||
public VideoPlayer(GraphicsDevice device) : base(device)
|
private bool disposed;
|
||||||
{
|
|
||||||
GraphicsDevice = device;
|
|
||||||
|
|
||||||
LinearSampler = new Sampler(device, SamplerCreateInfo.LinearClamp);
|
public VideoPlayer(GraphicsDevice graphicsDevice, AudioDevice audioDevice)
|
||||||
|
{
|
||||||
|
GraphicsDevice = graphicsDevice;
|
||||||
|
AudioDevice = audioDevice;
|
||||||
|
LinearSampler = new Sampler(graphicsDevice, SamplerCreateInfo.LinearClamp);
|
||||||
|
|
||||||
timer = new Stopwatch();
|
timer = new Stopwatch();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public void Load(Video video)
|
||||||
/// Prepares a VideoAV1 for decoding and rendering.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="video"></param>
|
|
||||||
public void Load(VideoAV1 video)
|
|
||||||
{
|
{
|
||||||
if (Video != video)
|
if (Video != video)
|
||||||
{
|
{
|
||||||
|
@ -97,19 +106,25 @@ namespace MoonWorks.Video
|
||||||
vTexture = CreateSubTexture(GraphicsDevice, video.UVWidth, video.UVHeight);
|
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;
|
Video = video;
|
||||||
|
|
||||||
InitializeDav1dStream();
|
InitializeTheoraStream();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Starts playing back and decoding the loaded video.
|
|
||||||
/// </summary>
|
|
||||||
public void Play()
|
public void Play()
|
||||||
{
|
{
|
||||||
if (Video == null) { return; }
|
|
||||||
|
|
||||||
if (State == VideoState.Playing)
|
if (State == VideoState.Playing)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
@ -117,16 +132,16 @@ namespace MoonWorks.Video
|
||||||
|
|
||||||
timer.Start();
|
timer.Start();
|
||||||
|
|
||||||
|
if (audioStream != null)
|
||||||
|
{
|
||||||
|
audioStream.Play();
|
||||||
|
}
|
||||||
|
|
||||||
State = VideoState.Playing;
|
State = VideoState.Playing;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Pauses playback and decoding of the currently playing video.
|
|
||||||
/// </summary>
|
|
||||||
public void Pause()
|
public void Pause()
|
||||||
{
|
{
|
||||||
if (Video == null) { return; }
|
|
||||||
|
|
||||||
if (State != VideoState.Playing)
|
if (State != VideoState.Playing)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
@ -134,16 +149,16 @@ namespace MoonWorks.Video
|
||||||
|
|
||||||
timer.Stop();
|
timer.Stop();
|
||||||
|
|
||||||
|
if (audioStream != null)
|
||||||
|
{
|
||||||
|
audioStream.Pause();
|
||||||
|
}
|
||||||
|
|
||||||
State = VideoState.Paused;
|
State = VideoState.Paused;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Stops and resets decoding of the currently playing video.
|
|
||||||
/// </summary>
|
|
||||||
public void Stop()
|
public void Stop()
|
||||||
{
|
{
|
||||||
if (Video == null) { return; }
|
|
||||||
|
|
||||||
if (State == VideoState.Stopped)
|
if (State == VideoState.Stopped)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
@ -152,28 +167,20 @@ namespace MoonWorks.Video
|
||||||
timer.Stop();
|
timer.Stop();
|
||||||
timer.Reset();
|
timer.Reset();
|
||||||
|
|
||||||
|
Theorafile.tf_reset(Video.Handle);
|
||||||
lastTimestamp = 0;
|
lastTimestamp = 0;
|
||||||
timeElapsed = 0;
|
timeElapsed = 0;
|
||||||
|
|
||||||
InitializeDav1dStream();
|
if (audioStream != null)
|
||||||
|
{
|
||||||
|
audioStream.StopImmediate();
|
||||||
|
audioStream.Dispose();
|
||||||
|
audioStream = null;
|
||||||
|
}
|
||||||
|
|
||||||
State = VideoState.Stopped;
|
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()
|
public void Render()
|
||||||
{
|
{
|
||||||
if (Video == null || State == VideoState.Stopped)
|
if (Video == null || State == VideoState.Stopped)
|
||||||
|
@ -187,39 +194,37 @@ namespace MoonWorks.Video
|
||||||
int thisFrame = ((int) (timeElapsed / (1000.0 / Video.FramesPerSecond)));
|
int thisFrame = ((int) (timeElapsed / (1000.0 / Video.FramesPerSecond)));
|
||||||
if (thisFrame > currentFrame)
|
if (thisFrame > currentFrame)
|
||||||
{
|
{
|
||||||
if (CurrentStream.FrameDataUpdated)
|
if (Theorafile.tf_readvideo(
|
||||||
{
|
Video.Handle,
|
||||||
|
(IntPtr) yuvData,
|
||||||
|
thisFrame - currentFrame
|
||||||
|
) == 1 || currentFrame == -1) {
|
||||||
UpdateRenderTexture();
|
UpdateRenderTexture();
|
||||||
CurrentStream.FrameDataUpdated = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
currentFrame = thisFrame;
|
currentFrame = thisFrame;
|
||||||
ReadNextFrameTask = Task.Run(CurrentStream.ReadNextFrame);
|
|
||||||
ReadNextFrameTask.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CurrentStream.Ended)
|
bool ended = Theorafile.tf_eos(Video.Handle) == 1;
|
||||||
|
if (ended)
|
||||||
{
|
{
|
||||||
timer.Stop();
|
timer.Stop();
|
||||||
timer.Reset();
|
timer.Reset();
|
||||||
|
|
||||||
var task = Task.Run(CurrentStream.Reset);
|
if (audioStream != null)
|
||||||
task.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
|
{
|
||||||
|
audioStream.Stop();
|
||||||
|
audioStream.Dispose();
|
||||||
|
audioStream = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (CurrentStream == Video.StreamA)
|
Theorafile.tf_reset(Video.Handle);
|
||||||
{
|
|
||||||
ResetStreamATask = task;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ResetStreamBTask = task;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Loop)
|
if (Loop)
|
||||||
{
|
{
|
||||||
// Start over on the next stream!
|
// Start over!
|
||||||
CurrentStream = (CurrentStream == Video.StreamA) ? Video.StreamB : Video.StreamA;
|
InitializeTheoraStream();
|
||||||
currentFrame = -1;
|
|
||||||
timer.Start();
|
timer.Start();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -231,40 +236,32 @@ namespace MoonWorks.Video
|
||||||
|
|
||||||
private void UpdateRenderTexture()
|
private void UpdateRenderTexture()
|
||||||
{
|
{
|
||||||
lock (CurrentStream)
|
var commandBuffer = GraphicsDevice.AcquireCommandBuffer();
|
||||||
{
|
|
||||||
var commandBuffer = GraphicsDevice.AcquireCommandBuffer();
|
|
||||||
|
|
||||||
commandBuffer.SetTextureDataYUV(
|
commandBuffer.SetTextureDataYUV(
|
||||||
yTexture,
|
yTexture,
|
||||||
uTexture,
|
uTexture,
|
||||||
vTexture,
|
vTexture,
|
||||||
CurrentStream.yDataHandle,
|
(IntPtr) yuvData,
|
||||||
CurrentStream.uDataHandle,
|
(uint) yuvDataLength
|
||||||
CurrentStream.vDataHandle,
|
);
|
||||||
CurrentStream.yDataLength,
|
|
||||||
CurrentStream.uvDataLength,
|
|
||||||
CurrentStream.yStride,
|
|
||||||
CurrentStream.uvStride
|
|
||||||
);
|
|
||||||
|
|
||||||
commandBuffer.BeginRenderPass(
|
commandBuffer.BeginRenderPass(
|
||||||
new ColorAttachmentInfo(RenderTexture, Color.Black)
|
new ColorAttachmentInfo(RenderTexture, Color.Black)
|
||||||
);
|
);
|
||||||
|
|
||||||
commandBuffer.BindGraphicsPipeline(GraphicsDevice.VideoPipeline);
|
commandBuffer.BindGraphicsPipeline(GraphicsDevice.VideoPipeline);
|
||||||
commandBuffer.BindFragmentSamplers(
|
commandBuffer.BindFragmentSamplers(
|
||||||
new TextureSamplerBinding(yTexture, LinearSampler),
|
new TextureSamplerBinding(yTexture, LinearSampler),
|
||||||
new TextureSamplerBinding(uTexture, LinearSampler),
|
new TextureSamplerBinding(uTexture, LinearSampler),
|
||||||
new TextureSamplerBinding(vTexture, 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)
|
private static Texture CreateRenderTexture(GraphicsDevice graphicsDevice, int width, int height)
|
||||||
|
@ -289,42 +286,53 @@ namespace MoonWorks.Video
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializeDav1dStream()
|
private void InitializeTheoraStream()
|
||||||
{
|
{
|
||||||
ReadNextFrameTask?.Wait();
|
// Grab the first video frame ASAP.
|
||||||
|
while (Theorafile.tf_readvideo(Video.Handle, (IntPtr) yuvData, 1) == 0);
|
||||||
|
|
||||||
ResetStreamATask = Task.Run(Video.StreamA.Reset);
|
// Grab the first bit of audio. We're trying to start the decoding ASAP.
|
||||||
ResetStreamATask.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
|
if (AudioDevice != null && Theorafile.tf_hasaudio(Video.Handle) == 1)
|
||||||
ResetStreamBTask = Task.Run(Video.StreamB.Reset);
|
{
|
||||||
ResetStreamBTask.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
|
int channels, sampleRate;
|
||||||
|
Theorafile.tf_audioinfo(Video.Handle, out channels, out sampleRate);
|
||||||
|
audioStream = new StreamingSoundTheora(AudioDevice, Video.Handle, channels, (uint) sampleRate);
|
||||||
|
}
|
||||||
|
|
||||||
CurrentStream = Video.StreamA;
|
|
||||||
currentFrame = -1;
|
currentFrame = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void HandleTaskException(Task task)
|
protected virtual void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
if (task.Exception.InnerException is not TaskCanceledException)
|
if (!disposed)
|
||||||
{
|
|
||||||
throw task.Exception;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
if (!IsDisposed)
|
|
||||||
{
|
{
|
||||||
if (disposing)
|
if (disposing)
|
||||||
{
|
{
|
||||||
Unload();
|
// dispose managed state (managed objects)
|
||||||
|
RenderTexture.Dispose();
|
||||||
RenderTexture?.Dispose();
|
yTexture.Dispose();
|
||||||
yTexture?.Dispose();
|
uTexture.Dispose();
|
||||||
uTexture?.Dispose();
|
vTexture.Dispose();
|
||||||
vTexture?.Dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// free unmanaged resources (unmanaged objects) and override finalizer
|
||||||
|
NativeMemory.Free(yuvData);
|
||||||
|
|
||||||
|
disposed = true;
|
||||||
}
|
}
|
||||||
base.Dispose(disposing);
|
}
|
||||||
|
|
||||||
|
~VideoPlayer()
|
||||||
|
{
|
||||||
|
// 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
namespace MoonWorks.Video
|
|
||||||
{
|
|
||||||
public enum VideoState
|
|
||||||
{
|
|
||||||
Playing,
|
|
||||||
Paused,
|
|
||||||
Stopped
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,78 +1,56 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using MoonWorks.Graphics;
|
|
||||||
using SDL2;
|
using SDL2;
|
||||||
|
|
||||||
namespace MoonWorks
|
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
|
public class Window : IDisposable
|
||||||
{
|
{
|
||||||
internal IntPtr Handle { get; }
|
internal IntPtr Handle { get; }
|
||||||
public ScreenMode ScreenMode { get; private set; }
|
public ScreenMode ScreenMode { get; private set; }
|
||||||
public uint Width { get; private set; }
|
public uint Width { get; private set; }
|
||||||
public uint Height { get; private set; }
|
public uint Height { get; private set; }
|
||||||
internal Texture SwapchainTexture { get; set; }
|
|
||||||
|
|
||||||
public bool Claimed { get; internal set; }
|
|
||||||
public MoonWorks.Graphics.TextureFormat SwapchainFormat { get; internal set; }
|
|
||||||
|
|
||||||
private bool IsDisposed;
|
private bool IsDisposed;
|
||||||
|
|
||||||
private static Dictionary<uint, Window> idLookup = new Dictionary<uint, Window>();
|
public Window(WindowCreateInfo windowCreateInfo)
|
||||||
|
|
||||||
private System.Action<uint, uint> SizeChangeCallback = null;
|
|
||||||
|
|
||||||
public Window(WindowCreateInfo windowCreateInfo, SDL.SDL_WindowFlags flags)
|
|
||||||
{
|
{
|
||||||
|
var windowFlags = SDL.SDL_WindowFlags.SDL_WINDOW_VULKAN;
|
||||||
|
|
||||||
if (windowCreateInfo.ScreenMode == ScreenMode.Fullscreen)
|
if (windowCreateInfo.ScreenMode == ScreenMode.Fullscreen)
|
||||||
{
|
{
|
||||||
flags |= SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN;
|
windowFlags |= SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN;
|
||||||
}
|
}
|
||||||
else if (windowCreateInfo.ScreenMode == ScreenMode.BorderlessFullscreen)
|
else if (windowCreateInfo.ScreenMode == ScreenMode.BorderlessWindow)
|
||||||
{
|
{
|
||||||
flags |= SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP;
|
windowFlags |= SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (windowCreateInfo.SystemResizable)
|
if (windowCreateInfo.SystemResizable)
|
||||||
{
|
{
|
||||||
flags |= SDL.SDL_WindowFlags.SDL_WINDOW_RESIZABLE;
|
windowFlags |= SDL.SDL_WindowFlags.SDL_WINDOW_RESIZABLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (windowCreateInfo.StartMaximized)
|
if (windowCreateInfo.StartMaximized)
|
||||||
{
|
{
|
||||||
flags |= SDL.SDL_WindowFlags.SDL_WINDOW_MAXIMIZED;
|
windowFlags |= SDL.SDL_WindowFlags.SDL_WINDOW_MAXIMIZED;
|
||||||
}
|
}
|
||||||
|
|
||||||
ScreenMode = windowCreateInfo.ScreenMode;
|
ScreenMode = windowCreateInfo.ScreenMode;
|
||||||
|
|
||||||
SDL.SDL_GetDesktopDisplayMode(0, out var displayMode);
|
|
||||||
|
|
||||||
Handle = SDL.SDL_CreateWindow(
|
Handle = SDL.SDL_CreateWindow(
|
||||||
windowCreateInfo.WindowTitle,
|
windowCreateInfo.WindowTitle,
|
||||||
SDL.SDL_WINDOWPOS_CENTERED,
|
SDL.SDL_WINDOWPOS_UNDEFINED,
|
||||||
SDL.SDL_WINDOWPOS_CENTERED,
|
SDL.SDL_WINDOWPOS_UNDEFINED,
|
||||||
windowCreateInfo.ScreenMode == ScreenMode.Windowed ? (int) windowCreateInfo.WindowWidth : displayMode.w,
|
(int) windowCreateInfo.WindowWidth,
|
||||||
windowCreateInfo.ScreenMode == ScreenMode.Windowed ? (int) windowCreateInfo.WindowHeight : displayMode.h,
|
(int) windowCreateInfo.WindowHeight,
|
||||||
flags
|
windowFlags
|
||||||
);
|
);
|
||||||
|
|
||||||
/* Requested size might be different in fullscreen, so let's just get the area */
|
Width = windowCreateInfo.WindowWidth;
|
||||||
SDL.SDL_GetWindowSize(Handle, out var width, out var height);
|
Height = windowCreateInfo.WindowHeight;
|
||||||
Width = (uint) width;
|
|
||||||
Height = (uint) height;
|
|
||||||
|
|
||||||
idLookup.Add(SDL.SDL_GetWindowID(Handle), this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public void ChangeScreenMode(ScreenMode screenMode)
|
||||||
/// Changes the ScreenMode of this window.
|
|
||||||
/// </summary>
|
|
||||||
public void SetScreenMode(ScreenMode screenMode)
|
|
||||||
{
|
{
|
||||||
SDL.SDL_WindowFlags windowFlag = 0;
|
SDL.SDL_WindowFlags windowFlag = 0;
|
||||||
|
|
||||||
|
@ -80,23 +58,18 @@ namespace MoonWorks
|
||||||
{
|
{
|
||||||
windowFlag = SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN;
|
windowFlag = SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN;
|
||||||
}
|
}
|
||||||
else if (screenMode == ScreenMode.BorderlessFullscreen)
|
else if (screenMode == ScreenMode.BorderlessWindow)
|
||||||
{
|
{
|
||||||
windowFlag = SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP;
|
windowFlag = SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL.SDL_SetWindowFullscreen(Handle, (uint) windowFlag);
|
|
||||||
|
|
||||||
if (screenMode == ScreenMode.Windowed)
|
|
||||||
{
|
|
||||||
SDL.SDL_SetWindowPosition(Handle, SDL.SDL_WINDOWPOS_CENTERED, SDL.SDL_WINDOWPOS_CENTERED);
|
|
||||||
}
|
|
||||||
|
|
||||||
ScreenMode = screenMode;
|
ScreenMode = screenMode;
|
||||||
|
|
||||||
|
SDL.SDL_SetWindowFullscreen(Handle, (uint) windowFlag);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Resizes the window. <br/>
|
/// Resizes the window.
|
||||||
/// Note that you are responsible for recreating any graphics resources that need to change as a result of the size change.
|
/// Note that you are responsible for recreating any graphics resources that need to change as a result of the size change.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="width"></param>
|
/// <param name="width"></param>
|
||||||
|
@ -106,40 +79,12 @@ namespace MoonWorks
|
||||||
SDL.SDL_SetWindowSize(Handle, (int) width, (int) height);
|
SDL.SDL_SetWindowSize(Handle, (int) width, (int) height);
|
||||||
Width = width;
|
Width = width;
|
||||||
Height = height;
|
Height = height;
|
||||||
|
|
||||||
if (ScreenMode == ScreenMode.Windowed)
|
|
||||||
{
|
|
||||||
SDL.SDL_SetWindowPosition(Handle, SDL.SDL_WINDOWPOS_CENTERED, SDL.SDL_WINDOWPOS_CENTERED);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static Window Lookup(uint windowID)
|
internal void SizeChanged(uint width, uint height)
|
||||||
{
|
|
||||||
return idLookup.ContainsKey(windowID) ? idLookup[windowID] : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void Show()
|
|
||||||
{
|
|
||||||
SDL.SDL_ShowWindow(Handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void HandleSizeChange(uint width, uint height)
|
|
||||||
{
|
{
|
||||||
Width = width;
|
Width = width;
|
||||||
Height = height;
|
Height = height;
|
||||||
|
|
||||||
if (SizeChangeCallback != null)
|
|
||||||
{
|
|
||||||
SizeChangeCallback(width, height);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// You can specify a method to run when the window size changes.
|
|
||||||
/// </summary>
|
|
||||||
public void RegisterSizeChangeCallback(System.Action<uint, uint> sizeChangeCallback)
|
|
||||||
{
|
|
||||||
SizeChangeCallback = sizeChangeCallback;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void Dispose(bool disposing)
|
protected virtual void Dispose(bool disposing)
|
||||||
|
@ -151,7 +96,6 @@ namespace MoonWorks
|
||||||
// dispose managed state (managed objects)
|
// dispose managed state (managed objects)
|
||||||
}
|
}
|
||||||
|
|
||||||
idLookup.Remove(SDL.SDL_GetWindowID(Handle));
|
|
||||||
SDL.SDL_DestroyWindow(Handle);
|
SDL.SDL_DestroyWindow(Handle);
|
||||||
|
|
||||||
IsDisposed = true;
|
IsDisposed = true;
|
||||||
|
|
|
@ -1,37 +1,12 @@
|
||||||
namespace MoonWorks
|
namespace MoonWorks
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// All the information required for window creation.
|
|
||||||
/// </summary>
|
|
||||||
public struct WindowCreateInfo
|
public struct WindowCreateInfo
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// The name of the window that will be displayed in the operating system.
|
|
||||||
/// </summary>
|
|
||||||
public string WindowTitle;
|
public string WindowTitle;
|
||||||
/// <summary>
|
|
||||||
/// The width of the window.
|
|
||||||
/// </summary>
|
|
||||||
public uint WindowWidth;
|
public uint WindowWidth;
|
||||||
/// <summary>
|
|
||||||
/// The height of the window.
|
|
||||||
/// </summary>
|
|
||||||
public uint WindowHeight;
|
public uint WindowHeight;
|
||||||
/// <summary>
|
|
||||||
/// Specifies if the window will be created in windowed mode or a fullscreen mode.
|
|
||||||
/// </summary>
|
|
||||||
public ScreenMode ScreenMode;
|
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;
|
public bool SystemResizable;
|
||||||
/// <summary>
|
|
||||||
/// Specifies if the window will open at the maximum desktop resolution.
|
|
||||||
/// </summary>
|
|
||||||
public bool StartMaximized;
|
public bool StartMaximized;
|
||||||
|
|
||||||
public WindowCreateInfo(
|
public WindowCreateInfo(
|
||||||
|
@ -39,7 +14,6 @@
|
||||||
uint windowWidth,
|
uint windowWidth,
|
||||||
uint windowHeight,
|
uint windowHeight,
|
||||||
ScreenMode screenMode,
|
ScreenMode screenMode,
|
||||||
PresentMode presentMode,
|
|
||||||
bool systemResizable = false,
|
bool systemResizable = false,
|
||||||
bool startMaximized = false
|
bool startMaximized = false
|
||||||
) {
|
) {
|
||||||
|
@ -47,7 +21,6 @@
|
||||||
WindowWidth = windowWidth;
|
WindowWidth = windowWidth;
|
||||||
WindowHeight = windowHeight;
|
WindowHeight = windowHeight;
|
||||||
ScreenMode = screenMode;
|
ScreenMode = screenMode;
|
||||||
PresentMode = presentMode;
|
|
||||||
SystemResizable = systemResizable;
|
SystemResizable = systemResizable;
|
||||||
StartMaximized = startMaximized;
|
StartMaximized = startMaximized;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue